ストーリー
田
田中VPoE
サービング基盤が設計できた。次はモニタリングと可観測性だ。「計測できないものは改善できない」— まずはログ収集から始めよう
あなた
今はCloudWatchでエラー率を見ているだけです。何を追加で記録すべきですか?
あ
田
田中VPoE
LLMのログは従来のアプリケーションログとは根本的に違う。プロンプトの内容、トークン数、コスト、応答品質…すべてを構造化して記録する必要がある。ただしPIIの取り扱いには細心の注意が必要だ
ログ設計の原則
LLMログに記録すべき情報
| カテゴリ | 項目 | 目的 |
|---|
| 識別情報 | リクエストID、トレースID、セッションID | リクエストの追跡とデバッグ |
| コンテキスト | サービス名、ユーザーID、環境(prod/staging) | フィルタリングと分析 |
| 入力 | システムプロンプト、ユーザー入力、RAG取得コンテキスト | 品質改善、問題再現 |
| 出力 | モデル応答、finish_reason | 品質評価、ハルシネーション検出 |
| メタデータ | モデル名、プロバイダ、温度、max_tokens | パフォーマンス分析 |
| パフォーマンス | レイテンシ、TTFT、入力トークン数、出力トークン数 | 性能監視 |
| コスト | トークン単価、リクエストあたりコスト | コスト管理 |
| セキュリティ | PII検出フラグ、インジェクション検出フラグ | セキュリティ監査 |
ログレベルの設計
| レベル | 記録内容 | 適用場面 |
|---|
| Minimal | メタデータ + パフォーマンス + コスト | 高トラフィック環境。コスト重視 |
| Standard | Minimal + 入出力の要約(先頭100文字) | 通常運用。品質監視 |
| Detailed | Standard + 入出力の全文 | デバッグ、品質分析。PII注意 |
| Debug | Detailed + 中間ステップ(RAG検索結果等) | 開発・調査時のみ |
PII除去パイプライン
PII検出の対象
| PII種別 | 検出方法 | マスキング例 |
|---|
| メールアドレス | 正規表現 | user@example.com → [EMAIL] |
| 電話番号 | 正規表現 | 090-1234-5678 → [PHONE] |
| クレジットカード番号 | 正規表現 + Luhnチェック | 4111-1111-1111-1111 → [CARD] |
| マイナンバー | 正規表現 | 123456789012 → [MY_NUMBER] |
| 人名 | NERモデル | 田中太郎 → [PERSON] |
| 住所 | NERモデル | 東京都渋谷区... → [ADDRESS] |
マスキングパイプラインの構成
ログ収集パイプライン:
リクエスト/レスポンス
│
▼
┌──────────────────┐
│ 1. 構造化 │ JSON形式にパース
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 2. PII検出 │ 正規表現 + NER
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 3. マスキング │ 検出されたPIIを置換
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 4. コスト計算 │ トークン × 単価
└────────┬─────────┘
│
├──→ Langfuse(モニタリング)
└──→ S3(長期保存)
構造化ログの設計
トレースとスパン
LLMの呼び出しは複数のステップで構成されることが多く、トレーシングが重要です。
RAGチャットボットのトレース例:
Trace: req-abc123
├── Span: input_validation (5ms)
│ └── PII検出、入力サニタイズ
├── Span: rag_retrieval (150ms)
│ ├── Span: embedding_generation (30ms)
│ ├── Span: vector_search (80ms)
│ └── Span: reranking (40ms)
├── Span: prompt_construction (2ms)
│ └── システムプロンプト + RAG結果 + ユーザー入力
├── Span: llm_call (2800ms)
│ ├── model: gpt-4o
│ ├── input_tokens: 1500
│ ├── output_tokens: 350
│ └── cost: ¥12.5
├── Span: output_validation (10ms)
│ └── PIIフィルタ、有害性チェック
└── Span: response (1ms)
└── ユーザーへの返却
Total: 2968ms
Langfuseへの送信例
from langfuse import Langfuse
langfuse = Langfuse()
# トレース開始
trace = langfuse.trace(
name="cs-ai-chat",
user_id="user-456",
metadata={
"service": "cs-ai",
"environment": "production",
},
)
# RAG検索スパン
retrieval_span = trace.span(
name="rag_retrieval",
input={"query": sanitized_query},
)
# ベクトル検索
results = vector_store.search(query_embedding, top_k=5)
retrieval_span.end(
output={"num_results": len(results), "top_score": results[0].score},
)
# LLM呼び出しスパン(Generation)
generation = trace.generation(
name="llm_call",
model="gpt-4o",
input=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": sanitized_query},
],
model_parameters={"temperature": 0.3, "max_tokens": 1024},
)
response = openai.chat.completions.create(...)
generation.end(
output=response.choices[0].message.content,
usage={
"input": response.usage.prompt_tokens,
"output": response.usage.completion_tokens,
},
)
ログの保存と管理
| 項目 | 設計 |
|---|
| 短期保存(ホット) | Langfuse(30日)。リアルタイム分析・ダッシュボード用 |
| 中期保存(ウォーム) | S3 Standard(1年)。品質分析・トレンド分析用 |
| 長期保存(コールド) | S3 Glacier(3年)。コンプライアンス・監査用 |
| データ削除 | 保持期間経過後に自動削除。GDPR対応の削除リクエスト処理 |
まとめ
| ポイント | 内容 |
|---|
| 記録すべき情報 | 識別情報、入出力、メタデータ、パフォーマンス、コスト、セキュリティ |
| PII除去 | 正規表現 + NER で検出し、ログ保存前にマスキング |
| トレーシング | 複数スパンで構成されるトレースとして構造化 |
| 保存戦略 | ホット(Langfuse)→ ウォーム(S3)→ コールド(Glacier)の階層 |
チェックリスト
推定読了時間: 30分