news 2026/10/11 3:42:54

Agent实战:用Cloudflare Web Search API给大模型装上实时联网能力

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent实战:用Cloudflare Web Search API给大模型装上实时联网能力

从做 Agent 的第一天起,我就意识到一件事:模型再厉害,本质上还是个“闭卷考生”。你问它常识、历史、代码逻辑,它答得头头是道;可一旦涉及“今天发生了什么”“最新价格是多少”“现在几点开售”这类实时信息,它就秒变“一本正经的胡说八道专家”。给 Agent 接上实时联网能力,是所有做 AI 应用、智能助手、自动化工作流的人都绕不开的必修课。这篇不绕弯子,直接讲 Cloudflare Web Search API——一个让我从爬虫泥潭里爬出来的搜索接口,以及我怎么把它干净利落地接进自己的 Agent,让模型从“背答案”变成“查答案”。

先说几个核心关键词:Cloudflare Web Search API、Agent 联网、Tool Calling、上下文注入、缓存降级。这些都是做实战接入时必须想清楚的点,也是这篇文章的主线。

1. 为什么“实时联网”是 Agent 落地时绕不开的一关

1.1 模型的知识截止,是幻觉的温床

所有大语言模型都有训练数据截止日期。哪怕是最新的模型,它的世界知识也只是“某个时间点之前的快照”。你可以把它想象成一个只看过某年报纸的资深编辑——文笔很好,认知框架很熟,但你问他今年新发布的一款手机怎么样,他只能根据旧型号勉强推测,而且会用非常笃定的语气说出来,让你以为他真知道。

我自己踩过一个特别典型的坑:让 Agent 去整理某个行业的最新政策变化。模型给出了一份结构完整、引用格式规范的报告,里面甚至带上了“发布日期”和“文件编号”。结果一查,所有细节都是它基于旧文件“脑补”出来的,日期对不上,条款编号也不存在。这还不是个别现象,而是所有不联网模型的通病——没有检索能力,模型就分不清“知道”和“推测”。

所以给 Agent 接 Web Search API,不是“锦上添花”,是“必须项”。尤其是客服助手、信息整理工具、新闻摘要机器人、投资分析辅助这类应用,联网搜索就是它的眼睛,没有眼睛,再强的推理能力也是闭着眼答题。

1.2 几类常见联网方案的痛,我替你都试过了

在碰到 Cloudflare Web Search API 之前,我试过三条路,各有各的坑。

第一类是自建爬虫。自己写爬虫去抓目标网站,再套 BeautifulSoup 或正则提取正文,最后丢给模型做摘要。听起来很自由,实际上维护成本极其夸张。目标网站的class命名改一次,解析逻辑就要重写;稍微有点反爬措施,就得处理代理池、验证码、请求频率,一套下来不比写业务代码轻松。我花了一周做的采集模块,上线第三天就碰上了改版,彻底报废。

第二类是直接套用某些搜索 API 服务的免费额度。这类方案前期顺手,但问题在于:大部分第三方搜索服务都要单独注册账号、单独计费,而且搜索结果经常带一堆广告字段和 URL 重定向,清洗起来非常头疼。更麻烦的是很多服务的免费额度对个人开发者很不友好,QPS 一旦稍微上来,响应直接变慢,体验拉满。

第三类是依赖某个 AI 平台自带的联网功能。比如直接调 LLM 厂商的 Web 插件,确实能用,但绑定得很死。我想在自建的 Agent 里同时做多步工具调用,让模型自己决定先搜索还是先查数据库,这些平台工具就没法灵活嵌入我的工作流。

到这一步我基本形成判断:我想要的是一个干净、标准、能当普通 HTTP 接口来用的搜索 API,最好和部署 Agent 的云生态在同一处,不用单独申请账号,也不用关心背后的搜索源怎么切换。Cloudflare Web Search API 恰好就是这种定位。

2. 摸清 Cloudflare Web Search API 的能力边界

2.1 它到底是个什么东西

Cloudflare Web Search API 不是让你自己搭搜索引擎,而是 Cloudflare 生态里提供的一个“搜索代理”。你要做的事很简单:把查询参数和结果条数发过去,API 把抓取并清洗后的搜索结果返回给你,里面包含标题、链接、摘要这些结构化字段,你只需要拿到 JSON 直接处理。

这个设计对我这种做 Agent 的人来说非常关键。因为 Agent 里的联网模块不需要“完整搜索引擎”——它需要的是“快速拿到一批可信链接和摘要,然后交给模型做进一步判断”。直接给你干净的 JSON,比给你一个布满追踪参数的网页强一百倍。

另一个让我选择它的原因是它和 Workers AI、Workers 部署环境天然同源。我本来就是把 Agent 的服务部署在边缘运行时的,账号里已经存好凭证,加一个 Web Search 能力不需要重新走建账号、绑银行卡、申请试用这一整套流程。如果你已经在用 Cloudflare 管域名或跑边缘函数,顺手上手成本真的很低。

2.2 参数与限制,提前知道能少踩不少坑

任何 API 都有边界,提前摸清楚就不至于在接入后被它卡脖子。根据我用下来的体验,有几个点要留意:

  • 入参维度:最核心的参数是query(查询词)和max_results(返回条数)。有些场景我还加上了语言、地区这类过滤参数,目的是减少无关内容。条数我一般控制在 5 到 15 之间——太少,上下文信息不够用;太多,token 消耗又压不住。
  • 返回字段:通常包含标题、链接、摘要这几类。摘要非常关键,因为 Agent 判断某条结果是否相关,靠的主要是摘要,而不是先点开全文。
  • 流量限制与计费:不同账户层级对应的配额不同。个人开发者的免费档在轻量场景下是够用的,但如果你的 Agent 会被很多人同时调用,最好在控制台提前确认一下自己的额度,并给调用量设个警报,防止某天某个用户的密集请求把整月配额烧光。
  • 源站不透明:API 没有把具体是哪些源站告诉你,也不承诺某条链接的顺序一定是按“你想的”相关性排的。所以实战中我会做二次排序和过滤(后面专门讲),不直接无脑用返回顺序。

这么说吧,把它看成“托管版搜索网关”而不是“可定制的搜索引擎”,你的预期管理就对了一半。你拿到的是经过它处理的高质量候选集,剩下怎么做取舍,是你的 Agent 自己该干的事。

3. 从零跑通:Token 获取与最小调用环境

3.1 创建令牌:权限给少一点,安全多一重

在写代码前,先把访问凭证准备好。Cloudflare 后台创建 API Token 的入口一般在控制台的“我的 Profile → API Tokens”里。新手最容易犯的错误是图省事,直接创建一个拥有所有权限的全局令牌,然后把它硬编码在代码里。这非常危险——一旦代码仓库泄露,别人拿到的就是整个账户的控制权。

我的建议是:新建一个专用 Token,权限只要和 Workers AI / AI 网关相关的项即可,不要勾账户级全部权限。Token 创建成功后会显示一次,记得立刻存到环境变量或密钥管理服务里,别贴进代码库。另外,把ACCOUNT_ID也准备好,调用 API 时路径里通常需要带这个 ID 来定位你的账户资源。

3.2 用 curl 快速验证响应结构

代码一上来就写 Python 不是不行,但我更建议先用 curl 把链路打通,确认请求格式和返回字段,避免后面 Python 代码里一层层排查。

一个典型的请求大致长这样(端点路径里的模型标识请以你后台实际显示为准,各家会把服务挂在不同模型入口下):

curl -X POST "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/{SEARCH_MODEL_ID}" \ -H "Authorization: Bearer {API_TOKEN}" \ -H "Content-Type: application/json" \ --data '{ "query": "2025 年 发布的 AI 编程助手 对比", "max_results": 5 }'

如果返回的是 200,你会看到结构里带results数组,每个元素包含类似title、url、description的字段。第一次跑通的时候,建议把原始 JSON 存一份到本地,后面写解析逻辑时直接对照真实字段名来取,比猜字段靠谱得多。

如果返回 401,检查令牌的权限是不是没勾上;如果 400,多半是max_results超了范围或者查询词编码有问题;如果 429,说明触发限流了,需要在代码里加退避重试。

4. Python 快速实现:把搜索结果变成“能喂给模型的干净文本”

4.1 封装一个 search 函数

跑通 curl 之后,把它变成 Python 代码就很简单了。核心思路是:写一个search_web函数,接收查询词和条数,返回结构清晰的搜索结果字符串。我用的是requests,你也可以换httpx,本质上没差别。

import os import requests import json ACCOUNT_ID = os.getenv("CF_ACCOUNT_ID") API_TOKEN = os.getenv("CF_API_TOKEN") SEARCH_MODEL_ID = os.getenv("CF_SEARCH_MODEL_ID") def search_web(query: str, max_results: int = 8) -> str: """调用 Cloudflare Web Search API,返回格式化后的搜索结果文本。""" url = f"https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/{SEARCH_MODEL_ID}" headers = { "Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json" } payload = { "query": query, "max_results": max_results } resp = requests.post(url, headers=headers, json=payload, timeout=15) resp.raise_for_status() data = resp.json() results = data.get("results", []) # 具体字段路径以实际返回为准 # 拼接成一个紧凑的、适合 LLM 阅读的文本块 lines = [] for i, item in enumerate(results, 1): title = item.get("title", "") link = item.get("url", "") snippet = item.get("description", "") lines.append(f"[{i}] 标题:{title}\n链接:{link}\n摘要:{snippet}") return "\n\n".join(lines)

这段代码里,我最重视的其实是最后的“文本布局”。因为搜索结果不是给人看的,而是要塞进模型上下文里看的。如果每条结果之间没有清晰分隔、没有序号、没有字段标识,模型读的时候容易把不同链接的内容混在一起,影响回答准确性。

4.2 为什么要做结果格式化,而不是直接丢 JSON

这一步很多人会省略,觉得“反正模型能读 JSON”。但实测下来,直接丢原始 JSON 有两个问题。第一,原始字段名和嵌套结构会浪费 token,尤其当你一次拿到 20 条结果时,多出来的括号和键名都是真金白银;第二,同一份内容,用自然语言标签(“标题”“链接”“摘要”)分隔后,模型对“哪条信息来自哪个链接”的对应关系明显更清晰,引用来源也不容易串。

我这边的经验是:给 LLM 的内容,永远提前格式化到“它要什么就喂什么”的程度。让它做选择题,不要让它做阅读理解。

4.3 异常处理:优雅降级比拼命重试更重要

调用外部 API,一定需要考虑失败场景。看我这版处理逻辑:

import time def search_web_with_retry(query: str, max_results: int = 8, retries: int = 2): for attempt in range(retries): try: return search_web(query, max_results) except requests.exceptions.Timeout: # 首超时,退避 1 秒再试一次 if attempt < retries - 1: time.sleep(1) continue return "【搜索服务超时】" except requests.exceptions.HTTPError as e: # 429 等限流情况,等久一点 if e.response.status_code == 429: time.sleep(3) continue return f"【搜索服务异常:{e}】" except Exception: return "【搜索服务不可用】"

注意这里有个反直觉点:except Exception后我直接返回了一条用户可见的占位文本,而不是抛异常。因为 Agent 的链路是:用户提问 → 模型判断要搜索 → 调用工具 → 模型据此回答。如果搜索挂了,Agent 不应该跟着挂,它应该明确告诉用户“联网查不了,我只能基于已有知识回答”,然后继续干活。把外部依赖故障变成可读的文本反馈,比强行让整条任务失败要好得多。

5. 接进 Agent 的工程化改造:从“能搜”到“会查”

5.1 让模型自己决定“什么时候该搜”

拿到搜索函数只是第一步。真正进阶的做法,是把search_web作为工具注册给模型,让模型在回答问题时自己判断是否需要联网。

主流做法是 Function Calling / Tool Calling:你给模型描述这个工具是干嘛的、需要什么参数,然后在对话循环里留下一个“模型请求调用工具”的出口。当用户问“今天天气怎么样”时,模型会返回一个工具调用请求,参数是{"query": "今日天气"};当用户问“1+1等于几”时,模型会直接回答,不动搜索工具。

伪代码长这样:

def agent_chat(user_message, messages): messages.append({"role": "user", "content": user_message}) while True: response = llm.chat_with_tools(messages, tools=[tool_schema]) if response.tool_calls: # 模型想调用搜索 for tool_call in response.tool_calls: if tool_call.name == "web_search": tool_result = search_web_with_retry(query=tool_call.args["query"]) # 把工具结果拼回去,让模型继续回答 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": tool_result }) else: # 模型可以直接回答,结束循环 return response.content

加入这个判断后,Agent 的“智商”会上一个台阶——它会把联网搜索当成自己工具箱里的一个工具,而不是每次都用、也不是每次都不用。

5.2 上下文拼接的 token 管理

搜索结果注入上下文后,模型回答质量高不高,很大程度取决于你“喂”得干不干净。我见过不少人踩的坑:max_results 填 20,每个摘要一两百字,结果一条用户提问愣是塞了四千多 token 的搜索内容进去。查询越泛,内容越杂,模型就越容易跑偏。

我的习惯是给工具返回做两层限制。

第一层,条数限制。默认 5-8 条,除非用户明确要求“全面一点”,否则绝不放宽。在 API 调参时就把max_results设死,返回的内容天然可控。

第二层,长度截断。每个摘要我会截到 200 字符左右,过长的摘要只保留开头部分,因为搜索结果摘要里最重要的信息基本都集中在前面。如果某条链接看起来特别相关,模型在后续推理中可以靠链接去“脑补”或再发起一次更精确的搜索,不需要一次性把所有正文都搬进来。

5.3 缓存与时效性策略:别让相邻提问重复烧配额

一个很容易被忽略但非常影响成本的点,是缓存。在同一个 Agent 会话里,用户往往会连续问类似问题。比如先问“AI 编程助手有哪些”,紧接着追问“哪款适合新手”。如果每次提问都重新搜索,你会重复烧很多配额,而且后一次的搜索结果跟前一次基本一致。

我在工程上做了个很轻的缓存层:用查询词做 key,结果存 10 到 30 分钟。这个时间窗口对“新闻事件”“实时行情”可能太长,但对“软件评测”“产品对比”“教程文章”完全够用。要做时效性更强的场景,可以加参数只缓存 2 分钟。这个策略让我在实际项目里的搜索 API 调用量降了四成,响应也快了不少。

另外一点:多轮对话里“硬搜”和“软搜”要分清。如果用户上一句问的是“帮我看看今天的资讯”,下一句说“再详细点”,这时候你其实不需要重新搜索,直接让模型基于上一轮的搜索结果展开就行。除非用户切换了话题,或者明确说“重新查”,否则优先复用当前会话里的搜索结果,而不是每次都冲出去搜一遍。

6. 从“能搜”到“会查”:真实项目里的降噪与调优

6.1 搜索结果的二次过滤,不要迷信 API 原始顺序

Cloudflare Web Search API 的返回结果虽然已经做了相关性排序,但它是“通用搜索引擎”的逻辑,不是“你的业务场景”的逻辑。同一个查询词,给做新闻综述的 Agent 和给做技术文档问答的 Agent,理想结果可能完全不同。

我在一个模拟项目里处理“最新 AI 工具推荐”这个问题时,发现 API 返回的前几条大多指向聚合资讯站,而真正专业的 GitHub Repo 和开发者教程排在后面。这时候如果直接把前面的结果喂给模型,答案会偏“泛资讯风”。

我的做法是:拿到结果后再做一遍关键词加权排序。比如说明自己是技术问答场景,就对 URL 里包含github.com、stackoverflow.com、docs.、blog.的链接给更高权重;如果是新闻场景,就优先保留 URL 里带news、times、today的站点。这步很糙,但对结果质量的改善立竿见影。

6.2 让模型先改写查询词,再出发搜索

还有一个性价比极高的优化:查询词改写。用户提问往往带口语、指代、或者缺少上下文。比如用户问“这个多少钱”,模型如果直接拿“这个多少钱”去搜,肯定搜不出东西。但如果在前一步让模型把问题改写成检索词,比如“这款无线耳机的官方售价”,搜索效果会好很多。

实现方式不需要单独调一个模型,直接在 Function Calling 请求之前加一个系统提示:

当用户问题需要联网搜索时,先将用户问题改写成一条简洁、明确、面向搜索引擎的关键词组合,然后再调用 web_search 工具。

就这么一行提示词,让 Agent 在触发工具调用前重写一遍 query,搜索命中率提升非常明显。有时候项目效果不好,不是 API 不行,而是你喂给搜索引擎的查询词本身就不是人话。

6.3 和 RAG 的合理分工:内部知识走知识库,开放信息走搜索

说到最后,我想强调一个问题:Web Search 不是用来替代 RAG 的,它俩是互补关系。

RAG 做的是“在私有/固定知识库里检索”,适合放公司制度、产品手册、技术文档这类内容稳定、需要精确引用的资料。Web Search 做的是“在开放实时世界中检索”,适合放新闻动态、价格行情、竞品信息这类每天都在变的资料。如果你让 Web Search 承担内部知识的职责,很容易搜到过时或来源不明的信息;反过来,如果你让 RAG 承担实时资讯的职责,知识库的更新成本会让你崩溃。

我的经验是:在 Agent 的 system prompt 里给模型明确分好工——凡是涉及内部流程、项目信息的问题,先在知识库工具里检索;凡是涉及外部环境、时效信息的问题,才去调 Web Search。这样既省搜索额度,回答质量也最稳。

最后分享一个实战体会。刚开始接入时,我也犯过“把搜索结果原封不动塞进提示词就完事”的错误,结果模型表面上在用联网信息,实际还是在靠自己的惯性回答。后来我才意识到:联网只是手段,真正决定 Agent 质量的,是你对搜索结果的清洗、排序和上下文编排。把这套链路理顺,Cloudflare Web Search API 这个入口才会真正变成 Agent 的“实时眼睛”,而不是一个偶尔被想起来的外挂。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 3:42:54

YOLOv11剪枝量化一条龙:推理提速5倍实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 3:42:18

STM32入门指南:从选型到开发环境,再到点灯与调试避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 3:41:50

神经网络在算法交易中的本质:从价格预测到订单流建模

1. 项目概述&#xff1a;这不是“写个模型就开干”的速成课&#xff0c;而是一次对算法交易底层逻辑的重新校准“神经网络&#xff1a;精通算法交易的艺术&#xff1a;使用 Python 深度学习构建算法交易策略&#xff08;一&#xff09;”——这个标题里藏着三个极易被新手误读的…

作者头像 李华
网站建设 2026/10/11 3:39:50

010 Editor实战:用二进制模板和脚本高效解析固件与文件格式

简介&#xff1a;010 Editor是一款功能强大的十六进制编辑器&#xff0c;面向软件开发者、系统管理员、数据分析师及逆向工程人员&#xff0c;适合处理二进制文件分析、磁盘映像查看、内存转储解析和网络流量捕获数据。内置无限撤销、列模式编辑、正则搜索替换、多行批量修改以…

作者头像 李华