跳到主内容RAG 检索效果差怎么排查?从 Query 到 Rerank 逐层定位 · AlgoMooc面试题 · RAG 项目面试题
RAG 检索效果差怎么排查?从 Query 到 Rerank 逐层定位
检索器只能在已有语料中挑选,不能找回从未进入候选空间的内容。
这一层应该保存文档身份、解析版本、切分版本、Chunk ID、索引版本和当前状态。
当原文件更新后,还要确认正确证据属于当前有效版本。旧版和新版同时存在时,检索可能找到一条语义高度相关、业务上却已经失效的内容。
第二层,Query 有没有被处理错?
很多检索问题不是模型不够强,而是送进检索的 Query 已经偏离用户原话。
多轮问题里,“这个怎么申请”需要结合历史补全对象,但历史中有多个实体时,重写可能选错。
否定问题里,“哪些情况不能申请”如果丢掉“不”,检索方向会完全相反。
精确编号、错误码和产品名如果被改成自然语言,原本最强的关键词信号可能被稀释。
多跳问题拆成子查询后,是否覆盖了所有信息点?子查询是否添加了原问题没有的条件?合并时是否只保留了其中一路?
这一层的排查顺序可以是:原问题是否清楚,改写是否保持实体与否定,扩写是否引入噪声,分解是否完整,失败时是否保留原 Query 回退。
不要只看改写结果是否“更像标准问题”。它的任务是提高检索可用性,不是润色句子。
第三层,过滤条件有没有挡掉正确证据?
知识库 ID、租户、权限、文档状态、版本、时间和文档类型都会决定哪些 Chunk 有资格参与检索。
正确证据被过滤后,后面的 BM25、向量、融合和 Rerank 都看不到它。
因此每次请求都要记录实际生效的过滤条件,而不只是业务层传入的参数。
例如,调用方传入了当前知识库,检索层又根据默认状态排除某类文档。最终条件可能比日志里看到的更严格。
调试时可以先确认正确 Chunk 在无过滤情况下能否找到,再逐项恢复过滤,定位是哪条规则把它排除。
但这不意味着可以长期去掉权限或状态过滤。调试绕过只用于定位,最终修复要保证安全边界不被破坏。
第四层,两路召回分别找到了什么?
BM25 与向量检索解决的失败类型不同。把两路混在一起,会看不出正确证据原本由哪一路找到,又在哪一步被压下去。
BM25 更依赖原词、编号和专业术语。用户换了说法时,它可能漏掉正确文档。
向量检索擅长语义改写,但短 Query、领域术语和否定表达可能出现偏移。
调试时应分别保存两路候选、原始分数、排名和 Chunk 来源。
否定问题则要比较正面描述与例外条款分别在哪一路排得更高。
正确证据两路都没有出现,先查 Query、模型、索引与候选范围。
第五层,融合为什么把正确证据压下去了?
如果使用加权求和,需要确认两路分数怎样归一化、权重如何设置,以及某一路是否因为数值范围更大长期主导排序。
如果使用基于名次的融合,也要查看正确证据在各路的原始名次。两路都排得很后,融合不会自动变成第一。
同一 Chunk 由两路召回时,应按稳定 ID 合并,同时保留召回通道。如果去重键不稳定,一段内容可能以两个候选出现;如果只保留一路记录,又可能丢掉有价值的分数与来源。
这一层至少保存融合前后列表、每条候选的通道、原始分数、归一化分数、综合分和去重键。
不要只保存最后一个综合数字。出现异常时,需要能还原它由哪些信号组成。
第六层,Rerank 是没起作用,还是根本没机会?
正确证据没有进入粗召回 Top-N,它就没有补救机会。此时更换更大的重排模型不会解决漏召回。
正确证据已经进入候选,却排在后面,才是典型排序问题。
比较 Rerank 前后名次,检查模型输入是否被截断、语种与文档是否适配、否定与条件是否得到正确判断。
Top-N 太小可能把正确证据挡在精排外,太大则增加成本与噪声。调试时扩大候选能帮助判断覆盖边界,但最终参数仍要回到评测集和延迟预算上决定。
有些系统只对部分页面执行 Rerank。翻页后结果重复或跳动时,要确认不同页面是否使用相同排序口径。
第七层,最终上下文真的包含完整证据吗?
最终只保留前几条,正确证据可能被最后一次阈值过滤丢掉。
相邻重复 Chunk 占满上下文,互补证据没有空间进入。
标题、表头或列表前导句在展示前被清洗掉,模型看到的内容不再完整。
所以 Trace 还要保存最终送给模型的原始上下文,而不是只保存候选 ID。
如果正确证据已经完整进入上下文,模型仍然答错,再去看 Prompt、证据冲突、引用约束和生成模型。
每一层都保存正确证据是否存在、名次怎样变化以及为何被过滤。最终答案只是证据流的最后结果。
三类典型问题,排查入口有什么不同?
用户用口语,文档用正式术语
如果向量路线已经找到正确术语,检查融合和 Rerank。两路都没找到,再考虑同义词词典、Query 扩写或 Embedding 适配。
不能看到最终答案错了,就直接给所有 Query 增加扩写。
用户问“哪些不支持”,却召回大量正面描述
先核对原 Query 与改写 Query 的否定词是否保留,再比较正面条款和例外条款在两路候选中的名次。
如果正确例外条款已经进入候选但排得靠后,检查融合与 Rerank。如果它根本没入库,改写也无法补救。
一个问题需要两段证据
必要时把问题拆成子查询,各自召回后合并。但最后要检查两条证据是否都进入上下文,以及它们之间有没有版本或范围冲突。
同义词更像表达差异,否定涉及意图与排序,多跳涉及问题分解和证据覆盖。失败类型不同,修复和回归样本也不同。
证据没入库、没召回、被融合压低、被 Rerank 排错和被上下文截断,对应不同修复入口。
一条可复现的 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 版本。
第五部分是预期判断,明确正确证据应该在哪一层出现,最终需要几段证据,以及哪些旧版本或无权限内容必须被排除。
一是把问题从大量运行日志中抽离出来,让每次修改都能快速重跑同一输入。二是让不同成员讨论的是同一份候选和证据,不会有人看旧索引,有人看新模型,最后得出互相矛盾的结论。
敏感文档不能直接复制时,也应保留经过授权的最小片段与稳定标识,并确保复现环境仍遵守权限。为了调试而绕过访问控制,会让结果失去真实约束。
如果没有人工确认正确证据,就明确标记待标注,而不是先假设某个候选正确。调试建立在错误标准上,只会让系统更稳定地返回错误内容。
修复以后,怎样防止只修好一个例子?
不要把问题手工改得更标准,再证明系统已经恢复。那只说明换了一个更容易的输入。
修复前后都追踪同一原文,才能判断它在哪一层发生变化。
同时换 Embedding、改 Query、调融合权重和扩大 Top-N,即使答案变对,也无法知道哪个改动有效。
修好一个否定问题后,再测试其他否定表达与正面问题,防止规则只记住某个关键词,或者把正常查询一起破坏。
正确证据名次上升是检索收益,最终答案改善是端到端收益。两者都要记录,不能让生成模型的偶然表达掩盖候选仍然错误。
回归结果还要绑定索引快照或内容版本。同一 Query 如果面对的知识库已经变化,前后候选不能直接归因于算法调整。先确认输入语料一致,再比较参数和模型,才能让实验结论可复现。
如果知识库必须同步更新,就保留更新前后的两套结果,明确区分内容变化带来的改善与检索策略带来的改善。否则一次正确答案可能只是新版资料恰好补齐,并不能证明算法修复有效。
原 Query、正确证据、单一变量和同类样本共同组成回归,避免用一个更容易的问题证明修复。
面试官换一层追问,就沿 Trace 往前走
每个问题都对应证据流上的一层。先指出正确证据此刻在哪里,再说明应查看的 Trace 字段和下一步回归,避免直接跳到某个调参动作。
正确证据没入库,为什么调检索没有用?
因为检索器只能在现有索引中排序。原文没有被解析、切分或写入,再高的 Top-K 也只会返回其他内容。
为什么要分别看 BM25 和向量候选?
两路解决的失败类型不同。拆开后才能判断正确证据由哪一路找回,又是否在融合时被压下去。
否定问题为什么容易出错?
正面条款与例外条款常共享大量主题词,Query 改写还可能丢掉否定。需要同时检查意图保持、候选内容和重排顺序。
Rerank 后仍然不对,先查什么?
先确认正确证据是否进入它的候选范围,再看输入截断、模型适配和重排前后名次。候选不存在时,问题仍在召回层。
Trace 为什么一定要带版本?
文档、索引、模型、提示词和参数都会变化。没有版本,同一个请求无法稳定复现,也无法判断结果变化来自哪里。
修复后为什么还要加相邻样本?
单个规则可能只修好一个句式,却破坏同类其他问题。相邻样本用来验证修复针对的是失败机制,而不是记住某个案例。
按正确证据的去向完成回答
完整回答沿着正确证据从原文到最终上下文的去向展开:
我不会先改 Prompt,而是先固定原 Query、参考答案和正确证据,沿证据流逐层排查。第一步确认原文已经正确解析、切分并写进当前索引;第二步比较原 Query 与改写 Query,检查实体、否定和过滤条件;第三步分别查看 BM25 与向量候选,再看融合和 Rerank 前后正确证据的分数与名次;最后核对实际交给模型的上下文是否完整。
每层都通过同一个 Trace ID 串起来,记录 Chunk ID、来源、召回通道、过滤条件、解析与索引版本、模型版本和候选变化。正确证据不存在就查离线入库,存在但没召回就查 Query 与检索,召回后排得靠后查融合与 Rerank,已经进入上下文仍答错才查 Prompt 和生成。
修复后继续用原 Query 和同一证据回归,一次只改一个主要因素,再补同类相邻样本。这样才能证明修复的是一类错误,而不是把问题换简单或碰巧让某个答案变对。
回答结束前,应明确指出正确证据在哪一层消失、这项修复对应哪个失败机制,以及原问题和相邻样本的回归结果如何证明没有制造新退化。判断必须来自留存的 Trace、候选变化和回归记录,而不是调参后的主观印象。