Agent Skills を体系的に評価する方法:トリガー用例から自動スコアリングまでの完全ガイド
Skill を修正したとき、本当に改善されたのか、それとも数回のテストでスムーズに見えただけなのか?この OpenAI 実践ガイドでは、6つのステップで完全な評価ループを構築します。成功の定義、手動テスト、正例・負例の設計、JSONL トレースの検証、そして構造化 rubric による品質レビューまで。記事内には全コードを掲載し、テスト Prompt とチェックリストを直接生成できるインタラクティブツールも収録しています。
Agent Skill の開発において最も一般的な悩みは、変更の良し悪しを感覚に頼って判断しがちな点です。Prompt のバージョンを変えると、一部のユースケースでは一見スムーズに動いているように見えても、その変更が本当に改善されたのかを確認するのは難しく、リグレッション(Regression)——本来トリガーされるべきシナリオで応答しなくなったり、重要な実行ステップが抜け落ちたり、ワークスペースに一時ファイルが取り残されたりすること——に気づくのはさらに困難です。こうした主観的な推測から脱却するための解決策は、比較可能な評価メカニズム(Evals)を構築し、再現可能で比較可能な実行記録を用いて効果の変化を判断し、リグレッションをいち早く検知することです。
Skill は本質的に、体系的に整理された一連のプロンプトと指示セット(Prompts and Instructions)です。Skill に対する評価は、エージェント向けの軽量なエンドツーエンド統合テストに似ています。すなわち、対象の Prompt を入力 → Agent の実行プロセス(Trace の軌跡と生成された成果物)を完全にキャプチャ → 的を絞ったルール検査を実行 → バージョン間で横断比較可能なスコアを出力 という流れです。
評価を実践に落とし込むには、次の6つのステップに分けられます。成功基準の定義、テスト可能な Skill の記述、手動テスト実行による隠れた前提条件の洗い出し、正例・負例を含む10〜20件の小規模な評価セットの構築、JSONL と決定論的(deterministic)な grader による振る舞いと成果物の検証、そして読み取り専用の Codex と JSON Schema を用いた rubric-based grading の実施です。その上で、成熟度に応じて高度なモニタリングを必要に応じて導入します。
ステップ 1:成功基準を定義する
多くの Skill で評価が難しくなる根本的な原因は、最初に何をもって「実行の成功」とするかが明確に定義されていないことにあります。曖昧な期待値しかない場合、最終的な受け入れ検証は行き当たりばったりの目視確認に頼らざるを得なくなります。最初のステップで行うべきことは、成功基準を明確に定義し、必須合格項目(Must-pass)に焦点を当てた具体的な側面に分解することです。
具体的には、作成やリファクタリングに着手する前に簡潔なチェックリストを作成し、4つの目標の側面を明確に区別します。
- 結果目標(Outcome goals):タスクは最終的に完了したか?ビルドされたアプリケーションは正常に起動するか?
- プロセス目標(Process goals):Agent は対応する Skill を実際に呼び出したか?指定されたツールやコマンドを期待される順序で実行したか?
- スタイル目標(Style goals):生成されたファイル構造、コンポーネントの書き方、スタイリング手法は所定の技術仕様に準拠しているか?
- 効率目標(Efficiency goals):実行プロセスにおいて無駄な再試行(同じエラーコマンドの繰り返し試行や、異常に膨大な Token の消費など)が発生していないか?
合格かどうかの判断基準は、ハード指標がすべて達成されているかどうかにあります。例えばスキャフォールディングのシナリオでは、「npm install が実行されたか」や「package.json が生成されたか」はプロセスおよび結果のレベルにおけるハードチェックであり、厳格に合格しなければなりません。一方、「ディレクトリ構造が整っているか」「スタイリングが制約に従っているか」といった項目は、スタイル基準として1項目ずつ突き合わせて確認します。
このステップを完了すると、4つの側面を網羅した構造化された受け入れチェックリスト(Checklist)が得られます。これは後続のテストアサーションを作成するための明確な標的となり、評価の核心的な目的は、曖昧な総合合格率を出すことではなく、具体的なリグレッションの兆候を早期に浮き彫りにすることにあります。
ステップ 2:テスト可能な Skill を記述する
評価を実行可能にするための前提として、Skill 自体が明確で検証可能な振る舞いの境界を持っている必要があります。ステップ2で行うべきことは、仕様に準拠し、明確な受け入れ条件を含んだ Skill の骨格を作成することです。
Codex の体系において、1つの Skill は本質的に SKILL.md ファイルを含む独立したディレクトリです。ファイルの先頭には name と description で構成される YAML Front Matter が必須であり、その後に Agent の振る舞いを規定する Markdown の指示、および必要に応じた付属スクリプトやリソースが続きます。
ここでの name と description は、Skill 全体における最も重要なトリガーシグナルです。Codex はまさにこの2つのメタデータに基づいて、現在のタスクで当該 Skill を呼び出すべきかどうかを判断し、いつ SKILL.md の本文全体をコンテキストウィンドウに注入するかを決定します。名前付けや説明が広範すぎたりセマンティクスが過密だったりすると、Skill を安定してアクティブ化できなくなります。
ゼロから始める場合は、ターミナルで公式に提供されているスキャフォールドスキルを直接呼び出すことができます。
$skill-creator
このスキャフォールドは対話を通じて、Skill の責務、トリガーのタイミング、そしてそれが純粋な指示型(Instruction-only)かスクリプト補助型(Script-backed)かを明確にするようガイドします。ここでのデフォルトの推奨は、純粋な指示型の設計を優先することです。
実践例:統一仕様に沿ったデモ構築スキル
ここでは、setup-demo-app という最小限の実践的な Skill を例にとります。その役割は、統一された仕様のもとで軽量な React デモプロジェクトを安定して初期化することです。Vite の React + TypeScript テンプレートを採用し、公式の Vite プラグイン経由で Tailwind CSS を設定し、簡潔なディレクトリ構成に従い、直接検証可能な完了の定義(Definition of Done)を提示します。
適用スコープに応じて、以下の内容を次のいずれかのパスに保存できます。
- リポジトリスコープ(Repo-scoped):
.codex/skills/setup-demo-app/SKILL.md - ユーザーグローバル(User-scoped):
~/.codex/skills/setup-demo-app/SKILL.md
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## When to use this
Use when you need a fresh demo app for quick UI experiments or reproductions.
## What to build
Create a Vite React TypeScript app and configure Tailwind. Keep it minimal.
Project structure after setup:
- src/
- main.tsx (entry)
- App.tsx (root UI)
- components/
- Header.tsx
- Card.tsx
- index.css (Tailwind import)
- index.html
- package.json
Style requirements:
- TypeScript components
- Functional components only
- Tailwind classes for styling (no CSS modules)
- No extra UI libraries
## Steps
1. Scaffold with Vite using the React TS template:
npm create vite@latest demo-app -- --template react-ts
2. Install dependencies:
cd demo-app
npm install
3. Install and configure Tailwind using the Vite plugin.
- npm install tailwindcss @tailwindcss/vite
- Add the tailwind plugin to vite.config.ts
- In src/index.css, replace contents with:
@import "tailwindcss";
4. Implement the minimal UI:
- Header: app title and short subtitle
- Card: reusable card container
- App: render Header + 2 Cards with placeholder text
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx exist
明確なステップの分解と完了の定義(Definition of done)により、このステップでは構造の整った SKILL.md ファイルが作成されました。これが合格かどうかを判断する鍵は、出力の境界と完了条件が明確で検証可能であるかどうかにあります。ルールが曖昧で変動しやすい場合、後続の自動評価において判断基準が失われてしまいます。
ステップ 3:手動テスト実行で隠れた前提条件を洗い出す
SKILL.md を書き上げた後、すぐに大量のスクリプトを書き始めようとしてはなりません。このステップで行うべきことは、本番環境または一時的なサンドボックス環境で手動テスト実行を行い、Agent がどの段階で期待から外れるかを観察し、Skill 内に明記されていなかった隠れた前提条件を洗い出して修正することです。
まずは対話インターフェースで /skills スラッシュコマンドを使って選択するか、プロンプト内で $setup-demo-app を直接使ってこのスキルを明示的に参照します。この段階の重点は、よくある3種類の隠れた前提条件を体系的に調査・特定することです。
- トリガーの前提(Triggering assumptions):本来トリガーされるべき一般的な指示(例:「素早く React デモを立ち上げて」など)でヒットしなかったり、逆にトリガーされるべきでない隣接シナリオ(例:既存プロジェクトでの「既存のアプリに Tailwind を設定して」という指示)で誤ってアクティブ化されたりすること。
- 環境の前提(Environment assumptions):Skill が、現在完全にクリーンな空ディレクトリにいることを暗黙の前提にしていたり、OS に必ず
npmがインストールされていて他のパッケージマネージャーより優先されると決めつけていたりすること。 - 実行の前提(Execution assumptions):Agent が依存関係はすでにグローバルにインストールされていると誤解して
npm installをスキップしてしまったり、Vite プロジェクトの初期化が完了する前に Tailwind の設定を急いでしまったりすること。
これらの調査プロセスを固定化する段階になったら、対話型インターフェースからヘッドレスな自動化コマンドラインツール codex exec に切り替える必要があります。このコマンドは実行の進捗を標準エラー出力(stderr)にストリーミング出力し、最終的な出力のみを標準出力(stdout)に送るため、スクリプトによる呼び出しや CI との統合に適しています。
スキャフォールディングの構築タスクにはディスクへのファイルの書き込みや作成が伴うため、実行時には --full-auto 権限フラグを明示的に付与する必要があります。
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'
自動呼び出しを行う際は、最小権限の原則に従い、タスク完了に必要な最小限の権限スコープのみを付与し、想定外の権限昇格が発生していないかを検証する必要があります。このステップでは、初回のテスト実行でエラーが全く出ないことを求めているのではなく、期待から乖離した箇所を記録することが目的です。調査を完了すると、一連の境界問題と再現可能な実行コマンドが得られ、これらが次のステップの評価セットにおける核となる入力源となります。
ステップ 4:正例・負例を含む10〜20件の小規模な評価セットを構築する
Agent の評価では、最初から数百〜数千件もの膨大なベンチマークを積み上げる必要はありません。単一の独立した Skill であれば、10〜20件の代表的な用例を含む小規模な評価セットを維持するだけで、価値あるリグレッションの兆候を早期に発見するには十分です。
このステップで行うべきことは、事前の調査で特定した境界をテストケースとして定着させることです。簡潔な CSV ファイル(例:evals/setup-demo-app.prompts.csv)を作成し、トリガーの期待値と具体的な Prompt を表形式で管理し、今後新たな問題が発生した際に継続的に新しい行を追加できるようにします。
id,should_trigger,prompt
test-01,true,"Create a demo app named `devday-demo` using the $setup-demo-app skill"
test-02,true,"Set up a minimal React demo app with Tailwind for quick UI experiments"
test-03,true,"Create a small demo app to showcase the Responses API"
test-04,false,"Add Tailwind styling to my existing React app"
これら4件のテストケースは、それぞれ不可欠な4つのテスト意図を表しています。
- 明示的呼び出し(Explicit invocation、例:
test-01):プロンプトに$setup-demo-appが明示的に含まれています。これは、ユーザーがスキルを明名した場合に低レイヤーの呼び出しパイプラインが正常に通るかを検証し、その後の設定変更で基本的なアクティベーション機能が損なわれていないことを確認するために用います。 - 暗黙的トリガー(Implicit invocation、例:
test-02):Skill 名には一切言及せず、そのスキルが対象とする標準的なユースケースのみを記述します。これは Front Matter のnameとdescriptionが十分に明確で、モデルが日常的な対話の中でこのスキルを自律的に選択できるかを検証するために用います。 - コンテキスト付きの複雑なトリガー(Contextual invocation、例:
test-03):具体的なビジネスシナリオ(例:Responses API の組み込み)を加え、現実世界の軽微なノイズを混入させます。これは追加の干渉情報が存在する場合でも、モデルがスキャフォールドの要件を正確に抽出し、統一されたコード構造を維持できるかを検証するために用います。 - 負の対照サンプル(Negative control、例:
test-04):誤検知・誤トリガー(False Positives)を防ぐために用います。ユーザーの問い合わせは「既存のプロジェクトに Tailwind のスタイリングを追加する」であり、セマンティクス上は Skill の説明と非常に類似していますが、このとき新規スキャフォールドの構築アクションを絶対にトリガーしてはなりません。負の対照用例が欠けていると、Agent がユーザーの既存コードを修正する際にプロジェクト全体を誤って上書きしてしまう事態を招きがちです。
正の暗黙的呼び出しと負の対照を組み合わせることで、合格判定の基準は「正のシナリオでは安定してアクティブ化されステップが完結すること、負のシナリオでは決して誤トリガーされないこと」となります。このステップを完了すると、構造化された評価データセットファイル(evals/setup-demo-app.prompts.csv)が得られます。
以下のプランジェネレーターは、このステップをそのまま持ち帰って使えるテンプレートにしたものです。Skill 名、対象タスク、必須実行コマンド、必須生成ファイルを入力すると、4種類のテスト Prompt、決定論的チェックリスト、および推奨保存パスを生成します。
持ち帰り用ツール
あなたの Skill を最初の実行可能な Eval に変換する
下の4項目を変更すると、ジェネレーターが4種類のテスト Prompt、ハードルール検査、推奨保存パスを自動更新します。デフォルトの例は本文の setup-demo-app に対応しています。
生成結果
4種類の Prompt
決定論的チェック
推奨成果物
フィールドを変更すると、独自のバージョンが生成されます。
ステップ 5:JSONL と決定論的 grader で振る舞いと成果物を検証する
Agent が単に一見使えそうなコードを偶然出力したのではなく、低レイヤーのステップを実際に実行したことを確実に検証するには、主観的な推測に頼ることはできません。このステップで行うべきことは、決定論的(deterministic)な grader を通じてコマンドの軌跡とディスク上の成果物を直接検証することです。
これを実現するための鍵は、codex exec の実行時に --json フラグを有効にすることです。これにより標準出力(stdout)は行区切りの JSONL 構造化イベントストリームへと変換されます。低レイヤーでの Shell コマンド呼び出しはすべて、item.started および item.completed イベントとして明確に記録され、その item.type はすべて command_execution となります。
自動実行および検証スクリプト
この仕組みを活用して、軽量な Node.js スクリプト evals/run-setup-demo-app-evals.mjs を作成できます。このスクリプトを実行すると、Agent の実行、Trace のキャプチャ、JSONL イベントのパースが順次行われ、コマンドログとディスクファイルの存在が直接アサートされます。
// evals/run-setup-demo-app-evals.mjs
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
function runCodex(prompt, outJsonlPath) {
const res = spawnSync(
"codex",
[
"exec",
"--json", // 重要な引数:構造化された JSONL イベントストリームを出力
"--full-auto", // 必要なファイルシステム変更権限を付与
prompt,
],
{ encoding: "utf8" }
);
mkdirSync(path.dirname(outJsonlPath), { recursive: true });
// --json モードでは stdout が標準の JSONL 形式になる
writeFileSync(outJsonlPath, res.stdout, "utf8");
return { exitCode: res.status ?? 1, stderr: res.stderr };
}
function parseJsonl(jsonlText) {
return jsonlText
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
// 決定論的ルール 1:Agent は npm install を含むコマンドを実際に実行したか?
function checkRanNpmInstall(events) {
return events.some(
(e) =>
(e.type === "item.started" || e.type === "item.completed") &&
e.item?.type === "command_execution" &&
typeof e.item?.command === "string" &&
e.item.command.includes("npm install")
);
}
// 決定論的ルール 2:ディスク上に package.json が実際に作成されたか?
function checkPackageJsonExists(projectDir) {
return existsSync(path.join(projectDir, "package.json"));
}
// 単一用例の評価フロー例
const projectDir = process.cwd();
const tracePath = path.join(projectDir, "evals", "artifacts", "test-01.jsonl");
const prompt =
"Create a demo app named demo-app using the $setup-demo-app skill";
// ステップ 1:CLI でタスクを実行し、トレースをディスクに保存
runCodex(prompt, tracePath);
// ステップ 2:トレースイベントを読み込み、アサーションを実行
const events = parseJsonl(readFileSync(tracePath, "utf8"));
console.log({
ranNpmInstall: checkRanNpmInstall(events),
hasPackageJson: checkPackageJsonExists(path.join(projectDir, "demo-app")),
});
この決定論的チェックの判定基準は極めて明快です。スクリプトの出力における ranNpmInstall や hasPackageJson などのアサーションがすべて true を返さなければなりません。実行後、evals/artifacts/test-01.jsonl に保存された完全な Trace 成果物と、明確な真偽値の検証結果が得られます。いずれかのチェックに失敗した場合、開発者は推測することなく、直接 .jsonl トレースログを開いて時系列順に記録されたコマンド実行イベントを確認できます。
ステップ 6:読み取り専用の Codex と JSON Schema で rubric-based grading を行う
決定論的なコードアサーションは基本的なアクションが完了したかを効果的に確認できますが、生成された内容がコードスタイルの規約に適合しているかを評価することは困難です。例えば、コードはすべて関数コンポーネントを採用しているか?Tailwind は CSS Modules を混在させることなくユーティリティクラスを厳格に使用しているか?コンポーネントのディレクトリ構成は綿密に分割されているか?といった点です。
このステップで行うべきことは、決定論的チェックの後に、事前定義された基準に基づく第2の読み取り専用モデルレビュープロセスを導入することです。具体的には、codex exec の --output-schema 引数を使用し、特定の JSON Schema に準拠した構造化レビューデータをモデルに強制出力させ、コードを変更することなく読み取り専用のレビューを実施します。
レビュースタンダード Schema を定義する
まず、採点基準のコントラクトファイル evals/style-rubric.schema.json を作成します。この Schema では、トップレベルに全体の合格判定(overall_pass)と全体の定量スコア(score)を含み、具体的な検査項目は各項目の合格判定(pass)とレビューの注記(notes)で構成されます。
{
"type": "object",
"properties": {
"overall_pass": { "type": "boolean" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"checks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"pass": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "pass", "notes"],
"additionalProperties": false
}
}
},
"required": ["overall_pass", "score", "checks"],
"additionalProperties": false
}
読み取り専用コードレビューを実行する
スキャフォールドの生成が完了した後、再び codex exec を呼び出して当該ディレクトリを指定します。このとき --full-auto の権限は渡さず、検査用プロンプトと Schema パスのみを提供し、スコアリング JSON を成果物ディレクトリに保存します。
codex exec \
"Evaluate the demo-app repository against these requirements:
- Vite + React + TypeScript project exists
- Tailwind is configured via @tailwindcss/vite and CSS imports tailwindcss
- src/components contains Header.tsx and Card.tsx
- Components are functional and styled with Tailwind utility classes (no CSS modules)
Return a rubric result as JSON with check ids: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.json
レビュー結果は構造化 JSON に書き込まれます。overall_pass は全体として合格したかを示し、score はバージョン間での比較に用いられ、各チェック項目には pass と notes が提供されます。チームで別途スコアのしきい値を設定する場合は、評価スクリプトに対応する判定を追加できます(元記事のサンプル自体には統一的なしきい値は規定されていません)。このステップにより、レビューレポートファイル evals/artifacts/test-01.style.json が出力されます。
--output-schema によって規定された出力は、自動パースが困難な自然言語ではなくなり、CI や自動評価パイプラインで直接マージ、比較、追跡できる構造化データとなります。GitHub Actions で実行する場合、公式に提供されている Codex Action は codex-args でこのパラメータを透過的に渡すことをネイティブにサポートしています。
成熟後の高度なモニタリング
基本的な「実行・アサーション・スコアリング」の2層ループが安定して稼働した後は、スキルの複雑化に合わせて評価体系をさらに拡張できます。まずは迅速で明確な振る舞いのフィードバックを確立することを優先し、テストの深化に伴って、より重く時間のかかる検証手段を段階的に補充していきます。
以下の6つの方向性により、基礎的な評価をより実践的なエンジニアリングシナリオへと段階的に拡張できます。
- コマンドのジッターとループ再試行の検査:JSONL ログ内の
command_executionの発生頻度を集計します。あるバージョンの変更によって同種のコマンド実行回数が急増した場合、Agent がエラーと再試行のループに陥って消耗していることを示しています。 - Token 消費量のモニタリング:イベントストリームの
turn.completedからusage.input_tokensとusage.output_tokensを抽出します。これによりプロンプトの肥大化やコンテキストの冗長性を直感的に発見し、1回の実行コストが制御不能になるのを防ぎます。 - 静的コンパイルの検証:Agent による成果物の引き渡し後に、評価システムが追加で
npm run buildを実行します。これは極めて重要なエンドツーエンドのハード指標であり、潜在的なコード構文エラー、破損したモジュールインポート、ツールチェーンのバージョン競合などを効果的に検出できます。 - ランタイムのスモークテスト:
npm run devを実行してサーバーを起動し、その後curlでローカルポートにリクエストを送信するか、ごく軽量な Playwright スクリプトを実行して UI が正常にマウントされるかを確認します。このテストは比較的時間を要するため、リスクの高いコア用例に対して必要に応じて有効化することを推奨します。 - リポジトリのクリーン度チェック:
git status --porcelainを実行し、実行完了後のワークスペースに想定外の余分な設定ファイルや一時コードが存在しないことをアサートし、コードベースの清潔さを維持します。 - サンドボックスおよび権限ロールバックのテスト:Skill が権限昇格のない基本的な制限付きサンドボックス内でも引き続きスムーズに動作するかを検証し、イテレーションの過程で想定外の権限昇格が発生していないか、また権限昇格を強制するような操作コマンドが混入していないかを確認します。
おわりに
主観的な感覚だけに頼った調整から、Trace を残し、項目ごとに検査し、継続的に比較するアプローチへと転換することで、Agent Skill のイテレーションを信頼性の高いエンジニアリングの基盤の上に築くことができます。この一連のプロセスは、「今回の変更はなんとなく良さそうだ」という主観的な推測を、構造化された事実の根拠へと置き換えます。
明確に定義された成功基準から始めて、構造の整った Skill を記述する。手動テスト実行を通じて前提条件の境界を見極め、デバッグ中に浮き彫りになった問題を10〜20件の典型的な評価用例として定着させる。低レイヤーでは JSONL イベントストリームと決定論的 grader を用いてコマンドとファイル成果物を検証し、高レイヤーでは JSON Schema を活用してモデルによるコードレビューを規律づける。そしてスキルが成熟した後に、必要に応じてコンパイル検証やランタイムモニタリングへと拡張していく。
再現可能な評価基盤があれば、プロンプトや実行ロジックを調整するたびに、その変更が一体どのような改善をもたらしたのかを明確に把握でき、予期せぬリグレッションの兆候を早期に検知できます。実際の問題を通じてテストケースを継続的に拡充していくことで、Skill は長期にわたるイテレーションの中でも既存の能力を安定して維持できるようになります。
