RAG 召回质量评估:不是看起来搜到了,而是证明关键证据没有漏掉
从 gold evidence、Recall@K 到排序指标,建立一套能定位 RAG 检索问题的召回评估视角。
RAG 召回质量评估:不是看起来搜到了,而是证明关键证据没有漏掉
前面几篇文章里,我们已经把 RAG 的检索链路拆开讲了一遍:资料怎么进来,怎么分块,怎么建索引,怎么在检索前后处理候选证据。
走到这里,很多人会以为系统已经差不多完成了。
因为系统看起来能搜到资料,能把资料塞给大模型,也能生成一段像样的回答。
但真实项目里最麻烦的地方恰恰在这里:
看起来搜到了,不代表搜对了;某一次搜对了,不代表以后都能稳定搜对。
尤其是 RAG 系统上线之后,你会不断遇到这些问题:
- 用户问的是上海差旅标准,系统召回了北京标准。
- 用户问的是最新版制度,系统召回了旧版通知。
- 用户问的是酒店和高铁两个额度,系统只召回了酒店额度。
- 用户问的是主管级别,系统召回了普通员工标准。
- 答案证据明明在知识库里,但没有进入 top_k。
- 召回结果里有一堆相关背景,却没有真正能回答问题的关键证据。
这时再凭感觉调 top_k、换 embedding 模型、加 rerank,就会很像摸黑修机器。
所以在继续优化 RAG 之前,你先回答一个更基础的问题:
怎么判断召回质量到底好不好?
这篇文章不把评估写成工具清单,而是从一个真实困惑出发:
RAG 系统生成错了答案时,
到底是模型不会答,
还是检索阶段没有把正确证据找回来?
还是看那条制度问答里的典型问题:
我去上海出差,高铁二等座和酒店每天最多能报多少?
这个问题要求系统至少找回几类证据:
上海属于哪个城市级别
高铁二等座怎么报销
酒店住宿每天上限是多少
这些标准适用哪个员工级别
引用的是不是当前有效版本
如果这些证据没有被召回,后面的生成模型再强,也只能在缺材料的情况下猜。
一、故事要从“答案错了,锅不一定在模型”开始
假设用户问:
我去上海出差,高铁二等座和酒店每天最多能报多少?
系统回答:
高铁二等座可以报销,酒店每天最多 450 元。
这个答案看起来挺像真的,但其实错了。因为当前制度里,上海普通员工住宿标准是 500 元/天,450 元是北京旧版标准里的一个历史值。
现在我们不能只问:
模型为什么答错了?
更应该先拆开看:
召回结果里有没有上海住宿标准?
召回结果里有没有高铁二等座标准?
召回结果里有没有旧版北京标准?
正确证据排在第几位?
错误证据排在第几位?
最终进入 prompt 的上下文是哪几段?
如果正确证据根本没有被召回,那就是检索问题。
如果正确证据被召回了,但排在第 20 位,没有进入上下文,那是排序问题。
如果正确证据和错误证据都进了上下文,但模型选择了错误证据,那是生成、提示词或证据冲突处理问题。
如果正确证据进了上下文,模型也引用了它,但最后数字抄错了,那又是另一类生成可靠性问题。
所以 RAG 评估的第一步,是把系统拆成两段:
检索评估:有没有把该找的证据找回来,并且排得足够靠前?
生成评估:模型有没有基于这些证据,生成正确、完整、可信的答案?
这篇主要看第一段:召回质量。
召回质量关心的不是“系统能不能生成一句话”,而是:
对每个问题,关键证据是否稳定出现在候选结果里,并且足够靠前。
二、评估召回前,先定义什么叫“正确证据”
很多召回评估做不起来,不是因为不会算指标,而是因为一开始没有定义清楚:
对这个问题来说,哪些资料才算应该被找回来?
比如这个问题:
我去上海出差,高铁二等座和酒店每天最多能报多少?
一个比较完整的标注可以长这样:
question_id: q_001
question: 我去上海出差,高铁二等座和酒店每天最多能报多少?
gold_evidence:
- doc_id: travel_policy_2026
chunk_id: city_level_shanghai
reason: 说明上海属于一类城市
- doc_id: travel_policy_2026
chunk_id: hotel_standard_tier_1_normal
reason: 说明普通员工一类城市住宿上限为 500 元/天
- doc_id: travel_policy_2026
chunk_id: transport_high_speed_rail_second_class
reason: 说明高铁二等座可按实际票价报销
reference_answer: 上海属于一类城市。普通员工酒店住宿上限为 500 元/天;高铁二等座可按实际票价报销。实际报销还需要符合事前审批和发票要求。
question_type: 多证据组合
difficulty: 中
这里最关键的是 gold_evidence。
它不是最终答案,而是回答这个问题必须依赖的证据集合。
有了这层标注,我们才能判断一次检索是不是合格:
retrieved_contexts:
1. 差旅制度总则
2. 上海地区住宿标准
3. 交通工具标准
4. 北京地区住宿标准
5. 发票要求
如果第 2 条和第 3 条命中了 gold evidence,但缺少“上海属于一类城市”的证据,那么这次召回就是部分合格。
它不是完全失败,但也不是完整成功。
这一步很朴素,却是 RAG 评估最重要的地基:
先把“正确答案”拆成“正确证据”,再评估检索系统有没有把这些证据找回来。
三、召回评估的基本对象:一条样本至少要有四样东西
一次可评估的 RAG 样本,至少应该包含四个对象:
用户问题 query
标准证据 reference contexts
召回结果 retrieved contexts
可选:标准答案 reference answer
用表格看更清楚:
| 对象 | 作用 |
|---|---|
query | 用户实际会问的问题 |
reference_contexts | 人工标注或规则构造的标准证据 |
retrieved_contexts | 当前检索系统实际返回的候选片段 |
reference_answer | 用于后续评估答案正确性,也可以辅助判断 context recall |
如果你只评估最终答案,就很难定位问题。
因为一个错误答案背后可能有很多原因:
没有召回正确证据
召回了但排序太低
召回了但上下文太碎
召回了但同时混入强干扰证据
召回没问题,但生成模型没用好
而检索评估会先把问题收窄到前两层:
该找的有没有找回来?
找回来以后排得够不够靠前?
这也是为什么 LlamaIndex 的检索评估会围绕 hit_rate、mrr、precision、recall、ap、ndcg 这类排序指标展开;RAGAS 也会把 Context Precision 和 Context Recall 单独作为上下文质量指标。
四、Recall@K:先看关键证据有没有漏
召回评估里最先看的指标,通常是 Recall@K。
它回答的问题是:
在前 K 个召回结果里,标准证据被找回来了多少?
公式可以这样理解:
Recall@K = 前 K 个结果中命中的标准证据数 / 标准证据总数
还是用差旅问题举例。
标准证据有 3 条:
A. 上海属于一类城市
B. 一类城市普通员工住宿上限 500 元/天
C. 高铁二等座可按实际票价报销
如果系统 top 5 召回结果命中了 B 和 C,但没有命中 A:
Recall@5 = 2 / 3 = 0.67
这说明系统已经找回了部分答案证据,但还缺一块。
为什么 Recall@K 很重要?
因为在这条链路里,生成之前必须先把证据找回来。
如果关键证据在检索阶段漏掉了,后面模型很难凭空补回来。即使模型靠常识猜对一次,也不能算系统可靠。
在生产 RAG 里,Recall@K 经常是第一优先级指标:
先保证关键证据别漏
再考虑噪声能不能压下去
最后再考虑排序和上下文预算
当然,Recall@K 也有一个明显边界:
只要 K 足够大,Recall 往往会变好,但上下文不可能无限大。
所以我们通常会同时看多个 K:
Recall@3
Recall@5
Recall@10
Recall@20
如果 Recall@20 很高,但 Recall@5 很低,说明答案其实能被找回来,只是排得太靠后。
这种情况不一定要重做索引,可能更应该加强 rerank、融合排序或过滤规则。
五、Hit Rate@K:先问“有没有至少命中一个”
Hit Rate@K 比 Recall@K 更粗一点。
它回答的问题是:
前 K 个结果里,是否至少命中了一个标准证据?
如果命中了,记 1;如果一个都没命中,记 0。
比如标准证据有 3 条,top 5 只命中其中 1 条:
Recall@5 = 1 / 3
Hit Rate@5 = 1
这两个指标表达的意思不同。
Hit Rate@K 适合快速判断:
系统有没有找到一点正确方向?
Recall@K 更适合判断:
系统有没有把完整证据链找回来?
对于简单问答,比如“公司年假最多多少天”,一个问题可能只需要一条证据。这时 Hit Rate@K 很有参考价值。
但对于多跳问题、组合问题、表格问题,Hit Rate@K 容易过于乐观。
因为只命中一条证据,不代表能回答完整问题。
对于我们的差旅例子,系统只命中“高铁二等座可报销”,却没命中“上海住宿上限”,就不能算真正成功。
所以一个经验是:
简单事实问答:Hit Rate@K 很有用
多证据组合问答:更重视 Recall@K 和 evidence coverage
六、Precision@K:再看召回结果里噪声有多少
如果 Recall 关心“有没有漏掉关键证据”,那么 Precision 关心“找回来的东西有多少是真的有用”。
Precision@K 可以这样理解:
Precision@K = 前 K 个结果中相关结果数 / K
假设 top 5 结果是:
1. 差旅制度总则
2. 上海地区住宿标准
3. 高铁二等座交通标准
4. 北京地区住宿标准
5. 发票抬头要求
如果真正和当前问题直接相关的是第 2、3 条,那么:
Precision@5 = 2 / 5 = 0.4
Precision 低意味着什么?
意味着系统虽然可能找到了答案,但同时带回了很多噪声。
噪声多会带来三个问题:
占用上下文窗口
干扰模型判断
增加生成成本和延迟
不过,RAG 里的 Precision 不能机械追求越高越好。
因为检索阶段通常宁可多拿一点候选,交给后处理阶段再筛。
更合理的目标是:
召回阶段:Recall 不要太低
重排后:Precision 和排序质量要明显提高
最终上下文:噪声少,证据完整
所以你可以分阶段看 Precision:
初始召回 top 50 的 Precision
rerank 后 top 10 的 Precision
最终进入 prompt 的 top 5 Precision
如果初始召回 Precision 低一点还能接受。
但如果最终进入 prompt 的上下文 Precision 仍然很低,模型就很容易被带偏。
七、MRR:正确证据第一次出现在第几名
MRR 是 Mean Reciprocal Rank,中文可以理解成“平均倒数排名”。
它关心的是:
第一个正确结果排在第几位?
如果第一个正确结果排第 1:
RR = 1 / 1 = 1
如果第一个正确结果排第 5:
RR = 1 / 5 = 0.2
如果完全没有正确结果:
RR = 0
把很多问题的 RR 平均起来,就是 MRR。
MRR 很适合评估这类场景:
用户问题通常只需要一个核心证据
只要第一个正确证据足够靠前,体验就会明显变好
比如用户问:
年假最多可以请多少天?
如果正确制度条款排第 1,模型很容易答对。
如果正确条款排第 8,而前面都是请假流程、病假说明、调休规则,模型就容易被干扰。
但 MRR 对多证据问题不够完整。
还是差旅问题:
系统第 1 条就命中了“上海住宿标准”,MRR 会很好看。
但如果它没有命中“高铁二等座标准”,答案仍然不完整。
所以 MRR 适合看排序首位质量,但不能替代 Recall@K。
八、NDCG:不只看有没有命中,还看好证据排得是否更靠前
有些问题里,证据不是简单的“相关”和“不相关”。
它可能有不同等级:
3 分:直接回答问题的核心证据
2 分:必要背景或补充条件
1 分:主题相关但不能直接回答
0 分:无关或干扰
比如差旅问题:
上海住宿标准:3 分
高铁二等座标准:3 分
差旅审批流程:2 分
差旅制度总则:1 分
北京住宿标准:0 分或干扰
这时可以用 NDCG@K。
它的直觉是:
高价值证据排得越靠前,分数越高。
高价值证据排得越靠后,分数越低。
NDCG 比 Precision、Recall 更适合评估排序质量。
尤其当你的召回结果里不是只有“对/错”,而是存在“强相关、弱相关、背景相关、干扰相关”时,它能更细地反映排序好坏。
不过 NDCG 的代价是标注更麻烦。
你不仅要标“是否相关”,还要标“相关程度”。
所以在早期阶段,可以先用:
Hit Rate@K
Recall@K
Precision@K
MRR
等系统进入稳定优化阶段,再引入 NDCG 或更细的等级标注。
九、Context Precision 和 Context Recall:把指标放回 RAG 上下文
传统检索指标来自搜索系统,但 RAG 有一个特殊点:
检索结果最后不是直接展示给用户,而是作为上下文交给大模型。
所以我们还需要问两个更贴近 RAG 的问题:
给模型的上下文里,有多少是真的有用?
标准答案里的关键事实,有多少能从上下文里找到依据?
这就是 Context Precision 和 Context Recall 想解决的问题。
Context Precision 关心:
retrieved_contexts 里有多少片段对回答当前问题有用?
如果上下文里 5 段,只有 2 段真正有助于回答问题,那么 context precision 就不会高。
Context Recall 关心:
标准答案里的关键事实,有多少能被 retrieved_contexts 支撑?
比如标准答案包含三个事实:
上海属于一类城市
住宿上限 500 元/天
高铁二等座可报销
如果召回上下文只能支撑前两个事实,不能支撑第三个事实,那么 context recall 就是不完整的。
这个角度特别适合 RAG。
因为我们真正关心的不是“某个 doc_id 是否命中”,而是:
模型拿到的上下文,是否足够支撑它生成正确答案。
RAGAS 的 Context Recall 就是类似思路:它会把参考答案拆成 claims,再判断这些 claims 是否能从检索上下文中得到支持。Context Precision 则更关注检索上下文里的片段是否相关,以及相关片段是否排得靠前。
如果你的系统还没有成熟的人工 gold context,也可以先用参考答案加 LLM-as-judge 做近似评估。
但要记住:
LLM 评估可以降低标注成本
人工标注仍然是关键样本的质量锚点
十、评估集怎么建:不要只用模型合成问题
指标本身不难,真正难的是评估集。
一个好的召回评估集,应该覆盖真实用户会问的问题,而不是只覆盖系统最容易答的问题。
可以从四类来源构建:
| 来源 | 适合做什么 |
|---|---|
| 真实用户日志 | 发现高频问题、真实表达、错别字和业务口语 |
| 人工设计问题 | 覆盖关键业务流程、权限边界、版本差异 |
| 从文档反向生成问题 | 快速扩大样本规模 |
| 历史失败案例 | 专门防止系统回归 |
只用模型从文档生成问题,会有一个常见问题:
生成的问题往往太像文档原文,检索难度被低估。
比如制度原文写:
一类城市普通员工住宿费标准为 500 元/人/晚。
模型可能生成:
一类城市普通员工住宿费标准是多少?
这个问题当然容易检索。
但真实用户可能会问:
我去上海出差,酒店一天最多报多少?
这里有很多隐含转换:
上海 -> 一类城市
酒店一天最多报多少 -> 住宿费标准
我 -> 普通员工或需要识别用户角色
所以评估集最好按问题类型分层:
简单事实问题
多证据组合问题
别名和缩写问题
时间版本问题
权限和角色问题
表格查询问题
跨文档推理问题
容易混淆的相似问题
这样评估结果才有诊断价值。
如果只看一个总分,你只能知道系统“好像变好了”或“好像变差了”。
按问题类型拆开看,你才能知道:
是不是别名问题拖了后腿?
是不是表格问题召回不好?
是不是旧版文档干扰太强?
是不是多证据问题 Recall 明显偏低?
十一、一个可落地的召回评估框架
可以把召回评估做成一个固定流水线:
1. 准备评估集
query + reference_contexts + reference_answer + question_type
2. 固定检索配置
embedding model、chunk 策略、索引版本、top_k、过滤规则、rerank 策略
3. 批量运行检索
对每个 query 记录 retrieved_contexts、score、rank、doc_id、chunk_id
4. 计算指标
Hit Rate@K、Recall@K、Precision@K、MRR、NDCG、Context Precision、Context Recall
5. 分桶分析
按问题类型、文档类型、部门、时间版本、难度、是否多证据拆开看
6. 失败样本归因
标注是 query 问法问题、chunk 问题、embedding 问题、metadata 问题、排序问题,还是权限/版本问题
7. 对比实验
每次只改一个关键变量,比较 baseline 和 candidate
8. 回归门禁
新版本上线前必须跑固定评估集,核心问题不能退化
这套流程的重点不是“算很多指标”,而是让每次优化都有证据。
比如你想比较三种方案:
A. 纯向量检索
B. 向量检索 + BM25 混合检索
C. 混合检索 + rerank
就不要只看几条 demo。
而是跑同一套评估集:
| 方案 | Recall@5 | Recall@10 | Precision@5 | MRR | 延迟 |
|---|---|---|---|---|---|
| 纯向量检索 | 0.62 | 0.78 | 0.46 | 0.55 | 120ms |
| 混合检索 | 0.74 | 0.86 | 0.42 | 0.58 | 180ms |
| 混合检索 + rerank | 0.73 | 0.85 | 0.61 | 0.72 | 520ms |
这张表能说明很多事。
混合检索提高了 Recall,但 Precision 下降,说明它带回了更多候选,也带回了更多噪声。
加 rerank 后 Recall 基本持平,但 Precision 和 MRR 提升,说明 rerank 把好证据排到了更靠前的位置。
但延迟也明显上升。
这时你做决策就不再靠感觉,而是看业务取舍:
如果是后台审核系统,可以接受 520ms,优先质量。
如果是高频在线助手,可能需要缩小 rerank 候选数,或只对复杂问题启用 rerank。
十二、指标变差时,应该怎么定位
评估的价值不在分数本身,而在它能指导你怎么修。
可以用下面这张诊断表:
| 现象 | 可能原因 | 优先优化方向 |
|---|---|---|
| Recall@20 低 | 正确证据根本找不回来 | 改 chunk、补 metadata、换 embedding、加混合检索、做查询改写 |
| Recall@20 高但 Recall@5 低 | 能找回,但排得太后 | 加 rerank、调融合排序、优化 score normalization |
| Hit Rate 高但多证据 Recall 低 | 只命中部分证据 | 子问题拆分、多路检索、父子块索引、结构化查询 |
| Precision@5 低 | top 结果噪声多 | rerank、过滤、去重、版本筛选、权限筛选 |
| MRR 低 | 第一个正确证据靠后 | 重排、提高标题/元数据权重、改查询构建 |
| NDCG 低 | 高价值证据没有排前面 | 等级标注、排序学习、规则加权 |
| Context Recall 低 | 标准答案里的事实缺证据支撑 | 增大候选、补证据链、多跳检索 |
| Context Precision 低 | 上下文里废材料太多 | 压缩、去重、过滤、上下文裁剪 |
| 检索指标好但答案错 | 不是召回主因 | 看 prompt、引用约束、冲突处理、生成评估 |
这里有个特别重要的判断:
不要一看到答案错,就先换大模型。
很多 RAG 错误不是模型能力问题,而是证据供应问题。
如果 Recall@K 很低,换更强的生成模型只是让它更会编。
如果 Context Precision 很低,换更强模型也可能只是让它更会在噪声里自圆其说。
先把证据链评估清楚,再决定是改检索,还是改生成。
十三、线上还要监控什么
离线评估能告诉你:
在固定评估集上,新版本是否比旧版本好。
但上线之后,用户问题会变,知识库会变,业务制度会变。
所以还需要线上监控。
线上监控不一定都能拿到标准答案,但至少可以记录这些信号:
query
retrieved doc_id / chunk_id / score / rank
最终进入 prompt 的 context
生成答案
引用来源
用户反馈
人工纠错
无答案率
低置信度率
检索延迟
rerank 延迟
上下文 token 占用
这些日志可以支持三件事。
第一,发现新问题。
比如用户开始频繁问“远程办公补贴”,但知识库里没有对应制度,系统总是召回差旅补贴。
第二,发现数据漂移。
比如新版报销制度上线后,旧版制度仍然频繁进入 top 3。
第三,反哺评估集。
线上失败样本应该定期进入离线评估集,变成回归测试。
一个成熟 RAG 系统的评估集不应该是一次性做完的。
它应该像单元测试一样持续增长:
真实失败
-> 归因
-> 修复
-> 加入评估集
-> 防止下次回归
十四、工具怎么选:先有评估思路,再用工具提效
工具可以帮你少写很多评估代码,但它不能替你定义什么叫好。
常见选择可以这样理解:
| 工具/框架 | 更适合做什么 |
|---|---|
| LlamaIndex Evaluation | 对 retriever 做离线评估,计算 hit rate、MRR、precision、recall、AP、NDCG 等指标 |
| RAGAS | 用 Context Precision、Context Recall、faithfulness 等指标评估 RAG 上下文和答案 |
| LangSmith | 做实验管理、trace、离线评估、线上观测和 LLM-as-judge |
| 自建脚本 | 适合强业务标注、doc_id 级评估、权限/版本等定制规则 |
早期可以很简单:
CSV / JSONL 评估集
一段批量检索脚本
几个基础指标
一个失败样本表
等流程跑顺之后,再接入更完整的平台。
不要反过来,一开始就把工具搭得很复杂,却没有一批可信的 gold evidence。
评估系统最值钱的资产不是 dashboard,而是那批被认真标注、能代表真实业务风险的问题。
十五、最后用一张清单记住召回评估
如果你只记一件事,可以记住这句话:
RAG 的召回评估,不是问“搜出来的内容像不像”,而是问“回答问题所需的证据有没有完整、靠前、干净地进入上下文”。
落地时可以按这张清单走:
1. 先把每个问题拆成标准证据,而不是只写标准答案。
2. 用 Hit Rate@K 看有没有至少命中。
3. 用 Recall@K 看关键证据有没有漏。
4. 用 Precision@K 看 top 结果里噪声多不多。
5. 用 MRR 看第一个正确证据是否足够靠前。
6. 用 NDCG 看高价值证据是否排在前面。
7. 用 Context Precision / Context Recall 看最终上下文是否适合生成。
8. 按问题类型分桶,不要只看平均分。
9. 把线上失败样本沉淀进离线评估集。
10. 每次优化前后都跑同一套评估,不靠 demo 体感判断。
这样,RAG 优化才会从“调参数玄学”变成一个可观察、可比较、可回归的工程过程。
下一篇进入 GraphRAG 时,我们会看到另一个问题:
普通向量检索更擅长找相似片段,但当问题需要关系、多跳推理和全局结构时,仅靠 chunk 召回就不够了。
这时,知识不再只是一堆文本片段,而会变成图里的实体、关系和路径。