なぜ今AIエージェントのテレメトリ削減が急務なのか

自律型AIエージェントは、与えられた目的に対して推論とツール実行を自律的に繰り返すループ構造を持ちます。OpenAI Agents SDKなどの実装例では、モデルの各ターンを turn_span()、LLM推論を generation_span()、関数のツール呼び出しを function_span() として個別のスパンに分割して記録します。そのため、エージェントが推論と実行のループを反復するほど生成されるスパン数が累積し、推論のたびに会話履歴全体を送信する構成を採用している場合は、過去のコンテキストがターンごとに繰り返し記録される傾向があると整理されています[1]。

OpenTelemetryのGenAIセマンティック規約では、gen_ai.input.messages や gen_ai.output.messages が推論スパンに記録可能なオプトイン属性として定義されています[2]。また、規約にはトレース本体から独立した詳細保持手段として、会話履歴や各種パラメータを gen_ai.client.inference.operation.details イベントに記録する形態も示されています[2, 3]。さらにツール実行スパンに対しても、渡された引数や実行結果を記録するオプトインのペイロードが規定されています[4]。

こうした詳細な会話ログやツール実行結果をそのまま収集すると、通信量とストレージ消費が急速に増大します。CNCFのメンバーブログが報告したClaude Codeの実験では、小規模なコード修正タスクにおいて内容記録関連のフラグを有効にしたところ、1回の実行だけで181件のテレメトリレコードと289 KBのJSONデータが出力されました[5, 6, 7]。

テレメトリの肥大化はデータ容量だけでなく、メトリクスの時系列数(カーディナリティ)にも深刻な影響を与えます。カーディナリティとは属性値のユニークな組み合わせの総数を指します[8]。同実験では、25セッション・8種類のメトリクスを収集した環境において、未最適化の状態では300系列の時系列が生成されました[8, 9, 10]。OpenTelemetry SDKの標準仕様では、メトリクスごとの既定カーディナリティ上限が1コレクションサイクルあたり2,000属性組み合わせに設定されています[11]。この上限を超過した場合、溢れたデータは overflow 系列へ集約されるため、属性ごとの詳細な内訳情報が欠落する原因になります[11]。

後輩Webサービスのトレースと同じ感覚でエージェントを計装すると、なぜ急にデータ量が跳ね上がるんですか?

先輩一般的なWebリクエストは1往復で処理が完結しやすいけれど、自律型エージェントは1回のタスクの中で推論とツール呼び出しを何度も繰り返すからだね。推論ごとにこれまでの会話履歴を丸ごと送り直す構成だと、同じプロンプトや文脈がスパンごとに重複して記録される構造になるよ。

後輩リクエスト回数そのものが少なくても、1回の実行に含まれるスパン数と属性データの蓄積が従来の通信とは大きく異なるわけですね。

収集・中継・保存の責任分離:Collectorの機能とバックエンドの保持ポリシー

テレメトリの削減に取り組む際は、データを収集・中継するOpenTelemetry Collectorの役割と、データを受理して蓄積するバックエンドの責任境界を明確に整理することが求められます。Collector内部では、receiverがテレメトリを受信し、パイプラインが定義する経路に沿って設定順にprocessorが加工を行った後、exporterが外部の保存先へと送信します[12, 13]。Collectorによるデータ削減処理は、中継層での受信後かつ外部送信先への出力前に実行されます[13]。

一時的な送信障害に備える機構として、exporterには送信キュー(sending queue)が備わっています。送信先のエンドポイントが利用できない場合、未送信のデータはメモリ内のキューに保持されて再送が試みられます[14]。さらにキューの永続化拡張として file_storage を指定した場合、WAL(Write-Ahead Log)がローカルディスクへ書き出され、Collectorが再起動した際もディスクからデータを読み戻して送信を継続できます[15]。ただし、キューの容量超過や再試行期限の超過が発生した場合はデータが破棄され、ローカルディスクの障害や容量枯渇によるデータ損失も生じ得ます[14, 16, 17]。

このように、Collectorの永続キューは送信失敗時のローカルな再試行を担う中継領域であり、送信先が受理した後のデータ保持期間や削除、ストレージ課金を制御する機能とは適用される層が分かれます[18]。

保存先となる可観測性バックエンドでは、それぞれ固有の保持期間と課金体系が設定されています。例えばGrafana Cloud Tracesでは、処理済みデータ量(processed)、書き込みデータ量(written)、保持データ量(retained)を課金指標とし、最低保持期間は無料アカウントで14日、有料アカウントで30日に定められています[19]。既定の保持期間は30日であり、最低期間を超える保持には追加課金が発生します[19, 20]。誤って送信されたトレースデータの削除は、サポート窓口への申請を通じて実施されます[20]。また、AWS X-Rayでは記録済みトレースと取得・スキャンされたトレースが個別の課金対象として計上され、それぞれ月間10万件および100万件の無料枠が提供されています[21]。トレースデータの保持期間は30日間です[18]。

区分保持・再試行の仕組み保持期間の決定要因課金・削除の扱い
OpenTelemetry Collector(中継層)メモリ上の送信キューおよびローカルディスクのWAL(file_storage)[14, 15]送信先障害時の再試行期限(超過時は破棄)[14, 17]保持費用は発生せず、Collectorのローカルメモリおよびディスクリソースを消費[15, 18]
Grafana Cloud Traces(保存層)クラウドストレージへの永続書き込みとインデックス化[19]最低保持期間(無料14日、有料30日)および既定30日設定[19, 20]processed / written / retainedに基づくGB課金。削除はサポート依頼[19, 20]
AWS X-Ray(保存層)AWSマネージドインフラでのトレース保持(保持期間30日)[18]プラットフォーム固定(30日間)[18]記録件数およびスキャン件数に応じた課金(無料枠あり)[21]

テレメトリ加工技術の比較とパイプライン設計

Collectorの中継パイプラインにおいてテレメトリを軽量化する手法には、属性の削除、切り詰め、ハッシュ化、イベント除外、サンプリングといった複数のアプローチが存在します。それぞれの技術特性とトレードオフを理解したうえで組み合わせることが重要です。

属性削除は、指定したキーを削除したり許可リスト方式で必要な属性のみを残したりする処理です[22, 23, 24]。送信データ量を直接的に抑制できますが、削除した属性を手掛かりとする検索や事後検証は行えなくなります[24]。

切り詰め(truncate)や正規化は、文字列の長さを制限(例:512文字上限)したりパスの一部をマスクしたりすることで、大まかな構造を残しつつデータ量を削る手法です[4, 25, 26]。全文検索や厳密な再現性は低下するものの、大枠の入出力形式やエラー文脈の把握を維持できます[25]。

ハッシュ化は、属性キーを保ったまま属性値をSHA-1などのハッシュ値に置換する処理です[26, 27]。同一値の照合には活用できますが、元の文字列の可読性は失われ、属性値の種類数そのものを削減する処理ではない点に留意が必要です[26]。

イベント除外は、filter processorを用いて特定のspan eventを条件一致で破棄する処理です[22, 28]。すべてのspan eventが除外された場合でも親スパン自体は無傷で維持されるため、実行時間やステータスコード、残存属性を手掛かりとした追跡可能性を維持できます[28, 29]。一方、ツール実行スパン(execute_tool)そのものを除外した場合は、スパン単位の呼び出し証跡が完全に消失します[29]。

サンプリングには主に確率サンプリングとTailサンプリングがあります。Trace IDに基づく確率サンプリング(probabilistic sampler)は、トレース全体の整合性を保ちながら一定割合を選別できますが、トレース終了前に判定を行うため、まれに発生するエラーや高遅延のトレースを確実に残す用途には不向きです[30]。サンプリング判断に必要な情報が得られない場合、既定の設定(fail_closed: true)ではデータを拒否します[31]。

対照的にTailサンプリング(tail_sampling processor)は、トレース完了後にステータスコードや実行時間、属性値を確認して保持判定を行います[32, 33, 34]。エラーが発生したトレースや遅延が閾値を超えたトレースを選択的に残すことが可能です[33, 34]。しかし、判定が完了するまで全スパンをCollectorのメモリ上に保持する必要があり、同一トレースの全スパンを単一のCollectorインスタンスに集約しなければなりません[32, 33, 34, 35]。Collectorに到達する入力ペイロード自体の容量や待機中のメモリ負荷は軽減されず、バッファ容量不足による早期破棄や最大トレースサイズ超過による即時破棄のリスクを伴います[32, 33, 36]。複数インスタンスで分散配置する場合は、Trace IDに基づくルーティング機構の導入が必要です[35]。

加工手法主な対象データ処理後の残存情報調査性・メモリ負荷への影響
属性削除・許可リストスパン・ログ・メトリクスの属性[22, 24]許可リストに含まれる指定属性のみ[24]該当属性による検索・集計が不可能になるが、中継負荷を速やかに低減[24]
切り詰め(Truncate)プロンプト、ツール出力、差分本文[4, 25]上限文字数(例: 512文字)までの先頭データ[4]入出力の概況やエラー発生箇所は追えるが、完全な全文再現性は低下[25]
ハッシュ化ユーザー識別子、セッションID等[26, 27]固定長ハッシュ値(照合用キー)[26]同一性の追跡は可能だが可読性は喪失。値の総種類数は減少しない[26]
スパンイベント除外スパン内の詳細ログ、ツール中間イベント[22, 28]親スパンの実行時間、状態、保持属性[28, 29]親スパンのメトリクスや成否は残るが、内部のやり取り詳細は消失[28, 29]
確率サンプリングトレース全体(Trace IDベース)[30]抽出されたトレースの全スパン[30]エラーや高遅延などの希少トレースを狙って残すことが困難[30]
Tailサンプリング完了したトレース全体[32, 34]条件(エラー・遅延・特定属性)に合致したトレース[33, 34]調査対象の精度は高まるが、判定待ちスパンによるCollectorメモリ負荷が増大[32, 33]

コンポーネントの安定性ステータスについて、公式READMEの現行情報では、attributesおよびtransformプロセッサはtraces、metrics、logsでbeta、filterはalpha、tail_samplingはtracesでbeta、probabilistic_samplerはtracesでbetaかつlogsでalpha、redactionはtracesでbetaかつlogsおよびmetricsでalphaと位置付けられています[37, 38]。なお、attributesプロセッサはtransformプロセッサへの移行が案内されています[38, 39]。

パイプライン内の処理順序に関して、OpenTelemetry公式では memory_limiter を最優先で先頭に配置し、不要データを破棄する処理を早期に実行し、コンテキストに依存する処理をバッチ化の前に置き、batch プロセッサはフィルタリングや変換の後に配置することを推奨しています[40]。Tailサンプリングと組み合わせる実務設計では、判定に必要な属性を事前に整理したうえで、機密情報の削除・切り詰めや不要イベントの破棄を先行して行い、その後にTailサンプリング判定を実施する順序が適しています[41]。これにより、判定材料を確保しながら待機バッファ内のデータ量を最小限に抑えることができます[32, 41]。

また、transformやfilterプロセッサにおけるOTTL(OpenTelemetry Transformation Language)の評価エラー時の挙動(error_mode)には注意が必要です。既定値である ignore では、ステートメントでエラーが発生してもログを記録して次の処理へと進みます[42, 43]。一方、propagate を指定した場合はエラーがパイプラインへ返され、該当ペイロード全体がCollectorから破棄されます[42, 43]。

補足:OTTLとは

OpenTelemetry Transformation Languageの略称です。Collectorの各種プロセッサでテレメトリの属性やフィールドを抽出・変換・削除するための条件式とステートメントを記述する構文規則です。

後輩バックエンドへの送信量を減らしたいなら、末尾で判断するTailサンプリングを真っ先に入れれば済む話に見えますが、どうして手前で加工するんですか?

先輩Tailサンプリングはトレース全体の成否や遅延を見極めるまでスパンをメモリに抱え込むからだよ。大きなプロンプトや差分本文を載せたまま溜め込むと、Collector自身のメモリが圧迫されてバッファから溢れてしまうんだ。

後輩下流の転送量を減らせても、中継地点のCollectorがパンクしたら本末転倒ですね。手前で不要なイベントや巨大な本文を削ぎ落としてから保持判定に回す必要があるわけですね。

機密情報・プライバシー保護の境界と「削減」「匿名化」の違い

AIエージェントのテレメトリには、プロンプトに含まれる顧客データやAPIキー、ツールが取得したソースコードの差分など、機密性の高い情報が含まれるリスクが常に伴います。OpenTelemetryのGenAI規約では、指示・入力・出力の本文内容を既定では記録しない方針が明示されています[44, 45, 46]。容量肥大化やプライバシー侵害が懸念される本番環境においては、本文内容を別のアクセス制御が施された外部ストレージに隔離保存し、トレースのスパン上には一意な参照識別子(reference)のみを記録する構成が推奨されています[44, 45, 46, 47, 48]。

計装元における収集設計の確認も欠かせません。規約上、入力・出力メッセージやツールの呼び出し引数・実行結果はすべてオプトイン属性(gen_ai.tool.call.arguments、gen_ai.tool.call.result など)として定義されています[49]。Claude Codeなどのエージェントツールでは、ユーザプロンプトやツール内容を出力する個別フラグに加え、APIのリクエストおよびレスポンスJSON全体をそのまま出力するフラグも存在します[50]。利用しているクライアントツールのバージョンと設定可能なフラグ一覧を個別に確認し、不要な機密情報が最初から送出されないよう設計することが基本となります[50]。

中継層での処理に関して留意すべきは、Collectorによるデータ削減処理と法的な匿名化は明確に要件が異なる概念であるという点です。属性の削除、切り詰め、ハッシュ化を適用したという事実単体では、法令や公的基準(NIST SP 800-188やGDPRなど)を満たす匿名化の根拠にはなりません[51]。残されたログ本文やスパン内の他属性との組み合わせにより、特定の個人が再識別可能であるかどうかを評価する必要があります[51]。特に暗号論的ソルトや鍵を用いない単純なハッシュ処理(あるいは鍵が漏洩した環境)では、IPアドレスのようなエントロピーの低い識別子候補に対して総当たり攻撃を行うことで容易に元の値が復元される恐れがあります[26, 52]。追加情報を用いて個人に結び付けられるデータは、規制上も個人データとして扱われ得ます[52]。

さらに、Collectorプロセッサの処理範囲と監査属性の限界も認識しておく必要があります。Redactionプロセッサはスパン、ログ、メトリクスデータポイントの「属性」を主たる対象として動作します[53]。ツールの実行結果やファイル内容がスパンイベントやログ本文の内部構造、あるいは独自属性のどこに格納されているかによって適用されるプロセッサが異なるため、Collectorが一律にすべての機密情報を除去できると判断することはできません[53]。また、Redactionプロセッサが出力する監査属性(削除された属性数を表す redaction.redacted.count や、マスク数を表す redaction.body.redacted.count など)は処理の実行状況を把握する手掛かりにはなりますが、計装されていない領域や設定対象外のフィールドに機密文字列が残存していないことまでを証明するものではありません[54, 55]。

本文削減後も残る調査可能性と相関関係の維持

プロンプトやツールの出力本文を削ぎ落とした場合でも、障害調査やパフォーマンス分析、コスト管理に必要な可観測性を十分に担保できます。OpenTelemetryの標準規約に準拠したメトリクスと属性設計を採用することで、コンテキストの過不足を最適化できます。

第1に、トークン消費量メトリクスによるコスト推計です。プロンプト本文を排除しても、gen_ai.client.inference.usage.* カウンターを保持することで、入力・出力・キャッシュ読み書き・推論(reasoning)の各トークン消費量をprovider、model、operation、modality別に集計できます[56, 57, 58]。これらのメトリクスは規約上、費用を近似推計するプロキシ(代理指標)として定義されており、請求要因となるモデル呼び出しの傾向を把握する手段として機能します[57, 58]。

第2に、レイテンシヒストグラムによる処理境界の切り分けです。計装元が推論・エージェント・ツールのdurationヒストグラム(gen_ai.client.operation.duration、gen_ai.invoke_agent.duration、gen_ai.execute_tool.duration など)を出力していれば、推論全体の時間、ストリーミングの初回チャンク到達時間、エージェント全体の呼び出し時間、個別ツール実行の所要時間を本文なしで分離して分析できます[56, 59, 58]。

第3に、エラー分類とツール実行メトリクスの追跡です。推論やツールの失敗は error.type 属性によって分類され、モデルやプロバイダごとに集計できます[60]。利用状況データを提供しているプロバイダであれば、失敗した推論呼び出しにおけるトークン消費量も捕捉可能です[60]。さらにツール計測パイプラインから gen_ai.invoke_agent.tool_calls(エージェント呼び出しごとのツール呼び出し回数分布・失敗含む)や gen_ai.execute_tool.duration、gen_ai.tool.name、error.type を収集することで、どの外部ツールが遅延やエラーを引き起こしているかを特定できます(ただしプロバイダ側で完結するサーバサイドツールはこのカウンターに含まれません)[61]。

第4に、本文を排除した状態での相関関係の維持です。調査可能性を維持するためのキーとして、trace_id、span_id に加え、会話セッションを束ねる gen_ai.conversation.id、ツールの呼び出し単位を識別する gen_ai.tool.call.id、プロバイダ側の応答識別子である gen_ai.response.id を維持します[62]。プロンプト本文を破棄する場合でも、テンプレート名を示す gen_ai.prompt.name とそのバージョン(gen_ai.prompt.version)を属性に含めることで、どのような指示系統で呼び出されたかを特定できます[62]。

ここで重要な運用設計は、高カーディナリティな識別子の配置場所を分けることです。リクエストごとに値が変動するID群はスパンやログの属性にのみ配置し、メトリクス側にはプロバイダ名、モデル名、操作名、エージェント名といった値の種類数が安定した属性のみを割り当てます[63]。これにより、メトリクス系列の爆発やオーバーフローによる集計内訳の欠落を防ぐことができます[11, 63]。

なお、監査性の観点における制約として、ファイル差分やツールの実行引数をテレメトリから完全に除外した場合、残されたスパン情報だけからモデルが参照した正確なコードやコミットされた差分内容を復元することは困難です[64]。完全な証跡が義務付けられる監査要件が存在する場合は、テレメトリとは別に分離された操作ログ保管庫を用意し、trace IDやtool call IDを照合キーとして紐付けるハイブリッド構成が必要となります[64, 65]。

実務導入における検証手順と削減効果の客観的評価

Collectorを用いた削減設定を本番環境へ導入するにあたっては、公開事例の数値を適切に解釈し、段階的な検証ステップを踏むことが重要です。

CNCFブログに掲載されたClaude Codeの実験では、ログおよびトレースのデータ量(バイト数)が概ね半減し、成功した tool.execution スパンを除外するだけでトレースデータ量の約5分の1(約20%)が削減され、最適化されたメトリクス合計値と元の生データ合計値との誤差は0.5%以内に収まったと報告されています[66]。また、25セッション・8メトリクスから生成された系列数が300から12へと圧縮された結果も示されています[9, 10]。

しかし、これらの数値を自社の環境にそのまま当てはめることは避ける必要があります。公開された実験では、セッションの実行期間やタスクの詳細な実行条件、生データの実測値が明示されていません[7, 10, 66]。さらに、記事で言及されているメトリクス結合タイムアウト(2秒)、クエリ増加窓(1分)、Collector内部メトリクス読み取り間隔(10,000ミリ秒)は個々の処理・集計窓のパラメータであり、実験全体の観測期間を示すものではありません[67]。送信ログ件数の集計クエリもログレコードのみを対象としており、データ量指標(bytes)もCollectorの送信キューに追加された要求サイズ(otelcol_exporter_enqueue_size_bytes)を計測したもので、バックエンド側での保存済みデータ量の直接測定値ではありません[67]。加えて、エージェントの通信にはgRPC(Protobuf形式)が設定されている一方で報告されたデータ量はJSON換算の数値(289 KB)となっており、通信経路上を流れた実バイト数とは計測尺度が異なります[5, 68]。したがって、これらの数値は特定の実験条件下で得られた単一の報告例として扱い、一般的な削減率として過度に期待を固定しない姿勢が求められます[69]。なお、同実験の公開構成では、サンプリングを用いずにフィルタリングと変換処理のみを組み合わせて削減を実現しています[70]。

報告項目公開された測定条件・値測定の尺度・対象未公表の前提・解釈上の留意点
レコード件数と容量1タスク実行で181レコード、289 KB[5, 7]出力されたJSONデータ量[5, 7]信号別の内訳や通信上のProtobufワイヤバイト数とは異なる[7, 68]
メトリクス時系列数25セッション・8メトリクスで300系列から12系列へ削減[9, 10]時系列のユニーク系列数[8, 9]実験の収集期間やセッションの負荷条件は未公表[10]
ログ・トレース削減率バイト数が概ね半減、ツール成功除外で約20%削減[66]sending queueに追加された要求バイト数[66, 67]バックエンドに実際に保存・課金された実容量の直接計測ではない[67]
メトリクス集計誤差最適化後と生データの合計差が0.5%以内[66]集計カウンタの合計値比較[66]2秒のmerge timeoutや1分の集計窓による影響を含む[67]

実務における導入手順としては、まず使用するCollectorバイナリの機能検証から着手します。配布物ごとに含まれるコンポーネントが異なるため、otelcol components コマンドを実行して対象バイナリがサポートするプロセッサと信号別の安定性を確認します[71, 72]。必要なプロセッサが含まれていない場合は、OpenTelemetry Collector Builder(OCB)を用いて独自ビルドを作成します[72]。次に設定ファイルを作成し、otelcol validate コマンドで構文とパイプライン登録の正当性を確認します[71, 73]。プロセッサを定義しただけでは有効化されないため、必ず service.pipelines 内に指定順序で組み込まれていることを確認します[71, 40]。

transformやfilterプロセッサの error_mode は、機密情報のマスキングなど失敗が許されない処理では propagate(エラー時にペイロードを破棄して情報流出を防ぐ)を選択し、通常のログ加工では ignore(ログ出力の上で処理継続)を選択するなど、要件に応じた明示的な設定を行います[42, 43, 74]。

運用開始時は、テスト環境においてdebug exporterを一時的に有効化し、合成データや機密情報を含まないテストペイロードを投入して、加工後の出力構造を目視確認します[73]。その後、小規模なエージェント群やゲートウェイから段階的に適用範囲を拡大します[73]。

意図した削減が行われているか、あるいは予期せぬデータ欠落が生じていないかを判断するためには、Collectorの内部メトリクスを同一信号・同一観測窓で比較監視します[75]。受信数を示す otelcol_receiver_accepted_spans、プロセッサの処理数を示す otelcol_processor_incoming_items / outgoing_items、そして送信数を示す otelcol_exporter_sent_spans の差分を照合し、プロセッサ前後での減少数が設定したフィルタ条件と合致しているかを確認します[75]。また、送信先の一時停止やネットワーク遅延に対しては、送信キューの滞留状況を監視する otelcol_exporter_queue_size や queue_capacity、キュー溢れを示す otelcol_exporter_enqueue_failed_spans を監視します[76]。なお、送信失敗を示す otelcol_exporter_send_failed_* メトリクスの上昇は再試行中のステータスを含んでいる場合があるため、この数値単体のみで即座にデータ消失と決めつけず、キューの拒否メトリクスやログと組み合わせて総合的に健全性を評価することが重要です[76]。