快速回答
如何让 OpenAI 模型通过函数调用读取网页?
在 tools 列表中声明一个 read_url 函数,然后运行一个循环:当模型返回工具调用时,用 URL 调用 WebReaderAPI 的 /read endpoint,并把 Markdown 作为 tool 消息回传。每个页面消耗 1 积分,使用浏览器时消耗 2 积分,价格自 2026 年 10 月起为 US$0.07/千页。无需 KYC;使用加密货币支付。
read_url schema 是什么样的?
两个参数就够了:要读取的 url(必填),以及一个可选的 max_chars,让模型可以保持长页面简短。description 是最重要的部分:它告诉模型这个工具返回什么、何时使用。保持事实性并简短。
我们将该工具命名为 read_url 而不是 web_fetch,以避免与某些 agent 运行时已在该名称下提供的工具发生冲突。
{
"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
}
}
}工具调用循环是如何工作的?
发送带有 tools 列表的对话。如果回复包含工具调用,就逐个运行,将结果作为带有 role tool 和匹配的 tool_call_id 的消息追加,然后再次询问模型。当回复返回时没有工具调用,它就是最终答案。
限制轮数。对于涉及少量页面的问题,三轮或四轮就足够了,而且这个上限可以保护你免受不断要求更多内容的模型影响。
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)})
如何降低 token 使用量和成本?
有三个设置很重要。将 max_chars 保持在适度水平,每页约 8,000 到 20,000 个字符,因为你返回的每个字符都会被模型读取。保持默认的 main_content=true,这样菜单和页脚永远不会进入上下文。并且如上所述限制循环,这样一个问题无法触发数十次读取。
在我们这边,成本按页计算,而不是按字符计算:1 积分 = 读取 1 个页面且无需浏览器;当页面需要浏览器渲染 JavaScript 时为 2 积分。失败的请求,或被网站 robots.txt 拒绝的请求,不扣费。一个每天读取 300 个页面的支持助手每月大约使用 9,000 积分,在 10,000 个网页(US$3.75)之内。
该 schema 能与其他模型一起使用吗?
可以,只需要在 wrapper 中做小的改动。大多数工具调用 API 接受用于参数的 JSON Schema,以及工具的名称和描述,因此 read_url 可以原样跨提供商迁移。MCP 页面 为使用 Model Context Protocol 的客户端打包了相同的函数,而 LangChain 页面 为 LangChain 和 LlamaIndex agent 封装了它。
页面被拒绝时模型应该看到什么?
一句简短、清晰的句子,就像上面的循环返回的那样:"Could not read the page: blocked_by_robots"。模型能很好地处理这种情况,并回退到其他来源或告诉用户。我们遵守 robots.txt:我们的爬虫以 WebReaderAPI 标识自身,遵循专为其或为所有爬虫编写的规则,不读取网站禁止的页面。被拒绝的页面不扣费。如果网站拒绝我们的请求,我们会返回其答复:我们从不破解 CAPTCHA、不登录账户,也不试图绕过封锁或付费墙。这些拒绝都不会消耗积分。
函数调用:常见问题
这需要 Responses API 还是 Chat Completions?
示例使用带有 tools 参数的 Chat Completions。相同的函数定义和相同的 read_url 请求体也适用于其他接受函数工具的 OpenAI 接口;只有围绕它们的循环会发生变化。
模型能在一轮中读取多个页面吗?
可以。一个回复可以包含多个工具调用;循环会运行每个调用,并在再次询问模型之前返回每个结果。当模型一次读取多个页面时,请将 max_chars 设得更低。
API 密钥会暴露给模型吗?
不会。密钥保留在你代码的环境中。模型只能看到工具的名称、描述以及你的函数返回的文本。
被拒绝的页面会花费什么?
什么也不花费。1 积分 = 读取 1 个页面且无需浏览器;当页面需要浏览器渲染 JavaScript 时为 2 积分。失败的请求,或被网站 robots.txt 拒绝的请求,不扣费。