このページの目次
評価と観測可能性
エージェントの評価が難しい理由: 出力はオープンエンド、プロセスは非確定的、リンクは多段階。しかし、評価がなければイテレーションは不可能だ。評価(オフラインでの品質測定)と観測可能性(オンラインでのランタイム可視化)は互いに補完し合い、エージェントを「一見良さそう」なものから「測定可能で改善可能」なものへと進化させる。
概要
エージェントの評価が難しい理由は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アプリケーションでもそのまま適用できる。
両者をサイクルに組み込む
観測可能性は「どのステップ」に問題があったかを答え、評価は「全体」として良くなったかを答える。前者が欠けていれば、なぜ間違えたのかわからない。後者が欠けていれば、変更が本当に改善につながったのかわからない。どちらかが欠けていれば、イテレーションは盲目の変更となる。
ベストプラクティス
- 調整を開始する前に、小さく現実的な評価セットを持つこと。 代表的なタスクが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