Resposta rápida
Como faço para um modelo OpenAI ler páginas web com function calling?
Declare uma função read_url na lista tools e então rode um loop: quando o modelo retornar um tool call, chame o endpoint /read da WebReaderAPI com a URL e envie o Markdown de volta como uma mensagem tool. Cada página custa 1 crédito, 2 com navegador, a partir de US$ 0,07/1K páginas em outubro de 2026. Sem KYC; pague com cripto.
Como é o schema do read_url?
Dois parâmetros bastam: o url a ler, obrigatório, e um max_chars opcional que permite ao modelo encurtar uma página longa. A descrição é a parte mais importante: ela diz ao modelo o que a ferramenta retorna e quando usá-la. Mantenha factual e curta.
Nomeamos a ferramenta read_url em vez de web_fetch para evitar conflito com ferramentas que alguns runtimes de agente já fornecem com esse 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
}
}
}Como funciona o loop de tool calling?
Envie a conversa com a lista tools. Se a resposta contiver tool calls, execute cada uma, anexe o resultado como uma mensagem com role tool e o tool_call_id correspondente, e pergunte ao modelo de novo. Quando uma resposta voltar sem tool call, ela é a resposta final.
Limite o número de rodadas. Três ou quatro bastam para uma pergunta sobre um punhado de páginas, e o limite protege você de um modelo que fica pedindo mais.
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)})
Como mantenho o uso de tokens e o custo baixos?
Três configurações importam. Mantenha max_chars modesto, em torno de 8.000 a 20.000 caracteres por página, porque cada caractere que você retorna é lido pelo modelo. Mantenha o main_content=true padrão, para que menus e rodapés nunca cheguem ao contexto. E limite o loop, como acima, para que uma pergunta não dispare dezenas de leituras.
Do nosso lado, o custo é por página, não por caractere: 1 crédito = 1 página lida sem navegador; 2 créditos quando a página precisa de um navegador para renderizar JavaScript. Uma requisição que falha, ou que o robots.txt de um site recusa, não é cobrada. Um assistente de suporte que lê 300 páginas por dia usa cerca de 9.000 créditos por mês, dentro de 10.000 páginas (US$ 3,75).
O schema funciona com outros modelos?
Sim, com pequenas mudanças no wrapper. A maioria das APIs de tool calling aceita um JSON Schema para os parâmetros e um nome e uma descrição para a ferramenta, então read_url passa entre provedores como está. A página de MCP empacota a mesma função para clientes que falam o Model Context Protocol, e a página de LangChain a envolve para agentes LangChain e LlamaIndex.
O que o modelo deve ver quando uma página é recusada?
Uma frase curta e clara, como o loop acima retorna: "Could not read the page: blocked_by_robots". Modelos lidam bem com isso e recorrem a outras fontes ou avisam o usuário. Respeitamos o robots.txt: nosso crawler se identifica como WebReaderAPI, segue as regras escritas para ele ou para todos os robôs e não lê uma página que o site proíbe. Páginas recusadas não são cobradas. Se um site recusa nossa requisição, devolvemos a resposta dele: nunca resolvemos CAPTCHAs, nunca entramos em contas e nunca tentamos contornar um bloqueio ou um paywall. Nenhuma dessas recusas usa um crédito.
Function calling: perguntas frequentes
Isso precisa da Responses API ou do Chat Completions?
O exemplo usa Chat Completions com o parâmetro tools. A mesma definição de função e o mesmo corpo read_url funcionam com outras interfaces da OpenAI que aceitam function tools; só o loop em volta delas muda.
O modelo pode ler várias páginas em um turno?
Sim. Uma resposta pode conter vários tool calls; o loop executa cada um e retorna todos os resultados antes de perguntar ao modelo de novo. Mantenha max_chars menor quando o modelo lê muitas páginas de uma vez.
A chave de API fica exposta ao modelo?
Não. A chave permanece no ambiente do seu código. O modelo só vê o nome da ferramenta, a descrição dela e o texto que sua função retorna.
Quanto custa uma página recusada?
Nada. 1 crédito = 1 página lida sem navegador; 2 créditos quando a página precisa de um navegador para renderizar JavaScript. Uma requisição que falha, ou que o robots.txt de um site recusa, não é cobrada.