跳到正文

RAG PROJECT INTERVIEW · Q17 / 25

RAG 检索效果差怎么排查?从 Query 到 Rerank 逐层定位

用同义词、否定问题和多跳问题三类 bad case,建立从入库证据到最终上下文的逐层排错方法。

作者与复核:吴师兄内容依据:公开 RAG 教程、GitHub 教学代码与工程推导查看关联教程 →查看吴师兄公开作品 →

25 道题目Q17
本页内容18 节

30 秒先说结论

按证据流逐层排查:先确认正确内容是否被解析并切进索引;再看原 Query 与改写 Query 是否召回;然后比较 BM25、向量、融合和 Rerank 各阶段候选;最后检查送入模型的上下文是否完整。每层都保存候选、分数、过滤条件和版本,才能把错误定位到具体模块。

RAG 回答错了,最常见的第一反应是什么?

改 Prompt,换大模型,或者把 Top-K 调大。

这三个动作都可能有用,但它们不应该成为排查起点。

因为最终答案只是链路最后一层。错误可能早在文档解析时就发生,也可能是 Query 被改写带偏、过滤条件挡掉了正确文档、向量召回没找到、融合时被压下去,或者 Rerank 把正确证据排到了后面。

如果连正确证据在哪一层消失都不知道,改参数更像碰运气。

排查前,为什么先要有“正确证据”?

如果只知道“答案错了”,排查没有参照物。

系统返回的每条候选都可能看起来相关,但只有能够直接支持参考答案的原文,才是这次问题应该追踪的对象。

因此,一条可调试样本至少要准备三样东西:原始 Query、参考答案、正确证据位置。

证据位置最好具体到文档、页码、Chunk 或内容区域,不能只写一个文件名。长文档中可能有许多相似段落,找到同一份文件并不等于找到能回答问题的那一段。

多跳问题还可能需要多条证据。

如果答案需要规则定义与例外条款共同支持,就要把两段都标出来。只跟踪其中一段,会把“部分召回”误判为“已经找到答案”。

正确证据是整条调试链的坐标。

后面每一层都只问一个问题:它还在不在,分数是多少,排在什么位置,为什么被保留或丢弃。

盲目调参和证据链排查的区别

盲目调参只看最终答案,证据链排查则先固定正确原文,再追踪它在哪一层出现或消失。

第一层,正确内容真的入库了吗?

先从离线链路查起。

原文件是否被系统接收,目标页面有没有解析出来,双栏和表格结构是否正确,切分后条件与结论是否仍在同一个可用语义单元。

接着确认 Chunk 是否写进当前可检索索引。

文档可能解析成功,却因为批量写入部分失败没有进入向量库。也可能新版本已经上传,检索仍然只看旧索引。权限、知识库和文档状态也可能让正确 Chunk 不在本次范围内。

如果正确证据根本不存在于索引,调 BM25 权重、替换 Embedding、扩大 Top-K 都不会修好问题。

检索器只能在已有语料中挑选,不能找回从未进入候选空间的内容。

这一层应该保存文档身份、解析版本、切分版本、Chunk ID、索引版本和当前状态。

当原文件更新后,还要确认正确证据属于当前有效版本。旧版和新版同时存在时,检索可能找到一条语义高度相关、业务上却已经失效的内容。

第二层,Query 有没有被处理错?

很多检索问题不是模型不够强,而是送进检索的 Query 已经偏离用户原话。

需要同时保存原 Query 与改写 Query。

多轮问题里,“这个怎么申请”需要结合历史补全对象,但历史中有多个实体时,重写可能选错。

否定问题里,“哪些情况不能申请”如果丢掉“不”,检索方向会完全相反。

精确编号、错误码和产品名如果被改成自然语言,原本最强的关键词信号可能被稀释。

还要检查 Query 分解。

多跳问题拆成子查询后,是否覆盖了所有信息点?子查询是否添加了原问题没有的条件?合并时是否只保留了其中一路?

这一层的排查顺序可以是:原问题是否清楚,改写是否保持实体与否定,扩写是否引入噪声,分解是否完整,失败时是否保留原 Query 回退。

不要只看改写结果是否“更像标准问题”。它的任务是提高检索可用性,不是润色句子。

第三层,过滤条件有没有挡掉正确证据?

过滤通常发生在相关性排序之前,却很容易被忽略。

知识库 ID、租户、权限、文档状态、版本、时间和文档类型都会决定哪些 Chunk 有资格参与检索。

正确证据被过滤后,后面的 BM25、向量、融合和 Rerank 都看不到它。

因此每次请求都要记录实际生效的过滤条件,而不只是业务层传入的参数。

例如,调用方传入了当前知识库,检索层又根据默认状态排除某类文档。最终条件可能比日志里看到的更严格。

调试时可以先确认正确 Chunk 在无过滤情况下能否找到,再逐项恢复过滤,定位是哪条规则把它排除。

但这不意味着可以长期去掉权限或状态过滤。调试绕过只用于定位,最终修复要保证安全边界不被破坏。

第四层,两路召回分别找到了什么?

不要一上来只看融合后的 Top-K。

BM25 与向量检索解决的失败类型不同。把两路混在一起,会看不出正确证据原本由哪一路找到,又在哪一步被压下去。

BM25 更依赖原词、编号和专业术语。用户换了说法时,它可能漏掉正确文档。

向量检索擅长语义改写,但短 Query、领域术语和否定表达可能出现偏移。

调试时应分别保存两路候选、原始分数、排名和 Chunk 来源。

同义词问题先看向量路线是否补回了正式术语。

精确标题和错误码先看 BM25 是否命中。

否定问题则要比较正面描述与例外条款分别在哪一路排得更高。

正确证据两路都没有出现,先查 Query、模型、索引与候选范围。

某一路已经找到,融合后却消失,再进入下一层。

第五层,融合为什么把正确证据压下去了?

关键词分数与向量相似度不是同一把尺子。

如果使用加权求和,需要确认两路分数怎样归一化、权重如何设置,以及某一路是否因为数值范围更大长期主导排序。

如果使用基于名次的融合,也要查看正确证据在各路的原始名次。两路都排得很后,融合不会自动变成第一。

合并去重也可能出错。

同一 Chunk 由两路召回时,应按稳定 ID 合并,同时保留召回通道。如果去重键不稳定,一段内容可能以两个候选出现;如果只保留一路记录,又可能丢掉有价值的分数与来源。

这一层至少保存融合前后列表、每条候选的通道、原始分数、归一化分数、综合分和去重键。

不要只保存最后一个综合数字。出现异常时,需要能还原它由哪些信号组成。

第六层,Rerank 是没起作用,还是根本没机会?

Rerank 只能重新排列已有候选。

正确证据没有进入粗召回 Top-N,它就没有补救机会。此时更换更大的重排模型不会解决漏召回。

正确证据已经进入候选,却排在后面,才是典型排序问题。

比较 Rerank 前后名次,检查模型输入是否被截断、语种与文档是否适配、否定与条件是否得到正确判断。

候选数量也要记录。

Top-N 太小可能把正确证据挡在精排外,太大则增加成本与噪声。调试时扩大候选能帮助判断覆盖边界,但最终参数仍要回到评测集和延迟预算上决定。

有些系统只对部分页面执行 Rerank。翻页后结果重复或跳动时,要确认不同页面是否使用相同排序口径。

第七层,最终上下文真的包含完整证据吗?

候选排对以后,还可能在截断与组装时出错。

最终只保留前几条,正确证据可能被最后一次阈值过滤丢掉。

多跳问题需要两段证据,系统却只保留其中一段。

相邻重复 Chunk 占满上下文,互补证据没有空间进入。

标题、表头或列表前导句在展示前被清洗掉,模型看到的内容不再完整。

所以 Trace 还要保存最终送给模型的原始上下文,而不是只保存候选 ID。

如果正确证据已经完整进入上下文,模型仍然答错,再去看 Prompt、证据冲突、引用约束和生成模型。

这时修改 Prompt 才有明确依据。

从 Query 到最终上下文的证据流

每一层都保存正确证据是否存在、名次怎样变化以及为何被过滤。最终答案只是证据流的最后结果。

三类典型问题,排查入口有什么不同?

下面均为教学示例。

用户用口语,文档用正式术语

先看原 Query 的 BM25 与向量候选。

如果向量路线已经找到正确术语,检查融合和 Rerank。两路都没找到,再考虑同义词词典、Query 扩写或 Embedding 适配。

不能看到最终答案错了,就直接给所有 Query 增加扩写。

用户问“哪些不支持”,却召回大量正面描述

先核对原 Query 与改写 Query 的否定词是否保留,再比较正面条款和例外条款在两路候选中的名次。

如果正确例外条款已经进入候选但排得靠后,检查融合与 Rerank。如果它根本没入库,改写也无法补救。

一个问题需要两段证据

先标注两条正确证据,再看单次检索找到了哪一条。

必要时把问题拆成子查询,各自召回后合并。但最后要检查两条证据是否都进入上下文,以及它们之间有没有版本或范围冲突。

这些问题不能统一叫“召回率低”。

同义词更像表达差异,否定涉及意图与排序,多跳涉及问题分解和证据覆盖。失败类型不同,修复和回归样本也不同。

检索失败现象与优先排查层

证据没入库、没召回、被融合压低、被 Rerank 排错和被上下文截断,对应不同修复入口。

一条可复现的 Trace 应该记录什么?

Trace 不是把若干日志文本放在一起。

它要能用同一个请求身份串起整条证据流。

请求层记录 Trace ID、原 Query、相关历史、改写与扩写结果、最终路由。

数据层记录知识库、权限、文档状态、解析版本、切分版本和索引版本。

召回层记录 BM25 与向量候选、Chunk ID、来源、通道、分数和过滤条件。

排序层记录融合前后分数、Rerank 模型版本、输入截断、重排前后名次。

上下文层记录最终交给模型的原文、顺序、总量和被截掉的候选。

生成层记录 Prompt 版本、模型版本、回答和引用。

这些信息不一定全部长期保存全文,敏感内容还要做权限与脱敏控制。但至少要保留能复现排序决策的标识、版本和关键候选。

没有版本,今天重放同一 Query 得到不同结果,也不知道是内容、模型还是参数变了。

没有候选,日志里只剩一句“回答错误”,团队只能重新猜测。

怎样把失败归到唯一责任层?

排查最容易混乱的地方,是几个阶段同时看起来“不够好”。例如正确证据没有进入最终上下文,既可能是向量召回没有找到,也可能是融合、重排或最后截断造成的。此时不要直接给整个链路贴上“检索差”的标签,而要为每一层定义一个可以判断通过或失败的输入输出。

离线入库层的输入是原文,输出是带稳定 ID、来源位置和版本的可检索 Chunk。只要人工标注的正确原文无法在当前索引中按身份找到,这一层就没有通过。召回层的输入是实际 Query 与过滤条件,输出是两路候选;正确 Chunk 已在索引中却没有进入候选,才继续检查查询表达、Embedding、关键词字段和候选范围。

融合与重排层不负责创造新证据。正确 Chunk 已经被某一路召回,却在合并、去重或精排后消失,问题才归到排序阶段。上下文组装层则检查重排后的候选是否因长度预算、相邻去重或模板清洗而丢失必要信息。只有正确证据完整进入模型输入后,才把失败继续交给生成与引用环节。

这种分层判断还有一个好处:修复验收可以落到阶段输出,而不必等待最终回答偶然变对。比如修复索引问题,先确认正确 Chunk 恢复可检索;修复融合问题,先确认它在相同候选下的名次与去重结果符合预期;修复上下文问题,先确认多段证据都实际进入输入。最终答案仍要回归,但它不再是唯一证据。

如果同一次请求同时暴露多个缺陷,就按证据流从前往后处理。前一层输入不正确时,后一层的表现没有可比性。先恢复最早发生的断点,再用同一份 Trace 重跑,才能看清后续是否仍有独立问题。

怎样准备一个最小复现包?

完整 Trace 很重要,但排查时不应该把整个系统环境都复制一遍才开始分析。

可以为每条重点问题整理一个最小复现包。

第一部分是输入,包括原 Query、必要的对话历史和实际过滤条件。

第二部分是目标,包括参考答案、正确证据原文、文档身份、页码和 Chunk ID。

第三部分是阶段结果,包括 BM25、向量、融合和 Rerank 的前几条候选,以及最终上下文。

第四部分是版本,包括解析、切分、索引、Embedding、Rerank 和 Prompt 版本。

第五部分是预期判断,明确正确证据应该在哪一层出现,最终需要几段证据,以及哪些旧版本或无权限内容必须被排除。

最小复现包有两个作用。

一是把问题从大量运行日志中抽离出来,让每次修改都能快速重跑同一输入。二是让不同成员讨论的是同一份候选和证据,不会有人看旧索引,有人看新模型,最后得出互相矛盾的结论。

敏感文档不能直接复制时,也应保留经过授权的最小片段与稳定标识,并确保复现环境仍遵守权限。为了调试而绕过访问控制,会让结果失去真实约束。

复现包还应记录“当前未知”。

如果没有人工确认正确证据,就明确标记待标注,而不是先假设某个候选正确。调试建立在错误标准上,只会让系统更稳定地返回错误内容。

修复以后,怎样防止只修好一个例子?

第一,必须用原始 Query 重跑。

不要把问题手工改得更标准,再证明系统已经恢复。那只说明换了一个更容易的输入。

第二,保持正确证据不变。

修复前后都追踪同一原文,才能判断它在哪一层发生变化。

第三,一次只改一个主要因素。

同时换 Embedding、改 Query、调融合权重和扩大 Top-N,即使答案变对,也无法知道哪个改动有效。

第四,增加同类相邻样本。

修好一个否定问题后,再测试其他否定表达与正面问题,防止规则只记住某个关键词,或者把正常查询一起破坏。

第五,分别看检索和生成。

正确证据名次上升是检索收益,最终答案改善是端到端收益。两者都要记录,不能让生成模型的偶然表达掩盖候选仍然错误。

回归结果还要绑定索引快照或内容版本。同一 Query 如果面对的知识库已经变化,前后候选不能直接归因于算法调整。先确认输入语料一致,再比较参数和模型,才能让实验结论可复现。

如果知识库必须同步更新,就保留更新前后的两套结果,明确区分内容变化带来的改善与检索策略带来的改善。否则一次正确答案可能只是新版资料恰好补齐,并不能证明算法修复有效。

RAG 检索问题修复验收清单

原 Query、正确证据、单一变量和同类样本共同组成回归,避免用一个更容易的问题证明修复。

面试官换一层追问,就沿 Trace 往前走

每个问题都对应证据流上的一层。先指出正确证据此刻在哪里,再说明应查看的 Trace 字段和下一步回归,避免直接跳到某个调参动作。

正确证据没入库,为什么调检索没有用?

因为检索器只能在现有索引中排序。原文没有被解析、切分或写入,再高的 Top-K 也只会返回其他内容。

为什么要分别看 BM25 和向量候选?

两路解决的失败类型不同。拆开后才能判断正确证据由哪一路找回,又是否在融合时被压下去。

否定问题为什么容易出错?

正面条款与例外条款常共享大量主题词,Query 改写还可能丢掉否定。需要同时检查意图保持、候选内容和重排顺序。

Rerank 后仍然不对,先查什么?

先确认正确证据是否进入它的候选范围,再看输入截断、模型适配和重排前后名次。候选不存在时,问题仍在召回层。

Trace 为什么一定要带版本?

文档、索引、模型、提示词和参数都会变化。没有版本,同一个请求无法稳定复现,也无法判断结果变化来自哪里。

修复后为什么还要加相邻样本?

单个规则可能只修好一个句式,却破坏同类其他问题。相邻样本用来验证修复针对的是失败机制,而不是记住某个案例。

按正确证据的去向完成回答

完整回答沿着正确证据从原文到最终上下文的去向展开:

我不会先改 Prompt,而是先固定原 Query、参考答案和正确证据,沿证据流逐层排查。第一步确认原文已经正确解析、切分并写进当前索引;第二步比较原 Query 与改写 Query,检查实体、否定和过滤条件;第三步分别查看 BM25 与向量候选,再看融合和 Rerank 前后正确证据的分数与名次;最后核对实际交给模型的上下文是否完整。

每层都通过同一个 Trace ID 串起来,记录 Chunk ID、来源、召回通道、过滤条件、解析与索引版本、模型版本和候选变化。正确证据不存在就查离线入库,存在但没召回就查 Query 与检索,召回后排得靠后查融合与 Rerank,已经进入上下文仍答错才查 Prompt 和生成。

修复后继续用原 Query 和同一证据回归,一次只改一个主要因素,再补同类相邻样本。这样才能证明修复的是一类错误,而不是把问题换简单或碰巧让某个答案变对。

回答结束前,应明确指出正确证据在哪一层消失、这项修复对应哪个失败机制,以及原问题和相邻样本的回归结果如何证明没有制造新退化。判断必须来自留存的 Trace、候选变化和回归记录,而不是调参后的主观印象。

INTERVIEW FOLLOW-UPS

面试官会怎样继续追问?

这些追问会继续检查方案取舍、验证方法和项目事实。
  1. Q01

    正确证据没入库时,为什么调检索参数没有用?

  2. Q02

    否定问题为什么容易召回正面描述?

  3. Q03

    多跳问题怎样判断应该拆成几个子查询?

  4. Q04

    怎样设计一条能贯穿全链路的 Trace?

PROJECT CHECK

换成你的项目,这几件事能否说清楚?

  • 保存每层候选、分数和过滤条件
  • 准备同义词、否定和多跳 bad case
  • 标记正确证据在哪一层消失
  • 修复后用原样本做回归
用自己的项目经历练一次