返回文章列表

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_ratemrrprecisionrecallapndcg 这类排序指标展开;RAGAS 也会把 Context PrecisionContext 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:正确证据第一次出现在第几名

MRRMean 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 PrecisionContext 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@5Recall@10Precision@5MRR延迟
纯向量检索0.620.780.460.55120ms
混合检索0.740.860.420.58180ms
混合检索 + rerank0.730.850.610.72520ms

这张表能说明很多事。

混合检索提高了 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 召回就不够了。

这时,知识不再只是一堆文本片段,而会变成图里的实体、关系和路径。