OpenTelemetryでLLMの運用コストとパフォーマンスを徹底可視化
LLMを活用したアプリケーション開発が本格化する一方で、多くの開発チームが運用フェーズでの課題に直面しています。「API利用料が想定以上に膨らんでいる」「応答速度が不安定でユーザー体験を損なっている」といった問題です。しかし、従来のAPM (Application Performance Monitoring) ツールだけでは、トークン消費量やプロンプトの内容といったLLM特有の指標を追うのが難しく、根本原因の特定に至らないケースが少なくありません。この記事では、オープンソースの標準仕様である OpenTelemetry を活用し、LLMアプリケーションのコストとパフォーマンスを深く可観測(オブザーブ)するための具体的な手法を解説します。
はじめに:LLMアプリケーションにおけるコストとパフォーマンスの重要性
LLMアプリケーションの運用における課題は、従来のWebアプリケーションとは性質が異なります。最大の要因は、多くのLLMがAPI経由の従量課金モデルで提供されている点です。入力するプロンプトと生成されるテキストの量、つまりトークン数に応じてコストが直接変動するため、非効率なプロンプトや意図しない長い応答は、即座に運用コストの増大に繋がります。
パフォーマンスも同様に重要です。LLMの推論処理には相応の計算リソースと時間が必要であり、単純なAPI呼び出しでも数百ミリ秒から数秒のレイテンシが発生します。特に、複数の処理を組み合わせるRAG (Retrieval-Augmented Generation) のような複雑なワークフローでは、どこがボトルネックになっているのかを特定し、ユーザーが待てる応答時間内に処理を完了させることが不可欠です。これらのコストとパフォーマンスの問題を解決するには、アプリケーションの内部動作を詳細に可視化する オブザーバビリティ の仕組みが鍵となります。
OpenTelemetryでLLMの実行状況を深く観測する基礎
OpenTelemetry (OTel) は、テレメトリデータ(トレース、メトリクス、ログ)を生成・収集するための、特定のベンダーに依存しないオープンな標準仕様およびツール群です。アプリケーションのコードを一度計装 (instrumentation) すれば、データを送信するバックエンド(Datadog, Grafana Cloud, Honeycombなど)を柔軟に切り替えられる利点があります。
LLMアプリケーションの観測において、OpenTelemetryの主要なシグナルは以下のように役立ちます。
- トレース (Traces): 一連の処理の流れを「スパン」と呼ばれる単位で記録します。ユーザーからのリクエスト開始から応答を返すまで、どの関数やAPI呼び出しにどれだけ時間がかかったかを可視化できます。RAGパイプラインにおけるベクトル検索、プロンプト生成、LLM呼び出しといった各ステップの所要時間を正確に把握するのに最適です。
- メトリクス (Metrics): トークン消費量、API呼び出し回数、エラー率、レイテンシといった数値を時系列データとして集計します。システム全体の傾向を把握し、ダッシュボードでの監視やアラート設定に利用します。
- ログ (Logs): エラーの詳細やデバッグ情報など、特定のイベントに関する文脈情報を提供します。トレースと紐付けることで、「この遅いリクエストでは、どのようなプロンプトが使われていたのか」といった深掘り分析が可能になります。
近年、主要なプログラミング言語のエコシステムでは、LLMクライアントライブラリ向けのOpenTelemetry計装が提供されています。例えば、Pythonの opentelemetry-instrumentation-openai のようなライブラリを使えば、わずかなコード追加でOpenAI API呼び出しのトレースや基本的なメトリクスを自動的に収集できます。
LLMコストを最適化する:トークンとAPI呼び出しの可視化戦略
LLMの運用コストを管理する上で最も重要な指標は トークン消費量 です。OpenTelemetryを使えば、どの処理が、どのモデルで、どれだけのトークンを消費しているかを正確に追跡できます。
コスト最適化の第一歩は、LLM呼び出しを記録するスパンに、トークン数に関する情報を属性 (Attribute) として付与することです。OpenTelemetryのセマンティック規約では、以下のようなLLM関連の属性が定義されつつあります。
llm.usage.prompt_tokens: プロンプトで消費したトークン数llm.usage.completion_tokens: 生成された応答で消費したトークン数llm.usage.total_tokens: 合計トークン数llm.request.model: 使用したモデル名 (例:gpt-4o)llm.vendor: LLM提供ベンダー名 (例:OpenAI)
これらの属性をスパンに追加することで、可観測性プラットフォーム上で柔軟な分析が可能になります。例えば、「モデルごとの合計トークン消費量」をグラフ化して高コストなモデルの利用状況を把握したり、「ユーザーID」や「テナントID」をカスタム属性として追加し、特定のユーザーによるコスト急増を検知したりできます。
以下は、Pythonで手動でOpenAI API呼び出しを計装し、トークン情報をスパン属性に追加する擬似コードの例です。
from opentelemetry import trace
import openai
# TracerProviderなどの初期化は完了している前提
tracer = trace.get_tracer(__name__)
def call_openai_with_telemetry(prompt_text: str, model_name: str):
with tracer.start_as_current_span("openai_chat_completion") as span:
try:
# スパンにリクエスト情報を付与
span.set_attribute("llm.vendor", "OpenAI")
span.set_attribute("llm.request.model", model_name)
# OpenAI API 呼び出し
response = openai.chat.completions.create(
model=model_name,
messages=[{"role": "user", "content": prompt_text}]
)
# レスポンスからトークン使用量を取得して属性に追加
usage = response.usage
if usage:
span.set_attribute("llm.usage.prompt_tokens", usage.prompt_tokens)
span.set_attribute("llm.usage.completion_tokens", usage.completion_tokens)
span.set_attribute("llm.usage.total_tokens", usage.total_tokens)
# ステータスを正常に設定
span.set_status(trace.Status(trace.StatusCode.OK))
return response.choices[0].message.content
except Exception as e:
# エラー情報を記録
span.record_exception(e)
span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
raise
この計装により、リクエストごとにコストの内訳が明確になり、「プロンプトが冗長すぎる」「意図せず高価なモデルが使われている」といった問題をデータに基づいて特定し、改善に繋げられます。
LLMパフォーマンスを改善する:レイテンシとスループットの徹底分析
ユーザー体験に直結するパフォーマンスの改善には、応答時間の詳細な分析が欠かせません。OpenTelemetryのトレースは、リクエスト全体の処理を時系列で可視化するウォーターフォール図を生成し、ボトルネックの特定を容易にします。
特にLLMアプリケーションでは、以下のレイテンシ指標が重要です。
- Time to First Token (TTFT): APIリクエストを送信してから、最初のトークンが返ってくるまでの時間。ストリーミング応答を実装している場合、この時間が短いほどユーザーは「すぐに反応が始まった」と感じ、体感速度が向上します。
- Total Latency (End-to-End Latency): リクエスト開始から、全ての応答が完了するまでの総時間。
これらの指標を計測するには、LLM呼び出しをラップする処理の中でタイムスタンプを記録し、スパンのイベント (Event) や属性として追加します。
RAGのように複数のコンポーネントが連携するシステムでは、パイプライン全体を一つの親スパンとし、各処理(データ検索、プロンプト整形、LLM呼び出し、後処理など)を子スパンとして作成することで、処理全体のどこに時間がかかっているかが一目瞭然になります。例えば、「ベクトルデータベースの検索クエリが遅い」のか、「LLM自体の推論に時間がかかっている」のかを明確に切り分けられます。
パフォーマンス監視では、レイテンシだけでなく スループット(単位時間あたりに処理できるリクエスト数)も重要です。特定のモデルや長いプロンプトのリクエストが増加すると、システム全体のスループットが低下することがあります。モデルごとのリクエスト数や平均レイテンシをメトリクスとして収集し、負荷状況とパフォーマンスの相関関係を監視することが、安定したサービス提供に繋がります。
OpenTelemetryデータを活用した問題特定と継続的な改善サイクル
データを収集するだけでは意味がありません。収集したテレメトリデータを活用して問題を特定し、改善アクションを実行し、その結果を再びデータで評価するという継続的な改善サイクルを回すことが重要です。
以下に、具体的な問題解決シナリオを挙げます。
-
シナリオ1:コストが予期せず増加した
- 検知: コスト監視ダッシュボードで、
total_tokensの合計値が急増していることを発見。 - 特定: 属性
llm.request.modelでドリルダウンすると、特定の高コストモデルの利用が急増していることが判明。さらにuser_idで絞り込むと、一部のユーザーからのリクエストであることがわかる。 - 分析: 該当するリクエストのトレースを確認。ユーザーが非常に長いドキュメントを要約させるために、何度もプロンプトに全文を含めていたことが原因と推測。
- 改善: 長文入力に対しては、より安価でコンテキストウィンドウが広いモデルを自動で選択するロジックを導入。あるいは、プロンプトの長さに応じた利用制限を実装する。
- 検知: コスト監視ダッシュボードで、
-
シナリオ2:APIの応答が遅いという報告があった
- 検知: レイテンシのP95値(95パーセンタイルの応答時間)が悪化しているというアラートが発火。
- 特定: トレースのウォーターフォール図を確認すると、RAGパイプライン内の「ベクトル検索」スパンの処理時間が長くなっているリクエストが多数存在。
- 分析: データベースの負荷状況やクエリのログを確認。特定の種類のクエリでインデックスが効率的に利用されておらず、フルスキャンに近い状態になっていることを発見。
- 改善: ベクトルデータベースのインデックス設定を見直し、クエリを最適化する。
このように、OpenTelemetryで得られた詳細なデータは、憶測ではなく事実に基づいた問題解決を可能にします。
実践的な可観測性基盤の構築と運用への組み込み
OpenTelemetryを本格的に導入するには、アプリケーションからのデータを集約し、分析バックエンドへ転送するための中間層として OpenTelemetry Collector の利用が推奨されます。Collectorは、アプリケーション側への設定変更を最小限に抑えつつ、データのフィルタリング、加工、複数バックエンドへの転送といった高度な処理を担います。
アプリケーションはOTLP (OpenTelemetry Protocol) 形式でデータをCollectorに送信し、Collectorは設定に応じてDatadog, New Relic, Grafana Cloud (Tempo/Mimir/Loki) といった任意の可観測性プラットフォームへデータをエクスポートします。これにより、将来的にバックエンドを乗り換える際も、アプリケーションのコードに手を入れる必要がなくなります。
運用に組み込む最終ステップは、ダッシュボードとアラートの整備です。
- ダッシュボード: 主要なKPI(モデルごとのトークン消費量、P95レイテンシ、エラー率など)を一覧できるダッシュボードを作成し、チーム全体で状況を共有します。
- アラート: 事前に定義した閾値(例: 「1リクエストあたりの
total_tokensが100,000を超えたら通知」「LLM APIのエラー率が5%を超えたら通知」)に基づいてアラートを設定し、問題の早期発見に繋げます。
LLMアプリケーションの複雑さとコスト構造は、私たち開発者に新しいレベルのオブザーバビリティを要求します。OpenTelemetryという標準技術を武器に、アプリケーションの内部を深く理解し、データに基づいた最適化を継続的に実践していくことが、これからのLLM開発の成功の鍵となるでしょう。


