Hugging Face 发布 agent-eval:在自有工具上评测开源模型的智能体能力
Is it agentic enough? Benchmarking open models on your own tooling
Hugging Face 发布 agent-eval 评测工具,用 transformers 作为案例,衡量智能体完成任务的过程成本而非只看最终答案。该工具在 bare、clone、skill 三种层级下运行模型×版本×任务的组合,每个组合作为独立 Hugging Face Job 在相同硬件上并行执行,并记录 match %、时间、token 和错误率等指标。
跨不同指标对 transformers 修订版本进行基准测试
这是一篇人工撰写的、面向 agent 的博客文章。
编码 agent 越来越多地代替我们与软件打交道:描述一个任务,agent 就会挑选库、编写调用代码、运行它们,并自行调试错误。当库成为阻碍时,它会毫不犹豫地绕过库,从头重写逻辑。这为库开发引入了一个新概念:代码不仅要正确、快速,还要设计得能让 agent 高效地驱动它。一个笨拙的 API 或过时的文档只会让我们开发者恼火,但现在它还会把 agent 引向一条更长、更昂贵的路径。
大多数基准测试只看最终答案。我们想要的却是整个过程:不只是 agent 是否做对了,还有它为此付出了多少工作量,以及这在不同模型、库修订版本和任务之间如何变化。我们正是这样测量的,并以 transformers 作为我们的案例研究。
在这里,我们将介绍一个专门针对“答案是如何找到的”这一问题的工具基准测试,并提供一个此类测试框架的简单实现,它完全运行在由 pi 编码 agent 驱动的开源模型上,将模型 × 修订版本 × 任务的完整扫描分散到 Hugging Face Jobs 上运行,从而让每次运行都使用完全相同的硬件。
但是,你该如何为 agent 优化软件呢?
我们坚信以下两条软件原则:
- 如果没有经过测试,那它就不能工作
- 如果没有文档,那它就不存在
在面向 agent 优化的工具领域中,这一点依然成立,而且这一次,这两条原则直接相互关联。
你希望你的工具能为 agent 所用:它必须是可被发现的。API 需要清晰,文档需要详尽。它们的结构需要让 agent 能够快速访问有用的文件和示例。如果你希望你的工具能为 agent 工作,那么你就应该针对 agent 使用场景对它进行测试。
针对 agent 使用场景测试软件
在整篇博客文章中,我们将以 transformers 为例:agent 使用它来解决 ML 任务(文本分类、图像描述、音频转录),而不是为它贡献代码;不过该测试框架的设计初衷是适用于任何可从命令行操作的工具。
我们对 transformers 的直觉是,只需做几处改动就能大幅简化其使用方式:一个 CLI、一个 Skill,以及自包含的、针对具体任务的示例。这与最近应用于 hf CLI 的配方相同,该 CLI 经过重新设计以面向 agent 优化,其中 agent 使用的 token 减少了 1.3–1.8 倍(最多达 6 倍)。我们想知道这种收益是否具有普遍性,以及它是否对 transformers 也有用。
直觉是有力的工具,但在我们向像 transformers 这样被广泛使用的代码库提交增加数千行代码的 PR 之前,我们想要更多证据。我们着手衡量成功应该是什么样子。
并非所有成功都是平等的
两个 agent 都能为一个情感分类任务生成正确的标签,但其中一个:
- 编写了一个 40 行的 Python 脚本,导入
transformers,调试了一个形状错误,重新运行了两次,最后打印出答案;
而另一个
- 输入
transformers classify --model ... --text "...",一次调用就完成了。
两者都达到了 POSITIVE (0.9999),以下是 agent 在这个确切任务上实际走过的两条路径:
# Task: classify the sentiment of "I absolutely loved the movie, it was fantastic!"
- # one agent: pipe a script into python and parse the output
- python - <<'PY'
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
- import torch
- import torch.nn.functional as F
-
- model = AutoModelForSequenceClassification.from_pretrained("distilbert/distilbert-base-uncased-finetuned-sst-2-english")
- tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased-finetuned-sst-2-english")
- inputs = tokenizer("I absolutely loved the movie, it was fantastic!", return_tensors="pt")
- with torch.no_grad():
- logits = model(**inputs).logits
- probs = F.softmax(logits, dim=1)
- idx = torch.argmax(probs, dim=1).item()
- print(model.config.id2label[idx], probs[0][idx].item())
- PY
+ # the other agent: one command
+ transformers classify \
+ --model distilbert/distilbert-base-uncased-finetuned-sst-2-english \
+ --text "I absolutely loved the movie, it was fantastic!"
两种方法都达到了相同的结果。但它们在成本、延迟、token 使用量和失败次数方面有着截然不同的表现。
如果你的评估只检查最终字符串,那么你既看不到这些,也看不到你对库所做的改动(CLI 改进、更好的错误消息、某个 Skill)是否真的帮到了 agent。
我们构建这套测试框架的目标,是评估 agent 完成给定任务需要做多少工作,以及库的改动是否提升了性能。
我们如何运行评估?
先简单说说我们在这里将如何评估 agent。
我们在三种变体(或称“层级”)下运行每个任务;这是 agent 接触 transformers 的三种不同方式:
bare pip install transformers, and nothing else
clone the full transformers source, checked out in the working directory
skill a packaged Skill: the CLI's docs + task examples, loaded in context
它们并非嵌套关系:skill 不包含 clone(它提供的是精选文档,而非源码树),两者也不严格互相包含,各自给 agent 提供不同类型的帮助。正如我们将看到的,模型有时在 clone 上的表现会优于 skill。
还有几个选择:
- 目前我们只关注能提供精确匹配的确定性任务,因为它们为实验提供了非常好的基础。模型作为评判者及其他方案,显然是其他任务接下来的方向。
- 每次运行都是独立的 Hugging Face Job:每个(模型 × 修订版本 × 任务)一个,因此整个扫描在相同硬件上并行运行,从而在大规模下保持比较的公平性。
- 结果和轨迹存放在 Hugging Face Bucket 中:速度快、无需版本管理,并且能处理极高的写入并发。
以哪些模型作为基准?
驱动 agent 的模型并非生而平等,它们的差异会改变你在运行它们时应该关注什么。
大型开放模型
一端是最大、能力最强的开放模型。在相当常见的任务上,它们最终应该能给出正确答案。对它们而言,任务完成率会饱和在接近 100%,不再能说明你的工具如何;更有意义的基准是 agent 达到目标所付出的努力:用了多少轮、多少 token 和多少秒,以及它们走的是干净路径还是用了已弃用的 API。
本地模型
本地模型在规模上差异很大,能力也参差不齐。像 “match %” 这样的指标比在大型模型上更有意义,因为你可以看到模型规模/能力如何影响你的特定工具上的结果。
这套测试框架不仅为库维护者提供如何改进仓库以利于 agent 交互的指导,还有助于评估不同 agent 和模型在用户关心的任务上表现如何。
该框架从多个维度为每次运行打分,这样你就可以针对每一类模型追问真正重要的是什么:
- match %:最终答案是否包含预期结果(按任务定义,不区分大小写的子串 / 正则 / 精确匹配,均在报告中明确说明);
- 中位时间和中位 token 数(新增 vs. 缓存 vs. 生成);
- 出错运行占比 %:包括一个守卫,用于标记什么都没产生的运行(0 个输出 token、无工具调用、无答案),这样静默失败就不会伪装成“0”;
- 标记采用率:工具定义的行为标记;其含义见下文说明。
所有这些都会汇总到一份你可以直接查看的报告中:
实时报告:Overview、Coverage 和 Results,全部在客户端完成。
而且由于它捕获了每次运行的原生 agent 轨迹,数字只是开始:你可以逐条命令地准确读到 agent 做了什么。这些轨迹可通过 Hub 的 agent-traces 查看器分享:

在 Hub 的 agent-traces 查看器中渲染的一次运行:MiniMax-M2.7 在 answer-question 任务上。
在 Hub 上打开此 trace ↗
在展示结果之前,先快速回顾一下设置。每次运行会改变四件事:驱动 agent 的模型、它所针对的 transformers 修订版本、任务,以及层级(bare / clone / skill)。如前所述,我们针对两类不同的模型类别查看不同的指标。
大型开放模型:固定模型,改变修订版本
由于大型开放模型通常能得到正确结果,你真正衡量的是它为此付出的努力。它用了十轮还是一轮?它是否因为信任过时的文档而走了一条你已弃用的 API 路径?它是否遇到了你未曾预见的错误?
自然的实验是固定一个强大的模型,改变工具的修订版本:我们测试所针对的 transformers 的连续 git 版本,从已发布的标签如 v5.8.0 和 v5.9.0 到引入 CLI 和 Skill 的特定提交。我们想观察它给 agent 带来的负担是上升还是下降。我们在 transformers 上使用测试框架,以检查添加专用 CLI 和 Skill 是否真的减轻了 agent 的工作。
对于我们在测试中使用的三个大型模型,所有任务的平均耗时表明,Skill 提交使任务耗时减少:

按层级划分的每个修订版本的中位时间:skill 提交(绿点)最快。
另一方面,在我们克隆仓库的实验中,可以看到由于引入 CLI 和示例的提交,token 消耗显著增加,我们稍后会看到。

按层级划分的每个修订版本的中位新增 token 数:一旦 CLI 进入仓库,克隆变体就会跃升。
阅读克隆变体的 trace 可以解释原因。该提交添加了一个命令,但它也直接将 CLI 的实现和一组 cli/agentic/*.py 使用示例放入了仓库。
在 clone 变体上,agent 面前有一个完整的 transformers 检出,大约三分之一的运行会去阅读新的接口(/cli/ 树和示例脚本),以在调用之前了解接口。这使中位输入从约 4k token 提高到约 6.4k token。
这两张图是一个权衡的两面:该提交为大型模型节省了时间(它们直接使用 CLI 而不是调试 Python),代价是更多 token(它们阅读了教会它们 CLI 的代码)。在合并 PR 之前值得了解的一个权衡。
不过,有一个尚未基准测试的注意事项对 CLI 有利:阅读它的成本会随着连续运行而摊销。我们的设置是为一次性实验构建的。每次运行都是一个全新的 agent,从头重新发现 CLI,因此每次都要付出发现成本。在实际使用中,agent 学习一次接口,然后在同一会话中一个接一个地解决任务,从而将这一成本分摊到许多请求上。我们在这里测得的 token 增长更接近最坏情况,而不是用户日常会看到的情况。
小型模型:固定修订版本,改变模型
开放模型让我们能够精细控制这里最关键的变量:规模、配置、量化、提供商、训练,以及任何因模型而异的东西。它们也是优秀工具界面最重要的地方:一个小模型在 bare 环境中被要求“使用 transformers 来做 X”时,可能会猜到一个几个版本前已经改变的 API,可能会进行不必要的工具调用,并可能得到错误的答案。
所以这里的实验与上述相反:固定修订版本,遍历模型。这有助于看出哪些模型真正能胜任任务,不仅仅是通过 token 数量和时间,而是具体到哪些模型无法可靠地处理工具调用。我们的直觉是,模型越小,工具使用和任务都越难;我们在各种模型规模上运行了测试框架来验证这一点:

各模型按层级的匹配百分比:技能层级提升了较大的模型,但降低了较小的模型。
这似乎也与摄入的 token 数量相关

各模型按层级的新增 token 中位数。
关于公平比较的说明:当覆盖率不均匀时,简单地对任务取平均会产生误导(一个只完成了快速任务的模型看起来很快)。报告有一个“仅共享任务”开关(跨模型和/或修订版本),以便进行同类比较,还有一个覆盖率热图,让你确切看到哪些任务 × 修订版本 × 模型单元格实际运行了。
调整工具:标记与结果
这里有两件事结合在一起:如何超越智能体是否成功,看到它做了什么以及如何做的;以及我们从测试框架中提取出的首批结果。
什么是标记?
匹配百分比、token 和时间告诉你一次运行的成本,但并不能告诉你底层发生了什么。
这就是我们引入标记概念的原因。标记是配置文件(每个工具的小型插件,教测试框架如何构建和驱动给定库)与一次运行匹配的命名模式。
它是你关心的行为的一行标签,针对智能体运行的 shell 命令、编写的代码、读取的文件或最终答案进行检查。一次运行可以触发多个标记或一个都不触发;报告显示每个标记触发的频率,按模型和修订版本分别统计。
对于 transformers,我们声明了几个,但只看最相关的两个:
cli:智能体调用了transformers命令行工具(例如transformers classify …)而不是编写 Python。pipeline:它使用了高级pipeline(...)Python API。
这些是我们观察的指标,以了解变更是否真正改变了智能体的行为。有趣的是,模型越大,它越倾向于利用新上下文而不是使用自己的记忆;因此利用了新引入的 CLI。

各模型按层级的 CLI 采用率:只有技能层级会使用它,并且随着模型增大而增加。
CLI 采用是新现象:CLI 在一个提交中落地,不在任何模型的训练数据中,并且文档很少。效果很明显:是技能变体,即附带 CLI 文档的那个,真正使用了它,达到 55.3%。
CLI + 技能提交有帮助吗?
对比不同模型规模下的提交,CLI + Skill 对更大的模型有帮助:在 skill 层级上,Kimi 和其他大型 agent 会使用 CLI,并在更少的轮次内完成。(在 clone 上,它们会先花费更多输入 token 来阅读新的 CLI 代码,正如我们上面所见,所以收益体现在时间和轮次上,而不是原始 token 上。)

Kimi-K2.6、GLM-5.1 和 MiniMax-M2.7 跨修订版本
但在一些较小模型的设置中,它似乎会损害性能。一个可能的解释是,小模型依赖记忆中的 API 模式,复现它们在训练数据中见过的 pipeline(...) 片段。新概念于是成了它们更容易出错的更大表面。你可以直接在 harness 上观察到这一点:更低的匹配率、更多重试、cli 标记几乎不触发。这在 Qwen3-4B 模型上尤其显著:Skill 几乎没有改变它的匹配率,但它的成本分布却受到了显著影响。
几乎所有这些都来自 clone 层级。checkout 现在包含 CLI 的实现和 cli/agentic/*.py 示例,而 4B agent 会大量读取它们:其中位数新 token 从约 2.4k 跃升至约 23k,时间和输出也大幅飙升,而准确率却没有提升。

Qwen3-4B 跨修订版本。CLI + Skill 提交把成本分布大幅拉开,在 clone 层级上,agent 会大量读取新发布的 CLI 源码(新 token 约为 10 倍),而匹配率却没有提升。(repeat tokens 保持平稳:此设置不使用 prompt 缓存。)
不过,有时 Skill 会直接破坏正确性。阅读 trace 可以看出原因,例如对于 Qwen3-14B:
添加 Skill 后,其整体匹配率从 67%(裸模型)降至 43%,而在最简单的任务上,这种崩溃非常明显:classify-sentiment 在 clone 变体上从 100% 变为使用 Skill 后的 0%。

Qwen3-14B 在 classify-sentiment 上,按层级划分:clone(蓝色)在各修订版本中保持 100%,但 Skill 变体(绿色)在 CLI + Skill 修订版本处崩溃至 0%。
查看 trace,模型把 CLI 误认为是一个可以直接调用的工具(就像 agentic-harness 工具,比如 web-search)。Skill 不是可执行工具:它是加载到 agent 上下文中的文档,而 transformers CLI 只应通过 shell(经由 bash)运行;所以这样行不通。
Qwen3-14B 读取了 Skill,并在其 56 次 Skill 运行中的 39 次里,要么发出一个 transformers(command="classify", ...)
工具调用(一个从未注册过的工具),要么在其 read/bash/edit/write
工具中找不到类似工具,于是断定它无法运行模型并放弃。无论哪种情况,它都没有退回到在 clone checkout 上得分 100% 的那一行
pipeline(...),而是宣称任务不可能完成。

Qwen3-14B 在 classify-sentiment(Skill 变体)上:它推理认为 read/bash/edit/write 无法运行模型,于是放弃。
这正是我们构建 harness 要捕捉的问题:同一个变更在加速大模型的同时,最终却破坏了小模型,这起初对我们来说有点反直觉,而且我们很可能会原样发布。给维护者的启示是:面向 agent 的 API 应该跨模型规模进行评估,因为新的能力可能会减少强模型的工作量,同时给较小模型增加歧义。这也暗示了一种修复方法:与其手写一个 Skill 并在事后检查,不如一开始就针对较弱模型生成并验证它。
这正是 Upskill 所做的:只有当强模型的解决方案能够可衡量地帮助较小的模型时,它才会将其转化为一项 Skill。
自己动手试试
该测试框架是一个 CLI,agent-eval。安装它,运行一套测试,在 HF Jobs 上将其扩展到多个模型 × 多个修订版本,并将报告发布为 Hugging Face Space。
仅限可信的本地使用。该测试框架会以绕过权限的方式运行编码代理,并执行你所指向的任意修订版本的代码,而且追踪记录可能包含提示词、输出和本地路径。在将其指向并非你编写的代码或分享结果之前,请参阅 SECURITY.md。
完整且持续更新的设置与使用说明位于 README 中。
结语
检查最终答案只能告诉你一个代理能否使用你的库。它无法告诉你代价:所需的轮次、token、错误,以及它到达那里所走的路径。该测试框架会在你所选的修订版本和模型上衡量这些。
在 transformers 上,它发现了我们本来会盲目发布的问题:CLI + Skill 对最大的开源模型有帮助,却会损害最小的模型。在合并之前值得了解!
它基于配置文件,并且设计为可适配:将其指向你自己的库,定义一些任务及其预期答案,就能得到同样的报告。代码和任务在 repo 中,追踪记录在 Hub 上。 如果你将其用于你的项目,请告诉我们!
致谢
该测试框架完全建立在 pi 之上,这是 Mario Zechner 的编码代理 CLI:它驱动每一次开源模型运行,并且只需要一个 HF_TOKEN 来为模型提供服务,这正是使开源模型全面测试变得可行的原因。
感谢我们所测试模型背后的模型构建者和推理提供商。总体而言,它们的表现远超 bare 基线所暗示的水平。
来源:Hugging Face Blog · huggingface.co
