メインコンテンツへスキップ

8.4.4 ログと監視

この節の位置づけ

多くの LLM アプリはローカルの Demo ではうまく動きますが、本番に出すとすぐにひとつの問題が見えてきます。

問題が起きても、どこが壊れたのかまったく分からない。

ログと監視の価値は、「たくさん記録すること」ではなく、次の点にあります。

システムに問題が起きたときに、原因を特定し、説明し、再現し、修復できるようにすること。

学習目標

  • ログ、指標、追跡がそれぞれ何の問題を解決するのか理解する
  • 構造化ログのフィールド設計を学ぶ
  • LLM システムで特に監視すべき指標を理解する
  • 最小限のログ + 監視の例を読めるようになる

初学者向けの用語ブリッジ

可観測性は、次の言葉を分けて考えると理解しやすくなります。

用語何に答えるかLLM アプリでの例
logある瞬間に何が起きたか検索開始、モデル呼び出し失敗、エクスポート完了
metric全体の傾向はどうかエラー率、P95 レイテンシ、平均 token コスト
trace1件のリクエストはどの経路を通ったかAPI -> 検索 -> モデル -> テンプレート描画 -> 返却
P95 / P99遅い 5% または 1% のリクエストはどれくらい遅いか平均は悪くないのに、たまに遅いと感じる原因を見る
observability外側からシステムの状態を理解できるかログ、指標、trace、ダッシュボード、アラートの組み合わせ

まずはこう覚えると十分です。ログは個別イベント、指標は集計値、trace は1件のリクエストの複数ステップをつなぐものです。


まずは全体像をつかもう

ログと監視は、「何が起きたか -> 全体の様子はどうか -> 1件のリクエストは何を通ったか」という順で理解すると分かりやすいです。

この節で本当に解決したいのは次のことです。

  • 問題が起きたとき、まずどの層を見るべきか
  • なぜログ、指標、trace のどれかひとつでも欠けると、原因調査が難しくなるのか

なぜこの話がとても重要なのか?

LLM システムの障害は、普通の API より見えにくい

普通の API のエラーは、たいてい分かりやすいです。

  • 500
  • タイムアウト
  • パラメータの間違い

でも LLM システムには、次のような“やわらかい障害”があります。

  • 回答の質が下がる
  • 検索結果がずれる
  • token コストが急増する
  • 特定の場面でだけ失敗する

だから観測できる仕組みがないと、システムはよくこうなります。

まだ動いているように見えるのに、実はもう半分壊れている。

ログと監視は、何を解決するのか?

ざっくり 3 層に分けて考えられます。

  • ログ:何が起きたか
  • 指標:どれくらい起きているか、どれくらい速いか、どれくらい高いか
  • 追跡:1件のリクエストがどの手順を通ったか

初心者向けのたとえ

可観測性は、次のように考えるとイメージしやすいです。

  • システムにメーターパネル、ドライブレコーダー、整備記録を付ける

これがないと、システムが壊れても言えるのはせいぜいこうです。

  • なんとなく変だ

でも、これがあれば分かるようになります。

  • どこから異常が始まったか
  • それは一時的か、ずっと続いているか
  • 1件だけの問題か、システム全体の問題か

まずログを見る:いちばん基本で、いちばん壊されやすい

「構造化ログ」とは?

ただ1行の文字列を出すよりも、たとえばこういう出力です。

print("request received")

それより価値が高いのは、次のような構造化されたフィールドを記録することです。

  • request_id
  • user_id
  • stage
  • latency_ms
  • model_name

最小の構造化ログの例

log = {
"trace_id": "trace_001",
"stage": "retrieval",
"query": "返金ポリシーは何ですか",
"latency_ms": 120,
"top_k": 3
}

print(log)

想定出力:

{'trace_id': 'trace_001', 'stage': 'retrieval', 'query': '返金ポリシーは何ですか', 'latency_ms': 120, 'top_k': 3}

このログのいちばん大きな利点は、

あとからフィールドごとに検索したり、集計したりできることです。文字列を人間が目で読むだけではありません。


指標:システム全体の体温計

監視すべき指標の代表例

LLM システムでよく見る指標には、次のようなものがあります。

  • リクエスト数
  • エラー率
  • 平均レイテンシ
  • P95 / P99 レイテンシ
  • token 使用量
  • ツール呼び出し回数
  • 検索ヒット率

最小の指標集計の例

requests = [
{"latency_ms": 800, "tokens": 600, "ok": True},
{"latency_ms": 1200, "tokens": 750, "ok": True},
{"latency_ms": 3000, "tokens": 900, "ok": False}
]

avg_latency = sum(r["latency_ms"] for r in requests) / len(requests)
error_rate = sum(not r["ok"] for r in requests) / len(requests)
avg_tokens = sum(r["tokens"] for r in requests) / len(requests)

print("avg_latency_ms =", avg_latency)
print("error_rate =", error_rate)
print("avg_tokens =", avg_tokens)

想定出力:

avg_latency_ms = 1666.6666666666667
error_rate = 0.3333333333333333
avg_tokens = 750.0

これが、監視ダッシュボードの最小の形です。

初学者がまず覚えるとよい指標表

指標何に答えているか
リクエスト数システムは忙しいか
エラー率システムはよく失敗しているか
平均 / P95 レイテンシユーザーは待たされすぎていないか
token 使用量コストが異常ではないか
検索ヒット率RAG の流れが悪くなっていないか
ツール呼び出し成功率Agent の実行層は安定しているか

この表は、初心者にとても向いています。
「指標はたくさんある」という話を、理解しやすい問いに戻してくれるからです。

ログ、指標、Trace の可観測性図

図の読み方

ログは「何が起きたか」、指標は「全体の傾向はどうか」、trace は「1件のリクエストがどこを通ったか」を答えます。LLM システムの障害対応では、この3つをつなげて見る必要があります。500 とタイムアウトだけを見ていても足りません。


追跡(trace):1件のリクエストは、何をたどったのか?

なぜ LLM システムでは trace が特に重要なのか?

1件のリクエストが、1つのモジュールだけを通るとは限らないからです。たとえば次のような流れがあります。

  1. API 受付
  2. 検索
  3. ツール呼び出し
  4. モデル生成
  5. 後処理

もし最終的な回答が間違っていたら、知りたいのは次の点です。

  • 検索が間違っていたのか
  • モデル生成が間違っていたのか
  • それともツール層が落ちたのか

最小の trace の例

trace = [
{"trace_id": "trace_001", "stage": "api_in", "latency_ms": 20},
{"trace_id": "trace_001", "stage": "retrieval", "latency_ms": 120},
{"trace_id": "trace_001", "stage": "llm_generate", "latency_ms": 850},
{"trace_id": "trace_001", "stage": "response_out", "latency_ms": 15}
]

for item in trace:
print(item)

想定出力:

{'trace_id': 'trace_001', 'stage': 'api_in', 'latency_ms': 20}
{'trace_id': 'trace_001', 'stage': 'retrieval', 'latency_ms': 120}
{'trace_id': 'trace_001', 'stage': 'llm_generate', 'latency_ms': 850}
{'trace_id': 'trace_001', 'stage': 'response_out', 'latency_ms': 15}

trace の核心は、

同じリクエストの「完全な旅の記録」を見られることです。

はじめて本番障害を調べるときの、いちばん安全な順番

通常は、次の順で見るのが安定しています。

  1. まず指標に全体的な異常があるかを見る
  2. 次にログで、どの種類のリクエストが失敗しているかを見る
  3. 最後に trace をたどって、全体の流れを確認する

この順番のほうが、いきなり大量のログを読むよりずっと原因を見つけやすいです。


実際に近い、最小の観測ループ

import time

def timed_stage(name, fn, *args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
latency_ms = int((time.time() - start) * 1000)
log = {
"trace_id": "trace_demo_001",
"stage": name,
"latency_ms": latency_ms
}
print(log)
return result

def fake_retrieve(query):
time.sleep(0.1)
return ["返金ポリシー"]

def fake_llm(docs):
time.sleep(0.2)
return f"{docs} に基づいて回答を生成する"

docs = timed_stage("retrieval", fake_retrieve, "返金ポリシーは何ですか")
answer = timed_stage("llm_generate", fake_llm, docs)
print(answer)

出力例です。実際の latency_ms は少し変わることがあります。

{'trace_id': 'trace_demo_001', 'stage': 'retrieval', 'latency_ms': 100}
{'trace_id': 'trace_demo_001', 'stage': 'llm_generate', 'latency_ms': 200}
['返金ポリシー'] に基づいて回答を生成する

この例は小さいですが、すでに次の重要な項目が入っています。

  • trace_id
  • stage
  • latency

LLM システムで、追加で特に監視すべきもの

普通の API と比べて、LLM システムでは次の項目もよく監視します。

token コスト

これは、次のことに直結するからです。

  • いくらお金がかかっているか
  • prompt がどんどん長くなっていないか

検索品質

たとえば次のようなものです。

  • top-1 が当たっているか
  • 検索結果が空の割合

ツール呼び出しの品質

たとえば次のようなものです。

  • ツール呼び出し成功率
  • パラメータ検証の失敗率
  • リトライ率

回答品質のシグナル

たとえば次のようなものです。

  • ユーザーの追加質問率
  • ユーザーの修正率
  • 低評価率

こうした指標はオフライン評価の代わりにはなりませんが、とても重要です。


なぜアラートは「サービスが落ちたか」だけを見てはいけないのか?

LLM システムの多くの問題は、直接 500 にはならない

たとえば次のような問題があります。

  • 回答品質が落ち続ける
  • token 使用量が急に2倍になる
  • 検索ヒット率が大きく落ちる

こうした問題では、システムはまだ「動いている」かもしれません。
でも、業務としてはもう明らかに悪化しています。

だからアラートは 2 層に分けるのがよい

  • 基本可用性アラート

    • エラー率
    • タイムアウト率
  • 業務品質アラート

    • 検索ヒット率の低下
    • 平均 token 数の異常増加
    • ユーザーのネガティブ反応の増加

初学者がまず覚えるとよいアラートの分け方

アラートの種類典型例
可用性アラートエラー率が高い、タイムアウト率が高い
コストアラートtoken が急増する、呼び出し回数が異常
品質アラート検索ヒット率が下がる、ユーザーの追加質問率が上がる

この表は、LLM システムの「壊れ方」は1種類ではない、と教えてくれます。


「知識ベース駆動の教材生成アシスタント」なら、最初に何を監視すべき?

この種のシステムは、普通の質問応答よりも「見た目は大丈夫そうなのに、実はずれている」問題が起きやすいです。

最初に作るときは、特に次のフィールドを追うとよいです。

監視ポイント何を見ているか
retrieved_count内部資料をちゃんと回収できたか
example_count本当に例題を抽出できたか
source_origin_mix内部資料と外部資料、どちらが主になっているか
export_successWord の出力に成功したか
schema_valid構造化結果がテンプレート要件を満たしているか

最小のログオブジェクトは、たとえばこう書けます。

log = {
"trace_id": "trace_001",
"topic": "割引の文章題",
"retrieved_count": 5,
"example_count": 2,
"schema_valid": True,
"export_success": True,
}

print(log)

想定出力:

{'trace_id': 'trace_001', 'topic': '割引の文章題', 'retrieved_count': 5, 'example_count': 2, 'schema_valid': True, 'export_success': True}

この例は初心者にとても向いています。
なぜなら、この種のプロジェクトの監視ポイントが、

  • モデルが速いかどうか
  • だけではなく、
  • ちゃんと資料を取れているか
  • 構造が形になっているか
  • 文書を書き出せているか

まで含むと分かるからです。


とても実用的なログフィールドの一覧

LLM サービスを作るなら、実用的なフィールドはだいたい次のとおりです。

フィールド役割
trace_id全体の流れをつなぐ
user_id / session_idユーザーや会話を特定する
stage今どの工程にいるか
latency_msその工程にどれくらいかかったか
model_nameどのモデルを使ったか
prompt_tokens / completion_tokensコスト分析
tool_nameどのツールを呼んだか
retrieval_topk検索設定
error_code失敗の種類

すべてのログに全部入れる必要はありませんが、設計の出発点としてはとても使いやすい一覧です。


初学者がよくやるミス

文字列だけを記録して、フィールドを記録しない

あとで集計しづらくなります。

成功だけを記録して、失敗を記録しない

これだと、問題の切り分けがとてもつらくなります。

trace_id がない

問題が起きても、1本の流れを最後まで追えません。

システム可用性だけを監視して、業務品質を監視しない

LLM プロジェクトでは、これは特に起こりやすい落とし穴です。


まとめ

この節でいちばん大事なのは、「ログの出し方を覚えること」ではなく、次を理解することです。

ログ、指標、trace が一緒になって、システムの可観測性を作る。これが、本番に出した LLM サービスを本当に運用できるかどうかを決める。

観測できなければ、多くの障害は推測するしかありません。
観測できれば、システムは初めて保守可能になります。

これをプロジェクトやシステム設計として見せるなら、何を見せるべきか

見せる価値が高いのは、たいてい次のようなものです。

  • 「ログシステムをつないだ」こと
  • ではなく、
  1. 1件のリクエストの trace
  2. 主要な指標のセット
  3. 典型的なエラーをどう特定したか
  4. 品質アラートと可用性アラートをどう分けたか

こうすると、見る人には次のことが伝わりやすくなります。

  • あなたが理解しているのは、可観測性の閉ループである
  • 単にログを print できるだけではない

練習

  1. 本節の timed_stage()error_code フィールドを追加してみましょう。
  2. 検索フェーズ専用の、自分のログ構造を設計してみましょう。
  3. 考えてみましょう。サービスのエラー率は変わっていないのに、ユーザーの追加質問率が急に上がったら、普通は何を意味するでしょうか?
  4. 自分の言葉で説明しましょう。なぜ LLM システムのアラートは、500 とタイムアウトだけを見ていてはいけないのでしょうか。