Risposta rapida
Come aggiungo la ricerca web a un modello OpenAI con function calling?
Dichiara una funzione search_web con uno schema JSON e, quando il modello la chiama, invia la query al https://llmwebsearch.com/api/v1/search di LLMWebSearch con POST. Restituisci le fonti numerate, con le prime 3 pagine in Markdown, come function_call_output; il modello risponde e le cita. Una ricerca costa 1 credito, a partire da 1,20 USD/1K ricerche a ottobre 2026. Senza KYC; paga in criptovalute.
Cosa ti serve
| Pacchetti | openai e requests (o openai-agents per la versione con Agents SDK) |
|---|---|
| Chiavi | OPENAI_API_KEY per il modello, LLMWEBSEARCH_API_KEY per la ricerca |
| Modello | Qualsiasi modello che supporti function calling, impostato in OPENAI_MODEL |
| API usata | Responses API: client.responses.create con tools e input |
| Fatturazione dalla nostra parte | Un credito paga una ricerca: fino a 20 risultati classificati, con le pagine dei primi 3 lette e restituite come Markdown. Leggere da 4 a 10 pagine costa 2 crediti. Una chiamata che termina in errore non costa nulla |
Come appare lo schema del tool?
Un function tool ha un name, una description e una parameters JSON Schema. Mantieni la descrizione concreta: cosa restituisce il tool e che le fonti vanno citate per numero. Due parametri bastano per la maggior parte degli agenti: la query e quanti risultati principali read per intero.
Il modello non vede mai la tua chiave API. Propone solo argomenti; il tuo codice decide come chiamare l'API e cosa inviare indietro.
Come funziona il loop di chiamata?
Invia il messaggio dell'utente con i tool. Se la risposta contiene elementi di tipo function_call, analizza il JSON arguments di ciascuno, esegui la ricerca e aggiungi un function_call_output con lo stesso call_id. Poi chiama di nuovo il modello con la lista di input più lunga. Quando una risposta non contiene alcuna chiamata di funzione, il suo testo è la risposta.
Il loop qui sotto si ferma dopo cinque round, una protezione contro un modello che continua a cercare. La guida al function calling di OpenAI descrive gli stessi elementi function_call e function_call_output, letta il 22 ottobre 2026.
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})
Esiste una versione più breve con l'Agents SDK?
Sì. L'OpenAI Agents SDK trasforma una funzione Python in un tool con un decorator ed esegue il loop di chiamata per te. La sua documentazione dei tool, letta il 22 ottobre 2026, mostra il decorator @tool da agents.decorators, con la descrizione presa dal docstring e la descrizione di ogni argomento dalla sua sezione Args. La stessa pagina menziona anche la forma @function_tool.
Runner.run_sync esegue l'agente finché non produce una risposta finale, chiamando search_web tutte le volte che il modello richiede.
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)
Perché usare un tool API invece della ricerca web integrata di OpenAI?
Soprattutto per avere il controllo su ciò che il modello legge e su quanto costa. Il nostro tool consegna al tuo codice le fonti grezze, con URL e testo della pagina, così decidi tu cosa entra nel contesto, puoi registrarlo nei log e salvarlo. Non addestriamo alcun modello sulle tue query né su ciò che l'API restituisce, e non aggiungiamo regole nostre contro la conservazione dei risultati; restano validi i diritti dei siti di cui leggi le pagine.
Inoltre il prezzo è fisso per ricerca invece di dipendere da quanto contenuto recuperato legge il modello. Il confronto dei tool integrati mette a confronto i prezzi per chiamata di OpenAI, Anthropic, Google e altri, con le date.
Come faccio a impedire al modello di cercare troppo?
Dillo nelle istruzioni: cerca una volta, leggi le fonti, cerca di nuovo solo se sono in disaccordo o non colgono il punto. Limita il numero di round nel tuo ciclo, come fa lo script qui sopra con cinque.
Mantieni max_chars contenuto. Tre pagine da 5.000 caratteri sono circa 4.000 token per ricerca, e ogni round li aggiunge all'input della chiamata successiva al modello.
OpenAI function calling: domande frequenti
Funziona con l'API Chat Completions?
Sì, con il vecchio formato tools di Chat Completions, in cui la funzione sta dentro un oggetto function e i risultati tornano come messaggi tool. La funzione di ricerca in sé non cambia.
Il modello può leggere una pagina che trova in una fonte?
Aggiungi una seconda funzione, read_urls, che invia gli URL a https://llmwebsearch.com/api/v1/extract. Leggere pagine di cui hai già gli URL costa 1 credito per ogni gruppo iniziato di 3 pagine restituite con contenuto, fino a 20 URL per chiamata; gli URL rifiutati o falliti non vengono contati.
Quale modello dovrei usare?
Qualsiasi modello che supporti il function calling. Provane due o tre sulle tue domande: la risposta dipende dalle fonti tanto quanto dal modello, e un modello più piccolo costa meno per round.
Fonti (2)
- Function calling — OpenAI API docs (read 2026-10-22)
- Tool (OpenAI Agents SDK) — OpenAI Agents SDK docs (read 2026-10-22)