跳到主要内容
Cowers://
全部文章
检索与 RAG

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(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):来源文件、页码、章节、时间、分类、权限标签……
字段 职责 谁会用到
正文 参与语义匹配;被提取出来作为模型的回答依据 检索算法 + 模型
元数据 标注这段话从哪来;作为检索时的过滤条件 检索系统 + 最终展示给用户

元数据不是"顺手记一下",它支撑三件事:

  1. 溯源——答案下方那句"来源:《退款政策》第 2 页"完全来自元数据。
  2. 过滤——"只查 2026 年的文档""只查财务部的资料"是靠元数据缩小范围,而不是靠语义。
  3. 权限——"这个用户只能看到他有权限的资料"必须在检索阶段就用元数据挡掉。

⚠️ 只存正文,不存元数据 错误操作: 建库时只把文本内容和向量存进去,认为来源信息"以后需要再补"。

实际结果: 答案无法给出出处,用户无从验证;发现某条知识过时了也定位不到源文件;权限过滤根本无法实现,只能全库开放或全库关闭。

原因: 切块之后,文本块与原始文档的联系只剩元数据这一条。丢掉它,块就成了无主的文字碎片。

正确做法: 在切块的同时写入元数据,而不是事后补。

⚠️ 「补的成本是重建整个索引」说得过重了。补元数据要不要重建,取决于补什么:

  • 补标量字段(来源、页码、部门、时间):多数向量库支持 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.pdfLangGraph.pdfMilvus.pdfRedis.pdfDocker.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 把语义变成可计算的距离 精确字面匹配
向量库 存储并按相似度返回候选 判断候选是否真的相关
检索器 提供统一的取用方式 保存数据
重排 把最相关的排到最前 找回压根没被召回的内容
模型 依据资料组织语言 提供资料本身

复习重点

可直接复述的结论

  1. RAG 不修改模型参数,它改变的是模型每次收到的输入;模型什么都没记住。
  2. 知识库负责"知道什么",模型负责"怎么说"——这是全篇的职责主线。
  3. 要新事实用 RAG,要新风格用微调;两者解决的不是同一个问题。
  4. 建库离线、查询在线,两条流水线必须共享同一个 Embedding 模型;换模型等于重建索引。
  5. 脏数据在 RAG 里会被检索机制主动挑出来,所以清洗必须发生在入库之前。
  6. 切块的本质是给检索提供可定位的粒度;重叠只是边界保险,不是越大越好。
  7. 没有元数据就没有溯源、过滤和权限,而它只能在切块时写入,事后补要重建索引。
  8. 向量检索匹配意思,关键词检索匹配字面,两者互补,生产系统通常都要。
  9. 检索器是接口,向量库是实现;检索只负责"找",此时模型还没被调用。
  10. 噪声导致的错误更难被发现(不等于绝对代价更大);召回可以多,进 Prompt 必须精,中间靠重排把关。
  11. 允许模型说"不知道",是 RAG 可信度的来源。
  12. 答案不以文字形式存在于某一段里时(实时数据、全量统计、全局概括),RAG 不是正确的工具。

自测问题

  1. 为什么说 RAG 没有让模型"学会"任何新知识?
  2. 文档向量提前算好,问题向量每次现算——这个不对称是怎么来的?
  3. 两个 Embedding 模型输出的向量维度相同,为什么仍然不能混用?这类故障为什么很难发现?
  4. 为什么说 RAG 里的脏数据比普通数据分析里的脏数据危害更大?
  5. 元数据为什么必须在切块阶段写入,而不能等需要时再补?
  6. 用户问"X-2041 型号怎么保修",纯向量检索可能出什么问题?
  7. 向量库和检索器的区别是什么?为什么检索器可以不依赖 Embedding?
  8. "检索 20 条"与"不要调大 Top K"看起来矛盾,实际区别在哪?
  9. HyDE 用一段可能不准确的生成文本去检索,为什么不会污染最终答案?
  10. 用户问"我们去年一共卖了多少台设备",为什么这个问题不该交给 RAG?