我的LLM应用终于不胡说了:给RAG加一层实时搜索的踩坑记录
做RAG的同学应该都懂,LLM一本正经胡说八道的样子有多让人血压飙升。上周我把实时搜索接进RAG链路里,幻觉率直接掉了四成。这篇文章不是讲原理的,纯纯的踩坑实录。
一、先还原一下现场
我做的项目是一个技术问答助手,接的是某大厂的通用模型,知识库用的是我们自己攒的技术文档,大概几千篇博客和官方文档的切片。
一开始效果还行,毕竟都是内部文档,答案挺准。但最近用户开始问一些新技术的问题,比如"Next.js 15 的 Server Actions 有什么变化"、“Django 5.0 的生成字段怎么用”。
模型怎么回的?它把 Next.js 14 的内容套到 15 上,Server Actions 的 API 签名都变了,它还按老版本教。用户照着做,代码直接报错。
原因很直接:知识库是静态的,模型的知识截止到训练数据,新东西它没见过。
传统的 RAG 思路是定期更新知识库,但我们这技术文档更新速度根本追不上社区。我算了下,要做到 T+1 更新,至少得配一个专门做数据同步的人,成本太高。
二、实时搜索:给模型装个"外接大脑"
跟同事讨论的时候,他说了个思路:与其让模型背所有知识,不如让它学会"查资料"。
具体做法:用户提问后,先不急着调模型,而是把问题丢给搜索引擎,拿前几条结果作为上下文,再让模型基于这些实时信息生成答案。
这就是给 RAG 加一层实时检索(Real-time Retrieval)。
2.1 为什么不用 Bing/Google 官方 API?
我一开始看的是 Google Custom Search JSON API,结果一看定价:$5 per 1000 queries。我日活虽然不高,但一个用户会话可能触发 3-5 次搜索,算下来一个月几百刀起步。
Bing Web Search API 稍微便宜点,但也要按月订阅,最低档几十刀。
对我这种独立项目来说,按月订阅就是原罪。流量波动大的时候,要么浪费额度,要么不够用。
后来试了下 SerpBase,按量付费,$3 起步,用多少扣多少。我先拿 100 次免费额度做了个 POC。
2.2 链路设计
整个流程大概这样:
用户提问
-> 意图识别(判断是否需要搜索)
-> 生成搜索 query(有时需要改写)
-> SerpBase API 搜索
-> 清洗/截断搜索结果
-> 拼接 prompt(系统指令 + 搜索结果 + 用户问题)
-> LLM 生成答案
-> 返回答案 + 引用来源
最关键的两步:意图识别和query 改写。
不是所有问题都需要搜索。比如"你好"、"你是谁"这种,直接模型回就行。我加了个简单的规则:问题中包含版本号、时间、“最新”、"现在"等关键词时,触发搜索。
Query 改写也很重要。用户问的是"Next.js 15 Server Actions 有啥变化",直接搜这个没问题。但如果用户问的是"我刚才说的那个功能在 Next.js 新版本里还能用吗",这种有上下文的,需要把历史对话考虑进去生成搜索词。
三、核心代码:搜索+上下文拼接
用的 Python + LangChain,但为了减少依赖,核心逻辑我自己写的,没全用 LangChain 的封装。
3.1 搜索模块
import requests
from typing import List, Dict
class RealtimeSearch:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.serpbase.com/v1/search"
def search(self, query: str, num_results: int = 5) -> List[Dict]:
params = {
"q": query,
"api_key": self.api_key,
"num": num_results,
"hl": "zh-CN",
"gl": "cn"
}
resp = requests.get(self.base_url, params=params, timeout=30)
resp.raise_for_status()
data = resp.json()
organic = data.get("organic_results", [])
return [
{
"title": item.get("title", ""),
"link": item.get("link", ""),
"snippet": item.get("snippet", "")
}
for item in organic[:num_results]
]
3.2 上下文拼接
拿到搜索结果后,不能直接全塞进 prompt。Google 结果的 snippet 通常几十到一百多字,5 条加起来可能就 500 token 左右,控制住别超模型窗口。
def build_prompt(question: str, search_results: List[Dict]) -> str:
context_parts = []
for i, res in enumerate(search_results, 1):
context_parts.append(
f"[{i}] {res['title']}\n{res['snippet']}\n来源: {res['link']}"
)
context = "\n\n".join(context_parts)
prompt = f"""你是一个技术助手。请基于以下参考信息回答用户问题。
如果参考信息不足以回答问题,请明确说明。
回答时请标注信息来源编号(如[1]、[2])。
参考信息:
{context}
用户问题:{question}
请用中文回答:"""
return prompt
3.3 完整调用链路
class RAGWithSearch:
def __init__(self, serp_key: str, llm_client):
self.search = RealtimeSearch(serp_key)
self.llm = llm_client # 你的大模型客户端
def needs_search(self, question: str) -> bool:
triggers = ["最新", "现在", "版本", "2024", "2025", "2026",
"怎么解决", "报错", "error", "bug"]
return any(t in question.lower() for t in triggers)
def answer(self, question: str) -> Dict:
if not self.needs_search(question):
# 直接走向量检索
return self._vector_rag(question)
# 实时搜索增强
results = self.search.search(question, num_results=5)
prompt = build_prompt(question, results)
response = self.llm.chat(prompt)
return {
"answer": response,
"sources": results,
"used_search": True
}
def _vector_rag(self, question: str) -> Dict:
# 你的原有向量检索逻辑
...
四、踩过的几个大坑
坑1:搜索结果质量不稳定
不是每次搜索都能拿到好结果。有些 query 搜出来的前几条是广告落地页、聚合站、或者 SEO 垃圾站,内容质量差。
我的解决方案是加个简单的内容过滤层:
BLOCKED_DOMAINS = ["some-spam-site.com", "low-quality-aggregator.cn"]
def filter_results(results: List[Dict]) -> List[Dict]:
filtered = []
for r in results:
domain = r["link"].split("/")[2].replace("www.", "")
if domain not in BLOCKED_DOMAINS and len(r["snippet"]) > 30:
filtered.append(r)
return filtered[:5]
还可以加一层:snippet 里如果包含大量重复关键词(SEO 填充特征),权重降低。
坑2:搜索 query 太短,结果太泛
用户问"Django 怎么样",直接搜这个,返回的结果太宽泛,对回答没帮助。
解决方案是在 query 生成阶段做意图扩展。简单做法是把问题翻译成英文再搜(技术内容英文结果通常更好),或者加一些限定词。
比如"Django 怎么样"扩展成"Django framework pros cons 2025",结果质量明显上升。
坑3:延迟问题
实时搜索意味着同步请求搜索引擎,这会增加整体响应时间。我测了一下:
- 纯向量检索:平均 800ms
- 向量检索 + SerpBase 搜索:平均 2.2s
- 如果搜索触发重试:可能到 3s+
对用户体验有影响。我的优化策略:
- 异步预搜索:用户输入时,根据已输入内容提前发搜索请求,缓存结果
- 流式返回:先返回向量检索结果,同时后台去搜,搜到了再增量更新答案(需要前端配合)
- 控制超时:搜索设置 3 秒超时,超时就只用向量库的结果兜底
坑4:成本比想象中低,但要注意配额
我算了下,一个典型会话触发 2 次搜索,每次搜 5 条结果。
SerpBase 普通搜索是 1 credit/次。我日活大概 200 个会话,一天 400 次搜索,一个月 1.2 万次。
用 $10 的 Starter 包(2 万次,$0.50/千次),一个月刚好。如果日活翻十倍,上 $50 的 Growth 包(12.5 万次,$0.40/千次),也才 50 刀。
相比 Bing API 的月订阅,这个成本结构对项目早期友好太多了。
五、效果对比
我做了个 A/B 测试,两周数据:
| 指标 | 纯向量 RAG | RAG + 实时搜索 |
|---|---|---|
| 幻觉率(人工抽检100条) | 28% | 16% |
| 用户满意度(1-5分) | 3.6 | 4.2 |
| 平均响应时间 | 0.8s | 2.1s |
| 月搜索成本 | $0 | ~$6 |
幻觉率降了 43%,这个提升我个人非常满意。响应时间虽然变长了,但我们在前端加了 loading 文案和来源引用展示,用户感知反而觉得"更靠谱了"。
六、延伸思考:实时搜索 + RAG 的边界
这种模式也不是万能的,说几个不适合的场景:
- 高并发聊天:如果每秒几百条消息,实时搜索的 QPS 和延迟会成为瓶颈
- 企业内部知识:如果问题完全依赖内部文档(如公司规章制度),外部搜索只会引入噪音
- 需要深度推理的问题:搜索给的是碎片化信息,复杂推理还是得靠模型本身
我的建议是:把实时搜索当作 RAG 的一个"插件",按需启用,而不是完全替代向量检索。
七、总结
如果你也在做 LLM 应用,被幻觉问题困扰,我真心建议试试加一层实时搜索。技术实现不复杂,核心就三步:判断要不要搜 -> 去搜 -> 把结果塞进 prompt。
SerpBase 这种按量付费的 API,对独立开发者和中小团队很友好,100 次免费额度足够你做个完整 POC。反正试试成本几乎为零,但幻觉率下降的收益是实实在在的。
对了,如果你搜出来的结果质量不稳定,记得加上我上面说的内容过滤和 query 改写,这两个小改动对最终效果影响巨大。
下一步我打算试试把搜索结果也做向量化存进向量库,搞个"动态知识库",有新搜索结果就增量更新。如果跑通了再写篇续集。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)