RAG 检索增强生成
LLM 的知识来自训练数据,它不知道你的内部文档、最新的产品手册、昨天才发布的政策。RAG(Retrieval-Augmented Generation,检索增强生成)解决的就是这个问题:在生成回答前,先从你的知识库里检索出相关片段,塞进上下文,让模型"看着资料"回答,而不是凭记忆瞎编。它的价值不只是减少幻觉,更在于让回答有据可查、可追溯、可更新。
为什么需要外挂知识
把知识"放进模型"有两条路:微调,或检索。微调把知识写进权重,更新一次要重新训练,成本高、周期长,而且模型仍可能在细节上出错,也无法告诉你"这条答案出自哪份文档"。检索则是把知识放在模型之外,每次回答时动态取用:
| 方式 | 知识在哪 | 更新成本 | 可追溯 |
|---|---|---|---|
| 纯模型 | 权重里 | 高(重新训练) | 否 |
| 微调 | 权重里 | 中(需训练) | 否 |
| RAG | 外部库 | 低(增删文档) | 是 |
对于"知识经常变""需要给出出处""需要精确引用原文"的场景,RAG 几乎总是更合适。微调更适合改变模型的风格、语气、领域术语习惯,而不是灌入具体事实。
RAG 的基本流程
一次 RAG 问答由四个阶段组成:
text
用户提问
→ 把问题转成向量
→ 在知识库向量索引里检索最相关的片段
→ 把片段和问题一起喂给模型
→ 模型基于片段生成回答关键在于中间两步:怎么把文档切成片段(分块),怎么找到相关片段(检索)。这两步的质量,直接决定最终回答的质量——检索阶段拿错了片段,再强的模型也只能基于错资料作答。
分块策略
知识库里的长文档不能整篇塞进上下文,必须切成片段(chunk)。分块粒度是个权衡:
| 粒度 | 优点 | 缺点 |
|---|---|---|
| 太小(单句) | 检索精准 | 丢失上下文,片段孤立难理解 |
| 太大(整章) | 上下文完整 | 噪音多、相关性被稀释、费 Token |
| 适中(一段) | 平衡 | 需要重叠避免切断语义 |
实践里常用的做法是按固定长度切,并让相邻块有重叠(overlap),保证一个完整语义不会正好被切两半。更精细的做法是按文档结构切(标题、段落、列表项),保留语义边界。分块没有万能最优解,要结合自己的文档结构试验。
嵌入与向量检索
嵌入(embedding)把一段文本映射成一个高维向量,语义相近的文本向量也相近。检索时,把用户问题也转成向量,在向量库里找距离最近的几个片段。
text
问题: "怎么重置密码"
→ 向量 [0.12, -0.04, ...]
→ 最近邻: "忘记密码时,点击登录页的'忘记密码'重置..." (相似度 0.91)要点:
- 嵌入模型决定语义匹配质量:好的嵌入模型能识别"重置密码"和"找回密码"是同一意思,差的只会匹配字面。选嵌入模型要用自己的语料测,而不是看榜单。
- 向量库负责高效近邻搜索:常用方案都支持大规模近似检索,工程上不必自己实现。
- 关键词检索仍有价值:向量检索擅长语义相似,但对专有名词、编号、代码标识符,关键词检索(如 BM25)往往更准。两者融合(hybrid)是生产环境的常见做法。
重排序
向量检索速度快但不够精细,往往召回一批"大致相关"的片段。重排序(rerank)用一个更精细但更慢的模型,对召回的这批片段重新打分,把最相关的排到前面,再截取 top-K 喂给模型。
text
检索召回 50 个片段(快,粗)
→ 重排序模型精细打分(慢,准)
→ 取 top 5 塞进上下文这一步能显著提升最终质量,因为它过滤掉了"向量距离近但实际不相关"的噪音,让模型拿到的是真正有用的片段。
引用与可追溯性
RAG 相比纯模型的最大优势是"可追溯":回答里每一条论断都能指向具体的源文档片段。实现上,把检索到的片段带上网元信息(文档名、章节、位置),要求模型在回答时标注引用:
json
{
"answer": "重置密码需点击登录页的'忘记密码'。",
"citations": [
{ "doc": "用户手册", "section": "3.2 账号安全", "snippet": "忘记密码时,点击..." }
]
}引用不只是为了好看——它是用户校验答案、建立信任的依据,也是定位检索错误的抓手。如果模型答错了,顺着引用能快速看出是"检索没找到对的片段"还是"检索对了但模型理解错了",从而知道该优化哪一环。
常见错误:文档更新了,索引没刷新
这是 RAG 系统最隐蔽的过期数据问题——模型答得很流畅,但答的是旧内容:
text
知识库里修改了退款政策(7 天 → 15 天)
但向量索引还是旧的 embedding
→ 模型回答:"退款期限是 7 天"(已经错了)症状:产品政策、价格、流程明明已经更新,问答机器人却还在 confidently 地回答旧版本。用户照着旧信息操作(比如以为能 7 天退款,实际已经改成 15 天),产生客诉。最坑的是:模型回答得很自信,引用也"看起来"对,很难第一时间发现数据过期。
为什么错:向量库是一份独立的快照,它不会自动跟随源文档变化。你改了知识库的 markdown,但 embedding 还是基于旧文本算的——检索命中的是旧片段,模型自然照着旧的答。
正确做法:把"文档变更 → 重新 embedding → 更新索引"做成一条流水线,而不是手动想起来才跑:
text
文档提交/更新
→ 触发增量索引(只重算变更的部分)
→ 带版本号或时间戳写入向量库
→ 旧的过期片段标记或删除定期校验索引与源文档的一致性,给答案带上"基于哪份文档、哪个版本"的元信息。RAG 的知识新鲜度,取决于索引同步机制,不是模型本身。
RAG 还是微调
常见的误区是"二选一"。实际上两者解决不同问题:
- 知识更新、事实问答、需要引用 → RAG。知识在外部,改文档即生效。
- 改变模型行为风格、领域术语、固定格式输出 → 微调。
- 两者可以叠加:微调让模型更懂领域表达方式,RAG 提供具体事实。
先做 RAG。多数"模型不懂我的业务"的问题,根源是模型没看到你的资料,而不是它没学会你的领域风格。RAG 见效快、成本低、可追溯,应当作为首选;微调作为 RAG 解决不了行为层面问题时的补充。
务实的清单
- 检索质量测过吗?拿真实问题测召回,而不是凭感觉。
- 分块粒度是否贴合文档结构,有没有重叠?
- 是否定期评估嵌入模型和重排序在自己的语料上的表现?
- 回答是否带引用,能不能从答案追回源片段?
- 文档更新后,索引是否同步刷新,有没有过期知识残留?
RAG 的本质,是把"知识"从模型权重里拿出来,放进一个你能管理、能更新、能审计的外部系统。把检索这一环做扎实,上层用哪个模型、怎么写 prompt,都是在可靠地基上盖楼。