你花了一个下午,写了个 Codex Skill。

它的任务很简单:每次 git add 之后,自动读一遍 diff,生成一条符合 Conventional Commits 规范的 commit message。你测了三遍,每次都完美通过。你很满意,把它塞进了项目仓库。

第二天早上,你打开终端,git add 了几个文件,然后让 Skill 跑。结果它输出了一行不知所云的英文,连 type: 前缀都没带。

你检查了 Skill 文件,没改过。检查了 prompt,没变。唯一的区别是:Codex 昨晚自动更新到了新版本。

这种事,每一个认真用过 Agent Skills 的人都遇到过。

问题:Agent Skills 是「薛定谔的靠谱」#

传统的软件测试,逻辑很清晰:你写了一个函数 add(a, b),你写了一个测试 assert add(2, 3) == 5。只要代码没改,这个测试明天跑、下个月跑、明年跑,结果都一样。

但 Agent Skills 不是这样。

Skills 本质上是一段自然语言指令加上一些工具调用规则。它的行为取决于 LLM 对它「理解」的程度。而 LLM 的理解能力,会随着模型版本、上下文窗口大小、甚至 prompt 里多一个逗号而产生微妙的变化。

更麻烦的是,很多 Skill 的「正确性」本身就是模糊的。什么是「好的 commit message」?什么算「正确的代码重构」?这些问题没有唯一正确答案,人看着觉得「差不多对」就行了。

这就造成了一个很尴尬的局面:你无法确定你的 Skill 是真的有用,还是碰巧在那个 prompt 上跑对了。

开源项目 Caliper 就是为解决这个问题而生的。它的核心理念简单直接:用统计学方法来衡量 Agent Skills 的可靠性。

Caliper 是什么#

Caliper 是一个专门为 AI Agent Skills 设计的功能测试框架。目前支持 Claude Code、Codex 和 Pi 三种 Agent 后端。

它的工作方式非常直观:

  1. 你定义一个「评测规格」(eval spec),描述 Skill 要完成什么任务、怎么判断成功
  2. Caliper 把这个任务跑 k 次(比如 3 次或 5 次)
  3. 统计成功率,得到一个 pass@k 分数
  4. 可选地,再跑一遍不带 Skill 的「基线」(baseline),看看 Skill 到底有没有提升效果

输出是这样的:

ID      Task                              k (3)   pass@k
task-1  Writes a conventional commit msg  3/3     100%     PASS
task-2  Generates a valid config file     2/3      96%     PASS

With skill     98%    ###################-
No skill       55%    ###########---------
Delta          +43%   ↑

数字比直觉诚实得多。有了 pass@k,你就不再靠「感觉这个 Skill 还行」来决策了。

两种评测方式:裁判打分 vs. 代码断言#

Caliper 提供了两种判断任务成功与否的方式,可以单独使用,也可以组合。

LLM 裁判(expect:#

你写一段自然语言描述,告诉一个 LLM「什么样算成功」。比如:

tasks:
  - name: Writes a conventional commit message
    prompt: "Summarize the staged git diff as a commit message."
    expect: >
      The response is a conventional-commit message: a concise subject
      line under 72 characters, followed by a body explaining why the
      change was made, not just what changed.

Caliper 会把整个会话记录(包括工具调用日志)发给另一个 LLM 当裁判,让它判断是否满足条件。

这种方式适合评判「质量」类任务:代码风格好不好、解释是否清晰、重构是否合理。这些任务没有标准答案,但 LLM 裁判可以做出相当靠谱的判断。

Python 断言(assert:#

对于有明确对错的事实性问题,直接用 Python 断言:

tasks:
  - name: Generates a valid config file
    cleanup: rm -f /tmp/app.config.json
    prompt: "Generate a config at /tmp/app.config.json with a 'port' of 8080."
    assert: |
      import json
      from pathlib import Path
      data = json.loads(Path("/tmp/app.config.json").read_text())
      assert data["port"] == 8080

文件有没有被创建、JSON 格式是否正确、端口号是不是 8080,这些事情不需要 LLM 来猜,代码判断更准确、更快、更稳定。

推荐的做法是:两个都用。 assert 验证事实,expect 验证质量。

从「写了一堆 Skill」到「验证了一堆 Skill」#

Caliper 推荐的完整工作流程是:

第一步:生成评测规格(grill-skill)

你不用手写 .eval.yaml。Caliper 内置了一个叫 grill-skill 的 Skill,它会读取你的 SKILL.md,然后像面试官一样问你几个问题:

  • 这个 Skill 最理想的成功场景是什么?(happy path)
  • 什么边界情况容易出错?(edge case)
  • 怎么故意刁难它?(adversarial)

问完之后,自动生成一个包含 3 个任务的评测规格。

第二步:跑评测(evaluate-skill)

caliper run my-skill.eval.yaml --k 3 --baseline

--k 3 表示每个任务跑 3 遍,--baseline 表示同时测一遍不带 Skill 的基线。

第三步:看报告,做决策

With skill     98%    ###################-
No skill       55%    ###########---------
Delta          +43%   ↑

如果 Delta 是负数或者接近零,说明这个 Skill 没有给 Agent 带来任何提升,可能是 prompt 写得太模糊,也可能是这个任务根本就不适合用 Skill 来解决。

如果 pass@k 不到 100%,Caliper 会保存每次尝试的完整记录(JSON),你可以去翻到底是哪次失败了、为什么失败,然后改进 Skill 或评测规格。

第四步:把 spec 文件提交到仓库

.eval.yaml 文件应该和 SKILL.md 放在一起,作为项目的一部分。这样任何贡献者都能跑同样的评测,保证 Skill 质量不退化。

pass@k 的统计逻辑:不是简单的「3 次跑过 2 次」#

Caliper 的 pass@k 计算有一个精妙的设计:它区分了「任务失败」和「基础设施故障」。

每次尝试的结果会被归类为以下几种:

结果类型含义计入 pass@k 吗
pass通过了所有判定✅ 成功
task_fail明确的任务失败✅ 计入分母
cheat读取了被禁止的文件✅ 计入分母
infra_error基础设施故障(网络、限流等)❌ 丢弃
timeout超时❌ 丢弃
judge_error裁判本身出了错❌ 丢弃

这个设计很重要。如果你跑 3 次,其中 1 次因为网络超时没跑完,直接算「0/3 = 0%」显然不公平。Caliper 会把它标记为 infra_error,不参与 pass@k 的计算,但会在报告中标注「有 N 次无效尝试」。

实际的 pass@k 公式是统计上的无偏估计:

usable  = pass + task_fail + cheat
pass@k  = 1 - (1 - pass/usable)^usable

这比简单的「通过次数 / 总次数」更准确,尤其在 k 比较小的时候。

多 Agent 对比:同样的 Skill,不同的 Agent,谁更稳?#

Caliper 的 Agent 后端和裁判后端是解耦的。你可以:

# 用 Claude Code 跑 Skill,用 Codex 当裁判
caliper run my-skill.eval.yaml --model claude-code --judge-model codex

# 反过来
caliper run my-skill.eval.yaml --model codex --judge-model claude-code

这意味着你可以用同一个评测规格,对比同一个 Skill 在不同 Agent 上的表现。

同一个 Skill,在 Claude Code 上 pass@k 可能 95%,在 Codex 上只有 72%。这个信息非常有价值:它告诉你这个 Skill 是「通用」的,还是高度依赖某个特定 Agent 的 prompt 理解方式。

如果你想把 Skill 发布到 skills.sh 这样的公共市场,pass@k 分数就是对用户最诚实的承诺。

隔离环境:每次尝试都是「第一次」#

Caliper 的一个关键设计是:每次尝试都运行在独立的临时 HOME 目录中,不带任何会话历史。

这意味着 Agent 不能靠「记住上次是怎么做的」来作弊。每次都是从零开始,就像用户第一次使用这个 Skill 一样。

这也解释了为什么同一个任务跑 3 次可能得到不同的结果:LLM 的非确定性 + 隔离环境 = 真实世界的行为分布。

我的一些观察#

用了 Caliper 之后,我对 Agent Skills 的理解发生了几个变化:

1. 「感觉好用」不等于「真的好用」

我有好几个 Skill,用了几个月,一直觉得它们很靠谱。跑了一遍 Caliper,发现其中两个的 baseline Delta 接近零,也就是说 Agent 不带 Skill 也能完成同样的任务。这两个 Skill 本质上只是在浪费 token 和上下文窗口。

2. 评测规格本身就是最好的文档

expectassert 的过程,迫使我精确地定义「什么算成功」。这个思考过程比 Skill 本身还有价值,它让我对任务的边界条件有了更清晰的认识。

3. 模型更新之后,一定要重新跑一遍

Codex 和 Claude 的模型更新频率越来越高。每次更新之后,重新跑一遍 Caliper 评测,能帮你及时发现哪些 Skill 因为模型行为变化而失效了。这比等用户来报 bug 主动得多。

总结#

Caliper 解决的问题很朴素:如果你不能衡量它,你就不能改进它。

Agent Skills 正在从「个人玩具」走向「团队基础设施」。当你的团队有十几个 Skill,其中一半是三个月前写的,另一半是实习生写的,你就必须有一个机制来保证它们真的在起作用。

pass@k 就是这个机制。它不完美,LLM 裁判也可能误判,但「有一个有噪声的指标」远比「完全靠感觉」要好。


参考来源:Caliper — Reliability testing for agent skills by edonadei