RAG 概念
RAG(Retrieval-Augmented Generation,检索增强生成)是一种在模型回答之前,先从外部知识库检索相关资料、再把资料作为依据交给模型的方法。
RAG 概念:让模型在回答之前先找依据
修订说明(核实于 2026-08-13,东八区) 本篇的主要问题是把「朴素的单轮向量文档问答」写成了整个 RAG 范式。修订如下:
- 「RAG 不修改模型参数」不是定义性特征。2020 年的原始 RAG 论文里检索器与生成器是联合训练的。已限定为「本篇讨论的推理时 RAG 实现」。
- 「入库和查询必须同一个模型」应为「向量空间兼容」:
bge/e5等模型对 query 和 document 用不同前缀,仍属兼容;且这条约束只管向量那一路,BM25 根本不用 Embedding。- 「检索时模型还没被调用」与第十节的查询改写 / HyDE 冲突,已改为「生成那次调用还没发生」。
- 「补元数据的成本是重建整个索引」过重:补标量字段多数可 upsert,向量可复用。
- 「答案必须逐字存在于某一段」把 RAG 划得太窄:多跳 RAG、GraphRAG、结构化 RAG 都能综合多处证据,且 GraphRAG 本身仍是 RAG。
- 「知识全部来自训练」「训练后一律不知道」「不知道时不会沉默」「生产系统通常两路都要」「噪声比信息不足更危险」「允许拒答就是可信度来源」等绝对化表述,已改为有条件的说法。
阅读提示 本笔记面向已经了解大语言模型(LLM)与 Prompt、正在学习 RAG 的读者。主线是:RAG 解决什么问题 → 两条流水线如何分工 → 每个组件为什么存在 → 什么时候不该用 RAG。
本篇只讲与框架无关的机制,不涉及代码分层、数据模型与选型——那部分见RAG架构设计;效果不好时如何定位与调优见RAG优化方案;关键词检索的算法细节见BM25 算法原理。
⚠️ = 常见误解 🆚 = 对比说明 💡 = 机制或选择建议
目录
- 一、RAG 到底解决什么问题?
- 二、为什么是两条流水线而不是一条?
- 三、为什么资料在入库前必须先清洗?
- 四、为什么必须先把文档切块?
- 五、一个文本块为什么要分成正文和元数据?
- 六、Embedding 为什么能表示"意思相近"?
- 七、检索为什么不只有一种方式?
- 八、为什么要有"检索器"这一层抽象?
- 九、为什么用户的原话往往不能直接拿去检索?
- 十、为什么资料不是越多越好?
- 十一、什么时候不该用 RAG?
- 速查表
- 复习重点
核心概念 **RAG(Retrieval-Augmented Generation,检索增强生成)**是一种在模型回答之前,先从外部知识库检索相关资料、再把资料作为依据交给模型的方法。
它的本质是一次职责拆分:知识库负责"知道什么",模型负责"怎么说"。
最小闭环可以记成:提问 → 检索 → 拼接依据 → 生成 → 附带来源。
本篇讲的是「推理时 RAG」,这不是 RAG 的全部 上面说「模型参数完全不变」,这对本篇介绍的这一类实现是准确的,也是当前工程实践中最常见的一类——但它不是 RAG 的定义性特征。
2020 年提出 RAG 的原始论文里,检索器和生成器是联合训练的,参数会更新。后续也有一批工作(RETRO、Atlas、RA-DIT 等)把检索信号带进训练过程。
所以准确的表述是:本篇讨论的是「推理时检索、不改动模型参数」的 RAG 实现。看到「RAG 就是不训练模型」这种说法时,知道它省略了前提即可——这个省略在工程语境下无伤大雅,但在读论文或和研究方向的人交流时会造成误解。
一、RAG 到底解决什么问题?
大语言模型的内在知识来自训练阶段(不含本次请求带进去的上下文),因此有三个无法靠"换个更大的模型"绕开的限制:
| 限制 | 表现 |
|---|---|
| 知识有截止时间 | 训练之后发生的事情基本不知道——少数情况下能从训练数据里的预告、趋势推测个大概,但那是猜测不是事实,反而更危险 |
| 不包含私有资料 | 公司内部文档、个人笔记从未参与训练 |
| 无法说明依据 | 答案是概率生成的结果,模型自己也指不出出处 |
当模型被问到不知道的内容时,它往往不会沉默,而是生成一个"看起来合理"的答案——这就是幻觉(Hallucination)。
(现代模型经过对齐训练后,遇到明显超出知识范围的问题有时确实会回答"我不确定"。但这个行为并不可靠:它高度依赖问题的措辞,且模型越是"半懂不懂"越倾向于编造——恰恰是最难被发现的那种情况。所以工程上仍应假设它会编。)
RAG 的思路不是让模型记住更多,而是在提问的那一刻,把相关资料放进它的上下文里。
没有 RAG: 问题 ─────────────────────► 模型 ──► 凭记忆回答(可能编造)
使用 RAG: 问题 ──► 检索知识库 ──► 相关资料 ──┐
├──► 模型 ──► 有依据的回答 + 来源
问题 ────────────────────────────┘🆚 RAG、微调与长上下文的分工
三者经常被拿来比较,但解决的其实是不同问题:
| 方案 | 改变什么 | 擅长 | 不擅长 |
|---|---|---|---|
| RAG | 不改模型,改输入 | 知识频繁更新、需要溯源、私有资料、量大 | 改变模型的语气、格式偏好与推理风格 |
| 微调(Fine-tuning) | 改模型参数 | 固定的输出风格、领域术语、特定任务格式 | 记忆事实(成本高、更新慢、仍然无法溯源) |
| 长上下文(Long Context) | 不改模型,全量塞入 | 资料很少且稳定、一次性分析单份长文档 | 知识库规模大、成本敏感、需要精确定位依据 |
💡 一句话判断 要模型知道新事实,用 RAG;要模型换一种说话方式,用微调。两者不冲突,可以叠加。
⚠️ 以为 RAG 让模型"学会"了新知识 错误理解: 把资料喂给 RAG 系统之后,认为模型已经掌握了这些内容,下次不给资料也能答对。
实际结果: 换一个不带检索的接口去问同样的问题,模型立刻打回原形,重新开始编造。
原因: RAG 全程没有修改任何模型参数。资料只是作为本次请求的输入文本存在,请求结束即消失。
正确理解: 模型在 RAG 里扮演的是"阅读理解答题者",不是"记忆者"。每一次回答都必须重新提供依据。
二、为什么是两条流水线而不是一条?
RAG 由两条独立的流水线组成,它们在不同时间运行,但在向量这一路上必须共享兼容的编码方案(最简单也最常见的做法就是用同一个 Embedding 模型)。
两点补充,避免把这条约束记得过死 ① 真正的要求是「向量空间兼容」,不严格等于「同一个模型」。 有些模型族为 query 和 document 设计了不同的编码方式——比如
bge系列要求给查询加指令前缀而文档不加,e5系列用query:/passage:区分。这时两侧的输入处理并不相同,但它们被训练成落在同一个空间里,所以是兼容的。判断标准是模型方规定的编码协议,不是"两边代码是不是一模一样"。② 这条约束只管向量那一路。 BM25 那一路根本不使用 Embedding(见第七节),它依赖的是分词器和词表——那里的一致性要求是"入库和查询用同一个分词器",与 Embedding 模型无关。混合检索系统里,两路各有各的一致性约束。
建库阶段(离线,提前完成,资料变化时才重跑)
─────────────────────────────────────────
原始文档 ──► 清洗 ──► 切块 ──► 文本块(正文 + 元数据)──► 向量化 ──► 存入向量库
查询阶段(在线,每次提问都要走一遍)
─────────────────────────────────────────
用户问题 ──► 查询改写 ──► 向量化 ──► 检索 ──► Top K 文本块
│
▼
提取正文、拼接为 Context
│
▼
Prompt(Context + 问题)──► 模型 ──► 答案 + 来源为什么要拆成两条? 因为向量化是重活。一个知识库可能有几十万个文本块,若每次提问都重新计算一遍全部向量,单次问答将耗时数分钟且成本极高。所以:
文档向量 → 提前算好、持久化保存(算一次,用无数次)
问题向量 → 每次提问现算一个(只有一句话,开销极小)⚠️ 以为检索器会直接给出答案 错误理解: 认为"检索"这一步返回的就是问题的答案。
实际结果: 拿到的是一个文本块列表,不是一段回答;直接返回给用户会得到几段莫名其妙的原文摘录。
原因: 检索与生成是两个独立的阶段。检索只做"找资料",此时还没有进入生成那一步。
正确理解: 检索器的职责是找,模型的职责是答。中间还差"提取正文 → 拼接 Context → 构造 Prompt"这三步。
⚠️ 注意措辞:说「检索阶段模型还没有被调用」在最基础的流程里成立,但一旦加了查询改写、HyDE、意图识别这类检索前置增强(见第十节),LLM 在检索之前就已经被调用过一次了。准确的说法是「生成答案的那次调用还没有发生」——要区分的是"检索"和"生成"两个职责,不是"有没有用到模型"。
三、为什么资料在入库前必须先清洗?
原始文档几乎不可能直接用。PDF 会带页眉页脚和页码,网页会带导航栏和广告,Word 会带修订痕迹,多份文档之间还会有大量重复内容。
关键在于:RAG 会把噪声放大一次,而不是稀释一次。
脏数据进入知识库
↓
噪声被切进文本块,与正文混在同一个块里
↓
这个块的向量被噪声"拉偏",语义不再纯粹
↓
检索时,系统主动把最像问题的那些块捞出来——包括捞错的
↓
模型拿到错误依据,且因为"有依据"而答得更自信普通数据分析里的脏数据只是拉低平均值;RAG 里的脏数据会被检索机制主动挑选出来送到模型面前。这是它必须在入库前处理的原因。
三类需要处理的噪声
| 类型 | 典型表现 | 后果 |
|---|---|---|
| 无意义内容 | 页眉页脚、页码、导航栏、版权声明、水印文字 | 稀释块的语义,每个块都掺一点无关词 |
| 重复内容 | 同一份文档的多个版本、复制粘贴的段落 | 检索结果被同义块占满,Top K 名额被浪费 |
| 结构丢失 | PDF 分栏被读成交错乱序、表格塌成一行、标题层级消失 | 语义彻底错乱,且切块无法找到正确边界 |
💡 为什么优先用规则而不是用模型清洗
| 手段 | 成本 | 可预测性 | 适用 |
|---|---|---|---|
| 规则(正则、标签剥离、去重、模板匹配) | 极低 | 高,同样输入必然同样输出 | 页眉页脚、固定模板、编码统一 |
| 模型清洗 | 高,且随文档量线性增长 | 低,可能顺手改写甚至编造内容 | 规则确实无能为力的非结构化残片 |
规则能解决的部分优先用规则。让模型改写正文还有一个隐蔽风险:它可能在"整理格式"时悄悄修改事实,而这类错误会直接固化进知识库。
💡 清洗的边界 清洗的目标是去掉不该进入知识库的内容,不是改写该进入的内容。原文的措辞要尽量保持原样——因为最终要展示给用户看的来源片段就是它。
四、为什么必须先把文档切块?
假设一份 100 页的 PDF 只生成一个向量,检索时能判断的只有"这整份文档跟问题相关吗",无法定位到具体是哪一段。粒度太粗,找到了也等于没找到。
长文档 → 切成多个小文本块(chunk) → 每块各自生成向量 → 检索时返回最相关的几块块大小的权衡
| 块大小 | 问题 |
|---|---|
| 过小(如每块 10 字) | 块数量爆炸,向量化、存储、检索成本全面上升;语义被切散,检索到的片段缺少上下文,模型看不懂 |
| 过大(如每块两万字) | 检索粒度过粗,一个块里混入大量无关内容,等于把噪声一起交给模型 |
没有普适最优值,它取决于文档类型:法律条文和聊天记录的合理块大小完全不同。
chunk_overlap:为什么相邻块要重复一部分
重叠区指相邻文本块之间刻意保留的重复内容,作用是防止一个完整语义恰好被切块边界劈开。
无重叠(语义被截断,两块都不完整):
原文:Checkpointer 用于保存工作流状态,支持暂停后继续执行。
块1:Checkpointer 用于保存工作流
块2:状态,支持暂停后继续执行
有重叠(完整语义至少完整地存在于一侧):
块1:Checkpointer 用于保存工作流状态
块2:保存工作流状态,支持暂停后继续执行⚠️ 把重叠区当成"越大越安全" 错误操作: 担心语义被切断,把重叠设成块大小的一半甚至更多。
实际结果: 向量库体积成倍膨胀;检索返回的前几名高度雷同,实际信息量远低于名义上的 Top K;向量化与存储成本同步上升。
原因: 重叠部分的文本会被重复向量化并重复存储,相邻块因此变得高度相似,在检索排序中互相挤占名额。
正确做法: 重叠通常取块大小的 10%~20%,作为边界保险而不是主要手段。真正减少截断的办法是下面的"结构优先"。
切块策略:结构优先于长度
文档本身的标题、段落、章节就是作者划好的语义边界,直接按固定字符数切等于无视它们。更合理的顺序是:
先按标题 / 段落 / 句子切分
↓
若某一段仍然超长
↓
再对这一段按固定长度继续切这样能尽量保证"一个小标题下的完整说明"落在同一个块里。
记忆口诀 chunk_size 决定每块有多大,chunk_overlap 决定相邻块重复多少。 前者控制粒度,后者只是边界保险。
五、一个文本块为什么要分成正文和元数据?
每个文本块通常封装成一个文档对象,包含两个部分:
文本块
├── 正文(page_content):这一块的实际文字内容
└── 元数据(metadata):来源文件、页码、章节、时间、分类、权限标签……| 字段 | 职责 | 谁会用到 |
|---|---|---|
| 正文 | 参与语义匹配;被提取出来作为模型的回答依据 | 检索算法 + 模型 |
| 元数据 | 标注这段话从哪来;作为检索时的过滤条件 | 检索系统 + 最终展示给用户 |
元数据不是"顺手记一下",它支撑三件事:
- 溯源——答案下方那句"来源:《退款政策》第 2 页"完全来自元数据。
- 过滤——"只查 2026 年的文档""只查财务部的资料"是靠元数据缩小范围,而不是靠语义。
- 权限——"这个用户只能看到他有权限的资料"必须在检索阶段就用元数据挡掉。
⚠️ 只存正文,不存元数据 错误操作: 建库时只把文本内容和向量存进去,认为来源信息"以后需要再补"。
实际结果: 答案无法给出出处,用户无从验证;发现某条知识过时了也定位不到源文件;权限过滤根本无法实现,只能全库开放或全库关闭。
原因: 切块之后,文本块与原始文档的联系只剩元数据这一条。丢掉它,块就成了无主的文字碎片。
正确做法: 在切块的同时写入元数据,而不是事后补。
⚠️ 「补的成本是重建整个索引」说得过重了。补元数据要不要重建,取决于补什么:
- 补标量字段(来源、页码、部门、时间):多数向量库支持
upsert或直接更新标量列,向量可以原样保留,不必重算 Embedding。Milvus 还可以用动态字段先接住计划外的字段。- 要把元数据拼进正文参与检索(如把章节标题前缀加到块文本里以提升召回):这改变了被向量化的文本,必须重新向量化。
- 建表时没预留字段:Milvus 的 Schema 不能随意加字段,这种情况才真的要重建 Collection。
所以真正的教训不是"补不起",而是建表时就把元数据字段规划好——留字段的成本接近零,重建 Collection 的成本才是真的高。
记忆口诀 正文负责"检索和回答",元数据负责"溯源和过滤"。
六、Embedding 为什么能表示"意思相近"?
Embedding(向量化)是把一段文本转换成一串数字(向量)的过程,转换的目标是让语义相近的文本在向量空间中距离更近,即使它们用词完全不同:
"保存工作流状态" ≈ "流程状态持久化" ← 没有一个共同的词,但向量距离很近
"苹果手机很好用" ≠ "苹果富含维生素" ← 共享"苹果"二字,但向量距离很远这正是它相对关键词匹配的价值:匹配的是意思,不是字面。
于是"找相关资料"这个模糊问题,就被转换成了一个可计算的数学问题——在向量空间里找离问题向量最近的那几个点。
关键约束:同一套知识库必须用同一个模型
⚠️ 建库和查询用了不同的 Embedding 模型 错误操作: 建库时用 A 模型,后来换成 B 模型做查询;或中途升级模型却没有重建索引。只要向量维度对得上,程序就不会报错。
实际结果: 系统正常运行、正常返回结果,但召回内容近乎随机。这是一类沉默的故障——没有异常、没有报错,只有效果莫名其妙地差。
原因: 每个模型都有自己的语义空间,维度相同不代表坐标含义相同。就像两个人各自画地图,都用"三个数字"表示位置,但一个用经纬高、一个用街区门牌——数字能比较,比较的结果没有意义。
正确做法: 把"文档向量与查询向量必须来自同一模型(含同一版本)"当作硬约束。换模型意味着整个索引必须重建,没有增量迁移的捷径。工程上如何强制保证见RAG架构设计。
七、检索为什么不只有一种方式?
向量检索并非万能。它擅长语义,恰恰不擅长精确字符串。
| 检索方式 | 原理 | 擅长 | 典型失败场景 |
|---|---|---|---|
| 向量检索(语义) | 比较向量距离 | 同义表达、概念性提问、口语化问题 | 精确匹配产品型号 X-2041,容易召回 X-2014 |
| 关键词检索(BM25 等) | 分词后计算词频与稀有度 | 型号、人名、错误码、API 名称、专有名词 | 用户换了个说法就完全召回不到 |
| 混合检索 | 两路并行,合并去重后重新排序 | 兼顾语义与精确匹配 | 需要处理两套分数如何融合的问题 |
用户问题
│
┌─────────┴─────────┐
▼ ▼
关键词检索 向量检索
(精确字面) (语义相近)
│ │
└─────────┬─────────┘
▼
合并、去重、排序
▼
返回文本块列表两者是互补而非替代关系:向量检索找得回"说法不同的同一件事",关键词检索找得准"一个字都不能错的东西"。混合检索值得作为生产系统的基线去评测——但要不要真的两路都上,应当由你自己数据上的对比结果决定:如果查询几乎全是自然语言长问句、极少出现型号和编号,单路向量可能已经够用,多一路只是多一份复杂度和延迟。BM25 的具体算法见BM25 算法原理,两路分数如何融合见RAG 优化方案。
八、为什么要有"检索器"这一层抽象?
不同的向量数据库,各自的调用方法、参数命名、返回结构都不一样。如果业务代码直接调用某一个数据库的原生方法,那么一旦更换底层实现,所有调用点都要改。
**检索器(Retriever)**就是为此设立的一层统一抽象:无论底层是什么,上层只用同一种方式调用,输入一个查询,返回一个文本块列表。
业务代码 ──► 检索器(统一接口) ──┬──► 向量库
├──► BM25 关键词索引
├──► 混合检索
└──► 任意自定义检索逻辑🆚 向量库与检索器不是同一层概念
| 是什么 | 是否持有数据 | |
|---|---|---|
| 向量库(Vector Store) | 一种具体的存储与检索实现 | 是,真实存放向量、正文与元数据 |
| 检索器(Retriever) | 一种统一的检索能力接口 | 否,自己不存任何东西 |
比喻:向量库是银行的保险柜(实际存放东西),检索器是取款机(统一的对外取用方式)。取款机背后换成哪家银行的系统,用户的操作方式不变。
这也解释了为什么检索器不一定依赖 Embedding:基于关键词的检索器完全不做向量化,照样满足"输入查询、返回文本块列表"这个契约。抽象规定的是契约,不是实现手段。
💡 这层抽象换来了什么 换向量库、加一路关键词检索、接入重排——这些改动都应该被关在检索器内部,上层业务代码一行不用动。具体的分层与接口设计见RAG 架构设计。
九、为什么用户的原话往往不能直接拿去检索?
用户提问的方式,和资料被写下来的方式,天然存在表述鸿沟。常见的三类失配:
| 失配类型 | 用户问的 | 为什么检索不到 |
|---|---|---|
| 指代不全 | "那它支持退款吗?" | "它"指什么只存在于上文,这句话单独拿去检索几乎没有信息量 |
| 用词不同 | "这个咋整" | 文档里写的是"故障处理流程",口语与书面语的向量距离可能很远 |
| 一问多义 | "A 和 B 有什么区别,哪个更快?" | 这实际是三个子问题,混在一起检索会得到一堆两头不靠的结果 |
查询改写就是在检索之前,先把用户的原话加工成更适合检索的形式。主要有三种思路:
① 重写:结合对话历史补全指代和省略
"那它支持退款吗?" → "X 产品是否支持退款?"
② 多查询:让模型生成若干个说法不同的等价问题,分别检索后合并
"这个咋整" → "如何处理故障" / "故障排查步骤" / "报错解决方法"
③ 假设文档:先让模型编一段"理想答案",用这段答案去检索
依据是:答案与答案的语义距离,通常比问题与答案的语义距离更近第三种思路(HyDE)有个反直觉之处:故意生成一段可能不准确的文本去做检索。这没问题——它只用来找资料,从不出现在最终答案里,找到真正的原文之后就被丢弃。
💡 什么时候才需要它 查询改写会额外增加一次模型调用,直接抬高每次问答的延迟和成本。单轮、表述清晰的问题不需要它。先把基础检索做准,确认失败确实源于"问题本身不适合检索",再加这一层。 各类改写策略的取舍见RAG 优化方案。
十、为什么资料不是越多越好?
检索到的资料并不会全部塞给模型。原因有两个,第二个更关键。
原因一:上下文窗口有限。 模型单次能处理的 Token 数有上限,全塞进去会直接超限。
原因二:噪声导致的错误比信息不足更难被发现。 这才是核心。
设知识库中有 LangChain.pdf、LangGraph.pdf、Milvus.pdf、Redis.pdf、Docker.pdf,用户问"LangGraph 的 Checkpointer 是什么?":
| 返回数量 | 结果 |
|---|---|
| 取前 3 条 | 基本都是 LangGraph 相关内容,模型容易答准 |
| 取前 20 条 | 前几条相关,后十几条掺进 Docker、Redis 等无关内容,模型被干扰,甚至可能从错误段落里提取答案 |
两类失败的可发现性不同:信息不足时,模型至少有机会说"资料中没有提到",这是个可见的信号;而被噪声包围时,模型可能从错误的资料里自信地组织出一个错误答案——它带着来源、语气笃定,看起来和正确答案没有区别。
⚠️ 但别把这条推成「噪声一定比信息不足更危险」。两者的代价取决于场景:医疗、法律这类高风险场景里,"漏掉一条关键禁忌"的后果可能远重于"多看几段无关内容"。而且噪声也不是只有坏处——适度的相关背景有时反而帮助模型组织表述。
准确的说法是:噪声导致的错误更难被发现,所以更需要主动防范(靠重排把关、靠 Faithfulness 指标监控);而信息不足导致的错误比较显眼,靠召回率指标就能看出来。"更危险"指的是可发现性,不是绝对代价。
⚠️ 效果不好就直接调大返回数量 错误操作: 发现答案不准,把返回条数从 3 改成 20,期待"多给点资料总没坏处"。
实际结果: 上下文被无关内容填满,成本翻数倍,准确率反而下降。
原因: 返回数量是按相似度排名截断的。调大它并不会让正确内容排得更靠前,只会把排名靠后的噪声一起放进来。
正确做法: 若正确内容根本没被召回,要解决的是检索本身(改写、混合检索、切块);若正确内容已召回但排在后面,要解决的是排序——这正是重排(Rerank)的用途。
💡 一个容易混淆的地方:召回多 ≠ 塞给模型多
引入重排之后,"取多少条"会分裂成两个不同的数字:
无重排: 检索 Top 3 ─────────────────────────────► 全部交给模型
(一个数字同时决定召回量和入参量,因此必须小)
有重排: 检索 Top 20 ──► 重排模型精细打分 ──► 保留最相关 3~5 条 ──► 交给模型
(召回追求"别漏") (排序追求"最准") (入参追求"别吵")两个阶段的目标不同:召回阶段宁可多拿一些,避免遗漏;进入 Prompt 的阶段必须精简,避免干扰。 所以"检索 20 条"和"不要调大返回数量"并不矛盾——前提是中间必须有重排这一道精筛。没有重排就直接放大数量,等于把噪声原封不动地端给模型。
生成阶段的提示词纪律
拿到干净的资料之后,Prompt 里通常要明确规定:
- 只能依据给定资料回答,不允许补充资料之外的内容
- 资料中没有答案时,明确说"不知道",而不是猜
- 标注答案来自哪一份资料的哪个位置
第二条尤其重要:允许模型拒答,是 RAG 可信度的必要条件。 一个从不说"不知道"的问答系统,用户无法判断哪句话可以相信。
⚠️ 但它只是必要条件,不是充分条件。「Prompt 里写了允许拒答」和「系统真的会在该拒答时拒答」是两回事——Prompt 是软约束,模型在追问或半相关上下文面前照样会编。可信度真正来自拒答机制被验证过:评测集里要有"应当拒答"的样本,要监控拒答率和误拒率,最好再加一层答案后验校验。具体做法见 RAG架构设计 第八节。
十一、什么时候不该用 RAG?
RAG 的适用前提是:回答所需的证据,已经以文字形式存在于知识库里,并且能被检索到。这个前提不成立时,它就不是正确的工具。
注意这个前提说的是「证据存在」,不是「答案逐字存在于某一段」 早前的表述是「答案已经存在于某份文档的某一段里」——这只描述了最基础的单跳 passage RAG,会把 RAG 的能力边界划得过窄。
实际上答案完全可以是综合多处证据推出来的:
- 多跳 RAG:先检索出 A,再用 A 里的线索去检索 B,答案来自 A + B 的组合;
- GraphRAG:把实体和关系建成图,答案沿关系路径推导,可能没有任何一段原文直接写着它;
- 结构化 / 混合 RAG:一部分证据来自文档,一部分来自数据库查询结果。
所以下表里「全局性问题 → GraphRAG」那一行要理解对:GraphRAG 本身仍然是 RAG(它检索的是图结构而非文本块),它是 RAG 的一种形态,不是 RAG 之外的替代方案。
更准确的边界是:当所需信息在知识库里根本不存在(实时数据)、或需要全量精确聚合(统计)、或需要执行动作时,RAG 不适用——这三条才是真正的硬边界,下表其余各行仍在 RAG 家族的射程内,只是需要更复杂的形态。
| 场景 | 为什么 RAG 不合适 | 更合适的做法 |
|---|---|---|
| 需要实时数据(库存、股价、天气) | 知识库是快照,永远滞后 | 工具调用 / API,见Agent 概念 |
| 需要精确统计("一共多少单""平均值是多少") | 检索只能召回片段,无法做全量聚合 | 查数据库,用 SQL |
| 全局性问题("这批文档整体讲了什么") | 答案不在任何单独一段里,而分散在全部内容中 | 分层摘要 / GraphRAG,见RAG 优化方案 |
| 知识极少且长期不变(十几条规则) | 建索引的复杂度超过收益 | 直接写进 System Prompt |
| 需要多步推理与执行动作 | RAG 只找资料,不做决策,也不执行 | Agent,RAG 作为它的一个工具 |
💡 与 Agent 的分工 RAG 解决"去哪里找知识",Agent 解决"下一步采取什么动作"。 两者常常组合:Agent 把 RAG 当作众多工具中的一个来调用。
速查表
| 术语 | 一句话定义 |
|---|---|
| RAG | 回答前先检索外部资料、再据此生成答案的方法 |
| 幻觉 | 模型在不知道的情况下生成看似合理的错误内容 |
| 建库阶段 | 离线流水线:清洗 → 切块 → 向量化 → 入库 |
| 查询阶段 | 在线流水线:改写 → 向量化 → 检索 → 拼接 → 生成 |
| Chunk(文本块) | 文档切分后的片段,是检索的最小单位 |
| chunk_size | 每个文本块的目标大小,决定检索粒度 |
| chunk_overlap | 相邻块之间重复的内容量,防止语义被边界截断 |
| 正文(page_content) | 文本块的实际内容,用于匹配和作为回答依据 |
| 元数据(metadata) | 来源、页码、章节、权限等附加信息,用于溯源与过滤 |
| Embedding | 把文本转换为语义向量的过程或模型 |
| 语义空间 | 某个 Embedding 模型的向量坐标体系,不同模型之间不可比 |
| 向量库 | 存储向量、正文与元数据的具体实现 |
| Top K | 检索返回的文本块数量 |
| 检索器(Retriever) | 统一的检索抽象接口,屏蔽底层实现差异 |
| 混合检索 | 语义检索与关键词检索并行,合并去重后排序 |
| 查询改写 | 检索前把用户原话加工成更适合检索的形式 |
| HyDE | 先生成假设答案、再用它去检索的改写策略 |
| 重排(Rerank) | 对召回结果二次精细打分排序,提升进入 Prompt 的精度 |
| Context | 从检索结果中提取并拼接、作为回答依据的文本 |
| Prompt | 包含 Context 与用户问题的最终输入文本 |
组件速记
| 组件 | 一句话职责 | 不能替代什么 |
|---|---|---|
| 清洗 | 把不该进知识库的内容挡在门外 | 修正原文中本身错误的事实 |
| 切块 | 把文档拆成可定位的检索单位 | 弥补文档结构本身的缺失 |
| 元数据 | 记录来源与过滤维度 | 参与语义匹配 |
| Embedding | 把语义变成可计算的距离 | 精确字面匹配 |
| 向量库 | 存储并按相似度返回候选 | 判断候选是否真的相关 |
| 检索器 | 提供统一的取用方式 | 保存数据 |
| 重排 | 把最相关的排到最前 | 找回压根没被召回的内容 |
| 模型 | 依据资料组织语言 | 提供资料本身 |
复习重点
可直接复述的结论
- RAG 不修改模型参数,它改变的是模型每次收到的输入;模型什么都没记住。
- 知识库负责"知道什么",模型负责"怎么说"——这是全篇的职责主线。
- 要新事实用 RAG,要新风格用微调;两者解决的不是同一个问题。
- 建库离线、查询在线,两条流水线必须共享同一个 Embedding 模型;换模型等于重建索引。
- 脏数据在 RAG 里会被检索机制主动挑出来,所以清洗必须发生在入库之前。
- 切块的本质是给检索提供可定位的粒度;重叠只是边界保险,不是越大越好。
- 没有元数据就没有溯源、过滤和权限,而它只能在切块时写入,事后补要重建索引。
- 向量检索匹配意思,关键词检索匹配字面,两者互补,生产系统通常都要。
- 检索器是接口,向量库是实现;检索只负责"找",此时模型还没被调用。
- 噪声导致的错误更难被发现(不等于绝对代价更大);召回可以多,进 Prompt 必须精,中间靠重排把关。
- 允许模型说"不知道",是 RAG 可信度的来源。
- 答案不以文字形式存在于某一段里时(实时数据、全量统计、全局概括),RAG 不是正确的工具。
自测问题
- 为什么说 RAG 没有让模型"学会"任何新知识?
- 文档向量提前算好,问题向量每次现算——这个不对称是怎么来的?
- 两个 Embedding 模型输出的向量维度相同,为什么仍然不能混用?这类故障为什么很难发现?
- 为什么说 RAG 里的脏数据比普通数据分析里的脏数据危害更大?
- 元数据为什么必须在切块阶段写入,而不能等需要时再补?
- 用户问"X-2041 型号怎么保修",纯向量检索可能出什么问题?
- 向量库和检索器的区别是什么?为什么检索器可以不依赖 Embedding?
- "检索 20 条"与"不要调大 Top K"看起来矛盾,实际区别在哪?
- HyDE 用一段可能不准确的生成文本去检索,为什么不会污染最终答案?
- 用户问"我们去年一共卖了多少台设备",为什么这个问题不该交给 RAG?