AIエージェントを本番運用していて、応答が遅い・出力がおかしいといった不具合の原因追跡に時間がかかっていないでしょうか。LLM(大規模言語モデル)呼び出しやツール実行を組み合わせるエージェント型アプリケーションは、通常のWebアプリより障害調査が難しくなります。処理が「エージェントの計画→モデル呼び出し→外部ツール呼び出し→記憶の読み書き」と多段に分岐するため、どこで時間を使い、どこで失敗したかが見えにくいからです。
この記事では、分散トレーシングの標準規格であるOpenTelemetry(略称OTel、アプリの処理経路を記録・収集するためのオープンな仕様とツール群)を使って、AIエージェントの内部処理を可視化する設計を、SREの視点から整理します。ローカル検証から本番エクスポートまでの流れと、監視設計に落とし込む際の判断基準を扱います。
エージェント特有のトレース構造をどう設計するか
OpenTelemetryの基本単位は「スパン(span)」です。1つの処理区間の開始・終了・付随情報を記録するデータの塊だと考えてください。
複数のスパンを親子関係でつなげたものが「トレース(trace)」で、1件のリクエスト全体の処理経路を表します。従来のWebアプリなら「HTTPリクエスト受信→DB問い合わせ→レスポンス返却」程度の浅い階層で済みますが、AIエージェントはそうはいきません。
最上位に agent.run(1回のユーザーリクエストやタスク全体)を置き、その下に次のような子スパンをぶら下げる構造が推奨されています。
agent.plan: エージェントが実行計画を立てるステップllm.chat: モデルへの1回の呼び出し(使用モデル名やトークン数を属性として記録)tool.call: 外部ツールや関数呼び出し(検索、API実行など)retrieval.query: ベクトル検索やDB照会などの情報取得memory.read/memory.write: エージェントの記憶領域の読み書きagent.handoff: 別エージェントや人間へのタスク引き継ぎagent.finalize: 最終応答の組み立て
この粒度で分割しておくと、「応答が遅い」という漠然とした問題を、「LLM呼び出しが遅いのか」「ツール呼び出しが詰まっているのか」まで切り分けられます。SREの障害対応では、この切り分けの速さがそのままMTTR(平均復旧時間)短縮に直結します。
収集経路の組み方とローカル検証手順
OpenTelemetryの標準的な構成は、アプリ本体からOTLP(OpenTelemetry Protocol、トレースデータをやり取りする標準プロトコル)でデータを送り、間に「OpenTelemetry Collector」というプロキシ的な中継サーバーを挟んでから、最終的な可視化基盤に転送する形です。
Agent Application
|
v OTLP (traces / metrics / logs)
|
v OpenTelemetry Collector
|
v Exporter
|
v Observability Backend (Jaeger / Grafana Tempo / Datadog など)Collectorを挟む理由は、アプリ側のコードを変えずにバッチ処理やフィルタリング、複数バックエンドへの同時送信を後から追加できるからです。ローカル検証時は、Collectorの設定ファイル(otel-collector-config.yaml)でOTLPを受信し、ログ出力するだけの最小構成が組めます。
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
exporters:
logging:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [logging]この設定ファイルをDockerコンテナにマウントして起動すれば、ローカルにOTLPエンドポイント(HTTPは4318番、gRPCは4317番)が立ち上がります。
docker run --rm -p 4317:4317 -p 4318:4318 \
-v "$PWD/otel-collector-config.yaml:/etc/otelcol/config.yaml" \
otel/opentelemetry-collector:latestPythonエージェント側は opentelemetry-sdk と opentelemetry-exporter-otlp をインストールし、TracerProvider にサービス名やバージョンを含む Resource を設定してから、start_as_current_span でスパンを生成していく流れになります。まずはこのローカル構成でトレースが正しく親子関係を持って出力されるかを確認するのが最初の一歩です。
監視ツールとの比較とSRE視点での使い分け
OpenTelemetryが登場する前は、Jaeger専用のクライアントライブラリやZipkin形式など、バックエンドごとに計装コードを書き分ける必要がありました。OpenTelemetryはこの計装部分を標準化し、送信先だけをCollectorの設定で切り替えられるようにした点が最大の利点です。
日本の現場でDatadogやNew Relic、Amazon CloudWatch、Google Cloud Traceといった商用APMを既に導入している場合でも、多くはOTLP受信に対応しているため、アプリ側の計装コードを変えずに乗り換え・併用が可能です。ベンダーロックインを避けたいチームにとっては、この移植性が採用理由になります。
コスト最適化の観点では、batch プロセッサでの送信間隔調整や、サンプリング設定(全リクエストではなく一定割合だけトレースを送る仕組み)が重要な調整ポイントになります。LLM呼び出しはトークン数や実行時間の属性を大量に持つため、全件記録すると転送量とバックエンドのストレージコストが膨らみやすい点は事前に見積もっておく方がよいでしょう。
今日から確認できること
既存のOpenTelemetry SDKやCollectorを使っている場合は、バージョンと計装範囲をまず点検してください。
- 使用中のOpenTelemetry Collectorのバージョンを
docker image inspectやヘルムチャートのvalues.yamlで確認し、公式リリースノートで破壊的変更がないか見る agent.runに相当する最上位スパンが存在するか、それとも個別のLLM呼び出しスパンだけがバラバラに送られていないかをトレース画面で確認するllm.chatスパンにモデル名・トークン数・レイテンシが属性として記録されているか(gen_ai.request.modelなどの属性キー)を確認する- サンプリングレートの設定値と、Collectorの
batchプロセッサのタイムアウト・サイズ設定を確認し、コストとのバランスを見直す - 障害発生時に、どのスパン単位でアラートやSLO(サービスレベル目標、可用性や応答時間の目標値)を設定しているかをダッシュボードで確認する
SLO設計との接続でいえば、agent.run の全体レイテンシを可用性・性能のSLIとして採用し、内部の llm.chat や tool.call は原因特定用の詳細メトリクスとして扱う切り分けが扱いやすい構成です。IaC(Infrastructure as Code)でCollectorやサンプリング設定を管理している場合は、TerraformやPulumiのモジュールにこれらの閾値をパラメータ化しておくと、環境ごとの調整も再現性を持って行えます。
まとめ
AIエージェントの可観測性は、通常のマイクロサービス監視の延長にありつつも、LLM呼び出しやツール実行という新しい処理単位への対応が必要です。
agent.runを頂点に、llm.chatやtool.callなどの子スパンで多段処理を可視化する構造を採用する- ローカルではCollector+ログ出力の最小構成で計装が正しく動くかを先に確認する
- 本番導入前にサンプリング設定とバッチ送信の調整でコストを見積もる
- 既存の商用APM環境でもOTLP対応状況を確認すれば、計装コードを変えずに移行・併用できる
まずは手元のCollector設定ファイルを開いて、receivers と exporters の設定がどのバックエンドを向いているかを確認するところから始めてみてください。