去年年底,团队里一位同事改了一行 SKILL.md 的描述文字,把“优先查询订单状态”改成了“根据用户诉求选择合适工具”。代码没报错,pytest 全绿,PR 顺利合并。三天后线上开始出现跳过订单校验直接退款的事故。
这件事让我意识到一个问题:我们给 Skill 写测试的方式,和给普通代码写测试的方式,几乎完全是两回事。 普通代码改坏了,测试挂掉;Skill 改坏了,Agent 依然“能跑”,只是行为悄悄漂移了。Skill 的本质是注入到推理阶段的程序性知识,它不编译、不抛异常,失效时往往只表现为“任务没做成”或者“用了更贵的路径做成”。
这篇教程不打算罗列所有 Agent 评测基准,而是聚焦一个具体问题:如果你手上有几个正在用的 Skills,怎么判断它们真的有用,以及在改动后不会退化。
先想清楚:Skill 评测到底在测什么
一个常见的误解是把 Skill 评测等同于“跑一遍任务看成功率”。但 Skill 的效用高度非均匀。SkillsBench 在 11 个领域 86 项任务上做了三条件对照(无 Skill / 人工精编 Skill / 自生成 Skill),人工 Skill 平均通过率提升 16.2 个百分点,听起来不错——但 84 项可比任务里有 16 项出现了负增益,也就是装了 Skill 反而更差。
软件工程领域的数字更刺眼。SWE-Skills-Bench 用 49 个公开 Skill 在真实 GitHub 仓库上做配对评测,39 个 Skill 的通过率提升为零,平均增益只有 +1.2%;token 开销最高增加了 451%,通过率却纹丝不动。也就是说,“感觉装了 Skill 之后更好了”这个判断,在统计上经常站不住脚。
所以 Skill 评测至少要回答三个问题,缺一不可:
它有没有用? 不是“能不能跑”,而是启用 Skill 相对于不启用,任务结果有没有可归因的提升。
它有没有副作用? 即使结果正确,执行路径是否变得更啰嗦、更贵、更危险。一个 Skill 让 Agent 多做了五步验证才给出正确答案,结果对了,但效率可能已经不可接受。
它会不会退化? 换模型版本、改一句描述、调整 temperature,Skill 行为是否漂移。没有回归测试的 Skill,本质上是在裸奔。
第一层:静态校验——先把明显有问题的挡在门外
在跑任何真实任务之前,有一批问题是可以在秒级内查出来的:SKILL.md 的 frontmatter 是否完整、脚本是否有明显的提示词注入模式、Skill 之间是否存在大段内容重叠。
NVIDIA 的 SkillEvaluator 把这一层拆成两步。第一步是校验,检查结构规范、元数据、脚本质量,以及提示词注入、数据外泄、敏感凭证等安全问题。第二步是去重,用向量相似度检查 Skill 内部是否存在重复指导,同时比较整个 Skill 目录,识别跨 Skill 的内容重叠。后一点容易被忽略:当一个环境里装了上百个 Skill,重复内容不仅浪费上下文,还会让多个能力相近的 Skill 同时进入 Agent 的选择范围,增加选错的概率。
腾讯的 TRACE 框架把类似的事情拆得更细,其中 Convention 维度专门看 SKILL.md 是否清楚说明了用途、适用范围和触发条件,元信息是否完整,产物边界是否明确。这些检查不需要调用模型,写一个简短的 Python 脚本挂在 CI 的 pre-commit 阶段即可。
一个容易踩的坑是只校验不评测。静态检查全过,不代表 Skill 有效。它只是必要不充分的下限。
第二层:轨迹评估——结果对了,路径不一定对
Agent 评测和传统模型评测最大的区别在于,被测对象已经从“输出”扩展到了“执行轨迹”。两个 Agent 都生成了同一份分析报告,一个沿着“读数据 → 调分析 Skill → 生成报告”走,另一个经历了工具调用失败、重复查询、误调无关 Skill、重试多次才勉强产出。只看最终结果,两者都是“成功”;看轨迹,后者可能是不可上线的那一个。
轨迹评估的核心指标可以分成步骤级和序列级。步骤级看每一次工具调用的工具选择是否正确、参数是否合法、执行是否成功;序列级看工具调用顺序是否合理、有没有冗余步骤、是否在应该读取 Skill 的时候真的读取了它。
这里有一个实践上的取舍:确定性检查优先于模型评判。能用单元测试、数据库断言、state diff 写出来的东西,不要交给 LLM Judge。比如“Skill 要求先查询订单再退款”,可以直接在沙箱里检查工具调用序列中 query_order 是否在 create_refund 之前出现;这比让一个模型去“判断轨迹是否合理”可靠得多,也便宜得多。LLM Judge 留给那些难以形式化的维度,比如“中间推理是否自洽”,并且需要持续用人工标注校准。
LangChain 的测试文档里把这件事分得很清楚:unit tests 用内存 fake 断言确定性行为,evals 则用确定性匹配或 LLM judge 来评估执行轨迹。两者不是替代关系,是分层关系。
第三层:端到端配对评测——把 Skill 变成唯一的变量
到了真正判断“这个 Skill 有没有用”的时候,最干净的做法是 A/B 配对:同一个任务、同一个模型、同一套提示词和评分标准,唯一的变化是有没有加载目标 Skill。NVIDIA 把两组的得分差称为 Skill Lift,并在独立沙箱中分别运行,避免互相污染。
SkillsBench 用的是同构但更严格的三条件范式:无 Skill、人工精编 Skill、Agent 自生成 Skill。三条件的好处是,你不仅能知道 Skill 有没有用,还能知道“让模型自己写一个 Skill”是不是比人工写的更差。他们的结论是,自生成 Skill 整体上没有带来显著收益。这意味着如果你打算走“让 Agent 自己生成并迭代 Skill”的路线,必须有配对评测来兜底,否则很容易把噪声当成改进。
配对评测的落地形式可以很简单:一个 YAML 文件描述用例,包含 prompt、预期结果、必须调用的工具、容差范围;跑两遍,一遍带 Skill,一遍不带;用确定性 scorer 打分,输出差值。
cases/refund-flow.yaml
id:refund-flow-001
prompt:"用户说订单 88123 的商品破损,要求退款。"
required_tools_before_final:
-query_order
-check_refund_policy
forbidden_tool_calls:
-create_refund# 未校验前不应直接退款
expected_final_state:
order_id:"88123"
refund_created:false
next_action:"await_user_confirmation"
tolerance:
max_extra_tool_calls:2
跑的时候不需要什么重型框架,一个 pytest 参数化用例加上 subprocess 调用你的 Agent 即可。重点不在于工具多先进,而在于配对和隔离:有 Skill 和无 Skill 的两次运行必须使用相同的输入和评分逻辑,并且彼此看不到对方的环境状态。
把评测变成门禁,而不是一次性动作
Skill 评测最容易死掉的方式,是写成一篇报告然后放在那里。真正防退化,必须把它塞进 CI。
阿里开源的 skill-up 做的事情本质上就是把软件测试的方法论平移到 Skill 上:声明式 YAML 用例,支持在 Claude Code、Codex、Qoder CLI 等多个 Agent Engine 中运行,通过规则、脚本或 Agent Judge 评分,在本地或 CI 中生成结构化报告。它规定的标准结构里,evals/eval.yaml 是必须的,SKILL.md 是被测对象,评测和演进是两个互补的循环。
AWS 公开的 Agent Evaluation 接入 GitHub Actions 的实践也遵循同一个模式:PR 触发评测数据集,采集 Agent Trace,计算 score,score 低于阈值就 block PR。这和传统的单元测试门禁没有本质区别,只是被卡的对象从“代码是否正确”变成了“Agent 行为是否稳定”。
一个最小可用的 pytest 门禁大概长这样:
tests/test_skill_regression.py
import pytest, json
from your_agent import run_agent
SKILL_ON = {"skills": ["refund-policy"]}
SKILL_OFF = {"skills": []}
@pytest.mark.parametrize("case", load_cases("evals/refund/*.yaml"))
def test_skill_does_not_regress(case):
without = run_agent(case["prompt"], config=SKILL_OFF)
with_skill = run_agent(case["prompt"], config=SKILL_ON)
baseline_score = score(without, case)
treatment_score = score(with_skill, case)
# 硬性要求:装了 Skill 不能比不装更差
assert treatment_score >= baseline_score - case.get("tolerance", 0.0), (
f"Skill regression: {baseline_score} -> {treatment_score}"
)
# 路径约束:Skill 声明的工作流必须被遵守
assert_tool_order(with_skill.trace, case["required_tools_before_final"])
assert_forbidden_absent(with_skill.trace, case["forbidden_tool_calls"])
关键在 tolerance 的设定和两个断言的分工:前者管结果不退化,后者管过程不跑偏。只断言结果,会漏掉“结果对了但路径烂了”的情况;只断言路径,会漏掉“步骤全对但最终状态错了”的情况。
几个值得提前知道的坑
负增益是常态,不是异常。 SkillsBench 的 16/84 负增益和 SWE-Skills-Bench 的 39/49 零提升都在说同一件事:Skill 的效用强烈依赖领域适配和执行上下文。版本不匹配的 Skill 可能主动把 Agent 带偏,而不是“没帮上忙”。所以评测的第一目标不是证明 Skill 有用,而是尽早发现它没用甚至有害。
Token 开销要单独度量。 SWE-Skills-Bench 里最高 451% 的 token 增长而通过率不变,意味着只看 pass rate 会完全错过效率退化。在 scorer 里加一条 token_delta 的记录,成本很低,信息量很大。
自生成 Skill 需要更严格的对照。 如果模型自己写 Skill 自己用,评测时必须保留“人工精编 Skill”和“无 Skill”两条基线。否则无法区分“自生成 Skill 有效”和“模型本来就能做,Skill 只是增加了噪声”。
可发现性是一个独立维度。 Skill 写得好,Agent 找不到也白搭。随着 Skill 目录增长,检索精确率会下降。NVIDIA 把可发现性单独列为一个维度,既看 Agent 能不能找到该用的 Skill,也看它会不会在无关任务里误用。评测集里应该包含“应该触发”和“不应该触发”两类用例,后者同样重要。
Skill 评测不需要一开始就建一套大而全的平台。从一个 YAML 用例、一个 pytest 文件、一条“装了 Skill 不能比不装更差”的断言开始,先让最有把握的那个 Skill 进入配对评测。跑上两周,你会对“它到底有没有用”有一个比任何主观感受都可靠的答案。剩下的,就是把它挂到 CI 上,让每一次 SKILL.md 的改动都过一遍同样的门。
本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。