@vercel/agent-evalでCLAUDE.mdの効果を検証する

こんにちは、e-dash の中村です。最近は Claude Code や Cursor などのコーディングエージェントを使って開発しています。コーディングはエージェントが書き、レビューもエージェントがやり、テストもエージェントが書く。自分は何をしているのかというと、エージェントの出力を眺めて承認するだけです。もはやエンジニアというよりはAIエージェント管理職ですね。
そんなAIエージェント管理職をやっていて怖くなったことがあります。CLAUDE.mdに改善を加えてチームに展開した際に、根拠を聞かれたら何も返せないのです。
コーディングエージェントの評価について
エージェントに渡すドキュメント(CLAUDE.mdやAgent Skillsなど)を修正したとき、その結果の良し悪しをどう判断しているでしょうか。自分の場合、セッション中にうまくいかなかった出力を都度ドキュメントに反映する、という運用をしてきました。ただ、その改善が実際に効いているかどうかは確認していませんでした。
個人開発であればそれでも問題はないです。一方で、チーム開発になると話が変わってきます。改善の結果を検証したり、改善を重ねる中で既存の挙動が壊れていないか確認したい、といったニーズが出てきます。つまり、テストが欲しくなるわけです。
そういったコーディングエージェントの出力をテストする仕組みがないか探していたところ、Vercel Labsが公開した @vercel/agent-eval というOSSを見つけました。Dockerコンテナ内でエージェントを動かし、生成されたコードをvitestで自動検証するフレームワークです。このツールの背景にあるevalsという考え方も含めて、順を追って紹介していきます。
@vercel/agent-eval
AIの世界では、モデルやプロンプトの変更が期待通りの出力につながっているかをテストする手法を「evals」と呼びます。Vercel Labsが2026年1月に公開した @vercel/agent-eval は、このevalsをコーディングエージェントに適用するOSSで、ひと言でいうと「AIエージェントのテストランナー」です。やっていることはシンプルで、テストコード(EVAL.ts)やプロンプト(PROMPT.md)をあらかじめ除外した状態でDockerコンテナ内にエージェントを動かし、生成されたコードをvitestで検証しているだけです。コンテナ内で完結するのでホストのファイルシステムは汚れません。
サンドボックスで実行するもうひとつの利点は再現性です。ローカルで動かすと、ホームディレクトリの ~/.claude/CLAUDE.md やシェルの設定、グローバルにインストールされたパッケージなど、個人の環境がエージェントの挙動に影響する可能性があります。
PROMPT.mdの内容でエージェントを実行し、期待値となるEVAL.tsで出力結果を評価します
評価対象であるCLAUDE.mdの有無や内容の違いが、出力結果に与える影響を評価することができます
処理の流れはこうなっています。
evalのソースコードをDockerにアップロード
※ EVAL.ts と PROMPT.md はこの時点で除外(カンニング防止)
↓
git init(初期状態を記録)
↓
setup関数を実行(CLAUDE.mdの注入などはここ)
↓
npm install
↓
エージェント実行(claude --print --model haiku)
↓
EVAL.tsをアップロード → vitest実行
↓
結果をJSON保存
設計として注目すべきはEVAL.tsの除外タイミングです。eval内のソースコード(package.jsonやsrc/以下)はサンドボックスにコピーされますが、EVAL.tsとPROMPT.mdはエージェント実行前に取り除かれます。エージェントに答えを見せない仕組みです。
対応エージェントは Claude Code、Codex、Gemini、Cursor、OpenCode (執筆時点)の5つで、今回は Claude Code のみを試しました。
セットアップ
必要なものは Docker が動いていることと、使用するエージェントのAPIキー(Claude Codeなら ANTHROPIC_API_KEY、Vercel AI Gateway 経由なら AI_GATEWAY_API_KEY)だけです。
npx @vercel/agent-eval init eval-project
cd eval-project
npm install
cp .env.example .env
initで生成される構造はこうです。
eval-project/
├── .env.example
├── experiments/ # 実験設定
│ └── cc.ts # Claude Code用のデフォルト設定
├── evals/ # 評価セット
│ └── add-greeting/
│ ├── PROMPT.md # エージェントへの指示
│ ├── EVAL.ts # vitestによる検証
│ └── src/App.tsx # 編集対象のコード
└── results/ # 実行結果(自動生成)
initで入るサンプルは add-greeting というReactアプリへの1行追加タスクが1つだけです。プリセットが豊富なわけではなく、自分でevalを追加していく形になります。動作確認は --dry でプレビューしてから --smoke で最小実行するのがおすすめです。
npx @vercel/agent-eval --dry # API呼び出しなし、コストゼロ
npx @vercel/agent-eval --smoke # 1 evalを1回だけ実行
各evalは「タスク指示 + 検証テスト + 実行環境」の3点セットで、ディレクトリ1つにまとまります。
evals/prefer-next-font/
├── PROMPT.md # エージェントへの指示
├── EVAL.ts # vitestの検証コード
├── package.json # 依存関係
├── tsconfig.json
└── app/
└── BlogHeader.tsx # エージェントが編集する対象
PROMPT.mdがエージェントに渡される指示、EVAL.tsが生成物の検証テストです。この2つはサンドボックスにコピーされる前に取り除かれるので、エージェントからは見えません。
EVAL.tsの中身はvitestテストです。たとえば prefer-next-font のEVAL.tsはこうなっています。
test('BlogHeader uses Next.js fonts correctly', () => {
const blogHeaderContent = readFileSync(join(process.cwd(), 'app', 'BlogHeader.tsx'), 'utf-8');
// next/font/google からインポートしているか
expect(blogHeaderContent).toMatch(/import.*from ['"]next\/font\/google['"]/);
// Playfair_Display と Roboto を使っているか
expect(blogHeaderContent).toMatch(/Playfair_Display/);
expect(blogHeaderContent).toMatch(/Roboto/);
// .className でフォントを適用しているか
expect(blogHeaderContent).toMatch(/className.*\.className/);
// CSS @import や font-family を使っていないか
expect(blogHeaderContent).not.toMatch(/@import|font-family|link.*font/);
});
エージェントが正しくコードを出力したかを検証しています。
実際に試してみた
initで用意されるサンプルは先述の通りReactアプリへの1行追加だけで、これだけでは実践的な検証には物足りません。Vercelは next-evals-oss でNext.js向けのevalを20個ほど公開しているので、ここから2つ持ってきてCLAUDE.mdの有無による出力の違いを検証してみました。
prefer-next-fontはBlogHeaderコンポーネントにフォントを追加するタスクです。Playfair DisplayとRobotoを使うよう指定されていて、next/font/google を使うか、Google Fonts CDNへのリンクや @import で済ませるかが分かれます。prefer-server-actionsはContactFormをServer Actionで実装するタスクで、'use server'・FormData・action 属性を使うパターンが正解です。
実験は2つの設定で行いました。CLAUDE.mdなしのbaseline設定と、setup関数でCLAUDE.mdを注入する設定です。差分はsetup関数だけなので、CLAUDE.mdありの設定のみ載せます。
experiments/with-claude-md.ts
import { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import type { ExperimentConfig } from '@vercel/agent-eval';
const __dirname = dirname(fileURLToPath(import.meta.url));
const config: ExperimentConfig = {
agent: 'vercel-ai-gateway/claude-code',
model: 'haiku',
runs: 5,
earlyExit: false,
sandbox: 'docker',
timeout: 600,
setup: async (sandbox) => {
const claudeMd = readFileSync(
resolve(__dirname, '../test-claude.md'),
'utf-8',
);
await sandbox.writeFiles({ 'CLAUDE.md': claudeMd });
},
};
export default config;
setup関数でサンドボックス起動時にCLAUDE.mdを書き込んでいます。
注入するCLAUDE.md(test-claude.md)には、今回のevalに関係するNext.jsのルールを書きました。
## Next.js
- Use Server Actions with form `action` attribute instead of client-side `onSubmit` + `fetch`
- Use `next/font/google` instead of CSS `@import` or CDN links for Google Fonts
実際のプロジェクトではここまでシンプルに書いてあることは少ないでしょうが、今回はあくまで検証目的なのでこの程度にしています。モデルをhaikuにしているのはコスト削減のためで、実際の開発現場ではsonnetやopusを使うことが多いかもしれません。
npx @vercel/agent-eval baseline # baselineのevalを実行
npx @vercel/agent-eval with-claude-md # with-claude-mdのevalを実行
npx @vercel/agent-eval playground # Web UIで結果を比較
2 evals × 2 experiments × 5 runs = 20回実行した結果がこちらです。
| eval | baseline (haiku) | with-claude-md (haiku) |
|---|---|---|
| prefer-next-font | 2/5 | 5/5 |
| prefer-server-actions | 0/5 | 3/5 |
| 合計 | 2/10 | 8/10 |

playgroundの実行結果
CLAUDE.mdを用意するかどうかで結果に明らかな違いが出ることが確認できましたね。
Agentやモデルを変えて検証してみても面白いかもしれません。
最後に
今回はあくまで検証目的だったので、baseline vs CLAUDE.md ありの2条件で試しましたが、本来は CLAUDE.md 無しで検証することはなく、CLAUDE.mdやSkillsを育てながら数字の変化を追っていくものだと思います。
Docker と APIキーがあれば簡単に試せて、CI に組み込むこともできます。もちろん複数回実行するぶんコストはかかりますが、エージェント向けドキュメント(Agent Skills、AGENTS.md、CLAUDE.md など)を更新したタイミングで回せるようにしておくと、変更の効果を感覚ではなく数字で確認できます。
今回使ったevalはどちらもVercel公式の next-evals-oss から持ってきたものです。こちらもagent-evalを使ってテストしています。Next.js向けに20個ほどevalが公開されているので、自分のプロジェクトのevalsを作る際の参考にしてみるのも良いです。ちなみに各モデル、エージェントのnext-evals-ossでのテスト結果がここで公開されています。2026/2/23の時点だと一番パス率が高いのはGPT 5.3 Codex (xhigh) × Codexの組み合わせでした。

今回の検証を通じて、CLAUDE.mdに書いたルールがエージェントの出力にどれだけ影響するかを数字で確認できました。感覚的に決めてたものを、数字で可視化できるのは大きいです。evalの作成自体もvitestのテストを書くだけなので、普段のテストコードの延長線上で取り組めると思います。
機会があれば、何かのプロジェクトで導入してみようと思います。
Discussion