claude plugin eval でプラグインの効き目を測る

Claude Code エンジニアリング 9分で読めます

claude plugin eval は、自作の Claude Code プラグインを一連のテストケースで実行し、結果を採点するコマンドです。プラグインを読み込んだ実行と、まったく読み込まない実行の両方を回すので、「点が高い」だけでなく「そのプラグインが点を上げたのか」まで分かります。

この記事は公式ドキュメントの Test plugins with evalsCreate plugins をもとにした非公式の日本語解説です。

評価スイートとは何か

評価スイート(eval suite)は、プラグインの中の evals/ ディレクトリに置くテストの集まりです。1件のテストをケースと呼び、ケースは「利用者が実際に打ちそうなプロンプト」と「1つ以上の採点器(grader)」でできています。採点器は、Claude が出したものに対する合否の判定で、返答に対する正規表現、特定のツールを呼んだかどうか、別のモデルに判定させるルーブリックなどがあります。

1回の実行では次のことが起きます。ケースごとに、Claude Code がまっさらな非対話セッションをそのプラグインだけ読み込んだ状態で立ち上げ、プロンプトを送り、ターン上限か時間上限に当たるまで作業させます。そのあと各採点器が、最終の返答・トランスクリプト・Claude が作ったファイルのいずれかを見て合否を付けます。

採点の単位に注意してください。エージェントの振る舞いは毎回同じではないので、1ケースにつき既定で3回実行します。1回の実行のスコアは「合格した採点器の割合」(重みを付けたならその加重平均)で、ケースのスコアはその3回の平均です。ケースは、スコアが --threshold(既定 1.0)以上のときに合格になります。

前提を先に確認してください。必要なのは Claude Code v2.1.269 以降claude --version で確認、claude update で更新)、plugin.json または .claude-plugin/plugin.json を持つプラグインディレクトリ、そして普段の Claude Code セッションと同じ認証です。実行も判定もあなたの資格情報で実際にモデルを呼びます。プランの利用量や API 請求に乗るので、気軽に大量に回すものではありません。

最初のスイートを作る

ケースは手で書けますが、公式が勧めているのは Claude に書かせる方法です。プラグインのルート(plugin.json がある場所)で次を実行します。

claude plugin eval init

そのディレクトリがまだ信頼されていなければ Trust this plugin directory? と聞かれるので y と答えます。対話セッションが開き、Claude がプラグインを読んだうえで「何ができれば成功か」を質問し、発動すべきプロンプトと発動すべきでないプロンプトを提案し、それぞれに採点器を設計し、一度試して挙動を確かめてから evals/ の下にケースのディレクトリを書き出します。用意ができたと言われたら /exit か Ctrl+D でシェルに戻ります。

次にスイートを走らせます。

claude plugin eval .

各ケースはプラグイン有りで3回、無しで3回なので、1ケースで6回の実行になります。終わると次のような要約表が出ます。

CASE        WITH  W/OUT Δ      RUNS COST    NOTES
first-case  1.00  0.33  +0.67  6    $0.41

1 case(s) · mean Δ +0.67 · 74s · $0.41
Report: /Users/you/my-plugin/evals/results/2026-09-10T17-02-11-482Z/report.html

WITH がプラグイン有りのスコア、W/OUT が無しのスコア、Δ がその差です。COST は定価換算の見積もり、NOTES にはプラグイン有りの側で落ちた採点器のうち重みが最大のものの説明が出ます。

最初に見つかりやすいのは、Δ がほぼ 0 で、tool_used: Skill の採点器が落ちているという形です。これは「自然な言い回しで Claude があなたのスキルを選んでいない」という意味なので、直すのはプラグイン本体ではなく SKILL.mddescription です。直して測り直します。

1件だけ安く回したいときは、片側を1回だけ走らせます。1回だけの結果はぶれるので、変化を確信する前に既定の3回で確認してください。

claude plugin eval . --case <case-name> --runs 1 --ablation none

ケースと採点器の書き方

claude plugin eval init が書くのは、開いて編集できる普通のファイルです。ケースは prompt.mdcase.yaml のどちらか(または両方)を含むディレクトリで、レイアウトはこうなります。

my-plugin/
├── .claude-plugin/plugin.json
├── skills/...
└── evals/
    ├── first-case/
    │   ├── prompt.md          # frontmatter: ケースの設定 / 本文: プロンプト
    │   ├── graders/
    │   │   ├── criteria.md    # frontmatter: type と設定 / 本文: ルーブリックやパターン
    │   │   └── skill-fired.md
    │   └── case.yaml          # 任意。context.* を書くときだけ
    ├── ignores-unrelated-request/
    │   └── ...
    └── results/               # 実行のたびに書かれる。.gitignore に入れる

自分で1件書いてみたいときは、空のひな形だけを作るコマンドがあります。これは何も実行しません。

claude plugin eval init --bare first-case

prompt.md の本文が、毎回 Claude に送られるメッセージです。スキル名を書かず、利用者が打つとおりの言い方にするのが要点です。frontmatter には実行の上限と使ってよいツールを書きます。

---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---

Write me a commit message for this change: I renamed getUser to fetchUser and updated the three call sites.

各実行は空の作業ディレクトリから始まります。作業に必要なものはプロンプトの中に書くか、case.yamlcontext で用意します。プロンプト中の @path は添付ファイルに展開されないので、ファイルを読ませたいなら allowed_tools でツールを許可してください。

graders/ の下の1ファイルが1つの判定です。判定に使える型は6つで、regex / tool_used / tool_order / file_exists はトランスクリプトとファイルから計算するので追加費用がかからずllmbaseline は判定用モデルを呼ぶので費用が乗ります。自前コードの採点器はありません。

llm 型はルーブリックを渡します。合格条件と不合格条件を具体的に書きます。

---
type: llm
---

PASS if <what a correct response contains>.
FAIL if <what a wrong or missing response looks like>.

もう1つ、「その答えを出したのが本当に自分のスキルか」を見る採点器を足します。your-skill-nameSKILL.mdname に置き換えてください。

---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?your-skill-name"'
---

これは実行中に1回でもそのスキルが呼ばれれば合格になります。plugin-name:skill-name という名前空間付きの形も拾います。

スコアを安定させるコツが公式に3つ挙がっています。生成ファイルのような長い出力は llm ではなく regex で採点する(毎回同じ見方をするため)。1ケースに「結果を見る採点器」と「どう到達したかを見る採点器」を1つずつ置く(答えが正しいかと、それを出したのがプラグインかが、両方分かる)。そして tool_used: Skill は通っているのに Δ が負なら、まずプラグインではなく判定モデルを疑う(小さい判定モデルは、正しい答えでも書式がルーブリックと違うと不合格にすることがあります。--judge-model sonnet で測り直します)。

プラグイン無しの対照実行

スコアが高いこと自体は、プラグインが役に立った証拠になりません。プラグインが無くても Claude が同じようにできるかもしれないからです。そのため既定では、各ケースの実行がプラグインを読み込まない状態でも同じ回数だけ繰り返されます。前者を with-arm、後者を without-arm と呼び、その差が Δ です。

読み方は単純です。with でも without でも 1.0 なら、そのケースを通したのはプラグインではありません。対照が要らない場面(採点器を調整している最中など)では --ablation none で片側だけにでき、費用が半分になります。

2アーム実行では、一部の採点器が scored: false として報告されます。「スキルが呼ばれたか」という判定はプラグイン無しでは絶対に通らないので、そのまま数えると without 側が 0 に引っ張られ、Δ が実際より大きく出てしまうからです。そこで Claude Code は次の採点器を両アームのスコアから外し、with 側では合否の表示だけを残します。

ケースの採点器が全部これに当たる場合は、外すと採点対象が無くなるので通常どおり採点されます。逆に「このスキルを呼んではいけない」という検査(min: 0 / max: 0)は両側で数えたいので、arm: both を付けます。なお --ablation none では何も除外されないため、同じスイートでも2つのモードで絶対スコアが変わります。ここを取り違えると、モードを変えただけで「スコアが下がった」と読んでしまいます。

CI で回す

CI では --json で結果を書き出し、終了コードでビルドを落とします。信頼プロンプトで止まらないように --trust-plugin を渡し、モデルを2つとも固定し(モデルの入れ替わりをプラグインの劣化と読み違えないため)、レポートはローカルに置き、費用に上限を掛けます。

claude plugin eval . \
  --trust-plugin \
  --json results.json \
  --threshold 0.8 \
  --model claude-sonnet-5 \
  --judge-model claude-haiku-4-5 \
  --no-publish \
  --max-cost-usd 20

終了コードの意味は次のとおりです。

HTML レポートの書き出しや公開に失敗しても、終了コードは変わりません。スコアが低い理由を知りたいときは、--json を外してローカルで回すと実行ごとの進捗と採点器の行が表示されます。

CI のランナーには Claude Code のインストールと、ANTHROPIC_API_KEY のような環境変数の資格情報が要ります。claude plugin eval init は質問のために端末を必要とするので、CI では claude plugin eval init --bare <name> で空のひな形だけを作ります。

費用を読みやすく保つには、変更のたびに回す軽いスイートには判定モデルを呼ばない採点器だけを置き、Δ が要らないところは --ablation none にし、partial: true の結果と skippedPaidGraders が付いた実行を推移グラフから除きます。

結果の集計は aggregate-result.json--json の出力)にあります。ゲートで読むのは主に aggregates.overallScoreaggregates.casesPassedaggregates.casesTotalaggregates.meanDelta、そしてケースごとの cases[].aggregates.scorecases[].aggregates.delta です。schemaVersion: 1 のバージョン付き文書で、フィールドは名前を変えずに追加される方針なので、スクリプトは知らないフィールドを無視する形に書いてください。