powered by TechFeed
表示モード
Deep Dive

LLMは「動かす」より「運用する」が難しい — コスト・可観測性・ルーティングを単一ゲートウェイで制御するStack Overflowの設計論

10月9日、Stack Overflow Blogが「Part 5: Operating an LLM system: observability, cost, routing, and the platform underneath」と題した記事を公開した。LLMシステムを本番環境で安定運用するための可観測性・コスト管理・ルーティング・インフラ基盤の設計について、実装レベルで踏み込んだ内容となっている。

10月9日、Stack Overflow Blogが「Part 5: Operating an LLM system: observability, cost, routing, and the platform underneath」と題した記事を公開した。LLMシステムを本番環境で安定運用するための可観測性・コスト管理・ルーティング・インフラ基盤の設計について、実装レベルで踏み込んだ内容となっている。


「動く」ではなく「運用できる」かどうかが問題だ

LLMシステムは壊れ方が通常のサービスと根本的に異なる。通常のサービスは500エラーを返す。LLMシステムは誤った判断を高速かつ大規模に実行し続ける。この非対称性こそが、可観測性や制御機構を後回しにできない理由だ。

本記事はStack Overflow Blogが連載している「LLMシステム成熟度モデル」の第5回にあたる。最初の4レベルでシステムを「動かす」ことと「正しくする」ことを扱い、Level 5では日々運用できるかという問いに答える。

記事全体を貫くコアの考え方はシンプルだ:すべてのモデルトラフィックが通る単一のチョークポイント、すべてを串刺しにするdecision_id、そしてどのベンダーが応答したかをドメイン層が知らない設計。この3つが揃っていなければ、以下のどの施策も機能しない。


可観測性:REDメトリクスだけでは不十分

通常のサービス監視(リクエスト数、レイテンシ、エラー率、CPU)は「サービスが生きているか」を教えてくれるだけで、「エージェントが正しく判断しているか」は何も教えてくれない。そのため、AIシステム固有のデシジョンレイヤーが必要になる。

記事が提示するメトリクスは4つのファミリーに分類される:

ファミリー 代表メトリクス 何を監視するか
decision agent_auto_execution_ratio 自動/人間の判断比率
safety guardrail_blocks_total, judge_disagreements_total 何がブロックされたか、ジャッジとの不一致
cost/perf llm_tokens_total, llm_cost_usd_total トークン消費と費用
quality shadow_eval_pass_ratio, human_override_rate 本番トラフィックの品質

最低限計装すべき4つとして記事が挙げるのはauto_execution_ratio、guardrail_blocks_total、llm_cost_usd_total、human_override_rate。この4つだけで、行動・安全・費用・品質のすべてをカバーできる。

トレースもHTTPレベルでは不十分で、エージェントの内部グラフ(どのノードが何秒かかったか、どこでブロックされたか)まで可視化する必要がある。decision_idを全スパンに付与することで、任意の判断を1回のトレース検索で再構築できる。トレースの実装にはOpenTelemetryが標準的な選択肢として使われており、スパンへのdecision_id付与もそのSDKで実現できる。

アラートは「原因」ではなく「症状」に張る。記事が提示する例:

  • auto_execution_ratioが7日間ベースラインから15%以上変動 → warning
  • rate(guardrail_blocks_total[5m])が直前1時間の3倍超 → critical
  • judge_disagreements / judge_invocationsが15%超 → critical
  • 1時間のコストがbudget/24超 → warning→page

SLOも通常とは異なる定義が必要だ。「判断を返せること」が可用性(5xxではなく「決定に失敗する」ことが真のダウン)、品質はshadow-evalがベースラインから-2%以内、コストは計画比±20%以内、という形で定義する。


コスト管理:請求書が来るまで見えない問題

LLMコストには厄介な性質がある。請求書が届くまで見えない。しかも、トークン数、コール数、リトライ、誰かが追加した冗長なプロンプト、といった誰も見ていない場所でスケールする。

解決策は「安いモデルに乗り換える」ではなく、コストを可観測にし、境界を設けること。そのすべてはチョークポイント(単一ゲートウェイ)に集約される:

def complete(self, req: ChatRequest) -> ChatResponse:
    rate_limiter.check(req.tenant_id, est_tokens(req))
    resp = self._adapter.complete(req)
    metrics.incr("llm_tokens_total", resp.tokens_in,  direction="in",  model=req.model, tenant_id=req.tenant_id)
    metrics.incr("llm_tokens_total", resp.tokens_out, direction="out", model=req.model, tenant_id=req.tenant_id)
    metrics.incr("llm_cost_usd_total", cost(req.model, resp), model=req.model, tenant_id=req.tenant_id, capability=req.capability)
    return resp

これでコストがテナント×ケイパビリティ×モデルの軸で帰属できる。「なんとなくAIが高い」ではなく、「このケイパビリティのこのプロンプトが原因」と特定できる。

コスト削減レバーは優先順位順に以下のとおり:

  1. モデルを呼ばない — ルールで解ける処理にLLMを使わない。これが最も効果が大きい
  2. バッチ処理 — N回のコールを1回に
  3. キャッシュ — エンベディングや繰り返しのルックアップを再利用
  4. モデルの適材適所 — 単純な判断には安価なモデルを使う。1コールあたり5〜20倍の差が出るのが一般的
  5. プロンプトの整理 — 不要なコンテキストを削る

レート制限と予算上限はRedisなどを使ってレプリカ間で状態を共有することが必須だ。各ワーカーがローカルメモリだけで管理すると、レプリカごとに上限の全量を許してしまう。また、静かに請求額を10倍にする「サイレントな乗数」としてリトライストーム(検証失敗によるモデルの再呼び出し)とツールループの無限化(エージェントが終了しない)の2つを明示的に監視・上限設定すべきとしている。


パフォーマンス:ボトルネックはモデルではない

パイプラインが遅い場合、原因はほぼモデルではない。記事が挙げる典型パターン:

  • O(n²)のマッチングループ → ハッシュ/キー結合で1億比較を2万操作に
  • レコードごとのモデル呼び出し → バッチ化、または決定論的なケースはコードで処理
  • 直列実行できるステージ → 有界な並行処理(上限なしの並行化はプロバイダーのレート制限を叩くだけ)
  • 再計算 → 決定論的な処理はキャッシュ

記事が強調するのは「最適化の前にクオリティのベースラインを固定する」こと。速くなったが品質が下がった変更はすべてリバートする。10,000×10,000アイテムのマッチングが「モデルが遅い」と感じられても、プロファイルを取ると実際は1億回のネストループが原因だった、という事例が紹介されている。


ルーティング・フェイルオーバー・キルスイッチ

記事では「モデルを1つ使う」ではなく「モデルのフリートを運用する」という考え方を提示している。安価なモデルと高性能なモデル、プライマリとジャッジ、複数プロバイダー——これらを単一ゲートウェイの背後に置き、ドメイン層はどのモデルが応答したかを知らない。この設計をLLMゲートウェイと呼ぶこともある。

ルーティングの基本的な考え方は、リクエストの「ケイパビリティ」タグに応じて適切なモデルを選択することだ。たとえば、単純な分類タスクには安価なモデルを、複雑な推論が必要なタスクには高性能なモデルを割り当てる。この判断をドメイン層ではなくゲートウェイ層に集中させることで、モデルの差し替えやA/Bテストがドメインコードに触れずに実施できる。

フェイルオーバーはプロバイダー障害時の自動切り替えを指す。ゲートウェイがプロバイダーのエラーレートを監視し、閾値を超えた時点でセカンダリプロバイダーへ自動的にトラフィックを切り替える。この切り替えもドメイン層には透過的であり、decision_idはそのまま引き継がれるためトレースの連続性が保たれる。

キルスイッチは、コスト爆発・安全性違反・品質劣化が検知された場合に特定のケイパビリティやモデルへのトラフィックをゼロにする機構だ。前述のアラート閾値(たとえばguardrail_blocks_totalの急増)と連動させ、人間の介入なしに自動停止できることが重要とされている。いずれの機構も、単一チョークポイントとドメイン非依存設計が前提にあって初めて機能する。


まとめ

Level 5で提示されている内容は、「LLMを動かすこと」と「LLMを運用すること」の間にある距離を埋めるための具体的な仕様書だ。メトリクス定義からSLO、コスト制御のコード、パフォーマンス最適化の順序、そしてゲートウェイによるルーティング・フェイルオーバー・キルスイッチまで、実装レベルで使える内容が揃っている。

詳細はPart 5: Operating an LLM system: observability, cost, routing, and the platform underneathを参照していただきたい。