SKILL 07 / META · DEEP DIVE

给 Skill 写 Eval

官方那句「先建评估再写文档」,现在终于有内置跑法了

2026-09-08 · BigCat

你改了一版 SKILL.md,跑一次感觉更好了。这句「感觉」值多少钱?答案是零——除非你有基线、有断言、有重复次数。

前置 → Skill 06 官方 Authoring 最佳实践精读(本期拆的是那一页里被跳过的评估章节)

// WHY THIS MATTERS

Anthropic 在 Skill authoring best practices 里把「先建评估,再写文档」加粗放在评估章节第一行,然后给了五步、给了 JSON 结构——紧接着一条 Note 说:目前没有内置的方式来跑这些评估,用户要自己搭。于是这一整节被绝大多数人跳过了。

但这条 Note 现在已经落后于工具本身:Claude Code 的 claude plugin eval 已经是一个带基线对照、带多种打分器、带 HTML 报告的完整 runner(本文写稿时处于 early access)。本期把三条可走的路径全部点名、给命令、给实测到的文件结构,最后回答那个真问题:怎么知道一次改动是真提升,而不是模型今天心情好。

// 01

它替你干掉什么:干掉「我觉得这版更好」

论断:skill 的输出是非确定性的,所以单次对比毫无信息量。你需要的东西只有三件——一条基线、一组能机器判的断言、一个大于 1 的重复次数。下面三条路径都提供这三件,成本差一个数量级。
三条路径(按上手成本排序) ──────────────────────────────────────────────────────────────── A claude plugin eval 内置 CLI · 目录即用例 · 自带 baseline arm 成本:几条命令 · 状态:early access B skill-creator 官方 plugin · 子代理跑 · mean±stddev + HTML viewer 成本:一次完整对话 · 状态:正式可装 C skillOverrides 手动双跑 零依赖 · 官方文档自己推荐的兜底做法 成本:全靠人眼 · 状态:永远可用 ──────────────────────────────────────────────────────────────── 它们量的都是同两件事(出处:code.claude.com · Skills) ① skill 有没有在该触发的 prompt 上被触发 ② 触发之后,输出有没有真的变好 「看见它触发了」只证明 Claude 找到了它,不证明它干了你想要的事。

第一条立刻能跑的用法

# 路径 A:内置 runner。先长出一个用例骨架(在 plugin / skills 目录根下跑)
claude plugin eval init --bare my-first-case
# → 生成 evals/my-first-case/prompt.md 和 evals/my-first-case/graders/criteria.md

# 填完之后,对当前目录跑三遍,并写一份自包含 HTML 报告
claude plugin eval . --runs 3 --report ./eval-report.html

# 路径 B:装官方 skill-creator,让它替你把整套流程跑完
/plugin marketplace add anthropics/claude-plugins-official
/plugin install skill-creator@claude-plugins-official
/reload-plugins
# 然后直接说:evaluate my summarize-changes skill with skill-creator

# 路径 C:兜底。在 .claude/settings.local.json 里把 skill 关掉,开新会话再跑一遍
{ "skillOverrides": { "summarize-changes": "off" } }

# 跑之前先做一次五秒体检:frontmatter 有没有静默解析失败
claude plugin validate .claude/skills

init --bare 吐出来的模板小得让人意外——prompt.md 的头部只有 max_turns 和 allowed_tools,graders/criteria.md 的头部只有 type 和 weight,正文各一句 TODO。用例就是目录,不是配置文件:一个用例一个文件夹,里面一份 prompt 加一个 graders/。

失败模式:plugin eval 本文写稿时(Claude Code v2.1.263)仍在 early access。账号没开通时,它会把参数解析完再退出,只打印一行「plugin eval is currently in early access」——什么都不跑,也不报错到你会注意的程度。别在 CI 里静默依赖它,先手动确认它真的跑起来了。
// 02

机制:它凭什么能把「感觉」变成数字

论断:这套东西的全部有效性来自三个设计——强制跑一条没有 skill 的对照臂、把「触发」从分数里摘出来、以及打分器分成免费的和要花钱的两类。三条都不是花架子,各自堵死一种自欺。

① 对照臂:默认就跑,不是可选项

claude plugin eval 的 --ablation 默认值是 with-without——只要目标解析出一个 plugin,它就自动加跑一条「没有这个 plugin」的臂,然后报告分数差。为什么必须默认打开?因为模型本来就很强。你的 skill 说「写 commit message 要用 conventional commits」,模型不带 skill 也可能这么写——不跑对照臂,这一分你会算到自己头上。skill-creator 那边把同一件事说得更死:with-skill 和 baseline 两组必须「在同一个 turn 里 spawn」,不许先跑完带 skill 的、过两天再补基线。

② 触发 ≠ 质量:with-only 打分器不计入分数

这是最见功力的一处。在 with-without 模式下,标了 with-only 的打分器(包括 tool_used: Skill)被当成「plugin 有没有被点着」的指示器,而不是分数的一部分。理由很直白:没装 skill 的那条臂当然不可能触发 skill,把这条算进分差等于白送自己一分。触发率和输出质量必须分开量——skill-creator 为此专门有一个 description tuning 步骤,生成 should-trigger 和 should-not-trigger 两组 prompt,量命中率,然后反过来改 description。

③ 打分器分两类:先榨干免费的

把一个非法的 type 塞进 grader,CLI 会把合法值全吐出来:regex | tool_order | tool_used | file_exists | llm | baseline。前四个是纯程序判定,不花钱也不引入方差;后两个是模型调用(--judge-model 默认 haiku)。这条分界线直接决定了你该怎么设计用例:能把成功标准写成「产出了 output.txt」「先 Read 后 Edit」「输出里出现了 feat(」的,就别写成一句让 LLM 去意会的「输出应当专业且清晰」。这也正是官方评估文档那条原则——宁可要更多题、判得糙一点,也不要更少题、人工精判。

④ 重复次数:默认就是 3

--runs 的默认值是 case.runs ?? 3。单次跑赢在 agent 评估里几乎没有信息量,因为同一个 prompt 两次跑出的轨迹可能完全不同。skill-creator 把这一步做到了报表里:python -m scripts.aggregate_benchmark 产出的 benchmark.json / benchmark.md,给每种配置写的是 pass_rate、时间、token 的 mean ± stddev 以及 delta。看到 delta 先看 stddev——差值淹在标准差里的,那不叫提升。

失败模式:断言写得太松,两条臂都 100% 通过,delta 恒等于 0,你以为 skill 没用,其实是题目没有区分度。skill-creator 的 agents/analyzer.md 专门要求做一遍 analyst pass,就是去抓这两种病:non-discriminating assertion(不管有没有 skill 都通过)和 high-variance eval(同配置跑三次结果乱跳,多半是题目本身 flaky)。
// 03

真实用法与边界:五步、三题、两个 Claude

论断:官方给的顺序是反直觉的——先跑失败、再写题、再量基线、最后才写 SKILL.md。顺序反过来,你写的是想象出来的需求。

五步(出处:Skill authoring best practices · Evaluation and iteration)

  1. 找缺口:不带 skill 跑真实任务,记下具体是哪里失败、缺哪块上下文。
  2. 造题:就着这些缺口建三个场景。skill-creator 的说法是「2–3 个真实用户会说的 prompt」,并且明确要求先只写 prompt、断言留到跑的时候再补——因为你还不知道它会怎么错。
  3. 量基线:测没有 skill 时的表现。
  4. 只写刚好够的字:够补缺口、够通过评估就停。
  5. 迭代:跑评估,对照基线,改。

Claude A / Claude B:为什么必须是新会话

官方推荐开两个实例:Claude A 帮你写和改 skill,Claude B 装着它去干真活,你把 B 的翻车现场带回给 A。文档举的例子很具体——「我让 B 出区域销售报表,它写了查询但忘了过滤测试账号,skill 里明明提了这条规则」,A 的回应可能是把 always 改成 MUST,或者调整章节顺序。

「fresh session」这四个字是硬要求,不是修辞:官方明说,写 skill 时留在会话里的上下文会掩盖 SKILL.md 本身的缺口——你以为它读懂了,其实它是记得你刚才口头说过。claude plugin eval 和 skill-creator 都用子代理跑用例,正是为了每条用例都拿到干净上下文。

三个工具各自量的是什么,别搞混

失败模式:拿 /skill-doctor 当质量评估。「从没被触发过」的 skill 通常不是没用,是 description 没进筛选——这是触发问题,该去改 description、跑 should-trigger 那组题,而不是去改正文。反过来,一个每次都触发、每次都帮倒忙的 skill,在 /skill-doctor 里看起来非常健康。
// 04

代价与替代品:eval 比 skill 贵,而且会腐坏

论断:一套认真的 skill eval,花的钱和时间通常超过写这个 skill 本身。所以真正的判据不是「该不该写 eval」,而是「这个 skill 值不值得一套 eval」——多数不值。

诚实的代价

不装它你还能怎么办

失败模式:给一个一次性 skill 配一套 eval。评估的价值来自重复使用——你要靠它挡住未来每一次改动的回归。一个写完就不会再动的 skill,eval 是纯亏损。

// 一页选购卡

先做这三件:① claude plugin validate .claude/skills——五秒,先确认 frontmatter 没静默挂掉;② claude plugin eval init --bare <name> 生成骨架,把成功标准尽量写成 regex / file_exists / tool_used 这三种免费打分器,再 claude plugin eval . --runs 3 --max-cost-usd <你的上限>;③ 两周后跑一次 /skill-doctor,把从没被触发过的挑出来单独治 description。

按场景选路径:要一份能给人看的报表和 mean ± stddev → 装 skill-creator@claude-plugins-official;要进 CI、要 exit code → claude plugin eval --threshold(先确认 early access 已开通);要评的是纯 prompt 质量而不是 skill 触发 → promptfoo;只是想知道有没有变好 → skillOverrides: off 开两个新会话手动跑。

读数备查:--runs 默认 3 · --ablation 默认 with-without · --threshold 默认 1.0 · --judge-model 默认 haiku · 免费打分器 regex / tool_order / tool_used / file_exists,收费的 llm / baseline · 用例目录默认 evals/ · 结果默认落在 evals/results/<timestamp>/。

看报表的顺序:先看 stddev,再看 delta;delta 淹在 stddev 里就当没提升。两条臂都满分的题,删掉重写——它没有区分度。

// 术语表

baseline arm / ablation(基线臂 · 消融)
同一道题再跑一遍、但不给 skill 的那条对照。没有它,你量到的是模型本来就会的东西。
grader(打分器)
判定一次运行算不算通过的规则。程序判定的免费且无方差,LLM 判定的花钱且自带噪声。
LLM-as-judge
让另一个模型给输出打分。用于「专业」「清晰」这类没法写成正则的标准。
non-discriminating assertion(无区分度断言)
不管有没有 skill 都通过的断言。它让 delta 恒为 0,看起来像 skill 没用。
variance(方差)
同一配置多次运行的结果波动。agent 评估里方差通常很大,所以默认要跑 3 次并看 stddev。
with-only grader
只在带 skill 那条臂上有意义的打分器(如 tool_used: Skill)。它是触发指示器,不计入分差。

// 延伸阅读