# OpenAI function calling: un tool read_url per il tuo modello

> Function calling OpenAI con read_url: schema JSON e ciclo Python per trasformare i link in Markdown pulito. Senza KYC, paghi in criptovalute.

- URL: https://webreaderapi.com/it/docs/openai/
- Ultimo aggiornamento: 2026-10-22
- Editore: WebReaderAPI (https://webreaderapi.com)

Descrivi una funzione, `read_url`, ed eseguila quando il modello lo richiede. Il modello sceglie quali pagine leggere; il tuo codice chiama l'API e restituisce il Markdown. Lo stesso schema funziona con altri modelli che supportano il tool calling.

**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.

*La definizione del tool*

```json
{
  "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.

*Python: il loop completo con il pacchetto openai*

```python
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](https://webreaderapi.com/it/docs/mcp/) impacchetta la stessa funzione per i client che parlano il Model Context Protocol, e la [pagina LangChain](https://webreaderapi.com/it/docs/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.

---

WebReaderAPI: WebReaderAPI è un'API web reader per agenti AI che converte qualsiasi pagina web pubblica in Markdown pulito, con pagamento in criptovalute e senza caricare documenti d'identità. I crediti prepagati da 0,07 USD/1K pagine non scadono mai e robots.txt viene rispettato. Senza KYC — niente documento, niente selfie, niente documenti. Paga in criptovalute.
