Agent Skill 也需要测试:如何搭建 Skill 评估框架

0 阅读14分钟

Agent Skill 也需要测试:如何搭建 Skill 评估框架

参考自 4 篇文献:


如何实践?将文章交给Agent,它会告诉你如何基于项目搭建评估框架!

为什么需要评估 Skill?

Skill 和代码一样是生产资产,但大多数 Skill 的质量保障停留在"跑几遍看看感觉"的阶段。Philipp Schmid 的统计很直白:SkillsBench 在 6,300 多个仓库中发现了超过 47,000 个 Skill,几乎没人在测试它们,大部分还是 AI 生成的。

你不会不写测试就发布代码,为什么写 Skill 就可以不做评估?

Eval(evaluation,评估)做的事情很简单:给 agent 一个 prompt,记录它做了什么,然后按一组规则打分。像轻量级的端到端测试——跑 agent、录过程、对结果。


核心循环

Skill 评估不是一次性动作,而是一个迭代循环。四篇文章虽来自不同生态(Anthropic / Google-Gemini / OpenAI-Codex),但收敛到了同一个骨架:

定义成功 → 手动试跑 → 构建 prompt 集 → 搭评估框架 → 跑评估 → 人工审查 → 改 Skill → 重跑 → ……

下面逐步展开。


Phase 0:先定义成功,再动手

在写任何 eval 代码之前,先用可测量的语言写下"成功"长什么样。

OpenAI 的文章把成功拆成四个维度,Philipp Schmid 用三个维度(合并了 Outcome 和 Process)。综合来看:

维度问的问题具体示例
Outcome(结果)产出能用吗?代码能编译、文件能打开、API 返回有效响应、npm run dev 能启动
Process(过程)agent 走对路了吗?触发了正确的 Skill、按预期顺序执行命令、没有跳步
Style(风格)产出符合约定吗?用了正确的 SDK import、model ID 没过期、命名规范一致
Efficiency(效率)过程划算吗?没有反复装同一个依赖、token 消耗合理、没有命令死循环

前两个是底线——不通过就别谈别的。后两个区分"能用"和"好用"。

关键原则:评结果,不评路径

Agent 经常走出你没预料到的路线但结果完全正确。Philipp Schmid 强调:不要惩罚通往正确答案的非预期路径。 两次运行可能产出完全一样的正确结果,但一个烧了 3 倍 token——效率维度会捕捉到这个差异,不需要在结果维度上扣分。

示例:Gemini Interactions API Skill 的成功标准

Philipp Schmid 为他的 Gemini Skill 定义了这些检查项:

  • 正确的 SDK import(from google import genai
  • 当前的 model ID(不是已废弃的 gemini-2.0-flash
  • 使用 interactions.create() 而非 generateContent
  • 多轮对话用 previous_interaction_id

大部分用正则就能检查——不需要复杂工具。


Phase 1:手动试跑,暴露隐藏假设

不要一上来就写自动化。先手动跑 3-5 次,这一步的目的不是打分,而是发现你没预料到的问题

OpenAI 的文章把隐藏假设分成三类:

类型示例
触发假设"搭个 React demo"该触发 setup-demo-app 但没触发;"给现有项目加 Tailwind"不该触发但触发了
环境假设Skill 假设目录是空的;假设 npm 已安装;假设特定操作系统
执行假设agent 跳过了 npm install;配置 Tailwind 的顺序和预期不同

每一个手动修复都是一个未来的 eval case。 你在这一步添加的缺失安装命令、修正的路径、收紧的 description——全部可以转化为自动化检查。这不是浪费时间,而是在为 Phase 2 积累素材。


Phase 2:构建 Prompt 集

10-20 个 prompt 足以启动。之后从真实失败中逐步扩充。

四种 Prompt 类型

类型目的示例
显式触发直接点名 Skill,验证基本功能Create a demo app using the $setup-demo-app skill
隐式触发描述场景但不提 Skill 名,验证 description 的匹配能力Set up a minimal React demo app with Tailwind
上下文触发加入领域噪音,验证在现实 prompt 中的鲁棒性Create a small demo app to showcase the Responses API
负面控制相邻但不该触发的场景,捕捉误触发Add Tailwind styling to my existing React app

负面测试的关键

四篇文章不约而同强调了同一点:负面测试是最容易被忽略但最有价值的部分。

差的负面测试:

"Write a fibonacci function" — 作为 PDF Skill 的负面测试毫无价值,明显不相关

好的负面测试:

"Add Tailwind styling to my existing React app" — 和 setup-demo-app Skill 共享关键词(Tailwind、React),但意图完全不同(增量修改 vs 从头搭建)

负面测试的价值在于暴露 description 写得太宽泛的问题。一个 description 过泛的 Skill 会在不该出场的时候出场,干扰 agent 的正常工作。

每个 Prompt 自带成功标准

不同 prompt 的成功标准可以不同:

[
  {
    "id": "py_basic_generation",
    "prompt": "Write a Python script that sends a text prompt to Gemini and prints the response.",
    "should_trigger": true,
    "expected_checks": [
      "correct_sdk",
      "no_old_sdk",
      "current_model",
      "interactions_api"
    ]
  },
  {
    "id": "negative_unrelated",
    "prompt": "Write a Python script that reads a CSV and plots a bar chart using matplotlib.",
    "should_trigger": false,
    "expected_checks": []
  }
]

should_trigger 标记这个 prompt 是否应该触发目标 Skill;expected_checks 列出该 prompt 需要通过的检查项 ID。负面测试不需要检查项——它唯一的职责是"不触发"。


Phase 3:搭建评估框架

评估框架 = 运行机制 + 评分机制

3.1 运行机制:永远带 Baseline

Anthropic 的 skill-creator 要求每个 prompt 跑两个版本:

配置什么时候用
with_skill加载目标 Skill
baseline(无 Skill)创建新 Skill 时:不加载任何 Skill,测"模型本来就会多少"
baseline(旧版 Skill)改进现有 Skill 时:加载旧版本,测"改了之后是否真的更好"

两个版本要同时启动,不要先跑完 with_skill 再补 baseline。这是因为:

  • 并行跑更快
  • 没有 baseline 就无法区分"模型本来就会"和"Skill 教会了它"

如果不加载 Skill 时 eval 也通过了——说明这个 Skill 可能根本不需要存在。这直接关联到后面的"退役检测"。

3.2 确定性检查:快、可靠、可解释

确定性检查是评估框架的骨架。它用正则匹配、文件存在性、命令序列等手段,覆盖所有能机械判定的维度:

# SDK import 是否正确?
def check_correct_sdk(code, language):
    if language == "python":
        return bool(re.search(r"from\s+google\s+import\s+genai", code))
    return bool(re.search(r"""['"]@google/genai['"]""", code))

# model ID 是否过期?
DEPRECATED_MODELS = ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash"]

def check_current_model(code, language):
    return not any(model in code for model in DEPRECATED_MODELS)

把所有检查注册到一个字典中,eval 运行时按 prompt 的 expected_checks 逐个调用:

CHECK_REGISTRY = {
    "correct_sdk":      check_correct_sdk,
    "current_model":    check_current_model,
    "interactions_api": check_interactions_api,
    "no_old_sdk":       check_no_old_sdk,
    # ... 按需扩展
}

def run_eval(test_case):
    output = run_agent_cli(test_case["prompt"])
    code = extract_code_blocks(output.response_text)
    results = {}
    for check_id in test_case["expected_checks"]:
        results[check_id] = CHECK_REGISTRY[check_id](code, test_case["language"])
    return results

确定性检查的优势:

  • ——毫秒级,零额外成本
  • 可解释——失败时直接看原始输出就能定位原因
  • 可复现——同样的输入永远给同样的结果

3.3 LLM-as-Judge:覆盖确定性检查够不着的地方

有些维度用正则搞不定——代码结构是否合理、设计是否美观、命名是否地道。这时引入第二个模型做评委。

Philipp SchmidOpenAI 都给出了同一个做法:用结构化输出约束评委模型的响应格式,让结果可解析、可追踪、可对比。

class CheckResult(BaseModel):
    passed: bool
    notes: str = Field(description="Brief explanation of the assessment.")

class DesignEvalResult(BaseModel):
    overall_pass: bool
    score: int = Field(ge=0, le=100)
    typography: CheckResult   # 字体是否有辨识度
    color_cohesion: CheckResult   # 配色是否协调
    layout: CheckResult   # 布局是否有设计感
    generic_ai_avoidance: CheckResult   # 是否避开了"AI 味"模板

要点:

  • 拆成独立维度分别打分,不要只给一个总分——总分掩盖了具体哪里出了问题
  • 评委模型和被测 agent 用不同的 session,避免自我验证
  • 只在确定性检查覆盖不了时才用 LLM judge——它更慢、更贵、结果有波动

OpenAI 的做法是通过 --output-schema 参数直接约束 Codex 的输出为 JSON Schema,这样评分结果的格式是确定的,只有内容是模型生成的。

3.4 人工审查:不可替代的最后一环

自动化检查告诉你"对不对",人工审查告诉你"好不好"。

Anthropic 的 skill-creator 为此做了一个专门的 eval-viewer 工具:

  • Outputs 标签页:逐个查看每个 prompt 和产出,留下文字反馈
  • Benchmark 标签页:pass rate、token 用量、执行时间的聚合对比
  • 迭代 2+ 时还能看到上一轮的产出和反馈,方便对比

反馈规则很简单:空反馈 = 认可,有文字 = 需要改进。 改 Skill 时只关注有具体抱怨的 case。


Phase 4:执行评估循环

目录结构

skill-creator 推荐的组织方式:

skill-workspace/
  iteration-1/
    eval-basic-generation/
      with_skill/
        outputs/
        timing.json
        grading.json
      without_skill/
        outputs/
        timing.json
        grading.json
      eval_metadata.json
    benchmark.json
    benchmark.md
    feedback.json
  iteration-2/
    ...

每轮迭代一个目录,每个 eval case 一个子目录,with_skill 和 baseline 并列存放。

一轮迭代的完整步骤

Step 1:并行启动所有运行

对每个 prompt,同时启动 with_skill 和 baseline 两个运行。不要一个一个来。

Step 2:等待期间起草 assertions

不要干等。趁运行还没结束,起草或更新检查项,解释给用户听。

Step 3:运行完成后立即记录 timing 数据

{
  "total_tokens": 84852,
  "duration_ms": 23332,
  "total_duration_seconds": 23.3
}

这是唯一的采集窗口——token 数和耗时只在任务完成通知中出现,不会持久化到别的地方。

Step 4:评分 + 聚合 + 分析

  1. 用确定性检查 + 可选 LLM judge 对每个运行评分

  2. 聚合为 benchmark:pass_rate、mean ± stddev、与 baseline 的 delta

  3. 做一轮分析,找出聚合数字掩盖的问题:

    • 不管有没有 Skill 都通过的 assertion → 没有区分度,可以删
    • 方差极高的 eval → 可能是 flaky case,需要增加试跑次数
    • token/时间的异常波动 → 可能是 Skill 引入了不必要的步骤

Step 5:展示给用户,收集反馈

用 eval-viewer 或直接在对话中展示结果,收集反馈后进入下一轮。

多轮试跑

Agent 行为是非确定性的。Philipp Schmid 建议:每个 prompt 跑 3-5 次,看分布而非单次结果。 单次 pass/fail 可能是运气好或运气差,只有分布才能给出可靠信号。

改进 Skill 的四个原则

skill-creator 给出了改 Skill 时最重要的思维方式:

A. 从反馈中泛化,不要为特定 case 打补丁

你只跑了十几个 prompt,但 Skill 要被用无数次。如果你为某个 case 加了一条特殊规则,它对别的 case 可能是负面影响。理解反馈背后的 pattern,写出通用的指令。

B. 保持精简,删掉没拉动效果的指令

读 agent 的执行 transcript(不只是最终输出)。如果某条指令让 agent 浪费时间做无用功,删掉它看看会不会更好。

C. 解释 why,而不是堆 MUST / ALWAYS

"如果你发现自己在用全大写写 ALWAYS 或 NEVER,这是一个黄色信号。" —— skill-creator

当代 LLM 有 theory of mind。告诉它"为什么这样做"比命令它"必须这样做"有效得多。Philipp Schmid 也验证了这一点:"Always use interactions.create()""The Interactions API is the recommended approach" 效果好,但注意前者是指令式的("用 X"),不是解释式的("X 被推荐"),两者有区别。

D. 提取重复工作

如果多个 test run 都独立写了同一个 helper script——把它打包进 Skill 的 scripts/ 目录。让 agent 组合现有代码,而不是每次从头发明。


Phase 5:Description 优化

name + description 是 Skill 的触发机制——agent 看到用户的 prompt 后,拿它和所有已安装 Skill 的 description 做匹配,决定是否加载。

这两个字段是整个 Skill 中最值得投入优化精力的部分

Philipp Schmid 的案例中,单独改 description 就修复了 7 个失败中的 5 个。Description 决定了触发率——触发都不对,Skill 内容写得再好也没用。

优化流程

skill-creator 提供了一套完整的自动化优化流程:

Step 1:生成触发评估 query(20 个)

should_trigger 和 should_not_trigger 各约一半。关键要求:

  • 用真实用户语气,包含文件路径、项目名、口语化表达、甚至拼写错误
  • 长度和风格要多样——有的详细有的简短,有的正式有的随意
  • 负面 query 要难——和 Skill 共享关键词但意图不同的才有价值

差的 query:

"Format this data""Extract text from PDF" — 太抽象,不像真人会说的话

好的 query:

"ok so my boss just sent me this xlsx file (its in my downloads, called something like 'Q4 sales final FINAL v2.xlsx') and she wants me to add a column that shows the profit margin as a percentage" — 有背景、有细节、有口语化表达

Step 2:用户审核 query 集

差的 query 导致差的 description。让用户过一遍,调整不合理的分类。

Step 3:自动优化循环

python -m scripts.run_loop \
  --eval-set trigger-eval.json \
  --skill-path ./my-skill \
  --model claude-opus-4-6 \
  --max-iterations 5

这个脚本自动执行:

  1. 把 query 集 60/40 分成 train 和 test
  2. 每个 query 跑 3 次,取平均触发率
  3. 根据失败 case 让模型提议新 description
  4. 用新 description 重新评估
  5. 迭代最多 5 轮

Step 4:按 test 集得分选最优 description

选 test 集的分数,不是 train 集——防过拟合。把最优 description 更新到 SKILL.md 的 frontmatter 中。

Description 写法要点

  • 以 "Load when…" 开头——这是给 agent 的路由指令,不是给人读的文档
  • 描述用户的意图,不是 Skill 的功能——用户不会说"我需要一个 PR 监控 Skill",他们会说"babysit my PR"、"make sure this lands"
  • 适度拓宽触发范围——当前模型倾向于 under-trigger(该触发时不触发),description 可以稍微"主动"一些
  • 50 词以内——这个成本每次会话、每个用户都在付

Phase 6:毕业与退役

Eval 的生命周期

一个 eval case 的角色会随时间变化:

阶段角色说明
初期Capability eval(能力检验)通过率从低往高爬,每次改进都能看到数字变化
成熟期Regression eval(回归检验)通过率稳定在 ~100%,职责变成"守住已有成果,防止倒退"

Philipp Schmid 的说法:eval 从"给你一座山去爬"毕业为"确保你不滑下来"。

Skill 退役检测

这是四篇文章中被反复提及但最容易被忽视的步骤:

不加载 Skill,重跑所有 eval。如果依然全部通过——model 已经内化了这个 Skill 的能力,可以下线。

随着基础模型升级,某些 Skill 会变成多余负担:占 context 窗口、增加 Skill 间的触发冲突、拖慢响应。定期做退役检测,让 Skill 库保持精简。

跨平台验证

Philipp Schmid 指出:同一个 Skill 在不同 agent 框架下表现可能不同。如果你的 Skill 要跨平台使用,在每个平台上都跑一遍 eval。


工具链速览

工具/方法来源用途
eval-viewer / generate_review.pyskill-creator可视化对比产出 + 收集人工反馈
aggregate_benchmark.pyskill-creator聚合 pass_rate / token / time 统计
run_loop.pyskill-creator自动化 description 优化循环
CHECK_REGISTRY 模式Philipp Schmid确定性检查的注册与调度
codex exec --jsonOpenAI输出 JSONL 事件流,可精确检查执行过程
--output-schemaOpenAI约束 LLM judge 的输出格式为 JSON Schema
Blind comparisonskill-creator给独立 agent 两个匿名产出做 A/B 判断

速查清单

步骤关键产出常见陷阱
定义成功四维度检查项列表标准太模糊,无法机械判定
手动试跑隐藏假设清单跳过这步直接自动化,遗漏环境和触发问题
构建 prompt 集10-20 个分类 prompt + 成功标准缺少负面测试;负面测试太简单、明显不相关
搭框架确定性检查 + 可选 LLM judge全靠 LLM judge,慢、贵、结果有波动
跑评估benchmark.json(含 baseline 对比)没跑 baseline 无法归因;单次试跑下结论
人工审查feedback.json只看聚合数字不看实际产出
改 Skill更新后的 SKILL.md为特定 case 过拟合;堆砌 MUST/ALWAYS
优化 description触发率测试报告description 太泛导致误触发;太窄导致漏触发
退役检测无 Skill 的 eval 结果永远不检查,Skill 越积越多拖累整体表现