William 的技术笔记

Agent 评测:怎么知道它真的变好了

2026-10-29 · #Agent#LLM#评测

这个系列里,评测已经出现过好几次。在 搭建 Agent 的基础知识 里,我列了几条基本做法:尽早建评估集、能用代码判断就用代码、认真读轨迹;工具 那篇讲了怎么从调用记录里看出工具设计的问题;记忆 那篇讲了怎么写跨会话的测试。

但这些都是某个具体问题下的评测。这篇想把评测本身当成主角:一套 Agent 评测到底由什么组成,怎么搭起来,怎么让它的结论可信。

之所以值得单独写一篇,是因为我越来越觉得,评测是 Agent 开发里杠杆最大、也最常被拖延的那部分。没有评测的时候,每次改动都只能靠“我试了几个例子,感觉好一点”来判断,而 Agent 的随机性足够大,让这种感觉经常是错的。更麻烦的是,模型每隔几个月就换一代,没有评测的团队每次都要花几周手动摸索新模型行不行;有评测的团队跑一遍就知道了。

为什么 Agent 评测比模型评测难

传统的模型评测很简单:给一个输入,拿到一个输出,和标准答案比一下。Agent 不一样,至少有四个地方变复杂了:

  • 多步。一个任务可能要几十轮调用,中间任何一步走偏,后面都会跟着偏。最终结果错了,你还得知道是哪一步错的;
  • 有环境。Agent 会改文件、写数据库、发请求。要判断它做没做对,常常不是看它说了什么,而是看环境最后变成了什么样;
  • 路径不唯一。修一个 bug 可以有十种写法,查一个问题可以走五条路。如果评分时死盯着“应该先调 A 再调 B”,就会把很多聪明的解法判成错;
  • 非确定性。同一个任务跑两次,可能一次成功一次失败。一次的结果说明不了什么。

这四点决定了 Agent 评测不能只是一个“输入、输出、比对”的表格,而是一套带环境、带多次试验、带多种评分方式的系统。

一次评测由什么组成

先把术语对齐。我习惯这样拆:

任务指令 + 初始状态 一次试验(trial),重复 k 次 Agent模型 + Harness 环境沙箱 · 模拟用户 轨迹每步调用与结果 最终状态文件 · 数据库 代码检查测试 · 状态比对 模型评审按评分标准打分 人工抽查 · 校准评分器 分数 + 成功率 · 轮数 · token · 耗时
图 1:一次评测的组成。评分器既可以看最终状态,也可以看轨迹。
  • 任务:一条指令加上它的初始状态,比如“修复这个 issue”加上某个提交时的代码仓库;
  • 试验(trial):让 Agent 在一个干净的环境里完整地做一遍这个任务。因为有随机性,同一个任务通常要跑多次;
  • 轨迹(transcript / trajectory):这次试验里的全部记录,包括模型的输出、每次工具调用和返回;
  • 最终状态:试验结束时环境的样子。Agent 说“我已经订好了机票”不算数,数据库里真的有这条订单才算;
  • 评分器(grader):看轨迹或最终状态,给出分数的那段逻辑。一个任务可以有多个评分器;
  • 评测集(suite):一组为了衡量某种能力而放在一起的任务。

把这些拆清楚的好处是,出问题时你知道该看哪一层:是任务写得有歧义,环境没清干净,评分器判错了,还是 Agent 真的不行。

任务从哪里来

从真实失败开始

最好的任务来源,是已经发生过的失败。用户报过的 bug、你在开发中手动测出来的问题、线上日志里被用户纠正的对话,每一条都可以变成一个任务。它们天然代表了真实分布,而且你已经知道“对”长什么样。

很多团队拖着不做评测,是因为觉得要先攒几百条任务才有意义。其实不用。二三十条从真实失败里来的任务,就足够在早期发现大部分问题。早期每次改动的效果往往很明显,小样本就看得出来;等 Agent 成熟了、改动的效果变小了,再扩充评测集。

还有一个现实的理由:越晚开始,越难开始。一开始,“该做什么”写在产品需求里,可以直接翻译成任务;等产品上线一段时间,“该做什么”就散落在各种线上问题和口头约定里了,要从中反推出评测标准会困难得多。

写一个好任务

一个好任务的标准是:两个懂行的人各自看一遍,会对“做没做对”给出同样的判断。做不到这一点的任务,评分器也判不准,它的失败率里混着大量噪声。

几个具体的检查点:

  • 指令里给出了完成任务需要的全部信息。如果评分器检查的是某个文件路径,指令里就要说清楚是哪个路径,否则 Agent 失败只是因为没猜中;
  • 评分器检查的就是指令要求的。指令说“修复 bug”,评分器却还检查了代码风格,这是在考一道没出的题;
  • 每个任务都有一个参考解。自己或让人实际做一遍,确认这个任务在当前环境里能做出来、能通过所有评分器。如果一个任务在很多次试验里通过率都是 0,先怀疑任务本身,而不是 Agent;
  • 正反两面都要有。只测“该做的时候做了”,Agent 就会被优化成什么都做。比如测搜索时,既要有该搜的问题,也要有不该搜、直接回答就好的问题;测权限时,既要有该执行的操作,也要有该拒绝或该先确认的操作。

别忘了“不该做的事”

最后一点值得单独说。Agent 的失败有两种:没做成该做的事,和做了不该做的事。前者容易测,后者容易漏。

后者包括:删了不该删的文件、改了任务范围之外的代码、在没确认的情况下发了消息、为了让测试通过直接改测试。这些在只看“任务完成没有”的评测里,可能都算成功。所以除了检查目标状态,还要检查不该变的东西没变。

评分器:代码、模型和人

评分器有三类,各有长处:

类型 例子 优点 缺点
代码 跑测试、比对数据库状态、检查输出格式、正则匹配 快、便宜、可复现、好调试 只能判断能写成规则的东西,容易判错合理的变体
模型 按评分标准给回答打分、两两比较 能处理开放式输出和细微差别 有随机性、有偏差,需要和人对齐
人 专家打分、抽查轨迹 标准最可靠 慢、贵、难规模化

原则还是那句话:能用代码判断的就用代码。代码评分器的结论最稳定,出了问题也最好查。模型评分器用在代码判断不了的地方,比如回答的语气是否合适、调研报告有没有抓住重点。人主要用来校准前两者。

一个任务往往会组合多个评分器。以一个编程任务为例,写成配置大概是这样:

id: fix-empty-password-login
prompt: |
  用户反馈:密码为空时也能登录成功。请修复这个问题,并补上测试。
environment:
  image: repo-snapshot:2026-10-01   # 固定的初始状态
  reset: per_trial                  # 每次试验都从头开始
graders:
  - type: tests                     # 隐藏测试,Agent 看不到
    run: pytest tests/hidden/test_auth.py
    weight: 0.6
  - type: state_check               # 不该动的东西没动
    forbid_changes: ["migrations/**", "tests/hidden/**"]
    weight: 0.2
  - type: llm_rubric                # 代码质量交给模型评审
    rubric: rubrics/code_quality.md
    weight: 0.2
trials: 5
metrics: [turns, tokens, latency]

注意隐藏测试和 forbid_changes:前者防止 Agent 只对着可见测试写代码,后者防止它通过修改测试来“通过”测试。

评结果,而不是评路径

一个很自然的冲动,是去检查 Agent 是不是按“正确的步骤”做的:先读了配置文件,再调了查询工具,最后才改代码。这种评分器写起来很直观,但通常是个坏主意。

原因是 Agent 经常会找到你没想到的路径,而且其中不少是更好的。一个只认固定步骤的评分器,会惩罚这种创造性,也会让评测变得非常脆弱:换一个模型,步骤顺序稍有不同,分数就掉了,但实际上任务完成得一样好。

所以大多数情况下,评分应该看结果:最终状态对不对,回答对不对。路径相关的东西,比如用了多少轮、多少 token、有没有调用危险工具,作为指标记录下来,而不是作为通过与否的条件。例外是那些路径本身就是要求的场景,比如“退款前必须先核实身份”,这时检查路径是对的,因为跳过核实本身就是错误。

另一个相关的做法是给部分分。一个任务包含三个子目标,Agent 做对了两个,和一个都没做对,是很不一样的。全有全无的打分会把这种差异抹掉,让你看不出改动是不是在往对的方向走。

用好模型评审

模型评审最常见的问题是:评分标准太笼统。“请给这个回答的质量打 1 到 10 分”,得到的分数几乎没有信息量,同一个回答跑两次可能差两三分。

几条让模型评审更可靠的经验:

  • 一个评审只看一个维度。“准确性”、“是否切题”、“语气”分开评,比一起打一个总分稳定得多;
  • 用二元或少档位。“是否引用了至少一个来源:是 / 否”,比“引用质量 1 到 10 分”可靠;
  • 给出判断标准和例子。写清楚什么样算通过、什么样算不通过,最好各附一个例子;
  • 允许它说“无法判断”。不给这个出口,它就会编一个分数出来;
  • 先说理由,再给结论。让评审先写分析再给分,比直接给分更稳定,也方便你回头检查它为什么这么判;
  • 定期和人对齐。拿一批人工打过分的样本跑一遍评审模型,看一致率。一致率低,就改评分标准,而不是直接相信它。

一个评分标准大概长这样:

你在评估一个客服 Agent 的回复。只判断一件事:回复中的退款政策是否与下面的政策文档一致。

政策文档:
{policy}

Agent 的回复:
{response}

先逐条列出回复中提到的政策要点,并指出每一条在文档中的依据。
然后给出结论,只能是以下之一:
- CONSISTENT:所有要点都能在文档中找到依据
- INCONSISTENT:至少有一条与文档矛盾或文档中没有
- NOT_APPLICABLE:回复中没有涉及退款政策

非确定性:跑一次不算数

pass@k 和 pass^k

同一个任务跑 k 次,有两种常用的汇总方式,它们回答的是完全不同的问题:

  • pass@k:k 次里至少一次成功的概率。它回答的是“给它几次机会,能不能做出来”;
  • pass^k:k 次全部成功的概率。它回答的是“它是不是每次都能做对”。

如果单次成功率是 p,并且各次独立,那么 pass@k = 1 − (1 − p)^k,pass^k = p^k。随着 k 增大,两者会朝相反的方向走:

单次成功率 p = 0.7 100%75%50%25%0 12345678 k(试验次数) pass@k→ 99.99% pass^k→ 5.8%
图 2:同一个 70% 成功率的 Agent,给 8 次机会几乎一定能做出来,但连续 8 次都做对的概率只有 6%。

该看哪一个,取决于产品形态:

  • 如果用户可以多次尝试并自己验证,比如编程助手生成几个方案让人挑,或者有测试能自动验证再重试,pass@k 更贴近真实体验;
  • 如果用户期望每次都对,比如客服 Agent 处理退款,一次出错就是一次事故,那就要看 pass^k。

pass^k 这个指标是 τ-bench 提出来的,它的发现很能说明问题:一些单次成功率看起来不错的 Agent,pass^8 会掉到很低。对面向用户的 Agent 来说,“平均做得不错”和“稳定地做得不错”是两回事,后者往往才是能不能上线的门槛。

样本太少,差异就是噪声

另一个常被忽略的问题是统计显著性。假设你的评测集有 50 个任务,旧版本通过率 60%,新版本 65%,这是变好了吗?

粗略算一下:通过率的标准误差大约是 √(p(1−p)/n),p = 0.6、n = 50 时约等于 7 个百分点,95% 置信区间大概是正负 14 个百分点。也就是说,5 个百分点的差异完全在噪声范围内。

几个缓解办法:

  • 每个任务跑多次,用平均成功率代替单次的成败,能降低任务内部的随机性;
  • 做配对比较。新旧版本跑同一批任务,逐个任务看谁赢谁输,比只比较两个总分敏感得多;
  • 看差异落在哪里。如果新版本多通过的任务都集中在你改动针对的那一类,这比总分的变化更有说服力;
  • 别为了一两个点的变化做决策。小改动的效果,往往要靠更大的评测集或线上数据才能确认。

环境:让每次试验都干净

Agent 评测和模型评测的另一个大区别是环境。环境出问题,评测结果就不可信,而环境的问题往往很隐蔽。

试验之间要隔离。每次试验都应该从同一个干净的初始状态开始。如果上一次试验留下的文件、缓存、数据库记录被下一次看到了,结果就会互相污染:可能是失败被传染,也可能是 Agent 直接捡到了上一次的答案。这和 沙箱 那篇说的“沙箱应该是一次性的”是同一个道理。

小心答案泄露。比如用某个历史提交作为编程任务的初始状态,如果没有把后续的 Git 历史清掉,Agent 完全可以 git log 一下,直接找到当时的修复提交。Agent 在这种事情上非常有创造力,它不是在“作弊”,它只是在用手边一切可用的信息完成任务。

基础设施本身也会产生噪声。超时设置、并发限制、外部 API 的限流、容器的 CPU 和内存配额,都会影响通过率。同一个 Agent,在资源宽松的机器上和资源紧张的机器上跑,分数可能差好几个点。比较两个版本时,要确保它们是在同样的环境配置下跑的;看公开基准的排行榜时,也要知道不同提交之间的环境未必一致。

外部依赖尽量固定。依赖真实的网页、真实的 API,结果会随时间漂移,今天能过的任务下个月可能因为网页改版而过不了。能用快照、录制的响应或本地模拟服务替代的,就替代掉;不能替代的,至少记录下来,在分析失败时把它们单独拎出来。

需要用户参与的任务,用模拟用户。客服、助理这类多轮对话的 Agent,任务的推进依赖用户的回应。常见做法是让另一个模型扮演用户,给它一份设定(它想要什么、知道什么、不知道什么),让它和 Agent 对话。τ-bench 就是这么做的。模拟用户的质量直接决定评测的质量,它也需要像评分器一样被检查:是不是太配合了,是不是泄露了本该由 Agent 去问出来的信息。

能力评测与回归评测

评测集通常有两种用途,它们的期望通过率完全不同,最好分开管理。

能力评测回答的是“它能做到什么”。这类评测集应该从低通过率开始,里面是 Agent 现在还做不好的任务,给团队一个可以往上爬的坡。如果一个能力评测集一开始就是 90%,它能告诉你的东西就很少了。

回归评测回答的是“它还能不能做到以前能做到的”。这类评测集的通过率应该接近 100%,每次改动都跑,掉了就要查。它的作用是守住底线,防止修一个问题的同时弄坏另一个。

两者之间有一条自然的流动路径:

真实失败新需求 · 用户反馈 能力评测通过率低用来爬坡 毕业 回归评测通过率接近 100%每次改动都跑 上线 线上暴露的新失败,回到起点 衡量进步守住底线
图 3:任务从能力评测"毕业"进入回归评测,线上的新失败又补充新的能力评测。

当一个能力评测集的通过率爬到很高、并且稳定之后,它就饱和了:分数已经没有上升空间,再好的改动也看不出差别。这时可以把其中的任务移进回归集,再从新的失败里补充更难的任务。一个长期不更新的评测集,最终只会告诉你“一切都好”。

去读轨迹

前面讲了很多指标和统计,但我想把最重要的一条放在这里:定期去读轨迹。

分数告诉你哪里有问题,轨迹告诉你为什么。而且,你经常会在轨迹里发现评测本身的问题,而不是 Agent 的问题:

  • Agent 其实做对了,但评分器只认一种写法,把它判错了;
  • 任务描述有歧义,Agent 按另一种合理的理解做了;
  • 环境里缺了某个依赖,Agent 花了二十轮在跟环境搏斗;
  • Agent 通过了,但用的是你没想到的捷径,比如改了测试、读了不该读的文件。

最后一种尤其要警惕。当分数突然大幅上升时,先别高兴,去读几条通过的轨迹,确认它是真的变强了,而不是找到了评测的漏洞。

读轨迹不需要全读。我的习惯是每次改动之后,抽几条新失败的、几条新通过的,再随机抽几条,各看一遍。重点看这几件事:

看什么 可能说明
在哪一步开始偏离 提示词或工具描述在那里有歧义
有没有反复做同一件事 工具返回不够清楚,或者缺少停止条件
有没有忽略已经拿到的信息 上下文太长,或者关键信息被埋了
失败时是怎么收尾的 是坦白说做不到,还是假装完成了
通过时用了什么方法 是真的解决了,还是钻了评测的空子

工具层面的症状(选错工具、参数填错、冗余调用)在 工具 那篇有一张更细的对照表,这里就不重复了。

读轨迹还有一个附带的好处:它会逼着你对“好”形成具体的判断。看了几十条轨迹之后,你会知道这个 Agent 的典型失败模式是什么,这比任何汇总指标都更能指导下一步的改进方向。

不同类型的 Agent,评测侧重点不同

上面讲的是通用的框架,落到具体类型的 Agent 上,评分方式和关注点会有不少差别:

类型 主要评分方式 要特别注意的 可参考的公开基准
编程 跑测试、静态检查 隐藏测试防过拟合;检查有没有改测试、改了范围外的代码 SWE-bench Verified、Terminal-Bench
对话 / 客服 检查最终状态,模型评审语气和政策一致性 需要模拟用户;关注 pass^k;路径约束(如先核实身份) τ-bench
调研 模型评审,结合事实核查 结论有没有来源支撑;来源本身是否可靠;覆盖面 GAIA、BrowseComp
操作电脑 / 浏览器 检查应用或系统的最终状态 环境快照和重置成本高;网页改版导致漂移 OSWorld、WebArena

公开基准适合用来选模型、了解大致水平,但不能代替你自己的评测集。原因很简单:它们测的不是你的任务、你的工具、你的用户。一个在公开基准上领先几个点的模型,在你的场景里未必更好。另外,越是热门的基准,越要考虑数据污染和“针对基准优化”的可能。

如果你的系统里用到了多个 Agent 或子 Agent,评测的原则是一样的:先评最终结果,再在轨迹里看是哪一个环节出了问题。具体怎么拆分任务、怎么设计 Agent 之间的交接,是另一个话题了。

离线评测之外

离线评测再完善,也只是用一组固定的任务去近似真实世界。上线之后,还需要其他几层来补位:

  • 线上监控:成功率、轮数、token、耗时、报错率的变化趋势,以及用户中途放弃的比例;
  • 用户反馈:显式的点赞点踩,以及隐式的信号,比如用户是否重新提问、是否手动撤销了 Agent 的改动;
  • A/B 测试:重大改动在小流量上对比,用真实结果说话;
  • 抽样人工审查:定期从线上随机抽一批轨迹,像读离线轨迹一样读一遍。

这几层各有盲区:离线评测覆盖不到没想到的场景,线上监控只能看到结果、看不到原因,用户反馈稀疏而且有偏,A/B 测试慢。把它们叠在一起用,才能比较放心。而最重要的是让它们形成闭环:线上发现的每一个新失败,都应该变成离线评测集里的一个新任务。

动手时的几条建议

  1. 现在就开始,从二三十个真实失败开始,不用等攒够几百条;
  2. 每个任务都要有参考解,先确认任务能做、评分器判得对;
  3. 正反两面都测,“该做的做了”和“不该做的没做”都要有;
  4. 评结果,不评路径,路径相关的东西记成指标,必要时给部分分;
  5. 能用代码判断就用代码,模型评审一次只看一个维度,并定期和人对齐;
  6. 每个任务跑多次,按产品形态选 pass@k 或 pass^k,别为了噪声范围内的差异做决策;
  7. 每次试验都从干净的环境开始,小心答案泄露和基础设施噪声;
  8. 区分能力评测和回归评测,饱和的评测集要及时更新;
  9. 定期读轨迹,分数突然大涨时尤其要读。

小结

  1. Agent 评测比模型评测难:多步、有环境、路径不唯一、结果不确定,所以它是一套系统,而不是一张表;
  2. 把一次评测拆成任务、试验、轨迹、最终状态和评分器,出了问题才知道该查哪一层;
  3. 最好的任务来自真实失败,好任务的标准是两个懂行的人会给出同样的判断,并且要有参考解;
  4. 评分优先用代码,模型评审要有明确、单一维度的标准,评结果而不是评路径;
  5. 一次不算数:pass@k 衡量能不能做出来,pass^k 衡量是不是每次都能做对,小样本上的小差异多半是噪声;
  6. 环境要隔离、要固定,环境的问题会伪装成 Agent 的问题;
  7. 能力评测用来爬坡,回归评测用来守底线,任务在两者之间流动,评测集需要持续更新;
  8. 读轨迹是最重要的一步,它既能发现 Agent 的问题,也能发现评测本身的问题。

写到这里,这个系列从循环、上下文、工具、记忆一路讲下来,每一篇最后都绕回到同一个问题:你怎么知道这样改是对的?评测就是这个问题的答案。它不会让 Agent 自己变好,但它让你每一次改动都有据可依,而不是在凭感觉。