RAG 效果不好,怎么一步步排查?

深入场景题实践约 7 分钟读完

一句话回答

先别急着换模型或调参数,拿几个具体的失败案例,沿着链路逐段看中间结果:知识库里有没有答案 → 解析出来的文本对不对 → 切块后是否完整 → 召回结果里有没有正确的片段 → 重排后它排第几 → 进入 Prompt 的上下文是什么 → 模型有没有忠实地使用它。找到第一个出错的环节再改,改完用评测集确认没有让其他问题变差。

详细解析

准备工作

  • 能看到中间结果:每次请求都记录改写后的查询、召回列表(ID、分数、排名)、重排分数、最终的 Prompt 和回答(见 链路追踪)。没有这些,排查只能靠猜
  • 收集一批失败案例:从用户点踩、人工抽检里收集,按现象分类,不要只看一个例子就下结论
  • 确认标准答案:对每个失败案例,人工找出知识库里能回答它的文档和原文片段。如果根本没有,那是知识库缺内容,不是检索的问题

沿链路逐段检查

  1. 入库:文档有没有同步进来、是不是最新版本、有没有被权限或元数据过滤掉
  2. 解析:直接看解析后的纯文本。PDF 多栏排版的顺序错乱、表格变成一堆对不上的数字、页眉页脚混进正文、扫描件没做 OCR 导致内容为空,都很常见
  3. 切分:找到包含答案的块,看它是否完整:答案有没有被切到两块里,块里是不是只剩"该功能"这类没有主语的描述,表头和数据有没有被分开
  4. 召回:看召回的前几十条里有没有包含答案的块。没有的话,用答案的原文本身去检索:能找到,说明问题和文档的表达差异大,考虑查询改写、混合检索;还是找不到,就检查 Embedding 模型和索引。有的 Embedding 模型要求查询和文档加上不同的前缀或指令,漏加也会影响效果
  5. 重排:召回里有,但重排后没进前 K 条,可能是重排模型不适合这个领域或语言、块太长被截断,或者 K 太小
  6. 上下文组装:最终放进 Prompt 的是不是重排后的前 K 条,有没有因为 token 预算被截掉;有没有新旧版本互相矛盾的资料同时出现
  7. 生成:上下文里明明有答案,模型却答错了。可能是 Prompt 没要求只根据资料回答、没允许说"不知道";上下文太长,答案在中间被忽略(见 上下文窗口);问题需要综合多块信息,模型的推理能力不够;输出被长度上限截断

常见现象和原因

现象 常见原因 先查什么
回答"没有找到相关信息",但知识库里明明有 召回没找到:表达差异大、专有名词、过滤条件太严、文档没入库 召回结果;用原文检索
问型号、编号,回答的是相近的另一个 纯向量检索对精确词不敏感 加关键词检索(见 混合检索)
多轮对话里一追问就答偏 用带指代的原句去检索 改写日志(见 查询改写)
表格类问题数字答错 表格解析错乱,或者表头和数据被切开 解析后的文本、切块
新旧政策混在一起回答 旧版本文档没下线,新旧块同时被召回 文档版本、更新时间的元数据
回答的内容在资料里找不到 Prompt 没有约束,资料不足时模型自由发挥 忠实度评分、Prompt
简单问题对,比较类、汇总类问题错 一次检索找不全多个实体的信息 子问题拆分;汇总类问题见 GraphRAG
只在某个语言或领域效果差 Embedding 模型不适合这个语言或领域 在评测集上对比几个模型

修完之后

把排查过的失败案例加进评测集。每次只改一个变量,跑完整的评测集,和改动前逐条对比,重点看有没有原来对、现在错的样本(见 RAG 系统怎么评估)。

代码示例

把前几步写成脚本,对每个失败案例自动定位第一个出问题的环节。chunkStore、hybridSearch、rerank 是项目里的检索函数,过滤条件要和出错的那次请求一致:

TypeScript
async function diagnose(c: { question: string; docId: string; evidence: string }, topK = 5) {
  const norm = (s: string) => s.replace(/\s+/g, '') // 忽略解析带来的空白差异
  const hasEvidence = (text: string) => norm(text).includes(norm(c.evidence))

  const docChunks = await chunkStore.listByDoc(c.docId)
  if (docChunks.length === 0) return '入库:文档没有被索引'
  if (!docChunks.some((ch) => hasEvidence(ch.text))) return '解析或切分:没有一块包含完整的证据原文'

  const recalled = await hybridSearch(c.question, 50)
  const recallRank = recalled.findIndex((ch) => hasEvidence(ch.text)) + 1
  if (recallRank === 0) {
    const byEvidence = await hybridSearch(c.evidence, 50) // 用证据原文本身去检索
    return byEvidence.some((ch) => hasEvidence(ch.text))
      ? '召回:问题和文档的表达差异大,考虑查询改写、混合检索'
      : '索引:用原文都检索不到,检查 Embedding 模型和索引配置'
  }

  const reranked = await rerank(c.question, recalled)
  const finalRank = reranked.slice(0, topK).findIndex((ch) => hasEvidence(ch.text)) + 1
  if (finalRank === 0) return `重排:召回时排第 ${recallRank},重排后没进前 ${topK}`

  return `检索正常(最终排第 ${finalRank}):检查上下文组装、Prompt 和生成`
}

面试官可能追问

换一个更强的模型,能解决问题吗?

只能解决最后一环的问题。如果正确的资料根本没进上下文,换多强的模型也答不对,反而可能"编"得更像真的。所以要先确认检索没问题,再考虑换生成模型。

检索结果里有正确答案,模型还是答错了,怎么办?

减少噪声:降低 K、提高重排阈值,去掉不相关和重复的块;把最相关的块放在开头或结尾;Prompt 里要求先找出相关的原文、再基于原文回答;检查是否有互相矛盾的资料。问题本身需要多步推理时,再考虑换更强的模型。

怎么判断是不是 Embedding 模型的问题?

只换 Embedding 模型、其他保持不变,在评测集上对比几个候选模型的 Recall@k。同时检查几项容易忽略的配置:模型是否支持中文和领域术语,查询和文档是否按模型要求加了前缀,文本是否超过模型的最大输入长度被截断,换模型后是否重新向量化了全部文档。

易错点

  • 一上来就换模型、调参数,不看中间结果
  • 忽略解析环节,大量问题其实出在 PDF 解析和表格上
  • 一次改多个变量,改完不跑评测集,不知道是哪个改动起了作用
  • 修好眼前的案例就上线,没检查其他案例有没有变差

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。