本地知识库跑了一段时间,测试问答效果一直挺稳,但某天同事拿当天刚上的热搜新闻来问,RAG 直接答不上来,甚至一本正经地给了错误答案。这其实不是个例:知识库快照构建那天之后的信息,模型检索得再准也拿不到。我干脆给 Agent 接了一路网络搜索,让它走“本地检索 + 联网搜索”的双通道方案,既能翻旧书里的历史资料,也能上网冲浪追最新热点。这篇文章就把这套改造的完整思路、核心实现和踩坑记录整理出来,适合正在做 RAG、AI 应用开发或者想给 Agent 加实时信息能力的开发者参考。
1. 项目背景与核心问题拆解
1.1 为什么本地知识库答不上热点问题
先聊一个很多人容易忽略的点:本地知识库本质上是“某个时间点的快照”。不管是把文档切块后做向量化存入数据库,还是用全文索引做关键词检索,数据写入的那一刻就固定了。之后世界上发生的任何新事情,它都不知道。常见表现有三种:
- 直接回答“不知道”:检索结果为空,模型在上下文里找不到对应内容,只能如实承认。
- 答非所问:硬凑一段语义相近但内容过时的信息,听起来很像回事,实际上是错的。
- 编造内容:模型在“必须回答”的压力下,根据训练时的印象或上下文碎片自行脑补,这就成了最危险的幻觉。
这里面第二个问题最隐蔽,因为用户很难分辨“过时的事实”和“正确的事实”。我遇到过最典型的情况是问某款产品的最新版本号,知识库里的文档停在旧版本,Agent 就非常自信地把旧版本当成最新版报给了用户。
1.2 方案选型:为什么用“本地检索 + 联网搜索”双通道
既然单靠本地知识库有缺口,常见的补法有以下几种:
- 高频重建知识库:定期重新处理文档、重新向量化。问题是热点问题往往发生在下次重建之前,时效性永远赶不上。
- 直接丢弃本地知识库,全走大模型的联网插件:能追上热点,但企业内部文档、私有资料就全浪费了,而且网络信息噪点太多。
- 本地检索 + 联网搜索双通道:根据问题性质动态决定走哪个通道,或者两个通道都查再融合结果。这是最贴合实际业务的方案。
我最终选的是第三种。原因很直接:知识库里的历史资料往往是在某个时间点全面写入的,适合回答“过去的事实”;网络搜索解决“现在发生了什么”。两者不是替代关系,而是互补关系。从成本角度看,网络搜索按调用次数计费,只有特定问题才触发,能控制开销;从效果角度看,双通道意味着召回的信息覆盖面更大,模型拿到更多可靠上下文,答案质量自然更高。
2. 核心实现与关键环节拆解
2.1 整体架构设计与检索流程
整个改造后的流程可以分成五步:
- 用户输入问题,Agent 先做意图判断。
- 判断问题是否需要实时信息,或者本地检索置信度过低。
- 需要则触发网络搜索,同时保留本地检索作为辅助来源。
- 把本地检索结果与网络搜索结果做归一化、去重、相关性重排。
- 把融合后的上下文交给大模型生成最终回答,并标注信息来源。
这里最关键的设计决策是“什么时候该搜索”。我用了混合判断策略:一条路是模型在 function calling 环节主动申请调用搜索工具,另一条路是系统先做一次轻量本地检索,如果最高相似度分数低于某个阈值就认定为“知识库可能没有”,自动转向搜索。两个信号取其一即可触发联网。
阈值设置不宜拍脑袋。我最初设的是 0.35,结果大量历史类问题被误判成需要联网,查回来一堆泛泛的百科内容,反而把本地精确答案淹没了。后来调成 0.25,配合时间敏感词检测(比如“最新”“今天”“发布”“暴涨”“2025”这类词),误判率明显下降。不同领域阈值差异很大,建议按业务数据实测调整。
2.2 查询改写:让搜索更精准的关键一步
直接拿用户原话去搜索通常效果很差。用户问“那个新发布的小米汽车到底多少钱”,搜索引擎拿到这句话,会对“小米”和“汽车”打侧重,反而匹配出大量无关资讯。所以在触发网络搜索前,我做了一次查询改写,把它变成搜索引擎更友好的形式,比如“小米 SU7 最新售价 2025”。
改写工作直接交给大模型来完成,用一段精简的系统提示词约束它输出。改写规则有三条:
- 去除口语化表达和指代词,提取核心实体与意图。
- 补全缩写和简称:比如“AI 应用开发”保持原样,“Agent”保留英文,不做想当然的翻译。
- 如果问题包含相对时间表述(“最近”“本周”),把当前日期信息也带过去,让搜索结果有更明确的时间锚点。
需要注意的是,改写不是每次都必要。简单实体查询(比如“某产品保修政策是什么”)直接原话搜索效果就很好。过度改写反而引入噪声。我现在的做法是:优先原话搜索,如果返回结果太少或相关性分数偏低,再做一次改写重试。这个“先原样、后改写”的策略让整体搜索成功率提升了不少。
2.3 搜索结果的处理与上下文注入
搜索 API 返回的结果往往是网页标题、摘要、URL 和发布时间。直接一股脑塞给大模型,会带来两个问题:一是上下文窗口被无关内容占满,生成质量下降;二是模型分不清哪些是有效信息,容易把营销标题当成事实。
我在注入前加了三个处理步骤:
- 过滤:去掉广告标记、来源不明的个人博客、与查询主题偏差过大的条目。这一步通常结合来源域名的信誉度和标题与查询的相关性来做。
- 重排:把结果按“发布时间新 + 语义相关度高”两个维度打分排序。热点问题优先看时间,政策制度类问题优先看权威来源,权重可以动态调整。
- 压缩:每篇只截取摘要部分,控制在 200 字以内,避免超长网页内容灌爆上下文。
处理完的结果,我会拼成一个独立的“网络情报”区块,放进系统提示词里,并明确告诉模型:该区块内容来自实时网络搜索,可能存在误差,回答时引用需谨慎。这个声明非常重要,它能降低模型盲目信任搜索结果从而导致二次幻觉的概率。
3. 实操过程:从零搭建一个能联网的 Agent
3.1 环境准备与依赖安装
我直接用 Python 生态来做,整体依赖不多,核心是三块:大模型接口、向量数据库、搜索 API。搜索 API 我优先选了 Tavily,因为它专门为大模型场景做了优化,返回的摘要干净、附带权威分数,省去自己清洗网页的麻烦。如果没有 Tavily,用 SerpAPI 或者直接调普通搜索引擎结果页再自行解析也可以,只是要多写几层解析逻辑。
pip install langchain langchain-openai langchain-tavily chromadb向量数据库我用 ChromaDB,本地运行、无需额外服务。如果项目偏生产,建议换 Milvus 或者 pgvector,架构上不冲突。搜索部分我封装了一个自定义 Tool,接口是 LangChain 的 BaseTool,这样既能接入 ReAct 流程,也能兼容 OpenAI function calling。
3.2 网络搜索工具的定义与接入
定义工具时,schema 写得越清晰,模型就越会用。我给的参数包括查询词、搜索条数、时间范围,模型能自己根据问题语义决定要不要加时间过滤。
from langchain_core.tools import BaseTool from langchain_tavily import TavilySearch class WebSearchTool(BaseTool): name = "web_search" description = "搜索互联网上的最新信息。当问题涉及新闻、热点、最新动态或本地知识库无法回答时使用。" def _run(self, query: str, max_results: int = 5, time_range: str = "m") -> str: client = TavilySearch(api_key="your-key") resp = client.search(query, max_results=max_results, topic="news" if time_range in ("d", "w") else "general", days=7 if time_range == "w" else None) results = [] for item in resp.get("results", []): results.append(f"{item['title']} | {item['published_at']} | {item['content']}") return "\n---\n".join(results[:max_results])这里有个细节值得展开:topic参数。普通搜索和新闻搜索返回的内容形态差异很大。问“最新政策”用 news 主题命中率更高;问“怎么做”这类教程类问题用 general 主题更好。我让模型通过time_range参数间接表达意图,比如词里带“今天”“紧急”时传d,带“本月”时传w,其余情况传空值,效果比单一搜索模式好不少。
3.3 双通道融合与最终回答生成
接入网络搜索工具之后,剩下的就是把它嵌进正常的 Agent 工作流。我的思路是先用一个“检索决策”节点判断是否需要调用搜索工具,然后并行执行本地检索和网络搜索,最后统一交给生成节点。
def retrieve(query: str, kb, need_web: bool): local_docs = kb.similarity_search(query, k=3) local_score = max(d.metadata.get("score", 0) for d in local_docs) if local_docs else 0 web_context = "" if need_web or local_score < 0.25: web_context = WebSearchTool()._run(query, max_results=5) return local_docs, web_context生成提示词我这样组织:先给本地知识库内容,再给网络搜索内容,然后明确区分两者的优先级。当两者信息冲突时(比如旧文档与新报道的发布时间线不同),要求模型优先采信时间标注更晚的内容,同时指出冲突。这个设计让答案不仅信息全面,还能向用户展示信息判断过程,可信度提升明显。
我把完整流程封装成了一个 FastAPI 接口,支持同步调用和流式返回。流式返回对用户体验很关键,因为联网搜索往往比本地检索多 2 到 4 秒,不好好做流式会让用户以为服务挂了。
4. 踩坑记录与性能调优
4.1 高频问题排查实录
改造过程不是一次就顺的,我前前后后踩了不少坑,整理成表格方便对照排查:
| 问题现象 | 根因 | 解决办法 |
|---|---|---|
| 搜索触发太频繁,普通历史问题也会联网 | 置信度阈值偏高 | 下调阈值,加入时间敏感词前置判断 |
| 网络搜索结果相关性差,牛头不对马嘴 | 原话直接搜索,未做改写 | 增加查询改写重试逻辑 |
| 模型输出自相矛盾,引用了过时内容 | 未给模型区分新旧信息优先级 | 提示词显式标明时间顺位规则 |
| 搜索 API 偶尔超时,整个请求被拖垮 | 没有设置超时与降级 | 搜索工具加 8 秒超时,超时则自动降级为纯本地检索 |
| 上下文塞满搜索摘要,生成长答案被截断 | 注入内容未做截断压缩 | 每条摘要强制控制在 200 字内,总条数不超过 6 |
| 新开发了一个智能招聘工具,知识库中完全没有相关文档 | 未考虑知识库不存在该主题的情况 | 本地检索若全部为空,直接强制联网并提示用户 |
这里面最值得说一句的是超时降级。网络请求不可控因素太多,第三方 API 偶尔抽风是常态。如果不做降级,一次搜索超时就会把整个 Agent 请求拖垮,用户感知到的就是“完全不可用”,比答不上来还伤体验。我设置的原则是:网络搜索是锦上添花,本地检索是保底,任何网络异常都不能阻断本地应答。
4.2 延迟与成本控制心得
联网搜索带来的最大代价就是延迟。本地向量检索通常在 200 毫秒内,一次 Tavily 调用呢,实测下来 1 到 3 秒之间波动,碰上慢的能到 5 秒。这个延迟对对话式应用来说感知很强,我有几个应对手段:
- 并行发起本地检索和网络搜索,而不是串行等待。反正本地检索只消耗毫秒级资源,并行后总耗时约等于网络搜索耗时。
- 结果缓存。相同或相似的问题在短时间内(比如 5 分钟)重复查询时,直接命中缓存。热点问题经常被多个人同时问到,这个收益非常可观。
- 使用流式输出让用户先看到局部响应,而不是白屏等待 4 秒。心理感知上的“变快”和真实变快同样重要。
成本方面,搜索 API 按调用次数计费(Tavily 有免费额度,超出后按千次计费)。我的策略是默认关闭联网,只有满足条件才触发;同一会话内对同一热点问题的追问,直接用上一轮的搜索结果,不重新搜索。这样把每次会话的搜索调用次数压到了 1.5 次左右,成本基本可控。
4.3 一个重要认知:Agent 不等于搜索框
接入搜索后,我很快发现一个需要纠正的产品行为:Agent 会倾向于什么问题都先搜一把,哪怕用户问的是知识库里已经写得很清楚的老问题。这本质上是因为模型对“搜索”这个工具有极高的调用倾向,它觉得搜索是万能解题工具。
解决办法是在系统提示词里明确约束:已经存在于本地知识库的高置信度内容,不允许联网,直接基于本地内容回答。同时把本地检索的 top 文档拼进提示词的“参考依据”区,让模型感知到“我已经有了足够信息”,降低它的搜索冲动。实测下来,联网搜索触发率从最初的 62% 降到了 28%,而回答质量没有明显下降,反而因为减少了无关信息的干扰变得更稳定了。
5. 效果验证与后续扩展
5.1 改造前后的效果对照
做完改造后,我用一组问题做了对照测试,覆盖历史知识、最近热点和混合类问题。这里挑几个有代表性的结果:
| 测试问题 | 改造前(仅本地知识库) | 改造后(双通道) |
|---|---|---|
| 去年上线的某产品保修政策是什么 | 正确,引用知识库原文 | 正确,优先本地结果,搜索未触发 |
| 某公司最新发布的财报数据是多少 | 错误,回答停留在上季度 | 正确,搜索命中最新报道,并标注发布时间 |
| 本地知识库完全没有的新项目背景 | 答“无法回答” | 给出网络搜索结果摘要,并附来源链接 |
| 需要结合内部文档和行业动态的分析题 | 只分析内部资料 | 内部资料 + 最新行业动态融合输出 |
整体数据上,我统计了 100 条混合测试问题:答案准确率从改造前的 71% 提升到了 89%,其中热点类问题的准确率从 33% 直接拉到 94%。代价是平均响应延迟增加了约 2.2 秒,但通过流式输出和缓存,用户侧感知影响不大。这个投入产出比我认为是相当划算的。
5.2 一些可以继续深挖的扩展方向
这套“本地知识库 + 联网搜索”的框架跑通之后,后续扩展空间还很大,我计划中的几个方向列在这里,供大家参考:
- 多轮对话中的搜索记忆:同一话题的追问不要重复搜索,而是沿用上一轮搜索结果并做增量更新。目前我的方案是只做了简单缓存,严格来说是“同一问题”缓存,不是“同一话题”延续。
- 垂直领域搜索源定制:比如技术类问题限定只搜官方文档和代码仓库,财经问题限定权威新闻媒体,能显著降低噪声。Tavily 支持自定义 domains 参数,一行配置就能实现。
- 信息可信度分级:把搜索来源按域名权威性、作者背景、引用频次分级,在注入上下文时带上可信度标签,让模型在引用时更有判断力。
- 自动评估回捞:每次回答后记录用户是否追问、是否满意,对不满意的问题重新排查检索链路,形成闭环优化。这也是我目前在迭代的重点。
我个人的实际操作体会是,接入网络搜索并不复杂,真正的难点在于判断“什么时候该搜”和“搜完怎么融合”。前者决定效果上限,后者决定输出质量。如果你也打算做类似改造,建议先拿 20 条覆盖历史、热点、歧义问题的测试集反复调阈值和提示词,跑通后再上公开渠道,能少走不少弯路。