RAG 评估入门:Recall@k、MRR、nDCG、Faithfulness
对每个 query,如果 Top-k 检索结果里。
RAG 评估入门:Recall@k、MRR、nDCG、Faithfulness
RAG 评估入门:Recall@k、MRR、nDCG、Faithfulness 到底怎么用?
适用场景:企业知识库问答 / 课程资料问答 / 文档助手 / 内部制度查询 / 产品手册助手
目标:把“感觉还行”的 RAG,变成“可量化、可对比、可迭代”的 RAG。
0. 为什么 RAG 一定要评估?
很多团队做 RAG 的常见现象是:
- 改了 chunking / embedding / Top-k / rerank / prompt,主观感觉忽好忽坏;
- “检索到了”但答案仍然错:Top-1 不准、或上下文不支持;
- “答得像真的”但其实胡编:缺乏 Faithfulness(依据一致性)约束;
- 无法解释:到底是 检索问题 还是 生成问题?
评估的意义就是:
把 RAG 拆成可度量的子问题(检索 + 生成),用指标把改动和效果绑定起来。
1. RAG 评估要评什么?
一个典型 RAG 系统可以拆成两段:
- 检索侧(Retrieval):给定 query,返回 Top-k chunk 列表(可能带 rerank)
- 生成侧(Generation):基于 Top-n chunk 生成回答(可带引用)
因此评估也分成两类:
- 检索侧指标:Recall@k、MRR、nDCG(回答“找得到/找得准”)
- 生成侧指标:Correctness(正确性)、Faithfulness(依据一致性)、Citation Coverage(引用覆盖率)(回答“答得对/答得有据可查”)
2. 评测集怎么做?
2.1 一个评测样本应该包含什么?
建议每条样本至少有这三项:
query:用户问题gold_evidence:正确证据(可以是 chunk id、段落、或文档+段落范围)gold_answer:参考答案(可选,但做 Correctness 更方便)
示例(JSON 伪例):
{
"qid": "q_001",
"query": "报销流程中差旅标准怎么规定?",
"gold_evidence": ["docA#sec3#chunk12", "docA#sec3#chunk13"],
"gold_answer": "差旅标准包括交通、住宿和伙食补贴,分别按员工级别与城市等级执行……"
}
2.2 gold_evidence 怎么标?
三种常见方式:
- chunk 级标注:直接标注“哪些 chunk 支持答案”(最推荐,最贴近 RAG)
- 段落/页码标注:先标注段落范围,再映射到 chunk(适合 PDF)
- 文档级标注:只标文档,不标段(太粗,容易高估检索效果)
3. 检索侧指标:Recall@k、MRR、nDCG
下面三项是 RAG 最常用的检索评估指标。
3.1 Recall@k:Top-k 里有没有“正确证据”
定义:
对每个 query,如果 Top-k 检索结果里出现任意一个 gold_evidence,则记为命中。
最后对所有 query 求平均。
- 优点:简单、最常用
- 缺点:不关心排序(Top-1/Top-3/Top-10 的差异会被“掩盖”)
3.2 MRR(Mean Reciprocal Rank):第一个正确结果排第几
定义:
看第一个正确证据在检索列表中的排名 rank,得分为 1/rank。
例如:
- 正确 chunk 在第 1 名:得分 1
- 在第 2 名:得分 0.5
- 在第 5 名:得分 0.2
- Top-k 都没有:得分 0
最后对所有 query 求平均。
- 优点:非常关注 Top-1 / Top-3 是否准确(RAG 体验关键)
- 缺点:只看“第一个正确”,不看后面还有多少正确
3.3 nDCG:考虑“相关程度”和“排序质量”
在很多任务里,证据不止一个,而且相关性可能是分级的(比如:强相关/弱相关)。
nDCG(Normalized Discounted Cumulative Gain)能同时考虑:
- 相关性分数(relevance)
- 排名折扣(越靠前贡献越大)
3.3.1 DCG@k(核心思想)
DCG@k = Σ_{i=1..k} (rel_i / log2(i+1))
rel_i:第 i 个结果的相关性(例如 0/1,或 0/1/2/3)log2(i+1):排名折扣
3.3.2 nDCG@k(归一化)
nDCG@k = DCG@k / IDCG@k
-
IDCG@k:理想排序(把最相关的都排在最前面)得到的 DCG -
优点:适合 多证据、多相关等级 的场景
-
缺点:需要定义
rel(相关性怎么打分)
4. 生成侧指标:Correctness vs Faithfulness
4.1 Correctness(正确性):答案对不对?
- 关注最终答案是否与事实一致、是否回答了问题
- 需要
gold_answer(参考答案)或人工评审
4.2 Faithfulness(依据一致性):答案是否“严格基于检索证据”?
- 关注答案中的关键结论是否都能在检索 chunk 中找到依据
- 即使答案“碰巧正确”,但如果证据里没有、模型自己推出来了,也可能在企业场景不可接受
5. Faithfulness 怎么评?(三种常用做法)
5.1 引用覆盖率
要求模型输出带引用,例如:
- 每个要点后加
[doc#chunk] - 或者每句话后加引用
然后统计:
- Coverage:关键句是否都带引用
- Validity:引用的 chunk 是否真的包含支撑信息
优点:工程简单、效果立竿见影
缺点:引用可能“乱贴”(需要 Validity)
5.2 句子级支持度打分
思路:把回答拆成句子,对每句在 Top-n chunk 里找“最能支持它的证据”,如果找不到就判为不支持。
可用两类“相似度/判别器”:
- Embedding 相似度(快,但可能误判)
- NLI/LLM Judge(更准,但更慢/更贵)
5.3 LLM-as-a-Judge
让一个更强的模型充当评审,输入:(query, retrieved_context, answer)
输出:Faithfulness 分数 + 解释 + 不支持的句子。
注意:
- 评审模型本身也可能偏差
- 建议做 抽样人工复核,以及保持评审 prompt 固定
6. Python:实现 Recall@k / MRR / nDCG
下面给一个不依赖第三方评测库的最小实现,方便快速集成到自己的 RAG pipeline。
import math
from typing import List, Dict, Set
def recall_at_k(retrieved: List[str], gold: Set[str], k: int) -> float:
# 是否在 Top-k 内命中任意 gold evidence
topk = retrieved[:k]
return 1.0 if any(x in gold for x in topk) else 0.0
def mrr_at_k(retrieved: List[str], gold: Set[str], k: int) -> float:
# Top-k 内第一个 gold 的倒数排名;没命中为 0
topk = retrieved[:k]
for i, x in enumerate(topk, start=1):
if x in gold:
return 1.0 / i
return 0.0
def dcg_at_k(retrieved: List[str], rel_map: Dict[str, float], k: int) -> float:
# DCG@k = Σ rel_i / log2(i+1)
dcg = 0.0
for i, x in enumerate(retrieved[:k], start=1):
rel = rel_map.get(x, 0.0)
dcg += rel / math.log2(i + 1)
return dcg
def ndcg_at_k(retrieved: List[str], rel_map: Dict[str, float], k: int) -> float:
# nDCG@k = DCG@k / IDCG@k
dcg = dcg_at_k(retrieved, rel_map, k)
# 理想排序:按 rel 从大到小
ideal = sorted(rel_map.items(), key=lambda kv: kv[1], reverse=True)
ideal_list = [cid for cid, _ in ideal]
idcg = dcg_at_k(ideal_list, rel_map, k)
return 0.0 if idcg == 0 else (dcg / idcg)
def evaluate_retrieval(dataset: List[Dict], k_list=(5, 10, 20)) -> Dict[str, float]:
# dataset 每条包含:
# - retrieved: List[str] (你的检索结果 chunk ids,已按分数排序)
# - gold_evidence: Set[str]
# - rel_map: Dict[str, float] (可选:用于 nDCG)
report = {}
for k in k_list:
recall_scores, mrr_scores, ndcg_scores = [], [], []
for ex in dataset:
retrieved = ex["retrieved"]
gold = set(ex["gold_evidence"])
recall_scores.append(recall_at_k(retrieved, gold, k))
mrr_scores.append(mrr_at_k(retrieved, gold, k))
rel_map = ex.get("rel_map")
if rel_map is not None:
ndcg_scores.append(ndcg_at_k(retrieved, rel_map, k))
report[f"Recall@{k}"] = sum(recall_scores) / max(1, len(recall_scores))
report[f"MRR@{k}"] = sum(mrr_scores) / max(1, len(mrr_scores))
if ndcg_scores:
report[f"nDCG@{k}"] = sum(ndcg_scores) / max(1, len(ndcg_scores))
return report
if __name__ == "__main__":
# 一个最小样例
dataset = [
{
"retrieved": ["c7", "c2", "c9", "c1"],
"gold_evidence": {"c2", "c3"},
# relevance 分级示例:c2 更关键
"rel_map": {"c2": 2.0, "c3": 1.0},
},
{
"retrieved": ["c4", "c5", "c6"],
"gold_evidence": {"c6"},
"rel_map": {"c6": 2.0},
},
]
print(evaluate_retrieval(dataset, k_list=(1, 3, 5)))
8. 如何解读指标?
8.1 常见组合与含义
- Recall@20 高、MRR@5 低:说明“能找到”但“排不前”,需要 rerank / 更强的融合策略
- Recall@k 低:说明检索本身有问题(embedding、索引、chunking、过滤条件、Hybrid)
- nDCG 上升但 Recall 不变:排序变好但覆盖没变,通常是 rerank 起效
8.2 检索指标高,生成还是错?
高概率是:
- Top-n context 里有正确证据,但 prompt 没约束好(模型“自由发挥”)
- chunk 太长/噪声太多,模型没读到关键句
- 证据分散在多个 chunk,缺少结构化整合(需要多段汇总或多跳检索)
此时应该上:
- 更严格的“仅基于 context”约束
- 引用输出 + 引用校验(Faithfulness)
- 更强的 rerank / 更小更干净的 chunk
更多推荐


所有评论(0)