Skip to content

RAG 检索增强生成 ​

模型的知识来自训练数据,它不知道你的内部文档、最新的产品手册、上周刚改的退款政策。RAG(Retrieval-Augmented Generation,检索增强生成)的做法是:回答之前,先从你的资料库里找出相关内容,放进上下文,让模型照着资料回答。

做一个能跑的 RAG 演示很简单,做一个可靠的 RAG 却会遇到很多问题:

  • 退款政策已经从 7 天改成了 15 天,问答机器人还在自信地回答“7 天”;
  • 用户问“错误码 E1023 是什么意思”,系统检索出一堆不相关的段落;
  • 普通员工问了一个问题,回答里引用了只有管理层才能看的文档;
  • 资料里根本没有答案,模型还是编了一个看起来很专业的回答。

读完这篇你会

  • 说清 RAG 的建索引流程和问答流程
  • 分别评估“检索”和“生成”两个环节的质量
  • 处理索引过期、关键词匹配、分块、权限和无答案等问题

1. 原理:开卷考试 ​

可以把 RAG 想成一场开卷考试:模型是考生,你的资料库是一本很厚的参考书。考生很聪明,但考试时间有限,只能看你翻给他的那几页。

  • 翻对了页,他能答得又准又快,还能指出答案在第几页;
  • 翻错了页,再聪明的考生也只能根据错误的资料作答。

所以 RAG 的质量,首先取决于“翻页”(检索)的质量,其次才是“答题”(生成)的质量。

RAG 分成两条流程:

text
建索引(资料变化时运行):
  文档 → 清洗 → 切成片段 → 每个片段转成向量 → 连同元数据存入索引

问答(每次提问时运行):
  用户问题 → 检索相关片段(向量 + 关键词)→ 重排序取前几条
          → 和问题一起组装成提示词 → 模型回答并标注引用 → 校验引用

2. 为什么不是把资料“训练进模型” ​

方式知识放在哪里更新成本能否给出处
只靠模型模型参数里无法更新不能
微调模型参数里需要重新训练不能
RAG外部资料库增删改文档即可能

知识经常变化、需要给出处、需要精确引用原文的场景,RAG 几乎总是更合适。微调更适合改变模型的风格、格式和领域表达习惯,而不是灌输具体的事实。两者也可以叠加使用。

3. 分块:把长文档切成片段 ​

长文档不能整篇放进上下文,要切成片段(chunk)。粒度是一个权衡:

粒度优点缺点
太小(一句话)匹配精准失去上下文,片段单独看不懂
太大(一整章)上下文完整无关内容多,相关性被稀释,占用 Token
适中(一段或一个小节)平衡需要处理好边界

实践建议:

  • 优先按文档结构切:按标题、段落、列表项切,而不是机械地按固定字数切;
  • 相邻片段适当重叠,避免一句完整的话被切成两半;
  • 每个片段带上所在章节的标题作为前缀,比如“退款政策 > 申请条件:……”,单独看也知道它在讲什么;
  • 保存元数据:文档 ID、标题、章节、版本、更新时间、访问权限。

4. 检索:向量和关键词结合 ​

向量检索:嵌入模型把文本转换成一串数字(向量),意思相近的文本,向量也相近。它能理解“重置密码”和“找回密码”是一回事。

关键词检索(如 BM25):按词语匹配,对专有名词、编号、代码标识符更准。

生产环境里通常两者结合(混合检索),各取一批结果再合并。

重排序:向量检索速度快但比较粗糙,常常召回一批“大致相关”的片段。用一个更精细的重排序模型对这批结果重新打分,只取最相关的几条放进上下文:

text
混合检索召回 50 条(快、粗)→ 重排序模型打分(慢、准)→ 取前 5 条

5. 组装提示词:要求依据资料回答 ​

text
## 规则
- 只根据下面“资料”部分的内容回答,不要使用资料之外的知识
- 每个关键结论后面标注来源编号,如 [S2]
- 资料中没有答案时,直接回答“资料中没有找到相关信息”,不要猜测

## 资料
[S1] 退款政策 > 申请条件(2026-09-01 版):自签收之日起 15 天内可申请……
[S2] 退款政策 > 到账时间:审核通过后 3~5 个工作日原路退回……

## 问题
(用户的问题)

模型返回后,程序再校验引用:答案里出现的每个来源编号,都必须是这次真正提供给它的片段。引用了不存在的编号,说明模型在编造。

6. 坑一:文档更新了,回答还是旧的 ​

现象:退款期限已经从 7 天改成了 15 天,问答机器人还在回答“7 天”,而且引用看起来也没问题。用户照着旧信息操作,产生了投诉。

原因:索引是源文档的一份独立快照,不会自动跟着变。文档改了,但索引里存的还是旧片段,检索命中的自然是旧内容。

修复:把“文档变更 → 重新建索引”做成自动流程:

text
文档新增 / 修改 / 删除
  → 触发增量索引(只处理变化的文档)
  → 写入时带上文档版本和更新时间
  → 删除该文档的旧片段

回答时在引用里展示文档的更新时间;定期抽查索引和源文档是否一致。RAG 的知识新鲜度取决于索引的同步机制,而不是模型。

7. 坑二:检索到的都是不相关的内容 ​

现象:用户问的问题在资料里明明有答案,但检索出的片段都不相关,模型只能回答“没有找到”或者答非所问。

修复:先单独评估检索的质量:

  1. 准备几十个真实的问题,人工标注每个问题“应该被检索到的片段”;
  2. 统计正确片段出现在前 5 条结果里的比例(召回率);
  3. 每次调整分块方式、嵌入模型、检索参数时,重新跑一遍,用数据判断是变好还是变坏。

常见的改进方向:调整分块粒度、给片段加上标题前缀、改用混合检索、增加重排序,或者先让模型把口语化的问题改写成更适合检索的查询。

8. 坑三:编号、型号、专有名词搜不到 ​

现象:用户问“错误码 E1023 是什么意思”,资料里有一篇专门讲 E1023 的文档,却没有被检索出来。

原因:纯向量检索擅长“意思相近”,但对编号、型号、代码这类没有语义的字符串不敏感。“E1023”和“E1032”在向量上可能非常接近。

修复:加入关键词检索,形成混合检索;对于明显是编号、型号的查询,可以直接用精确匹配优先。

9. 坑四:分块切断了表格和步骤 ​

现象:问“退款需要几步”,回答只给出了前三步。原来的文档有六个步骤,被切到了两个片段里,只检索到了第一个。

修复:

  • 按结构分块,把一个完整的列表、表格、代码块作为一个整体;
  • 片段太大时,再在内部按行切分,并给每一部分加上表头或标题;
  • 检索到某个片段时,可以把它前后相邻的片段也一起带上。

10. 坑五:检索到了用户无权查看的文档 ​

现象:普通员工问了一个关于薪酬的问题,回答引用了只有人事部门才能看到的文档内容。

原因:检索时没有按权限过滤,所有文档都在同一个索引里,谁问都能检索到。

修复:在检索阶段就按权限过滤,而不是生成后再过滤:

  • 每个片段的元数据里记录访问权限(所属租户、部门、角色、文档 ID);
  • 检索时带上当前用户的权限条件,只在他能访问的范围内检索;
  • 文档的权限变更后,同步更新索引中的元数据。

一旦无权查看的内容进入了上下文,就不能指望模型“自觉不说”。

11. 坑六:资料里没有答案,模型还是编了一个 ​

现象:用户问了一个资料里完全没有涉及的问题,模型给出了一个看起来很专业的回答,但完全是编的。

修复:

  • 在提示词里明确要求“资料中没有答案时直接说明”(见第 5 节);
  • 检索结果的相关度都很低时,程序直接返回“没有找到相关资料”,不再调用模型;
  • 校验引用:没有引用任何资料的结论,标记为“未经资料支持”或直接不展示;
  • 在测试集里加入一些“资料中没有答案”的问题,确认系统会如实回答。

排查思路:先分清是“翻错页”还是“答错题” ​

一个问题回答错了,先看这次检索到了哪些片段:

  • 正确的片段没有被检索到 → 检索问题:检查分块、嵌入模型、关键词检索、权限过滤、索引是否过期;
  • 正确的片段检索到了,但回答还是错的 → 生成问题:检查提示词、片段的排列顺序和数量、模型能力;
  • 片段本身的内容就是错的或过时的 → 数据问题:检查源文档和索引同步。

为此,日志里要记录每次问答的:问题、检索到的片段 ID 和得分、最终的提示词、模型回答和引用。

检查清单 ​

  • [ ] 按文档结构分块,片段带标题前缀和完整元数据
  • [ ] 使用混合检索,并有重排序
  • [ ] 文档变更会自动触发增量索引,旧片段会被删除
  • [ ] 检索阶段按用户权限过滤
  • [ ] 提示词要求依据资料回答、标注来源、没有答案时如实说明
  • [ ] 程序校验引用,低相关度时不强行回答
  • [ ] 有标注好的测试问题集,分别评估检索和生成的质量

延伸阅读 ​

为复用而记录,为理解而整理。