RAG 召回摘要问题排查
🔍 “只召回摘要/标题”不是单一故障,而是索引内容、检索粒度、返回字段、上下文压缩四个环节之一改变了原文。排查时不要先调模型或 topK,应先沿数据流逐层打印证据。
先建立正确的数据流
原始文档 → 解析结果 → 原文 chunk → embedding / 稀疏索引 → 候选集 → 重排 → 最终上下文 → 生成答案
每一层都抽样记录 doc_id、chunk_id、正文前 200 字和字符区间。在哪一层正文首次变成标题或摘要,问题就在哪一层。
30 分钟最小诊断法
- 选 3 个答案明确、可定位到原文的问题。
- 对每个问题保存召回前 20 条候选,不要只看最终 5 条。
- 分别检查
indexed_text、returned_text、prompt_context。 - 临时关闭重排、压缩、查询改写与摘要链,一次只恢复一个组件。
- 记录 Recall@K:正确证据是否进入候选集;若进入但没被使用,再检查重排和生成。
1) 先确认:你到底把什么写进了向量库?
最常见情况:embedding 的输入用的是 summary / title,或者只把摘要字段入库了,所以召回结果当然也只像“摘要”。
- 检查 embedding 代码:embedding 的
text_to_embed是否来自摘要字段? - 检查入库字段:向量库里每条记录是否有
content_chunk(原文片段)以及doc_id / source / offset等元信息? - 快速验证:随便抽一条向量库记录,看
text/payload是否就是摘要。
修复原则:
- embedding 必须来自原文 chunk(摘要可以作为 metadata,或辅助 BM25/重排特征,但不要替代 chunk 原文)。
- 召回返回的
text应该是可直接拼进 prompt 的原文片段,不是 summary。
2) Chunk 策略:别把 chunk 做成“自动摘要”
如果你在切分时做了“每段先总结成一句话再入库”,那召回自然像摘要。
推荐做法(经验值):
- chunk size:中文 300–800 字(或 500–1500 tokens)
- overlap:10%–20%(例如 80–150 字)
- chunk 里保留:原文 + 小量结构(标题层级/小节名)
另外,务必把这些写进 metadata:
document_title / section_title / heading_pathchunk_indexsource_urlstart_char/end_char或页码
这样召回后你能“回填上下文”,不至于只靠一段话。
3) 召回时返回字段:不要只取 summary 字段
很多人 query 时只取了 summary(比如为了省 token),导致看起来“召回都是摘要”。
- 检查 query 返回:是否有
chunk_text?还是只取了summary? - 如果你做了两段式:先召回 doc,再取 doc.summary ——那你需要在第二段再召回 doc 下的 chunks,或者直接召回 chunks。
正确链路(常见可行):
- query → topK chunks(向量/BM25/混合)
- 可选:cross-encoder rerank top 50 → top 5–10
- 把 top chunks 原文拼 prompt(带来源)
4) 你看到“像摘要”,也可能是重排/压缩器在作祟
如果你用了:
- LLM-based rerank
- contextual compression(压缩检索结果)
- “回答前先总结证据”的链
这些会把 chunk 压成摘要再喂给下游。
排查方法:
- 在进入 LLM 前,打印/日志输出最终 prompt 里的“证据块”到底是什么。
- 关掉压缩器,直接把原文 chunk 喂给生成,看是否恢复。
5) 混合检索 + 重排,能显著减少“泛泛摘要”
如果你的语料结构化强、标题多,纯向量容易召回“概述性段落”。
建议:
- Hybrid:BM25(关键词) + 向量(语义)一起召回,再合并去重
- Rerank:cross-encoder(例如 bge-reranker、cohere rerank 等)对 top 50 做重排
- MMR:让 topK 更“多样”,减少都落在概述段落
6) 如果你确实需要摘要:让摘要作为“补充视图”,不是召回主体
可以在每个 chunk 或 doc 旁边存:
doc_summary(文档级)chunk_summary(可选,用于浏览/展示) 但回答时:- 证据引用:用
chunk_text - 用户展示:可以额外显示 summary 作为“预览”
建议的日志结构
{
"query": "原始问题",
"rewritten_queries": ["改写后的问题"],
"candidates": [
{
"chunk_id": "doc-7#p3#c2",
"dense_score": 0.72,
"bm25_score": 8.4,
"rerank_score": 0.91,
"text_preview": "原文前 200 字"
}
],
"selected_chunk_ids": ["doc-7#p3#c2"],
"prompt_context_chars": 4830
}
线上不要默认记录完整敏感正文,可保存脱敏预览、哈希和原文引用。
用指标定位,而不是凭感觉
- Recall@K 低:索引、query、过滤或召回有问题。
- Recall@K 高但 MRR 低:需要融合或重排。
- 排名正常但 Context Precision 低:候选噪声过多,组装策略有问题。
- 证据充分但 Faithfulness 低:生成 Prompt、引用约束或模型有问题。
易被忽略的原因
- metadata 过滤条件过严,正确文档在召回前已被排除。
- 文档解析失败,只保存了目录、页眉或 OCR 摘要。
- parent-child retrieval 先找到父摘要,但没有回填子 chunk。
- topK 按“文档”去重后只剩每篇文档的标题块。
- 上下文窗口预算按字符错误估算,原文在尾部被截断。
- reranker 输入长度限制导致正文被截掉,只看到了标题。
- 索引版本与查询服务版本不一致。
回归检查清单
- 随机抽样索引记录,确认是原文而非摘要
- 正确证据在 Top-20 中可见
- 返回字段与 Prompt 使用字段一致
- 标题只作为上下文,不替代正文
- 关闭每个增强组件后有对照结果
- 记录 Recall@K、MRR、Context Precision 和 Faithfulness
- 每个线上失败问题进入固定测试集