通过 AI 网关给 Agent 接上实时网页搜索
💡 工具推荐:URL 编码器、API 测试工具、JSON 格式化、AI Token 计数器
这次发布背后有一个很具体的失败模式:当 Agent 需要一个实时网页时,它通常是先猜一个 URL,然后调用工具去抓。猜错是常态,所以你会在 Agent 的调用记录里看到 404,仿佛那是模型犯的错。Cloudflare 的做法是给 Agent 一个搜索引擎,而不是一个随机 URL 生成器,并把调用接到 AI Gateway 上,让它们与模型推理共用同一套日志、额度和访问控制。首发合作方是 Ceramic.ai、Exa 与 Linkup。
检索层让模型连上知识截止日之后的世界
到底发布了什么,为什么它重要
Cloudflare 的 Web Search API 把合作方的搜索能力集成进 AI Gateway。你的应用可以得到来自网络的新鲜结构化片段,并直接注入上下文——这解决的是一个结构性限制,而不是临时性问题:模型先训练、后被冻结,因此任何与近期事件、正在变化的 API 或快速迭代的发布相关的内容,都在它的知识范围之外。官方给出的动机正是如此:模型的好坏,取决于你喂给它的上下文。Agent 不再靠猜 URL 或凭空编一份文档,而是让检索步骤变成一次真正的查询、得到真正的结果,模型看到的是可以直接引用的片段,而不是一个猜测。
# REST path: one POST, no SDK required.
import json, os, urllib.request
ACCOUNT = os.environ['CLOUDFLARE_ACCOUNT_ID']
TOKEN = os.environ['CLOUDFLARE_API_TOKEN']
URL = 'https://api.cloudflare.com/client/v4/accounts/{}/ai/websearch/'.format(ACCOUNT)
payload = {
'query': 'Cloudflare Workers Durable Objects long-running video pipelines',
'provider': 'ceramic',
'limit': 5,
'options': {'gateway': {'id': 'default'}},
}
req = urllib.request.Request(URL, data=json.dumps(payload).encode(),
headers={'Authorization': 'Bearer ' + TOKEN,
'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
# Keep the source link. A snippet without a URL cannot be cited or audited.
for row in data.get('result', []):
print(row.get('title'), row.get('url'))抓取标准条款才是更有意思的那一半
Cloudflare 表示其搜索合作方承诺遵守它的爬虫抓取标准:爬虫必须符合其公开的 Verified bot 要求,并且搜索响应必须带上被抓取内容的来源链接。请把它读成一个带有治理副作用的产品特性。署名不是最后补上的礼貌,而是响应契约的一部分,这意味着你的流水线可以放心依赖“会拿到一个能给读者看的链接”。它同时给站点主明确了互动方式:表明你的爬虫身份、遵守 robots.txt、把来源交回去。如果你要做任何消费该接口的东西,请让链接从头到尾跟着片段走,因为你一旦把它剥掉,既丢掉了审计轨迹,也丢掉了读者核实这条说法的能力。
# Workers binding: one line in the request path, provider as a knob.
PROVIDERS = ['ceramic', 'exa', 'linkup']
export default {
async fetch(request, env) {
const q = new URL(request.url).searchParams.get('q') || 'cloudflare ai gateway';
for (const provider of PROVIDERS) {
try {
const res = await env.AI.websearch({ gatewayId: 'default', query: q, provider, limit: 5 });
const body = await res.json();
if (body && body.result && body.result.length) {
return Response.json({ provider, results: body.result });
}
} catch (err) {
// Fall through to the next provider rather than failing the request.
}
}
return Response.json({ error: 'no provider returned results' }, { status: 502 });
},
};两条接入路径:REST 调用与 Workers 绑定
不用 SDK 也能接上
有两条路径。从已有的后端、移动应用或服务出发,用你的 Cloudflare API token 向账号级的 AI Web Search 接口发一个 POST,带上 query、provider、limit,以及 options 里的 gateway id。从 Cloudflare Workers 出发,则是一行绑定调用:把 gateway id、query、provider、limit 传给 env.AI.websearch,再读 JSON。两者都支持与搜索合作方之间的自带密钥,这正是 AI Gateway 对推理提供商已经支持的同一套模式,因此已经与某个合作方有合约的组织可以直接指向自己的凭据。结果按合作方的列表 API 价计费、Cloudflare 不加价,并且会出现在常规的 AI Gateway 可观测日志里,也就是说一次搜索调用可以和它所服务的那次推理调用一起被查询。
# Grounding is a pipeline, not a call. Retrieve, filter, attribute, then answer.
def build_grounded_prompt(question, search_results, max_snippets=4):
kept, seen = [], set()
for row in search_results:
url = row.get('url')
text = (row.get('snippet') or '').strip()
if not url or len(text) < 60 or url in seen:
continue
seen.add(url)
kept.append({'url': url, 'title': row.get('title'), 'text': text})
if len(kept) >= max_snippets:
break
if not kept:
return {'prompt': None, 'action': 'answer from priors and say so explicitly'}
context = '\n\n'.join('[{}] {}\n{}'.format(i + 1, r['title'], r['text'])
for i, r in enumerate(kept))
return {'prompt': context + '\n\nQuestion: ' + question,
'sources': [r['url'] for r in kept],
'action': 'answer with inline citations'}
# A retrieval step that can silently return nothing is worse than no retrieval step.接地是一条流水线,那就按流水线来做
一次搜索调用加一个提示词模板不叫接地,那叫演示。这条流水线有四步,价值在中间两步。第一步,用真正的查询去检索。第二步,激进地做过滤,因为太短、重复或缺少 URL 的片段比什么都拿不到更糟——它会占掉上下文,还看起来很权威。第三步,署名,把来源 URL 与每个片段绑定,让模型可以引用、让你的日志可以还原某条说法从哪来。第四步,作答,并且把空结果显式处理:如果检索没有返回可用内容,正确行为是依靠模型已有知识作答并明确说明,而不是安静地当作检索成功继续下去。静默的检索失败,是那种会让你失去信任的失败。
# Not every provider for every caller, and not every provider for every data class.
POLICY = {
'public-docs-agent': {'providers': ['ceramic', 'exa', 'linkup'], 'zdr_required': False},
'customer-support': {'providers': ['exa'], 'zdr_required': True},
'internal-research': {'providers': ['ceramic', 'linkup'], 'zdr_required': True},
}
CAPABILITIES = {
'ceramic': {'zdr': False},
'exa': {'zdr': True},
'linkup': {'zdr': True},
}
def allowed_providers(agent, requested):
rule = POLICY.get(agent)
if rule is None:
return {'allowed': [], 'reason': 'agent not enrolled'}
ok = [p for p in requested if p in rule['providers']]
if rule['zdr_required']:
ok = [p for p in ok if CAPABILITIES[p]['zdr']]
return {'allowed': ok, 'reason': 'filtered by policy'}
print(allowed_providers('customer-support', ['ceramic', 'exa']))检索成本与推理成本要放在同一张表里算
治理:额度、日志、留存与提供商选择
因为调用跑在 AI Gateway 上,运维层面的故事和你已经为推理搭好的那套完全一致。Web 搜索消耗 AI Gateway 额度,于是支出集中可见,而不是散落在各家厂商的账单里。请求进入可观测日志,你可以把一次搜索调用与它所喂养的那次模型调用、以及周边的延迟与错误字段关联起来。Cloudflare 表示会标注支持零数据留存(ZDR)的合作方,让你知道哪些情况下数据不会被保留——如果你的查询里带着敏感内容,这一点很关键。它还让你控制谁能访问哪个搜索提供商,而这恰恰是多数团队最容易漏写的一环:一个公开文档 Agent 和一个客服 Agent 不应该自动拥有相同的提供商,而对留存敏感的流程,只应被限制在你能真正确认其留存策略的提供商上。
# Web search draws on the same AI Gateway credits as inference. Meter it.
PRICE_PER_CALL = 0.0 # list pricing from the provider, no Cloudflare markup
def search_budget(task, calls=1, tokens_in=0, tokens_out=0, usd_per_m_in=2.0, usd_per_m_out=10.0):
inference = (tokens_in / 1e6) * usd_per_m_in + (tokens_out / 1e6) * usd_per_m_out
return {'task': task,
'search_calls': calls,
'search_cost': round(calls * PRICE_PER_CALL, 6),
'inference_cost': round(inference, 4),
'total': round(calls * PRICE_PER_CALL + inference, 4)}
print(search_budget('docs-question', calls=2, tokens_in=1800, tokens_out=400))
# Twenty searches per request looks cheap until you multiply it by your traffic.在原生 Server Tools 落地之前该做什么
Cloudflare 说原生 Server Tools 正在进入 AI Gateway,网页搜索会是首批之一,也就是说你终将不必自己定义这个工具。但不要等它来解决你的检索层问题。把搜索这一步建模成一个接口:请求与提供商无关,结果形状统一,这样在 Ceramic.ai、Exa 与 Linkup 之间切换只是改配置,而不是重写,将来某个 Server Tool 也可以直接塞到同一个接口下面。先写失败路径,再写成功路径。并且把搜索调用和 token 放进同一个预算里去计量,因为真正贵的习惯不是一次大检索,而是一个每轮都检索、且从不自知的循环。
📌 常见问题 FAQ
Cloudflare 的 Web Search API 是什么?
一个集成进 Cloudflare AI Gateway 的网页搜索能力,让应用可以调用合作方的搜索服务,并把新鲜的结构化网页片段注入模型上下文。首发合作方为 Ceramic.ai、Exa 与 Linkup。
有哪些提供商可用?
首发为 Ceramic.ai、Exa 与 Linkup。Cloudflare 表示同样支持自带密钥,因此已有合作方合约的组织可以使用自己的凭据。
费用怎么算?
按合作方的列表 API 价计费,Cloudflare 不加价,并从你的 AI Gateway 额度余额中扣除。它也会与你的模型推理调用一起被计量和记录。
怎么调用?
两种方式:向账号级的 AI Web Search REST 接口 POST 一个带 query、provider、limit 与 gateway id 的请求;或在 Workers 里调用绑定方法 env.AI.websearch,传入同样字段。
抓取标准承诺是什么?
Cloudflare 表示首发合作方同意满足其爬虫抓取标准:爬虫必须符合 Verified bot 要求,并且搜索响应必须包含被抓取内容的来源链接。