15 分で読了 #ai #Agent
このページの目次

評価と観測可能性

エージェントの評価が難しい理由: 出力はオープンエンド、プロセスは非確定的、リンクは多段階。しかし、評価がなければイテレーションは不可能だ。評価(オフラインでの品質測定)と観測可能性(オンラインでのランタイム可視化)は互いに補完し合い、エージェントを「一見良さそう」なものから「測定可能で改善可能」なものへと進化させる。

概要

エージェントの評価が難しい理由は3つある: 出力がオープンエンド⁠(正解が一つではない)、プロセスが非確定的⁠(同じプロンプトでも結果が毎回異なる)、リンクが多段階⁠(1つのタスクで20回以上のツール呼び出しが発生する)。しかし、⁠評価がなければイテレーションは不可能だ⁠——プロンプトを変更し、モデルを置き換え、ツールを追加した際、なぜ「良くなった」と言えるのか?「ただ別の間違い方に変えただけ」ではないのか?ここでは、互いに補完する2つの側面について解説する:⁠評価(eval)——オフラインで反復可能に品質を測定する;⁠観測可能性(observability)——オンラインでエージェントのランタイム動作を明確にする。前者は「全体として良くなったか」に答え、後者は「どのステップで問題が発生したか」に答える。これらをイテレーション・サイクルに組み込むことで、エージェントは「一見良さそう」なものから「測定可能で改善可能」なものへと進化するのである。

評価: オフラインでの品質測定

flowchart LR
    D["eval 集<br/>代表性任务 + 期望/rubric"] --> R["跑当前版本"]
    R --> J["打分"]
    J -->|可编程?| P["代码断言"]
    J -->|开放式?| L["LLM-as-judge"]
    J -->|有完成标准?| O["outcome 评分"]
    P --> I["聚合 & 对比"]
    L --> I
    O --> I
    I -->|改 prompt/模型/工具| R

スコアリング手法は信頼性の高い順に適用し、前者で可能な限り後者は使用しないこと:

1. プログラマブル検証(最も信頼性が高い)

コードで判定可能なものは、絶対にモデルに任せない。「JSONを返す場合はスキーマに準拠しているか」「CSVにpriceという数値列があるか」「テストが通過するか」「特定キーワードが含まれているか」——直接アサートする。確実で、コストゼロ、バイアスなし。構造化出力(output_config.format / strict: true)を使用することで、この種の検証を安定して実行可能になる(プロンプトエンジニアリングと構造化出力参照)。

2. LLM-as-judge(オープンエンドの場合のみ使用)

要約の品質や回答が適切かどうかなど、プログラムで判定できない場合は、1つのモデルをジャッジとして使用する。重要なのはバイアスの低減である:

  • 明確なルブリックを提供する⁠: 「良いか悪いか」を独立して判定可能な項目に分解する(「出典が引用されているか」「3つのサブ質問すべてに回答しているか」など)。ジャッジに感覚で総合点を付けるのではなく、明確な基準を用いる。曖昧なルブリック → ノイズの多いスコア。
  • ジャッジと被評価対象を分離する⁠: 同じモデルが自分の出力を評価しないようにする(自己承認)。異なるモデルを使用するか、少なくとも独立したコンテキストをジャッジとして用いる。
  • 既知のバイアスに注意する⁠: ジャッジは長い回答を好む傾向があり、自社のモデルのスタイルを好む傾向があり、選択肢の順序に影響を受ける——ルブリックが具体的であればあるほど、これらのバイアスの影響は小さくなる。
  • スコアリング自体も評価する⁠: ジャッジの採点をサンプリングして人手で確認し、ジャッジが信頼できることを確認してから大規模展開する。

3. 成果ベース(完了基準がある場合)

Managed Agentsでは、「結果指向評価」がプリミティブとして実装されている。user.define_outcomeイベントを送信し、以下を含む:

  • description(タスク)、rubric({type:"text"|"file"},⁠必須⁠, 独立して判定可能な基準を記述)、max_iterations(デフォルト3、最大20)。

プラットフォームは独立したコンテキストのグラダーを使用して、iterate → grade → revise ループを実行する。各ラウンドで span.outcome_evaluation_start/ongoing/end イベントを発行し、end.result ∈ {satisfied, needs_revision, max_iterations_reached, failed, interrupted} となるまで、基準を満たすか上限に達するまで繰り返す。これは「評価+自動改善」をランタイムに内蔵するものである。レポート、モデル、ドキュメントなど、「完了時の状態」が明確なタスクに適している。

4. 回帰テスト

失敗を1つ発見するたびに、それを評価ケースとして固定する⁠。これにより、評価セットは時間とともに強化される——オンラインで1回失敗するたびに、オフラインに1つの防御ラインが追加される。

最も重要なプラクティス⁠: 調整を開始する前に、小さく現実的な評価セットを持つこと⁠。代表的なタスクが20件だけでも(既知の失敗や境界ケースを含み、ハッピーパスだけでなく)、単一の直感に頼るよりもはるかに優れている。

観測可能性: ランタイムの可視化

エージェントを本番環境にデプロイした後、以下の質問に答えられる必要がある: どのステップを踏んだか?どのステップが遅かったり失敗したりしたか?どのくらいのトークンを消費したか?なぜ停止したのか?

  • トレース / スパン⁠: 1つの実行を可視化可能なステップに分解する——各 tool_use/tool_result/思考/モデル呼び出しは1つのスパンとなる。Managed Agentsはイベントストリームを直接提供し、span.model_request_end には model_usage(今回の推論におけるトークン内訳)が含まれており、遅延とコストの段階的な帰属に使用できる。

  • usage(コスト+キャッシュ健全性): 各レスポンスの usage は以下を提供する:

    フィールド確認事項
    input_tokensキャッシュされていない入力(全額課金)
    output_tokens出力
    cache_read_input_tokensキャッシュヒット(約0.1倍);⁠長期間0の場合 = 静かな無効化要因が存在する⁠(コンテキストエンジニアリング参照)
    cache_creation_input_tokensキャッシュ書き込み(約1.25倍)

    注意: input_tokens はキャッシュされていない残量のみであり、合計 = 3つのフィールドの和——1つのフィールドだけ見て「トークンを使っていない」と思わないこと。

  • stop_reason の分布⁠: end_turn / tool_use / max_tokens / refusal の割合は健全性の指標である。max_tokens が高い = 出力が切り捨てられている(上限を引き上げるかストリーミングを使用)、refusal が異常に増加 = プロンプトが拒否トリガーを発動、pause_turn が多い = サーバー側ツールループが継続中。

  • OpenTelemetry: トレース/メトリクスを標準的なバックエンドにエクスポートして一元管理する。

実際の例: 当サイトの静的ジェネレーターはビルド時にOTelを初期化する——ビルドログの OTel tracer initialised, exporting to ... という行がそれである。この「各ステップをトレースし、各実行を定量化する」という考え方は、LLMアプリケーションでもそのまま適用できる。

両者をサイクルに組み込む

build run(带 trace) eval(打分) 看 trace 定位失败步 再 run —— 可观测答"哪步出了问题",评估答"整体变好没"

観測可能性は⁠「どのステップ」⁠に問題があったかを答え、評価は⁠「全体」⁠として良くなったかを答える。前者が欠けていれば、なぜ間違えたのかわからない。後者が欠けていれば、変更が本当に改善につながったのかわからない。どちらかが欠けていれば、イテレーションは盲目の変更となる。

ベストプラクティス

  • 調整を開始する前に、小さく現実的な評価セットを持つこと。 代表的なタスクが20件だけでも(既知の失敗や境界ケースを含み)、単一の直感に頼るよりもはるかに優れている。
  • スコアリングは信頼性の高い順に適用する。 プログラマブル検証 > LLM-as-judge > 成果スコアリング; assert で判定可能なものはモデルに任せない(構造化出力により、この種の検証を安定して実行可能にする)。
  • ジャッジと被評価対象を分離し、独立して判定可能なルブリックを提供する。 自己評価・自己承認を行わない。「良いか悪いか」を個別に判定可能な項目に分解し、ノイズとバイアスを低減する。
  • 失敗を1つ発見するたびに、回帰評価として固定する。 オンラインでの各失敗がオフラインでの防御ラインとなり、評価セットは時間とともに強化される。
  • 各ステップをトレースし、各実行を定量化する。 1つの実行をスパンに分解し、usage/stop_reason により遅延とコストを段階的に帰属する; OTelで標準バックエンドにエクスポートして一元管理する。
  • キャッシュヒット率を監視に組み込む。 cache_read_input_tokens が長期間0の場合 = 静かな無効化要因が存在し、コストが静かに倍増する。
  • 非確定的システムでは集計値を見よ。単一の直感に惑わされるな。 通過率、トークン分布、stop_reason の割合; バッチ結果は custom_id/トレースIDで帰属し、順序に頼らないこと。

トレードオフと失敗パターン

  • ハッピーパスのみをテストする⁠: 本番環境で境界条件に遭遇すると崩壊する → 評価セットには失敗/境界ケースを含め、事故に応じて成長させる(回帰テスト)。
  • ジャッジ = 被評価対象⁠: 自己評価・自己承認 → ジャッジと被評価対象を分離する; 対応するエージェントエンジニアリングの⁠「作成とレビューは別レーンで行う」マルチエージェントオーケストレーション参照)。
  • ルブリックが曖昧⁠: 「良さそうに見える」では安定してスコアリングできない → 独立して判定可能な項目を記述する。
  • 単一結果のみを見、分布を見ない⁠: 非確定的システムでは集計値(通過率、トークン分布、stop_reasonの割合)を見るべきであり、単一の直感に惑わされるな。
  • 帰属しない⁠: バッチ/並列結果がどの行に対応するか不明 → custom_id / トレースIDで帰属し、順序に頼らない(Batchesの結果は本来無順序である)。
  • キャッシュヒット率を監視しない⁠: コストが数倍になってから気づく → cache_read_input_tokens を監視ダッシュボードに組み込む。

参考文献

  • Anthropic 公式ドキュメント⁠: Building Evals、Managed Agents Define Outcomes & Observability、Handling Stop Reasons(platform.claude.com)

Keywords: eval, eval set, programmatic check, structured outputs, LLM-as-judge, rubric, judge bias, outcome-based, user.define_outcome, grader, span.outcome_evaluation, max_iterations, regression, observability, trace, span, model_usage, usage, input_tokens, output_tokens, cache_read_input_tokens, stop_reason 分布, OpenTelemetry, OTel, authoring vs review lane, custom_id