新規 無料登録で、呼び出し 10 回分(最大 $1)をお試しいただけます。カード登録は不要です。

LLM エージェントの可観測性:トレースとセッションで 10 の問題を再現

目次
  1. LLM エージェントのトレース、スパン、セッションとは
  2. 調べたいことに応じて、どの画面を見るか
  3. LLM エージェントが遅いのはなぜか
  4. LLM エージェントのコストが高いのはなぜか
  5. ユーザーからの不具合報告をもとに、どうデバッグするか
  6. 自分で実装していないコーディングエージェントもトレースできるか
  7. モデルにトレースを読ませられるか
  8. すべてのエージェントが記録すべき項目
  9. ゲートウェイ側のトレーシングでは分からないこと
  10. よくある質問
  11. LLM エージェントの可観測性とは
  12. 関連記事

LLM エージェントの可観測性は、モデル呼び出しを 2 つの単位でまとめるところから始まります。トレースはエージェントの 1 回の実行をタイムラインで表したもの、セッションは合計コストを含む 1 つの会話です。各呼び出しに 3 つのリクエストヘッダーを付ければ、LLM ゲートウェイがリクエストログから両方を構成します。トレーシング用の SDK は不要です。ゲートウェイはアプリケーションコードとモデルプロバイダーの間に置くプロキシで、すべてのリクエストを記録します。この構成を小規模なサポートエージェントと Claude Code に適用し、よくある 10 の問題を再現しました。遅延の原因が 3 つ、コスト増の原因が 4 つ、動作不良の原因が 3 つです。

TL;DR

  • 3 つのリクエストヘッダー(X-Trace-Id、X-Span-Id、X-Session-Id)を付けるだけで、Synthorai ゲートウェイのリクエストログからトレースとセッションを構成できます。トレーシング用の SDK は不要です。
  • 独立した 3 つのサブエージェントのチェックを同時に送ると、コストを変えずに所要時間の中央値を 3.7 s から 1.8 s に短縮できました。
  • GPT-6 Luna でプロンプトの先頭行からタイムスタンプを移すと、空のキャッシュから開始してもキャッシュヒット率が 0% から 73% に上がりました。
  • Claude Code の 1 回の実行コストは $0.1728 で、そのうち 72% を 3 つのエージェントそれぞれの初回呼び出しが占めていました。

この記事は、全 7 本のガイドシリーズの概要です。各節では、対応するガイドの検証結果を数値とともにまとめています。コードとスクリーンショットはリンク先で確認できます。実行日はすべて 2026 年 10 月 10 日と 11 日です。レイテンシーは 5 回の実行の中央値で、それ以外の数値はすべて 1 回の実行結果です。コードは github.com/synthorai-io/use-cases にあります。

LLM エージェントのトレース、スパン、セッションとは

トレースは、1 つのユーザーメッセージやタスクを処理するためにエージェントが行う一連の動作です。スパンはその中の 1 ステップ、セッションは複数のトレースからなる会話全体を指します。今回のサポートエージェントでは、2 ターンの顧客との会話が 1 セッションになり、その中に 2 つのトレースが入ります。各トレースには 5 回または 6 回のモデル呼び出しがあります。一部はサブエージェントによる呼び出しです。サブエージェントは、メインエージェントがタスクの一部を任せるために起動する別のエージェントで、専用のプロンプトを持ち、多くの場合はより安価なモデルを使います。

用語対象ヘッダー同じ値を送る範囲
トレースエージェントの 1 回の実行X-Trace-Idその実行の全呼び出し
スパン実行内の 1 回のモデル呼び出しX-Span-Idその呼び出しのみ
セッション1 つの会話X-Session-Idその会話の全呼び出し
エージェント呼び出し元のサブエージェントX-Agent-Idそのサブエージェントの全呼び出し
ラベルユーザー、機能、リリースX-Synthorai-Metadataグループ化や絞り込みの対象にしたい呼び出し

ID は任意の文字列で、それぞれ最大 128 文字の印字可能な ASCII 文字を使えます。ラベルは文字列のキーと値を持つフラットな JSON オブジェクトです。リクエストごとに最大 10 個、各値は最大 128 バイトです。ゲートウェイはレスポンスに ID を返しますが、これらのヘッダーをモデルプロバイダーには転送しません。OpenAI Python SDK では、リクエストごとのヘッダーを extra_headers に指定します。

resp = client.chat.completions.create(
    model="gpt-6-luna",
    messages=messages,
    extra_headers={
        "X-Session-Id": session_id,   # one per conversation
        "X-Trace-Id": trace_id,       # one per user message
        "X-Span-Id": "plan",          # one per call
        "X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
    },
)

別のツール経由ですでにトレース ID を送っているエージェントなら、何も追加せずに使える場合があります。3 種類の ID のうちリクエストに含まれていないものは、OpenTelemetry、LiteLLM、Langfuse、Open WebUI、Claude Code、OpenAI Codex CLI の対応するヘッダーから読み取ります。OpenTelemetry はトレーシングのオープン標準で、traceparent ヘッダーにトレース ID を格納します。3 つの HTTP ヘッダーで LLM エージェントをトレースするでは、デモ用エージェントを順に構築しています。GPT-6 Luna による計画の呼び出し、2 つのローカルツール、DeepSeek V4.1 Flash による 3 つの並列チェック、そして回答の呼び出しという構成です。デモでは 21 回のモデル呼び出しで $0.0017 かかりました。

調べたいことに応じて、どの画面を見るか

このシリーズで扱う疑問は、それぞれコンソール内の特定の画面、またはコンソール外からログを読む方法で調べられます。コンソールでは、各トレースをウォーターフォールで表示します。共通の時間軸上にモデル呼び出しごとのバーが並ぶため、並列実行された呼び出しは重なり、前の呼び出しを待って実行されたものは階段状に見えます。

疑問確認する場所ガイド
この実行に 8 秒かかったのはなぜかトレースの概要にある、ゲートウェイ内の時間とアプリ内の時間エージェントのレイテンシー
この会話で最もコストがかかったターンはどれかトレースごとに 1 行で表示するセッションページエージェントのコスト
キャッシュヒット率が 0% なのはなぜかセッションページのキャッシュヒット列エージェントのコスト
昨日のこのユーザーのリクエストで何が起きたかトレース一覧でのラベル検索エージェントのデバッグ
Claude Code のどのサブエージェントにコストがかかったかセッションページのエージェント別テーブルClaude Code のサブエージェント別コスト
チャット画面でこれらを質問できるかClaude の MCP コネクターLLM ログについて Claude に質問する
自作プログラムでログを調査できるかMCP ツールまたは REST APIエージェントにログ調査ツールを渡す

各トレースページには比較ボタンもあります。2 つのトレースを同じページに表示し、呼び出しを名前で対応付け、両方のウォーターフォールを同じ時間軸に並べます。このシリーズの修正はすべて、この方法で修正前後の実行を比較して確認しました。

LLM エージェントが遅いのはなぜか

まず、トレース上部にある時間の内訳を確認します。最初のリクエストから最後のレスポンスまでの経過時間、つまりユーザーが待った時間を、ゲートウェイ内の時間とアプリ内の時間に分けています。ゲートウェイ内の時間は、少なくとも 1 つのモデルリクエストが処理中だった時間です。アプリ内の時間は、アプリケーションコードだけが動いていた時間です。デモのトレースでは、経過時間 8.5 s のうちゲートウェイ内が 7.0 s、アプリ内が 1.6 s だったため、モデル呼び出しを調べるべきだと分かります。レイテンシーのガイドでは 3 つの原因を再現し、それぞれ 5 回ずつ実行しました。

原因トレース上の特徴修正修正前の中央値修正後の中央値
独立した呼び出しを順番に送信バーが階段状に並ぶPython で複数の呼び出しを同時に待つ asyncio.gather でまとめて送る3.7 s1.8 s
呼び出し間のコードが遅いyour app の行が長い4 つの検索処理を同時に実行する11.0 s9.2 s
1 行の回答にも推論を使う出力数が回答の長さに比べて大きすぎるreasoning_effort="none"5 回答で 25.6 s10.2 s

コードが遅いケースでは、修正前後でモデル呼び出しの所要時間はほぼ同じでした。変わったのはアプリ内の時間です。ガイドに載せた比較では、your app の行が 4.6 s から 1.3 s に短くなりました。この部分はモデルを変えても短縮できません。アプリ側のプロファイラーや OpenTelemetry のスパンから調べます。3 つ目のケースには GPT-6 Luna を使いました。reasoning_effort は回答前にどれだけ推論するかを指定するリクエストパラメーターです。none にすると、5 回答分の出力トークンが 1,150 から 190 に減りました。LLM エージェントが遅いのはなぜかに、3 ケースすべてのコードと比較用トレースを載せています。

LLM エージェントのコストが高いのはなぜか

まずトレース一覧をコスト順に並べ、最も高額なセッションを開きます。そこでキャッシュヒット列と入力列をターンごとに確認します。キャッシュヒット率は、入力トークンのうちプロバイダーのプロンプトキャッシュから読み取られた割合です。プロバイダーが以前に処理したプロンプトの接頭部分には、通常の入力より低い料金が適用されます。コストのガイドでは 4 つの原因を再現しました。小規模なデモなので金額は 1 セント未満ですが、他の環境でも参考になるのは比率です。

原因確認できる箇所修正前修正後
プロンプトの先頭行にタイムスタンプがあるキャッシュヒット率が 0% のままキャッシュ利用率 0%キャッシュ利用率 73%、入力コスト 66% 減
履歴に全ツールの結果を含めて再送しているターンごとに入力が増える入力トークン 24,546入力トークン 8,053
リトライループ1 つのトレースに呼び出しが多すぎるが、ステータスはすべて 20010 回の呼び出し2 回の呼び出し
安価に処理できるステップに高価なモデルを使っているセッションページのエージェント別テーブル$0.0012$0.0003

最初の行は、空のキャッシュから GPT-6 Luna で実行した結果です。ガイドでは、キャッシュが効かなくなるもう 1 つのパターンも扱っています。ユーザーメッセージの送信時に指示を追加するのに、保存時にはその指示を含めないケースです。これでは毎ターン、前のターンで実際には送っていないプロンプトを組み立て直すことになります。送信したとおりにメッセージを保存すると、その会話のヒット率は 0% から 76% に上がりました。履歴のケースでは、入力トークンが 3 倍でも入力コストの増加は 21% にとどまりました。71% がキャッシュから読み取られていたためです。最後の行では、GPT-6.1 Sol を使う 3 つのサブエージェントのチェックが、セッションコストの 81% を占めていました。同じチェックを DeepSeek V4.1 Flash で実行すると、コストは 10 分の 1 になりました。LLM エージェントのコストが高いのはなぜかで、各ケースを詳しく説明しています。

ユーザーからの不具合報告をもとに、どうデバッグするか

ラベルでユーザーの実行を探し、トレースを読んで問題のある呼び出しを特定します。問題の呼び出しでも、ステータスは 200 であることがよくあります。各リクエストに _user ラベルを付けておけば、トレース一覧の検索欄に metadata._user = u_1002 と入力して、そのユーザーのセッションとトレースを取得できます。デバッグのガイドでは、続いて 3 つの不具合を再現しました。

  • 404 とそのリトライ。存在しないモデル ID への呼び出しは 95 ms で失敗し、コストは $0 でした。リトライ時にトレース ID を引き継ぎ、スパン名を新しくした(policy-check.retry)ため、失敗した呼び出しとリトライがウォーターフォール上で隣り合って表示されます。
  • ステータス 200 なのに回答が途切れる。max_tokens=40 で推論を有効にしたまま DeepSeek V4.1 Flash を 3 回続けて呼ぶと、いずれもステータス 200、出力トークン数はちょうど 40、回答は空になりました。推論だけで上限を使い切ったためです。トレースには同じスパンが 3 回現れ、出力数が上限と一致します。上限を 2,000 に上げて推論を無効にすると、1 回の呼び出しで 49 トークンの回答が返りました。上限を上げるだけでも回答は返りましたが、推論に 1,876 トークンを使い、22 s かかりました。
  • 2 ターン目に 400 が返る。保存した履歴からツール呼び出しが欠落していたため、1 ターン目は動いていた GPT-6 Luna が 2 ターン目を拒否しました。0.5 s で終了し、料金はかかりませんでした。

2 つ目の不具合を検知するには、すべての呼び出しで finish_reason を記録する必要があります。これはモデルが生成を止めた理由を示すレスポンスフィールドです。値が length なら max_tokens の上限に達したことを意味するため、ステータスが 200 でも失敗として扱うべきです。LLM エージェントをデバッグするでは、すべてのエージェントが記録すべき 5 つの項目を挙げています。この記事の後半にも再掲します。

自分で実装していないコーディングエージェントもトレースできるか

Claude Code は各リクエストにセッション ID とエージェント ID を送るため、本体を変更せずにトレースできます。2 つの環境変数で接続先をゲートウェイに切り替え、3 つ目で各サブエージェントの種類を追加できます。

export ANTHROPIC_BASE_URL="https://synthorai.io"   # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1          # optional: names sub-agent types such as Explore

Claude Sonnet 5.5 を使う Claude Code で、2 つのサブエージェントとともに失敗するテストを 1 つ修正したところ、11 リクエストで $0.1728 かかりました。セッションページでエージェント別に見ると、2 つのサブエージェントが全体の 39% を占めていました。また、キャッシュ書き込みが全体の 77%、3 つのエージェントそれぞれの初回呼び出しが全体の 72% でした。30 秒後にキャッシュが温まった状態で同じ処理を再実行すると、コストは $0.0812 でした。Claude Code のサブエージェント別コストでは、チームの支出を開発者別、リポジトリ別に分ける方法も扱っています。

モデルにトレースを読ませられるか

ゲートウェイはリクエストログを MCP サーバーとして公開しているため、モデルから MCP 経由で読めます。MCP(Model Context Protocol)は、モデルにツールを渡すための標準です。ログの読み方は、誰が調査するかで変わります。

調査する主体使うものモデルのトークン料金検証結果
質問したい人ゲートウェイをコネクターとして追加した Claudeチャットプランに含まれる3 つの質問に 14 回のツール呼び出しで回答し、すべての数値が課金記録と一致
固定クエリを実行するプログラムREST APIなしコスト上位 3 セッションを $0 で取得
調べる対象を自分で決める自作エージェントプログラム内の MCP ツール別途支払い1 回のコスト調査で $0.0015

チャットのケースでは、Claude Fable 5.1 を OAuth で接続し、リクエストログへの読み取り権限だけを与えました。OAuth では API キーを貼り付ける代わりに、ブラウザーでログインします。これにより、4 つの読み取り専用ツールと、有効期間が 7 日のキーが付与されました。ツールが返すのはコスト、トークン数、ステータスで、プロンプトや回答の本文は含みません。プログラムのケースでは、GPT-6 Luna が 26 セッションの中からキャッシュ、モデル選択、リトライループに問題があるセッションを、1 回のツール呼び出しで特定しました。コストは $0.0015 でした。Claude Sonnet 5.5 も価格を調べたうえで、3 回のツール呼び出しにより同じセッションを特定し、$0.0631 かかりました。LLM ログについて Claude に質問するとエージェントにログ調査ツールを渡すで、それぞれの設定を説明しています。

すべてのエージェントが記録すべき項目

次の 5 項目で、このシリーズのすべての問題を追えます。

  • 実行ごとのトレース ID と会話ごとのセッション ID をヘッダーで送り、アプリ側のログにも記録します。
  • 処理ステップと試行を識別できる呼び出しごとのスパン名 を付けます。例は plan-2 や policy-check.retry です。
  • ユーザー、機能、リリースのラベル を付け、不具合報告や請求額に対応する実行を絞り込めるようにします。
  • 各呼び出しのレスポンスヘッダー x-request-id を記録し、後からその呼び出しの請求コストを調べられるようにします。
  • 各呼び出しの finish_reason と出力トークン数 を記録し、length は失敗として扱います。

また、すべての呼び出しでクライアントのタイムアウトと max_tokens を設定し、どちらにも余裕を持たせます。表示される回答の長さだけを基準に上限を決めると、先ほどの空の 200 レスポンスにつながります。推論モデルでは、長い 1 回の呼び出しに数分かかることもあります。

ゲートウェイ側のトレーシングでは分からないこと

リクエストログから追跡できるのはモデル呼び出しだけで、アプリケーションコードの内部は見えません。コードが遅いケースで示したように、ゲートウェイはリクエスト間の空白からコードの実行時間を推定します。ツール、検索、キューのスパンを取得するには OpenTelemetry SDK が必要です。X-Trace-Id がなければゲートウェイは traceparent を読むため、両方のビューでトレース ID を共有できます。また、トレースにはプロンプトとレスポンスの本文は含まれず、コストも事後の報告です。予算をリアルタイムに管理するには、エージェントのループ内でレスポンスを受け取るたびに、その usage からコストを計算する必要があります。

よくある質問

LLM エージェントの可観測性とは

LLM エージェントの可観測性とは、実行ごとに何が行われたかを把握できることです。どのモデルをどの順序で呼び出し、それぞれにどれだけ時間とコストがかかり、どれが失敗したかを確認します。実際には、呼び出しをトレース(1 回の実行)とセッション(1 つの会話)にまとめ、ユーザーや機能のラベルを付けて管理します。

関連記事

← ブログに戻る