作者复盘 RAG 系统生产失效案例,指出约 73% 故障出在检索层而非生成层,并给出从分块到向量匹配的全链路检查清单。
我第一次看到 RAG 系统给出一个自信满满的错误答案时,做了所有人都会做的事:怪罪模型。我换了个更大的模型,调了提示词,在"仅根据提供的上下文作答"后面加了粗体。答案丝毫没有变好。
问题从来不在模型。模型忠实地总结了我传给它的上下文——问题出在上下文本身就不对。它检索到了错误的文本块,所以答了错误的问题,而且答得行云流水。
这其实是常态,不是例外。2026 年的行业分析反复得出同一个数字:当 RAG 失败时,约 73% 的问题出在检索端,而非生成端。大语言模型替一个在它看到第一个 token 之前好几个环节就已发生的错误背了锅。
所以,这是我在上线前希望有人给我的检查清单——按完整流程组织,因为检索不是一个步骤,而是一条链,任何一环都可能断裂。朴素的 RAG("分块、向量化、余弦相似度、塞进提示词")从来都只是原型。这份清单要填的是原型和生产级之间的鸿沟。
让我们逐环节来看。
首先,思维模型:两条路径,不是一条流水线
很多 RAG 痛苦的根源在于把 RAG 当成单一流程。实际它是两条完全独立的路径,大多数人无意中把它们耦合在了一起。
索引路径(离线)。在文档新增或变更时运行:解析源文档 → 清洗文本 → 分块 →(可选)为每个块丰富上下文 → 向量化 → 写入向量数据库和关键词索引。每个文档可能耗时数分钟,在后台运行。
查询路径(在线)。在每个用户请求时实时运行,受延迟预算约束(目标是端到端控制在约 3 秒以内):接收查询 → 可选改写查询 → 检索候选 → 重排序 → 组装带引用的提示词 → 生成 → 记录链路追踪。
最常见的架构错误是耦合这两条路径。如果重建索引会导致查询路径下线,你就无法在不停机的情况下迭代分块策略或切换向量化模型——于是你停止了迭代,而一条冻结的流水线就是一条过时的流水线。从第一天起就让两者保持独立。
检查:你能否在保持实时搜索不中断的情况下,对整个语料重新分块和重新向量化?如果不能,先把两条路径解耦,其他的都先放一放。
☐ 1. 你的分块是不是把一个完整思路劈成了两半?
分块是流水线无声失败的地方,因为糟糕的分块不会报错——它们只是安静地返回技术上相关、实际上毫无用处的上下文。
朴素的默认方案——"每 1,000 个字符分一块,重叠 100 个"——是快速的起点,也是缓慢的瓶颈。固定大小分块会在句子中间、表格行中间、函数中间截断。检索到的块看起来相关,却缺失了最重要的那一半。
更好的方案,按投入成本从低到高排序:
结构感知分块——按照文档自身的边界来分割:文档按 ## 标题分割,代码按每个函数或每个类分割,表格按每行分割。成本低,收益大,而且尊重内容实际被组织的方式。
语义分块——逐句计算相似度,在语义发生转变的地方开始一个新块,使每个块都是一个完整的思路。计算量更大,但一项已发表的对比研究表明,在同一数据集上相比固定大小分块,语义分块显著提升了准确率。
需要坚守的规则:每个块应该能够独立回答一个问题。如果一个块只有和相邻块放在一起才有意义,说明你的分割过于激进了。还要注意块大小——太小会碎片化思路,太大会稀释信号,迫使模型在一大堆基本无关的文本中取平均值。
检查:随机抽取十个块,冷读一遍。每个块能独立站住脚吗?还是有一半只是残缺的句子和孤零零的表格行?
☐ 2. 你在向量化的是块本身——还是带上下文的块?
一个微妙但影响巨大的问题。如果只对块内的原始正文文本做向量化,你会剥离掉告诉人类这个块是什么意思的上下文——它属于哪个章节,关于哪个产品,前面出现过什么。
两个修复方案,成本相对于收益都很低:
向量化上下文,而非仅正文。在向量化前,给块前面加上标题、简短的文档摘要,或者一两句话描述这个块的内容。这样块的向量就和人们实际提问的方式对齐了。(这就是"上下文检索"的核心思想——在索引前给每个块一点定位上下文,能可衡量地提升召回率。)
保留元数据。每个文档都带有结构信息——作者、日期、来源、章节、产品版本、文档类型、访问级别。把这些和块一起存储。下一环节就会用到。
检查:你索引中一个孤立的块是否携带了任何关于它来自哪里的信号,还是只是一个没有任何定位上下文的赤裸段落?
☐ 3. 你用的是混合搜索——还是只用向量搜索?
这是最常见的检索错误,而且因为向量搜索通常表现还不错,所以它隐藏在众目睽睽之下。
纯向量(语义)搜索擅长语义理解。问"怎么解决登录问题?",它会找出关于身份验证、OAuth、会话超时的文本块,即使没有一个用了"登录"这个词。这就是它的魔力。
但一旦查询中包含精确内容,它就露馅了。用户搜索错误码 ERR_SSL_PROTOCOL_ERROR、SKU WX-4200 或具体函数名——向量搜索完全不知道该怎么做,因为语义相似性对序列号毫无意义。它返回一些"大致关于错误"的东西,而错过了语料库中就在那里躺着的精确匹配。
解决方案是混合搜索:对同一个查询同时运行关键词搜索(BM25/全文)和向量搜索,然后用某种方式融合结果——RRF(倒数排序融合)是标准的合并方式。关键词负责精确匹配,向量负责语义理解。2024–2026 年的基准测试(BEIR、MTEB 等)共识很直白:BM25 + 稠密向量用 RRF 融合,在几乎所有公开基准上都超越了单独使用其中任何一种。纯稠密检索在这场争论中败下阵来。
检查:你的检索能否对字面的错误码和模糊的概念问题一视同仁地处理?如果不能,你很可能只用向量搜索,加一个关键词索引是你最高杠杆的改动。
☐ 4. 你在转换查询——还是在用用户原始输入直接检索?
这是大多数人完全跳过的一个环节:用户字面上的问题往往不是检索器想要的。人们提问含糊、复合、依赖上下文;你的索引里装的是精确的、独立的陈述。弥合这个鸿沟的就是查询转换,而且它是最大的隐性收益之一。
主要模式,各解决不同问题:
查询重写/扩展——在检索前清理并丰富原始查询,使其更好地匹配语料库。在多轮对话中尤为重要,因为"第二个呢?"如果不重写成独立查询就毫无意义。
HyDE(假设文档嵌入)——让大语言模型生成一个针对问题的假设答案,然后用这个答案的向量去检索。一个假答案在形态上往往比问题本身更接近真实文档,从而提升精确率。
迂回提示——把一个窄义问题重写成更广义的问题,检索背景知识,再收窄到具体问题。适用于字面措辞偏离语料库的模糊查询。
分解——把一个多部分问题拆分成独立的子查询,分别检索,再综合回答。"我们的退款政策在 B2B 和 B2C 之间有何不同,有什么例外?"实际上是三次检索,不是一次。
你不需要用全部这些。但如果直接把用户原始文本喂给检索器,你就丢失了大量的召回率——对复合问题和对话式问题尤其如此。
检查:拿出你最难的十个真实用户问题。如果先改写、拆分或扩展,有多少能检索得更好?如果大部分都是,那加一个转换步骤。
☐ 5. 你在重排序——还是在信任第一轮检索的顺序?
这个错误让我付出了最多质量代价,原因却最不显而易见:我以为只要正确的块被检索出来,模型就会用它。但这个块在列表中的位置至关重要。
向量搜索使用双编码器——分别对查询和每个块编码,然后比较向量。速度快,但牺牲了细粒度相关性。所以真正最匹配的块经常被检索出来……排在第 8 位,埋在七个"还挺相关"的结果下面。而模型有明确的"lost in the middle"(中间丢失)问题——信息淹没在长列表中间时,模型会 demonstrably 忽略它。正确答案就在上下文中,模型还是漏掉了。
重排序解决了这个问题。用混合搜索检索出一大批候选(取前 20–50 名),然后运行一个交叉编码重排器,对每一对(查询,块)联合打分——查询和块一起看,这使得它在细粒度相关性上远比第一轮检索强。只保留前 3–8 名。
效果是显著的,不是边际性的:跨编码器重排器在困难集合上通常能提升 5–15 个百分点的 MRR,在某些推理密集的基准测试中,重排将 nDCG@10 从约 0.13 提升到约 0.40——同一批检索到的候选项,仅靠重新排序就实现了约 3 倍的提升。
能击败约 80% 生产部署的配方:混合搜索检索约 20 条 → 重排到约 5 条 → 向 LLM 发送 3–5 条。重排 100+ 候选项很少值得回报;信号集中在头部。
检查项:在检索和 prompt 之间是否有重排步骤?如果 chunk 是直接从向量相似度进入上下文窗口的,加上重排——这往往是整个流程中投资回报率最高的改动。
☐ 6. 你的上下文组装是在帮助模型——还是在堆垃圾?
你已经检索并重排到了正确的 chunk。但在这个无人谈论的阶段,你仍然可能功亏一篑:就是你实际组装 prompt 的方式。
悄悄造成危害的做法:
顺序。由于"中间丢失"效应,把最强的 chunk 放在上下文的最开头和最末尾,而不是埋在中间。
容量。更多的 chunk 并不代表更好。为了保险塞入 30 个 chunk 会稀释信号,并诱使模型在噪声中取平均值。只发送那些值得占据位置的少数 chunk。
没有引用。要求模型引用每个论点由哪个 chunk 支撑。这既能抑制无根据的虚构,也能让你根据来源验证答案。
长上下文 ≠ 跳过检索。前沿模型现在已经有百万 token 的上下文窗口,条件反射是"把所有东西都丢进去,谁还需要检索"。抵制它。将整个语料库倾倒进去会更慢、更贵、准确度更低,因为模型仍然需要在大海捞针。把大窗口用于真正的综合任务(长报告、整个代码库),而不是作为检索的替代品。
检查项:你发送了多少个 chunk,按什么顺序?如果答案是"能塞多少塞多少,按检索顺序",你正在把质量扔在地上。
☐ 7. 你需要 Agentic RAG——还是在把复杂性强加到一个已经坏掉的流程上?
以上描述的都是单次 retrieve-then-generate(先检索后生成)传递。这有一个上限:它在简单问题上有效,在复杂、多跳的问题上就会崩溃,因为答案不在任何一个单一的 chunk 中。Agentic RAG 应运而生。
这是一种结构性的转变。经典 RAG 做一次前置检索调用,无状态——如果它漏了,就没有补救。Agentic RAG 把检索移到一个推理循环内部(ReAct 模式:模型在思考和调用工具之间交替)。现在模型可以检索、查看结果、觉得不够、Rewrite 查询、再次检索,直到得到所需的信息才停止。检索成为智能体反复使用的工具,而不是置于其前的固定步骤。
这解锁了多跳问题——"2024 年奥运会主办国的 GDP 是多少?"需要在第一跳(奥运会 → 法国)之后才能进行第二跳(法国 → GDP)。单次检索无法做到这一点;但能检索、推理、再检索的智能体可以。相关的进阶模式包括 GraphRAG(从文档中构建知识图谱,以回答需要跨多个文档连接实体的问题)以及给智能体配备明确的工具:search、fetch-full-document-by-id、exact-match/regex,甚至 prune-context-to-discard-junk。
一个坦诚的警告——这直接关联到过度工程化:Agentic RAG 成本更高(更多调用、更长延迟、更多不确定性),只在真正复杂或高风险的检索场景(法律、医疗、金融、多跳)才值得。它不是用来修复一个坏掉的基础流程的。如果你的分块很糟糕且没有重排,智能体只会更昂贵地、反复地做出糟糕的检索调用。先把单次传递检索做扎实。只有当问题真正需要多跳时,才添加智能体循环。
检查项:你那些失败的问题实际上需要跨文档链接事实吗——还是用你还没实现的混合搜索 + 重排就能很好地回答?
☐ 8. 你能否隔离测量检索质量——并捕捉住虚构?
这是隐藏所有其他问题的元错误。大多数团队做端到端的 RAG 评估:阅读最终答案,觉得"看起来不错",就上线了。但端到端的答案混合了检索和生成,所以当它出错时,你无法判断是哪一半出了问题。你会花一周时间调 prompt,结果实际上是一个分块 bug。
你需要分别测量两件事:
独立测量检索质量。给定一个查询,正确的 chunk 是否进入了检索集合(召回率),以及它的排名有多高(排名 / nDCG / MRR)?对于多跳和 Agentic 设置,召回率必须沿着整个链条测量,而不是单次调用。RAGAS 等框架存在,但即使是一个手工构建的真实查询集映射到正确的源 chunk,也比凭感觉强。
答案的忠实度 / groundedness。最终答案中的每个声明是否真的由检索到的上下文支撑?这是捕捉最可怕失败的检查:那个检索了 8 个 chunk、使用了 6 个、凭空发明了第 7 个事实的智能体。没有忠实度分数或裁判 gating 输出,那种虚构就会上线,然后两天后被客户发现。
一个来之不易的警告:小评估集会欺骗你。如果你的测试集小且简单,每种方法都接近完美且看起来同样好——在真实数据上重要的差异在玩具集上完全看不见。检索评估只有在方法真的可能在上面失败时才算是评估。如果一切都得 95%,你建的是一个冒烟测试,而冒烟测试会欣然为你希望修复的那个坏掉的层祝福。
检查项:如果检索明天退化了,会有一个数字告诉你吗——还是只能靠用户?如果是后者,你还没有能力测量检索,上面所有内容都是猜测。
完整检查清单,一屏掌握
将索引路径与查询路径解耦,这样你可以不停机地迭代。
分块时让每个单元独立站立——结构感知或语义分块,绝不是盲目的固定大小。
嵌入上下文——在头部预先追加标题/摘要,保留元数据。
混合搜索——BM25 + 向量,用 RRF 融合。绝不只用向量。
变换查询——在检索前先 Rewrite、HyDE、step-back 或 Decompose。
用跨编码器重排——检索约 20 条,重排到约 5 条,发送 3–5 条。
精心组装上下文——最好的 chunk 放首尾,少而精,带引用。
只在需要时才引入 Agentic——在坚实基础上处理多跳和高风险场景。
分别评估检索召回率和答案忠实度——在足够困难的集合上。
牢记的一句话
当 RAG 给出一个糟糕的答案时,首先怀疑检索——它比模型更可能是罪魁祸首。
伸手去用更大模型的直觉几乎总是错的。更大的模型会像小模型一样流利地总结错误的 chunk。杠杆在上游——在于找到正确的 chunk、使其可用、将其排名到模型能看到的位置,以及能够判断链条中任何一个环节何时断裂。
我是从一个又一个自信地给出错误答案中学会这份检查清单的。你不必如此。
哪个检索 bug 骗了你最久? 我的一个分块问题我花了两周时间怪模型。分享你的——以及我遗漏的任何检查项——在评论区。
For further actions, you may consider blocking this person and/or reporting abuse