No KYC — no ID, no selfie, no documents. Pay in crypto: BTC, USDT, USDC, ETH, SOL, LTC
Language: English
Get an API key

Docs · OpenAI · checked on 22 October 2026

OpenAI function calling: give the model a search_web tool

Describe a search_web function to the model, run the call it asks for against our API, and send the sources back. The model then answers with citations. This page shows the JSON schema, the Responses API loop and the shorter OpenAI Agents SDK version.

Last updated: · 5 min read

Quick answer

How do I add web search to an OpenAI model with function calling?

Declare a search_web function with a JSON schema, and when the model calls it, POST the query to LLMWebSearch's https://llmwebsearch.com/api/v1/search. Return the numbered sources, with the top 3 pages as Markdown, as a function_call_output; the model answers and cites them. A search costs 1 credit, from $1.20/1K searches as of October 2026. No KYC; pay in crypto.

What you need

Packagesopenai and requests (or openai-agents for the Agents SDK version)
KeysOPENAI_API_KEY for the model, LLMWEBSEARCH_API_KEY for search
ModelAny model that supports function calling, set in OPENAI_MODEL
API usedResponses API: client.responses.create with tools and input
Billing on our sideOne credit pays for one search: up to 20 ranked results, with the pages of the first 3 read and returned as Markdown. Reading 4 to 10 pages makes it 2 credits. A call that ends in an error costs nothing

What does the tool schema look like?

A function tool has a name, a description and JSON Schema parameters. Keep the description concrete: what the tool returns, and that sources should be cited by number. Two parameters are enough for most agents: the query, and how many top results to read in full.

The model never sees your API key. It only proposes arguments; your code decides how to call the API and what to send back.

How does the call loop work?

Send the user's message with the tools. If the response holds items of type function_call, parse each one's JSON arguments, run the search, and append a function_call_output with the same call_id. Then call the model again with the longer input list. When a response contains no function call, its text is the answer.

The loop below stops after five rounds, a guard against a model that keeps searching. OpenAI's function-calling guide describes the same function_call and function_call_output items, read on 22 October 2026.

search_tool_loop.py (Responses API)
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})

Is there a shorter version with the Agents SDK?

Yes. The OpenAI Agents SDK turns a Python function into a tool with a decorator and runs the call loop for you. Its tools documentation, read on 22 October 2026, shows the @tool decorator from agents.decorators, with the description taken from the docstring and each argument's description from its Args section. The same page also mentions the @function_tool form.

Runner.run_sync runs the agent until it produces a final answer, calling search_web as often as the model asks.

search_agent.py (OpenAI Agents SDK)
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)

Mostly for control over what the model reads and what it costs. Our tool hands your code the raw sources, with URLs and page text, so you decide what enters the context, can log it and can store it. We do not train any model on your queries or on what the API returns, and we add no rule of our own against storing results; the rights of the sites whose pages you read still apply.

The price is also fixed per search instead of depending on how much retrieved content the model reads. The built-in tools comparison puts the per-call prices of OpenAI, Anthropic, Google and others side by side, with dates.

How do I stop the model from searching too much?

Say it in the instructions: search once, read the sources, search again only if they disagree or miss the point. Cap the number of rounds in your own loop, as the script above does with five.

Keep max_chars modest. Three pages of 5,000 characters is about 4,000 tokens per search, and each round adds them to the input of the next model call.

OpenAI function calling: common questions

Does this work with the Chat Completions API?

Yes, with the older tools format of Chat Completions, where the function sits inside a function object and results go back as tool messages. The search function itself does not change.

Can the model read a page it finds in a source?

Add a second function, read_urls, that posts URLs to https://llmwebsearch.com/api/v1/extract. Reading pages whose URLs you already have costs 1 credit for each started group of 3 pages returned with content, up to 20 URLs per call; refused or failed URLs are not counted.

Which model should I use?

Any model that supports function calling. Try two or three on your own questions: the answer depends on the sources as much as on the model, and a smaller model costs less per round.

Sources (2)
  1. Function calling — OpenAI API docs (read 2026-10-22)
  2. Tools (OpenAI Agents SDK) — OpenAI Agents SDK docs (read 2026-10-22)

From $1.20/1K searches · no KYC

Get an API key