Yuan的博客
EN

给 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 个:

  1. Faithfulness —— 答案的每一项主张都要由上下文支持(幻觉检测器)
  2. Answer Correctness —— "事实分解 + 语义相似度"的复合指标
  3. Context Recall —— ground truth 有多少进入了上下文(衡量检索缺口)
  4. Context Precision —— 相关文档是否排在前面(信噪比)
  5. Context Entity Recall —— truth 里的关键实体有没有出现在上下文中
  6. 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@5MRR
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 漂移得到不同分的问题。


一些收获

  1. 先打通端到端。在搞组件级指标之前,先把"最终答案对不对"夯实了。否则你会调 Faithfulness 调好几天,端到端却纹丝不动。

  2. 按层解耦。检索和生成用不同的指标。出问题时,你能立刻知道是哪一层的锅。

  3. 少而锐。RAGAS 8 个主要指标 → 我留下 2 个(Faithfulness + Context Recall)。砍掉的要么没信号(Answer Relevancy、Noise Sensitivity),要么在"短 truth、长 answer"场景下失灵(Answer Similarity、Answer Correctness),要么冗余(Context Entity Recall、Context Precision)。

  4. 成本意识。日常迭代用免费指标(Hit@K / MRR),只在关键 gating 点上烧 LLM judge。

最大的领悟:指标不是看覆盖面,而是看能不能在每个阶段给你一个决策。把后期指标用在前期(用 LLM judge 挑 embedding 模型)→ 慢又贵。把前期指标用在后期(用 Hit@K 给 agent 打分)→ 看不到真正的问题。