本番で崩れないエージェントシステムの設計
なぜ多くのエージェントが本番で失敗するのか — ステートマシン、明示的なツール契約、リトライ、深い可観測性を使って信頼できるエージェントを設計する方法。

印象的なデモから脆い本番エージェントへ
エージェントシステムは、LLMが単にプロンプトに答えるだけでなく、次に何をするかを決めるアプリケーションです:どのツールを呼ぶか、どのデータを取得するか、どのステップを実行するか、そしていつ「完了」とするか。これらはモデル、複数のツール(API、DB、サービス)、計画/実行ループ、そしてそれらを繋ぐインフラの組み合わせです。
デモでは魔法のように見えます:エージェントが計画を立て、いくつかのツールを呼び、完璧な結果を返します。ハッピーパスは短く、レイテンシは低く、同時に何も失敗しません。
デモが動き、本番で壊れる理由
実際の負荷下では、同じエージェントがデモでは見なかった負荷に晒されます:
- APIがタイムアウトしたり、部分的なデータを返したり、契約が変わる
- 複数のリクエストが共有リソースを争い、状態を汚染する
- 長時間の会話がメモリを膨らませ、コンテキスト上限を越える
- 微妙なモデルの誤りが多くのツール呼び出しで累積する
結果として、再現が難しいフレーク、沈黙するデータ破損、時折ハングや無限ループするユーザーフローが起きます。
真のビジネスインパクト
フレークするエージェントは「驚き」を損なうだけではありません。:
- インシデントやオンコールを引き起こす
- 下流システムに誤答を流し込む
- 利用者の信頼を損ない、機能を使われなくなる
- リトライや暴走ループでクラウドコストを膨らませる
このガイドが扱うこと
この記事は「より良いプロンプト」ではなく、エンジニアリングパターンに焦点を当てます。ステートマシン、明示的なツール契約、リトライと障害処理戦略、メモリと同時実行制御、そして負荷下でエージェントを予測可能にする可観測性パターンを見ていきます。
なぜ多くのエージェントアーキテクチャはスケールで壊れるのか
多くのエージェントは単一のハッピーパスのデモでは問題ありません。しかし、トラフィック、ツール、エッジケースが同時に来ると壊れます。
脆い振る舞い:ループ、停滞、部分実行、沈黙するエラー
素朴なオーケストレーションはモデルが1〜2回の呼び出しで「正しく振る舞う」ことを期待します。実使用では次のようなパターンが繰り返し現れます:
- ループ:完了や失敗を認識できずに同じプランや同じツールを繰り返し呼ぶ
- 停滞:ツールやサブタスクをタイムアウト無しで待ち、ユーザーセッションをハングさせる
- 部分実行:ワークフローの半分だけ終わる(例:メールを下書きして送らない、計画だけ立てて実行しない)
- 沈黙するエラー:ツールが失敗したりスキーマが合わないのに、エージェントは自信ありげに不完全または誤った答えを返す
明示的な状態と終了条件がないと、これらの振る舞いは避けられません。
隠れた非決定性とツールの信頼性欠如
LLMのサンプリング、レイテンシのばらつき、ツールのタイミングは隠れた非決定性を生みます。同じ入力が異なる分岐を辿り、異なるツールを呼び、ツール結果を異なる形で解釈することがあります。
スケールすると、ツールの問題が支配的になります:
- 上流APIやDBのタイムアウトとフラッキーさ
- ツール契約と実際の返却値の間のスキーマドリフト
- エージェントが学習していない不整合なエラーフォーマット
これらはすべてスパリュースなループ、再試行、誤った最終回答につながります。
同時実行がエッジケースとプロダクト不一致を増幅する
10 RPSでは滅多に壊れないものが、1,000 RPSでは常に壊れます。同時実行は次を露呈します:
- 共有状態やキャッシュのレースコンディション
- 枯渇するレート制限による連鎖的なツール障害
- 単一依存の障害で発生するリトライのスロッシュ(thundering herd)
プロダクトチームはしばしば決定論的ワークフロー、明確なSLA、監査性を期待します。エージェントを無制約に放置すると、確率的でベストエフォートな振る舞いになり、信頼性が最も重要なときに予測不能になります。
アーキテクチャがこのミスマッチを無視して、エージェントを従来サービスのように扱うと、システムは壊れやすくなります。
本番向けエージェント設計の原則
本番対応のエージェントは「賢いプロンプト」ではなく、規律あるシステム設計です。役立つ考え方は、エージェントを時折LLMを呼ぶ“小さく予測可能な機械”として扱うことです。ミステリアスなLLM塊がシステムを操るのではありません。
本番用エージェントに必要な条件
重要なのは次の4つの性質です:
- 安全性:データアクセス、副作用、ユーザーへの約束に関する制約を守る。明示的な権限、ツールのガードレール、信頼できない出力の慎重な扱い。\n- 予測可能性:同じ入力と状態なら狭い期待範囲内で振る舞う。できること・できないことを説明可能にする。\n- デバッグ可能性:問題が起きたら、どの状態、どの決定、どのツール、どのモデル呼び出しが原因かを辿れる。隠れたループや構造のない“思考”をなくす。\n- 変更耐性:モデルやツール、戦略を全面書き換えなしにアップグレードできる。
これらはプロンプトだけでは得られません。構造から得られます。
自由形式ループより明示的なワークフローを好む
多くのチームが最初に採るパターンは「doneでない間、モデルを呼び、考えさせ、ツールを呼ぶ、繰り返す」です。プロトタイプは簡単ですが運用は難しい。
より安全なパターンは、エージェントを明示的なワークフローとして表現すること:
- 限られた状態集合を定義する(例:
COLLECTING_INPUT,PLANNING,EXECUTING_STEP,WAITING_ON_HUMAN,DONE)。\n- 状態間で許可される遷移を定義する。\n- LLMは主に局所的決定:次の状態、ツールの選択、パラメータの埋めを行う。
これによりエージェントは各ステップが検査可能、テスト可能、リプレイ可能なステートマシンになります。自由形式ループは柔軟に見えますが、明示的ワークフローがインシデントのデバッグ性と挙動の監査性をもたらします。
“ゴッドエージェント”を小さなスキルに分割する
すべてをやろうとする巨大なエージェントは魅力的ですが、計画、検索、ビジネスロジック、UIオーケストレーションなど無関係な責務が密結合します。
代わりに、小さく責務が明確なエージェント/スキルを組み合わせます:
- タスクを分解するプランナー
- 具体的なステップを実行するエグゼキュータ
- ドメインごとのスペシャリスト(請求、サポート、分析など)
各スキルは独自のステートマシン、ツール、安全ルールを持てます。組成ロジックは高レベルのワークフローになり、一つのエージェントの巨大なプロンプトが肥大化するのを防ぎます。
このモジュール化により各エージェントは推論しやすくなり、ある機能だけを進化させても他が不安定になりにくくなります。
ポリシー、状態、ツールを分離する
便利なメンタルモデルはエージェントを三層に分けることです:
- 意思決定ポリシー(LLMプロンプト + モデル)
どのように次のアクションを選ぶかをカプセル化します。モデルや温度、プロンプトを変えてもシステム配線に触れずに済むようにします。\n - ステートマシン/ワークフローエンジン
現在どこにいるか、どの遷移が可能か、進捗をどう永続化するかを管理します。ポリシーが動作を提案し、ステートマシンが検証して適用します。\n - ツーリング層
実世界で何が起こるかを実装する:API、DB、キュー、外部サービス。ツールは狭く型付けされた契約を公開し、認可、レート制限、入力検証を施します。
この分離によりビジネスロジックをプロンプトに隠す落とし穴を避けられます。LLMは明確で決定論的な殻の中の意思決定コンポーネントになるのです。
小ささと明瞭さを設計する
最も信頼できるエージェントは派手なデモではありません—ホワイトボード上で挙動を説明できるものです。
具体的には:
- 各エージェントは一つの仕事と一つの主要成功指標に集中する。\n- ワークフローと状態遷移を散文ではなく明示的にエンコードする。\n- LLMには、全手順を発明させるのではなく、明確に定義されたオプションの選択をさせる。
小さく、合成可能で構造化されたエージェントを志向することで、スコープが増えても複雑さで倒れることがなくなります。
エージェントワークフローを明示的なステートマシンとしてモデル化する
多くのエージェントは「考える/行動する/観察する」のループとして始まります。デモには良いですがすぐに不透明で脆くなります。より良いアプローチは、エージェントを明示的なステートマシンとして扱うこと:有限の状態集合とイベントで駆動される遷移です。
状態と遷移としてのフロー表現
モデルに次の行動を暗黙に決めさせる代わりに、小さな状態図を定義します:
- PLAN – ユーザー要求を解釈し、ステップに分解、ツールを選ぶ。\n- CALL_TOOL – 検証済み入力で単一ツール呼び出し(またはバッチ)を実行。\n- VERIFY – ツール出力を簡単な不変量や追加のモデルチェックで検証。\n- RECOVER – エラーを処理:再試行、フォールバック、エスカレーション。\n- DONE – 最終回答を返しワークフローを閉じる。\n- FAILED – 明確な理由とコンテキストを持つ終端エラー。
これらの状態間の遷移は、UserRequestReceived, ToolCallSucceeded, ToolValidationFailed, TimeoutExceeded, HumanOverrideなどの型付きイベントで引き起こされます。各イベントと現在の状態が次の状態とアクションを決定します。
これによりリトライやタイムアウトが簡単になります:CALL_TOOLには指数バックオフで3回までリトライ、PLANは再試行しない、のように各状態にポリシーを付与できます。
レジリエンスとスケールのための状態外部化
現在の状態と最小限のコンテキストを外部ストア(DB、キュー、ワークフローエンジン)に永続化します。エージェントは純粋関数になります:
next_state, actions = transition(current_state, event, context)
これにより:
- レジリエンス – ワーカーが途中で落ちても別のワーカーが最後の永続化状態から再開できる。\n- 水平スケーリング – ステートレスワーカーがイベントを消費して状態を更新し、次のイベントを発行する。\n- リプレイと補償 – 実行を再構築し任意の状態から駆動し直せる。ロールバックが必要なときに補償アクションを実行できる。
推論と監査性の利益
ステートマシンがあれば、エージェントの挙動は全て明示的になります:どの状態にいて、どのイベントが起き、どの遷移が発火し、どの副作用が発生したか。これはデバッグを高速化し、インシデント調査を簡素化し、コンプライアンス向けの自然な監査トレイルを作ります。ログと状態履歴から、特定のリスクのあるアクションがどの状態からのみ取られているかを証明できます。
エージェント向けの信頼できるツール契約を設計する
ツールが“散文の中のAPI”ではなく、明示的な保証を持つインターフェースに見えるとエージェントはずっと予測可能に振る舞います。
プロンプトだけでなく契約を定義する
各ツールは次をカバーする契約を持つべきです:
- 入力スキーマ:必須フィールド、型、列挙、制約、デフォルト。\n- 出力スキーマ:成功ペイロード、nullableフィールド、”結果なし”の意味。\n- エラーモデル:
InvalidInput,NotFound,RateLimited,TransientFailureといった型付きエラーの意味。\n- SLA:レイテンシ期待値、可用性目標、レート制限。
この契約を構造化されたドキュメントとしてモデルに提示し、どのエラーが再試行可能か、どれがユーザー介入が必要か、どれがワークフローを止めるべきかをプランナーが理解できるようにします。
厳格なJSONと厳密な検証
ツールのI/Oは他の本番APIと同様に扱います:
- 厳格なJSONスキーマ(OpenAPI、JSON Schemaなど)を入力と出力に使う。\n- 呼び出す前に検証してモデルミスを捕まえ、呼び出した後にも検証してツールの退化を捕まえる。\n- 小さな問題は自動修復(型変換など)してもログに残し、後で微調整する。
これによりプロンプトを簡素化でき、スキーマ駆動の制約で幻覚的な引数や意味のないツールシーケンスを減らせます。
バージョニングと互換性
ツールは進化します。エージェントが壊れないように:
- ツール契約にバージョンを付け(
v1,v1.1,v2)エージェントをバージョンにピンする。\n- フィールドは段階的に非推奨にする。古いフィールドはしばらく読めるように保つ。\n- フィールド追加は後方互換的に行い、意味を黙って変えない。
これにより成熟度の異なるツールとエージェントの混成が安全になります。
障害と劣化モードの扱い
契約は部分的失敗を念頭に設計します:
- アイテムごとのエラー詳細を持つ部分結果を許容する。\n- キャッシュや近似、古いデータなどの劣化応答を定義し、硬い失敗ではなく継続を可能にする。\n- どのフィールドが「ベストエフォート」か、どれが「必須」かを明確にする。
エージェントはこれを受けて機能を低下させながら続行するか、ユーザーに確認を求めるか、代替ツールに切り替えるかを選べます。
セキュリティと認可の境界
ツール契約はセキュリティ制限をエンコードする自然な場所です:
- ツールが読み書きできる範囲をスコープする。\n- 機密操作には明示的パラメータ(例:
confirm: true)を要求する。\n- ユーザースコープとシステムスコープの操作を区別する。
これらはサーバー側のチェックと組み合わせ、モデルだけに「正しく振る舞う」ことを頼らないでください。
良い契約がエージェントを簡素にする理由
ツールに明確でバリデートされ、バージョン管理された契約があると、プロンプトは短くなり、オーケストレーションロジックは単純になり、デバッグが格段に容易になります。複雑さを壊れやすい自然言語指示から決定論的なスキーマとポリシーに移すことで、幻覚的なツール呼び出しや予期せぬ副作用が減ります。
リトライ、冪等性、障害処理パターン
信頼できるエージェントは「すべてはいつか失敗する」と仮定します:モデル、ツール、ネットワーク、調整レイヤーでさえ。目標は失敗を避けることではなく、それを安価かつ安全にすることです。
冪等性:安全な再試行の基礎
冪等性とは「同じ要求を繰り返しても外部から見た効果は1回と同じ」であること。これは、部分失敗や曖昧な応答の際にツール呼び出しを頻繁に再発行するLLMエージェントにとって重要です。
ツールを冪等にする方法:
- リクエストID:各呼び出しに安定した
request_idを付け、ツールがそのIDを見たら同じ結果を返す。\n- Upsert設計:自動採番IDではなく自然キーや合成キーで“作るか更新するか”の意味を持たせる。\n- チェックサムとバージョニング:コンテンツハッシュやバージョン番号を付与して重複や古い書き込みを検出する。
コストを爆発させないリトライ戦略
一時的な失敗(タイムアウト、レート制限、5xx)には構造化されたリトライを使う:指数バックオフ、スロットリング回避のジッター、厳格な最大試行回数。各試行を相関IDでログに残してエージェント挙動を追跡できるようにします。
恒久的な失敗(4xx、バリデーションエラー、ビジネスルール違反)は再試行しないでください。構造化エラーをエージェントポリシーへ返し、再計画、ユーザーへの問い合わせ、別ツール選択などを行わせます。
サーキットブレーカーとフォールバック
エージェント層とツール層の両方にサーキットブレーカーを実装します:繰り返し失敗したツールへの呼び出しを一時的に遮断し、即時失敗にしてフォールバックや劣化モードに切り替える。
エージェントループからの無差別な再試行を避けてください。冪等なツールと明確な失敗クラスがなければ、副作用、レイテンシ、コストが増幅するだけです。
エージェントのメモリ、状態、データ整合性の管理
信頼できるエージェントは「状態とは何か」「それがどこにあるか」を明確にします。
短期状態と長期メモリの分離
エージェントはリクエストを扱うサービスのように扱います:
- 短期状態:現在のタスクやサブタスクを完了するために必要なすべて(目標、現在のステップ、ツール出力、部分決定、制御変数)。スコープを絞り、ワークフロー完了後に破棄する。\n- 長期メモリ:セッションやランを超えて残す情報(ユーザープロファイル、好み、過去の決定、プロジェクト履歴、学習済みショートカット)。
これらを混同するとバグに繋がります。例えば一時的なツール結果をメモリに入れると、将来の会話で古いコンテキストを使ってしまいます。
状態の保存場所
主な選択肢は次の通り:
- インコンテキスト(プロンプト内) – シンプルで低レイテンシだが耐久性に欠ける。単一ランの短期状態に最適。\n2. 外部ストア – DB、キャッシュ、ベクターストア。長期メモリや再起動・ワーカー間の調整が必要な状態に使う。\n3. ハイブリッド – 権威ある状態は外部に保ち、次のステップに必要な分だけをコンテキストにロードする。
良いルール:LLMは明示的状態オブジェクトに対するステートレス関数である。状態をモデルの外に永続化し、そこからプロンプトを再生成する。
“ログをメモリにする”アンチパターンを避ける
会話ログやトレース、プロンプトをそのままメモリ代わりに使うのは一般的な失敗パターンです。
問題点:
- 検索が恣意的で壊れやすい。\n- 重要な事実が長いテキストに埋もれる。\n- 複数ランが矛盾し「最後の書き込み」が不明確になる。
代わりに構造化メモリスキーマを定義します:user_profile, project, task_historyなど。ログは状態から派生させるものとし、その逆にしないでください。
共有データとツールの整合性
複数のツールやエージェントが同じエンティティ(CRMレコードやチケット)を更新するときは次が必要:
- 主要エンティティの単一の信頼できる情報源を使う。\n- 冪等なツール契約を優先:安定IDとアップサート設計で再試行を安全にする。\n- 競合更新があり得る場合は楽観的同時実行制御(バージョン番号やタイムスタンプ)を適用する。
重要な操作では会話ログとは別に決定ログを記録する:何を変えたか、なぜ、どの入力に基づくか。
スナップショットと再開可能な実行
クラッシュ、デプロイ、レート制限を乗り切るためにワークフローは再開可能であるべきです:
- 各重要ステップごとに状態スナップショットを永続化:現在のステップ、入力、ツール結果、保留アクション。\n- ステートマシンの各遷移はスナップショットから再生可能にする。\n- 障害や再起動時には最後のスナップショットをロードして継続する。
これによりタイムトラベルデバッグが可能になり、悪い決定に至った正確な状態を検査・再生できます。
プライバシー、保持、最小限のメモリ
メモリは資産であると同時に負債でもあります。本番エージェントでは:
- 保存してはいけないもの(秘密、未加工ドキュメント、機密PII)を明示的に定義し、必要ならマスキングやハッシュ化を行う。\n- メモリタイプごとに保持ポリシーを定義(セッションレベル、30日、法的保留など)。\n- ユーザーに長期メモリの閲覧・削除コントロールを提供する。\n- フルプロンプトやツール入力を保存する代わりに、小さな構造化サマリを保存する。
メモリは設計・バージョン管理・ガバナンスされたプロダクトの一部として扱い、無秩序なテキストの蓄積にしないでください。
同時実行、レート制限、バックプレッシャのあるエージェントシステム
エージェントはホワイトボード上では順次に見えますが、実負荷では分散システムのように振る舞います。多くの同時ユーザー、ツール、バックグラウンドジョブがあるとレース、重複作業、順序の問題を扱う必要があります。
ワークフローにおける同時実行ハザード
一般的な故障モード:
- レースコンディション:二つの実行が同じチケットやカート、ドキュメントを同時更新して上書きする。\n- 重複作業:再試行やワーカーの誤設定で同じタスクを二重に処理する(例:二重課金)。\n- 順序ずれの影響:ツール呼び出しの完了順が想定外で古い結果が新しい状態を上書きする。
これらは冪等なツール契約、明示的ワークフロー状態、データ層での楽観的/悲観的ロックで緩和します。
キューと同期フローの選択
同期のリクエスト–レスポンスは単純だが脆い:依存先が全て稼働して高速である必要がある。エージェントが多くのツールや並列サブタスクに扇状展開するなら、長時間実行や副作用をキュー化します。
キューベースのオーケストレーションの利点:
- ワーカー・プールで同時実行を制御できる\n- リトライと重複除去を集中管理できる\n- 遅い/フラッキーなツールをユーザー向けレイテンシから隔離できる
レート制限とバックプレッシャ
エージェントは通常三種類の制限に直面します:
- モデル:tpmやrpm、コンテキストサイズの制約
- ツール:内部サービスのQPSやCPU制約
- 上流API:サードパーティのクォータやハードキャップ
明示的なレート制限層を用意し、ユーザー/テナント/グローバル単位で制御します。トークンバケットやリーキーバケットを使い、RATE_LIMIT_SOFTやRATE_LIMIT_HARDのような明確なエラーを返してエージェントが優雅にバックオフできるようにします。
バックプレッシャはシステムが負荷時に自らを守る方法です。戦略として:
- 重要度の低いトラフィックを優先的に切る\n- 機能劣化(小さいコンテキスト、少ないツール呼び出し)\n- 低優先キューを一時停止しつつ重要フローを維持する
キュー深度、ワーカー利用率、モデル/ツールのエラー率とレイテンシパーセンタイルを監視します。キュー増大と429/503/遅延の上昇はエージェントが環境を超えている初期信号です。
エージェント挙動の可観測性:トレース、メトリクス、ログ
任意のタスクについて「何をしたか?」と「なぜそうしたか?」を素早く答えられなければ、信頼できるエージェントは作れません。エージェント可観測性はその答えを安価かつ正確にすることです。
見るべきもの
各タスクに対し、次を通すトレースを設計します:
- すべてのエージェントステップと状態遷移\n- すべてのツール呼び出しと応答\n- すべてのモデル呼び出しとプロンプトバリアント
トレース内に重要決定(ルーティング、プラン修正、ガードレール発動)の構造化ログと、ボリュームと健全性のためのメトリクスを付与します。
有用なトレースには通常次が含まれます:
- タスクメタデータ:テナント、ユーザー、チャネル、優先度\n- エージェント状態:現在の状態名、次の状態、リトライ回数\n- ツールI/O:入力、出力、遅延、エラー、サーキットブレーカー状態\n- モデル呼び出し:プロンプトテンプレートID、モデル名、トークン数、遅延
ロギングとマスキング
プロンプト、ツール入力、出力は構造化形式でログに残しますが、マスキング層を通してください:
- PIIや秘密をマスクする\n- 大きすぎるペイロードはトランケートし相関用のハッシュを添える\n- 感度レベルを付けてアクセスと保持を制御する
本番はデフォルトでマスク済みにし、低環境でのみ未加工を許可する機能フラグを用いるとよいです。
実務的に重要なメトリクス
最低限追跡すべきは:
- タスクの成功/失敗率(エージェント・ユースケース別)\n- タスクあたりの平均/P95ステップ数\n- レイテンシ:エンドツーエンドとツール/モデル別\n- 成果あたりコスト(トークン、ツール費用)と成功あたりコスト
インシデント時、良いトレースとメトリクスがあれば「エージェントがフレークだ」ではなく「P95タスクがToolSelectionで2回のリトライ後に失敗、原因はbilling_serviceの新しいスキーマ」といった具体的な声明が出せます。
エージェントシステムのテストと評価戦略
エージェントのテストは、彼らが呼ぶツールと、それらを繋ぐフローの両方をテストすることです。分散システムのテストとして扱い、単なるプロンプト調整に留めないでください。
単体テスト:プロンプトではなくツール契約をテストする
まずツール境界の単体テストから始めます:
- スキーマを検証:必須フィールド、列挙、範囲、不変量。\n- 冪等性とエラー意味論のチェック(どのエラーが返るか、どのエラーが再試行可能か)。\n- 不正入力に対してツールが優雅に扱い構造化エラーを返すことをアサートする。
これらのテストはLLMに依存せず、合成入力でツールを直接呼んで期待される出力やエラー契約を検証します。
統合テスト:フローとマルチステップ挙動
統合テストはエージェントワークフロー(LLM + ツール + オーケストレーション)をエンドツーエンドで検証します。
シナリオベースのテストを設計します:
- 主要ユーザージャーニーのハッピーパス(予約、返金、エスカレーションなど)。\n- エッジケース:欠損データ、部分的なツール失敗、タイムアウト、レート制限。\n- 複数ツールの相互作用:ツールAの出力がツールBの入力になるパス。
これらのテストは状態遷移とツール呼び出しを検証します。LLMのすべてのトークンを検証するのではなく、どのツールがどの引数で呼ばれたか、どの順序か、最終状態が何かをチェックします。
LLMとツールの決定的フィクスチャ
テストを再現可能に保つため、LLMレスポンスとツール出力をフィクスチャ化します:
- LLM応答を一度記録し(プロンプト+モデル+設定ごと)、JSONフィクスチャとして保存する。\n- ツールの背後の外部システムをモックしてテストが実際のサービスを叩かないようにする。\n- テストでは明示的シードと固定温度設定を使う。
典型パターン:
with mocked_llm(fixtures_dir="fixtures/llm"), mocked_tools():
result = run_agent_scenario(input_case)
assert result.state == "COMPLETED"
(コードブロックはそのまま保持しています)
プロンプトとスキーマの回帰スイート
プロンプトやスキーマの変更は回帰ランを必須にします:
- 入力コーパスと期待される状態、ツールトレース、分類をキュレートして保存する。\n- これらをゴールデンファイルとしてロックし、差分が行動の変化を示す。\n- 重大なフローのドリフトは明示的に承認またはロールバックする。
スキーマ進化(フィールド追加や型の厳格化)は専用の回帰ケースでカバーして、古い契約を前提にしているエージェントやツールを壊さないようにする。
本番投入前のオフライン評価
新しいモデルやポリシー、ルーティング戦略を本番に直接投入してはいけません。
代わりに:
- 回帰コーパスを新設定でオフラインで再実行する。\n- サンプリングした過去のインタラクションでリプレイテストを行う。\n- 自動メトリクス(タスク成功率、ツールエラー率、レイテンシ、コスト)と、必要に応じてサンプルの人間評価を算出する。
オフラインゲートを通過した後にのみ、新バリアントを段階的に本番へロールアウトします(機能フラグ経由が望ましい)。
テストデータ管理と匿名化
エージェントログには機密データが含まれます。テストはこれを尊重しなければなりません:
- テストデータセットは匿名化または合成入力で作る。\n- 識別子、自由文のPII、秘密はログやフィクスチャに保存する前に削除またはハッシュ化する。\n- アクセスを区分し、エンジニアは挙動トレースを見られても生のユーザー秘密にはアクセスできないようにする。
これらをCIパイプラインに組み込み、匿名化チェックなしにテストアーティファクトが生成・保存されないようにします。
本番での運用、監視、進化
エージェントの運用は静的なモデルのデプロイではなく、分散システムの運用に近いです。ロールアウト制御、信頼性目標、厳格な変更管理が必要です。
安全なロールアウト戦略
新しいエージェントや振る舞いは段階的に導入します:
- シャドウモード:既存システムと並行してエージェントを走らせ決定をログに残すがユーザーには影響させない。オフラインで出力を比較する。\n- カナリア:トラフィックの小さな割合(例:1–5%)に新バージョンを曝露し、エラー率や品質を監視してから拡張する。\n- A/Bテスト:ユーザ向けフローではビジネスKPIで比較する(単なるモデル指標ではない)。
これらはすべて機能フラグと設定駆動のポリシーで裏付ける:ルーティングルール、使用ツール、温度、セーフティ設定はコードではなく設定で切り替えられ、即時に元に戻せるようにします。
SLOとインシデントワークフロー
システム健全性とユーザ価値を反映したSLOを定義します:
- 信頼性:タスク成功率、ツール呼び出し成功率、エンドツーエンド成功率。\n- レイテンシ:重要経路のp50/p95。\n- 品質:自動評価スコア、人間評価の分布、タスク固有の成功指標。
これらをアラートに結び付け、他の本番サービスと同様に所有者、ランブック、標準的な緩和手順(フラグのロールバック、トラフィックドレイン、安全モード)を用意します。
継続的改善と変更管理
ログ、トレース、会話の記録を使ってプロンプトやツール、ポリシーを洗練します。各変更はバージョン化されたアーティファクトとしてレビュー、承認、ロールバック可能に扱います。
プロンプトやツールの変更を黙って行うのは避けてください。変更管理がなければ回帰と特定の編集の関連付けができず、インシデント対応は推測合戦になります。
信頼できるエージェントシステムの参照アーキテクチャ
本番対応のエージェントシステムは関心の分離が明確だと恩恵があります。目標は「意思決定は賢く、インフラでは愚かに保つ」ことです。
コアコンポーネント
1. ゲートウェイ / APIエッジ
クライアント(アプリ、サービス、UI)の単一入口。次を担います:
- 認証と認可(ユーザー、サービス、テナント)\n- レート制限とクォータ\n- リクエスト整形(スキーマ、サイズ上限、基本検証)
2. オーケストレータ
オーケストレータは「脳幹」であって脳ではありません。次を調整します:
- プランナー:ユーザー意図をワークフロー/ステートマシンに翻訳する\n- ステートオーケストレータ:ワークフローを実行し、状態を追跡し、リトライやタイムアウトを管理する\n- ポリシーエンジン:安全性、コンプライアンス、許可ツール、PIIルール、コスト予算を強制する
LLMはオーケストレータの背後にあり、プランナーや言語理解を要する特定ツールから呼ばれます。
3. ツール&ストレージ層
ビジネスロジックは既存のマイクロサービス、キュー、データシステムに残します。ツールは薄いラッパーとして次を扱います:
- 内部HTTP/gRPCサービス\n- データベース、ベクターストア、キャッシュ\n- 外部API
オーケストレータは厳格な契約経由でツールを呼び出し、ストレージが真の情報源であり続けます。
統合、コントロール、テレメトリ
ゲートウェイで認可とクォータを強制し、オーケストレータで安全性、データアクセス、ポリシーを強制します。すべての呼び出し(LLMとツール)は構造化テレメトリを発行し、次にフィードします:
- ステップごとの振る舞いのトレース\n- SLOとレート制限のためのメトリクス\n- セキュリティとコンプライアンスのための監査ログ\n- ユーザー/プロジェクト/ツール別のコスト勘定
シンプルなアーキテクチャ(ゲートウェイ→単一オーケストレータ→ツール)は運用しやすいです。プランナー、ポリシーエンジン、モデルゲートウェイを分離すると柔軟性は増しますが、調整とレイテンシ、運用の複雑度も増します。
まとめと次のステップ
明示的なステートマシン、明確なツール契約、規律あるリトライ、深い可観測性というコア要素が揃えば、現実負荷下で予測可能に振る舞うエージェントが作れます。最後はこれらをチーム内で繰り返し実践することです。
コアパターンを一枚で表すと
各エージェントをステートフルなワークフローとして考えます:
- ステートマシンが合法なステップ(plan→gather→act→summarizeなど)と遷移を定義する。\n- ツール契約が各アクションの能力を厳格スキーマ、タイムアウト、エラー面で定義する。\n- リトライと冪等性が外部との全相互作用を保護し、リプレイが安全になる。\n- 可観測性(トレース、メトリクス、ログ)がすべての決定とツール呼び出しを説明可能にする。
これらが揃うと、システムはエッジケースで優雅に劣化し、崩壊しなくなります。
本番化チェックリスト(軽量)
プロトタイプを本番ユーザーに出す前に確認してください:
- ワークフロー:状態と遷移が明示的で、隠れたループや無限のツール連鎖がない。\n- 契約:全ツールに型付き入出力、明確な失敗モード、タイムアウトがある。\n- 安全性:入力/出力/アクションにガードレール(レート制限、許可リスト、クォータ)。\n- リトライ:ツールごとにポリシー定義、全副作用呼び出しに冪等キーが存在する。\n- 状態:メモリと永続状態はスコープ化、バージョン管理、復旧可能である。\n- 可観測性:任意のユーザーセッションを単一トレースで説明できる。\n- テスト:シナリオベースのテストとプロンプト/ツール/ポリシーの回帰スイートを持つ。
どれかが欠けていれば、まだプロトタイプ段階です。
チームの責任分割
持続可能なセットアップは通常次の分担になります:
- プロダクトチーム:エージェント行動、プロンプト、ドメイン固有ツール、評価データセットを所有。\n- プラットフォーム/インフラチーム:ステートマシン基盤、共通ツールSDK、ログ・トレース、ポリシー適用、共通評価基盤を所有。
これによりプロダクトは速く動け、プラットフォームは信頼性・セキュリティ・コスト管理を強制できます。
将来拡張と安全な反復
基盤が安定したら次を検討できます:
- 学習ベースのポリシー:ログ化されたトレースを利用してルーティングやツール選択を改善する。\n- 強化学習:短期応答ではなくタスク完了や収益など長期の成果を最適化する。\n- 自己調整ワークフロー:観測されたパフォーマンスに基づき温度やツール、サブフローを自動調整する。
ここでの進展は段階的に行い、機能フラグの裏でオフライン評価と強力なガードレールを用いて導入してください。
テーマは一貫しています:失敗を設計し、巧妙さより明快さを優先し、観測と巻き戻しが容易な範囲で反復すること。これらの制約を守れば、エージェントシステムは怖いプロトタイプではなく組織が依存できるインフラになります。
よくある質問
エージェントシステムとは何ですか?通常のLLMアプリと何が違いますか?
エージェントシステムは、LLMが単一のプロンプトに答えるだけでなく、次に何をするかを決定するアプリケーションです:どのツールを呼び出すか、どのデータを取得するか、ワークフローのどのステップを実行するか、そしていつ終了するかを判断します。
単なるチャット補完と違い、エージェントシステムは次を組み合わせます:
- 決定ポリシー(LLM + プロンプト)
- 進捗を追うワークフローやステートマシン
- 一連のツール(API、データベース、サービス)
- 再試行、状態永続化、ログ、可観測性のためのインフラ
本番では、LLMは全システムではなく、明確で決定論的な殻の中にある一つの意思決定コンポーネントになります。
デモでうまく見えるエージェントが本番で失敗するのはなぜですか?
デモは通常、ひとつのハッピーパスで動きます:単一ユーザー、理想的なツール動作、タイムアウト無し、スキーマ差分無し、短い会話。実運用ではエージェントは以下に直面します:
- 不安定なツール:タイムアウト、5xxエラー、応答フォーマットの変化
- 同時実行:共有リソースやレート制限を巡る競合
- 長時間セッション:コンテキスト膨張、メモリの混乱、状態のズレ
- モデル誤りの連鎖:小さなミスが複数のツール呼び出しで拡大
ワークフローや契約、障害処理が明確でなければ、これらはループ、停止、部分的な作業、沈黙するエラーを引き起こし、デモ環境では表面化しません。
エージェントを予測可能でデバッグしやすくするにはどうすればいいですか?
LLMを自由なループの中ではなく、明確な構造の中で動かすことです:
- 有限の状態と許可された遷移を持つステートマシンとしてモデル化する。\n- LLMは主に局所的な選択(次に呼ぶツール、パラメータの埋め方)に使い、任意の手順を発明させない。\n- 遷移を外部に永続化して、すべてのステップがリプレイ可能かつ監査可能にする。\n- エージェントを小さく集中させる:一つの主要な仕事と成功指標。
こうすれば、挙動を段階的に説明・テスト・デバッグでき、不透明な“エージェントの思考”ループを追いかける必要がなくなります。
エージェントをステートマシンとしてモデル化するとはどういう意味ですか?
エージェントをwhile not done: call LLMのようなループではなく、名前付きの状態と型付きイベントを持つワークフローとして設計します。
典型的な状態例:
PLAN– リクエストを解釈しステップに分解するCALL_TOOL– 特定のツール呼び出し(またはバッチ)を実行するVERIFY– 出力を簡単な不変条件や二次チェックで検証するRECOVER– リトライ、フォールバック、エスカレーションでエラーを処理するDONE/FAILED– 終端状態
イベント(例:ToolCallSucceeded, TimeoutExceeded)と現在の状態が次の状態を決めます。これによりリトライやタイムアウトの方針が明示化され、プロンプトや寄せ集めのコードに散らばることがなくなります。
エージェント向けのツール契約はどう設計すべきですか?
ツールをプロンプトの中の散文ではなく、プロダクションAPIのように設計します。各ツールが備えるべき要素:
- 入力スキーマ:必須フィールド、型、列挙値、制約、デフォルト
- 出力スキーマ:成功ペイロード、nullableフィールド、”結果なし”の意味
- エラーモデル:
InvalidInput,NotFound,RateLimited,TransientFailureのような型付きエラー - 運用期待値:レイテンシ目標、可用性ターゲット、レート制限
呼び出す前に入力を検証し、呼び出し後にも出力を検証します。ツール契約にバージョンを付け、エージェントは特定バージョンにピン留めすることでスキーマの変化で壊れないようにします。
エージェントワークフローで障害、リトライ、冪等性はどう扱うべきですか?
外部呼び出しはいつか失敗すると想定し、それを安価で安全にする設計をします。
主要なパターン:
- 冪等性:副作用のあるツール呼び出しは安定した
request_idやビジネスキーを受け取り、同じIDなら同一の結果を返す。\n- ターゲットを絞ったリトライ:タイムアウトや5xx、レート制限などの一時的エラーのみを指数バックオフ+ジッターでリトライし、最大試行回数を厳格にする。\n- サーキットブレーカー:繰り返し失敗するツールへの呼び出しを一時停止してフォールバックに切り替える。\n- 構造化された失敗面:エージェントがリトライ、再計画、ユーザーへの問い合わせのいずれをするか判断できるように明示的なエラー型を返す。
これにより信頼性を保ちつつ、無限ループや重複副作用、コスト暴走を防げます。
エージェントのメモリと状態はどう管理すべきですか?
短期状態と長期メモリを分離し、LLM自体はステートレスとして扱います。
- 短期状態:現在のタスク完了に必要な情報(目標、ステップ、ツール出力、リトライ残回数など)。これはタスク完了後に破棄可能。\n- 長期メモリ:ユーザープロファイル、好み、プロジェクト履歴など、セッションをまたいで保持すべき情報は外部ストアに構造化して保存。\n LLMは明示的な状態オブジェクトに対する純粋関数として扱い、必要な状態だけを読み込んでプロンプトを生成し、更新した状態を永続化します。生ログや会話履歴をそのままメモリにするのは避け、コンパクトで構造化されたレコードを保持してください。
エージェントシステムの同時実行、レート制限、バックプレッシャにどう対処すべきですか?
多くのユーザやバックグラウンドジョブがあると、ワークフローは分散システムとして振る舞います。
対策:
- キューを使う:長時間実行や副作用のあるステップはキューに移し、ワーカープールで並行度を制御する。\n- レート制限とバックプレッシャ:モデル、内部ツール、外部APIに対してユーザー単位・テナント単位・グローバルのスロットリングを設ける。トークンバケットやリーキーバケットを利用し、
429/503やキュー深度の上昇を監視して機能劣化やトラフィック遮断を行う。\n- 冪等性とロック:重複作業や競合を避けるため、冪等なツール契約と楽観的/悲観的ロックを組み合わせる。
キュー深度、ワーカー利用率、レイテンシパーセンタイル、エラー率を監視して過負荷を早期に検知します。
本番で安全にエージェントを運用するためにどんな可観測性が必要ですか?
タスクについて「何をしたか」と「なぜそうしたか」を素早く答えられるようにします。
必要な可観測性:
- トレース:各タスクに対してステート遷移、ツール呼び出し、モデル呼び出しを通したエンドツーエンドのトレース。\n- 構造化ログ:ツール選択、プラン修正、ガードレール発動など重要な決定を相関ID付きで記録。\n- メトリクス:タスク成功率、ステート別失敗率、レイテンシ(全体およびツール/モデル別)、1成果あたりのコスト。\n- マスキングと保持制御:プロンプトやツールI/Oをログに残す前にPIIや秘密情報をマスクし、感度に応じた保持方針を適用する。
これがあれば「エージェントが不安定だ」と感じる段階から、どの状態・ツール・変更が原因かを特定できるようになります。
エージェントシステムを時間とともに安全にロールアウト・運用するには?
エージェントは静的なプロンプトではなく進化するサービスとして扱い、他の本番サービスと同じ運用規律を適用します。
良い運用プラクティス:
- シャドウモード、カナリア、機能フラグで段階的にデプロイする。\n- 信頼性、レイテンシ、品質に関するSLOを定義し、それに基づくアラートとランブックを整備する。\n- プロンプト、ツール、ポリシーの変更には回帰テストとオフライン再生を必須にする。\n- 責任分離:プロダクトチームがドメイン固有の行動・ツールを管理し、プラットフォームチームがステートマシン基盤、共通SDK、ログ・トレース・ポリシーを管理する。
こうして継続的に改善しつつ、障害を抑え、診断可能で巻き戻し可能な変更を行えます。