レッスン3: 決定的な検証器: pass/fail を出せる検査だけが有効
学習目標:
- 「最も速く、最も信頼でき、最もスケールする」を基準に検証器の候補を並べ、特定の出力に合うものを選ぶ
- エージェントが読んで反復できる形で pass/fail を返す、決定的な検証スクリプトを書く
- 厳しすぎる検証器が正しい出力を弾く偽陰性を見抜き、正規化で直し、決定的な検査の天井がどこにあるかを理解する
前提: レッスン1と2(実行できる検査がなければ「完了に見える」が唯一の信号になること、終状態を優先しプロセスは補助にすること、成功基準は測定可能でなければならないこと) | 前: << レッスン2 | 次: レッスン4 >>
「何を検証するか」から「どう検証するか」へ
レッスン2を終えた時点で、具体的な成功基準が書き出せているはずです。本レッスンで使う例を取り上げましょう。エージェントが一群の売上CSVを読み、タイトル、チャネル別の項目、合計を持つ report.json に集約する、というものです。レッスン2で学んだのは、この基準を終状態として枠づけること——ファイルが存在する、フィールドが揃っている、合計が項目の件数の総和と一致する——であって、「ファイルを読み、次に合計を計算し、次にファイルを書く」といったターンごとのプロセス確認ではありません。
基準は手元にあります。次の問いは実務的なものです。その基準と突き合わせるために、何を使うのか?
目視で確認することもできます。別のモデルに読ませてフィードバックをもらうこともできます。あるいは、JSONを読み込んで件数を合計し、一致しなければ非ゼロで終了する十数行のNodeを書くこともできます。3つとも結論には辿り着きますが、コストと信頼性は大きく違います。
公式ドキュメントは順位付けの原則を示しています。最も速く、最も信頼でき、最もスケールする採点方法を選ぶ1。このものさしで測ると、3つのカテゴリははっきり順番に並びます。
- コードベースの採点——最も速く最も信頼でき、極めてスケールする。弱点は、ルールに縛られない柔軟さを要する複雑な判断のニュアンスを扱えないこと1。
- LLMベースの採点——速く柔軟で、スケールし複雑な判断にも向くが、まず信頼性を確かめてからスケールさせること1。(レッスン4で扱います。)
- 人間による採点——最も柔軟で品質も高いが、遅く高価。可能なら避けること1。
ここでの「信頼できる」は同じ入力なら毎回同じ判定という意味です。コードベースの採点が首位なのは賢いからではなく、予測可能な形で愚かだからです——今日は雰囲気で通し、明日は言い回しで落とす、ということをしません。コンピューティングにおいて、決定的システムは同一の入力に対して毎回同じ出力を生成しますが、非決定的システム——エージェントのような——は同じ開始条件でも異なる応答を生成しうる2。本レッスンで扱う「決定的な検証器」は、その予測可能に愚かな検査のことです。決定的なものが、非決定的なものを評価するわけです。
評価タスクの設計に関わる原則がもう1つあります。自動採点が可能になるように問題を構成する(たとえば多肢選択、文字列一致、コードによる採点、LLMによる採点)1。自動化の目があるところでは、それを取りにいってください。
検証器のメニュー: 信号を返すものなら何でもよい
「検証器」と言うと専用のフレームワークのように聞こえますが、ハードルはずっと低いところにあります。公式ドキュメントの定義はほとんど無愛想なくらいです。検査とは、Claude が会話の中で読み取れる信号を返すもの全般である。テストスイート、ビルドの終了コード、linter、出力を fixture と diff するスクリプト、あるいはデザインと比較するブラウザのスクリーンショット3。
このリストを分解しましょう。npm test を走らせて全部グリーンなら合格で、コードの成果物には最適です——コードによる解決策は自動テストで検証可能です4。ビルドの終了コードは最も手軽です。ツールチェーンがすでにアサーションを書いてくれているからです。linter だけでは十分ではありませんが、「犯すべきでないミス」の床としては優秀です。diff スクリプトは、今回の実行の出力を、あらかじめ用意した fixture ファイル(正解と分かっているサンプル)と比較するもので、出力が安定していて形式が固定の場合に向きます。スクリーンショット比較は、フロントエンドのスタイルを調整しているときのためのものです。
工学的な実践の注記を1つ。コンパイラや静的型チェッカー(tsc --noEmit のような)も、終了コードと読みやすいエラー位置を出すため、実際のプロジェクトではこの用途でよく使われます。これは私の補足であって公式のリストにはありません——公式のお墨付きとして扱わないでください。
これらの方法は排他的ではありません。検証器はスペクトラムをなします——一方の端は「fixture との完全な文字列一致」、もう一方の端は「Claude に判定を頼む」です2。本レッスンは左半分を扱い、レッスン4が右へ進みます。
最小形: output == golden_answer
スペクトラムの左端はこう見えます1。
等値比較があるだけです。これは完全一致(exact match)と呼ばれます。モデルの出力があらかじめ定めた正解と一致するかを測るもので、通常は空白と大文字小文字を正規化したうえで行います。単純で曖昧さのない指標で、感情分析(positive、negative、neutral)のように答えが明確に区分されるタスクに最適です1。
ここでの「正規化」は平たい話です。比較する前に、意味を担っていない差異を消しておくということです。コードにするとこうなります。
2つ目の呼び出しの結果はじっと見る価値があります。trim() と toLowerCase() は改行と大文字小文字を救いますが、あのピリオドは救いません。正規化をどこまで進めるべきかは、あなたのタスクにとってどの差異が無関係かで決まります——どんなライブラリも代わりに下せない判断です。本レッスン後半の落とし穴の節は、その判断を誤ったときに何が起きるかの話です。
検証器の出力を読めるものにする
先ほどの定義には飛ばされがちな一節がありました。信号はClaude が会話の中で読み取れるものでなければならない3、という部分です。この一節が、検証スクリプトの出力の書き方を決めます。2つの失敗メッセージを比べてみてください。
前者はエージェントに「間違っている」とだけ告げ、あとは推測に任せます。後者はどのフィールドが落ちたか、期待値は何か、実際の値は何かを告げます——次のターンでその数値を直接直しにいけます。同じ pass/fail でも、情報量は桁違いです。公式ガイダンスの別の一節も同じことを言っています。成功を主張するのではなく、Claude に証拠を示させる——テスト出力、実行したコマンドとその返り値、あるいは結果のスクリーンショット。証拠をレビューするほうが自分で検証をやり直すより速く、見ていなかったセッションでも機能する3。あなたの検証スクリプトは、その証拠の生産者です。それが曖昧なら、証拠も曖昧になります。
実務的な規則を2つ。合格は終了コード 0、不合格は非ゼロを使うこと(CIやシェルの && がそのまま利用できます)。標準出力には1行に1つの失敗を、「どのフィールドが、何を期待し、何だったか」を明記して書くこと。
pass/fail があると、エージェントの振る舞いが変わる
実行中、エージェントには環境からの信号が必要です。"During execution, it's crucial for the agents to gain “ground truth” from the environment at each step (such as tool call results or code execution) to assess its progress."(実行中、エージェントが各ステップで環境から「ground truth」——ツール呼び出しの結果やコード実行の結果など——を得て自らの進捗を評価することが決定的に重要である)4。検証器がなければ、得られる進捗の信号は自分が今書いた段落だけです——完了したと思えば、完了なのです。検証器があれば、環境の中に、自分の判断から独立した事実の出どころが存在することになります。
そこで振る舞いの連鎖が変わります。pass か fail を出すものを Claude に渡せば、ループは自分で閉じる。Claude が作業し、検査を実行し、結果を読み、検査が通るまで反復する3。言い換えれば、エージェントはテスト結果をフィードバックとして解を反復できる4、ということです。
本シリーズのコース7で手書きしたようなハーネスループでは、実装は検査をツールとして公開することになります。
検証器の出力は tool_result を経由して会話に流れ戻ります。モデルは total が不一致: 申告値 48、items の count 合計 50 を読み、次のターンでそれを直しにいきます。あなたは1ステップも関与していません。
便利な拡張が2つあります。その1、検査は末尾だけに置く必要はありません。 レッスン2で、複雑なワークフローは、すべての中間ステップを検証するのではなく、特定の状態変化が起きているはずの個別のチェックポイントに分割できると扱いました5。それらの検証チェックポイントは、決定的な検査を置く自然な着地点です——「すべてのCSVを読み終えた後、行数は個々のファイルの行数の総和と一致するはず」のように。同じ振り返り記事は、エージェントの適応性を、リトライロジックや定期的なチェックポイントといった決定的な安全策と組み合わせることにも触れています5(そこでの「チェックポイント」は、本シリーズのコース9で扱った状態保存型のリカバリーチェックポイントを指す点に注意してください)。
その2、1種類の検証はAPI層まで前倒しできます。 ツール定義に strict: true を加えると、Claude のツール呼び出しが常にスキーマと厳密に一致することを保証できます6。スキーマは、あなたによるパラメータ構造の宣言です。
この1行があるだけで、「フィールド名のタイプミス」「count を文字列で渡した」といった構造上の問題は、「自分でコードを書いて確認するもの」からプラットフォームレベルの保証へと変わります。ツールは、決定的システムと非決定的エージェントのあいだの契約であり2、strict はその契約をインターフェースに書き込む方法です。
ただしこれが取り締まるのは構造であって、意味ではありません。total が実際にすべての count の総和と等しいかについて、スキーマは何も語れません——その部分は依然として自分で検証します。
落とし穴: 厳しすぎる検証器は正しい出力を弾く
これが決定的な検査の最もよくある壊れ方であり、しかも起きても気づかないことが多いものです。
ツール評価に関する公式ガイダンスは鋭いです。書式、句読点、有効な言い換えのような見せかけの差異のせいで正しい応答を弾いてしまう、厳しすぎる検証器は避けること2。
「見せかけの差異」が鍵となる語です。同じ正しい答えでも、末尾に空白が付いているかもしれないし、positive を Positive と書くかもしれないし、単語のあいだの空白が1つではなく2つかもしれません。これらの差異はタスクにとって何の意味も持ちませんが、バイト単位で比較する検証器にとっては致命的です。壊れ方の向きも陰湿です。エラーを通してしまうのではなく、正しい出力を罰するのです——これが偽陰性です。
具体的な破綻と修正を示します。fixture のタイトルは 2026 年 Q1 チャネル集計 です。エージェントの report.json はデータがすべて正しく、ただタイトルの前後に空白があり、語のあいだの空白が2つ多いだけです。素朴で厳格な検証器はこうなります。
実行すると、実際の出力はこうです。
内容が完全に正しいレポートが、末尾の改行1つで落とされました。この結果がエージェントに戻されれば、タイトルの空白をいじりにいきます——タスクとは無関係な方向にです。
修正は関数1つです。
replace(/\s+/g, " ") は連続する空白を1つに畳み、trim() は前後を削り、toLowerCase() は大文字小文字を揃えます。この変更後、同じファイルが通ります——完全なスクリプトと実際の実行結果は、以下の演習のレベル2にあります。
逆方向の注意も1つ。正規化は「多ければ多いほど良い」ものではありません。句読点まで削ってしまうと、total: 48 と total: 4.8 のような本物のエラーをならしてしまうかもしれません。判断基準はいつも同じです——この差異は意味を担っているか? 担っているなら厳しく。担っていないなら正規化してください。
決定的な検査の天井はどこか
決定的な検証器には、はっきりした適用範囲の境界があります。
第1の境界は自由記述テキストです。 調査の成果物は自由記述テキストであり、唯一の正解を持つことがまれなので、プログラムによる評価が困難です5。要約に対して output == golden_answer を書くことはできません——同じ素材を与えられても、よく書けた2本の要約はまったく異なる言い回しを使いうるからです。この種の判断はレッスン4の領域に渡ります。
第2の境界は「より広いシステム要件に合っているか」です。 コードによる解決策は自動テストで検証可能ですが、"However, whereas automated testing helps verify functionality, human review remains crucial for ensuring solutions align with broader system requirements."(しかし、自動テストは機能の検証に役立つ一方で、解決策がより広いシステム要件に沿っていることを保証するには、人間によるレビューが依然として決定的に重要である)4。あるパッチが全テストに合格しながら、モジュール全体を保守不能にする悪い設計であることはありえます。マルチエージェントの振り返り記事も同じことを響かせています。自動評価のある世界でも、手作業のテストは依然として不可欠です5。
決定的な検査が受け持つのは「間違っていてはいけないものが間違っていない」という床の部分です。その床より上には、別の道具が必要になります。
釣り合い: 何にでも検証器が要るわけではない
逆向きの誤りもよくあります。使い捨てのスクリプトのために完全な検証スイートを構築してしまうことです。
エージェントが一度実行したら削除するデータ移行スクリプトを書いたのに、あなたは構造検証、fixture 比較、リグレッションサンプルを整備する——検証器を書く時間が、出力を目で確かめる時間を上回っています。
判断のものさしは相変わらずあの一文です。複雑さの追加を検討すべきなのは、それが結果を明らかに改善する場合だけである4。検証器に限って言えば、自分に問うべきことがあります。
- この検査は何回実行されるのか?1回きりで、しかもあなたがすぐそばで見ているなら、目のほうが速いかもしれません。
- それがない場合、エラーが発見されるまでどれくらいかかるのか?「すぐ、目の前で見ているから」と「下流が壊れたとき、2日後」では、結論はまったく変わります。
- このタスクでプロンプトを何回反復する予定か?1回を超えた瞬間から、前後を比較する安定したものさしが必要になります。さもないと「良くなったのか」は常に当て推量のままです。
一方、書かなければならないのはいつかについて、公式ガイダンスは厳しい言い方をしています——常に検証手段(テスト、スクリプト、スクリーンショット)を用意すること。検証できないなら、リリースしない3。
💻 演習
まとめ
- 採点方法の順位付けの原則は「最も速く、最も信頼でき、最もスケールする」です。コードベースの採点は3つすべてで首位に立ち、引き換えに、ルールに縛られない柔軟さを要する複雑な判断のニュアンスを扱えません1。
- 検査は、Claude が会話の中で読み取れる信号を返すものなら何でもよいのです。テストスイート、ビルドの終了コード、linter、出力を fixture と diff するスクリプト、デザインと比較するスクリーンショット3。
- 検証器はスペクトラムをなします。左端は fixture との完全な文字列一致、右端は Claude に判定を頼むこと2。最小形は
output == golden_answer だけで、通常は空白と大文字小文字を正規化したうえで行い、答えが明確に区分されるタスクに最適です1。
- pass/fail があれば、ループは自分で閉じます。作業し、検査を実行し、結果を読み、通るまで反復する3。それは、エージェントが進捗を評価するために各ステップで環境から「ground truth」を得る必要があるからであり4、テスト結果をフィードバックとして反復できる理由でもあります4。
- 複雑なワークフローは、すべての中間ステップを検証するのではなく、特定の状態変化が起きているはずの個別の検証チェックポイントに分割できます5。構造検証の1種類は、
strict: true でAPI層まで前倒しして、ツール呼び出しがスキーマに厳密に従うようにすることさえできます6。
- 最大の落とし穴は厳しすぎる検証器です。書式、句読点、有効な言い換えのような見せかけの差異のせいで、正しい応答を弾いてしまいます2。修正は、先に正規化してから比較し、厳しさは本当に意味を担う部分のために取っておくことです。
- 決定的な検査には天井があります。調査の成果物はプログラムによる評価が困難であり5、自動テストは機能を検証しますが、"human review remains crucial for ensuring solutions align with broader system requirements"(解決策がより広いシステム要件に沿っていることを保証するには、人間によるレビューが依然として決定的に重要である)4、そして自動評価が成熟していても手作業のテストは依然として不可欠です5。
- 使い捨てのスクリプトに完全な検証器を作らないこと——複雑さの追加を検討すべきなのは、それが結果を明らかに改善する場合だけです4。ただし逆向きに、検証できないなら、リリースしないこと3。
>> レッスン4: LLMを裁判官にする: ルーブリック、フォーマット、判定させてはいけないもの