OpenTelemetry LLMオブザーバビリティ実践:2026年AIアプリケーションのエンドツーエンド監視ガイド
DevOps
2026年、なぜLLMアプリケーションに専門的なオブザーバビリティが必要なのか
従来のアプリケーション監視はレイテンシ、スループット、エラー率に焦点を当てています。LLMアプリケーションは全く新しいオブザーバビリティ次元を導入します:トークン使用量、API呼び出しコスト、ハルシネーション率、コンテキスト品質。
| 次元 | 従来の監視 | LLM監視 |
|---|---|---|
| レイテンシ | リクエストレイテンシ | 最初のトークンまでの時間(TTFT)、合計レイテンシ |
| スループット | QPS | トークン/秒 |
| エラー | エラー率 | エラー率 + ハルシネーション率 |
| コスト | サーバーコスト | API呼び出しコスト(トークン課金) |
| 品質 | N/A | 精度、関連性、完全性 |
| コンテキスト | N/A | プロンプト長、コンテキストウィンドウ利用率 |
OpenTelemetry LLMセマンティック規約
Span: gen_ai.client.chat
Attributes:
gen_ai.system = "openai"
gen_ai.request.model = "gpt-4o"
gen_ai.usage.input_tokens = 1523
gen_ai.usage.output_tokens = 847
gen_ai.usage.total_tokens = 2370
Python実装:LLM呼び出しのトレース
pip install opentelemetry-api opentelemetry-sdk \
opentelemetry-exporter-otlp openai
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
trace_provider = TracerProvider()
trace_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True))
)
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer("llm-app", "1.0.0")
meter = metrics.get_meter("llm-app", "1.0.0")
LLM呼び出しの手動トレース
import openai, time
from opentelemetry.trace import Status, StatusCode
client = openai.OpenAI(api_key="sk-xxx")
token_counter = meter.create_counter("gen_ai.client.token.usage", unit="tokens")
llm_latency = meter.create_histogram("gen_ai.client.operation.latency", unit="ms")
def traced_chat_completion(messages, model="gpt-4o", temperature=0.7, max_tokens=4096):
with tracer.start_as_current_span("gen_ai.client.chat") as span:
span.set_attribute("gen_ai.system", "openai")
span.set_attribute("gen_ai.request.model", model)
start_time = time.time()
try:
response = client.chat.completions.create(
model=model, messages=messages,
temperature=temperature, max_tokens=max_tokens
)
content = response.choices[0].message.content
usage = {
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
"total_tokens": response.usage.total_tokens
}
latency_ms = (time.time() - start_time) * 1000
span.set_attribute("gen_ai.usage.input_tokens", usage["prompt_tokens"])
span.set_attribute("gen_ai.usage.output_tokens", usage["completion_tokens"])
span.set_status(Status(StatusCode.OK))
token_counter.add(usage["prompt_tokens"], {"gen_ai.token.type": "input", "gen_ai.request.model": model})
token_counter.add(usage["completion_tokens"], {"gen_ai.token.type": "output", "gen_ai.request.model": model})
llm_latency.record(latency_ms, {"gen_ai.request.model": model})
return {"content": content, "usage": usage, "latency_ms": latency_ms}
except Exception as e:
span.set_status(Status(StatusCode.ERROR, str(e)))
raise
コスト監視
COST_PER_TOKEN = {
"gpt-4o": {"input": 0.000005, "output": 0.000015},
"gpt-4o-mini": {"input": 0.00000015, "output": 0.0000006},
}
def calculate_cost(model, input_tokens, output_tokens):
rates = COST_PER_TOKEN.get(model, {"input": 0, "output": 0})
return (input_tokens * rates["input"]) + (output_tokens * rates["output"])
品質監視:ハルシネーション検出
def evaluate_answer_quality(question, answer, context):
with tracer.start_as_current_span("llm.quality.evaluation") as span:
eval_prompt = [
{"role": "system", "content": "回答品質を評価。JSONで返信:{grounded, relevance, completeness}"},
{"role": "user", "content": f"質問:{question}\n参考:{context}\n回答:{answer}"}
]
result = traced_chat_completion(eval_prompt, model="gpt-4o-mini", temperature=0)
import json
evaluation = json.loads(result["content"])
span.set_attribute("llm.quality.grounded", evaluation["grounded"])
return evaluation
LLMオブザーバビリティツール比較
| ツール | OpenTelemetry | トークン追跡 | コスト監視 | 品質監視 | セルフホスト |
|---|---|---|---|---|---|
| OTel + Grafana | ネイティブ | カスタム | カスタム | カスタム | はい |
| Langfuse | 統合 | ネイティブ | ネイティブ | ネイティブ | はい |
| Helicone | プロキシ | ネイティブ | ネイティブ | 基本 | いいえ |
| Arize Phoenix | 統合 | ネイティブ | ネイティブ | ネイティブ | はい |
5つのよくある落とし穴
1. ストリーミングレスポンスのトークン計数の無視
ストリーミングレスポンスのusageフィールドが空の場合があります。
2. 不適切なサンプリングレート
高トラフィックLLMアプリで100%サンプリングすると大量のTraceデータが生成されます。
3. プロンプト内容のTraceへの漏洩
完全なプロンプトをSpan属性に記録しないでください。
4. モデルバージョンの未区別
同じモデル名が異なるバージョンに対応する場合があります。
5. エンドツーエンドのSpan相関の欠落
RAGパイプラインのSpanはparent spanで関連付ける必要があります。
10のエラートラブルシューティング
| # | 症状 | 原因 | 解決方法 |
|---|---|---|---|
| 1 | TraceがGrafanaに表示されない | OTLP Exporter設定エラー | endpointと認証を確認 |
| 2 | トークン計数がゼロ | ストリーミングでusage未取得 | 最後のchunkで取得 |
| 3 | コスト計算が不正確 | 価格表が古い | COST_PER_TOKENを更新 |
| 4 | ハルシネーション率が異常に高い | RAG検索品質が低い | ベクトルインデックスを確認 |
| 5 | Span属性が欠落 | セマンティック規約バージョン不一致 | OTel SDKを更新 |
| 6 | メモリリーク | TracerProviderが未クローズ | 終了時にshutdown()を呼び出し |
| 7 | 重要なTraceがサンプリングで消失 | サンプリングレートが低すぎる | エラーは100%サンプリング |
| 8 | TTFTが異常に高い | ネットワーク遅延またはコールドスタート | ネットワークとモデルウォームアップを確認 |
| 9 | 品質評価自体がハルシネーション | 評価モデルが信頼できない | より強力な評価モデルを使用 |
| 10 | MetricとTraceが不一致 | タイムゾーン不一致 | UTCを統一使用 |
おすすめツール
- JSONフォーマッター:/ja/json/format
- Base64エンコーダー:/ja/encode/base64
- ハッシュ計算:/ja/encode/hash
まとめ:LLMアプリケーションのオブザーバビリティは、トークン使用量、APIコスト、ハルシネーション率、回答品質という従来の指標を超える次元に注目する必要があります。OpenTelemetryのLLMセマンティック規約が2026年の標準化アプローチを提供します。Grafanaダッシュボードと組み合わせることで、コストから品質まで包括的なLLMアプリケーション監視を実現できます。
ブラウザローカルツールを無料で試す →
#OpenTelemetry#LLM可观测性#AI监控#Token追踪#Grafana#RAG监控#成本监控#质量监控