LLMアプリが「静かに壊れる」時代に備える——LLM Observability 2026年版実践ガイド
AIエージェントを本番運用していると、ある日こんな出来事が起きます。
ステータスコードは 200。APIエラーも出ていない。ログには「成功」と書いてある。なのに、ユーザーから「返答がおかしい」「同じ質問に毎回違う答えが来る」というクレームが届く。
サーバーは動いている。ネットワークも問題ない。でもアプリケーションは、ゆっくりと静かに壊れています。
これが LLMアプリ特有の障害パターン です。従来のシステム監視ツールは、「プロセスが生きているか」「レスポンスタイムが正常か」は教えてくれる。けれど「出力の品質が落ちていないか」「コストが静かにスパイクしていないか」「プロンプトを変えた後に何が変わったか」は、ぜんぜん教えてくれません。
そこで 2026 年に本格的な注目を集めているのが LLM Observability(LLM 可観測性) という考え方です。この記事では、なぜこの概念が生まれたのか、どんなツールがあるのか、Python での実装例を交えながら整理していこうと思います。
第1章:「Observability」と「Monitoring」は何が違うのか
まず言葉の整理から始めましょう。
Monitoring(監視) は、あらかじめ決めた指標をウォッチすることです。CPU 使用率が 80% を超えたらアラート、レスポンスタイムが 500ms を超えたらアラート——このように「何を見るか」を事前に定義します。
Observability(可観測性) は、もう少し広い概念です。システムの 内部状態を外部から推測できる能力 のことを指します。予期しない問題が発生したとき、「なぜそうなったのか」を掘り下げられる設計になっているか、という問いです。
LLMアプリにこの二つを当てはめると、違いがよく見えてきます。
Monitoring で把握できること:
- API リクエスト数
- レスポンスタイム
- エラーレート
- インフラコスト
LLM Observability で追加できること:
- 各推論ステップの入出力トレース
- トークン使用量とモデル別コスト
- 出力の品質スコア(幻覚率、根拠の適切さ)
- プロンプトバージョンごとの比較
- ユーザーセッション単位の会話品質
- RAG パイプラインの検索品質
特にエージェントが複数のツールを呼び出す マルチステップ処理 では、どのステップで品質が落ちたのかを追跡する能力が不可欠です。「最終出力が変だった」だけわかっても、デバッグのしようがない。
# 悪い例: エラーかどうかしか判別できない
try:
response = llm.call(prompt)
save_result(response.text)
except Exception as e:
log.error(e)
# よい例: トレースで内部状態を記録する
with tracer.trace("rag_pipeline") as span:
span.set_input(prompt)
retrieved_docs = retriever.search(query)
span.set_attribute("retrieved_docs_count", len(retrieved_docs))
with tracer.span("llm_call") as llm_span:
response = llm.call(prompt, context=retrieved_docs)
llm_span.set_attribute("tokens_used", response.usage.total_tokens)
llm_span.set_attribute("model", response.model)
span.set_output(response.text)
トレースを見れば、「検索は 5 件ヒットしていたけどプロンプトへの組み込みが失敗していた」「LLM の呼び出し自体は成功したけど入力が空だった」といった原因が一目でわかります。
第2章:主要ツール 4 選を比較する
2026 年時点で、LLM Observability の主要プレイヤーは大きく分けると 4 種類のアプローチに分類できます。
| ツール | タイプ | オープンソース | 無料枠 | セルフホスト |
|---|---|---|---|---|
| Langfuse | LLM Engineering Platform | ✅ MIT | 50K events/月 | ✅ 無料 |
| Helicone | プロキシ型ゲートウェイ | △ 一部OSS | 100K requests/月 | ✅ あり |
| LangSmith | トレーシング + 評価 | ❌ | 5K traces/月 | Enterprise のみ |
| Arize Phoenix | ML/LLM 監視 | ✅ ELv2 | Unlimited(自己ホスト) | ✅ 無料 |
それぞれの特徴を見ていきましょう。
Langfuse:フレームワーク非依存の本命
Langfuse は MIT ライセンスの OSS で、セルフホストも Cloud 版も使えます。OpenTelemetry に対応していて、LangChain・LlamaIndex・独自実装を問わずトレースを取れるのが最大の強みです。
プロンプト管理機能もあって、「プロンプト v1 と v2 で出力品質がどう変わったか」を可視化できます。チームで AI アプリ開発している場合、プロンプトのバージョン管理を Git 以外の場所でも行いたいというニーズにそのまま応えてくれます。
Helicone:5 分でセットアップできるプロキシ型
Helicone は「ベース URL を変えるだけ」というアプローチが特徴的です。既存のコードにほぼ手を加えずにログ取得を始められます。
OpenAI・Anthropic・Together AI など 20+ プロバイダーに対応していて、マルチプロバイダー構成を取っているチームには便利です。ただし品質評価(Hallucination スコアなど)は他ツールとの併用が必要になります。
LangSmith:LangChain ユーザーの鉄板
LangChain や LangGraph を使っているなら、LangSmith との統合は非常にスムーズです。エージェントの各ステップが自動でトレースされるうえ、アノテーション機能でチームメンバーが出力に「良い/悪い」をラベリングできます。人間によるフィードバックを評価データセットに取り込む流れが整っています。
ただし LangChain 外での利用はやや摩擦があり、Closed Source なのでデータをクラウドに送りたくないケースには向きません。
Arize Phoenix:評価重視の OSS
Arize AI の OSS 版である Phoenix は、LLM の出力品質を多角的に評価する機能が充実しています。幻覚検出、根拠との整合性チェック、有害コンテンツ検出など「出力が良いかどうか」の評価に特化したツールです。
完全にローカルで動かせるので、機密データを外部に送れない業務ユースケースにも適しています。
第3章:Langfuse の実装例——Python で始める LLM Observability
実際に手を動かしながら理解するのが一番早いので、Langfuse を使った実装例を見てみましょう。
インストール
pip install langfuse openai
環境変数の設定
export LANGFUSE_PUBLIC_KEY="pk-lf-xxxxxxxxxxxxxx"
export LANGFUSE_SECRET_KEY="sk-lf-xxxxxxxxxxxxxx"
export LANGFUSE_HOST="https://cloud.langfuse.com"
# セルフホストの場合: export LANGFUSE_HOST="http://localhost:3000"
最もシンプルな統合: OpenAI SDK のラップ
from langfuse.openai import openai # OpenAI SDK をラップしたモジュールを使う
client = openai.OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "あなたは丁寧な日本語アシスタントです。"},
{"role": "user", "content": "機械学習とは何ですか?"}
]
)
print(response.choices[0].message.content)
# → Langfuse ダッシュボードに自動でトレースが記録される
たったこれだけで、以下の情報が Langfuse ダッシュボードに記録されます。
- 入力プロンプト
- 出力テキスト
- モデル名
- トークン数(入力 / 出力 / 合計)
- レスポンスタイム
- コスト(概算)
マルチステップの RAG パイプラインをトレース
from langfuse import observe, Langfuse
from langfuse.openai import openai
client = openai.OpenAI()
langfuse = Langfuse()
@observe()
def retrieve_documents(query: str) -> list[str]:
"""ベクトル DB からドキュメントを検索する(ダミー実装)"""
# 実際には Chroma や Pinecone などを使う
return [
f"ドキュメント1: {query} に関連する情報...",
f"ドキュメント2: {query} の補足情報...",
]
@observe()
def generate_answer(query: str, context: list[str]) -> str:
"""コンテキストを使って回答を生成する"""
context_text = "\n".join(context)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"以下のコンテキストに基づいて質問に答えてください:\n{context_text}"
},
{"role": "user", "content": query}
]
)
return response.choices[0].message.content
@observe() # ← この一行でトレースのルートになる
def rag_pipeline(query: str) -> str:
"""RAG パイプライン全体"""
docs = retrieve_documents(query)
answer = generate_answer(query, docs)
return answer
# 実行
result = rag_pipeline("Python の非同期処理の書き方を教えてください")
print(result)
@observe() デコレータを付けるだけで、各関数の入出力・実行時間がすべてネストしたトレースとして記録されます。Langfuse ダッシュボードでは rag_pipeline → retrieve_documents → generate_answer という木構造で可視化されます。
セッションとユーザーを紐付ける
マルチターン会話では、会話全体を一つのセッションとして追跡したいことがあります。
from langfuse import Langfuse
from langfuse.openai import openai
langfuse = Langfuse()
client = openai.OpenAI()
def chat_with_user(user_id: str, session_id: str, messages: list) -> str:
"""ユーザーとセッションを紐付けてトレースする"""
trace = langfuse.trace(
name="chat_session",
user_id=user_id,
session_id=session_id,
metadata={"channel": "web_app"}
)
generation = trace.generation(
name="chat_completion",
model="gpt-4o",
input=messages
)
response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
answer = response.choices[0].message.content
generation.end(
output=answer,
usage={
"input": response.usage.prompt_tokens,
"output": response.usage.completion_tokens
}
)
return answer
# 使い方
messages = [
{"role": "user", "content": "こんにちは、調子はどうですか?"}
]
reply = chat_with_user(
user_id="user_123",
session_id="session_abc",
messages=messages
)
これで「user_123 が session_abc というセッションでどんな会話をして、どれくらいのトークンを消費したか」が一目でわかるようになります。コスト分析やユーザーごとの利用量制限にも役立ちます。
プロンプトのバージョン管理
Langfuse にはプロンプトを管理する機能があります。コード内にプロンプトをべた書きすると、変更のたびにデプロイが必要になってしまう。Langfuse で管理すれば、ダッシュボードからプロンプトを更新するだけで即座に反映できます。
from langfuse import Langfuse
langfuse = Langfuse()
# Langfuse ダッシュボードで作成したプロンプトを取得
prompt = langfuse.get_prompt("customer-support-system")
# 最新バージョンのプロンプトテキストを使う
system_message = prompt.compile(
product_name="AI SKILL Lab",
language="Japanese"
)
print(system_message)
# → ダッシュボードで設定したプロンプト(変数展開済み)
prompt.compile() でテンプレート変数を展開できます。バージョン管理も Langfuse 側で自動的に行われるので、「昨日の v2 と今日の v3 でどの程度品質が変わったか」をトレースデータと照らし合わせて分析できます。
第4章:Helicone で既存コードに 1 行だけ追加する
「Langfuse の SDK を入れるのはちょっとコードの変更が大きい...」という場合は、Helicone のプロキシモデルが便利です。
from openai import OpenAI
# Before(変更前)
client = OpenAI(api_key="your-openai-key")
# After(変更後): base_url と default_headers を追加するだけ
client = OpenAI(
api_key="your-openai-key",
base_url="https://oai.helicone.ai/v1",
default_headers={
"Helicone-Auth": "Bearer your-helicone-key"
}
)
# あとはまったく同じ使い方
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}]
)
base_url と default_headers の 2 箇所だけ変えれば、すべてのリクエストが自動でログされます。既存のコードベースが大きい場合、SDK の差し替えより遥かに楽です。
Anthropic の Claude API でも同様に使えます。
import anthropic
client = anthropic.Anthropic(
api_key="your-anthropic-key",
base_url="https://anthropic.helicone.ai",
default_headers={
"Helicone-Auth": "Bearer your-helicone-key"
}
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "こんにちは"}]
)
Helicone でキャッシュを有効にする
同じプロンプトへのリクエストをキャッシュしてコストを削減することもできます。
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Pythonとは何ですか?"}],
extra_headers={
"Helicone-Cache-Enabled": "true",
"Helicone-Cache-Ttl": "86400", # 24時間キャッシュ
"Helicone-Session-Id": "session-xyz",
"Helicone-User-Id": "user-123"
}
)
よく使われる FAQ 系の質問に対しては、キャッシュを効かせるだけで API コストを大幅に削減できます。
第5章:実際の運用で気をつけたいこと
技術的な設定が整ったら、次は「どう運用するか」が問題になってきます。
①「コストスパイク」を早期に検知する
LLMアプリの運用で地味に怖いのが、知らない間にコストが増えていること。プロンプトが長くなった、ユーザーが急増した、特定ユーザーが異常に大量リクエストを送っている——こういった変化を早期に検知するために、コストのアラートを設定しておくことが大切です。
Langfuse ではカスタムアラートを設定でき、「1 時間あたりのトークン使用量が X を超えたら通知」という設定が可能です。
# トークン使用量をトレースに記録しておけばダッシュボードで可視化できる
generation = trace.generation(
name="llm_call",
model="gpt-4o",
usage={
"input": response.usage.prompt_tokens,
"output": response.usage.completion_tokens,
"total": response.usage.total_tokens
}
)
②「プロンプト変更の影響」を必ず測定する
プロンプトを変えるのはとても簡単ですが、その影響を測定している人は意外と少ないです。
「なんとなく良くなった気がする」で終わらせず、変更前後のトレースデータを比較することで、実際に出力品質が改善しているかどうかを確認できます。
Langfuse では Experiments 機能を使って、同じ入力に対して複数プロンプトバージョンを並行評価できます。
# A/B テスト的なプロンプト評価
for prompt_version in ["v1", "v2"]:
prompt = langfuse.get_prompt("answer-prompt", version=prompt_version)
trace = langfuse.trace(
name="prompt_comparison",
metadata={"prompt_version": prompt_version}
)
# 同じ入力で出力を生成
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": prompt.compile()},
{"role": "user", "content": test_query}
]
)
# スコアを記録(人間評価・自動評価どちらでも)
trace.score(
name="quality",
value=evaluate_response(response.choices[0].message.content),
comment=f"Prompt {prompt_version} evaluation"
)
③「エージェントのループ」を検知する
マルチエージェントシステムで起きやすい問題の一つが、エージェントが同じツールを無限に呼び出し続けるループです。コストが際限なく増え続け、最終的にタイムアウトするまで誰も気づかない——というケースが実際に発生します。
トレースを見れば「このセッションで同一ツールが 20 回呼ばれている」という異常をすぐに検知できます。
import time
from langfuse import observe, Langfuse
langfuse = Langfuse()
@observe()
def agent_tool_call(tool_name: str, args: dict) -> str:
"""ツール呼び出しをトレース"""
start = time.time()
result = execute_tool(tool_name, args)
elapsed = time.time() - start
# 遅延が異常に長い場合は警告をメタデータに残す
if elapsed > 10:
langfuse.trace(
name="slow_tool_alert",
metadata={
"tool": tool_name,
"elapsed_seconds": elapsed,
"severity": "warning"
}
)
return result
④ 個人情報のマスキング
ユーザーの入力にメールアドレスや電話番号が含まれることがあります。それをそのままトレースに記録すると、観測基盤がプライバシーリスクになります。
import re
from langfuse import observe
def mask_pii(text: str) -> str:
"""個人情報をマスクする"""
# メールアドレスをマスク
text = re.sub(r'\b[\w.-]+@[\w.-]+\.\w+\b', '[EMAIL]', text)
# 電話番号をマスク
text = re.sub(r'\b0\d{1,4}[-\s]?\d{1,4}[-\s]?\d{4}\b', '[PHONE]', text)
return text
@observe()
def safe_llm_call(user_input: str) -> str:
"""PII をマスクしてからトレースする"""
masked_input = mask_pii(user_input)
# トレースにはマスク済みの入力を記録
# 実際の LLM 呼び出しにはオリジナルを使うかどうかはポリシー次第
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": user_input}]
)
return response.choices[0].message.content
第6章:ツール選定の考え方
長々と見てきましたが、「結局どれを使えばいいの?」という話をまとめておきます。
Langfuse を選ぶべき場面:
- LangChain/OpenAI/Anthropic のどれを使っているかに関係なく導入したい
- セルフホストしてデータを外部に出したくない
- プロンプトのバージョン管理も一元化したい
- チームで評価スコアを管理したい
Helicone を選ぶべき場面:
- とにかく今日から始めたい(5 分で設定完了)
- 既存コードへの変更を最小限にしたい
- コストとレイテンシの監視だけで十分
- マルチプロバイダー構成を取っている
LangSmith を選ぶべき場面:
- LangChain / LangGraph を中心に使っている
- 人間によるアノテーションワークフローが必要
- Closed Source でも問題ない
Phoenix(Arize)を選ぶべき場面:
- 完全ローカルで動かしたい
- 出力の品質評価(幻覚検出など)に力を入れたい
- 既存の ML 監視基盤(Arize AI)と統合したい
# ツール選定チェックリスト(コードで表現するなら)
def choose_observability_tool(requirements: dict) -> str:
if requirements.get("self_host") and requirements.get("framework_agnostic"):
return "Langfuse"
if requirements.get("minimal_code_change"):
return "Helicone"
if requirements.get("langchain_heavy"):
return "LangSmith"
if requirements.get("evaluation_first") and requirements.get("fully_local"):
return "Phoenix"
# デフォルト: まず Langfuse から試してみる
return "Langfuse"
まとめ
LLMアプリは「動いている」と「正しく動いている」が全然別の話です。HTTP 200 は返ってくるけど、出力品質が徐々に落ちていく——これは本番環境で実際に起きます。
2026 年に本格的に普及しつつある LLM Observability は、そのギャップを埋めるための考え方とツールセットです。
- Langfuse:フレームワーク非依存、セルフホスト可能、OSS
- Helicone:プロキシ型、最小コード変更、マルチプロバイダー対応
- LangSmith:LangChain エコシステムに深く統合
- Phoenix:評価重視、完全ローカル動作
どれから始めても、「始めること」が大事だと思います。トレースを取り始めてから初めて「あ、このステップでこんな問題が起きてたのか」という発見が生まれる。
AIエージェントが「もう一人の開発者」として機能し始めた今、その動作を観測する仕組みを持つことは、もはや任意ではなく必須の設計要素になりつつあります。
まずは Langfuse の無料枠(月 5 万イベント)か Helicone の無料枠(月 10 万リクエスト)から試してみてはいかがでしょうか。