クイック回答
function calling で OpenAI モデルにWeb検索を追加するには?
JSON schema でsearch_web関数を宣言し、モデルがそれを呼び出したら検索クエリを LLMWebSearch のhttps://llmwebsearch.com/api/v1/searchに POST します。番号付きのソースを、上位3ページを Markdown としてfunction_call_outputで返します。モデルはそれらを引用して回答します。検索1回で1クレジット、2026年10月時点の$1.20/1K検索から。KYC不要、暗号資産で支払い。
必要なもの
| パッケージ | openaiとrequests(Agents SDK 版はopenai-agents) |
|---|---|
| キー | モデル用にOPENAI_API_KEY、検索用にLLMWEBSEARCH_API_KEY |
| モデル | function calling をサポートする任意のモデル。OPENAI_MODELで設定 |
| 使用API | Responses API:client.responses.createをtoolsとinputで |
| 当社側の課金 | 1クレジットで検索1回分です。最大20件のランク付けされた結果を取得でき、上位3ページは本文抽出されてMarkdownで返されます。4〜10ページを読む場合は2クレジットです。エラーで終わった呼び出しは無料です |
ツールスキーマはどのような形?
関数ツールにはname、description、そして JSON Schema のparametersがあります。説明は具体的に:ツールが何を返すか、そしてソースは番号で引用すべきこと。ほとんどのエージェントには2つのパラメータで十分です:queryと、上位何件の結果を全文でreadか。
モデルがAPIキーを見ることはありません。モデルは引数を提案するだけで、APIをどう呼び、何を返すかはコードが決めます。
呼び出しループはどう動く?
ツールを付けてユーザーのメッセージを送ります。レスポンスに型がfunction_callの項目が含まれていれば、それぞれの JSON argumentsを解析し、検索を実行し、同じcall_idを持つfunction_call_outputを追加します。そして、より長くなった入力リストでモデルを再度呼びます。レスポンスに関数呼び出しが含まれなければ、そのテキストが回答です。
以下のループは5ラウンド後に停止します。検索し続けるモデルへのガードです。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日閲覧のツールドキュメントには、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の組み込みWeb検索ではなくAPIツールを使う理由は?
主に、モデルが何を読み、いくらかかるかを制御するためです。当社のツールは生のソースをURLとページ本文とともにコードに渡すので、何をコンテキストに入れるかを自分で決められ、ログに残し、保存することもできます。当社はあなたの検索クエリやAPIが返す内容でモデルを学習させることはなく、結果の保存を禁じる独自のルールも設けていません。ただし、あなたが読むページのサイトの権利は引き続き適用されます。
また、料金は取得したコンテンツをモデルがどれだけ読むかに依存せず、検索ごとに固定です。組み込みツール比較では、OpenAI、Anthropic、Googleなどの1回あたりの料金を、確認日とともに並べて比較しています。
モデルが検索しすぎるのを防ぐには?
instructionsに書きます。1回検索し、ソースを読み、それらが食い違うか要点を外している場合にのみ再検索する、と。自分のループでラウンド数を上限設定します。上のスクリプトでは5にしています。
max_charsは控えめに保ちます。5,000文字のページ3枚で1回の検索あたり約4,000トークンになり、各ラウンドでそれが次のモデル呼び出しの入力に加算されます。
OpenAI function calling:よくある質問
Chat Completions APIで動作しますか?
はい、Chat Completionsの従来のtools形式で動作します。functionはfunctionオブジェクトの中に置き、結果はtoolメッセージとして返します。検索関数自体は変わりません。
モデルはソース内で見つけたページを読めますか?
2つ目の関数read_urlsを追加し、URLをhttps://llmwebsearch.com/api/v1/extractにPOSTします。URLをすでに持っているページを読む場合は、コンテンツ付きで返される3ページごとの開始グループにつき1クレジットで、1回の呼び出しにつき最大20 URLです。拒否されたURLや失敗したURLはカウントされません。
どのモデルを使えばいいですか?
function callingをサポートするモデルならどれでも。自分の質問で2〜3試してください。答えはモデルと同じくらいソースにも左右され、小さいモデルほど1ラウンドあたりのコストが低くなります。
出典 (2)
- Function calling — OpenAI API docs (read 2026-10-22)
- ツール(OpenAI Agents SDK) — OpenAI Agents SDK docs (read 2026-10-22)