RAG 的 demo 一天就能搭出来,但“它到底靠不靠谱”这个问题,很多人答不上来。原因很简单:大多数人只测了端到端——问一个问题,看答案对不对。对了就过,错了也不知道错在哪。
这篇讲怎么把评测拆开做。
一、为什么必须拆成两段测
RAG = 检索(Retrieval)+ 生成(Generation)。最终答案错了,至少有四种可能:
- 检索没找到正确文档
- 找到了,但模型没用上
- 用了,但掺了自己的编造
- 文档里明明有答案,模型却说“不知道”
只看端到端答案对错,你无法定位,也就无法修。所以评测必须分层:检索测召回质量,生成测忠实度与相关性。
二、检索侧:能量化“找得准不准”
前提是标注:每条 query 标注哪些 chunk 是相关的。小样本起步(50~100 条),后续迭代补。
| 指标 | 含义 |
|---|---|
| Hit@K | top-K 里是否命中至少一个相关文档 |
| Recall@K | top-K 覆盖了多少比例的相关文档 |
| MRR | 第一个相关文档排名的倒数,衡量相关结果排得靠不靠前 |
| NDCG@K | 考虑排序位置的加权指标,多相关文档时更细 |
计算这三个最常用的:
fromtypingimportListdefhit_at_k(retrieved:List[str],relevant:set,k:int)->float:return1.0ifset(retrieved[:k])&relevantelse0.0defrecall_at_k(retrieved:List[str],relevant:set,k:int)->float:ifnotrelevant:return0.0returnlen(set(retrieved[:k])&relevant)/len(relevant)defmrr(retrieved:List[str],relevant:set)->float:forrank,doc_idinenumerate(retrieved,start=1):ifdoc_idinrelevant:return1.0/rankreturn0.0聚合到整个评测集:
importstatisticsdefevaluate_retrieval(dataset,k=5):# dataset: [{"retrieved": [doc_id, ...], "relevant": [doc_id, ...]}, ...]hits,recalls,mrrs=[],[],[]foritemindataset:r,rel=item["retrieved"],set(item["relevant"])hits.append(hit_at_k(r,rel,k))recalls.append(recall_at_k(r,rel,k))mrrs.append(mrr(r,rel))return{"hit@k":statistics.mean(hits),"recall@k":statistics.mean(recalls),"mrr":statistics.mean(mrrs),}# 输出形如:{'hit@k': 0.xx, 'recall@k': 0.xx, 'mrr': 0.xx} 【填真实数值】三、生成侧:把“答案好不好”拆成三个问题
- 忠实度 Faithfulness:答案里的每个断言,是否能被检索到的 context 支持?——测幻觉
- 答案相关性 Answer Relevance:答案是否切题,还是答非所问?
- 上下文精度/召回 Context Precision / Recall:检索内容里有多少是有用的;该有的有没有
这几个就是 RAGAS 的指标体系,不用自己造轮子:
# pip install ragas datasetsfromdatasetsimportDatasetfromragasimportevaluatefromragas.metricsimportanswer_relevancy,context_precision,context_recall,faithfulness data=Dataset.from_dict(拆成句子,逐句问评审模型——“这句话能否被给定的 context 支持?”)你是严格的评审员。给定【上下文】和【答案】,
请逐句判断答案中的每个事实性断言是否被上下文支持。
只输出 JSON:{“unsupported”: [“断言1”, …], “score”: 0~1}
不要使用上下文以外的任何知识。
聚合:幻觉率 = 不被支持的句子数 / 总句子数 【填真实】
关键提醒:judge 本身也会错。所以要抽样人工复核,并且给 judge 也算一个准确率——否则你只是把一个不确定的东西换成了另一个。这一步是很多评测文章会漏掉的,也是面试里最能体现深度的点。
五、端到端 + 一张能直接用的 badcase 分类表
端到端通过率 = 判定回答正确的样本占比(人工或 judge)。但比分数更重要的是把错误归类:
| 类型 | 现象 | 定位侧 | 典型原因 | 修复方向 |
|---|---|---|---|---|
| 检索未召回 | 相关 chunk 不在 top-K | 检索 | 切分、embedding、query措辞 | 调 chunk 大小、加混合检索、query改写 |
| 召回了没用 | 检索到了,答案却没引用 | 生成 | prompt 未强调依据 | 改 prompt、加引用约束 |
| 用了但编造 | 答案含 context之外的内容 | 生成 | 模型幻觉 | 引用校验、降温度、加固拒答 |
| 该答却拒答 | context有答案,却说不知道 | 生成/检索 | 拒答阈值过严 | 调阈值、补充 context |
有了这张表,每个错误都能落到“检索还是生成”上,修复方向也就出来了。
六、五个常见的坑
- chunk 切分直接决定检索上限:切太碎丢上下文,切太大噪声多,这是最该先调的东西
- 用户问法和文档措辞不一致:一定要测口语化、同义改写、错别字
- 多跳问题:答案分散在多个 chunk,单次检索拿不全,需要单独建一类
- 只看平均值:会被简单样本拉高,要按 tag 分组看(政策类、多轮类、多跳类)
- 成本:全量评测很贵,日常抽样跑,回归再全量
写在最后
RAG 评测的核心不是算出一个分数,而是让每一个错误都能被定位。分数只用来做基线对比和回归卡点,定位才是日常。