第11章 評価とテスト(Evaluation and Testing)
11.1 概要
Section titled “11.1 概要”本書はここまで、繰り返し「評価セットで測れ」と述べてきた。第6章6.7.1節ではプロンプト改善のサイクルとして、第7章7.7節ではツール改善の基盤として、第3章3.4節ではファインチューニングを判断する根拠として。本章はその中身にあたる。
なぜこれほど強調するのか。理由は単純で、エージェントは「なんとなく良くなった」という感覚で改善できないからである。
従来のソフトウェアなら、入力に対する出力は決定的であり、テストは通るか落ちるかのどちらかである。エージェントは違う。同じ入力でも出力が揺れ(第2章2.1節)、実行経路が毎回変わり(第4章4.3.1節)、部分的に正しく部分的に誤っている出力がありうる。プロンプトを1行変えたときに何が改善し何が壊れたのかは、測らなければ分からない。
もうひとつ強調しておきたいのは、評価は後回しにできないということである。「まず動くものを作って、あとで評価を整える」という順序は、実際にはうまくいかない。評価がないまま改善を重ねると、退行に気づけないまま複雑さだけが増えていく。最小限の評価セットは、最初のプロトタイプと同時に作り始める。
11.2 評価の3つの深さ
Section titled “11.2 評価の3つの深さ”エージェントの評価は、見る粒度によって3層に分かれる。
この関係は、**「エンドツーエンドは何かがおかしいことを教え、コンポーネントはどこがおかしいかを教える」**と要約できる。両方が必要である。
11.2.1 エンドツーエンド評価
Section titled “11.2.1 エンドツーエンド評価”ユーザーの目標が達成されたかどうかだけを見る。最も重要な指標だが、失敗の原因は分からない。
利点: ユーザー価値に直結する。実装を変えても指標が有効であり続ける。
欠点: 診断能力がない。「成功率が72%から65%に落ちた」ことは分かっても、なぜかは分からない。
11.2.2 軌跡評価(Trajectory Evaluation)
Section titled “11.2.2 軌跡評価(Trajectory Evaluation)”エージェントがたどった経路 — 計画、ツールの選択、引数、中間の推論 — を評価する。
エージェント特有の重要な観点がここにある。正しい答えを出していても、軌跡としては失敗していることがある。 不要なツールを5回呼んで偶然たどり着いた場合、結果は正解でもコスト・レイテンシ・信頼性の面で問題を抱えている。
第5章5.4節で挙げた失敗モード(暴走・停滞・誤りの累積)は、いずれも軌跡を見なければ検出できない。
11.2.3 コンポーネント評価
Section titled “11.2.3 コンポーネント評価”個々の部品を切り出してテストする。検索器の精度、個別ツールの正確さ、サブエージェントの出力品質など。
これが最も診断に役立つ。 「タスク完遂率が低い」という事実から、「検索の再現率が40%しかない」という原因まで降りてこられる。
11.3 追跡すべき指標
Section titled “11.3 追跡すべき指標”正答率だけを見るのは不十分である。第7章7.7.2節でツール評価の指標を挙げたが、ここではエージェント全体の指標を整理する。
11.3.1 品質の指標
Section titled “11.3.1 品質の指標”| 指標 | 内容 | 測り方 |
|---|---|---|
| タスク完遂率 | 目標が達成されたか(第7章7.7.2節の「タスク完遂率」と同じもの) | 客観的な判定器、またはLLM判定 |
| ツール正確性 | 適切なツールを選んだか | 期待するツール集合との比較(決定的) |
| 引数正確性 | 引数は正しかったか | 期待値との比較、またはスキーマ検証 |
| ステップ効率 | 無駄なステップがなかったか | ステップ数、重複呼び出し回数 |
| 計画品質 | 立てた計画は妥当か | LLM判定(Planner-Executor の場合) |
| 計画遵守 | 計画どおり実行したか | 計画と実際の軌跡の照合 |
| 推論の一貫性 | 各判断が論理的につながっているか | LLM判定 |
| 出典の正しさ | 根拠が実在し内容と合致するか | 決定的な照合が可能(RAGの場合) |
ツール正確性は決定的に測れる点が重要である。LLM判定に頼る必要はなく、「このタスクではツールAとツールBを呼ぶべき」という期待値を用意して照合すればよい。順序を問うかどうか、回数を問うかどうかは用途次第で調整する。
11.3.2 運用の指標
Section titled “11.3.2 運用の指標”| 指標 | 見えるもの |
|---|---|
| 1タスクあたりのコスト | 第2章2.4節の試算が現実と合っているか |
| レイテンシ(P50 / P95) | 体感品質。P95が重要 |
| ステップ数の分布 | 上限に張り付いていないか |
| ツール別エラー率 | どのツールの設計が悪いか(第7章7.7.2節) |
| 上限到達率 | 予算やステップ上限で打ち切られた割合 |
| 人間へのエスカレーション率 | 自律的に解決できた割合 |
上限到達率は特に有用である。 これが高いなら、タスクが難しすぎるか、エージェントが非効率に動いている。第5章5.4.1節で「上限が発動したら設計を見直す」と述べたのは、この指標を見るためである。
11.3.3 指標の組み合わせで見る
Section titled “11.3.3 指標の組み合わせで見る”単独の指標は誤導する。組み合わせて解釈する。
| 観測 | 解釈 |
|---|---|
| 完遂率が高い + ステップ数が多い | 動くが非効率。ツールの粒度を見直す(第7章7.2.2節) |
| 完遂率が高い + ツール正確性が低い | 遠回りして偶然たどり着いている。脆い |
| 完遂率が低い + ツールエラー率が高い | ツールの説明かスキーマの問題(第7章) |
| 完遂率が低い + 上限到達率が高い | タスクが重すぎるか、停滞している(第5章5.4.2節) |
| コストだけが増加 | コンテキストが膨らんでいる(第8章8.3節) |
11.4 評価データセットを作る
Section titled “11.4 評価データセットを作る”11.4.1 良い評価タスクの条件
Section titled “11.4.1 良い評価タスクの条件”第7章7.7.1節で述べたとおり、評価タスクは現実的なワークフローに根ざし、複数のツール呼び出しを必要とするものにする。
✅ 良い評価タスク 「来週、田中さんと決済リニューアルの件で打ち合わせを設定して。 前回の設計レビューの議事録を添付し、会議室も押さえて。」
❌ 弱い評価タスク 「来週 tanaka@example.com と打ち合わせを設定して」前者は人物の特定、日程の空き確認、文書の検索、会議室の予約、予定の作成という連携を要求する。後者は単一のツール呼び出しで終わる。
11.4.2 データセットの構成
Section titled “11.4.2 データセットの構成”実務では、性質の異なる3つのセットを持つとよい。
| セット | 目的 | 作り方 |
|---|---|---|
| 回帰テストセット | 過去に直した失敗が再発しないこと | 本番で起きた失敗を毎回追加する。最も価値が高い |
| 開発セット | 改善の効果を測る | 想定ユースケースを網羅的に |
| ホールドアウトセット | 過学習していないことを確認 | 開発中は触らない |
回帰テストセットは、評価基盤の中で最も費用対効果が高い。 大規模なデータセットを最初から作る必要はない。「本番で失敗したケースを見つけたら、必ず評価セットに追加してから直す」という規律を守るだけで、実用的なセットが育っていく。
11.4.3 規模
Section titled “11.4.3 規模”厳密な統計を求めるなら数百件が必要だが、最初は20〜50件で十分である。それでも「変更したら3件壊れた」という情報は得られる。ゼロから始めるより、小さく始めて育てるほうがよい。
ただし件数が少ないと、揺れと改善の区別がつきにくい。同じ設定で複数回実行し、ばらつきを把握しておく。第2章2.3.1節で述べたとおり、temperature=0 でも完全な再現性はない。
11.5 ツールの単体テスト
Section titled “11.5 ツールの単体テスト”ツールは普通の関数なので、普通の単体テストが書ける。 ここは従来のソフトウェアテストがそのまま通用する数少ない領域であり、確実に押さえるべきである。
テストすべき観点は、第7章の設計指針に対応する。
import pytest
def test_正常系(): result = search_orders(customer_id="CUS-00123", limit=5) assert not result.is_error assert "CUS-00123" in result.content
def test_該当なしはエラーではない(): """0件は「失敗」ではない。モデルが次の手を打てる形で返す(第7章7.5.2節)。""" result = search_orders(customer_id="CUS-99999") assert not result.is_error assert "該当する" in result.content # 次にどうすべきかが書かれている
def test_引数不正のエラーメッセージが回復可能(): """エラーは「次に何をすべきか」を伝える(第7章7.5.1節)。""" result = search_orders(customer_id="不正な値") assert result.is_error assert "CUS-" in result.content # 正しい形式が示されている assert "不正な値" in result.content # 受け取った値が示されている
def test_出力に上限がある(): """コンテキストを溢れさせない(第7章7.4.2節)。""" result = search_orders(customer_id="CUS-00001", limit=50) assert len(result.content) <= 8000
def test_切り詰めたら明示される(): result = search_orders(customer_id="CUS-BIG", limit=10) assert "全" in result.content and "件中" in result.content
@pytest.mark.parametrize("path", [ "../../../etc/passwd", "/etc/passwd", "link_to_outside",])def test_パストラバーサルを拒否する(path): """セキュリティ上の境界は必ずテストする(第7章7.6.6節)。""" result = read_file(path) assert result.is_errorimport assert from 'node:assert/strict'import { test } from 'node:test'
test('正常系', () => { // Python のキーワード引数は位置引数にする const result = searchOrders('CUS-00123', 5) assert.ok(!result.isError) assert.ok(result.content.includes('CUS-00123'))})
test('該当なしはエラーではない', () => { // 0件は「失敗」ではない。モデルが次の手を打てる形で返す(第7章7.5.2節)。 const result = searchOrders('CUS-99999') assert.ok(!result.isError) assert.ok(result.content.includes('該当する')) // 次にどうすべきかが書かれている})
test('引数不正のエラーメッセージが回復可能', () => { // エラーは「次に何をすべきか」を伝える(第7章7.5.1節)。 const result = searchOrders('不正な値') assert.ok(result.isError) assert.ok(result.content.includes('CUS-')) // 正しい形式が示されている assert.ok(result.content.includes('不正な値')) // 受け取った値が示されている})
test('出力に上限がある', () => { // コンテキストを溢れさせない(第7章7.4.2節)。 const result = searchOrders('CUS-00001', 50) assert.ok(result.content.length <= 8000)})
test('切り詰めたら明示される', () => { const result = searchOrders('CUS-BIG', 10) assert.ok(result.content.includes('全') && result.content.includes('件中'))})
// pytest の parametrize に相当する仕組みは node:test にないため、// ループで名前を変えながら同じテストを登録するfor (const path of ['../../../etc/passwd', '/etc/passwd', 'link_to_outside']) { test(`パストラバーサルを拒否する: ${path}`, () => { // セキュリティ上の境界は必ずテストする(第7章7.6.6節)。 const result = readFile(path) assert.ok(result.isError) })}エラーメッセージの内容までテストするのが要点である。第7章で「エラーは回復のための情報である」と述べたが、それが守られているかは自動で検証できる。
セキュリティ境界のテストは特に重要である。パストラバーサル、SQLの複文、コード実行のサンドボックス — 第7章で扱ったこれらの防御は、テストがなければ次のリファクタリングで壊れる。
11.6 フローの統合テスト
Section titled “11.6 フローの統合テスト”ツール単体では正しくても、組み合わせると失敗することがある。統合テストではエージェントを実際に動かす。
11.6.1 何をアサートするか
Section titled “11.6.1 何をアサートするか”async def test_調査タスクの軌跡(): result = await run_agent( "先月の決済APIのエラー率が上がった原因を調べて", registry, SYSTEM_PROMPT, )
# ① タスク完遂(客観的に判定できるなら最良) assert result.status == "completed"
# ② ツール正確性 — 呼ぶべきツールを呼んだか called = {c.name for c in result.tool_calls} assert {"search_logs", "query_metrics"} <= called
# ③ ステップ効率 — 無駄に多くないか assert len(result.tool_calls) <= 12
# ④ 予算 — コストが想定内か assert result.cost_usd < 0.50
# ⑤ 出典 — 根拠が示されているか(決定的に検証できる) assert result.citations, "出典が含まれていない" for c in result.citations: assert document_exists(c.doc_id), f"存在しない文書を引用: {c.doc_id}"
# ⑥ 安全性 — 呼んではならないツールを呼んでいないか assert "delete_records" not in called/** 第5章のループが返す結果。テストから見たい項目を型にしておく。 */interface AgentRunResult { status: 'completed' | 'failed' | 'needs_user_input' toolCalls: { name: string; input: Record<string, unknown> }[] costUsd: number citations: { docId: string }[]}
test('調査タスクの軌跡', async () => { const result = await runAgent( '先月の決済APIのエラー率が上がった原因を調べて', registry, SYSTEM_PROMPT )
// ① タスク完遂(客観的に判定できるなら最良) assert.equal(result.status, 'completed')
// ② ツール正確性 — 呼ぶべきツールを呼んだか const called = new Set(result.toolCalls.map((c) => c.name)) // Python の集合演算 <= に相当する演算子はないため every で書く assert.ok(['search_logs', 'query_metrics'].every((n) => called.has(n)))
// ③ ステップ効率 — 無駄に多くないか assert.ok(result.toolCalls.length <= 12)
// ④ 予算 — コストが想定内か assert.ok(result.costUsd < 0.5)
// ⑤ 出典 — 根拠が示されているか(決定的に検証できる) assert.ok(result.citations.length > 0, '出典が含まれていない') for (const c of result.citations) { assert.ok(documentExists(c.docId), `存在しない文書を引用: ${c.docId}`) }
// ⑥ 安全性 — 呼んではならないツールを呼んでいないか assert.ok(!called.has('delete_records'))})⑥の観点を忘れないでほしい。 「やるべきことをやったか」だけでなく「やってはならないことをしなかったか」も検証する。第13章のセキュリティにつながる。
11.6.2 決定論的にできる部分は決定論的にする
Section titled “11.6.2 決定論的にできる部分は決定論的にする”エージェント全体は確率的だが、テストの一部は決定的にできる。
- ツールをモックする。外部APIに依存せず、固定の応答を返すツールに差し替える。これでツール側の揺れを排除できる
- 記録と再生(record/replay)。実際の実行を記録しておき、テストではLLMの応答を再生する。プロンプト以外の変更(ループのロジック、ツールのディスパッチ)を検証するのに有効で、速く安い
- シードを固定する。完全な再現性は保証されないが、ばらつきは減る
11.6.3 サンドボックスで実行する
Section titled “11.6.3 サンドボックスで実行する”第4章4.3.3節で「エージェントを採用するならサンドボックスでの十分なテストが前提になる」と述べた。統合テストは必ず隔離された環境で行う。
- 本番DBに接続しない(読み取り専用のレプリカか、テスト用データ)
- 外部への送信系ツールは無効化するかモックにする
- ファイル操作は一時ディレクトリに閉じる
- コード実行はコンテナ内に限る(第7章7.6.2節)
テストのつもりで走らせたエージェントが本番のレコードを消した、という事故は現実に起こりうる。
11.7 LLM-as-a-Judge
Section titled “11.7 LLM-as-a-Judge”11.7.1 なぜ必要か
Section titled “11.7.1 なぜ必要か”「回答が的確か」「要約が原文に忠実か」といった品質は、文字列一致では測れない。ここでLLMに評価させるのが LLM-as-a-Judge である。
用途は2つに分かれる。
- 参照あり(reference-based): 期待する出力や参照文脈と比較する。開発時の評価に向く
- 参照なし(referenceless): 入力と出力だけで判定する。本番監視に必須である。本番には正解ラベルがないため
11.7.2 判定を安定させる技法
Section titled “11.7.2 判定を安定させる技法”判定基準が曖昧だと、判定自体が揺れる。安定させるための技法がある。
① 評価手順を明示する
「良いかどうか判定して」ではなく、何をどう見るかを段階的に書く。criteria(基準)を一文で書く方式は試作には便利だが、本番の安定性を求めるなら evaluation steps(評価手順)まで明示する。
以下の手順で評価してください。
1. 回答に含まれる事実的な主張をすべて列挙する2. 各主張について、提示された文脈に根拠があるかを確認する3. 根拠のない主張が1つでもあれば 0、すべて根拠があれば 1 とする4. 判定の理由を、根拠のない主張を具体的に挙げて説明する② 判断を分解する
大きな判定を小さなノードに分け、決定的な分岐を持たせる方式(DAG的な構成)が有効である。特に**「必須の条件」がある場合**に効く。
構文が壊れている出力の「内容の質」を評価しても意味がない。機械的に判定できる部分は機械的に判定し、LLMには判断が必要な部分だけを任せる。 これはコスト削減にもなる。
③ 閉じた質問に分解する
「この要約は良いか」より、「要約に含まれる各文は原文に根拠があるか(Yes/No)」のほうが判定が安定する。開いた質問より閉じた質問のほうが、判定のばらつきが小さい。
11.7.3 既知のバイアスと失敗モード
Section titled “11.7.3 既知のバイアスと失敗モード”LLM判定は万能ではない。 系統的なバイアスが知られており、対策なしに使うと誤った結論を導く。
| バイアス | 内容 | 対策 |
|---|---|---|
| 位置バイアス | 2つの出力を比較させると、提示順で判定が変わる | 順序を入れ替えて2回評価し、一致しなければ引き分けとする |
| 冗長性バイアス | 長い出力を高く評価しがち | 評価基準に「簡潔さ」を明示的に含める。長さを揃える |
| 自己選好バイアス | 自分(同じモデル)が生成した出力を高く評価しがち | 生成と判定で異なるモデルを使う |
| 基準の曖昧さ | 判定基準が漠然としていると揺れる | 11.7.2節の技法を適用する |
| 甘い判定 | 全体に高いスコアをつけがち | 明確な減点条件を書く。人間のラベルで較正する |
最も重要な対策は、人間のラベルで較正することである。
11.7.4 判定器そのものを評価する
Section titled “11.7.4 判定器そのものを評価する”見落とされがちだが、判定器は評価対象であると同時に評価される対象でもある。
手順は次のとおりである。
- 50〜100件程度、人間が正解ラベルを付ける
- 同じデータをLLM判定器にかける
- 一致率を測る。分類問題として、適合率・再現率を見る
- 一致しなかったケースを分析し、判定プロンプトを改善する
偽陽性(実際は悪いのに良いと判定)は特に危険である。 問題が見えなくなるためである。偽陰性(良いのに悪いと判定)はノイズにとどまる。この非対称性を意識して閾値を決める。
人間との一致率が7割程度しかない判定器を信じて意思決定するのは危険である。一致率が低いなら、その指標は使わないほうがよい。
11.7.5 コストに注意する
Section titled “11.7.5 コストに注意する”第3章3.7.3節で触れたとおり、評価コストが本番の推論コストを上回ることさえある。1タスクの評価に複数の判定を走らせれば当然そうなる。
対策として、即時性の不要な評価には**バッチAPI(約50%割引、第2章2.2.2節)**を使う、判定には軽量モデルを使う、機械的に判定できる部分はLLMを使わない、といった手段がある。
11.8 Human-in-the-Loop 評価
Section titled “11.8 Human-in-the-Loop 評価”自動評価がどれだけ整っても、人間による評価は不要にならない。役割が違うからである。
11.8.1 人間評価の役割
Section titled “11.8.1 人間評価の役割”| 役割 | 内容 |
|---|---|
| 判定器の較正 | 11.7.4節。自動評価が信用できるかを決める基準になる |
| 未知の失敗の発見 | 自動評価は「測ると決めた指標」しか見ない。想定外の問題は人間しか気づけない |
| ドリフトの検出 | モデル更新やデータ分布の変化による、じわじわした劣化 |
| 主観的品質の判断 | 「この回答は担当者にとって役立つか」といった、業務文脈に依存する判断 |
11.8.2 現実的な運用
Section titled “11.8.2 現実的な運用”全件を人間が見るのは不可能である。サンプリングして継続的に見る体制を作る。
- 本番トラフィックから毎日一定数を無作為抽出してレビューする
- 自動評価で低スコアだったものを優先的に見る
- ユーザーからの否定的フィードバックがあったものは必ず見る
- レビューで見つかった失敗は、必ず回帰テストセットに追加する(11.4.2節)
最後の点が、人間評価と自動評価をつなぐ回路である。人間が見つけた問題が自動テストに変換されていくことで、評価基盤が育つ。
11.8.3 フィードバックの収集設計
Section titled “11.8.3 フィードバックの収集設計”ユーザーからの信号を集める仕組みも、評価基盤の一部である。
- 明示的なフィードバック(良い/悪いのボタン)
- 暗黙的な信号(やり直した、途中で止めた、結果をコピーした)
- エスカレーション(人間の担当者に引き継がれた割合)
暗黙的な信号は量が多く得やすいが、解釈に注意が要る。「やり直した」は不満の表れかもしれないし、単に別のことを聞きたくなっただけかもしれない。
11.9 評価ツールの現況(2026年7月)
Section titled “11.9 評価ツールの現況(2026年7月)”| ツール | 位置づけ | 特徴 |
|---|---|---|
| LangSmith | 評価 + トレーシング | LangChain/LangGraph と統合。データセット管理、実験比較、本番トレースからの評価 |
| DeepEval | 評価フレームワーク | pytest ライクな記述。エージェント向け指標(ツール正確性、タスク完遂、軌跡)が揃う。G-Eval / DAG による判定器構築 |
| Ragas | RAG評価 + エージェント評価 | 検索と生成の品質指標(忠実性、文脈適合性、回答関連性)に強い。加えてエージェント向け指標(ツール呼び出し正確性、目標達成度、話題遵守)も備える |
| Promptfoo | 評価・レッドチーム | 設定ファイル駆動。CI連携が容易。OpenAI Evals Platform の移行先として案内されている |
| MLflow | 実験管理 + 評価 | 既存のML基盤と統合したい場合 |
| Langfuse | トレーシング + 評価 | オープンソース。自前ホスティング可(第12章) |
注意: 第10章10.3.3節で見たとおり、OpenAI の Evals Platform は2026年6月3日に非推奨化され、2026年11月30日に停止する。移行先として Promptfoo が案内されている。評価基盤も例外なく変化するため、評価データセット自体はツールに依存しない形(JSONなど)で保持しておくことを勧めたい。
11.9.1 ツールを使うか自作するか
Section titled “11.9.1 ツールを使うか自作するか”最小限の評価なら自作で足りる。必要なのは、データセットを回してスコアを集計するループだけである。
async def run_eval(dataset: list[dict], agent) -> dict: results = [] for case in dataset: r = await agent(case["input"]) results.append({ "id": case["id"], "completed": r.status == "completed", "tools_ok": set(case["expected_tools"]) <= {c.name for c in r.tool_calls}, "steps": len(r.tool_calls), "cost": r.cost_usd, }) n = len(results) return { "完遂率": sum(r["completed"] for r in results) / n, "ツール正確性": sum(r["tools_ok"] for r in results) / n, "平均ステップ": sum(r["steps"] for r in results) / n, "平均コスト": sum(r["cost"] for r in results) / n, "失敗ケース": [r["id"] for r in results if not r["completed"]], }interface EvalCase { id: string input: string expectedTools: string[]}
interface EvalSummary { 完遂率: number ツール正確性: number 平均ステップ: number 平均コスト: number 失敗ケース: string[]}
async function runEval( dataset: EvalCase[], agent: (input: string) => Promise<AgentRunResult>): Promise<EvalSummary> { const results = [] for (const c of dataset) { const r = await agent(c.input) results.push({ id: c.id, completed: r.status === 'completed', toolsOk: c.expectedTools.every((name) => r.toolCalls.some((t) => t.name === name) ), steps: r.toolCalls.length, cost: r.costUsd, }) } const n = results.length return { // Python は真偽値をそのまま合計できるが、JavaScript では数えて割る 完遂率: results.filter((r) => r.completed).length / n, ツール正確性: results.filter((r) => r.toolsOk).length / n, 平均ステップ: results.reduce((a, r) => a + r.steps, 0) / n, 平均コスト: results.reduce((a, r) => a + r.cost, 0) / n, 失敗ケース: results.filter((r) => !r.completed).map((r) => r.id), }}これだけでも、プロンプトを変えたときの影響を測るには十分である。ツールが必要になるのは、実験の比較・履歴の管理・本番トレースとの連携が欲しくなってからである。
第10章10.5.2節で述べたフレームワーク選択と同じ考え方が適用される。まず自作し、具体的に困ってから導入する。
11.10 評価を回す仕組み
Section titled “11.10 評価を回す仕組み”11.10.1 CIに組み込む
Section titled “11.10.1 CIに組み込む”評価は、実行されなければ意味がない。プロンプトやツールを変更したら自動で走る状態にする。
ただし、すべての評価をすべての変更で走らせるのは高価である。段階を分ける。
| 段階 | 内容 | 実行タイミング |
|---|---|---|
| ツールの単体テスト | 決定的、高速、無料 | 全コミット |
| 回帰テストセット(小) | 20〜50件。外部ツールはモックし、LLM呼び出しは実行する | プルリクエスト |
| 開発セット(全件) | 実際のLLM呼び出し | マージ時、または日次 |
| ホールドアウト | 過学習の確認 | リリース前 |
11.10.2 何と比較するか
Section titled “11.10.2 何と比較するか”スコアの絶対値には意味がない。比較して初めて意味を持つ。
- ベースラインとの比較: 変更前のバージョン
- 前回リリースとの比較: 退行の検出
- 単純な手法との比較: 「エージェントを使わない場合」との比較。これが最も重要なことがある
最後の点を強調しておきたい。第4章4.5節で「LLMが不要な課題は多い」と述べた。エージェントが単純なワークフローに勝てているかを測ることは、技術選定そのものの検証になる。
11.10.3 モデルのバージョン変更に備える
Section titled “11.10.3 モデルのバージョン変更に備える”第2章2.6節で「モデルIDは固定スナップショットを指定する」と述べた。評価基盤があると、この運用が現実的になる。
新しいモデルが出たら、評価セットを走らせて比較してから切り替える。「新しいほうが良いはずだ」で切り替えると、特定のケースで退行していることに気づけない。
11.11 まとめ
Section titled “11.11 まとめ”- エージェントは出力も経路も確率的であり、統計的に測るしかない。「なんとなく良くなった」では改善できない
- 評価は最初のプロトタイプと同時に作り始める。 後回しにすると退行に気づけないまま複雑さが増す
- 評価には3つの深さがある。エンドツーエンドは何かがおかしいことを、コンポーネントはどこがおかしいかを教える
- 正しい答えでも軌跡としては失敗していることがある。 無駄なステップは、コスト・レイテンシ・信頼性の問題である
- 指標は組み合わせて解釈する。単独では誤導する
- ツール正確性は決定的に測れる。 LLM判定に頼らなくてよい部分は頼らない
- 回帰テストセットが最も費用対効果が高い。 本番の失敗を必ず追加してから直す、という規律だけで育つ
- 最初は20〜50件で十分。ゼロから始めるより小さく始めて育てる
- ツールは普通の関数なので普通の単体テストが書ける。エラーメッセージの内容とセキュリティ境界までテストする
- 統合テストでは「やってはならないことをしなかったか」も検証する
- 統合テストは必ずサンドボックスで行う
- LLM-as-a-Judge は評価手順を明示し、判断を分解し、閉じた質問にすると安定する
- 判定には位置・冗長性・自己選好のバイアスがある。生成と判定は別モデルにする
- 判定器そのものを人間のラベルで較正する。 一致率が低い判定器を信じて意思決定してはならない。特に偽陽性が危険
- 評価コストが本番コストを上回ることがある。 バッチAPIと軽量モデルを検討する
- 人間評価は不要にならない。未知の失敗の発見とドリフトの検出は人間にしかできない
- 評価データセットはツールに依存しない形で保持する。評価基盤も変化する(OpenAI Evals Platform は2026年11月30日に停止)
- スコアの絶対値に意味はない。ベースライン、前回リリース、そして「エージェントを使わない場合」と比較する
問1 あるエージェントの評価で次の結果が出た。それぞれについて、11.3.3節を参考に何が起きていると解釈できるか述べ、次に調べるべきことを示せ。
| ケース | 完遂率 | 平均ステップ数 | ツール正確性 | 上限到達率 | 平均コスト |
|---|---|---|---|---|---|
| (a) | 88% | 4.2 | 91% | 2% | $0.12 |
| (b) | 85% | 18.6 | 52% | 8% | $0.94 |
| (c) | 41% | 19.8 | 87% | 61% | $1.10 |
| (d) | 79% | 5.1 | 88% | 3% | $0.87 |
問2 次のツールについて、11.5節を参考に単体テストを最低6つ設計せよ。テスト名と、何を検証するか(アサーションの内容)を書くこと。第7章の設計指針のどれに対応するかも示せ。
def read_file(path: str, start_line: int = 1, end_line: int | None = None) -> ToolResult: """作業ディレクトリ配下のファイルを行範囲を指定して読む。"""// Python は docstring だけを本体にしてシグネチャを示せるが、TypeScript には// 本体のない関数定義がないため declare で型だけを宣言する。// declare には既定値を書けないため、省略可能な引数として表す/** 作業ディレクトリ配下のファイルを行範囲を指定して読む。 */declare function readFile( path: string, startLine?: number, endLine?: number | null): ToolResult問3 LLM-as-a-Judge で「回答が提示された文脈に忠実か」を判定させたい。
(a) 11.7.2節の3つの技法を適用した判定プロンプトを書け (b) この判定器を較正するための手順を、11.7.4節に沿って具体的に述べよ (c) 偽陽性と偽陰性のうち、この用途ではどちらがより危険か。理由とともに述べよ
問4 あるチームが「評価は後回しにして、まずプロトタイプを完成させる」という方針を取ろうとしている。11.1節と11.4.2節を踏まえ、この方針の問題点を3つ挙げよ。また、最小限の労力で評価を始めるとしたら何から着手すべきか、具体的に述べよ。
問5 本番監視のために参照なしのLLM判定を導入したところ、スコアは常に0.9前後で安定していたが、ユーザーからの苦情は増えていた。
(a) 考えられる原因を、11.7.3節のバイアスを参考に3つ挙げよ (b) この状況を検出するために、どのような検証をすべきだったか (c) 11.8節を踏まえ、自動評価だけに依存しない体制をどう作るべきか
問6 第2章2.6節で「モデルIDは固定スナップショットを指定する」と述べ、本章11.10.3節でその運用に評価基盤が必要だと述べた。新しいモデルへの移行を判断する評価計画を設計せよ。何を測り、どういう基準で切り替えを判断するか、退行が見つかった場合どうするかを含めること。
参考文献・出典
Section titled “参考文献・出典”| 出典 | 内容 | 参照日 |
|---|---|---|
| Confident AI — LLM Agent Evaluation Metrics (2026) | ツール正確性・ステップ効率・タスク完遂、エンドツーエンド/軌跡/コンポーネントの3階層、計画品質と計画遵守 | 2026-07-29 |
| DeepEval — LLM-as-a-Judge Guide | G-Eval / DAG / 閉じた質問への分解、参照あり・参照なしの区別、人間ラベルによる較正 | 2026-07-29 |
| Anthropic — Building Effective Agents | サンドボックスでの十分なテスト、測定に基づく改善、コーディングエージェントの客観的判定 | 2026-07-29 |
| Anthropic — Writing Tools for Agents | 評価タスクの作り方、正答率以外の指標、ホールドアウトセット | 2026-07-29 |
| OpenAI — Deprecations | Evals Platform の停止予定(2026-11-30)と Promptfoo への移行案内 | 2026-07-29 |
| roadmap.sh — AI Agents Roadmap | 章構成の基準、評価フレームワークの分類 | 2026-07-29 |
次章予告: 第12章ではデバッグと監視を扱う。評価が「良くなったか」を測るものだとすれば、デバッグと監視は「いま何が起きているか」を見るものである。構造化ログとトレーシング、そして可観測性ツールの現況を見ていく。
Built with Astro ・ Deployed on Cloudflare Pages
© 2026 watakumi — made with 💜 & ☕ ・watakumi.page