LLMアプリのオブザーバビリティ|トレーシングとログ設計で「なぜこの出力になったか」を後から追えるようにする

LLMを組み込んだアプリを本番に出すと、ほぼ確実に次の質問が来ます。「昨日のあの回答がおかしかったんですが、何が起きていたんですか」。このとき、リクエストIDから当時のプロンプト・使ったモデル・検索で拾った文書・ツール呼び出しの結果までを数分でたどれるかどうかで、その後の運用のしんどさがまったく変わります。ところがLLMアプリの観測は、これまでのWebアプリのログ設計をそのまま持ち込むと肝心なところが抜け落ちます。この記事では、何を計測し、どう残し、どう使うかを、公開されている仕様と実務の判断軸から整理します。
通常のWebアプリのログと、LLMアプリのログは何が違うか
まず、既存の感覚をどこで捨てるかをはっきりさせます。違いは4つです。
1つ目は、失敗が例外として現れないことです。従来なら失敗はHTTPの5xxやスタックトレースとして観測できました。LLMアプリでは、APIは200を返し、レスポンスも正しいJSONで、それでも中身が間違っている、という失敗が主役になります。エラー率のダッシュボードは緑のまま、ユーザーだけが困っている状態です。「成功/失敗」の二値では品質を表現できず、出力そのものを後から見られる仕組みが要ります。
2つ目は、非決定性です。同じ入力でも出力が毎回同じとは限りません。温度やtop_pの設定、プロバイダ側のモデル更新、会話履歴の差、ツールが返した値の違いなど、揺らぎの原因は複数あります。再現できない前提で調査するには、「そのとき何を送って何が返ってきたか」を記録に残す以外に手がありません。ログが再現の代わりになる、という点が従来と大きく違います。
3つ目は、入出力が長いことです。1リクエストのプロンプトが数万トークンになることは珍しくありません。そのままログに流すと、ストレージ費用も、観測基盤側の属性サイズ上限も、個人情報の扱いも、いっぺんに問題になります。OpenTelemetryのGenAI仕様も、モデルへの指示・ユーザーメッセージ・モデル出力を「機微であり、かつサイズが大きいことが多い」と明記しています。
4つ目は、コストとトークンが一級の指標になることです。入力トークン数がそのまま費用に効き、しかも会話が伸びるほど増えます。レイテンシと並んで、トークン数は最初から取るべき数字です。
あわせて読みたい
ハルシネーション対策の実務|検証フローの作り方と運用のコツ
最低限とるべきシグナル
「あれば便利」を挙げ始めるときりがないので、これが無いと調査が詰まる、という水準から並べます。
リクエスト単位のトレース
起点は、1つのユーザーリクエストに1本のトレースIDが振られていることです。その下に、LLM呼び出し・ツール実行・検索・後処理がそれぞれspanとしてぶら下がる形にします。エージェントのように複数回のLLM呼び出しがループする構造では、これが無いと「何回まわったのか」すら分かりません。アプリの構造化ログ側にも同じトレースIDを載せておけば、トレースとログを行き来できます。従来の分散トレーシングとまったく同じ考え方です。
モデル呼び出しごとの基本属性
LLM呼び出しのspanには、少なくとも次を持たせます。
- プロバイダとモデル名。リクエストしたモデル名と、レスポンスが実際に名乗ったモデル名は分けて持つのが安全です。エイリアスが特定バージョンに解決されることがあり、「いつからおかしくなったか」を追うときに効きます。
- 入力トークン数と出力トークン数。プロバイダのレスポンスに入っている数字をそのまま取ります。
- レイテンシ。ストリーミングなら、最初のチャンクが届くまでの時間と全体の所要時間を分けます。体感速度は前者で決まります。
- finish reason(停止理由)。軽視されがちですが非常に効きます。出力が途中で切れたのか、ツール呼び出しで止まったのか、拒否されたのかが一発で分かります。
- エラー種別とリトライ回数。自動リトライを挟んだ場合、何回まわって最終的にどうなったのかを残します。
停止理由の語彙はプロバイダごとに違います。たとえばAnthropicのMessages APIはstop_reasonとしてend_turn・max_tokens・stop_sequence・tool_use・pause_turn・refusal・model_context_window_exceededを定義しています。この粒度が残っていれば、「出力が尻切れになる」という報告がmax_tokensによるものかどうかを推測せずに判定できます。
ツール呼び出しとRAG検索のspan
エージェントやRAGを含む構成では、LLM呼び出しの外側で起きていることのほうが原因である場合が多いです。ツール実行は、ツール名・呼び出しID・所要時間・成否を持つspanにします。検索は、クエリ、要求件数(k)、返ってきた文書のIDとスコアを持たせます。本文をまるごと残さなくても、IDとスコアだけあれば「そもそも正解文書が候補に入っていなかった」のか「入っていたのに使われなかった」のかを切り分けられます。ここが分かれば、直すべきなのが検索側か生成側かが決まります。
ヒント
なお、トレースは1件ずつ見るためのもので全体傾向には向きません。トークン使用量とレイテンシは、モデル別・機能別に集計できるメトリクスとしても出しておきます。トレースを全件保存しない運用にする場合でも、メトリクスだけは全件から集計する構成にしておくと、コストや遅延の傾向を見失わずに済みます。
最低限そろえるシグナル
- リクエスト単位のトレースIDと、アプリログへの伝播
- プロバイダ名・リクエストしたモデル名・応答が名乗ったモデル名
- 入力トークン数と出力トークン数(キャッシュ読み書きの内訳があれば分けて)
- 全体レイテンシと、ストリーミング時の初回チャンクまでの時間
- finish reason(停止理由)とエラー種別、リトライ回数
- ツール実行のspan(名前・呼び出しID・所要時間・成否)
- 検索のspan(クエリ、k、返却文書のIDとスコア)
- プロンプトのバージョン識別子(テンプレート名とversion)
OpenTelemetryのGenAI semantic conventionsは今どうなっているか
属性名を自分たちで決めてもアプリは動きますが、観測ツールを乗り換えるときや、フレームワークが自動で吐くテレメトリと突き合わせるときに困ります。共通語彙としてOpenTelemetryのGenAI semantic conventionsが整備されているので、現況を押さえておきます。
位置づけと成熟度
GenAI関連の規約は、2026年6月12日公開のSemantic Conventions v1.42.0で、コアのsemantic-conventionsリポジトリから分離され、専用のsemantic-conventions-genaiリポジトリへ移されました。GenAI領域は変化が速く、安定性の制約が強いコア規約とはリリース速度が合わないためです。
そして重要なのが成熟度です。GenAI規約のドキュメントはいずれも先頭に「Status: Development」と書かれており、個々のgen_ai.*属性もDevelopment表示です。安定(Stable)扱いなのはerror.type・server.address・server.portのようなコア規約由来のものだけで、属性名や構造は今後変わりうる前提で採用する必要があります。
注意
span名と操作名
規約は、操作の種類をgen_ai.operation.nameで表現し、span名をそこから組み立てるよう定めています。モデル推論はchat・generate_content・text_completionのいずれかで、span名は「操作名+モデル名」。検索はretrievalで「操作名+データソースID」、ツール実行はexecute_tool+ツール名、エージェント呼び出しはinvoke_agent+エージェント名、計画立案はplan+エージェント名という形です。ほかにembeddingsやinvoke_workflowも定義されています。推論spanではgen_ai.operation.nameとgen_ai.provider.nameが必須(Required)、gen_ai.request.modelは利用可能なら記録する条件付き必須(Conditionally Required)です。
主な属性
推論spanで実務上とくに効くものを抜き出します。要件レベルは規約が定めている区分です。
| 属性 | 要件レベル | 中身 |
|---|---|---|
gen_ai.provider.name | Required | プロバイダ識別子(anthropic・aws.bedrock・gcp.vertex_aiなど) |
gen_ai.request.model / gen_ai.response.model | Conditionally Required / Recommended | リクエストしたモデル名と、応答を生成したモデル名 |
gen_ai.response.finish_reasons | Recommended | 停止理由の配列 |
gen_ai.usage.input_tokens / gen_ai.usage.output_tokens | Recommended | 入出力トークン数 |
gen_ai.usage.reasoning.output_tokens | Recommended | 推論(thinking)に使われた出力トークン数 |
gen_ai.usage.cache_read.input_tokens / gen_ai.usage.cache_creation.input_tokens | Recommended | プロバイダ管理キャッシュの読み出し/書き込みトークン数 |
gen_ai.response.time_to_first_chunk | Recommended | ストリーミング時、最初のチャンクまでの秒数 |
gen_ai.conversation.id | Conditionally Required | 会話(スレッド)の識別子 |
gen_ai.prompt.name / gen_ai.prompt.version | Conditionally Required | 名前付きプロンプトテンプレートを使った場合の名前とバージョン |
gen_ai.input.messages / gen_ai.output.messages / gen_ai.system_instructions | Opt-In | 入力メッセージ・出力メッセージ・システム指示の本文 |
gen_ai.conversation.idには注意書きがあり、識別子が手元に無いときに新しいUUIDやトレースIDやリクエスト内容のハッシュを代わりに入れてはいけない、とされています。「無いなら入れない」が正しい扱いです。逆にgen_ai.prompt.nameとgen_ai.prompt.versionは積極的に入れたい属性です。プロンプトを変えたら品質が変わった、という調査は日常的に発生しますが、どのバージョンで生成された出力かがトレースに無いと突き合わせができません。バージョン文字列の体系は任意(SemVerでも日付でもタグでも可)と規約に書かれています。
検索spanにはgen_ai.data_source.idとgen_ai.retrieval.top_kがあり、返ってきた文書はgen_ai.retrieval.documents(Opt-In)にIDとスコアの配列として記録する形が示されています。ツール実行spanはgen_ai.tool.nameが必須で、gen_ai.tool.call.idとgen_ai.tool.typeがRecommended、引数と結果はOpt-Inです。
メトリクスとリトライの扱い
メトリクスは、クライアント視点のgen_ai.client.token.usage(ヒストグラム、単位は{token})とgen_ai.client.operation.duration(同、単位は秒)が中心で、ストリーミング向けにgen_ai.client.operation.time_to_first_chunkなどが用意されています。エージェント向けにはgen_ai.invoke_agent.durationのほか、1回の実行あたりの推論回数(gen_ai.invoke_agent.inference_calls)とツール呼び出し回数(gen_ai.invoke_agent.tool_calls)があり、暴走の検知に素直に使えます。
リトライの扱いも規約に書かれています。一時的な障害でクライアントが自動リトライした場合、spanは全リトライを含む論理操作の所要時間をカバーすべき、とされています。つまりspanの所要時間は「1回のHTTPリクエストの時間」ではありません。レイテンシの外れ値を追うときは、リトライが混ざっている前提で見る必要があります。
なお規約にはプロバイダ固有の拡張もあり、OpenAI向けのopenai.request.service_tier・openai.response.system_fingerprintや、MCP向けのmcp.method.name・mcp.session.idなどが定義されています。そもそも各プロバイダは返してくる使用量の粒度が違います。AnthropicのMessages APIはusageにinput_tokens・output_tokensに加えてcache_creation_input_tokens・cache_read_input_tokens、さらにoutput_tokens_details.thinking_tokensまで返します。Gemini APIのusageMetadataはpromptTokenCount・candidatesTokenCount・totalTokenCountに加えてcachedContentTokenCount・thoughtsTokenCountなどの内訳を持ちます。共通規約に載らない情報でも原価計算やキャッシュ効果の検証には要るので、使っているプロバイダのリファレンスは一度目を通しておくことをおすすめします。
この節の属性名・要件レベル・メトリクス名は、OpenTelemetryのGenAI semantic conventionsリポジトリの公開ドキュメント(2026年7月時点)に基づきます。全体がDevelopment段階のため、採用前に必ず最新の一次情報を確認してください。
あわせて読みたい
プロンプトキャッシュ(Prompt Caching)とは|LLM APIのコストとレイテンシを下げる仕組みと実務
入出力本文を残すか、残さないか
ここがLLMのオブザーバビリティで一番判断が要るところです。原因調査の観点では、プロンプトと応答の本文が残っているほど楽になります。一方で、本文には顧客情報や社外秘が入りうるうえ、サイズも大きい。この対立をどう捌くか。
仕様が示している3つのパターン
OpenTelemetryのGenAI仕様は、計装ライブラリは既定では本文を記録すべきでない(SHOULD NOT)、ただしオプトインの手段は提供すべき(SHOULD)としたうえで、アプリ側の選択肢を3つ挙げています。
- 1
記録しない(既定)
指示・入力・出力を記録せず、属性(モデル名・トークン数など)だけを残します。 - 2
span属性に記録する
gen_ai.system_instructions・gen_ai.input.messages・gen_ai.output.messagesに本文を載せます。仕様は、テレメトリ量が扱える範囲で、かつプライバシー規制が及ばないか保管先が規制に適合している場合、たとえば本番前の環境などに適する、と説明しています。 - 3
外部ストレージに保存して参照だけ残す
本文は別のストレージに置き、spanには参照を記録します。テレメトリ量が問題になる本番環境や、機微データを安全に扱う必要がある場合に推奨されるパターンで、アクセス制御を分離できるのが利点です。
3つ目は要するに、「観測基盤に見せる情報」と「調査のために厳重に保管する情報」を分ける設計です。観測基盤にはチーム全員がアクセスする一方、プロンプト本文は限られた担当だけが見られるようにしたい、という要求は現実によくあります。
実務での落とし所
どれか1つを選ぶのではなく、環境ごとに使い分けるのが現実的です。開発・ステージングでは本文をspan属性に載せて構いません(ただし本番データのコピーを流しているなら本番と同じ扱いにします)。本番は既定で本文を残さず、サンプリングで一部だけ詳細を残すか、外部保管+参照方式にします。トレースID・モデル名・トークン数・レイテンシ・停止理由・ツール名・検索結果のIDとスコアは、通常は機微情報を含まないので常時記録して問題ありません。
マスキングには期待しすぎないほうがよいと考えています。氏名やメールアドレスのように形式が決まったものは検出できますが、「A社の来期の値下げ方針」のような文脈依存の機微情報は機械的には拾えません。マスキングは補助であって、根本の対策は「本番の全件を長期保管しない」ことです。保持期間も先に決めます。本文つきトレースは30日、属性だけのトレースは90日、集計メトリクスは13か月、といった段階分けが扱いやすい形です。決めずに始めると、あとから「消してよいか誰も判断できないデータ」が積み上がります。
注意
あわせて読みたい
AI利用時の情報管理|入力してよいデータの線引きと社内での運用
サンプリングの考え方
全件のトレースを保存するとコストが問題になります。OpenTelemetryのドキュメントは、サンプリングをヘッドサンプリング(できるだけ早い段階で、トレース全体を見ずに決める)とテールサンプリング(トレース内のspanをほぼすべて見てから決める)に整理しています。前者は理解も設定も簡単で効率的ですが、トレース全体を見ないためエラーのトレースを必ず残すといった保証はできません。後者はエラー・レイテンシ・属性に基づく判定ができる代わりに、状態を持つ資源集約的なコンポーネントが必要で運用が難しいと明記されています。
LLMアプリでは、残したいのはエラーのトレースだけではありません。ユーザーが低評価を付けたもの、コストが突出したもの、finish reasonがmax_tokensだったもの、エージェントのループ回数が多かったもの。つまり「200で返ってきたけれど怪しい」ものを残したいわけです。これはトレース全体を見ないと判定できないので、テールサンプリング的な仕組みが要ります。OpenTelemetry Collectorのcontribにはtail sampling processorがあり、この判定をパイプライン側へ寄せる選択肢になります。
素朴だが有効な代替もあります。ユーザーフィードバックのAPIを用意し、低評価が付いたときにアプリ側でそのトレースを保存対象として印を付ける方式です。仕組みは単純ですが、いちばん見たいトレースが確実に残ります。
コスト可視化とアラート設計
観測を入れる動機として、原因調査と並んで大きいのがコストです。ここはLLMのコスト管理と役割を分けて、トレースから何が読めるかに絞ります。
トークン数の合計だけを見ていても、増えた理由は分かりません。トレースに属性が乗っていれば、次元を切って割り算ができます。機能別(どの画面が費用の大半を占めるか)、モデル別(高価なモデルに寄っている処理はどれか)、リクエストあたりの平均トークン数とその分布(平均より裾が問題になりがちです)、キャッシュ読み出しトークンが入力全体に占める割合(プロンプトキャッシュが効いていない状態に気づけます)、そしてエージェントの1依頼あたりの推論回数とツール呼び出し回数。金額そのものをspanに書き込むかは好みが分かれますが、単価は変わりうるので、トークン数を正としてダッシュボード側で単価を掛ける構成のほうが後から直せて扱いやすいです。
アラートでよくある失敗は、絶対値のしきい値を並べてしまい、鳴りっぱなしで誰も見なくなることです。次のような組み方をおすすめします。
| 見るもの | 具体例 | なぜ効くか |
|---|---|---|
| エラー率 | プロバイダの5xx・タイムアウト・レート制限の率 | 素直な障害検知。従来の監視と同じ |
| 打ち切り率 | finish reasonがmax_tokensになった割合 | 尻切れは品質劣化として現れる。急増したらプロンプトか入力データの変化を疑う |
| 空応答・拒否率 | 応答が空、または拒否として返った割合 | プロンプト変更やモデル更新の影響が出やすい |
| 検索の空振り率 | 検索が0件、または最高スコアが閾値未満だった割合 | RAGの品質低下を生成の前段で捉えられる |
| 出力スキーマ違反率 | 構造化出力の検証に失敗した割合 | パース失敗がアプリ例外になる前に検知できる |
| 反復回数の分布 | 1実行あたりのツール呼び出し回数のp95 | ループの兆候。コスト急増の予兆でもある |
しきい値は絶対値ではなく前週同時刻比や移動平均からの乖離で置くと、トラフィックの変動に振り回されにくくなります。そして、鳴ったときに人が何をするのか決まっていないアラートは作らないことです。これは従来の監視と同じ原則ですが、LLMアプリでは「品質っぽい指標」をいくらでも作れてしまうぶん、より意識的に絞る必要があります。
メモ
あわせて読みたい
構造化出力(Structured Outputs)とは何か|LLMの返答をプログラムで安全に扱うための仕組み
評価(eval)とトレースをつなぐ
観測を入れただけでは品質は上がりません。上がるのは、観測したものが評価に流れ込み、改善の判断材料になったときです。接続の仕方は3つあります。
1つ目は、失敗トレースを評価データセットに戻す経路です。本番で問題になった入力を、期待する挙動とセットで評価セットに追加します。これが回り始めると、評価セットが「実際に起きた失敗の蓄積」になり、机上で作った質問リストよりずっと役に立ちます。トレースに入力が残っていなければこの経路は作れません。ここが、本文を一部だけでも残しておく実務上の最大の理由です。
2つ目は、オフライン評価にトレースの構造を使うことです。RAGなら、検索spanに残った文書IDと評価セットの正解文書IDを突き合わせるだけで検索段の再現率が測れます。生成の良し悪しを判定する前に、そもそも材料が揃っていたのかが分かるわけです。エージェントなら、実行したツールの並びが期待した手順と一致しているかを見られます。
3つ目は、本番トレースから一定割合を抽出し、別のLLMに採点させる運用です。ただし採点側のバイアスや、採点結果自体をどう検証するかを決めずに始めると、数字だけが増えて判断には使えません。
あわせて読みたい
LLM-as-a-Judge(LLMによる評価)とは|出力の良し悪しをLLMに採点させる仕組みとバイアスへの備え
いずれの経路でも鍵になるのは、「どのバージョンで生成された出力か」が分かることです。プロンプト、モデル、検索インデックス、アプリのリリース。これらがトレースの属性に入っていれば、評価スコアの変化を原因に結びつけられます。入っていなければ、「先月より悪くなった気がする」で終わります。
あわせて読みたい
LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順
既存APMに乗せるか、LLM専用ツールを使うか
選択肢は大きく3つあり、どれが優れているかではなく、どこに重心があるかで選びます。
すでにトレースとログの基盤を運用しているなら、そこにgen_ai.*属性を持つspanを流すのがいちばん摩擦の少ない構成です。フロントエンドからDBまで1本のトレースでつながるので、「遅いのはLLMなのか前段のSQLなのか」を同じ画面で切り分けられます。一方で、プロンプトと応答を並べて読む、評価スコアを紐づける、といったLLM固有の作業には向かないことがあります。
LLMやエージェントに特化したツールは、会話単位のビューア、トレースからの評価セット作成、プロンプトのバージョン管理といった、開発ワークフローに寄った機能を備えるものが各種あります。オープンソースで自社運用できるものとSaaSがあり、導入前に確認したいのは、データの保存場所、セルフホストの可否、OpenTelemetryのデータを受けられるか(独自SDKに固定されないか)、そしてエクスポートの手段です。
実際には、メトリクスと横断トレースは既存基盤、詳細な入出力とプロンプト管理は専用ツール、という併用に落ち着くチームが多い印象です。運用対象は増えますが、それぞれの得意な部分だけを使う形になります。
ツール選定で先に決めること
- プロンプト・応答の本文を、どこに、どのくらいの期間置いてよいか(社内の情報管理ルールとの整合)
- セルフホストが必要か、SaaSでよいか。SaaSなら保存リージョンと委託先管理の要件
- OpenTelemetryのデータをそのまま送れるか。独自SDK専用だと乗り換え時にアプリ改修が要る
- 既存のAPMやログ基盤と、トレースIDでつなげられるか
- 評価(eval)の実行と結果の保管を、どちら側に置くか
- データのエクスポート手段があるか。無ければ移行時に履歴を失う
ヒント
小さく始める順番
一度に全部やる必要はありません。効果の大きい順に並べると、だいたい次のようになります。
- 1
トレースIDを通す
リクエスト単位のトレースIDを発行し、アプリのログとLLM呼び出しの記録の両方に載せます。この時点で、問い合わせから該当リクエストを特定できるようになります。 - 2
LLM呼び出しをspanにする
プロバイダ名・リクエストしたモデル名・応答モデル名・入出力トークン数・レイテンシ・停止理由・エラー種別を属性に持つspanを作ります。本文はまだ入れません。 - 3
ツールと検索をspanにする
ツール実行と検索を子spanにし、検索には文書IDとスコアを入れます。ここまでで原因切り分けの骨格がそろいます。 - 4
メトリクスを出す
トークン使用量と所要時間を、モデル別・機能別に集計できる形で出します。コストとレイテンシの傾向が見えるようになります。 - 5
本文の扱いを決める
開発では記録し本番では既定で記録しない、あるいは外部保管+参照方式にする、という方針を保持期間とあわせて文書化します。 - 6
怪しいトレースを拾う経路を作る
ユーザーフィードバックか代理指標(打ち切り率・検索空振りなど)で、見たいトレースを確実に残す仕組みを入れます。 - 7
評価セットへ戻す
拾ったトレースを評価セットに追加し、変更の前後で同じセットを流して比較する運用にします。ここまで来て、観測が改善に直結します。
計装の形を擬似コードで示すと次のような具合です。属性名はGenAI semantic conventions(Development段階)に合わせています。具体的なAPIは使う言語とSDKのバージョンで変わるため、実装時は各SDKの最新ドキュメントを確認してください。
# 擬似コード。言語・SDKは問わない前提の骨格
span = tracer.start_span("chat " + request_model, kind=CLIENT)
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.provider.name", provider) # 例: "anthropic"
span.set_attribute("gen_ai.request.model", request_model)
span.set_attribute("gen_ai.prompt.name", "summarize-ticket")
span.set_attribute("gen_ai.prompt.version", "2026-07-20")
try:
res = call_model(...) # 自動リトライを含む論理操作
span.set_attribute("gen_ai.response.model", res.model)
span.set_attribute("gen_ai.response.finish_reasons", [res.stop_reason])
span.set_attribute("gen_ai.usage.input_tokens", res.usage.input_tokens)
span.set_attribute("gen_ai.usage.output_tokens", res.usage.output_tokens)
if capture_content: # 既定はfalse。環境ごとに切り替える
span.set_attribute("gen_ai.input.messages", to_json(messages))
span.set_attribute("gen_ai.output.messages", to_json(res.messages))
except ProviderError as e:
span.set_attribute("error.type", classify(e)) # 低カーディナリティの分類にする
raise
finally:
span.end()
error.typeは、例外メッセージをそのまま入れるとカーディナリティが爆発します。規約も、プロバイダが返すエラーコードか例外のカノニカル名、あるいは低カーディナリティな識別子を使うべきとしています。ここは既存のメトリクス設計と同じ注意点です。
あわせて読みたい
AIエージェントとは何か|従来の自動化との違いと任せてよい仕事
よくある質問
OpenTelemetryのGenAI semantic conventionsは、もう本番で使ってよいですか
プロンプトと応答の本文は、ログに残すべきですか残さないべきですか
既存のAPMだけで足りますか。LLM専用ツールは必要ですか
トレースを全件保存するとコストが高すぎます。どこを削ればよいですか
トークン数はアプリで数えるべきですか、レスポンスの値を使うべきですか
まとめ
LLMアプリのオブザーバビリティは、新しい概念というより、既存の分散トレーシングに「出力の中身」と「トークン」という軸を足したものです。ただしその2つが加わることで、設計上の判断は確実に増えます。失敗が例外として現れないのでエラー率の監視だけでは足りず、非決定的なので再現の代わりにログが要り、入出力が長く機微なので何を残すかを選ばなければなりません。
出発点は、リクエスト単位のトレースIDを通し、LLM呼び出し・ツール実行・検索をspanに分けることです。属性はOpenTelemetryのGenAI semantic conventionsに寄せておくと後の乗り換えが楽になります。ただし、この規約は本記事作成時点でDevelopment段階であり、名前は変わりうるという前提を忘れないでください。
本文の扱いは環境ごとに分けるのが現実解です。本番で全件を観測基盤に流すのは、コストと情報管理の両面で勧められません。そのうえで、低評価や打ち切りといった「200で返ってきたけれど怪しい」トレースを確実に拾う経路を作り、それを評価セットに戻す。ここまでつながって初めて、観測は改善の道具になります。
ダッシュボードを増やすことが目的ではありません。「昨日のあの回答は何だったのか」に、推測ではなく記録で答えられる状態を作ること。そこから逆算して必要なものだけを入れるのが、いちばん短い道だと考えています。
あわせて読みたい
LLMのコスト管理|トークン課金の考え方と削減の定石
出典・参考
- OpenTelemetry GenAI Semantic Conventions (GitHub)
- Semantic conventions for generative client AI spans (OpenTelemetry)
- Semantic conventions for generative AI metrics (OpenTelemetry)
- Semantic Conventions for GenAI agent and framework spans (OpenTelemetry)
- GenAI attributes registry (OpenTelemetry)
- Semantic Conventions for OpenAI client operations (OpenTelemetry)
- Semantic Conventions for Model Context Protocol (OpenTelemetry)
- Semantic Conventions v1.42.0 リリースノート (OpenTelemetry)
- Semantic conventions for generative AI systems (opentelemetry.io)
- Inside the LLM Call - GenAI Observability with OpenTelemetry (OpenTelemetry Blog)
- Sampling (OpenTelemetry Docs)
- Tail Sampling Processor (opentelemetry-collector-contrib)
- Messages (Claude Docs)
- Generating content (Gemini API Docs)
関連する記事
LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順
LLMアプリの品質を測る評価(eval)の作り方を、実務目線で整理します。実運用に似せた評価データの用意、タスク型に応じた指標設計、自動評価と人手レビューの併用、LLM-as-a-judgeの使いどころと落とし穴、失敗トレースをテストへ戻す継続的な評価まで、小さく始める手順にまとめました。
LLMのコスト管理|トークン課金の考え方と削減の定石
LLMのAPI利用で避けて通れないトークン課金の仕組みと、コスト管理の実務を解説します。料金の構造、見積もりの立て方、モデルの使い分けやキャッシュ活用といった削減の定石、監視の仕組みづくりをまとめます。
AIエージェントとは何か|従来の自動化との違いと任せてよい仕事
生成AIの活用で注目されるAIエージェントを冷静に解説します。チャットボットやRPAとの違い、自律性がもたらす利点とリスク、実務でエージェントに任せてよい仕事と任せるべきでない仕事の線引きをまとめます。


