RAG 答错时,换模型或调大 Top-K 往往治标不治本。这篇教程把优化手段按检索前、检索中、检索后、生成侧四层拆开,并给出一张「按问题现象反查优化手段」的对照表,让每次调参都有据可依。

它解决什么问题

RAG 是一条证据加工流水线:文档解析、切分、索引、召回、重排、上下文组装、生成,任何一环出错都会传染到下游。表格解析错位、Chunk 切断前提条件、权限过滤太晚、正确片段根本没进候选池——这些问题的最终表现都是「答案错了」,病根却完全不同。

没有方法论的团队容易陷入两种玄学:一答错就换 Embedding 模型,或者把 Top-K 越调越大。前者补不回候选池里根本不存在的证据,后者只是用 token 成本换噪声。这套优化体系要解决的问题是:让每个 badcase 都能定位到具体环节,让每次改动都能在固定评测集上复现收益。

核心原理

优化闭环只有四步:标注 badcase → 按环节归因 → 改策略 → Golden Set 回归。

归因时沿链路顺序问四个问题:

  1. 正确证据有没有进入粗召回候选池?——召回问题。
  2. 进了候选池,排名是否靠前?——排序问题。
  3. 排名靠前,进入上下文的内容是否正确、完整、不冲突?——上下文问题。
  4. 上下文正确,模型是否依据它回答?——生成问题。

每一类问题对应完全不同的修复方向。候选池里没有答案时调 Rerank 毫无意义——重排只能改变错误结果的先后顺序。评估指标同样按层拆开:检索层看 Recall@K、MRR、Context Precision;生成层看 Faithfulness(忠实度)、Answer Relevancy 和引用准确性。没有标注好的评测集,一切优化都无法验证,只能算随机调参。

深入机制

检索前:让问题变得可检索

用户提问常常缺少检索所需的对象、时间和范围:「这个报错咋整」没有错误码,「钱能退吗」没有订单状态。检索前优化在不动索引的前提下改善入口:

  • 规范化改写:把口语、缩写、指代还原成完整表达,多轮对话中尤其必要。
  • 多查询扩展(Multi-Query):让模型生成同一问题的几种不同表述并行检索,覆盖「取消订阅」与「关闭自动续费」这类表达差异。
  • 查询分解:把「对比 A 和 B 的手续费与争议处理」拆成多个子问题分别检索再汇总。
  • HyDE:先生成一段假设性答案,用它的向量去检索真实文档,缓解短查询与长文档的形态差异。
  • Step-back:问题太细时先退后一步,检索背景概念再回答具体点。

一条纪律:原始 Query 要和改写结果一起参与召回并融合。改写模型可能误解意图,原始查询是最后的保险。

检索中:混合检索、重排与过滤

混合检索补的是向量检索的结构性弱点:错误码、SKU、版本号、专有名词这类精确词,容易被语义相近但内容错误的文档挤掉。BM25 按词频做字面匹配,向量按语义做近似匹配,两路并行后用 RRF(Reciprocal Rank Fusion)按名次融合——RRF 只看排名不看分数,规避了 BM25 分与余弦分无法直接比较的麻烦。

Rerank 把「语义相近」重排成「能够回答」。双塔检索分别编码 Query 和文档,速度快但判断粗;Cross-Encoder 把 Query 和候选拼在一起编码,能识别「这段是否包含回答问题所需的条件」,代价是只能处理几十到几百条候选。所以标准链路是漏斗形:

粗召回 recall_top_k:30~100 条(向量 + BM25 混合)
-> 重排 rerank_top_n:5~10 条(Cross-Encoder)
-> 上下文 context_top_n:3~6 条(进入 Prompt)

三个参数分别控制召回率、精排成本和上下文信噪比,要在同一评测集上一起调,而不是只动一个。

元数据过滤要发生在召回之前或之中,而不是之后。tenant_id、文档类型、版本、时间这些条件先用元数据收紧候选范围,再算相似度;如果先取 Top-10 再过滤掉 8 条无权文档,剩下的 2 条并不代表「库里只有两条相关内容」,只是证据被过滤动作截断了。

检索后:上下文压缩与引用校验

进入 Prompt 的每个片段,都应该能支撑答案里的某个结论、条件或例外。检索后的工作是提纯:

  • 去重与合并:多路召回的重复结果、同文档的相邻 Chunk 先合并。
  • 压缩:对长片段做查询相关的抽取或摘要,只保留与问题相关的原句。风险是漏掉隐含条件,所以先按规则过滤,仅对长片段调用 LLM 压缩。
  • 排序:最相关的证据放前面,同文档片段保持原始顺序,冲突版本标注时间——缓解 Lost in the Middle 式的注意力稀释。
  • 引用校验:生成后核对答案中每个关键结论是否真有片段支撑、引用编号是否指向正确来源,不通过则降级为拒答或重试。

生成侧:拒答与忠实度约束

Prompt 里至少写清四条边界:只依据给定上下文回答;证据不足时明确说明;关键结论附引用;不把旧版本当当前事实。配合检索分数阈值做拒答:最高分低于阈值说明库里大概率没有答案,直接告知用户比硬答更负责任。要注意,Prompt 只能「要求」模型守边界,它是否真遵守,需要引用校验和评测集在系统层面验证。

工程实践

  • 评估集先行:50~100 条样本起步,覆盖高频问题、线上失败、精确匹配(错误码、SKU)、越权与过期、应拒答五类;每条样本带 golden answer 和 golden context。
  • 版本化每一次改动:解析器、Chunk 策略、Embedding、索引参数、改写 Prompt、Rerank 模型、生成 Prompt、评估集都记版本,否则指标波动无法归因。
  • A/B 只改一个变量:Rerank 有无、top_k 大小、改写策略分别实验;LLM-as-a-Judge 只是辅助信号,上线前必须抽样人工复核。
  • 数据治理永远排第一:去重、脱敏、过期下架、解析质量修复的收益,经常大于检索算法所有 trick 的总和。

对比与权衡

按问题现象反查优化手段:

现象 最可能的环节 优先尝试的手段
错误码、型号查不到 召回 加 BM25 混合检索;检查文档是否入库
口语化问题答非所问 检索前 Query 规范化改写、多查询扩展
短查询匹配到泛化内容 检索前 HyDE、Step-back
正确片段在 Top-50 却没进上下文 排序 换 Rerank 模型;调大 rerank_top_n
答案被无关内容带偏 上下文 压缩、去重、调小 context_top_n
引用了过期版本 检索中 元数据时间过滤;冲突版本标注时间
库里没答案却硬答 生成侧 分数阈值拒答;Prompt 证据边界
结论与引用对不上 生成侧 引用校验;结构化输出
越权看到其他租户内容 检索中 权限过滤前置到召回阶段

面试视角

RAG 效果不好,你的排查顺序是什么? 按链路分层:先看正确证据是否进了粗召回候选池,再看排名、上下文、生成。每层有对应指标和修复方向,绝不同时改 Prompt、模型和检索参数。

为什么需要混合检索,向量检索不够吗? 向量擅长语义相近,但对错误码、SKU、专有名词等精确词不稳定;BM25 恰好互补。两路结果用 RRF 按名次融合,规避分数尺度不一致的问题。

Rerank 的价值是什么,放在哪里? 向量分数衡量「像不像」,Rerank 判断「能不能答」。Cross-Encoder 把 Query 和候选一起编码,精度高但慢,所以放在粗召回之后,只处理几十条候选,输出 5~10 条进上下文。

如何系统性地控制幻觉? 三段治理:检索侧保证证据正确(混合检索 + 重排),上下文侧去噪防淹没(压缩 + 排序 + 数量预算),生成侧约束行为(证据边界 Prompt + 低分拒答 + 引用校验)。

Top-K 是不是越大越好? 不是。粗召回、重排后、进上下文应分三层配额(如 100/10/5)。直接调大进上下文的数量只会推高 token 成本、稀释注意力;要提召回应该扩大粗召回池,再靠重排和压缩把噪声滤掉。

小结

  1. RAG 优化是数据驱动的闭环:标注 badcase、分层归因、改策略、Golden Set 回归,顺序不能跳。
  2. 检索前靠改写、多查询、HyDE 让问题可检索;检索中靠混合检索、Rerank、前置过滤保证证据质量。
  3. 检索后靠去重、压缩、排序提纯上下文;生成侧靠证据边界、拒答阈值和引用校验兜底忠实度。
  4. 三层 top-k 配额分开调,评估指标分检索层与生成层,每次改动记版本。
  5. 先修数据治理,再调检索算法,最后才考虑换模型——这个顺序能省掉大部分无效投入。