誰もがプロンプトに執着している。挨拶を微調整し、トーンをいじり、モデルが十分に温かみのある響きになっているかどうかを心配する。だが、それは本質から逸れた、気を散らすものに過ぎない。AIエージェントが実際のユーザーに本物のメールを送り始める時、真の危険は「Cheers」と書くべきところを「Best regards」と書いてしまうことではない。危険なのは、エージェントの決定からメッセージが受信トレイに届くまでの間に何が起きたのかを、確信を持って説明できないことだ。私はまず、境界(バウンダリ)を見る。そここそが、本番システムが静かに崩壊していく場所だからだ。

コントラクトこそが弱点である

AIのデモは寛容だ。ブラウザウィンドウ内でのスムーズな会話は、その裏にある想定の乱れを隠してしまう。しかし本番環境において、真の脆弱性は3つの要素間のコントラクト(契約)に潜んでいる。すなわち、「エージェントの決定」、「アクションを実行するツール」、そして「結果を検証するステップ」だ。もしこの境界が曖昧であれば、システムは完璧に動作しているように見えて、ある瞬間突然機能しなくなる。そして、沈黙のうちに失敗し、顧客セグメント全体に重複メールを送信したり、なぜ送られたのか明確な記録を残さずに、不適切なタイミングでメッセージを放り出したりするのだ。プロンプトが詩のように美しく書かれていたとしても、その下のアーキテクチャがいまだに紐で繋ぎ止められているような状態であれば、問題は起こる。

エージェントに自由に書かせるのをやめなさい

最も多い間違いは、エージェントに白紙を渡してしまうことだ。チームはエージェントにメールの内容を生のテキストで記述させ、その後に続くツールがその散文から意図を解析してくれることを期待してしまう。これは脆弱な設計だ。LLMは妥当な意図を提案するかもしれないが、インフラに必要なのは創造性ではない。コントラクトが必要なのだ。機械が曖昧さなく検証できる、特定のフィールドが必要なのだ。

エージェントがメールリクエストを発行する際、その出力は、システム(配管)が要求するものを正確に保持していなければならない:

  • Template version(テンプレートのバージョン): ユーザーがどのバージョンのメール本文を見たのかを把握するためのもの。
  • Recipient scope(受信者の範囲): 「たった今登録したユーザー」といった自然言語ではなく、ユーザーIDやセグメントルールによって定義されたもの。
  • Trace ID(トレースID): エージェントからエグゼキューター、メールプロバイダー、そしてログに至るまで、このリクエストを追跡するためのユニークな識別子。
  • Time window(有効時間枠): この送信がいつまで有効か。これにより、数時間前の古いエージェントの決定が、真夜中にメールをトリガーしてしまう事態を防ぐ。
  • Idempotency(べき等性): エージェントがリトライしたりネットワークが瞬断したりしても、同じ論理的な送信が二度実行されないようにするためのキー。

生のテキストは、APIとしては最悪だ。緊急性、対象、アクションに関する曖昧さを残してしまう。特定のフィールドであれば、機械が読み取り可能であり、監査可能で、テスト可能になる。それらは、漠然とした指示を、検証可能なコマンドへと変えるのだ。

文章ではなく、アクションを

エージェントに自由度の高い執筆タスクを任せるのではなく、許可されたアクションのメニューに制限するのだ。固定されたEnum(列挙型)を持つ内部APIのように考える。エージェントは件名を起草したり、挨拶に悩んだりすることはない。send_review_requestsend_retry_notice といったアクションを選択する。それが、エージェントに与えられる創造的な自由の限界である。

次に、決定論的なエグゼキューター(実行器)がそのアクションキーを受け取り、バージョン管理から正しいテンプレートを呼び出し、サニタイズされたデータを流し込み、検証済みのソースから受信者リストを補完し、最終的なコマンドを構築する。エージェントは「何が起きるべきか」を決定する。退屈で予測可能なコードが、「どのように起きるか」を決定するのだ。

この分離により、システムはテストが容易になる。LLMの推論を一切実行することなく、特定の入力状態が確実に send_retry_notice をトリガーするかどうかを検証できる。ユニットテストは、モデルのTemperature(温度)ではなくマッピングロジックをチェックするため、高速かつ決定論的になる。統合テストは、モデルの機嫌が良かったかどうかではなく、エグゼキューターがアクションをメールサービスに正しくマッピングできているかどうかに集中できる。

5つのレイヤーで構築する

堅牢なシステムは、単一のプロンプトから生まれるものではない。それはレイヤー(階層)によって構築され、各レイヤーが単一の明確な責任を負う。

1. バックエンドがイベントを安全なデータに集約する。
トリガーがWebhookであれ、データベースの変更であれ、あるいはスケジュールされたジョブであれ、このレイヤーは入力をサニタイズし、予期しないフィールドを削ぎ落とし、エージェントが必要なものだけを渡す。Webhookのペイロードに20個のフィールドが含まれていても、エージェントに必要なのが2個だけなら、その2個だけを渡す。チェックされていない生のユーザーテキストが、決定レイヤーに到達してはならない。

2. エージェントが固定されたスキーマからアクションを選択する。
エージェントはコンテキストを確認し、判断を下し、あらかじめ定義されたアクションキーの一つと、必要なメタデータを一緒に出力する。文章を起草することはない。受信者を推測することもない。次のレイヤーがJSONスキーマに照らして検証できる、構造化されたペイロードを返すのである。

3. ツールは権限と必須フィールドを検証します。
このエージェントコンテキストには、このユーザーに対して send_review_request を実行する権限がありますか? 受信者のスコープは空ではなく、許可された制限内ですか? べき等性キー(idempotency key)は存在し、ログ内で一意ですか? トレースIDは正しく構成されていますか? メールサービスに一切触れる前に、ここで明確にエラーを出して停止してください。

4. メールサービスは、トレースIDとともに送信をログに記録します。
システムから送信されるすべてのメッセージは、プロバイダーのAPIを経由してオブザーバビリティ(観測性)スタックに届くまで、そのトレース識別子を保持している必要があります。ユーザーから「メールが2通届いた」という苦情があった場合、1つのIDをクエリするだけで、重複の発生源(エージェントの再試行、不安定なエグゼキューター、あるいは誤作動したコールバックなど)を正確に特定できる必要があります。

5. E2E(エンドツーエンド)テストは、実際の受信トレイの内容と効果を確認します。
レンダリングされたメッセージを実際のメールボックスで開きます。件名は正しく入力されていますか? 配信停止リンクは正しく機能しますか? 主要なコールトゥアクション(CTA)ボタンをクリックした際、正しいユーザー状態で正しいページに遷移しますか? 単体テストに合格したということは、コードが実行されたことを意味するに過ぎません。メールが人間にとって実際に機能するかどうかは、受信トレイのテストでしか分かりません。

推測ではなく証拠を

このパイプラインでテストが失敗したとき、あなたには4つの具体的な証拠が必要です。それ以外は認められません。

  1. エージェントによる元の決定。 どの操作を選択し、入力コンテキストの全体像はどうなっていましたか?
  2. ツールによる正規化されたコマンド。 テンプレート、ハイドレーションロジック、および検証ルールを適用した後、決定論的なエグゼキューターは何を構築しましたか?
  3. 隔離された受信トレイ内のメッセージ。 送信したはずの内容のログではなく、専用のテスト用メールボックスにキャプチャされた、ヘッダーを含む実際のMIMEメッセージです。
  4. リンククリック後の最終的な効果。 メールが目的を果たしたことを証明する、結果としてのページの状態、データベースの変更、または外部イベントです。

もし証拠が一つでも欠けていれば、チームはその空白を推測で埋めようとします。彼らは推測することになります。自動化において推測を行うことはコストがかかります。それは時間を浪費させ、信頼を損ない、あらゆるインシデントを、...ではなく、法医学的なミステリーに変えてしまいます。