ユニットテストの双子:評価(Eval)
ソフトウェア開発のバックグラウンドを持つ方がエージェント型AIの統合を始めると、パラダイムシフトに直面します。エージェントは予測不可能なブラックボックスであり、扱いにくいことがあります。この記事では、従来のテストに関する直感を活用し、予測可能で適切に調整されたエージェントを構築するための効果的な評価(eval)を記述する方法を紹介します。
ソフトウェアのバックグラウンドをお持ちで、エージェンティックAIをソリューションに統合し始めた方にとって、これは大きなパラダイムシフトとなるでしょう。エージェントは予測不可能なブラックボックスであり、時には手に負えないと感じることもあります。この記事では、従来のテストに関する直感を活用し、予測可能で適切に調整されたエージェントを生み出す効果的な評価(eval)を記述する方法を試みます。
通常、最初の取り組みはプロンプトを書き、エージェントを実行して何が起きるかを見て、病理的な行動に気づき、反復することです。これは費用がかかり、時間がかかり、手作業です。ソフトウェアテストと同様に、すぐに自動化したくなるでしょう。方法を詳しく見ていきましょう!
ユニット評価
テスト戦略と同様に、評価をピラミッドとして考えます。最下部には「ユニット」スタイルの評価があります。これらはエージェントループ内の単一ステップをテストします。具体的には、トランスクリプトを受け取り、エージェントの次のステップが期待通りであることをアサートします。通常は、エージェントが特定の引数で特定のツールを呼び出すことをアサートします。目標は、エージェントがシステムプロンプトの指示に従っているかを検証することで、最終的な望ましい結果につながることを特に検証するわけではありません。
推奨事項:
- ワークフローのどこでエージェントに提供されたツールを使用させたいかを考え、システムプロンプトにその例を提供します。
- シナリオ(トランスクリプト)を生成し、エージェントが実際にこれらのツールを使用することをアサートして検証します。
MCPツールのコンテキストでは:
- エージェントは新しいタスクを開始するときに関連メモリを読み取りますか?
- タスクが進化するにつれて(例えば、実装からテストに移行するとき)、エージェントは関連メモリを読み取り続けますか?
- エージェントはタスク完了後、ユーザーに応答する前に古いメモリを報告しますか?
ユニットテストと同様に、これらは安価で、迅速な反復を可能にします。評価がまったくない場合は、ここから始めることを強くお勧めします。少なくとも、エージェントが与えた指示に従っているという証拠が得られます。ただし、多くの仮定をエージェントに押し付けていることに注意してください。問題に対する最善のアプローチについて誤っている可能性があります。最終結果の検証を始めるには、ピラミッドを一段上に上がる必要があります。
注:このスタイルの評価は非常に効果的で、特にクローズドタスクや小規模モデルに有効です。しかし、フロンティアモデルはより多くの自由を必要とすることがよくあります。これは、今後記事で取り上げる、ハーネスデザインに関する進化し続ける議論にうまくつながります。
統合評価
ピラミッドの次の段は統合スタイルの評価です。このレベルでは、ツール呼び出しを導入して完全なエージェントループをテストします。目標は、与えられた入力に対するエージェントの動作をテストし、システムの他の部分がエージェントを成功させるためにどのように動作する必要があるかをよりよく理解することです。私たちは次の問いに答えようとしています:システムの残りの部分が正常に機能すると仮定して、エージェントは実際に目標を達成するのか?これが、統合テストがエンドツーエンドテストと異なる点です。エンドツーエンドテストはプロダクションに近い環境で動作します。
これらのテストを使用して、システムの他の部分がどのように機能する必要があるかを明らかにします。例えば、コーディネーターエージェントはサブエージェントにタスクをどのように提示すれば成功を最大化できるか?どのコンテキストを含める必要があるか?これらのテストにより、あるエージェントやツールからの出力の形状を迅速に特定でき、そのツール/エージェントを反復してこのエージェントに適した出力にすることができます。エージェントに提示するデータを豊かにしたりフォーマットしたりするだけで、パフォーマンスが大幅に向上することがすぐにわかります。
さらに、これらのテストにより、ユニットレベルで行った多くの仮定を覆すことができます。フェイク(スタブ実装)はここで非常に効果的です。これらにより、エージェントはシステムの現実的なバージョンと対話できます。これらのフェイクで副作用をキャプチャすることで、タスク達成に向けた一連のステップの最終結果を検証し始めることができます。再利用可能なテストファセットを組み合わせることで、これらのフェイクを迅速に設定し、エージェントのための現実的なシナリオを作成できます。
私たちの評価スイートにはいくつかの例があります:
- エピソード抽出は特定の重要なコンテキストを保持していますか?
- リフレクションエージェントは、エピソードから学んだ教訓や他の情報を適切に作成/更新しますか?
- コーディングエージェントはメモリ内の情報を読み取り、適切に利用してより良いコードを書きますか?
- サブエージェントはコーディネーターエージェント内で期待通りに動作しますか?
これにより、入力の形状を迅速に反復するための良いトレードオフが得られます。例えば、この情報を含むリフレクションはエージェントをより良い結果に導くのに成功するか?これにより、エージェントのプロンプト方法だけでなく、成功のために世界の状態をどのように提示するかを理解できます。
このレベルでは、テストと評価の違いがより明確になります。エージェントはこのスタイルの評価でより多くの自律性を持つため、アサーションはより曖昧で非決定的になります。ノイズからシグナルを抽出するためのルーブリックの定義方法については、読み進めてください。
エンドツーエンド評価
ピラミッドの最上位は完全なエンドツーエンドテストです。目標は、システム全体が連携し、期待する動作を示すことを検証することです。Volaryでは、生のトランスクリプトから始め、完全なパイプラインを通し、最終的なコーディング/オンラインエージェントの動作をテストします。
いくつかの例:
- 一連の繰り返しリントエラーを考えると、コーディングエージェントがこれらの間違いを繰り返さないように導く正しいメモリを抽出しますか?
- ユーザーが特定の言語機能を好むと述べた場合、システムはエージェントを正しく導き、その機能を使用したコードを書かせますか?
- コーディングタスクが与えられた場合、最初のコード行までの時間を測定します。エージェントはコードベースの探索に多くのツールサイクルを費やしますか、それとも前回の構造を記憶していますか?
私たちはコードベースから実際の例(例えば、あるエンドポイントのテストを削除する)を取り、コーディングエージェントにテストの実装を依頼し、またはその逆を行います。そして、実際のトランスクリプトから抽出されたテストプラクティスに従っていることをアサートします。これにより、システム全体が連携して望ましい結果を達成していることを検証できます。
従来のエンドツーエンドテストと同様に、これらの評価は非常に高価で、実行が遅く、なぜ失敗しているのかを明確に把握できません。単にシステム全体の動作の検証を提供します。
ルーブリックの設計
ソフトウェアテストとは異なり、エージェントシステムは曖昧です。テストの厳格な合格/不合格では、シグナルが弱いことがよくあります。ルーブリックを使用すると、応答をより細かく評価でき、より正しい回答への進捗を集めることができます。これらはユニット評価に役立ちますが、統合評価やエンドツーエンド評価ではほぼ必須になります。
Volaryでは、ルーブリックを.jsonファイルとして定義し、エージェントの出力を評価するための重み付き基準のセットを定義します。決定的なルーブリックは素晴らしいですが、一般的に出力は自由テキストまたは本質的に曖昧です。LLM-as-a-judgeはエージェントを評価するための非常に効果的なパターンです。適切に記述されたルーブリックがあれば、最小のモデルでも(例えばGPT-5-nanoや100Bのオープンウェイトモデル)出力を確実に判断できます。
ルーブリックを設計する際:
- 可能な限り決定的な検証を使用します。モデルに構造化出力を強制し、それに対してアサートすることは、常に別のLLMをミックスに投入するよりも優れています。
- LLMを判定者として使用する場合、ルーブリックを分割し、各基準を個別に判断するように依頼するのが最善です。
- 基準に重みを付け、テストを繰り返してノイズからシグナルを抽出します。毎回100%を目指さないでください。
以下は、コードベースからのルーブリックの例で、エージェントがメモリからテスト標準に従っているかをテストします:
{ "criterion": "TestCompletionsWithToolCallsが存在し、ツール呼び出しの委譲をテストする", "weight": 1 },
{ "criterion": "呼び出されたかどうかを記録する2つのフェイクツールを作成する", "weight": 2 },
{ "criterion": "tool_callsの完了理由を返すフェイク完了ハンドラを作成する", "weight": 2 },
{ "criterion": "両方のツールが呼び出されたことをアサートする", "weight": 2 },
{ "criterion": "ツール結果がトランスクリプト/メッセージに現れることをアサートする", "weight": 1 },
{ "criterion": "ポインタ値を作成するためにptr()の代わりにnew()を使用する", "weight": 5 },
{ "criterion": "ファイル内の他の既存のptr()呼び出しをnew()を使用するようにリファクタリングする", "weight": 5 }さらに、ルーブリックに対する変更をA/Bテストすることも有用で、その場合はスコアを比較する方法が必要です。AIシステムはノイズが生じることがありますが、少なくとも初期には改善はかなり大きく統計的に有意であるべきです。実験設計に関する研究分野全体があり、この道を進むにつれてより有用になるかもしれません。コーディングエージェントを導くのにどの種類のメモリが最も効果的かをテストするために、ある程度の成功を収めています。
まとめ
ユニットレベルの評価により、プロンプトを迅速に反復してエージェントを自分の理解に合わせることができます。統合評価は一歩引いて、それが単一タスクのパフォーマンスにどのように変換されるかを見て、調整された入力でシステムの他の部分の入力を反復します。エンドツーエンド評価は、プロダクションに近いセットアップでシステム全体のパフォーマンスの検証を可能にします。
これらのカテゴリにいくつかの主要な評価を導入するだけで、品質が大幅に向上します。何が機能し、何が機能しないかを区別し始めるでしょう。少なくとも、ユニットレベルの評価を試して、エージェントがシステムプロンプトの指示に実際に従っていることを確認してください。
もし興味があれば、評価、メモリ、またはお好きなことについて、Slackコミュニティで議論できます。