LLM運用の可観測性を実装する:OpenTelemetryでつくるPrompt/Token/Latency監視の実践
LLMアプリは「動く」だけでは本番品質になりません。運用を始めると、次のような問題が必ず発生します。
- 昨日まで 1.2 秒だった応答が突然 4 秒台になる
- コストが月末に急増したが、どの機能が原因かわからない
- 回答品質が落ちたと言われるが、どのプロンプト変更が影響したか追えない
- リトライ回数や外部API待ちの偏りが可視化されていない
この課題を解く鍵が「可観測性(Observability)」です。本記事では OpenTelemetry を軸に、LLM アプリの監視をゼロから構築する実装を、実際に運用で使える粒度で説明します。
なぜ APM だけでは LLM を見切れないのか
従来の Web アプリ監視(CPU、HTTP レイテンシ、エラーレート)だけでは、LLM 特有の故障点が見えません。理由は、LLM の品質とコストが「入力テキスト」と「推論設定」に強く依存するためです。
少なくとも次の軸が必要です。
- Prompt 可視化: システム/ユーザー/ツール呼び出しの構成
- Token 可視化: input/output token、モデル別単価、キャッシュヒット率
- 推論経路可視化: retrieval → rerank → generation の各ステップ時間
- 品質シグナル: hallucination 率、参照文書一致率、ユーザー評価
つまり、HTTP 1 本のログでは不十分で、トレース単位で LLM 実行を分解する必要があります。
アーキテクチャの全体像
最初に、実装対象を次の構成とします。
- API: FastAPI
- LLM: OpenAI / Azure OpenAI(抽象化)
- RAG: pgvector + reranker
- Observability: OpenTelemetry SDK + OTLP Exporter + Grafana Tempo/Loki/Prometheus
処理フローは次の通りです。
- リクエスト受信時に
trace_idを生成 - Retrieval、Rerank、Generate をそれぞれ span 化
- 各 span に token、model、temperature、cache_hit を attribute として記録
- 失敗時は exception をイベントとして保存
- レスポンス時にコスト推定を metrics として送信
ステップ1:OpenTelemetryの初期設定
まずは Python で最小セットを導入します。
|
|
次に初期化コードを用意します。
|
|
ここで重要なのは、service.name を固定することです。デプロイごとに揺れるとダッシュボードが分断され、比較分析ができません。
ステップ2:LLM処理を span で分割する
実運用では「遅い」の原因が retrieval なのか generation なのかで対応が変わります。そこで、処理を細かく span 化します。
|
|
この分割で、「retrieval が中央値 70ms → 280ms に悪化」「特定モデルだけ output token が急増」など、運用判断に直結する情報が取得できます。
ステップ3:コストをメトリクス化する
運用現場で最も効くのは、推定コストをリアルタイムに可視化することです。モデル単価表をコードに持ち、1リクエストごとに計算して metrics に送ります。
|
|
推奨は次の3指標です。
llm_cost_usd_total(counter)llm_tokens_input_total/llm_tokens_output_total(counter)llm_latency_ms(histogram)
これを feature、tenant、model のラベルで集計すると、予算統制が一気に楽になります。
ステップ4:品質低下を検知する仕組みを入れる
レイテンシとコストだけでは不十分です。品質監視を最低限でも導入します。
4-1. 自動評価ジョブ
夜間バッチで固定データセット(100問程度)を流し、次を記録します。
- 正答率(正解文との semantic similarity)
- 出典一致率(回答が引用した文書IDの妥当性)
- 禁止事項違反率(PII、コンプラNG)
4-2. 本番フィードバック
UI で 👍 / 👎 を取り、trace_id と紐づけます。こうすると「悪評の大半が temperature=0.9 の実験フラグ経由」など、根因分析が可能です。
ステップ5:運用で効くダッシュボードを作る
実際に使われるダッシュボードは、項目を欲張らない方が強いです。最初は次の 6 つに絞ってください。
- P50/P95 レイテンシ(全体 + モデル別)
- リクエスト数とエラー率(HTTP + LLM例外)
- 日次コスト(全体 + feature別)
- input/output token 推移
- retrieval 件数と空振り率
- ユーザー評価(👍率)
特に P95 とコストは同一画面に置くのがポイントです。高速化で品質が落ちた、または品質改善でコストが跳ねた、というトレードオフが即時に見えます。
よくある失敗と回避策
失敗1:Prompt全文を生で保存して個人情報を漏らす
対策は、PII マスキングを export 前に必ず実行することです。メール、電話番号、住所は正規表現だけでなく、NER ベースで二重防御すると安全です。
失敗2:span属性の命名がバラバラ
llm.input_tokens と input_token_count が混在すると集計不能になります。命名規約をリポジトリに固定し、CI で lint してください。
失敗3:高カーディナリティ地獄
user_id をそのままメトリクスラベルに入れると TSDB が破綻します。ユーザー軸は trace/log に置き、metrics は tenant や plan 程度に抑えます。
導入ロードマップ(2週間)
- Day 1-2: FastAPI + LLM呼び出しに trace 埋め込み
- Day 3-4: token/cost メトリクス送信
- Day 5-6: Grafana ダッシュボード構築
- Day 7-9: しきい値アラート設計(P95、error、cost)
- Day 10-12: 品質評価バッチ導入
- Day 13-14: インシデント演習(意図的劣化を検知できるか)
2週間で「見える化」は十分達成できます。完璧を目指すより、まず計測可能にすることが重要です。
まとめ
LLM運用で本当に困るのは、失敗そのものではなく「失敗の理由が見えない」状態です。OpenTelemetry を使って retrieval、generation、token、cost、品質を一貫して観測できるようにすると、改善サイクルが回り始めます。
可観測性は守りではなく、開発速度を上げるための攻めの基盤です。まずは span を3つに分けるところから始めてください。それだけで、LLM運用の景色が大きく変わります。