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 的 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 如果面对的知识库已经变化,前后候选不能直接归因于算法调整。先确认输入语料一致,再比较参数和模型,才能让实验结论可复现。
如果知识库必须同步更新,就保留更新前后的两套结果,明确区分内容变化带来的改善与检索策略带来的改善。否则一次正确答案可能只是新版资料恰好补齐,并不能证明算法修复有效。

原 Query、正确证据、单一变量和同类样本共同组成回归,避免用一个更容易的问题证明修复。
面试官换一层追问,就沿 Trace 往前走
每个问题都对应证据流上的一层。先指出正确证据此刻在哪里,再说明应查看的 Trace 字段和下一步回归,避免直接跳到某个调参动作。
正确证据没入库,为什么调检索没有用?
因为检索器只能在现有索引中排序。原文没有被解析、切分或写入,再高的 Top-K 也只会返回其他内容。
为什么要分别看 BM25 和向量候选?
两路解决的失败类型不同。拆开后才能判断正确证据由哪一路找回,又是否在融合时被压下去。
否定问题为什么容易出错?
正面条款与例外条款常共享大量主题词,Query 改写还可能丢掉否定。需要同时检查意图保持、候选内容和重排顺序。
Rerank 后仍然不对,先查什么?
先确认正确证据是否进入它的候选范围,再看输入截断、模型适配和重排前后名次。候选不存在时,问题仍在召回层。
Trace 为什么一定要带版本?
文档、索引、模型、提示词和参数都会变化。没有版本,同一个请求无法稳定复现,也无法判断结果变化来自哪里。
修复后为什么还要加相邻样本?
单个规则可能只修好一个句式,却破坏同类其他问题。相邻样本用来验证修复针对的是失败机制,而不是记住某个案例。
按正确证据的去向完成回答
完整回答沿着正确证据从原文到最终上下文的去向展开:
我不会先改 Prompt,而是先固定原 Query、参考答案和正确证据,沿证据流逐层排查。第一步确认原文已经正确解析、切分并写进当前索引;第二步比较原 Query 与改写 Query,检查实体、否定和过滤条件;第三步分别查看 BM25 与向量候选,再看融合和 Rerank 前后正确证据的分数与名次;最后核对实际交给模型的上下文是否完整。
每层都通过同一个 Trace ID 串起来,记录 Chunk ID、来源、召回通道、过滤条件、解析与索引版本、模型版本和候选变化。正确证据不存在就查离线入库,存在但没召回就查 Query 与检索,召回后排得靠后查融合与 Rerank,已经进入上下文仍答错才查 Prompt 和生成。
修复后继续用原 Query 和同一证据回归,一次只改一个主要因素,再补同类相邻样本。这样才能证明修复的是一类错误,而不是把问题换简单或碰巧让某个答案变对。
回答结束前,应明确指出正确证据在哪一层消失、这项修复对应哪个失败机制,以及原问题和相邻样本的回归结果如何证明没有制造新退化。判断必须来自留存的 Trace、候选变化和回归记录,而不是调参后的主观印象。