ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从搜索框到Agent:Chatbot联网搜索的演进与ReAct实战

从搜索框到Agent:Chatbot联网搜索的演进与ReAct实战 从“点一下搜索”到“让 Agent 自己决定怎么搜”Chatbot 联网搜索这几年走过了一段很有意思的演进路。很多人以为给机器人接个搜索 API 就是“联网搜索”了其实那只是最粗的“搜索框”模式。真正的 Agent 化是把搜索引擎从一条固定调用链变成模型手里的一个工具让它根据提问去制定搜索计划、拆解多步问题、选择查询词、筛选结果甚至反复搜直到拿到可信证据。这篇文章就把这条演进路径拆开讲一遍前半段聊清楚两种模式到底差在哪有哪些技术组件在背后支撑后半段直接给出一份可复现的实操代码和一套线上排查清单。想给自家 Chatbot 加联网能力、准备入坑 Agent 开发的读者这篇应该能帮你少走不少弯路。1. 从搜索框到 Agent演进逻辑与方案拆解1.1 早期 Chatbot 的“搜索框”模式先说清楚“搜索框”模式长什么样。它的核心逻辑就是一次调用、一次拼接、一次生成用户提问系统直接把问题拼成 query 发给搜索引擎 API拿回一串结果列表塞进 LLM 的上下文里让模型在生成回答时“参考”这些网页摘要。这个方案最开始确实有效因为早期的问答型 Chatbot 面对的多是简单事实类问题比如“今天北京气温多少”或者“XX 产品的发布时间”一次搜索基本就够。但用过的都知道这套方案有几个硬伤。第一多步任务处理不了。用户问“对比一下 A 产品和 B 产品的配置差异然后告诉我哪个更适合程序员”一次搜索根本搞不定因为你需要先分别搜两个产品、再找参数页、再对比。第二搜索 query 的质量没法保证。模型直接拿原始问题去搜问题里带比较词、模糊表达结果排序会非常差。第三无法验证和修正。如果第一次搜出来的结果不相关搜索框模式不会自动重试模型只能在垃圾结果上强行编答案。第四token 消耗大。一次塞进去十条完整摘要大模型吃进去一堆噪声回答质量反而下降。我做过一个很直观的类比搜索框模式就像你在餐厅里大喊一声“我要吃鱼香肉丝”服务员只负责把菜端到桌上好不好吃、有没有放香菜全靠厨师搜索引擎的默认习惯。而 Agent 模式是服务员会先问你忌口、再去后厨看食材、跟厨师商量做法、端上来之前还尝一口。核心差异在于决策权从“一次性指令”变成了“多轮循环中的主动判断”。1.2 Agent 模式如何解决根本问题Agent 化的联网搜索本质是把搜索从“一次性动作”升级为“推理循环中的一环”。在这个循环里LLM 是大脑它不只负责生成文本还负责推理下一步该做什么是否需要搜索、要搜什么关键词、要不要再搜一次、当前结果够不够回答、最终怎么组织答案。这一套流程用工程术语说就是 ReAct 模式推理和行动交替进行。具体流程是这样的用户提问Agent 分析问题如果判断需要外部信息就从工具清单里选定“搜索”这个工具生成一个精炼的查询词调用搜索引擎 API拿到结果后再思考结果里有没有答案没有就换个角度再搜一次有就直接回答。这个过程可以跑好几轮直到模型认为信息充足。这恰恰解决了刚才说的三个问题多步任务可以拆成多次搜索query 由模型根据上一步结果动态调整信息不足时可以迭代补充。有人可能会说这不就是“多调几次 API”吗没那么简单。真正的关键是工具调用的抽象能力。在代码层面Agent 的工具是一个带描述和参数 schema 的函数模型通过所谓的 Function Calling 机制决定“要不要调”和“传什么参数”。你可以在工具描述里写清楚它能干什么、参数怎么填这比写死逻辑灵活得多。另一个关键是记忆。Agent 每一轮行动的结果都会回写到对话上下文里形成一个“我搜了什么、结果是什么、下一步怎么办”的思维链这让后续的搜索决策更有依据。1.3 为什么必须理解这次演进如果不理解这次演进很容易出现两种误区。第一种是把“接了个 API”当“做了 Agent”结果一问多步问题就露馅第二种是反过来盲目追求复杂架构连简单搜索都要上编排框架结果开发成本翻倍线上问题一堆。把演进路径搞清楚你就知道在什么阶段该用哪种方案。从技术准备角度来说Agent 化搜索牵扯到几个关键知识点Tool Calling 协议、ReAct 循环设计、上下文预算控制、搜索 API 的选择、框架抽象与直接实现之间的取舍。这些正是下文要拆的细节。读者里如果有一部分是从前端或产品转过来的我建议先把 Function Calling 和 ReAct 这两个概念吃透它们是我在实战里觉得最反直觉、但最核心的两个点。2. 核心技术拆解把搜索变成 Agent 的“手和眼”2.1 搜索引擎作为工具Tool 与 Web Search API把搜索能力暴露给 Agent第一步是把它封装成一个 Tool。这里的关键不是“调用函数”而是“让模型知道有这个函数、知道什么时候调、知道怎么填参数”。一个标准的 Tool 定义包含三个部分名称比如 search_web、描述告诉模型“当用户需要最新、实时或网页信息时使用”、参数 schemaJSON Schema声明 query、max_results、language 等字段。模型看到这些信息在自己的推理过程中决定要不要调用这就是 Function Calling。搜索 API 的选型直接决定实测效果我踩过不少坑。市面常见的几类可以放在一起看API 服务数据覆盖返回格式适用场景备注Tavily网页、新闻结构化 JSON、摘要、来源面向 LLM 的搜索优化被很多 Agent 框架默认集成有免费额度实测响应快SerpAPIGoogle 搜索结果结构化 JSON需要 Google 排名的场景价格偏贵结果噪声大Brave Search独立索引结构化 JSON隐私友好索引质量中等有免费层适合个人项目博查Bocha多引擎聚合结构化 JSON中文场景优化好国内开发者常用选型时不要只看数据覆盖重点看“返回结果是不是给 LLM 准备的”。有些 API 返回原始 HTML 或广告夹杂的信息Agent 直接拿进来回答会非常痛苦。更聪明的做法是用“摘要来源 URL”的结构化返回LLM 可以直接读摘要做判断需要验证时再顺着链接去抓全文。我在项目里还加了一层后处理把每次搜索返回的摘要截断到 300 字以内只保留主题句和关键实体。这样既压缩 token又减少引用噪声。封装工具时还有一个细节经常被忽略要给 Tool 描述写“触发条件”和“不触发条件”。比如“当用户询问天气、股价、新闻等实时信息时使用当用户可以凭常识或模型知识回答时不要使用”。这样做能显著减少模型乱调搜索的次数节省成本和时延。2.2 Agent 框架选型LangChain、Dify、CrewAI 等很多热词都在讨论 Agent 框架实际项目里我也用过 LangChain、Dify、CrewAI 这一票体验差异还挺大的。先说结论没有“唯一最好”只有“跟场景匹配”。LangChain 是最常见的代码级框架优点是可以把 Tool、Prompt、Memory、Agent 循环全部写成代码精细化控制每一步缺点是 API 变化太快几乎每隔一个版本就换风格网上找的资料经常过期。如果你愿意跟着最新文档走它适合做深度定制。Dify 是可视化平台适合产品原型和团队里不会写代码的同学。它把知识库、工具、工作流都封装成了界面配置半小时就能跑出一个可联网搜索的 Chatbot。但一旦需要复杂的条件分支、自定义循环可视化的表达能力就开始不够用。我的经验是Dify 适合快速验证真正上线还是得回到代码不然排错都想哭。CrewAI 主打多 Agent 协作适合需要分工合力的场景比如一个搜索 Agent、一个总结 Agent、一个写作 Agent 组合。它把“多角色团队”包装得很简单但也会引入新的复杂度角色之间的沟通上下文怎么管理、消息怎么串都是额外负担。如果你只是给现有 Chatbot 加一个搜索功能我甚至建议先不考虑框架。纯手写一个 ReAct 循环其实只要几十行代码用requests调搜索 API再把工具调用逻辑写成一个if result.tool_calls的判断就能跑。框架的真正价值在于帮你处理并发了、缓存了、回调了这些工程细节而不是让模型变聪明。小项目手写大项目选 LangChain 或自研编排这个思路比较稳妥。2.3 Agent Skills让 Agent 学会“怎么搜”的抽象层“Agent Skills”是最近社区里非常热的一个词。理解它最好的方式是看它的结构一个 Skill 是一组有组织的资源通常包含一个SKILL.md说明文件描述技能做什么、怎么用、若干可执行脚本、以及必要的配置或模板。模型看到 Skill 描述后会按里面的引导去执行这比零散地堆 Tool 更系统。拿联网搜索来举例。一个“搜索洞察”Skill 可以包含一个自动把用户问题重写为几路查询词的脚本、一个爬取搜索结果页并清理正文的工具、一个把搜索发现整理成结构化 memo 的函数。这些资源都在同一个 Skill 包内模型按步骤调用而不是让开发者硬编码每一步。这和 Tool 的区别在于Tool 是单个操作Skill 是“操作流程配套资源”的集合。我实际用下来的感觉是Skill 能很好地提升 Agent 行为的一致性。比如同一个搜索任务没有 Skill 时不同模型版本可能乱搜一气有了 Skill 的引导大家会按照定义好的“先重写 query再搜索两条路径最后汇总”这个流程走。缺点是 Skill 的调试成本不低因为内部逻辑越复杂出 bug 的概率越大。社区里像 Claude Agent Skills 这类实践文档已经很多做搜索类 Agent 的朋友值得去翻一翻尤其是里面关于“skill loading”和“testing”的部分写得很细很多坑他们都踩过。3. 实操从零搭一个可联网搜索的 Agent3.1 设计思路与工作环境准备这一节我会用一个最小可运行的实现演示怎么把一个能联网搜索的 Chatbot 开发出来核心还是 ReAct 循环。既然前面提到了搜不到结果怎么办我在这里也把“自问自答再搜索”的逻辑写进去。工作环境其实很简单一台能装 Python 的机器准备一个 LLM APIOpenAI 兼容格式的都可以一个搜索 API Key。为了不把文章变成框架阅读笔记我用最裸的方式实现 ReAct好处是你一眼能看清每一步在干什么坏处是工程化的时候还要自己处理并发、重试和日志。不过这不影响理解原理。以下是常见实践中的最小实现思路代码我会给出适合直接运行的版本。3.2 核心代码工具定义、Agent 编排、提示词设计先把搜索工具定义出来。这里用 Tavily 示例实际上换成其他 API 只需要改解析逻辑import requests import json from typing import Optional TAVILY_API_KEY 你的_key TAVILY_SEARCH_URL https://api.tavily.com/search def search_web(query: str, max_results: int 5) - list: payload { api_key: TAVILY_API_KEY, query: query, max_results: max_results, search_depth: basic, include_answer: True, } resp requests.post(TAVILY_SEARCH_URL, jsonpayload, timeout15) resp.raise_for_status() data resp.json() return [ { title: item.get(title), url: item.get(url), content: item.get(content)[:300] if item.get(content) else } for item in data.get(results, []) ]工具描述怎么写很关键。当我把这段描述喂给模型时它必须清楚自己拥有一个“可以获取实时信息”的入口search_tool { type: function, function: { name: search_web, description: 针对用户的查询词执行网页搜索返回标题、URL 和摘要列表。当问题涉及实时数据、事件、资料查证或最新信息时必须使用。, parameters: { type: object, properties: { query: {type: string, description: 搜索查询词要简洁、具体尽量使用关键词而非完整句子}, max_results: {type: integer, description: 返回结果条数默认5} }, required: [query] } } }接下来是 Agent 主循环。我不依赖框架直接调用 OpenAI 兼容端点的 Chat Completions模拟 Function Calling 流程。核心逻辑是把用户消息发给模型如果模型返回tool_calls就执行对应的搜索函数把结果作为新消息追加进上下文然后再次请求模型直到模型不再要求调工具。from openai import OpenAI # 需要 pip install openai client OpenAI(base_url你的模型服务地址, api_key你的模型key) def run_agent(user_question: str, max_rounds: int 5): messages [{ role: user, content: f请回答用户问题{user_question}\n 你可以先搜索再回答。如果没有足够信息继续搜索或换搜索词。 }] for step in range(max_rounds): resp client.chat.completions.create( model你的模型名, messagesmessages, tools[search_tool], tool_choiceauto, ) assistant_msg resp.choices[0].message messages.append(assistant_msg.model_dump()) tool_calls getattr(assistant_msg, tool_calls, None) if not tool_calls: break for tc in tool_calls: args json.loads(tc.function.arguments) if tc.function.name search_web: results search_web(args.get(query), args.get(max_results, 5)) content \n.join( f标题:{r[title]}\nURL:{r[url]}\n内容:{r[content]} for r in results ) messages.append({ role: tool, tool_call_id: tc.id, content: content[:1500] if content else 无结果请改写查询词重试 }) return messages[-1].content这里有个经验点搜索返回的内容不要原样全塞进 context。我给的最大值是 1500 字符超过就直接截断。主要原因是摘要太长模型会“抓不住重点”而且回答时反而更容易引用到杂质信息。再配合 max_rounds 做步数上限可以避免 Agent 在一个问题上无限循环烧 token。这篇代码我实测跑下来对“最新新闻”“产品对比”“查证某个事件”三类任务都很稳。如果想让回答更有可信度可以在最后追加一句“请在你的回答末尾给出来源 URL 列表”模型通常懂得照做。3.3 参数调整与性能优化并发、超时、重试联网搜索 Agent 跟普通 Chatbot 最大的区别是多了一个外部调用环节这意味着并发和失败场景都更复杂。先从并发讲起因为这是热词里被问爆的“agent 怎么扛并发”。真相是搜索引擎 API 的限流通常比 LLM API 更严格比如 Tavily 免费层每分钟就几十次你 Agent 再快也扛不住。两个办法在应用层用信号量或连接池限住并发请求数给同一条 query 加短时缓存。import asyncio from asyncio import Semaphore sem Semaphore(10) # 同一时间最多10个并发 async def limited_search(query: str): async with sem: return await asyncio.to_thread(search_web, query)超时和重试更不能省。搜索 API 经常会慢尤其在抓取长尾网页时。建议给每个搜索请求都设 15 秒超时配合指数退避重试第一次失败等 1 秒重试第二次等 3 秒最多三次。模型端也一样Chat Completions 请求设 30 秒超时。不要只依赖默认请求库的超时比如刚才代码里timeout15就是硬性防护。上下文窗口的预算管理同样重要。Agent 每搜一轮就多一组工具结果。搜个三四轮上下文可能就吃掉好几千 token。我的做法是设定一个“搜索摘要池”每一轮搜索后只保留最新两轮的结果摘要前面那些视为过期信息直接裁掉。这样既保证模型能追踪最新进展又不会让早期无用结果占用窗口。4. 实战中的坑与排查技巧4.1 现象一Agent 在搜索和总结之间死循环症状模型一直重复调用搜索要么搜了不回答要么不断改 query 再搜直到 max_rounds 被截断。这种问题在弱模型上非常常见因为它们分不清“什么时候该停”。排查思路按照这个顺序来。首先看日志把每一步的 tool_calls 和返回内容打出来确认模型是不是拿不到有效结果还硬搜。其次看提示词里有没有明确的“停止条件”。比如我后来加了“如果你从搜索结果中已经能回答请直接给出结论不要继续搜索”这句话死循环概率大幅下降。最后看模型能力有些轻量模型 Function Calling 稳定性本身就差不换模型很难调好。一个隐藏的坑是工具描述里没有强调“搜索结果是参考。不是必须全用”。模型如果把每次结果都当真可能永远觉得信息不够。我在工具描述里加了一句话“如果结果与查询词不相关请忽略并重新构造查询词如果相关请直接引用它们组织答案。”4.2 现象二搜索结果太乱答复胡编这是联网搜索最常见的失败模式。现象是搜索 API 确实返回了内容但模型面对一堆杂乱摘要时还是按训练时的固有权重去“自由发挥”一段信息里夹杂不少编造成分甚至把广告语当成结论。原因有两层。一层是搜索 query 本身太烂模型直接把用户问题原封不动丢过去导致结果里全是泛泛页面。另一层是上下文里的结果摘要带太多噪音模型无从分辨。我的解法是用一个轻量的“摘要清洗”步骤拿到搜索返回后先用一个便宜的模型或纯规则脚本把摘要压缩成“一句话核心事实关键实体列表”再去喂给主 Agent。这一步成本很低但对准确率提升非常明显。还有一个做法是强制模型引用 URL当用户问“你从哪看到的”的时候答案必须可追溯。4.3 现象三并发一高就报错流量稍微上来之后最常见的问题是抛出429限流和连接池枯竭。前者是 API 限流后者是代码里没有合理复用连接。我说两个比较实用的修复。第一用连接池。把httpx.Client例化为全局单例而不是每次搜索都新建连接对象能显著减少 TIME_WAIT 积压。第二给 LLM 和搜索各设独立的信号量限制同时在建的请求数。我从一个实际项目里发现把这两层限流设置好以后系统从 QPS 5 涨到 QPS 20错误率基本保持不变。还要自查一下循环内部有没有潜在的阻塞。比如 Agent 循环是串行的用户有 20 个并发请求时如果只用单线程跑每个请求里搜索两次、模型两次总共四轮外部调用串行会非常慢。正确做法是每个对话请求自己是一个并发任务内部再做好信号量控制而不是把所有用户排队处理。4.4 现象四模型不会正确调用工具总有一些模型版本或配置会在该搜索时不搜索不该搜索时乱搜索。这通常是工具描述的问题描述写得太模糊或参数示例太少。解决办法很简单给每个参数加上示例值。比如 query 参数描述写“搜索查询词”模型可能把整句话塞进去写成“搜索查询词例如NVIDIA RTX 5090 性能评测、2025 年 3 月 AI 行业融资汇总”模型一般就能正确抽取关键词。另外一个常用招数是 few-shot。在系统提示词里给一个“用户问 X → 你决定搜索 Y → 获取结果 → 回答”的示例样本模型会模仿这个动作轨迹。如果修改描述和示例都没用那基本就是模型选型问题。实测下来功能相对来说比较稳健的模型是 Claude 系列和 GPT-4o 以上的版本小型开源模型在这块差距明显。在手写 4o-mini 级别的模型上我一般会加“搜索前必须给一句推理理由”的显式指令让它想清楚再动手能减少一半乱调用。最后分享一点个人体会。我在实际项目里做过太多“以为接个搜索就万事大吉”的版本每次都被现实狠狠教育搜索 API 只是地基上面的 Agent 循环设计、上下文管理、工具描述、异常处理才是真正决定体验的部分。如果你正准备做一个带联网搜索的 Chatbot我强烈建议你按“先用代码裸跑一个 ReAct 循环、再逐步引入框架和 Skill”这个顺序来这样你能对每个报错和每次调优都心里有数。后面这个方向还有很多好玩的扩展比如把搜索记忆做进会话态里或者让多个 Agent —— 一个负责搜、一个负责筛、一个负责写——协作完成更复杂的报告型问答这些都是顺着这条演进路线自然冒出来的机会。
返回列表