Skip to content

9.5.1 一个 RAG 错误,怎么判断到底是“没召回到”还是“召回到了没用好”? ​

先给结论:判断一个 RAG 错误,最先要问的不是“模型为什么答错”,而是“正确证据有没有进入候选结果”。如果关键证据根本没进候选集,问题大概率在召回链路;如果证据已经进来了但最终答案还是错,问题更可能在过滤、重排、上下文构造或生成阶段。

这是错误归因里最基本的一刀。因为它能把很多混乱的讨论先拆成两类:

  • 证据没进来
  • 证据进来了,但没用好

这两类问题后面的修法通常完全不同。

为什么这一步这么重要 ​

如果你不先判断证据有没有进候选集,就很容易出现这种情况:

  • 回答错了,就直接调 prompt
  • 回答漏了,就直接加 top k
  • 生成不稳,就怀疑模型本身

但真实系统里,很多错误在更早的地方就已经注定了。

例如:

  • query 没改写对,后面再怎么 rerank 也救不回来
  • metadata filter 配错了,关键文档根本进不了候选池
  • chunk 切得太碎或太散,相关信息没法一起进来

所以先判断“证据到了没有”,是最省时间的一步。

一个更稳的排查顺序 ​

遇到一个失败案例时,可以先按下面这个顺序看。

第一步,先定义这道题的正确证据应该是什么 ​

不要一上来就看模型答案。先确认:

  • 这道题正确答案应依赖哪些文档
  • 是单文档还是多文档
  • 有没有时间版本、权限、租户或产品线边界
  • 哪些证据是必须命中的

如果这一步都不明确,后面就很难判断系统到底错在哪。

第二步,看关键证据有没有进入初次召回结果 ​

这一步看的不是最终答案,而是检索输出。

重点检查:

  • 正确文档有没有在召回结果里
  • 如果有,它的排名大概在什么位置
  • 如果没有,是完全没召回,还是被 filter 排掉了

如果关键证据在初次召回里根本不存在,这通常更像:

  • query understanding 问题
  • query rewrite 问题
  • embedding / BM25 / hybrid recall 问题
  • metadata filter 问题
  • 索引和切块问题

第三步,看关键证据有没有进入最终上下文 ​

有些问题不是“没召回”,而是“召回到了,但没被送给模型”。

这一步要看:

  • rerank 后关键证据还在不在
  • 去重、聚合、相邻块扩展时有没有把它挤掉
  • 上下文长度限制下,它是不是被截断了
  • 长上下文排序有没有把关键证据放到不利位置

如果证据在召回阶段有,但在最终上下文里消失了,问题更像:

  • rerank 不稳定
  • top k / top n 截断过早
  • 聚合策略不对
  • 上下文构造不合理

第四步,看模型有没有忠实使用已经给到的证据 ​

如果关键证据已经进入最终上下文,但回答还是错,那就要重点看:

  • 模型有没有忽略关键证据
  • 模型有没有把证据拼错
  • 模型有没有补充材料之外的内容
  • 任务指令有没有让模型偏向总结、发挥或外推

这时问题更像:

  • prompt 约束不够
  • 生成策略不稳
  • 证据排序和组织方式不利于使用
  • 任务要求和上下文不匹配

一个最实用的判断框架 ​

你可以把一个失败案例先粗分成下面三类:

第一类,证据缺失 ​

表现通常是:

  • 正确文档没有被召回
  • 召回结果全是表面相关内容
  • 明明库里有答案,但系统像完全不知道

优先检查:

  • query 是否表达清楚
  • rewrite / expansion 是否偏了
  • embedding / BM25 / hybrid 召回策略
  • filter 条件
  • chunk 和索引设计

第二类,证据存在但没有被保留下来 ​

表现通常是:

  • 初次召回里其实有正确材料
  • 但 rerank 或上下文构造后丢了
  • 最终 prompt 里看不到关键证据

优先检查:

  • rerank 排序
  • top k / top n 截断
  • 去重和聚合策略
  • 上下文长度控制
  • 相邻块扩展和文档级合并

第三类,证据已给到,但回答仍然不忠实 ​

表现通常是:

  • prompt 里已经有关键证据
  • 但回答仍然漏了、拼错了、编了
  • 回答语言看起来很流畅,但和证据并不一致

优先检查:

  • prompt 约束
  • 回答格式要求
  • 证据展示顺序
  • 模型是否被要求进行过度归纳
  • 是否缺少 groundedness / faithfulness 评测

一个最小排查示意 ​

python
def locate_failure(case):
    if not case["gold_evidence_in_recall"]:
        return "recall_or_filter_problem"
    if not case["gold_evidence_in_final_context"]:
        return "rerank_or_context_construction_problem"
    if not case["answer_grounded_in_evidence"]:
        return "generation_or_prompting_problem"
    return "need_finer_analysis"

这个示意不是为了代替真实分析,而是强调排查顺序:

  • 先看证据有没有进来
  • 再看证据有没有留下
  • 最后看证据有没有被忠实使用

两个很常见的误判 ​

误判一,看到回答错,就断定是模型问题 ​

很多错误在生成前就已经发生了。如果证据没进上下文,模型再强也没法凭空答对。

误判二,只要召回到了,就认为检索没问题 ​

这也不对。因为召回到但排在很后面、或者被后续步骤挤掉,本质上仍然会造成最终失败。

一个更适合团队协作的做法 ​

如果你是多人协作做 RAG,最好让每个失败案例都至少记录这几个字段:

  • gold_evidence_in_recall
  • gold_evidence_in_final_context
  • answer_grounded
  • primary_failure_stage
  • secondary_failure_stage

这样后面统计时,你们看到的就不只是“这周错了多少题”,而是:

  • 这周主要退化在哪一层
  • 是召回问题增多,还是生成忠实性问题增多

一句话总结 ​

判断一个 RAG 错误,最先要分清的是:关键证据到底有没有进候选结果和最终上下文。先把“没找到”和“没用好”分开,后面的归因才不会乱。