RAG的承诺与陷阱:一个工程师的诚实起步 / The Promise and the Pit of RAG
第 1 章 · RAG的承诺与陷阱:一个工程师的诚实起步
The Promise and the Pit of RAG — An Engineer's Honest Beginning
> 任何复杂的工程系统,在被第一次严肃地复盘之前,都只是一堆乐观主义的化石。 > — 改编自 Gerald Weinberg
---
1.1 一场价值 480 万美元的"幻觉":ContractAI 的复盘
2023 年 11 月 17 日,凌晨 2 点 47 分,ContractAI(一家总部位于波士顿、专注私募基金合同审查的法律科技创业公司)的 CTO 林昊然被一通电话惊醒。打电话的是他们的最大客户——一家管理规模 87 亿美元的私募基金的首席合规官。对方的语气克制但冰冷:"林先生,你们的系统在过去 72 小时内生成了 47 条引用,其中至少 23 条对应的合同条款在我们的存档中根本不存在。我的法律团队刚刚花了整个周末核实这件事。你们打算怎么解释?"
48 小时后,ContractAI 的工程团队和客户的法务团队联合完成了一次事后剖析(post-mortem)。结论被整理成一份 14 页的内部备忘录,其核心结论只有一句话:"GPT-4 没有出错。它只是在认真地引用了不存在的材料。"
这句话听起来像悖论,但它精确地揭示了 RAG(Retrieval-Augmented Generation)系统在 2023 年的核心病理学。让我们回到那个具体的故障现场。
故障现场的技术回放
ContractAI 的产品架构在 2023 年 10 月上线,号称"全球第一款真正理解私募基金合同的 AI"。它的核心 pipeline 是当时业界最标准的 Naive RAG 形态:
1. 客户上传 LPA(Limited Partnership Agreement,有限合伙协议)、Side Letter(侧函)、Subscription Agreement(认购协议)等私募基金核心法律文件; 2. 系统用 LangChain 的 `RecursiveCharacterTextSplitter`,以 1000 个字符为块大小、200 个字符为重叠,对每份 PDF 进行切分; 3. 切分后的文本块通过 OpenAI `text-embedding-ada-002` 模型生成 1536 维向量,存入 Pinecone; 4. 用户提问时,系统将问题同样编码为向量,在 Pinecone 中检索 top-5; 5. 把这 5 个文本块与系统提示词拼接,送入 GPT-4-32k; 6. GPT-4 输出回答,并在文本中标注 `[1] [2] [3]` 这样的引用编号。
表面上看,这是一个教科书式的 RAG 实现。LangChain 官方教程、Pinecone 的 quickstart、OpenAI 的 cookbook——任何一个 2023 年的工程师都能在两小时内搭出这个 demo。
但 2023 年 11 月的客户问题,就出在这个"教科书式"上。
故障点 #1:PDF 解析丢掉了层级信息。 私募基金合同是高度结构化的法律文书。一份 LPA 通常包含 12–18 个 Article(章)、每个 Article 下有若干 Section(节)、每个 Section 下有若干 Subsection(款)。条款之间的引用关系非常密集,例如:
> *"Notwithstanding anything to the contrary in Section 3.2(a)(iv), the General Partner shall…"*
这里的 "Section 3.2(a)(iv)" 是一个精确的层级引用。但是 ContractAI 使用的 `PyPDFLoader` 是一种基于文本流的解析器,它会把整个 PDF 拉平成一个巨大的字符串,然后用 `RecursiveCharacterTextSplitter` 按字符数硬切。结果就是:一段关键的"豁免条款"可能被切成两个块,分别落在两个 1000 字符的窗口里,而这两个窗口在向量空间中与其他不相关的条款高度相似。
当客户的合规官问:"在什么情况下 GP(普通合伙人)可以单方面修改收益分配条款?" 系统检索到的 top-5 文本块里,没有一个完整包含相关条款,反而包含了几个表面相似但语义无关的段落。GPT-4 拿到这 5 个"残缺"的上下文,凭借其强大的语言建模能力,自行脑补出了一段看似合理的"引用",并以 `[3]` 的形式标注了出来。
故障点 #2:检索没有元数据过滤。 私募基金客户通常同时管理多只基金(Fund I、Fund II、Co-Investment Vehicle 等),每只基金的条款有细微差异。客户的合规官在提问时,并不会主动指明"针对 Fund II"——他们假设系统"应该知道"。但 ContractAI 的检索器是纯粹的 dense retrieval,没有 metadata 过滤,也没有 query understanding。结果就是:系统可能从 Fund I 的旧版条款中检索到了一段已被 Fund II 修订的条款,而 GPT-4 会自信地把旧版本呈现为现行版本。
故障点 #3:没有引用溯源校验。 这是最致命的工程失误。ContractAI 当时的产品逻辑是"信任 LLM 的输出",即把 GPT-4 生成的 `[1] [2] [3]` 直接展示给用户,而没有在输出后做一次"引用是否真实存在于检索结果中"的反向校验。如果做了这一步校验,至少那 23 条"幻觉引用"中的绝大多数都会被立刻拦下。但 ContractAI 团队的工程文化是"模型已经很强了,不要在它后面加太多规则",这个观点在 2023 年的 AI 圈非常流行。
复盘会上三个值得记住的教训
1. RAG 系统的失败模式不是"模型答错了",而是"系统提供了错误的材料,模型忠实地基于错误材料作答"。 林昊然后来在一次公开演讲中复盘道:"我们一开始以为是 prompt 不够好,调了两周 prompt,完全没用。后来发现是检索阶段就出错了。"
2. 法律、医疗、金融这些高 stakes(高风险)领域的 RAG 系统,必须有"硬约束"层。 所谓硬约束,就是无论模型怎么输出,都必须满足的不变式(invariant)。"引用编号必须真实指向检索结果中的某个文本块"就是一条硬约束。
3. 上线前的 acceptance test(验收测试)必须包含"对抗性样本"。 ContractAI 团队当时只有 happy path 的测试用例,没有诸如"提问中包含需要跨文档交叉验证的实体"、"提问要求引用某个 Article 的具体款"这样的对抗性测试。
480 万美元——这是 ContractAI 因为这次事故失去的客户合同总额,外加被该客户在行业内传播的负面口碑所间接损失的潜在客户。他们没有倒掉——这家公司今天仍然在运营,并成为 RAG 工程化做得最好的法律科技公司之一——但那次复盘彻底改变了他们的工程文化。
本章要回答的核心问题
ContractAI 的故事不是孤例。从 2023 年到 2025 年,几乎每一家认真做过生产级 RAG 的公司都经历过类似的事故。区别只在于:是发生在客户发现之后,还是发生在内部 review 阶段。
我们写这本书,不是为了给 RAG 唱赞歌。恰恰相反,我们相信 RAG 是当前大模型落地最务实的工程范式之一——但前提是你必须诚实地对待它的复杂性。RAG 不是"给大模型插上一个 U 盘"。它是四项相互耦合的子工程的组合:知识表征(Knowledge)、检索(Retrieval)、生成(Generation)、反馈(Feedback)。任何一个子工程的偷懒,都会在系统中产生蝴蝶效应。
这一章,我们要做三件事:
1. 提出一个分析 RAG 失败的三层根因框架; 2. 提出一个全书的四象限方法论骨架; 3. 用三个公开的生产案例做解剖。
读完本章,你应该能用一张"四象限诊断卡片"对自己或团队的 RAG 系统做一次 X 光级体检,并列出至少 5 个潜在失败面。
---
1.2 RAG 幻觉的三层根因分析
"幻觉"(hallucination)是 RAG 系统最臭名昭著的症状,但也是最容易被误诊的症状。
2024 年之前,很多团队把幻觉归咎于"模型不够大"或"模型还不够对齐"。这种诊断是错的。在大模型已经发展到 GPT-4、Claude 3、Gemini 1.5 这一代之后,纯粹的模型层幻觉(即"在没有任何上下文的 open-domain 问题上凭空捏造")已经不是生产事故的主要来源。 生产事故的主要来源,是 RAG pipeline 自身的工程缺陷。
我们提出一个三层根因模型。任何一次 RAG 幻觉,都必然发生在以下三个层次中的至少一个:
- **L1 · 检索失败(Retrieval Failure)**:系统没有把对的内容找出来。
- **L2 · 上下文构造失败(Context Construction Failure)**:系统找出了对的内容,但送到模型嘴边时已经被损坏、错位或稀释。
- **L3 · 模型归纳失败(Model Induction Failure)**:检索和上下文都正确,但模型在归纳阶段出错。
逐层展开。
---
L1 · 检索失败:最常见的隐形杀手
检索失败的形态至少有五种。我们用工程语言来定义它们:
(a) 召回缺失(Recall Miss)
用户问了一个相关问题,但 top-k 个结果中没有任何一个真正相关。这是 dense retrieval 的经典病态——语义相似度高 ≠ 语义相关。两个不同的"principal"在法律语境下可能指 GP(普通合伙人)和 LP(有限合伙人),向量空间里它们的距离可能很近,但在业务上是完全相反的角色。
判断方法:用 `recall@k`(k 召回率)这一指标。形式化定义为:
$$\text{Recall@k} = \frac{|\text{relevant} \cap \text{retrieved}_k|}{|\text{relevant}|}$$
如果你的 `recall@5` 长期低于 0.7,整个 RAG 系统的性能上限就被锁死了。
(b) 精度稀释(Precision Dilution)
top-k 中混入了大量不相关的文本块。常见原因是 chunk size 过大。一个 2000 字符的文本块可能同时包含"基金的成立日期"和"基金的清算条款",当用户问"基金如何清算"时,这块文本虽然相关,但其中的"成立日期"部分会稀释真正有用的信息密度。模型会被无关内容分散注意力。
判断方法:用 `precision@k`(k 精确率):
$$\text{Precision@k} = \frac{|\text{relevant} \cap \text{retrieved}_k|}{k}$$
(c) 关键词盲区(Lexical Blindness)
Dense retrieval 在精确关键词、专有名词、代码标识符、数字型号上的表现远不如 BM25 这样的稀疏检索。问"基金代码 9B4F-2023 的最新 NAV(净资产值)是多少",纯向量检索大概率会失败。
判断方法:构造一个"关键词 benchmark",专门测试系统对型号、人名、代码、日期的检索能力。
(d) 元数据盲区(Metadata Blindness)
用户的查询意图里有强烈的元数据约束("只看 Fund II"、"只看 2024 年之后的合同"、"只看与中国子公司相关的条款"),但检索器没有把元数据纳入过滤。1.1 节中 ContractAI 的故障点 #2 就是这种类型。
判断方法:构造"跨桶查询"测试集(cross-bucket query set),记录系统在没有元数据过滤时的 baseline。
(e) 查询理解失败(Query Understanding Failure)
用户的提问是模糊的、口语化的、或者包含大量代词。一个朴素的 RAG 系统会把用户的原始 query 直接送入 embedding 模型。一个成熟的 RAG 系统会先做一次 query rewriting,把"那个基金去年亏损后 GP 还能拿 carry 吗"改写成"如果基金发生亏损,普通合伙人是否仍然有权获得业绩报酬"。HyDE(Hypothetical Document Embeddings)、query expansion、step-back prompting 都是这个层面的技术。
判断方法:构造"自然语言查询 vs 检索关键词"配对集,测量两者在向量空间中的距离分布。
---
L2 · 上下文构造失败:被严重低估的故障层
即使检索阶段找出了正确的 top-k 文本块,这些文本块在送入模型之前,往往要经过多道加工工序。每一道工序都可能引入新的错误。
(a) 排序错误(Re-ranking Error)
很多 RAG 系统在 dense retrieval 之后会接一个 cross-encoder reranker(如 Cohere Rerank、bge-reranker)。Reranker 的目的是用更精细的 query-document 交互信号重新排序。但 reranker 也会犯错,尤其是当文档很长、包含多个主题时。
(b) 上下文窗口溢出(Context Window Overflow)
GPT-4 的上下文窗口从 8k 到 128k 不等,但"窗口大"不等于"用得满"。长上下文会导致三种现象:
1. Lost in the Middle:研究表明,LLM 对位于上下文中间位置的信息注意力显著低于首尾两端。 2. 注意力稀释:上下文越长,模型对每一条信息的相对注意力越低。 3. 推理成本爆炸:长上下文的 token 成本和延迟都是超线性的。
(c) 上下文压缩损失(Compression Loss)
为了塞进更多文档,团队会用 LLM 或专门的压缩模型(如 LLMLingua)对文本块做摘要式压缩。但压缩是有损的——关键的限定词、否定词、例外条款经常被牺牲掉。在法律和医疗领域,一字之差可能改变整个含义。
(d) 模板与分隔符混乱(Template / Delimiter Confusion)
送入 LLM 的 prompt 模板如果设计不当,模型可能分不清"系统指令"、"检索内容"和"用户问题"的边界。常见的故障模式:用户问"这段合同说了什么",系统 prompt 又包含了"请引用以下材料回答",模型可能误以为检索到的内容是用户问的问题本身。
(e) 引用编号与原文不一致(Citation-Original Mismatch)
这是 ContractAI 故事中故障点 #3 的更广义版本。当你给模型指令"在引用处标注 [1] [2] [3]"时,模型的引用编号是基于它"认为"的来源——但它的"认为"和系统实际传入的 top-k 顺序可能不一致。结果就是:模型标了一个 `[3]`,但用户在第 3 个文本块里找不到对应的内容。
判断方法:自动化地做"引用反查"。系统输出 `[3]` → 自动回查第 3 个文本块 → 检查 `原文 ∩ 输出` 的覆盖率。如果覆盖率低于 60%,这一层就是失败的。
---
L3 · 模型归纳失败:真正的模型层幻觉
只有在 L1 和 L2 都验证正确之后,我们才应该怀疑 L3。
L3 的典型形态包括:
(a) 反事实组合(Counterfactual Composition)
模型把来自不同文本块的信息"拼接"成一个看似合理但实际不存在的陈述。例如:文本块 A 说"Fund I 的管理费是 1.5%",文本块 B 说"Fund II 的 carry 是 20%",模型可能输出"Fund I 的 carry 是 20%"。
(b) 过度泛化(Overgeneralization)
模型把一个具体条款泛化为一般原则。一份合同的"特殊情形"被表述为"普遍规则"。
(c) 否定遗漏(Negation Omission)
模型倾向于省略否定词。"不得"、"禁止"、"除外"这类词在归纳过程中被吞掉。这一现象在 2023 年的 GPT-3.5/4 中相当常见,到 2024 年的 Claude 3 / GPT-4o 中有所改善,但仍未根除。
(d) 时态错位(Tense Misalignment)
合同中的"已变更"、"将生效"、"曾经约定"等时间限定词在归纳时被抹平。
(e) 推理链断裂(Reasoning Chain Break)
多跳问题(multi-hop question)需要模型在多个文本块之间做逻辑推理。即使所有文本块都正确检索,模型的链式推理也可能失败。
判断方法:对 L3 的判断必须建立在 L1/L2 的质量基线之上。不要在 L1/L2 不达标时讨论 L3——否则你会浪费大量时间在 prompt engineering 上,而真正的故障点在检索。
---
三层根因模型的工程意义
三层根因模型不是一个理论玩具。它的工程意义在于:它让故障定位有了明确的责任划分。
在 ContractAI 的故事里:
- 故障点 #1(PDF 解析丢层级)属于 **L1 的 (a) 召回缺失** —— 关键条款的语义被打散,dense retrieval 找不到完整段落。
- 故障点 #2(无元数据过滤)属于 **L1 的 (d) 元数据盲区** —— 检索器没有利用结构化字段。
- 故障点 #3(无引用溯源校验)属于 **L2 的 (e) 引用编号不一致** —— 上下文构造阶段没有引入硬约束。
注意:这三层故障都不在 L3。这意味着 ContractAI 团队即使把 GPT-4 升级到 GPT-5,也无法解决他们的问题。这是一个让很多 AI 产品经理不愿意面对的结论:模型升级解决不了工程问题。
---
1.3 知识库工程不是"管道问题",而是"产品问题"
2024 年初,一位在硅谷 RAG 创业公司担任 VP of Engineering 的朋友告诉我一句话:"我们公司 90% 的 RAG 生产事故,最后都追溯到了文档预处理阶段。" 这句话被我们反复验证过。
为什么知识库工程总是被低估?
因为它"看起来简单"。把 PDF 解析成文本、按字符数切分、生成 embedding、存入向量数据库——这套操作在任何一篇 RAG 教程里都是前三章的内容。**但"看起