给 RAG Agent 挑评估指标 —— 来自一线的笔记
我最近在 FinanceBench 上搭了一个 RAG Agent(150 个问答,对应 10-K / 10-Q 报告)。挑评估指标这件事比我预想的要棘手得多。我一开始以为评估就是"最后跑一个 LLM judge"。被坑了几次之后才意识到:每个阶段都需要自己的指标,选错了反而会把你优化到错误的方向上去。
下面是事情发展的过程,大致按我做决定的顺序来。
阶段 1:在写任何代码之前,先决定要测什么
在动代码之前,我把 RAG 评估拆成三层:
- 检索层(Retrieval layer) —— Hit Rate、MRR、Precision@K、Recall@K、NDCG。不调用 LLM,免费,几秒就跑完。每次你改 chunk size 或者换 embedding 模型时都应该跑一遍。
- 上下文层(Context layer) —— Context Precision、Context Recall。召回的 chunk 到底有没有用?需要 LLM,比较贵。
- 生成层(Generation layer) —— Faithfulness、Answer Relevancy、Answer Correctness。答案本身好不好?Faithfulness 最重要,它直接衡量幻觉。
这样拆的意义是成本 vs 频率的权衡:
- 日常调优 → 免费的检索指标
- 发版门禁 → RAGAS 核心几项
- 终版报告 → 跑完整套
不这样拆,每次小改动都跑全套,几天就把 API 预算烧光。
阶段 2:从 RAGAS 8 个主要指标里挑了 6 个
深入研究 RAGAS 后,我从它 8 个主要指标里挑了 6 个:
- Faithfulness —— 答案的每一项主张都要由上下文支持(幻觉检测器)
- Answer Correctness —— "事实分解 + 语义相似度"的复合指标
- Context Recall —— ground truth 有多少进入了上下文(衡量检索缺口)
- Context Precision —— 相关文档是否排在前面(信噪比)
- Context Entity Recall —— truth 里的关键实体有没有出现在上下文中
- Answer Similarity —— 纯 embedding 余弦相似度,0 次 LLM 调用
一上来就被我砍掉的两个:
- Answer Relevancy —— 它会从答案反向生成 N 个假设性问题,再用余弦相似度跟原问题比。我的问题本身就很窄("3M FY2018 的 CapEx 是多少?")—— 即便答案错了,相关关键词也还在,反向生成的问题依然跟原问题很接近。几乎每一行打分都贴近 1.0,毫无区分度。
- Noise Sensitivity —— 测试嘈杂上下文是否会让模型困惑。但我把检索阈值从默认降到 0.45(更多 chunk = 更多噪声)之后,准确率反而从 50% 提到了 80%。噪声并不是我的瓶颈,跑这个指标只是白白烧 LLM 调用。
我以为这 6 个能撑住。结果并没有。
阶段 3:真正跑过一遍之后 —— 把 6 个砍到 2 个
每个 RAGAS 指标每条数据要 1-5 次 LLM 调用,跑完 150 条耗时间也耗预算。但更大的问题是:好几个指标在我数据上根本不可信。一个一个说:
砍掉 Answer Similarity。FinanceBench 的 truth 很短,比如 "$1577.00",但我的 agent 输出是长篇带计算步骤的 markdown。embedding 相似度在这种"短 truth、长 answer"的场景下会系统性低估 —— 分数主要反映长度差异,而不是正确性。一个会误导你的指标比没有指标更糟。
砍掉 Context Entity Recall。两个原因:(1)NER 对金融表格里的数字和缩写处理得很差,子串匹配在格式差异上会出大量假阴性("$1,577M" vs "1577 million");(2)跟 Context Recall 重叠太多 —— 砍掉损失很小。
砍掉 Answer Correctness。RAGAS 默认是 0.75 × 事实分解 + 0.25 × embedding 相似度 —— 那 25% 的 embedding 部分跟 Answer Similarity 同病。我试过把权重改成 [1.0, 0.0],又加了校准 prompt 让它别惩罚啰嗦的回答。折腾了一通之后它对数值题打分还是很混乱($302.6M vs $303M,差 0.13%,被打 0 分)。这条路我放弃了,转去自己写领域专用的 judge(见下一阶段)。
砍掉 Context Precision。在 multi-page-gold 场景下跟 Context Recall 重叠,并且检索层的 Page Hit@K 已经覆盖了信噪比。在这里再算一遍就是冗余。
最后只活下来 2 个:
- Faithfulness —— 抓幻觉。没有别的指标能替代它:它专门盯着"上下文里没有的主张"。在金融场景尤其重要 —— Agent 编一个数字比承认不知道还要糟。
- Context Recall —— 抓检索缺口。跟检索层的 Hit@K 互补:Hit@K 告诉你"页面在不在里面";Context Recall 告诉你"那页的关键信息有没有真的进到 chunk 里"(chunk 切分有可能把关键信息切掉)。
再加上下一阶段的自建 LLM judge + 数值预过滤器,四件东西支撑起整套评估。
不要为了"完整性"去跑指标。坏的指标会带偏你;重复的指标只是更贵。每个指标都应该回答"砍掉它我损失什么信号?"如果答不上来,就砍掉。
阶段 4:升级检索层评估
前三个阶段我都在 RAGAS 那边折腾。检索层的指标一开始很土 —— 就是"答案字符串有没有出现在召回上下文里某处"。简单的子串匹配。
升级到了 IR 标准三件套:
- Page Hit@K —— top-K 是否至少包含一个正确页面
- Page Recall@K —— 相关页面被召回的比例
- MRR —— gold page 的平均倒数排名
为什么这三个都要? 因为它们回答的是不同的问题。Hit@K → "找没找到";Recall@K → "对于多页 gold(占我数据的 23%),全部都拿到了吗";MRR → "找到之后排在什么位置"。
具体例子。我分三轮迭代了检索栈,每轮加一个组件:
| 阶段 | Hit@5 | MRR |
|---|---|---|
| Baseline(voyage embedding + BM25 hybrid) | 84.4% | 0.685 |
| + 公司过滤(识别查询中的公司,加 payload filter) | 97.8% | 0.767 |
| + voyage rerank-2(cross-encoder 二次排序) | 100% | 0.863 |
只看 MRR,你会觉得 filter 没起什么作用(0.685 → 0.767 看着像噪声)。只看 Hit@5,你会觉得 rerank 没起什么作用(97.8% → 100% 看着像噪声)。两个一起才能看到全貌:filter 解决了"候选池被其他公司污染";rerank 解决了"找到了但是排错位置" —— 两个不同的问题,各自由不同的指标暴露出来。
最终 test-105 的成绩:Hit@5 = 98.1%,MRR = 0.821。有这两个数字,我可以放心地把端到端的失败甩锅给生成侧,不用再回头折腾检索了。
阶段 5:自建端到端 judge
跑了几轮端到端之后,很明显 RAGAS 的 Answer Correctness 在金融数值题上不可靠:
"$302.6M"vs truth"$303M"(差 0.13%)→ 打 0 分"Yes, 1.45x"vs truth"Yes, 1.5x"(方向对,3.3% 偏差)→ 打分不稳- 带计算步骤的长 markdown → 偶尔因为啰嗦被扣分
根因:通用 judge 用一套打分规则套所有题;但在金融场景下,"直接抽取"和"派生计算"需要非常不同的容忍度,文本题和数值题也需要不同的规则。
于是我写了一个一次性调用 GPT-4.1 的 judge,先把 truth 分成 6 类,再按类别施加不同的容忍度:
| Truth 类别 | 容忍度 |
|---|---|
| pure_number(直接抽取) | 只允许格式变化(逗号、$、单位换算)—— 不允许四舍五入 |
| number_with_context(派生计算) | ±2% → 1.0;±2-10% 且方向一致 → 0.5;超过 ±10% → 0.0 |
| pure_text / yesno_text | ≥80% 覆盖 → 1.0;50-80% → 0.5;<50% → 0.0 |
| yesno_*(任何是非题) | 硬规则:方向反了 → 0.0 |
关键设计是先分类,再按类别施加容忍度 —— 比单一全局阈值准确得多。领域专用的数据集就该配领域专用的 judge。
最终成绩:dev 45 correctness = 0.922,test 105 = 0.871。可以发布。
一个加分小技巧:数值题先走确定性预过滤器(±2% 内直接打 1.0),miss 之后才落到 LLM judge。省钱也提升一致性 —— 同一道题每次得分一样,没有跑两次因为 temperature 漂移得到不同分的问题。
一些收获
先打通端到端。在搞组件级指标之前,先把"最终答案对不对"夯实了。否则你会调 Faithfulness 调好几天,端到端却纹丝不动。
按层解耦。检索和生成用不同的指标。出问题时,你能立刻知道是哪一层的锅。
少而锐。RAGAS 8 个主要指标 → 我留下 2 个(Faithfulness + Context Recall)。砍掉的要么没信号(Answer Relevancy、Noise Sensitivity),要么在"短 truth、长 answer"场景下失灵(Answer Similarity、Answer Correctness),要么冗余(Context Entity Recall、Context Precision)。
成本意识。日常迭代用免费指标(Hit@K / MRR),只在关键 gating 点上烧 LLM judge。
最大的领悟:指标不是看覆盖面,而是看能不能在每个阶段给你一个决策。把后期指标用在前期(用 LLM judge 挑 embedding 模型)→ 慢又贵。把前期指标用在后期(用 Hit@K 给 agent 打分)→ 看不到真正的问题。