AI エージェントの evals 入門 — unit test では測れない品質を数値で担保する

AI エージェントの evals 入門 — unit test では測れない品質を数値で担保する

社内向けチャットボットを本番に出した翌週、サポートチームから「変な回答が混じるんですが」と報告が入ります。手元で 10 回触って問題なかった挙動が、1 万回のうちにどれだけ破綻するかは、素のテストからは見えません。

伝統的なソフトウェアテスト(unit test / integration test)では、AI エージェントの品質を担保できません。AI エージェント用の評価、つまり evaluation(略して evals)という別の仕組みが必要になります。

この記事では、unit test では足りない理由、Evals の 3 層構造(Unit / Integration / End-to-End)、LLM-as-a-judge の使い方と落とし穴、統計的に意味のあるサンプル数、本番モニタリングとの関係、そして最小構成の実装パターンまでをまとめます。

なぜ unit test では足りないのか

伝統的なソフトウェアテスト

test("add function", () => {
  expect(add(1, 2)).toBe(3);
  expect(add(0, 0)).toBe(0);
});

このテストは、入力と期待出力が確定しているので動きます。add(1, 2) の答えは 3 で、未来永劫そのままです。

AI エージェントの出力は非決定

test("agent writes a Python function", () => {
  const result = await agent.run("Write a Python function that reverses a string");
  // ... 何をチェックすればいい?
});

LLM の出力は、同じ入力でも違う結果になります(temperature、ランダム性のため)。しかも正解は複数あり得て、[::-1] でも reversed() でも ''.join(...) でも OK です。「正しい」の基準も曖昧で、コードが動けばいいのか、可読性もか、パフォーマンスもか、で変わります。単純な expect(x).toBe(y) では捉えられません。

評価の軸が複数ある

AI エージェントの「良さ」は 1 次元ではありません。答えが合っているかという正確性、必要な情報が揃っているかという網羅性、要求されたフォーマットに沿っているかという形式適合、読み手に合った言い方になっているかというトーン、害のある内容を含まないかという安全性、無駄なトークンを使っていないかという効率、どの軸で測るかで評価が変わってきます。全部を 1 つの数値にまとめると、どこが悪いか分からなくなります。

Evals の 3 層構造

ソフトウェアテストに unit / integration / E2E があるように、evals にも層があります。

Layer 1: Unit evals — プロンプト単位の評価

1 つのプロンプトに対する 1 ターンの応答を評価する層です。たとえば「このテキストを英訳して」というプロンプトで翻訳の品質を見たり、「このコードをレビューして」で指摘の網羅性を測ったりします。

評価方法は 3 つから選びます。期待出力との match(厳密もしくはセマンティック)、LLM-as-a-judge によるスコアリング、good / acceptable / bad のカテゴリ分類です。サンプル数は 50〜200 件、カテゴリごとに最低 20 件を確保します。実装は GitHub の braintrust、promptfoo、openai evals などが使えます。

Layer 2: Integration evals — ツール連鎖の評価

エージェントがツールを使ってタスクを完遂できるかを見る層で、1 ターンではなく複数ターンのツール呼び出しを含みます。「このバグを直して PR を出して」というタスクで git、ファイル編集、テスト実行、コミット、PR 作成の全連鎖を評価したり、「このドキュメントを検索して要約」で web search + 読み込み + 要約の流れを見たりします。

評価軸は 3 つ。タスク完了率(Success Rate)、中間ステップの正しさ(Trajectory analysis)、ツール呼び出しの効率(無駄な呼び出しがないか)です。サンプル数は 20〜100 件で、unit evals の 50〜200 件より少なめにします。1 件あたりの実行コストが大きい分、カスタム harness が必要になることも多いです。

Layer 3: End-to-End evals — ユースケース単位

実ユーザーのシナリオをエージェントが完結できるかを見ます。「顧客サポートチャットで不満を持った顧客を満足させて帰す」「Deep Research で 2026 年の AI 市場レポートを 10 ページ書く」といった粒度です。

評価方法は、人間のレビュー(最も信頼できるが遅く高価)、LLM-as-a-judge、本番トラフィックでの A/B テストを組み合わせます。サンプル数は 10〜50 件で、シナリオを変えて多様性を確保します。モックしたバックエンドや合成ユーザーなど、本番環境に近い整備が必要です。

LLM-as-a-judge — 使い方と落とし穴

評価の自動化で避けて通れないのが LLM-as-a-judge、別の LLM にスコア付けさせる手法です。

基本パターン

async function judge(input: string, output: string, criteria: string): Promise<number> {
  const prompt = `
以下の出力を、次の基準で 1-5 のスコアで評価してください。

## 入力
${input}

## 出力
${output}

## 基準
${criteria}

スコアと理由を JSON で返してください。
`;
  const result = await claude.messages.create({ ... });
  return parseInt(result.text);
}

使いどころ

主観性の高い評価、たとえば自然さ・読みやすさ・トーンを測るときに向いています。A と B のどちらが優れているかという多数候補からの選定、要求された構造に沿っているかの形式適合性チェックにもよく使われます。参考までに、Anthropic の evals ドキュメント に基本パターンがまとまっています。

落とし穴 1: Judge のバイアス

LLM-as-a-judge には、よく知られた 4 つのバイアスがあります。長い回答を高く評価しがちな length bias、A/B 比較で先に出された方を優先しがちな position bias、自分と似たスタイルを高く評価する self-preference、具体的な理由を書いた方を高く評価する verbosity bias です。

対策: バイアスを薄める

複数 judge の投票(Claude judge、GPT judge、Gemini judge の多数決)でバイアスを平均化します。A/B 比較は半々で順序を入れ替えて位置のランダム化を効かせ、ルーブリックで「このスコアはこういう場合に出す」を細かく指定しておきます。さらに 100 件を人間が評価して judge のスコアと相関係数を取り、0.7 を下回ったらルーブリックを見直すという整合性チェックまで組み合わせます。

落とし穴 2: Reward hacking

Judge が特定のパターンを好むと、エージェントがそれを exploit するようになります。たとえば「ステップバイステップで」と書くと judge のスコアが上がると分かった瞬間、エージェントは意味もなく「ステップバイステップで」を含めるようになります。

対策: hack されない設計

Judge のプロンプトを頻繁に更新し、複数の judge をローテーションさせます。さらに人間レビューを定期的に挟む(spot check)ことで、judge の抜け穴が本番に広がる前に検知できます。

統計的妥当性のためのサンプル数

典型的な数

目安としては、1 プロンプトのバリアント比較で 50〜100 件、モデル間比較で 200〜500 件、本番 KPI の推定で 1000 件以上です。

Rule of thumb

95% 信頼区間で ±5% の精度が欲しいなら、約 400 件が目安になります。

サンプル数 ≈ (1.96 / 0.05)² × 0.25 ≈ 400

ただし、評価対象に偏りがあるなら(例: タスクの難易度が様々)、カテゴリごとに 20〜50 件は確保しておきます。

継続的評価

リリースごとに全 1000 件を回すとコストがかかりすぎるので、Full regression をリリース前に 1000 件、Smoke test は毎日 50 件(代表的なサブセット)、Canary は本番トラフィックの 1〜5% を新バージョンで、という階層に分けて運用します。

本番モニタリングと evals の関係

Evals はリリース前

本番に出す前に「品質が許容範囲か」を数値で担保します。

本番モニタリングはリリース後

実トラフィックで、タスクが完了する割合(成功率)、エラーが出る割合と分類(失敗率)、応答時間(レイテンシ)、1 リクエストあたりのトークン消費(コスト)、thumbs up/down といったユーザーフィードバックを継続的に見ます。

両方のループ

Evals (リリース前) → 本番 → モニタリング → 失敗事例を収集
    ↑                                             ↓
    ← Evals データセットに追加(失敗事例を eval 化)

本番で失敗したケースを、次のリリースの eval に組み込むことで、品質を上げ続けるループが回ります。

最小実装 — 50 行で始める

Promptfoo で始める

promptfoo は OSS の prompt eval ツールで、設定は YAML 1 枚で済みます。

# promptfooconfig.yaml
prompts:
  - "Translate to Japanese: {{text}}"
providers:
  - anthropic:messages:claude-sonnet-5
  - openai:chat:gpt-5
tests:
  - vars:
      text: "Hello, world"
    assert:
      - type: contains
        value: "こんにちは"
  - vars:
      text: "I love you"
    assert:
      - type: llm-rubric
        value: "日本語として自然な愛情表現を含むこと"

npx promptfoo eval で全プロバイダ × 全テストを一括実行し、結果を web UI で確認できます。

自作する場合

// evals.ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

type EvalCase = {
  input: string;
  judge: (output: string) => Promise<boolean>;
};

async function runEvals(cases: EvalCase[]) {
  const results = await Promise.all(cases.map(async (c) => {
    const output = await agent.run(c.input);
    const passed = await c.judge(output);
    return { input: c.input, output, passed };
  }));
  const passRate = results.filter(r => r.passed).length / results.length;
  console.log(`Pass rate: ${(passRate * 100).toFixed(1)}%`);
  return results;
}

const cases: EvalCase[] = [
  {
    input: "What is 2+2?",
    judge: (out) => Promise.resolve(out.includes("4")),
  },
  {
    input: "Translate 'cat' to Japanese",
    judge: async (out) => {
      const result = await client.messages.create({
        model: "claude-haiku-4-5-20251001",
        max_tokens: 100,
        messages: [{
          role: "user",
          content: `この翻訳は 'cat' の正しい日本語訳ですか: "${out}". yes か no で答えてください。`,
        }],
      });
      return result.content[0].text.toLowerCase().includes("yes");
    },
  },
];

runEvals(cases);

これだけで、プロンプトごとに pass/fail を自動判定できる最小構成が動きます。

MulmoTerminal / MulmoClaude での実運用

MulmoTerminal や MulmoClaude のような長時間動くエージェントでは、3 層それぞれで evals を回します。Layer 1 ではプロンプトテンプレートを変更したときに走る regression suite を 50〜200 件のカテゴリ別テストで組みます。Layer 2 では「GitHub issue から PR を作る」のようなタスクに対する完了率を、20 件のカスタムベンチマークで維持します。Layer 3 では Multi-agent 対話の品質を LLM-as-a-judge で評価し、10〜30 件のシナリオを月 1 で回す運用にしました。

Singularity Society BootCamp の第 4 期でも、参加者が自分のプロダクトごとに eval suite を作る前提で動いています。並列実行については AI エージェント並列化で生産性 10 倍 もあわせてどうぞ。

まとめ

AI エージェントは unit test では捉えきれません。Evals は Unit(プロンプト単位)、Integration(ツール連鎖)、E2E(ユースケース)の 3 層で設計します。LLM-as-a-judge は便利ですが、length bias / position bias / reward hacking といった落とし穴を抱えています。統計的妥当性には全体で 400 件以上、カテゴリごと 20〜50 件が目安です。Evals をリリース前に、本番モニタリングをリリース後に、両方のループを回すのが基本の運用になります。

Promptfoo で始めるか、50 行の自作スクリプトから始めるかはチームの事情次第です。「それっぽく動く」から「数値で担保」に移行するのが、AI エージェントを本番で動かし続ける前提になります。

関連リンク


Singularity Society はテクノロジー集団として MulmoClaude / MulmoTerminal / MulmoCast を開発しています。エンジニア・起業家向けの実践プログラム BootCamp も運営しています。

この記事をシェア

関連記事

記事一覧に戻る