빠른 답변
OpenAI 모델에 function calling으로 웹 검색을 추가하려면 어떻게 하나요?
JSON 스키마로 search_web 함수를 선언하고, 모델이 이를 호출하면 검색 쿼리를 LLMWebSearch의 https://llmwebsearch.com/api/v1/search로 POST하세요. 상위 3개 페이지를 Markdown으로 담은 번호 매긴 출처를 function_call_output로 반환하면, 모델이 답변하고 이를 인용합니다. 검색 한 번에 크레딧 1이 들며, 2026년 10월 기준 US$1.20/1K 검색부터 시작합니다. KYC 없이, 암호화폐로 결제.
필요한 것
| 패키지 | openai 및 requests (Agents SDK 버전은 openai-agents) |
|---|---|
| 키 | 모델용 OPENAI_API_KEY, 검색용 LLMWEBSEARCH_API_KEY |
| 모델 | function calling을 지원하는 모든 모델, OPENAI_MODEL에서 설정 |
| 사용하는 API | Responses API: tools 및 input와 함께 client.responses.create |
| 우리 쪽 과금 | 크레딧 1개로 검색 1회를 이용할 수 있습니다. 최대 20개의 순위 결과를 받고, 상위 3개 페이지의 본문을 읽어 Markdown으로 반환합니다. 4~10개 페이지를 읽으면 크레딧 2개가 됩니다. 오류로 끝난 호출에는 비용이 청구되지 않습니다 |
도구 스키마는 어떻게 생겼나요?
함수 도구에는 name, description, 그리고 JSON Schema parameters가 있습니다. 설명은 구체적으로 유지하세요. 도구가 무엇을 반환하는지, 그리고 출처는 번호로 인용해야 한다는 점을 담으세요. 대부분의 에이전트에는 파라미터 두 개면 충분합니다. query, 그리고 상위 결과 몇 개를 전체로 read할지입니다.
모델은 여러분의 API 키를 절대 보지 않습니다. 모델은 인자만 제안하며, API를 어떻게 호출하고 무엇을 돌려보낼지는 여러분의 코드가 결정합니다.
호출 루프는 어떻게 작동하나요?
사용자의 메시지를 도구와 함께 보내세요. 응답에 function_call 유형의 항목이 있으면 각 항목의 JSON arguments을 파싱하고, 검색을 실행한 뒤, 동일한 call_id를 담은 function_call_output를 추가하세요. 그런 다음 더 길어진 입력 목록으로 모델을 다시 호출하세요. 응답에 함수 호출이 없으면 그 텍스트가 답변입니다.
아래 루프는 다섯 라운드 후에 중단되며, 계속 검색하는 모델에 대한 방어 장치입니다. OpenAI의 function-calling 가이드도 동일한 function_call 및 function_call_output 항목을 설명하며, 2026년 10월 22일에 확인했습니다.
import json
import os
import requests
from openai import OpenAI
client = OpenAI()
MODEL = os.environ["OPENAI_MODEL"] # any model that supports function calling
SEARCH = "https://llmwebsearch.com/api/v1/search"
tools = [
{
"type": "function",
"name": "search_web",
"description": "Search the live web. Returns numbered sources with URLs; the first `read` include page text. Cite sources as [1], [2].",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "What to search for, as a person would type it."},
"read": {"type": "integer", "minimum": 0, "maximum": 10, "description": "How many top results to read in full."},
},
"required": ["query"],
"additionalProperties": False,
},
}
]
def search_web(query, read=3):
resp = requests.post(
SEARCH,
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": query, "max_results": 5, "read": read, "max_chars": 5000},
timeout=60,
)
if resp.status_code != 200:
return "Search failed: " + resp.json().get("error", {}).get("code", str(resp.status_code))
parts = []
for i, r in enumerate(resp.json()["results"], start=1):
text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
return "\n\n".join(parts)
input_list = [{"role": "user", "content": "How should I size a Postgres connection pool?"}]
for _ in range(5):
response = client.responses.create(model=MODEL, tools=tools, input=input_list)
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
print(response.output_text)
break
input_list += response.output
for item in calls:
args = json.loads(item.arguments)
output = search_web(args["query"], int(args.get("read", 3)))
input_list.append({"type": "function_call_output", "call_id": item.call_id, "output": output})
Agents SDK로 더 짧은 버전이 있나요?
있습니다. OpenAI Agents SDK는 데코레이터로 Python 함수를 도구로 바꾸고 호출 루프를 대신 실행합니다. 2026년 10월 22일에 확인한 이 SDK의 tools 문서에는 agents.decorators의 @tool 데코레이터가 나오며, 설명은 docstring에서, 각 인자의 설명은 해당 Args 섹션에서 가져옵니다. 같은 페이지에서 @function_tool 형식도 언급합니다.
Runner.run_sync는 에이전트가 최종 답변을 낼 때까지 실행하며, 모델이 요청하는 만큼 search_web를 호출합니다.
import os
import requests
from agents import Agent, Runner
from agents.decorators import tool
@tool
def search_web(query: str, read: int = 3) -> str:
"""Search the live web and return numbered sources with their URLs.
Args:
query: What to search for, as a person would type it.
read: How many top results to read in full, from 0 to 10.
"""
resp = requests.post(
"https://llmwebsearch.com/api/v1/search",
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": query, "max_results": 5, "read": max(0, min(read, 10)), "max_chars": 5000},
timeout=60,
)
resp.raise_for_status()
parts = []
for i, r in enumerate(resp.json()["results"], start=1):
text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
return "\n\n".join(parts)
agent = Agent(
name="Researcher",
instructions="Answer from search_web results only and cite them as [1], [2].",
tools=[search_web],
)
result = Runner.run_sync(agent, "How should I size a Postgres connection pool?")
print(result.final_output)
OpenAI의 내장 웹 검색 대신 API 도구를 사용하는 이유는 무엇인가요?
주로 모델이 무엇을 읽고 비용이 얼마나 드는지를 제어하기 위해서입니다. 저희 도구는 URL과 페이지 텍스트가 포함된 원본 소스를 코드에 전달하므로, 무엇이 컨텍스트에 들어갈지 직접 결정하고, 로그로 남기고, 저장할 수 있습니다. 저희는 귀하의 검색 쿼리나 API가 반환하는 내용으로 어떤 모델도 학습시키지 않으며, 결과 저장을 금지하는 자체 규칙도 두지 않습니다. 다만 귀하가 읽는 페이지의 사이트 권리는 여전히 적용됩니다.
또한 가격은 모델이 검색한 콘텐츠를 얼마나 읽는지에 따라 달라지는 대신 검색당 고정입니다. 내장 도구 비교에서는 OpenAI, Anthropic, Google 등의 호출당 가격을 날짜와 함께 나란히 볼 수 있습니다.
모델이 너무 많이 검색하지 않게 하려면 어떻게 하나요?
지침에 명시하세요: 한 번 검색하고, 소스를 읽고, 소스가 서로 다르거나 핵심을 놓쳤을 때만 다시 검색하라고요. 위 스크립트에서 다섯으로 한 것처럼 자체 루프에서 라운드 수를 제한하세요.
max_chars을 적당히 유지하세요. 5,000자의 페이지 세 개는 검색당 약 4,000 토큰이며, 각 라운드는 이를 다음 모델 호출의 입력에 더합니다.
OpenAI 함수 호출: 자주 묻는 질문
Chat Completions API와 함께 작동하나요?
네, 함수가 function 객체 안에 있고 결과가 tool 메시지로 돌아오는 Chat Completions의 이전 tools 형식과 함께 작동합니다. 검색 함수 자체는 바뀌지 않습니다.
모델이 소스에서 찾은 페이지를 읽을 수 있나요?
URL을 https://llmwebsearch.com/api/v1/extract으로 전송하는 두 번째 함수 read_urls를 추가하세요. 이미 URL을 알고 있는 페이지를 읽는 경우, 콘텐츠와 함께 반환되는 3개 페이지 단위로 크레딧 1개가 청구되며, 호출당 최대 20개 URL까지 가능합니다. 거부되거나 실패한 URL은 집계되지 않습니다.
어떤 모델을 써야 하나요?
함수 호출을 지원하는 모델이면 됩니다. 직접 만든 질문으로 두세 개를 시험해 보세요: 답변은 모델만큼이나 소스에 달려 있으며, 작은 모델은 라운드당 비용이 더 적습니다.
출처 (2)
- 함수 호출 — OpenAI API docs (read 2026-10-22)
- 도구 (OpenAI Agents SDK) — OpenAI Agents SDK docs (read 2026-10-22)