⚖️

動作確認の自動化で学ぶ LLM as a Judge — 「操作するAI」と「判定するAI」を分ける理由

に公開

1. はじめに

前回の記事では、自然言語で「何を確認したいか」を書くだけで AI がシミュレータを操作してテストする仕組みの全体像を紹介しました。

https://zenn.dev/kyoichi/articles/ai-qa-agent-01-overview

実装を進める中で、最も苦労したのが「テスト結果の判定」でした。AI にシミュレータを操作させること自体はうまくいったのですが、操作した AI 自身に「合格か不合格か」を判定させると、どうしても甘い判定になってしまいます。

この記事では、その問題を「LLM as a Judge」というパターンの観点から解説します。本質は「AIの出力を別のAIが評価する」というシンプルな設計ですが、その効果を大きく左右するのが心理学の 確証バイアス(Confirmation Bias) という概念です。この考え方は動作確認に限らず、コードレビューやコンテンツ評価など、AI に判定を任せるあらゆる場面に応用できます。

LLM as a Judge とは

LLM as a Judge は、AI システムの評価で一般的に用いられるパターンです。主な用途としては:

  • 応答品質評価: チャットボットの回答が「有用か」「安全か」を別のモデルで採点
  • コンテンツモデレーション: 生成テキストが基準を満たすか判定
  • RLHF(人間フィードバックからの強化学習)の代替: 人間の代わりに LLM が評価スコアを付与

本パターンを UIテストの合格(Pass)/不合格(Fail)判定に応用したのが、この設計です。「実行エージェントが操作した結果を、判定エージェントが独立に評価する」という構造を採用しました。

この分離が重要である理由を理解するには、最初の統合型設計での問題を振り返る必要があります。

統合型設計の問題:確証バイアス

確証バイアスの定義

心理学における 確証バイアス(Confirmation Bias) は以下のように定義されます:

人は自分の信念や仮説を支持する情報を選択的に集め、矛盾する情報を無視または軽視する傾向がある。

テスト実行の文脈では、「このボタンをタップするとメッセージが送信される」という期待を持ちながら操作すると、送信成功の証拠を優先的に認識し、失敗の証拠を見落とします。

この認知的バイアスは人間特有ではなく、LLM にも同様に作用します。

最初の統合型設計での問題

最初の実装では、1つのエージェントが操作・報告・判定のすべてを担当していました。このエージェントはテストケースの期待結果を知った状態で操作を行います。

テストケースの期待結果例:

## 期待結果
- 送信したメッセージ "Hello" がチャット画面内に表示されている
- AI からの応答テキストが1つ以上表示される
- 応答のストリーミングが完了している

この設計での実行結果は 判定不能(Inconclusive)100% でした。

原因は、エージェントが操作開始前に「期待される最終状態」を把握していたため、「Hello が表示されるはず」という期待を持ちながら画面を観察していたことです。その結果、期待に合致する証拠は優先的に認識される一方、確実な根拠となるUI要素一覧が不十分になり、判定段階では情報不足で判定不能に陥りました。

具体的には、「送信したメッセージが表示されたはず」という暗黙の仮定のもと、実際にUI要素一覧に対象要素が存在するかの確認が甘くなったのです。

人間のテストプロセスにおける実行と判定の分離

人間のテストプロセスでは、実行者と判定者を分離することが標準的なベストプラクティスです。自分が実行したテストは「成功したはず」という心理的バイアスが作用しやすく、特にテスト設計者が実行と判定を兼ねると、無意識に甘い判定が生じやすくなります。コードレビューでも同様に、自分のコードの自己レビューでは見落としが増加することが知られています。

LLM にも同等の認知的バイアスが存在することが実装を通じて明らかになったため、ios-qa-agent の設計思想を根本から変更する必要が生じました。

分離設計:qa-runner と qa-judge

改善後の実装では、エージェント群を2つの役割に明確に分割しました。

qa-runner:実行と報告

テスト実行エージェント(qa-runner)のプロンプトには、期待結果セクションを含めません。これが最も重要な設計判断です。

受け取る情報:

  • テストケースの前提状態と操作意図
  • App Map(アプリのUI構造を記録するナレッジファイル)
  • 実行制約パラメータ(max_actions, timeout_seconds)

受け取らない情報:

  • テストケースの「期待結果」セクション

qa-runner の責務は客観的な事実報告に限定されます。返却する構造化レポート例:

## 実行レポート: TC-001

### Status: completed

### Actions Performed
1. ホーム画面 → tap "Compose" → チャット画面
2. チャット画面 → type_text "Hello" → 入力確認
3. チャット画面 → tap "send" → メッセージ送信

### Final State
- 画面: チャット画面
- UI要素一覧:
  - StaticText "Hello" (userMessage) — 表示中
  - StaticText "こんにちは!..." (assistantMessage) — 表示中
  - ActivityIndicator: not present

### Observations
- 送信後、約8秒でストリーミング完了

qa-runner は合格/不合格/判定不能といった判定を下しません。操作内容と観察結果のみを報告します。

qa-judge:判定

結果を判定するエージェント(qa-judge)が受け取る情報:

  • テストケースの期待結果セクション
  • qa-runner の実行レポート全文

qa-judge が保有していない情報:

  • 実行プロセスの体験(操作を実施していないため)

この「クリーンなコンテキスト」が設計の要です。qa-judge は実行プロセスを通じたバイアスを保有しません。実行レポートを読み込み、期待結果と対照するのみです。

判定方式は Item-by-Item Assessment(項目ごとの照合)で行います:

### Item-by-Item Assessment

| # | 期待項目 | 判定 | 根拠 |
|---|---------|------|------|
| 1 | "Hello"がチャット画面に表示 | 合格 | UI要素一覧に StaticText "Hello" (userMessage) が存在 |
| 2 | AI応答が1つ以上表示 | 合格 | UI要素一覧に assistantMessage が存在 |
| 3 | ストリーミング完了 | 合格 | ActivityIndicator: not present を確認 |

### 最終判定: 合格
### 信頼度: 高

各期待項目ごとに「どの根拠から判定したか」を明示することで、判定プロセスの追跡可能性を確保します。

判定精度の設計原則

分離設計が解決するのは確証バイアスだけではなく、全体的な判定精度にも影響します。qa-judge には 偽 Pass リスクを最小化する という設計思想があります。

テスト判定の誤りは2種類存在し、リスク度が異なります。

テスト判定における誤分類:

  • 偽合格: 不具合があるのに「通過」と判定
  • 偽不合格: 正常に動作しているのに「失敗」と判定

偽不合格は人間による確認で対応可能なため、修正コストは限定的です。一方、偽合格は「テスト成功」という安心感のまま本番リリースが進行し、ユーザーに不具合機能が配信されます。検出の遅延に比例して復旧コストが増大します。

判定ルール

qa-judge の判定ロジック:

全期待項目が合格 → 最終判定: 合格
1項目でも不合格 → 最終判定: 不合格
1項目でも情報不足 → 最終判定: 判定不能

判定不能は「人間による再確認が必要」を示すシグナルであり、偽合格より安全です。

信頼度評価も保守的に設計:

信頼度 基準
全期待項目に高品質な根拠がある
一部項目で単一情報源に依存、または視覚的確認のみ
根拠が不足、またはUI要素一覧が不十分

信頼度が低い合格判定は判定不能と同等に扱われます。これが実装上の重要な規約です。

報告品質の検証

qa-judge は判定の前に qa-runner の報告品質を検査します:

  • 操作結果に関連する要素がUI要素一覧に記録されているか
  • 状態遷移を示す要素が記録されているか
  • ストリーミング操作後の ActivityIndicator 有無が明記されているか

この検査で1項目でも不満足な場合、信頼度を低に降格します。つまり、qa-runner が情報不足で報告した場合、qa-judge は「判定不可」と正確に報告することになります。

これにより、最初の設計の問題が根本的に解決されました。「情報不足なまま判定しようとする」のではなく、「情報が不足していることを理由に判定不能と明示的に判定する」という明確な方針になったのです。

分離設計の効果

実際にテスト対象のアプリで実行したところ、分離の効果は明確でした。

統合型の設計では、判定不能が100%でした。操作したエージェント自身が判定するため、証拠の収集が甘くなり、結局「情報が足りなくて判定できない」という結果になっていたのです。

分離後は、不合格を正しく検出できるようになりました。たとえば、API クレジットが枯渇して AI の応答が返ってこない状況を、統合型では見落としていましたが、分離後の判定エージェントは「応答テキストが存在しない」という事実を客観的に捉えて不合格と判定しました。環境の問題をテストで初めて検出できたのです。

問題を解消した後は、高い信頼度での合格判定が安定して出るようになりました。

LLM as a Judge の設計原則

ios-qa-agent の実装経験から導出した、LLM as a Judge パターンの汎用的な設計原則を以下に整理します。

原則1:実行エージェントに「期待状態」を隠す

確証バイアスを排除する最も直接的な対策。本実装では qa-runner に期待結果を渡しません。

応用例:

  • コードレビュー AI: PR 説明文を見せずコード自体のみをレビュー。「実装内容」を独立に評価
  • データ検証 AI: 「この値は正しい」という前提なしにデータを検証
  • コンテンツフィルタリング: 作成者の意図を知らず、テキスト自体のみを評価

原則2:判定エージェントに「過程」を過度に提供しない

実行者のプロセスログ(試行錯誤、失敗、復旧の詳細)を判定者に渡すと、判定が感情的情報に影響されます。「苦労の末にうまくいった」という文脈が判定を歪める可能性があります。

本設計では qa-judge は「最終状態の要約」のみを受け取ります。実行過程の大変さは判定に影響しません。

応用例:

  • 自動採点 AI: 学習者の試行錯誤プロセスではなく最終解答のみ評価
  • 作品評価 AI: 創作背景や作者の意図ではなく作品そのものを評価
  • 論文査読 AI: 著者の苦労や研究プロセスではなく最終論文品質を評価

原則3:「判定不能」を有効な判定結果として設計

「合格または不合格のいずれかを出さねばならない」というプレッシャーが、最も危険な誤判定(偽合格)を生みます。

「判定できない」という正直な回答を第一級の判定結果として扱うことで、システム全体の信頼性が向上します。

応用例:

  • 応答品質評価: 「良くも悪くもない」ケースを正直に判定不能と報告
  • コンプライアンスチェック: 「不足情報で判定不能」を有効な結果として処理
  • セキュリティ診断: 確信を持たない場合は「判定保留」と報告

汎用的な応用シナリオ

LLM as a Judge は QA にとどまらず、以下の領域で同様の効果が期待できます:

  • コードレビュー: 実装 AI と レビュー AI の完全分離(実装側はレビュー基準を知らない)
  • コンテンツモデレーション: 生成側と評価側の独立したコンテキスト保持
  • データパイプライン検証: 変換 AI と 検証 AI の明確な責務分離
  • 採点・評価システム: 生成側と採点側のバイアス隔離

全領域で共通するのは「生成」と「評価」を分離し、評価者に生成プロセスのバイアスを持ち込まないという思想です。

まとめ

「生成した側が自分で評価すると甘くなる」——これは AI でも人間でも同じです。実行と判定を分離し、実行側に期待結果を見せないだけで、判定の信頼性は大きく変わります。

この原則は動作確認に限った話ではありません。コードレビュー、コンテンツ評価、採点——AI に判定を任せるあらゆる場面で、「生成と評価の分離」は有効な設計パターンです。

次回は、AI がテストを繰り返すたびにアプリの画面構造を覚えていき、次回はより少ないステップで正確に操作できるようになる「自己進化」の仕組みを解説します。

参考リンク

Discussion