OpenAI Agents SDK のエージェントを WhatsApp で動かす
電話番号の Webhook の送信先を、Python アプリ内のルートに設定します。メッセージを受信するたびに、Runner.run と送信者の番号をキーにしたセッションでエージェントを実行し、result.final_output を、その番号のトークンと phone number id を使って Cloud API から返信します。同じ番号で WhatsApp Business アプリも引き続き使えます。
SDK 組み込みのセッション(ファイル保存またはインメモリ)が、セッション ID ごとに過去のやり取りを記憶します。
MCPServerStreamableHttp では、サーバー認証に httpx.Auth か Authorization ヘッダーを渡せます。
Meta の最新のメッセージ送信例で使われている Graph API のバージョンです。
WhatsApp のメッセージはどのようにエージェントの実行になりますか?
Meta が呼び出す、自社アプリ内のルートを経由します。電話番号の Webhook の送信先にそのルートを設定し、パスには長くランダムなシークレットを含めてください。ルートは、Meta の検証用 GET に対して hub.challenge をプレーンテキストで返し、各 POST を受け付けてすぐに 200 を返したうえで、処理をキューに入れます。Meta は応答が遅いとリトライするため、先にエージェントを実行するとエージェントが二重に動いてしまうからです。
その後、ワーカーがペイロードから送信者とテキストを読み取り、Runner.run を呼び出します。このとき、送信者の番号をセッション ID としたセッションを渡します。SDK がその ID で会話を保存するため、同じ顧客の次のメッセージは前回の続きから始まり、別の顧客と履歴が混ざることはありません。最初は SQLiteSession で十分です。複数のワーカーで動かす場合は、SDK に含まれる Redis や SQLAlchemy のセッションを使えます。
ここでは URL 内のシークレットが認証の役割を果たします。Meta は Tech Provider のアプリシークレットで配信に署名するため、顧客側では署名ヘッダーを検証できないからです。
import os, httpx
from agents import Agent, Runner, SQLiteSession
agent = Agent(name="Front desk", instructions="Answer briefly and politely.")
URL = f"https://graph.facebook.com/v25.0/{os.environ['PHONE_NUMBER_ID']}/messages"
async def handle(sender: str, text: str) -> None:
session = SQLiteSession(sender, "whatsapp.db")
result = await Runner.run(agent, text, session=session)
async with httpx.AsyncClient() as http:
await http.post(
URL,
headers={"Authorization": f"Bearer {os.environ['WA_TOKEN']}"},
json={"messaging_product": "whatsapp", "to": sender,
"type": "text", "text": {"body": result.final_output}},
)返信はエージェントではなく、なぜ自社のコードが送るべきですか?
絶対に破ってはいけない唯一のルールは時間に関するルールであり、プロンプトよりもコードのほうが確実に守れるからです。顧客がメッセージを送ると、Meta は 24 時間のカスタマーサービスウィンドウを開きます。この間はフリーテキストを送れますが、ウィンドウ外では承認済みのメッセージテンプレートしか送れず、フリーテキストはエラー 131047 で返されます。
届いたばかりのメッセージに答えるワーカーは常にウィンドウ内にいるため、result.final_output をテキストとして送る方法は構造上安全です。翌朝のフォローアップやリマインダーなど、後から行う処理は別の経路に分け、フリーテキストではなく必ずメッセージテンプレートを送ってください。
エージェントにツールを持たせることもできます。注文や予約の照会などにはファンクションツールを渡し、WhatsApp への送信はワーカー側に残してください。そうすれば、モデルが任意の引数で呼び出せるものの中に、アクセストークンが入りません。
エージェントから EasyCoexistence の MCP サーバーを使えますか?
使えます。MCPServerStreamableHttp を通じて接続できますが、ヘッダーを足すだけより設定は多くなります。SDK は params["auth"] に Authorization ヘッダーまたは httpx.Auth を受け付けます。EasyCoexistence の MCP サーバーは、ブラウザーでのサインイン後に動的クライアント登録と PKCE を使って独自の OAuth トークンを発行するため、固定のキーはありません。MCP Python SDK には、PKCE とトークン更新を含むそのフローを実行する HTTP 認証ハンドラーの OAuthClientProvider があり、これを params["auth"] に渡します。
この方法が向くのは、エージェント自身に判断させたい場合です。たとえば、番号の接続状態を確認する、メッセージテンプレートの状態を読む、ツールの説明にあるルールに従って send_message と send_template を選ぶ、といった用途です。応答するだけの窓口であれば、前述のワーカーのほうが短く済み、問題も起きにくくなります。
どちらの方法でも Webhook は必要です。MCP はリクエストとレスポンスの仕組みであり、顧客のメッセージがエージェントに届く経路は、Meta が呼び出すルートだけだからです。
よくある間違い
- Meta に 200 を返す前にエージェントを実行する。遅い応答はリトライされ、顧客に回答が 2 回届きます。
- 全顧客で 1 つのセッションを使う。送信者の番号をキーにしないと、履歴が混ざります。
- 遅れて送るフォローアップでフリーテキストを使う。24 時間を過ぎると 131047 で失敗するため、メッセージテンプレートを送ってください。
easycoexistence.com で番号を接続し、Webhook の送信先を自社のルートに設定したら、ダッシュボードでトークンと phone number id を確認します。料金は番号 1 件あたり月額 US$ 9 から、台数が増えると US$ 1 まで下がり、最初の 3 日間は無料です。
よくある質問
OpenAI Responses API のホスト型 MCP ツールは必要ですか?
いいえ。ワーカーが Runner.run でエージェントをローカル実行し、WhatsApp 側は自社のコードが行う通常の HTTPS 呼び出しです。
どの Graph API バージョンを使えばよいですか?
Meta の最新の例では v25.0 が使われています。バージョンはコード内の 1 か所で固定しておくと、アップグレードが 1 行の変更で済みます。
電話から人が対応を引き継げますか?
はい。Coexistence により WhatsApp Business アプリが引き続き使え、電話からの返信は echo イベントとしてルートに届くため、ワーカーはそれを使ってエージェントを一時停止できます。
EasyCoexistence は会話を保存しますか?
いいえ。Meta がルートへ直接配信します。セッションが保存する内容は、すべて自社のデータベースにあります。
続きを読む
始める準備はできましたか?
WhatsApp Coexistence の設定は数分で完了し、数か月もかかりません。スマートフォン上のアプリも引き続き使えます。
無料トライアルを始める確認日