RAG 系统常见的失败模式有哪些?检索质量差怎么排查和优化?
结论:RAG 的失败几乎都能落到五个层次上——没召回、召回错、没用好、生成不忠实、工程链路坑。 排查顺序必须是"从检索往生成走":先把 top-k 打印出来人工看一眼,正确片段在不在里面。 超过一半的"模型答得不好",根因其实在检索,改提示词是无效劳动。
1. 失败模式对照表
| 现象 | 根因层 | 怎么确认 | 修复手段 |
|---|---|---|---|
| 回答"资料中没有相关信息" | 召回 | top-k 里确实没有正确块 | 查询改写、混合检索、放宽元数据过滤 |
| 引用的是旧版本制度 | 索引 | 同一标题出现两个版本 | 入库去重 + 版本字段过滤 + 全量重建 |
| 答案泛泛而谈,不落到条款 | 分块 | 块长度顶到上限,含多个主题 | 按结构重切、缩小块 |
| 关键条款被忽略 | 编排 | 该块在 prompt 中部 | 重排 + 首尾放置 |
| 数字/日期凭空捏造 | 生成 | 答案中的数字不在任何块里 | 强制引用、拒答策略、降温度 |
| 多轮对话突然答偏 | 查询 | 原始 query 含"它/那个" | 指代消解式改写 |
| P99 延迟 4 s+ | 工程 | 打点看检索 vs 生成 | 缓存、减少 k、异步预热 |
2. 三层定位法
用户投诉"答错了"
│
├─① 检索层:top-k 里有没有正确片段?
│ 没有 ──▶ 召回问题:改写 / 混合检索 / 补文档 / 查过滤条件
│ 有 ──┐
│ ▼
├─② 编排层:它进 prompt 了吗?排在第几?被截断了吗?
│ 被截断/排中间 ──▶ 编排问题:减条数、重排、调顺序
│ 位置正常 ──┐
│ ▼
└─③ 生成层:模型忽略证据还是编造?
忽略 ──▶ 提示词:明确"只依据资料"、要求逐条引用
编造 ──▶ 约束:引用校验、阈值拒答、换更强模型
经验比例大致是 6:2:2,所以先花时间在检索上,收益最高。
3. 一个可复用的排查脚本
def debug_query(question: str, k: int = 10, must_contain: str | None = None) -> None:
"""把一次请求的中间态全打出来,别靠猜。"""
hits = store.query(query_embeddings=embed([question]), n_results=k,
include=["documents", "metadatas", "distances"])
docs, metas, dists = hits["documents"][0], hits["metadatas"][0], hits["distances"][0]
hit_at = None
for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists)):
flag = ""
if must_contain and must_contain in doc:
flag, hit_at = " <== 正确片段", i
print(f"[{i:02d}] cos={1 - dist:.3f} src={meta['source']} {doc[:60]!r}{flag}")
print(f"\n召回结论:{'命中于第 ' + str(hit_at) + ' 位' if hit_at is not None else '未召回'}")
print(f"上下文总字符:{sum(len(d) for d in docs[:5])}")
有了 hit_at,你就知道该修召回还是修排序:
| 观察 | 判断 | 动作 |
|---|---|---|
| 未召回 | 语义/词面都没覆盖 | 加混合检索、改写查询、检查文档是否入库 |
| 排在第 8~10 位 | 召回有但排序差 | 上重排,或调整 RRF 权重 |
| 排第 1 位但仍答错 | 生成问题 | 查提示词与截断,不该继续调检索 |
4. 优化手段的优先级
| 优先级 | 手段 | 收益 | 成本 |
|---|---|---|---|
| 1 | 修分块(结构切分、加标题) | 高 | 低 |
| 2 | 加重排 | 高 | 中 |
| 3 | 混合检索 | 高(字面类查询) | 中 |
| 4 | 查询改写 | 中高(多轮、口语) | 中 |
| 5 | 换 embedding 模型 | 中 | 高(全量重建) |
| 6 | 换生成模型 | 中 | 低 |
顺序别乱:在检索召回率 60% 的系统上调提示词,最多把 60% 用到 65%;把召回拉到 90%,同样的提示词能到 85%。
5. 线上要盯的四个数
| 指标 | 含义 | 异常信号 |
|---|---|---|
| 空召回率 | top-1 分数低于阈值的请求占比 | 突增说明新问法或文档缺失 |
| 引用命中率 | 答案里的 [n] 是否都能对应真实片段 | 下降说明模型在编引用 |
| 平均上下文 token | 成本与截断风险 | 逼近上限说明该压缩了 |
| 人工差评率 | 抽样标注 | 唯一可信的终局指标 |
6. 常见追问
Q:用户说"答得不对",第一步做什么? 复现那条 query,把原始 query、改写后的 query、top-k 文本全打出来。80% 的情况看一眼就知道问题在哪,不需要实验。
Q:长尾 query 怎么系统性发现? 按"空召回 + 低分"聚类线上日志,每周看一次 top 失败簇,反推该补哪类文档。
Q:加了重排反而变差? 检查重排模型的输入是否超 max_length 被截断,以及是否把 min_score 设得太高导致候选全被过滤。
小结
RAG 调优的本质是分层归因:先证明证据在不在,再证明模型用没用。跳过任何一层,都会陷入"改提示词—没用—再改"的循环。