Risposta rapida
Come faccio a far leggere pagine web a un modello OpenAI con il function calling?
Dichiara una funzione read_url nella lista tools, poi esegui un loop: quando il modello restituisce una tool call, chiama l'endpoint /read di WebReaderAPI con l'URL e rimanda il Markdown indietro come messaggio tool. Ogni pagina costa 1 credito, 2 con un browser, a partire da 0,07 USD/1K pagine a ottobre 2026. Senza KYC; paghi in criptovalute.
Come è fatto lo schema di read_url?
Bastano due parametri: l'url da leggere, obbligatorio, e un max_chars opzionale che permette al modello di accorciare una pagina lunga. La description è la parte più importante: dice al modello cosa restituisce il tool e quando usarlo. Tienila breve e fattuale.
Chiamiamo il tool read_url invece di web_fetch per evitare conflitti con tool che alcuni runtime degli agenti forniscono già con quel nome.
{
"type": "function",
"function": {
"name": "read_url",
"description": "Read a public web page and return its main content as Markdown, with its title and source URL. Use it when the user gives a link or when a page must be read to answer.",
"parameters": {
"type": "object",
"properties": {
"url": { "type": "string", "description": "The http or https address of the page." },
"max_chars": { "type": "integer", "description": "Most characters to return, 1000 to 500000. Default 20000." }
},
"required": ["url"],
"additionalProperties": false
}
}
}Come funziona il loop di tool calling?
Invia la conversazione con la lista tools. Se la risposta contiene tool call, eseguine ciascuna, aggiungi il risultato come messaggio con role tool e il relativo tool_call_id, e interroga di nuovo il modello. Quando arriva una risposta senza tool call, quella è la risposta finale.
Limita il numero di round. Tre o quattro bastano per una domanda su qualche pagina, e il limite ti protegge da un modello che continua a chiedere altro.
import json
import os
import requests
from openai import OpenAI
READ_URL = {
"type": "function",
"function": {
"name": "read_url",
"description": "Read a public web page and return its main content as Markdown, with its title and source URL.",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "The http or https address of the page."},
"max_chars": {"type": "integer", "description": "Most characters to return. Default 20000."},
},
"required": ["url"],
"additionalProperties": False,
},
},
}
def read_url(url, max_chars=20000):
resp = requests.get(
"https://webreaderapi.com/api/v1/read",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
params={"url": url, "max_chars": max_chars},
timeout=75,
)
body = resp.json()
if resp.status_code != 200:
return "Could not read the page: " + body["error"]["code"]
return "# " + (body.get("title") or url) + "\nSource: " + body["final_url"] + "\n\n" + body["markdown"]
client = OpenAI()
model = os.environ.get("OPENAI_MODEL", "gpt-4.1-mini")
messages = [{"role": "user", "content": "What does https://example.com/blog/first-post announce? Answer in two sentences."}]
for _ in range(4):
reply = client.chat.completions.create(model=model, messages=messages, tools=[READ_URL])
msg = reply.choices[0].message
if not msg.tool_calls:
print(msg.content)
break
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({"role": "tool", "tool_call_id": call.id, "content": read_url(**args)})
Come tengo bassi l'uso di token e i costi?
Tre impostazioni contano. Mantieni max_chars contenuto, intorno agli 8.000–20.000 caratteri per pagina, perché ogni carattere che restituisci viene letto dal modello. Lascia il valore predefinito main_content=true, così menu e footer non arrivano mai nel contesto. E limita il loop, come sopra, così una singola domanda non può scatenare decine di letture.
Dal nostro lato, il costo è per pagina, non per carattere: 1 credito = 1 pagina letta senza browser; 2 crediti quando la pagina richiede un browser per eseguire JavaScript. Una richiesta che fallisce, o che il robots.txt di un sito rifiuta, non viene addebitata. Un support assistant che legge 300 pagine al giorno consuma circa 9.000 crediti al mese, dentro 10.000 pagine (3,75 USD).
Lo schema funziona con altri modelli?
Sì, con piccole modifiche nel wrapper. La maggior parte delle API con tool calling accetta un JSON Schema per i parametri e un nome e una description per il tool, quindi read_url si sposta tra provider così com'è. La pagina MCP impacchetta la stessa funzione per i client che parlano il Model Context Protocol, e la pagina LangChain la incapsula per gli agenti LangChain e LlamaIndex.
Cosa deve vedere il modello quando una pagina viene rifiutata?
Una frase breve e chiara, come quella restituita dal loop qui sopra: "Could not read the page: blocked_by_robots". I modelli la gestiscono bene e passano ad altre fonti o lo dicono all'utente. Rispettiamo robots.txt: il nostro crawler si identifica come WebReaderAPI, segue le regole scritte per lui o per tutti i robot e non legge una pagina che un sito vieta. Le pagine rifiutate non vengono addebitate. Se un sito rifiuta la nostra richiesta, restituiamo la sua risposta: non risolviamo mai CAPTCHA, non accediamo agli account e non tentiamo di aggirare un blocco o un paywall. Nessuno di questi rifiuti consuma un credito.
Function calling: domande frequenti
Serve l'API Responses o Chat Completions?
L'esempio usa Chat Completions con il parametro tools. La stessa definizione di funzione e lo stesso body read_url funzionano con altre interfacce OpenAI che accettano function tool; cambia solo il loop attorno.
Il modello può leggere più pagine in un solo turno?
Sì. Una risposta può contenere più tool call; il loop esegue ciascuna e restituisce ogni risultato prima di interrogare di nuovo il modello. Tieni max_chars più basso quando il modello legge molte pagine insieme.
La chiave API è esposta al modello?
No. La chiave resta nell'environment del tuo codice. Il modello vede solo il nome del tool, la sua description e il testo che la tua funzione restituisce.
Quanto costa una pagina rifiutata?
Niente. 1 credito = 1 pagina letta senza browser; 2 crediti quando la pagina richiede un browser per eseguire JavaScript. Una richiesta che fallisce, o che il robots.txt di un sito rifiuta, non viene addebitata.