RAG 答不准,问题多半不在模型:召回漏斗的上限
RAG 在生成前先从外部知识库检索证据,把命中的片段拼进上下文,让模型“开卷答题”。它由索引期的解析、切块、嵌入、建索引,和查询期的改写、召回、融合、重排、拼装、生成共同组成,任何一环丢了信息,后面都补不回来。最常见的误区是把 RAG 当成消灭幻觉的开关,或以为向量检索可以取代关键词检索;实际上混合召回、交叉编码器重排与合理的上下文排序才是生产默认。读完你能分清检索问题与生成问题,用召回率与忠实度分层评测,并知道 top-k、分块和 HNSW 参数该怎么调。
title: RAG 答不准,问题多半不在模型:召回漏斗的上限 date: 2026-11-06T12:05:00+08:00 type: kb tags: [科普] status: published summary: RAG 在生成前先从外部知识库检索证据,把命中的片段拼进上下文,让模型“开卷答题”。它由索引期的解析、切块、嵌入、建索引,和查询期的改写、召回、融合、重排、拼装、生成共同组成,任何一环丢了信息,后面都补不回来。最常见的误区是把 RAG 当成消灭幻觉的开关,或以为向量检索可以取代关键词检索;实际上混合召回、交叉编码器重排与合理的上下文排序才是生产默认。读完你能分清检索问题与生成问题,用召回率与忠实度分层评测,并知道 top-k、分块和 HNSW 参数该怎么调。 ---
是什么
RAG(Retrieval-Augmented Generation,检索增强生成)是在模型生成答案之前,先从外部知识库里找出一批证据片段,拼进上下文,再让模型基于这些证据作答。一句话直觉:把闭卷考试改成开卷考试,答案仍然由考生写,但参考书是他自己翻的。
它最早由 Lewis 等人在 2020 年提出,用稠密检索器从 Wikipedia 取段落,喂给 BART 生成,把“参数化记忆”和“非参数化记忆”拼在一起(原始论文)。今天的工程形态复杂得多,但骨架没变,而且必须拆成两段看:索引期把文档变成可检索的单元,查询期把问题变成一小组证据。企业知识库问答、客服工单、代码库问答、合同比对,都是它的典型场景。
关键认知是:RAG 不是一个模型,而是一条流水线。流水线上任何一处把信息丢了,后面的模型再强也补不回来。
核心原理
索引期:文档先被切碎,再被压成向量
原始文档要先解析。PDF 的表格、双栏排版、页眉页脚,都会在解析阶段变成噪声;这一步做不好,后面全是垃圾进、垃圾出。
然后是切块(chunking)。这里最容易被低估:切块决定了检索系统“能够看见什么”。按固定字数硬切,会把一个定义、一张表格、一段前提从中间劈开;更稳的做法是按语义边界切,并且给每个块补上它失去的上下文——文档标题、章节路径、这份文件在说什么。Anthropic 把这套做法叫 Contextual Retrieval:在嵌入和建 BM25 索引之前,先用一个小模型给每个块生成一句“它来自哪里、指代什么”的前缀,其评测中失败检索减少了 49%,叠加重排后减少 67%(Anthropic)。这不是玄学,它补的正是切块时丢掉的指代关系。
接着是嵌入。双塔模型(bi-encoder)把查询和段落分别压成一个向量,再用余弦相似度比较。注意它是非对称任务:查询短、段落长,很多模型(如 E5 系列)训练时就要求查询加 query:、段落加 passage: 前缀,漏掉前缀会稳定掉点(E5 模型卡)。索引侧漏了,查询侧再对也救不回来。
向量入库后通常用 HNSW 做近似最近邻:它把向量组织成多层图,上层稀疏、跨度大,下层稠密、精度高,查询时从顶层贪心下降,逐层缩小范围(HNSW 论文)。所以它是“近似”的:ef_search 调大,召回更高、延迟更高;调小则相反。很多人以为向量库返回的就是最相似的 k 条,其实返回的是“图走到的那 k 条”。
查询期:召回、融合、重排、拼装
一条查询进来,先做改写:把“它什么时候上线”里的指代补全,把复合问题拆成子问题,或者用 HyDE 先生成一段假设答案再去检索。改写的目的不是让问题更漂亮,而是让查询的措辞更接近文档的措辞。
然后双路召回。稠密向量擅长语义相近、换了说法的查询;BM25 擅长专有名词、型号、错误码、版本号这种必须字面命中的词。两者错的方式不一样,所以生产系统一般不二选一。融合最常用 RRF:只看排名,不看原始分数,避免 BM25 的 0–15 分和余弦的 0.6–0.95 被硬凑到一起。k 取 60 是 2009 年那篇论文留下的经验值,业界普遍沿用。
def rrf(rank_lists, k=60, top_n=4):
score = {}
for ranks in rank_lists:
for pos, doc_id in enumerate(ranks, start=1):
score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + pos)
return sorted(score.items(), key=lambda x: -x[1])[:top_n]
print(rrf([["d3", "d1", "d8"], ["d1", "d7", "d3"]], top_n=3))
# d1 在两路都靠前,融合后排第一;单路命中的只能排在后面
融合之后是重排。召回阶段用的是双塔,查询和段落各自编码,彼此没见过面;交叉编码器(cross-encoder)把“查询 + 段落”拼成一条输入一起过模型,能捕捉词与词之间的交互,判断准得多,但每条候选都要跑一次前向,成本高。所以标准做法是漏斗:召回 20–50 条,重排后只留 4–6 条。重排只能改变顺序,不能把没召回的东西变出来——这就是“召回是天花板”的第一层含义。
最后是拼装。这里有个反直觉的实验结论:把答案放在 20 篇文档的第 1 位,GPT-3.5-Turbo 准确率是 75.8%;放到第 10 位,掉到 53.8%,比完全不给文档的 56.1% 还低(Lost in the Middle)。也就是说,上下文不是越长越有用,模型对开头和结尾更敏感,中间容易被忽略。实践上的对策很朴素:少而精,把最重要的证据放在开头,次重要的放在结尾。
为什么说上限在检索
把整条链路串起来看,它是一个乘法:检索没找到,重排无从谈起;重排没排上来,模型看不到;模型看到了但被噪声淹没,也可能抽不出来。Barnett 等人把 RAG 的失败点归纳成七类,前四类——内容缺失、正确文档排名不够、没进上下文、进了上下文却没被抽取——全都发生在“生成”之前或边界上(Seven Failure Points)。所以线上答错时,先别急着换更大的模型,先把命中的块打印出来看:答案到底在不在里面。
常见考点与误区
- 误区一:RAG 能消灭幻觉。 不能。检索不到、检索到冲突内容、模型不按证据说话,都会产生幻觉。RAG 降低的是“不知道却硬答”的概率,前提是提示词允许它说“根据现有资料无法回答”,并且要求逐句给出处。
- 误区二:向量检索更先进,BM25 可以退休。 错。用户搜
ERR_SSL_VERSION_OR_CIPHER_MISMATCH或某个内部型号时,语义模型很可能把它糊成一团,BM25 反而一击即中。两者是互补关系,不是替代关系。 - 误区三:top-k 越大,答案越稳。 越大噪声越多,中间位置越容易被忽略,延迟和 token 成本还线性上涨。正确姿势是“宽召回、窄重排”,进 prompt 的常常只要 4–6 条。
- 误区四:长上下文可以替代 RAG。 长窗口解决的是“装得下”,不解决“找得准、更新得了、说得出出处”。把整个知识库塞进去,既贵又会被中间内容稀释,还没法做权限过滤。
- 误区五:RAG 和微调是一回事。 RAG 改的是“模型此刻知道什么”,知识更新只需重建索引;微调改的是“模型怎么说话、怎么按格式输出”,新知识靠微调灌进去既慢又容易忘。
- 考点:chunk 多大合适? 没有万能值。判断标准是“这个块能不能独立回答一个问题”,再配合标题路径、少量重叠和上下文前缀。先用 300–800 token 起步,用评测集调,不要凭感觉定 512。
实践建议
- 先建黄金集,再谈优化。 准备 100–300 条真实问题,标注标准答案和应命中的文档,把指标拆成两层:检索看 Recall@k、nDCG,生成看忠实度、答案相关性(RAGAS 的四个指标就是这个思路)。每次只改一个变量,否则你永远不知道是哪一步带来的提升。
- 把权限和元数据前置。 文档的部门、密级、生效时间要在召回阶段就过滤,不要等生成完再删句子;否则模型已经“看过”了不该看的内容,引用也可能泄露。
- 按漏斗组织链路。 混合召回 20–50 条,RRF 融合,交叉编码器重排到 4–6 条,最重要的放开头、次重要的放结尾,并要求带引用输出。这套组合比单独调大 top-k 有效得多。
- 把索引当生产资产维护。 文档会更新、删除、改权限,索引必须能增量重建;嵌入结果要缓存,否则一次全量重建的账单会很难看。监控四个数:无答案率、引用命中率、检索 p95 延迟、索引滞后时间。
- 小库别过度设计。 如果知识库只有几十页且很少变,直接全量放进提示词可能更准也更便宜;RAG 的价值在规模、更新频率和可追溯性,不在这里。
一句话总结:RAG 的答案质量由召回漏斗决定,模型只是最后那个把证据说清楚的人。