DeepSeek Harness Hub
← 返回列表

edonadei/caliper

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
需源码安装

Caliper:了解你的 agent 技能是否真的有效

暂不能直接安装(需源码编译或环境不满足):仓库缺少 package.json,无法用 dsh 插件安装命令安装。 · 最近上游提交 2026/9/17 · 已提供中文文档

在启用和未启用你的技能、MCP 和规则的情况下运行你的真实 agent。看看哪些真正有帮助,以及它们在 token 上的成本。支持 Claude Code、Codex、Pi 和 Hermes。

综合分
62.1
GitHub 分
62.1
用户评分
★ Stars
166
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add edonadei/caliper
仓库缺少 package.json,无法用 dsh 插件安装命令安装,改用 GitHub 源安装
🟢实装验证通过· 2026/9/18
由 dsh-plugin-verify(GitHub Actions)在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查需源码安装

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

npm 包caliper(未发布到 npm,仅可源码安装)
Node 引擎未声明 engines.node
dsh CLI 依赖未声明 dsh 版本约束
入口文件缺少入口声明

仓库缺少 package.json,无法用 dsh 插件安装命令安装

验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/17 08:24:28

用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

Caliper:了解你的 agent 技能是否真的有效

PyPI
Python
Skills

Caliper 是一个用于 agent 技能的轻量级评估工具。写一份简短的规范,描述“好”是什么样子,运行它,然后得到一个可以持续跟踪的成功率。它与你已经在使用的 agent 兼容:Claude Code、Codex、Pi 或 Hermes。Caliper 会把技能安装到 agent 查找技能的位置,并让 agent 自行选择。

教你的 agent 进行评估:

npx skills@latest add edonadei/caliper

或者自己运行:

Run the evaluation.
caliper run commit-commands.eval.yaml --k 3

The control subject: your skill is not there.
caliper run commit-commands.eval.yaml --k 3 --ablate commit-commands

Compare the runs. Did your skill improve it?
caliper compare .caliper/results/commit-commands/.json .caliper/results/commit-commands/.json

你编写一份规范,也就是一个描述“有效”意味着什么的 YAML 文件。可以手写,也可以让 /grill-skill 为你生成。--ablate 会在移除该技能的情况下运行相同的任务——声明的 MCP server 也可以用同样方式消融——然后 caliper compare 会逐任务对比两次运行:

caliper compare, without commit-commands vs full neighbourhood on commit-commands: both tasks go 33.3% to 100.0% (+66.7%); tokens 290K to 180K, wall 1m 1s to 42s

Agent 技能很难测试。一个今天在你的机器上、针对这个提示词有效的技能,可能会在模型更新或一行提示词修改后,明天就失效。Caliper 让可靠性变得可衡量:定义成功是什么样子,反复运行该技能,并得到一个可以随时间跟踪的成功率。

使用 Caliper 来回答诸如以下问题:

- 换了新模型后,我的 agent 还能保持同样的表现吗?
- 我修改提示词后,技能是否有所改进?
- 我的技能是否会在该触发时触发,并在不该触发时保持安静?
- 这个技能值得占用上下文吗?还是基础 agent 在没有它的情况下也能通过?
- 它是否仍然能通过上周通过的那些工作流?
- 哪个 agent(Claude Code、Codex、Pi 或 Hermes)运行这个技能更可靠?

快速开始

路径 A:Agent 驱动(让你的 agent 来操作)

1. 安装技能

npx skills@latest add edonadei/caliper

2. 交互式生成规范

在你的 agent(Claude Code 或 Codex)中:

/grill-skill ./my-skill/SKILL.md

grill-skill 会读取你的 SKILL.md,向你提问,并写出一个包含 3 个任务的 .eval.yaml(正常路径、边界情况、对抗性情况)。

3. 运行并测量

/evaluate-skill run my-skill.eval.yaml --k 3

浏览过往运行记录:

/evaluate-skill list
/evaluate-skill report my-skill

路径 B:CLI(自行运行)

1. 安装 CLI

pipx install caliper-eval   # requires Python 3.10+

2. 编写一份 spec

commit-writer.eval.yaml
skills:
- ./SKILL.md                     # the skill under test
- ../changelog-writer/SKILL.md   # a neighbour it might steal work from

tasks:
Autorater: the LLM judge reads the transcript and decides
- 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.
activates: [commit-writer]

Script execution: a deterministic Python assertion
- name: Keeps the subject line under 72 characters
prompt: "Commit the staged changes."
assert: |
import subprocess
subject = subprocess.run(
["git", "log", "-1", "--pretty=%s"], capture_output=True, text=True
).stdout.strip()
assert len(subject)  for a run to diff against

4. 读取输出
commit-writer 在 k=3 时的 caliper 运行结果。三行:'Writes a conventional commit message' 通过 3/3(100.0%,80K tokens),act 列显示绿色对勾;'Keeps the subject line under 72 characters' 2/3(66.7%,PARTIAL,84K tokens),显示绿色对勾;'A release summary belongs to changelog-writer' 没有执行分数,act 列显示红色叉号,并显示 'trigger only'。在 2 个已评分任务上得分 83.3%。在 3 个已断言任务上激活率 77.8%。每个技能的表显示,对于每个技能,9 次尝试中有多少次需要它以及它触发了多少次:commit-writer 在 9 次中有 6 次被需要,在这些尝试中触发了 6/6(100.0%),但也在不需要它的尝试中触发了 2/3(66.7%);changelog-writer 在 9 次中有 3 次被需要,仅触发了 1/3(33.3%),且从未在不需要时触发(0/6,0.0%)。commit-writer 正在接管本应属于 changelog-writer 的提示。下方的失败面板显示了断言错误以及 commit-writer 在 changelog 提示上激活的尝试

报告以每个任务的失败面板结尾:对于每次未通过的尝试,显示输出以及断言或 autorater 给出的原因。完整结果也会以 JSON 形式保存在项目的结果根目录下的 .caliper/results// 中,供你检查或之后运行 caliper compare。--verbose 会添加 pass@k 和 pass^k 列(两者均从原始比率推导而来),并为每个任务添加一个面板。

不确定该在 spec 中放什么?

Eval Starter Pack 包含四个可复制粘贴的
模板,每个都能捕获一种真实的 agent 失败(虚假成功、工具误用、
失控循环、提示回归)。每个模板都能按原样针对一个
捆绑示例运行通过,然后只需编辑两三行带注释的代码,即可指向你自己的技能。

工作原理

.eval.yaml spec
│
▼
Harness  ──── 针对 agent(Claude Code / Codex / Pi / Hermes)运行你的技能
│
▼
Judge   ──── LLM autorater 和/或确定性的 Python 断言
│
▼
成功率 + 保存的转录记录

每次尝试都在一个隔离的临时 home 中运行,没有会话历史。结果会保存为 JSON,你可以之后检查并对比差异。

Agent 技能

该仓库附带两个 agent 技能。使用以下命令安装两者:

npx skills@latest add edonadei/caliper

evaluate-skill:运行和管理评估

在你正常工作流中直接创建、验证、运行和汇总评估,无需单独的终端。如果缺少 Caliper,该技能会自动安装它。

然后在 Claude Code 中使用它:

/evaluate-skill run my-skill.eval.yaml --k 3
/evaluate-skill validate my-skill.eval.yaml

或者在 Codex 中:

Use the evaluate-skill skill to run my-skill.eval.yaml with k=3 and summarize the result.

grill-skill:交互式创建评估
还没有评估?grill-skill 会引导你创建它们。它读取你的 SKILL.md,就良好行为应该是什么样对你进行访谈,并生成一个包含 3 个任务的规格(正常路径、边界情况、对抗性情况)。然后它运行评估并循环:k=1 用于验证,k=3 用于测量,在你提交之前进行一次消融运行以进行对比。

/grill-skill ./my-skill/SKILL.md

如果你已经在技能所在目录中,则无需提供路径:

/grill-skill

如果技能旁边已经存在一个 .eval.yaml,grill-skill 会读取现有任务,并针对缺口对你进行访谈,而不是从头开始。

核心概念

| 术语 | 它是什么 |
|---|---|
| Spec(规格) | 一个 .eval.yaml 文件,描述要运行的技能、评判器和任务 |
| Backend(后端) | 执行技能的 CLI 智能体(claude-code、codex、pi、hermes) |
| Judge(评判器) | 决定通过/失败的东西:读取对话记录的 LLM(expect:)、Python 断言(assert:),或两者兼有 |
| success rate(成功率) | 主要分数:运行 k 次,测量单次运行成功的频率(pass@k/pass^k 是次要视图,在 --verbose 下显示) |
| Neighbourhood(邻域) | 一个规格所声明的一组技能(skills:)。全部安装,均不预加载,且全部可断言。这是你的 description 必须赢得的竞争 |
| Activation(激活) | 智能体选择加载某个技能。用 activates: 进行断言,并在独立于成功率的计分板上单独计分 |
| Ablation(消融) | 在移除某个已声明的技能或 mcp: 服务器的情况下重新运行相同任务(--ablate),以证明它确实在起作用。为裸智能体命名每一个技能。它是任务的一个属性,所以运行一次并持续与之对比 |
| Attempt(尝试) | 单个任务的一次隔离运行(全新的临时 home,无会话历史) |

选择引擎

引擎(后端 + 模型)是一个运行时轴,而非规格字段。规格描述测试什么以及如何评判成功,而你在调用时选择运行并评分它的智能体。两者都默认为 claude-code;用 --model / --judge-model 选择不同的:

caliper run my-skill.eval.yaml                          # claude-code(默认)
caliper run my-skill.eval.yaml --model codex            # codex,其默认模型
caliper run my-skill.eval.yaml --model codex:gpt-5.6-sol
caliper run my-skill.eval.yaml --model pi --judge-model claude-code

| 后端 | 要求 | 最适合 |
|---|---|---|
| claude-code | 已安装并认证 Claude Code CLI | 测试 Claude Code 斜杠命令技能 |
| codex | 已安装 Codex CLI(npm install -g @openai/codex) | 测试 Codex 技能 |
| pi | 已安装并认证 pi CLI(npm install -g @earendil-works/pi-coding-agent) | 测试 pi 技能(agentskills.io) |
| hermes | 已安装并认证 Hermes Agent CLI(Nous Research) | 在 Hermes 上测试技能;hermes:/ 选择模型 |
Caliper 仅通过 CLI 代理运行技能,因此每个后端都能实际加载并运行技能。不存在直接 API 后端:若要按 API 计费方式运行,请使用 API 密钥(例如 ANTHROPIC_API_KEY / OPENAI_API_KEY)配置这些 CLI 之一,而不是选择单独的后端。

技能引擎和评判引擎是相互独立的:你可以通过将 --model 与 --judge-model 配对,用 Claude 评判器测试 Codex 技能,或任意其他组合。

Claude Code 设置

安装并认证 claude CLI。--model claude-code 使用你现有的 Claude Code 认证,无需额外配置。

Codex 设置

npm install -g @openai/codex
codex login

--model codex 会调用 codex exec。如果安装了 Codex 桌面应用,Caliper 会优先使用应用自带的二进制文件,而不是 PATH 上的 codex。设置 CODEX_CLI_PATH 可强制使用特定二进制文件。

pi 设置

npm install -g @earendil-works/pi-coding-agent
pi   # then authenticate (e.g. /login for a subscription provider, or set the provider API key)

--model pi 会运行 pi --print --mode json,并将声明的技能安装到其代理目录下,pi 会在那里发现它们(其 --skill 标志是预加载,而 caliper 从不这样做;pi 自有的 --no-skills 之所以存在,是因为发现是默认行为)。它会复用你的 ~/.pi/agent 认证和设置;设置后,--model pi: 中 :model 部分会覆盖 pi 配置的默认值。设置 PI_CLI_PATH 可强制使用特定二进制文件。注意:pi 的内置默认提供商是 google,因此在不指定模型的情况下运行 --model pi 时,依赖你的 pi 配置来解析一个你已认证的提供商。

Hermes 设置

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
hermes login   # authenticate
hermes model   # pick a default model/provider you have credits for

Hermes 是一个有状态、始终在线的代理(持久记忆、人格、自动生成的技能),因此 Caliper 会将其规范化为中性代理,以使其得分与其他后端具有可比性:每次尝试都在一个隔离的 HERMES_HOME 中运行,其中仅植入你的 ~/.hermes 认证/配置(绝不包含 SOUL.md/MEMORY.md),并带有 --ignore-rules 和 --yolo(这样审批提示不会挂起非交互式 oneshot),并且只安装规范中声明的技能(其 --skills 标志被记录为预加载,因此 caliper 不会传递它)。--model hermes 会运行 hermes -z(oneshot),然后运行 hermes sessions export 以恢复完整的工具调用轨迹;--model hermes:/(例如 hermes:anthropic/claude-opus-4-8)会选择模型,否则使用你的 ~/.hermes/config.yaml 默认值。将其指向你有额度的提供商。如果运行因未选择模型或提供商登录失效而失败,Caliper 会提示你运行 hermes model。设置 HERMES_CLI_PATH 可强制使用特定二进制文件。Hermes 会自我更新(hermes update),因此它不属于 caliper update-cli 的一部分。

检查已安装的 CLI 版本:

caliper update-cli --check

推荐工作流

1. 为你关心的某一个行为创建一份 spec。
2. 在迭代 spec 时使用 --k 1 运行。
3. 为 LLM 评判器可能猜错的事实(文件、JSON、命令输出)添加 assert:。
4. 一旦任务稳定,就改用 --k 3 或更高。
5. 用 --ablate (或 --ablate mcp:)运行一次,并对两次运行执行 caliper compare,以证明该定制确实产生了差异。该对照组是任务的属性,因此保留它,并在 skill 变化时重新与之做 diff。
6. 将 spec 与 skill 一起提交,这样贡献者就能运行相同的 eval。

/evaluate-skill run my-skill.eval.yaml --k 3 --verbose

Spec 格式

要搭建一份 spec 的脚手架,请使用 evaluate-skill
或 grill-skill skill,或者手写
下面的 YAML。

skills:                         # 安装在 agent 查找 skills 的位置,
- ./SKILL.md                  #   绝不会粘贴进 prompt
- ../evaluate-skill/SKILL.md  # 路径来源:该文件当前的内容
- repo: vercel-labs/agent-skills   # git 来源:caliper 会克隆它
ref: a1b2c3d                     #   可选——省略则跟踪默认分支
path: skills/tdd/SKILL.md        #   可选——默认为根目录下的 SKILL.md
完全省略 skills: 即为裸 agent

注意:没有 backend/model 或 judge: 块。引擎是一个运行时
轴:在运行时传入 --model / --judge-model(默认:claude-code)。

sandbox:
extra_path:
- ./bin                     # 在每个 attempt 内前置到 PATH
forbidden_files:            # 额外的模式;spec 本身以及任何
- "./answers/."          #   .caliper/ 目录始终被禁止

mcp:                            # 可选:agent 可以使用的 MCP 服务器
weather:                      # 服务器名称 → transcript 中的 mcp__weather__ 调用
command: python3            # harness 启动的本地 stdio 服务器
args: [./servers/weather.py]
env:
API_TOKEN: ${MCP_API_TOKEN}   # ${VAR} 在运行时从你的 shell 解析
gdrive:                       # 通过 HTTP 访问的远程(托管)服务器
type: http                  # http 或 sse
url: https://mcp.example.com/gdrive
headers:
Authorization: Bearer ${GDRIVE_TOKEN}   # ${VAR} 在运行时解析

tasks:
- name: Short task name
setup:       # 可选,在每次 attempt 之前运行
cleanup:     # 可选,始终在每次 attempt 之后运行
prompt:
expect:
assert: |
可选的内联 Python 断言
assert True

- name: Task with external assertion script
prompt: "Generate a report"
assert: ./assertions/check_report.py

- name: A neighbour's prompt: yours must not hijack it
prompt: "我的提交信息技能有多可靠?运行 10 次。"
activates: [evaluate-skill]   # 恰好是这些技能,不能有其他

- name: 无关工作,预期静默
prompt: "在整个仓库中将 resolved_model 重命名为 engine_model。"
activates: []                 # 不应触发任何内容

每个任务至少需要 expect、assert 或 activates 中的一项。任务 ID 会自动分配为 task-001、task-002,依此类推。

要升级现有 spec? 在 v0.10 中,skill: 变成了 skills:。请参阅 docs/MIGRATING-to-skills.md 获取一份简短清单,其中包括查找替换会漏掉的两个陷阱(prompt:/expect:/assert: 字符串中过时的 skill.path,以及那些点名了它们正在测试的技能的 prompt)。

skills:,以及它的邻近项

每个条目都按其 frontmatter 中的
name: 安装到 agent 自己的 skills 根目录下,并且不会预加载任何内容。各条目是平级的:没有哪个条目是“被测技能”,因此 activates: 始终显式列出技能名称。

这个集合是封闭的。agent 只能看到这些技能,别的什么都看不到,这正是让激活成为一项测量而非猜测的原因。这也意味着你没有声明的技能永远无法激活:如果你的技能委托给另一个技能,也要声明那一个,并枚举整条链(activates: [mine, helper]),这样“它真的委托了吗?”就变得可断言了。

一个技能必须是某个目录中的 SKILL.md,并带有 frontmatter 的 name: 和
description:。单独的斜杠命令 .md 会被拒绝:没有名称也没有描述,agent 就没有任何可发现的内容。

路径来源与 git 来源

一个条目有两种写法之一,而形态就是区别所在:

| 条目 | 含义 |
|---|---|
| - ./SKILL.md | 路径来源——你磁盘上的一个文件,无论它在运行时说什么 |
| - {repo: …, ref: …, path: …} | git 来源——caliper 会克隆它,并将 ref: 解析为一个提交 |

git 来源让你可以为你的 description 提供真正的竞争对象,而不必把别人的仓库 vendor 到你的仓库里。一个条目就是一个技能;共享同一仓库和提交的条目共享一次克隆,因此从一个包中点名五个技能只需五个条目和一次获取。

repo: 接受任何 git 能克隆的内容。裸的 owner/name 会展开为
https://github.com/owner/name;URL、scp 风格的 git@host:owner/name,或
文件系统路径会原样传递。要通过相对路径指向一个本地仓库,请写成 ./owner/name——开头的 ./ 就是用来把它与简写区分开的。

ref: 是可选的,省略它会跟踪默认分支,因此它会*
移动。这是被允许而非被禁止的,因为 caliper 会记录它解析到的提交,而 compare 会在它移动时告诉你——见下文。固定一个提交仍然值得:固定后的条目一旦获取就完全离线,而未固定的条目每次运行都要付出一次 git ls-remote 的代价。

caliper run 会在第一次尝试前获取,因此一个错误的 repo: 会让你付出
没什么。caliper validate 从不接触网络:它能从缓存中解析 git 源时就解析,其余的则报告为未缓存(并且当这意味着它无法检查你的 activates: 名称时会说明这一点)。

检出内容落在 ~/.cache/caliper/skills/(或 $XDG_CACHE_HOME/caliper/…),以解析后的提交为键——因此它们是不可变的,在所有引用它们的 spec 之间共享,并且可以安全删除。设置 CALIPER_CACHE_DIR 可将它们放到别处。

如果某个 git 源无法获取且未缓存,运行会拒绝——一个静默缺失的成员会让你的技能与并不存在的竞争对手进行衡量。如果它已缓存但远程不可达,运行会使用缓存并说明这一点。

技能漂移

caliper compare 会报告任何在两次运行之间文本发生变化的成员。一个发生移动的 git 源会收到警告:spec 说明了它的字节来自哪里,而你正在读取的差异是被混淆的。一个发生移动的 path 源会不加警告地显示——那通常正是这次运行所要衡量的编辑。

⚠ tdd changed between runs — git source, a1b2c3d → e4f5g6h; pin ref: to hold it fixed
my-skill changed between runs — path, 4fc7951 → bcbcbde

这是成员关系不变时文本的变化。成员关系的变化——安装了不同的技能——是另一个独立的邻域警告。

activates::agent 是否伸手去用了它?

activates: 断言每次尝试中加载的技能的精确集合。

| 形式 | 含义 |
|---|---|
| (省略) | 未断言;该列仍会显示加载了什么,以暗色显示 |
| activates: [a] | 恰好 a 触发,没有其他 |
| activates: [a, b] | 两者都触发,这正是委托型技能断言其链的方式 |
| activates: [] | 什么都没触发;静默成立 |

一个带有 activates: 而没有 expect:/assert: 的任务是一个触发探针:它只询问 agent 伸手去用了什么,完全跳过评判器(因此比执行任务便宜得多),并报告为 trigger only 而不是零。将它用于邻域和静默探针,那里没有值得评分的工作。

激活在它自己的记分板上评分,绝不混入成功率。失败的 description 和失败的正文要在不同的地方修复,所以把两者混在一起的一个数字既指不出前者也指不出后者。

MCP 服务器(mcp:)
可选的 mcp: 块声明了被测 agent 可以使用的 MCP 服务器。它是为本次评估授予 agent 的一项能力,属于运行环境的一部分,就像 sandbox: 一样,因此它位于 spec 中,而不是隐藏在某个标志之后。它是一个以服务器名称为键的顶层映射(与 sandbox: 和 skills: 同级,并且无论评估是否声明了任何 skill 都适用)。每个服务器的工具在 transcript 中会以带命名空间的调用形式出现,expect: 评判器可以对其进行验证(在 claude-code 和 codex 上为 mcp____,在 hermes 上为 mcp__),因此如果该 spec 要在多个引擎下运行,请围绕工具的行为来措辞 expect:,而不是某个后端的精确拼写。

服务器要么是本地(stdio),即由 harness 启动的 command,要么是远程(type: http 或 sse),即位于 url 的托管端点,这也是大多数连接器(Google Drive、Notion 等)使用的形式:

mcp:
weather:                      # local stdio server (the default transport)
command: python3            # required: the local stdio command to spawn
args: [./servers/weather.py]  # optional
env:                        # optional
API_TOKEN: ${MCP_API_TOKEN}
gdrive:                       # remote server
type: http                  # required for remote: http or sse
url: https://mcp.example.com/gdrive   # required for remote
headers:                    # optional: usually auth
Authorization: Bearer ${GDRIVE_TOKEN}

- claude-code、hermes 和 codex。 这三者都会接入 mcp::claude-code 支持 stdio 和远程(HTTP/SSE);hermes 支持 stdio 和远程头部认证(它会在隔离的 HERMES_HOME 中将该块转换为其原生的 mcp_servers 配置,在 harness 边界解析 ${VAR},并覆盖你的任何个人服务器,以便某次尝试只能看到所声明的集合);codex 以相同方式支持 stdio 和远程头部认证,将该块转换为隔离的 ~/.codex/config.toml 中的 [mcp_servers.] 表(stdio 为 command/args/env,远程为 url 加上由边界解析后的字面量组成的静态 http_headers 映射;codex 从其唯一的可流式 HTTP 传输中根据 url 推断,因此 http/sse 会合并到它上面),在边界解析 ${VAR},并替换你真实配置中的任何个人服务器,以便某次尝试只能看到所声明的集合。hermes 或 codex 不支持远程 OAuth,因为它需要 harness 无法驱动的交互式浏览器流程。在不支持 mcp: 的后端上运行声明了 mcp: 的 spec 是硬错误,而不是静默无操作。pi 不会也将不会原生支持 mcp::其 agent 在设计上就没有 MCP。与其使用 MCP,不如将该能力暴露为你的 skill 所驱动的 CLI 工具(一个带有 README 的 skill)或 pi 扩展,或者在 claude-code/hermes/codex 上运行该评估。在 pi 上运行 mcp: spec 会失败,并给出该指引。
- 传输方式由 type: 设置。 省略(或 stdio)表示本地 command;http/sse 表示远程 url。这两组字段互斥:stdio 服务器不能设置 url/headers,远程服务器不能设置 command/args/env。
- 密钥不进入规范文件。 stdio 的 env:、远程的 headers: 或远程的 url: 中的值可以以 ${VAR} 的形式引用宿主环境变量;它在运行时从你的 shell 中解析(绝不会写入提交的规范文件),未设置的变量会导致运行失败并给出清晰的错误信息。
- 服务器名称必须匹配 [A-Za-z0-9_-]+,以便后端的命名空间工具句柄(mcp____ / mcp__)格式正确。

caliper validate 会检查 mcp: 块,并报告格式错误的条目(名称错误、未知键、未知 type、stdio 服务器缺少或为空的 command,或远程服务器缺少 url)。

已声明的服务器可以像技能一样为单次运行被消融:caliper run  --ablate weather 会将其从 harness 配置中排除,因此 agent 永远不会看到它的工具定义。如果技能和服务器声明了相同的名称,请加以限定——服务器用 --ablate mcp:weather,技能用 --ablate skill:weather;有歧义的裸名称会被拒绝,而不是被猜测。运行会记录它移除了什么(RunMeta.ablated,如 mcp:weather)以及它实际运行所用的服务器(RunMeta.mcp_servers),因此 caliper compare 可以从一个可检查的标记来标注这一对。即使消融移除了所有服务器,仍会将尝试隔离到零服务器,而不是回退到你的环境配置,作者编写的 mcp: {} 也是如此。

评判

LLM 自动评分器(expect:)

评判引擎读取完整的尝试记录,并判定是否满足 expect 条件。当后端捕获工具调用轨迹时(Claude Code、Codex、pi、Hermes),这些轨迹会被包含在内,因此评判器可以验证诸如“agent 使用了工具 X”之类的事情,而不必仅依赖最终文本。

评判引擎在运行时选择,默认为 claude-code;用 --judge-model 将其指向不同的 agent(例如 --judge-model codex),独立于技能的 --model。

确定性断言(assert:)

Python 断言在本地运行。对于 LLM 评判器可能会猜测的事实,请使用这些断言:

- 文件存在 / 文件内容精确匹配
- JSON / schema 有效性
- 命令输出
- 图像或截图
- 仓库状态

tasks:
- name: Writes an output file
cleanup: rm -f /tmp/out.txt
prompt: "Write hello world to /tmp/out.txt"
assert: |
from pathlib import Path
path = Path("/tmp/out.txt")
assert path.exists(), "Output file was not created"
assert path.read_text().strip() == "hello world"

当 expect 和 assert 同时存在时,两者都必须通过。

CLI 参考

| 命令 | 描述 |
|---|---|
| caliper run  | 运行评估规范 |
| caliper validate  | 验证规范文件 |
| caliper list [spec] | 列出 spec 和已保存的运行。按 spec 分组,每一行带有其 Run id 以及该运行 ablate 了哪些 subject —— 借此找到用于对比的对照组 |
| caliper report  | 重新渲染已保存的结果 |
| caliper compare   | 逐任务对比同一 eval 的两次已保存运行。每一侧可以是 spec 名称(该 spec 的最新运行)或 results-JSON 路径;两者必须是不同的运行 |
| caliper update-cli [backend] | 检查或更新已安装的 agent CLI 版本 |

结果保存位置

运行结果保存在一个 results root 下 —— 一个包含 .caliper/ 的目录 ——
并且每条命令解析到同一个位置:从你的工作目录向上查找最近的 .caliper/,
且不离开 git 仓库。项目的首次运行会在仓库根目录创建它。

my-project/            /.json 落在这里
├── .git/
├── .caliper/
└── evals/
└── my.eval.yaml     逐任务地对比两次已保存的运行,因此你不必
手写 JSON 脚本来回答“这个改动是否导致了回归?”。
bash
每个 spec 的最新运行(裸 spec 名称解析为其最新运行)
caliper compare commit-simple-full commit-simple-short

通过指向其结果 JSON 来固定特定运行
caliper compare .caliper/results/demo/2026-07-01T10-00-00Z.json \
.caliper/results/demo/2026-07-02T09-00-00Z.json

用于发布 / 不发布决策的机器可读 diff
caliper compare A B --format json

每个位置参数(A、B)的寻址方式与 report 的参数完全相同:一个 spec
名称(解析为其最新运行)或指向结果 JSON 的路径。还有
没有 --run-a/-b 标志。要固定一次历史运行,请指定其 JSON 路径。

caliper 对两次 commit-simple 运行的比较:commits cleanly 稳定保持在 100%,handles conflict 从 100.0% 回退到 20.0%(-80.0%),pushes upstream 变为未测量;1 个回退、1 个未测量,且两侧都有未匹配的任务

如何解读这份差异:

- 每一行读作 before → after。 运行名称只在表头中给出一次
(消融对会标题为 without  → full neighbourhood),因此
没有 A/B 图例。
- 任务按名称匹配,所以重新排序无关紧要。只出现在一次运行中的任务
会列为 unmatched,并被排除在增量之外。
- Δ 是 after − before,而标题中的 Δ (matched) 只对两侧都测量到的
任务取平均,因此严格保持同类比较。负的 Δ 会渲染为红色,并标记为
regression。
- 不可用的尝试不能伪造损失。 某一侧没有可用尝试
(rate-limit / timeout / judge error)时显示 —,并且永远不会计为回退。
- Token 和 wall-clock 增量是次要的,永远不算回退:下降为绿色
(更便宜),上升为红色(需要权衡的取舍)。只有分数会输入
has_regression。

--format json 会序列化完整比较结果(每个任务的分数、增量、
回退标志、未匹配列表、警告、skill_drift,以及每一侧的使用量)
以便脚本处理。每个 skill_drift 条目都带有成员的 name、
source_kind,以及两侧的 a_ref/b_ref —— 因此脚本可以看到
每个成员的漂移,包括那些不会引发警告的路径来源成员。

评分

每次尝试都带有一个有类型的 outcome,因此基础设施和评判器噪声
不会被计为任务失败:

| Outcome | 含义 | 是否计入分数? |
| --- | --- | --- |
| pass | 满足了任务的评判器 | ✅ 成功 |
| task_fail | 技能确实未能完成任务 | ✅ 尝试 |
| cheat | 检测到读取了禁止文件 | ✅ 尝试 |
| infra_error | harness 失败:非零退出,或检测到 rate-limit / spending-cap | ❌ 不可用 |
| timeout | 超过时间预算且没有结果 | ❌ 不可用 |
| judge_error | 评判器没有产生裁决(无法解析 / 出错的 autorater) | ❌ 不可用 |
| not_checked | 任务没有编写 expect:/assert:,因此它是一个触发探针 | ⊘ 未询问 |

not_checked 是唯一两者都不是的 outcome:它像不可用尝试一样从分母中移除,
但并没有出错,因此永远不会被报告为错误,其 token 也不会被计为浪费的开销。

主要指标是 原始成功率:单次运行成功的频率,
在 可用 尝试(获得了公平机会的尝试)上计算。不可用
尝试会从分母中移除,并作为单独的 “N unusable” 计数报告:

usable  = pass + task_fail + cheat
score   = successes / usable                # 原始比率;若 usable == 0 则为 None

保留了两个次要视图,供需要的人使用(在 --verbose 下显示,
并在 JSON 中每个任务上以 pass_at_k / pass_hat_k 呈现):

pass@k  = 1 - (1 - score) ^ usable   # P(k 次中至少 1 次通过)
pass^k  = score ^ usable             # P(k 次全部通过)

该看哪一个取决于该技能的实际使用方式:

| 你要问的问题 | 指标 | 对于 1/3 的技能(k=3) |
| --- | --- | --- |
| 单次运行有多可靠?(默认)* | 成功率 | 33% |
| 如果我重试最多 k 次并保留任意一次成功,我能得到一次成功吗? | pass@k | 70% |
| 它能在每一次运行中都成功,毫无例外吗? | pass^k | 4% |

当重试成本低且你会保留成功的那次运行时,使用 pass@k;这是
乐观视角,始终 ≥ 原始比率。当技能无人值守运行且一次失败就会中断
链条时,使用 pass^k;这是严格视角,始终 ≤ 原始比率。Caliper
以原始比率为主,因为 pass@k 会美化不稳定的技能(1/3 → 70.4%)。

聚合值是任务成功率的平均值,跳过没有可用尝试的任务。要获得相对于裸
agent 的差值,请用 --ablate 运行相同的任务,并对两次保存的运行执行
caliper compare。

--fail-fast N 会在某个任务连续出现 N 次 infra_error 或 timeout
结果后停止为该任务调度新的尝试(默认 0 表示运行全部 k 次)。提前停止
的任务显示为 ABORTED;如果所有已完成的尝试都不可用,其 score
保持为 null,并在聚合中被跳过。

并行与停止运行

--workers 统计的是尝试次数,而不是任务数:每个(任务,尝试)对
都独立调度,因此在单任务规格上 --k 10 --workers 4 会一次运行四次
尝试。它们按轮询顺序排列——每个任务的第 1 次尝试,然后是每个任务的
第 2 次尝试——因此提前停止的运行会留下每个任务较浅的样本,而不是
前几个任务的完整样本。例外是 --fail-fast N,它保持每个任务的尝试
按顺序进行——它统计的连续次数只有在顺序执行时才有意义——同时仍然
并行运行不同的任务。请谨慎提高 --workers:并发 agent 会共享你的
上游速率限制,而被限流的尝试会以 infra_error 的形式落地,既花钱又
什么都测不到。

被中断的运行在读取它的任何地方都会被标记:caliper list 用 ⊘ 标记
其分数,caliper compare 会在任一侧提前停止时发出警告,因为较浅的
样本本身就可能改变差值。

被限流的尝试会被重试,而不是计入。当提供商返回 429 /
overloaded / 503 时,那次调用什么都没测到,因此 caliper 会重试两次
(2 秒后 4 秒,带抖动),并将结果记录为带 retries 计数的一次
尝试——分数的分母是尝试次数,绝不是生成次数。任何发生过重试的运行
都会在其用量摘要下说明。有两种失败被有意地不
重试:一次超时(没有任何迹象表明下一次启动会更快)和一次裸崩溃
(重试其中一个会掩盖一个可复现的缺陷)。

支出上限会停止运行。 上限或用量限制不会在最后一次尝试之前解除,因此 caliper 会中止,而不是把剩余的每一次尝试都花在撞同一堵墙上——它会保存已经运行的内容,并将上限报告为原因。--fail-fast 仍然按尝试次数计数:一次重试的尝试就是一次尝试,因此该标志的启动次数开销可能达到过去的三倍。

Ctrl-C 会停止运行但不会丢失它。 第一次中断会杀死正在进行的 agent,跳过尚未开始的尝试,并将已经运行的一切保存为普通的运行文件——按其已有的尝试进行评分,RunMeta 中带有 interrupted: true,报告上有一行 interrupted:。退出码为 130。被中断本身杀死的尝试会被丢弃,而不是记录为失败。再次按 Ctrl-C 可立即退出而不保存。运行中途发生致命的后端错误(例如凭据过期)会以同样的方式保存,然后报告该错误。

Token 和时间用量

Pass@k 告诉你一项技能是否有效;用量告诉你达到那里花费了多少。两次运行可能有相同的分数,而其中一次消耗了两倍的 token。Caliper 记录每次尝试的 token 量和墙钟时间,并按运行汇总。评判延迟单独记录为 judge_seconds,并在其自己的 Judge 行中汇总——它不属于 Wall,Wall 保持为 agent 自身的时间,并且只有在评判器实际运行时才会出现:

With skill    100.0%  ████████████████████

Tokens   1.2M in / 340K out
Wall     6m 18s  12.6s per attempt
⊘ unusable spend: 180K tokens, 42s  (2 attempts, not counted in the average)

- 结果表带有每个任务的 Tokens 和 Wall 列,因此你可以一眼发现昂贵的任务;下方的摘要行汇总整个运行。
- 每个 AttemptRecord 都带有一个可选的 usage 对象,将 token 分为四类:
- input_tokens:提示词,不包括缓存
- output_tokens:生成的输出
- cache_read_tokens:缓存命中
- cache_creation_tokens:缓存写入

这四类是互不重叠的,因此计算出的 total_tokens 永远不会重复计数。墙钟时间来自 duration_seconds,它已经被记录。
- 每个 AttemptRecord 还带有一个可选的 transcript 数组,包含有序的轮次(role、content,以及存在时的工具 tool_name/tool_input/tool_output)。这会在保存的结果中保留完整的工具调用轨迹,以供后续检查;没有该字段的旧 JSON 仍可加载(transcript 为 null)。
- 每个 SkillSnapshot 都会记录 source_kind("path" 或 "git")以及 git_repo/git_sha,因此保存的运行会说明邻域中每个成员是如何获取的,并且——对于 git 来源——还会说明获取时的确切提交。没有该字段的旧 JSON 仍可加载,并读取为 "path"。
- 在摘要中,in = input + cache_read + cache_creation,out =
output。不可用的切片(超时 / 基础设施 / 评判器错误)被单独拆分出来,
这样浪费的开销保持可见,同时不会扭曲每次尝试的平均值。
- 支持情况: claude-code、codex、pi 和 hermes 都会报告用量;
无法报告的后端会将这些字段留为 null 并渲染为 —。codex 将缓存包含在其
input_tokens 中,因此它被归一化为上述非缓存约定。
- 美元成本被有意不追踪:它在各后端之间不一致。
token 是体量信号,所以如果你需要美元数字,请在下游推导。
- 消融运行就是一次普通的已保存运行,因此消融与完整对比视图就是
caliper compare,与任何其他 diff 一样——相同的表格、尝试条带,以及
token/墙钟时间增量。
- report --format json 会添加一个派生的 usage_totals 块;保存的 JSON 保留
原始的每次尝试 usage(总计始终是派生的,从不持久化)。

已保存结果中的激活字段

- 每个 AttemptRecord 都携带 activated,即 agent 选择加载的技能,
在每次尝试时都会记录,无论任务是否对此作出断言。当没有任何东西是可观测的
时,它为 null:一次整个邻域都被消融的尝试(未安装任何技能),或者一次
超时 / 基础设施失败,其被截断的转录未显示任何激活。在这些情况下,一个裸的
[] 会捏造出“描述从未触发”,所以 caliper 从不写入它。
- 一个确实显示了激活的被截断转录会保留它:截断可以隐藏证据,但绝不会
凭空捏造证据。该尝试仍然不进入激活分数(该分母按结果排除了 timeout /
infra_error),并且其 activation_passed 为 null——这是一个观察,绝不是
一个判定。参见激活可采纳性。
- activation_passed 是判定:null = 未断言(与 activated 的 null
不同,与现有的 assert_passed 惯用法一致)。
- TaskResult 携带 activation_expected(任务的 activates: 集合)以及
派生的 activation_usable / activation_successes / activation_score。
- AggregateScore 携带 avg_activation_score、activation_tasks 和
activation_per_skill(每个技能的 expected/fired/hits,以及派生的
recall/precision),与执行部分的 scored_tasks 并列。
- RunMeta.era 记录一次运行是在哪种加载规范下产生的。
#18 之前的运行没有 era,并且 caliper compare 拒绝跨该边界进行 diff,
因为那些运行测量的是别的东西(参见
ADR 0013)。
两次同 era 运行之间的邻域变化只会发出警告。
- RunResults.skill_snapshots 是一个列表,每个已声明技能一个快照,
因为邻居的 description 是产生该分数的一部分。在 #18 之前保存的运行
携带一个单数的 skill_snapshot;它们仍然可以加载,并且它们的
缺失的 era 正是让 compare 拒绝它们的原因。
- RunMeta.mcp_servers 记录一次运行在配置时(经过任何消融之后)所使用的 mcp: 服务器。结合 RunMeta.ablated,它能让 compare 将 mcp: 标记与该次运行实际拥有的内容进行核对,因此,如果某个 spec 在两次运行之间丢弃了该服务器,就不会被误读为对该服务器的消融。对于在该字段存在之前保存的运行,它是 None——表示未知,而不是“无”——并且当两次运行记录了不同的服务器时,compare 会发出警告(mcp_mismatch)。
- TaskComparison 携带 a_activation/b_activation/activation_delta/activation_regression,而 RunComparison 携带 has_activation_regression,并与 has_regression 严格分开。

贡献

欢迎贡献。请参阅 CONTRIBUTING.md,了解适合上手的领域、PR 前检查清单、ruff 格式化约定和固定版本,以及一次性的 pre-commit install 步骤。

故障排除

codex judge failed: model ... is not supported
该模型名称对你的 Codex 账户不可用。请使用 codex exec --model  接受的模型。

Judge model ... is unavailable / Judge authentication failed / Judge rate limited
judge CLI 已到达提供商,但调用被拒绝。Caliper 会在 harness 边界处(根据 CLI 的结构化输出)对这些情况进行分类,并建议传入 --judge-model  以选择一个可用的 judge 引擎或模型。示例:caliper run my-skill.eval.yaml --judge-model claude-code:claude-haiku-4-5-20251001。

某个任务仅因为 assert: 而通过
当任务只有 assert: 时,不会运行 LLM judge。如果你还希望由 LLM 评估 transcript,请添加 expect:。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群