最終更新: 2026年8月16日
LLMオブザーバビリティとは、本番のLLMアプリケーションで発生するプロンプト、応答、レイテンシ、コスト、エラーを構造化トレースとして収集・可視化し、劣化やハルシネーションを検知できる状態を維持する運用実践のことです。従来のAPM(New Relic、Datadog)は「HTTPリクエスト単位」の可観測性しか提供しないため、非決定的なLLM呼び出しやマルチステップエージェントには不足します。2026年時点で本命となっているのは OpenTelemetry GenAI Semantic Conventions を土台に、Langfuse か Arize Phoenix のいずれかで UI とEvalsを重ねる構成です。この記事では私が3つのプロダクションで運用してきた結果を元に、どの組み合わせをいつ選ぶべきかを整理します。
- OpenTelemetry の GenAI Semantic Conventions は 2026年前半に Stable に昇格し、ベンダーロックインなしでトレース基盤を統一できるようになった。
- Langfuse はセルフホスト前提のプロンプト管理・データセット評価に強く、CTOレビュー向けのコスト分解ダッシュボードが最短で立ち上がる。
- Arize Phoenix は OpenInference 準拠のトレースと Evals ライブラリを組み込みで持ち、オフライン評価とドリフト検出に強い。
- 本番の最低ラインは p50/p95/p99 レイテンシ・token 使用量・エラーレート・retryカウントの4指標を span 属性として送出すること。
- プロンプトキャッシュヒット率、フォールバックモデルの発火率、PII 検出件数を SLO として追跡すると、コスト事故を事前検知できる。
- ツール選定は「トレース基盤(OTel)」「ホスト形態(SaaS or Self-hosted)」「Evals深度」の3軸で決めるのが再現性が高い。
なぜ従来のAPMではLLMアプリを監視しきれないのか
私が最初にプロダクション LLM を任されたとき、既存の Datadog APM だけで足りると思っていました。実際、ルート span でレイテンシと 5xx は見えます。しかし本番3週目に「回答の質が落ちた」という報告が来たとき、私たちの手元には プロンプトの内容も、モデルが返した内容も、どのモデルバージョンが呼ばれたかも 残っていませんでした。従来 APM の想定する「1リクエスト = 1決定的な処理」というモデルが、非決定的な LLM 呼び出しに合っていないのです。
LLM アプリの障害は HTTP 500 として現れるとは限りません。ハルシネーション、ツール選択ミス、無限ループ、コスト暴走、コンテキスト長超過による切り詰め ── いずれも「200 OK」で返ってきます。これを検知するには、span 属性としてプロンプト、応答、トークン数、モデル名、ツール呼び出し、リトライ回数を残す必要があります。2026年になって OpenTelemetry の GenAI Semantic Conventions が Stable になったことで、この属性名がやっとベンダー間で揃いました。裏を返せば、ここを踏まずに独自属性でトレースを作ると、ツールを乗り換えるたびにダッシュボードを作り直す羽目になります。
もう一つ従来 APM が苦手なのは「エージェント」です。1つのユーザークエリが 10 回以上の LLM 呼び出しとツール実行に分岐するので、フラットなトランザクションビューでは全体像がつかめません。ツリー構造でネストされた span を可視化できる LLM 特化のオブザーバビリティが必要になります。私たちが エージェント Evals を回すときも、まず本番トレースをデータセット化するところから始めます。
本番LLMで最低限とるべきシグナル
「最初にどこまで計測すべきか」は、私が最も頻繁に聞かれる質問です。過剰計測はコストとノイズを増やすだけなので、まずは以下の4カテゴリを固めることをおすすめします。
- レイテンシ:span 単位で p50 / p95 / p99。ストリーミングを使う場合は「最初のトークンまでの時間(TTFT)」と「トークンあたりの平均時間」も分ける。
- トークン使用量とコスト:
gen_ai.usage.input_tokens と gen_ai.usage.output_tokens。モデル別レートを掛けて USD/リクエストを算出。
- 信頼性:エラーレート、リトライ回数、フォールバック発火率、レートリミット命中回数。
- 品質:LLM-as-a-Judge スコア、ユーザーフィードバック(👍/👎)、決定論的ルール違反(スキーマ違反、禁止語など)。
この4分類が SLO の元になります。例えば私のチームでは「p95 レイテンシ < 4.5 秒」「エラーレート < 0.5%」「フォールバック発火率 < 8%」「LLM-Judge スコア平均 > 0.85」を SLO として設定しています。フォールバック発火率をあえて SLO に入れているのは、これが上がっているときは主モデル(Claude Sonnet 5 や GPT-5)側で潜在的な障害が起きているサインだからです。5分間隔でアラート閾値を超えたら PagerDuty に飛ぶようにしています。
もう一つ実務で効いたのは「セッション ID」の付与です。1人のユーザーが 20 分間に 30 回 LLM を呼び出しているケースを1つの単位として扱うことで、コスト暴走を「1リクエストのコスト」ではなく「1セッションのコスト」で見られるようになります。これができていないと、無限ループのエージェントが1時間で 200 ドル溶かした事故に気づけません。
OpenTelemetry GenAI Semantic Conventions の使い方
2026年の本番構成で外せないのが OpenTelemetry の GenAI Semantic Conventions です。これは LLM 呼び出しの span 属性名を業界標準として定義したもので、モデル名は gen_ai.request.model、入力トークンは gen_ai.usage.input_tokens、出力トークンは gen_ai.usage.output_tokens、レスポンスの終了理由は gen_ai.response.finish_reasons のように統一されています。
この規約に沿ってトレースを送出しておくと、後から Langfuse、Arize Phoenix、Datadog、Grafana Tempo など、どのバックエンドに繋ぎ換えても同じダッシュボードが動きます。逆に、独自命名(例:llm.model、tokens_in など)で作ると、ツール移行のたびにクエリを書き直す羽目になります。私が過去に一度やらかしたのがこれで、Langfuse から Phoenix に移すのに丸 3 日かかりました。
OpenLLMetry(Traceloop 提供)や OpenInference(Arize 提供)といったライブラリは、この規約を LangChain / LlamaIndex / OpenAI SDK / Anthropic SDK に自動計装として差し込んでくれます。手で SDK ラッパーを書く前に、まずはこれらのオートインストルメンテーションを試すのが定石です。
Langfuse と Arize Phoenix の実運用比較
UI と Evals を提供する OSS ツールは 2026 年時点で Langfuse と Arize Phoenix が二強です。私は両方を本番運用した経験があります。以下は、率直な比較です。
| 比較軸 | Langfuse | Arize Phoenix |
| ライセンス | MIT(Cloud も無料枠あり) | Elastic License 2.0 |
| ホスト形態 | Docker Compose / Helm でセルフホスト容易 | 単一コンテナで即起動、K8s 対応も可 |
| トレース規約 | 独自 SDK + OTel(GenAI SemConv 対応) | OpenInference(OTel 準拠) |
| プロンプト管理 | ◎(バージョニング・A/B・ラベル) | △(限定的) |
| Evals ライブラリ | ◯(LLM-as-a-Judge 内蔵) | ◎(Phoenix Evals + Ragas 連携) |
| データセット/実験管理 | ◎ | ◎ |
| コストダッシュボード | ◎(最初から USD 表示) | ◯(手動セットアップ) |
| 推奨シーン | プロダクト側の LLM 機能運用 | ML 寄りのオフライン評価・ドリフト検出 |
ざっくり言うと、Langfuse は「PMやCTOに見せる本番ダッシュボード」を最短で立ち上げたい場合に強く、Phoenix は「MLエンジニアが実験・評価を回す」用途に強いです。両方導入するチームもありますが、初手ならユースケースが多い方から入るのがよいでしょう。私は Web プロダクトなら Langfuse から、RAG 精度改善プロジェクトなら Phoenix から入ることが多いです。
SaaS か セルフホスト か
プロンプトと応答はプロダクトの中核データなので、金融・医療・エンタープライズ向けの場合はセルフホスト一択です。Langfuse は PostgreSQL + ClickHouse 構成で、月間 1000 万 span までなら 4vCPU / 16GB のインスタンスで捌けます。Phoenix はもう少し軽量で、開発環境なら docker run 1発で起動します。
実装:FastAPI + LangChain にトレースを差し込む
言葉だけでは動かないので、FastAPI + LangChain + Anthropic Claude の最小構成に OpenTelemetry トレースを差し込む例を示します。ここでは OpenLLMetry を使って自動計装し、Langfuse に送出します。PydanticAI や LangGraph でも同じアプローチが使えます。
# requirements.txt
# fastapi==0.115.0
# uvicorn==0.32.0
# langchain==0.3.14
# langchain-anthropic==0.3.1
# traceloop-sdk==0.35.0
# opentelemetry-exporter-otlp==1.29.0
import os
from fastapi import FastAPI
from pydantic import BaseModel
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
from traceloop.sdk import Traceloop
from traceloop.sdk.decorators import workflow
# Traceloop を Langfuse に向ける (OTLP エンドポイント)
Traceloop.init(
app_name="support-bot",
api_endpoint="https://cloud.langfuse.com/api/public/otel",
headers={
"Authorization": f"Basic {os.environ['LANGFUSE_AUTH']}",
},
disable_batch=False, # 本番では必ず True 側 (バッチ送信)
)
app = FastAPI()
llm = ChatAnthropic(model="claude-sonnet-5-20260315", max_tokens=1024)
class Query(BaseModel):
session_id: str
user_id: str
question: str
@workflow(name="answer_question")
def answer(q: Query) -> str:
# session_id / user_id を span 属性として付与
Traceloop.set_association_properties({
"session.id": q.session_id,
"user.id": q.user_id,
})
resp = llm.invoke([HumanMessage(content=q.question)])
return resp.content
@app.post("/ask")
def ask(q: Query):
return {"answer": answer(q)}
この構成で立ち上げると、Langfuse の UI に answer_question という workflow trace が現れ、その中に anthropic.chat という span がネストされます。gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.request.model などの GenAI SemConv 属性は自動で埋まります。session.id でグルーピングすれば、1セッションあたりの合計コストが即座に見えます。
フォールバックとリトライを span に残す
本番では「Claude Sonnet 5 → GPT-5 → Gemini 3 Pro」といったフォールバックを組むことが多いですが、フォールバックが発火した事実を span 属性に残しておかないと後で原因分析ができません。llm.fallback.triggered=true と llm.fallback.reason="rate_limit" を付ける習慣をつけましょう。私のチームでは、フォールバック発火率が 24 時間平均で 5% を超えたら Slack に自動通知します。
最初に作るべきダッシュボードと SLO
「Here is the dashboard you will wish you had built first」── これは私が新人 LLM ops エンジニアに毎回言うセリフです。以下が本番初日から必要になる 5 枚のパネルです。
- レイテンシ p50/p95/p99(モデル別):モデル切替の判断材料。
- USD/日(モデル別・機能別):予算アラートの基盤。CFO 向け説明の必須データ。
- エラーレート + リトライ回数:上流(Anthropic/OpenAI)障害の即時検知。
- キャッシュヒット率(プロンプトキャッシュ):これが下がるとコストが跳ねる。50% 下回ったら要調査。
- LLM-Judge スコア分布:モデルアップグレード時の品質退行を即座に検知。
これに加えて、以下の SLO を最初から設定しておくと、事故検知が劇的に早くなります。
- 可用性 SLO: 99.5% (28日ウィンドウ)
- レイテンシ SLO: p95 < 4.5s (機能別)
- コスト SLO: 1セッションあたり平均 < $0.15
- 品質 SLO: LLM-Judge 平均 > 0.85
Langfuse を使っている場合、これらの指標は built-in ダッシュボードから CSV エクスポートできるので、Grafana や Metabase に横流ししてトラフィックライトを作るのが定番です。エージェント側の品質退行検知については、専用の Evals パイプライン を組んで CI に統合すると事故を未然に防げます。
PII 対策とコスト暴走の防ぎ方
本番で最も怖い事故は、トレース経由での PII 漏洩と、無限ループによるコスト暴走の2つです。この2つは相互に関連していて、両方とも「入力を span に生で流している」ことが根本原因です。
PII 対策としては、OTel Collector 側で attributes processor と正規表現ベースの redaction を挟むのが最も安全です。SDK 側でマスクすると開発者が個別に忘れがちなので、Collector で強制する運用にしましょう。Presidio(Microsoft) を Collector の前段に立てると、名前・住所・電話番号を高精度で検出できます。プロンプト本文自体は gen_ai.prompt.content ではなく gen_ai.prompt.hash(SHA-256 の先頭 16 文字)だけを送るという運用にしているチームもあります。
コスト暴走対策は、以下の三段構えが定番です。
- ハード上限:1セッションあたりの最大 LLM 呼び出し回数を SDK 側でハードコード(例: 30 回)。
- ソフトアラート:1ユーザーあたり 1日 $10 を超えたら Slack に警告。
- キルスイッチ:全体 USD/分が閾値を超えたら、フィーチャーフラグで LLM 呼び出しを即座に無効化。
3番目のキルスイッチは、私が実際に「デバッグ中の無限ループが 1 時間で $800 溶かした」インシデントを経験してから追加しました。フィーチャーフラグ(LaunchDarkly、Unleash、Statsig など)から on/off を切れるようにしておくと、本番でヤバい状況になったときに即座に止められます。
よくある落とし穴と回避策
私が過去に踏んだ地雷と、他社の LLM ops エンジニアから聞いたトラブルを整理しておきます。
- 非同期処理でトレースが途切れる:Python asyncio 環境では context propagation が壊れがち。
opentelemetry-instrumentation-asyncio を必ず入れる。
- ストリーミングレスポンスの span が閉じない:SSE / WebSocket の場合、コールバックの
on_llm_end で明示的に span.end() を呼ぶ実装が必要。
- 本番でトレースサンプリング率を 100% にする:トラフィックが跳ねたときにトレースストア側で詰まる。10-30% のヘッドサンプリングが実務的。
- プロンプトのバージョンを span に残していない:プロンプト変更で品質退行したときに原因が特定できない。
prompt.version と prompt.name を必ず属性化する。
- ツール呼び出しを span 化していない:エージェントのデバッグで必須。tool 名、引数、返り値を子 span として残す。
- コンテキスト長超過で切り詰められた事実を検知していない:
finish_reason=length を集計してアラート化する。
特にプロンプトバージョニングは、Langfuse を使うと Git 的にバージョン管理できるので運用が楽になります。Phoenix ユーザーは自前で MLflow などと組み合わせるケースが多い印象です。Claude Agent SDK の Hooks と組み合わせれば、ツール呼び出しの前後で自動的に span を作れます。
よくある質問
LLMオブザーバビリティと通常のAPMの違いは何ですか?
通常の APM は HTTP リクエストの成否とレイテンシを追跡します。LLM オブザーバビリティはそれに加えて、プロンプト内容、モデル応答、トークン数、コスト、ツール呼び出しツリー、品質スコア(LLM-as-a-Judge)を span 属性として保持し、非決定的な処理の異常を検知できるようにします。既存の APM を置き換えるのではなく、補完する位置づけです。
Langfuse と Arize Phoenix のどちらを選ぶべきですか?
プロダクト側で LLM を使うプロダクトチーム(SaaS の機能追加など)なら Langfuse が最短でダッシュボードとプロンプト管理を提供してくれます。RAG の精度改善や ML 実験主体のチームなら Phoenix の Evals ライブラリが強力です。両方併用するチームもあります。
OpenTelemetry GenAI Semantic Conventions は必須ですか?
2026 年時点では事実上の必須です。この規約に沿って span 属性を送出しておくと、Langfuse・Phoenix・Datadog・Grafana など、どのバックエンドにも切り替えられます。独自属性で作るとツール移行時に全ダッシュボードを作り直すことになります。
トレースに含めたプロンプトから PII は漏れませんか?
デフォルト設定のままだと漏れます。OTel Collector の attributes processor で正規表現による redaction を挟むか、Microsoft Presidio のような PII 検出ライブラリを前段に立てるのが標準的な対策です。より厳格な運用では、プロンプト本文ではなく SHA-256 ハッシュだけを span に残すチームもあります。
1リクエストあたりのコストはどう計算しますか?
span 属性の gen_ai.usage.input_tokens と gen_ai.usage.output_tokens、そしてキャッシュヒット時の gen_ai.usage.cache_read_input_tokens にモデル別の単価を掛けて算出します。Langfuse は主要モデルの単価テーブルを内蔵しているのでダッシュボードに USD が直接表示されます。Phoenix ではプラグインまたは手動で単価テーブルを設定します。
サンプリング率はどう決めればよいですか?
本番のトラフィックが数百 QPS 以下なら 100% トレースでも問題ありません。それ以上になるとトレースストア(ClickHouse など)側の書き込みが詰まるので、10-30% のヘッドサンプリングに落とすのが実務的です。ただしエラーとフォールバック発火のトレースは 100% 残す tail sampling を併用することをお勧めします。