一、知识库的通病:回答停在训练那天
我给一个内部文档助手做过 RAG,踩过最典型的坑:用户问"我们行业最近的 XX 政策有什么变化",模型答得头头是道,但引用的是一年前的旧文档。原因不复杂------RAG 的检索范围只有你喂进去的那批本地文档,外面世界变了,知识库根本不知道。
解决办法业内叫"接地"(grounding):生成之前,先去外部实时搜一把,把最新的搜索结果一起塞进上下文。这篇讲我用 SerpBase(官网 https://serpbase.dev)给 RAG 补实时数据的做法,纯工程视角,代码可以直接改。
二、两种"给模型接搜索"的方式
先摆清楚选择:
- 让模型读网页:把 URL 抓下来、清洗成文本再喂进去。灵活,但慢、贵,还要处理页面里的广告和导航噪音。
- 调搜索 API:直接拿结构化的搜索结果(标题、链接、摘要),作为引用来源注入上下文。快,且返回结构统一,好做引用标注。
做内部知识助手,我选了后者------搜索 API 返回的就是"干净的结果列表",天然适合做引用。SerpBase 的 /google/search 一次调用(1 credit)就返回 organic 数组,每条带 title、link、snippet,正好是 RAG 想要的检索结果。
三、最小接线:搜索 → 拼上下文 → 生成
先写一个检索函数,把查询结果转成带编号的引用片段:
python
import requests
def retrieve(query, top_k=5):
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "你的_API_KEY"},
json={"q": query, "gl": "us", "hl": "en"}
)
data = r.json()
docs = []
for item in data.get("organic", [])[:top_k]:
docs.append({
"title": item["title"],
"url": item["link"],
"text": item.get("snippet", "")
})
return docs
然后拼成上下文,要求模型只用这些来源回答:
python
def build_prompt(question, docs):
ctx = "\n".join(
f"[{i+1}] {d['title']}\n{d['text']}\n{d['url']}"
for i, d in enumerate(docs)
)
return (
f"只根据以下实时搜索结果回答,引用时标注编号:\n\n{ctx}\n\n"
f"问题:{question}"
)
把 build_prompt 的输出丢给任意 LLM,回答里就会带上 12 这样的引用,用户能点回去看原文。这就是 RAG 接实时数据的最小闭环。
四、把它接进现有 RAG 知识库
内部知识库一般已经有一套本地向量检索,我的做法是"本地优先,实时兜底":
- 先跑本地检索,如果相似度够高,直接用本地文档回答
- 本地命中不足(比如问到最新动态、竞品信息),再触发搜索 API 检索
- 两路结果合并,按来源标注(本地文档 vs 实时来源)
这样既保住了内部文档的私密性,又补上了外部实时数据的短板。
五、几个必须处理的工程细节
第一,搜索结果要"投影",不能整包塞。 organic 里字段很多,进 prompt 前只留 title/snippet/link,别把整条 JSON 塞进去,token 会爆。
第二,加时效过滤。 SERP 里有的结果是很久以前的,做实时问答要优先取近期的。如果结果里带 date 字段,可以据此排序或过滤。
第三,缓存高频问题。 同一个问题短时间被问多次,缓存检索结果就好,不必每次都调 API。我的缓存键用 (query, gl, hl),缺一个字段都会串味。
第四,失败要有兜底。 搜索 API 调用失败时,退回到纯本地回答,别让整个问答流程挂掉。响应里的 status 字段(0 为成功)就是判断依据。
六、为什么用搜索 API 而不是自己爬
做 RAG 接地最怕的就是"检索这一步不稳定"。自己爬要维护代理池、处理验证码、解析随时改版的页面结构;搜索 API 把这一步变成一次 POST,返回结构化 JSON,还带 credits_charged 能对账成本。对个人开发者和小团队来说,省下来的维护时间远比那点调用费值钱。
SerpBase 新账户有 100 次免费搜索、不绑卡,拿这个额度足够把上面的检索链路完整跑通一遍,验证效果后再决定要不要上生产。
七、行动清单
- 先用
retrieve()跑通一个查询,看返回结构符不符合你的引用格式 - 写
build_prompt(),让模型只依据搜索结果回答 - 接进现有 RAG,做"本地优先 + 实时兜底"
- 加字段投影、时效过滤、缓存和失败兜底
- 用免费额度评估召回和延迟是否达标
RAG 的短板往往不在模型,在检索的覆盖面。给它接上实时搜索,很多"答得不对"的问题会自然消失。