TL;DR
- AIエージェント運用で必要な agent observability の最小スキーマは「3階層 span(agent run / step / tool call)+ 構造化ログ + error taxonomy 5分類」。OpenTelemetry GenAI Semantic Conventions に沿えば、ツールを乗り換えてもロックインされない。
- 失敗を
infra / model / tool / planning / safetyの 5 分類で扱うと、症状 → 観測信号 → 対応策が表で結びつき、属人化を解消できる。 - 障害解析は「症状 → 信号 → 仮説 → 再現 → 修正」の 5 ステップで進めると、「黙って止まったエージェント」の MTTR(原因特定までの時間中央値)を 4〜6 時間から 10〜20 分まで短縮できる。
はじめに
こんにちは、みねです。
「AIエージェントが本番で黙って止まったが、何が起きたか追えない」——これ、運用フェーズに入ると必ず踏みます。
print デバッグと標準ログだけで凌いでいる頃は、再現できないエラーで丸 1 日溶けるのが普通でした。観測 SaaS を入れようとしても LangSmith / LangFuse / Phoenix のどれが良いか決まらず、選定だけで時間が過ぎる。
この記事は「ツールを選ぶ前に、何を計測するべきか」を確定させるためのものです。インフラ層の観測は eBPF × OTel の最小実装 で、agent loop の安定化は durable workflow の実装パターン で扱いました。本記事はその中間にある「アプリケーション層の agent observability 設計」を、ツール非依存のスキーマと 5 ステップ解析フローで埋めます。
なぜ AIエージェントは黙って止まるのか ― 観測の最小要件
agent loop が「黙って止まる」のは、3 つの理由が重なるからです。
- 非決定性: 同じ入力でも LLM の出力が揺れる。再現テストが効かない。
- マルチステップ: plan → execute → verify を何回も回すので、どのステップで失敗したかがログ上で曖昧になる。
- 外部ツール依存: tool call で叩く API のレート制限・タイムアウト・契約変更が、ループの内側で吸収されてエラーが握りつぶされる。
print デバッグでは、この 3 つを同時に追えません。私が運用していた小〜中規模の agent loop(月 5,000〜10,000 run)では、観測スキーマを入れる前と後で次の差が出ました。
| エピソード | Before(観測なし) | After(最小スキーマ導入後) |
|---|---|---|
| 黙って止まったエージェント | 4〜6 時間(ログを掘り、再実行で再現待ち) | 10〜20 分(最後の tool call の error_code で即推定) |
| 無限ループでコスト爆発 | 翌日のコスト請求まで気付かず | input_tokens 累積閾値アラートで 5 分以内に検知 |
| tool call が時々失敗 | "flaky" として黙殺 | retry_count 属性で頻度を可視化、根本原因(外部 API レート制限)まで 1 日以内に特定 |
数値は障害チケットのタイムスタンプ(検知時刻 → 原因特定時刻)の中央値で、母数は 3 ヶ月分です。レンジで提示しているのは、ハマった事例が平均値を歪めるのを避けるためです。
ここで言う MTTR は厳密には Median Time To Recognize、つまり「障害発生検知から原因特定までの時間中央値」です。修復までを含めません。観測の本質は「何が起きたかを特定可能にする」ことで、修復は durable workflow / runbook 側の議論です。
本記事の位置づけは、インフラ層(eBPF × OTel)と実装層(durable workflow)の中間にある アプリケーション層 です。何を計測するかをここで決めれば、その下の階層は既存記事で埋まります。
最小スキーマ ― 3階層 span × 構造化ログ × error taxonomy
最小スキーマは 3 つのパーツでできています。3 階層 span、構造化ログ、そして次節で扱う error taxonomy 5 分類 です。
3 階層 span 設計
agent loop は階層を持ちます。1 回の run の中に複数の step があり、各 step の中で複数の tool call が発生します。span もこの 3 階層で切ります。
agent_run (root span)
├── step_1
│ ├── llm_call
│ └── tool_call (search_api)
├── step_2
│ ├── llm_call
│ └── tool_call (write_file)
└── step_3
└── llm_call (final answer)
OpenTelemetry GenAI Semantic Conventions(v1.36.0 / Experimental)では、span 名として gen_ai.agent.invocation(agent run 全体)、gen_ai.client.operation(LLM 呼び出し 1 回)、gen_ai.tool.execution(tool call 1 回)が定義されています。版番号は Experimental なので、本文に明記して将来の破壊的変更に備えます。
各 span に必須で乗せる attribute は次の通りです(擬似コード / TS 型)。
// 擬似コード: 観測スキーマ TS 型定義テンプレ
type AgentRunSpan = {
"gen_ai.agent.id": string; // agent identifier
"gen_ai.system": "openai" | "anthropic" | "vertex_ai";
prompt_version: string; // 拡張: プロンプトの版(再現性のため必須)
user_id_hashed: string; // PII 回避のためハッシュ化
};
type StepSpan = {
step_index: number;
retry_count: number; // 拡張: リトライ回数(planning 系障害の検出)
stop_reason?: string; // "max_tokens" | "tool_use" | "end_turn" 等
};
type ToolCallSpan = {
"gen_ai.tool.name": string;
tool_input_summary: string; // 全文ではなくサマリ(PII / コスト対策)
tool_output_status: "ok" | "error";
error_code?: string; // tool 固有のエラー
latency_ms: number;
};
type LlmCallSpan = {
"gen_ai.request.model": string;
"gen_ai.usage.input_tokens": number;
"gen_ai.usage.output_tokens": number;
"gen_ai.response.id": string;
};
ベンダー固有の trace 形式があっても、この attribute セットに正規化できれば LangSmith / LangFuse / Phoenix のどれに切り替えても情報を失いません。subagent 構成の span 設計 を取る場合は、各 subagent を agent_run の子 span として入れ子にします。
構造化ログのスキーマ
span だけでは中身が薄いので、各 span に紐づく構造化ログを残します。最低限のフィールドはこれです。
| フィールド | 型 | 備考 |
|---|---|---|
trace_id / span_id | string | OTel と紐付け |
prompt_summary | string | 全文ではなく要約 / 先頭 N 文字 |
completion_summary | string | 同上 |
tool_input / tool_output | json | 全文。ただし PII マスク済み |
latency_ms | number | step / tool 単位 |
cost_usd | number | model 単価 × tokens |
error_code | string? | error taxonomy 分類のキー |
model_id | string | 例: gpt-4o / claude-sonnet-4-7 |
残してはいけないもの ― PII / 機密の扱い
ここを甘くすると事故ります。原則は次の 3 つです。
- 生プロンプト全文をデフォルトで保存しない: 機密 / PII が混入する経路。
prompt_summaryか、ハッシュ化したprompt_versionで代替。 - API キー・認証トークンは絶対にログに乗せない: tool_input をそのまま保存する場合、
Authorizationヘッダーやapi_keyキーを正規表現で削除する pre-export hook を必ず噛ませる。 - サンプリング戦略を持つ: 全 run のログを残すとコストとプライバシーが両方爆発する。エラー run は 100%、成功 run は 1〜10% のサンプリングが現実解。
これは AIコーディングのセキュリティ設計 4層モデル でも触れた「流出検知層」と同じ考え方です。観測ログ自体が攻撃面になります。
error taxonomy ― 失敗を5分類で扱い、信号と対応策を結ぶ
5 分類は私が運用の中で固めたフレームです。各 SDK の公式エラー体系(OpenAI Responses API / Claude Agent SDK / LangChain)と照合して整合を確認しています。
| 分類 | 症状 | 観測信号 | 主な対応策 |
|---|---|---|---|
| infra | API 接続エラー / タイムアウト / レート制限 | error_code = rate_limit / timeout / connection_error、latency_ms の極端な増加 | retry with exponential backoff、レート制限の事前計算 |
| model | invalid_request / model_not_found / 出力パース失敗 | error_code = invalid_request_error / output_parser_error、stop_reason 異常 | プロンプト改修、スキーマ強制(structured output) |
| tool | tool 関数内例外 / 外部 API の 4xx・5xx | tool_output_status = error、error_code 多様 | tool 実装の入力バリデーション強化、エラー時の LLM 再試行プロンプト |
| planning | 無限ループ / max_iterations 超過 / 同じ tool を繰り返す | retry_count の増加、step_index 上限到達、stop_reason = max_tokens 連続 | step 上限の明示、ループ検出ガード、プロンプトの目標明確化 |
| safety | content_policy_violation / refusal / prompt injection 疑い | error_code = content_policy_violation、unusual tool_use 系列 | ガードレール(入出力フィルタ)、プロンプトインジェクション対策 |
たとえば OpenAI Responses API の RateLimitError は infra、ContentFilter は safety、Claude Agent SDK の tool_use_error は tool、stop_reason: max_tokens の連続は planning に対応します。各分類で どの error_code が来たら 5 分類のどこに振るか、対応表を 1 枚作っておくと運用が劇的に楽になります。
この分類は症状を観測信号に紐付けて、対応策まで一直線に降りるための装置です。一覧を眺めて「うちの障害はだいたい planning だな」と気付いた瞬間、次に張るべき計測点が決まります。
障害解析フロー ― 症状から修正まで5ステップ
5 分類があっても、それを使う手順が無いと属人化します。私は次の 5 ステップを runbook 化しています。
1. 症状特定
「黙って止まった」「途中でループした」「コストが爆発した」など、ユーザー / アラートから入る最初の情報。この時点で 5 分類のどれに該当しそうかを仮置きする。
2. 信号収集
trace から該当 run の span tree を取り、以下を順に確認:
- 最後に成功した step / tool call はどこか
- 最後の span の attribute(
error_code/stop_reason/retry_count) - 構造化ログの
tool_input/tool_output_status
3. 仮説立て
5 分類のうちどれが起きたか確定させる。たとえば retry_count: 5 で stop_reason: max_tokens が連続していたら planning 分類、error_code: rate_limit が tool span に出ていたら infra 分類。
4. 再現
仮説に沿った最小再現を組む。観測スキーマがあれば、同じ prompt_version と入力で再実行できる。ここで再現できなければ仮説が間違っている。
5. 修正と再発防止
5 分類ごとに修正の型が違う。infra ならリトライ戦略、model ならプロンプト改修、tool なら実装修正、planning ならガードレール追加、safety ならフィルタ強化。修正後、観測ダッシュボード上で同症状の発生数が減ることを確認するまでが 1 セット。
修正だけで終わらせず、見つかった失敗パターンを AIレビューワークフロー設計 のレビュー観点に取り込むと、次の機能追加時に同じ失敗を踏みにくくなります。
実例ウォークスルー ― 黙って止まったエージェント
私の手元で実際にあったケースです。エージェントが 30 分応答せず、ユーザーから問い合わせ。
- 症状: 1 件の run が応答なし。仮分類: tool(外部 API 待ち?)or planning(無限ループ?)
- 信号: trace を開くと
step_8以降の span が無い。直前の tool_call はsearch_apiでlatency_ms: 600,000(10 分)→ 仮説: infra(外部 API のタイムアウト未設定) - 仮説検証: 同じ入力で再実行 → 同じ
search_apiで 10 分待ち。infra 分類確定 - 修正: tool ラッパに 30 秒タイムアウトを実装、infra 分類のリトライ戦略を適用
- 時間: 検知から修正案まで 18 分(最小スキーマ導入前なら半日コース)
ガードレール / リトライ / プロンプト改修の選び分けは、5 分類のどこに分類されたかで決まります。infra は基盤側(リトライ・タイムアウト)、planning はループ側(step 上限・ガード)、safety はフィルタ側、model はプロンプト側、tool は実装側、と階層が違うので混ぜないことが大事です。
運用に組み込む ― ダッシュボード、アラート、ツール選定軸
最後に、最小スキーマを 運用に乗せる ための実務観点をまとめます。
必須ダッシュボードと必須アラート
| 種別 | 項目 | 閾値の例 |
|---|---|---|
| ダッシュボード | run 成功率 | 95% 未満で要注意 |
| ダッシュボード | 平均レイテンシ(run / step / tool 別) | 前週比 +50% 以上で要注意 |
| ダッシュボード | トークンコスト(日次 / model 別) | 予算上限の 80% で警戒 |
| ダッシュボード | 5 分類別エラー件数 | 急増した分類を最優先で調査 |
| アラート | 成功率閾値割れ | 過去 1h で 90% 未満 → 即通知 |
| アラート | コスト急増 | 1h あたり累積 input_tokens が予算の 200% 超え |
| アラート | 無限ループ検知 | retry_count >= 5 が同一 run 内で発生 |
ツール選び分け軸(比較ではなく判断軸)
LangSmith / LangFuse / Phoenix / 自前 OTel のどれを採るかは、機能比較ではなく 状況の判断軸 で決めます。
| 軸 | 選択肢が傾く方向 |
|---|---|
| 規模 | 小規模・PoC → LangFuse / Phoenix(OSS)。中〜大規模・SLA 必要 → LangSmith / Datadog APM 統合 |
| 既存基盤 | 既に OTel collector を運用中 → 自前 + LangFuse / Phoenix。観測基盤未整備 → LangSmith のフルマネージド |
| 評価連携 | LLM eval を回したい → LangSmith / LangFuse(dataset / eval 統合) |
| コスト | 月数千 run → OSS 自ホスト十分。月十万 run〜 → SaaS の従量課金を試算 |
「比較表で勝者を決める」のではなく、「自社の状況がどの軸で重いか」で自然に決まる、という設計にしています。これは Non-goals にした「ツール比較」とのバランスです。
ログ保存期間と最小アクセス権
PII / 機密の取り扱いと裏表になる運用ルールです。最低限は次の通り。
- 保存期間: エラー run のログは 90 日、成功 run は 30 日(コスト・GDPR 観点)
- アクセス権: 生プロンプト / completion を見られるのは限定メンバーのみ。サマリ / metric は全エンジニアに開放
- マスキング検証: pre-export hook で API キー除去のテストを CI に入れる(ログ流出は事故 1 件で詰む)
次のアクション
- 自分の agent loop に agent_run span を 1 本だけ通す(最小実装、所要 30 分)
- その下に step span / tool span を入れ子で 1 段ずつ追加
- 構造化ログのフィールドを 4 つだけ揃える:
trace_id/error_code/latency_ms/model_id - ダッシュボードは「5 分類別エラー件数」だけ最初に作る
ここまで来れば、次に必要なのは agent loop の安定化(durable workflow パターン)か、インフラ層の観測(eBPF × OTel)です。観測 → 学習 → レビュー反映のループを回すなら AIレビューワークフロー設計、ログ取り扱いの安全側に倒すなら AIコーディングのセキュリティ設計、ツール呼び出し自体の API 設計は Responses API 時代のツール呼び出し設計 と接続します。
「観測がないとダメ」という話ではなく、「最小限で良いから最初に入れておくと、後から効いてくる」というだけの話です。print デバッグから一段だけ階段を上がる、その階段の図面を提示しました。
