Graph通話ボット
Graph通話ボット
Microsoft Teams内で、同僚のように名前でElevenLabsエージェントを呼び出したりチャットしたりできます。
概要
この方式では、エージェントを呼び出し可能なTeams IDとして利用します。ユーザーは名前で検索して1対1で通話でき、エージェントはリアルタイムに応答します。電話番号、PSTN、Communications Creditsは不要です。名前で呼び出せる唯一の方式であり、運用には最も手間がかかります。
Microsoft Graphリアルタイムメディアボット(Cloud Communications通話プラットフォーム)を使用します。メディアSDK(Microsoft.Skype.Bots.Media)はWindows Server上の.NET専用であり、Teams通話で生のオーディオを扱うLinuxまたは.NET以外の方法はありません。
仕組み
ボットはアプリケーションホスト型メディアで応答し、毎秒50オーディオフレーム(20 ms PCM 16 kHz)を受信します。これをWebSocket経由でElevenLabsエージェントにブリッジし、エージェントのオーディオを通話にストリーミングで返します。
要件
- Azure Bot登録とアプリ(Entraアプリ登録)。
- 管理者の同意を得たGraphアプリケーション権限:
Calls.AccessMedia.All(生のメディア)とCalls.Initiate.All。 - パブリックIPと開放されたメディアポートを備えたWindows Server VM(物理コア2つ以上、例:
Standard_D4s_v3)。 - メディア/シグナリングエンドポイント用のパブリックFQDNに設定されたCA署名TLS証明書(メディアプラットフォームは自己署名証明書を拒否します)。
- 両方向ともPCM 16000 Hzに設定したElevenLabsエージェント:VoiceタブでTTS出力形式、Advancedタブでユーザー入力オーディオ形式を設定します。
D2s_v3(2 vCPU=物理コア1つ)では、MediaPlatform needs a system with at least 2 coresというエラーで失敗します。物理コアが2つ以上のサイズ(例:D4s_v3)を使用してください。
権限とロール
ステップ1 — ボットとGraph権限を登録する
アプリ登録とそれに紐付けたAzure Botを作成し、通話権限を付与して同意します(同意にはGlobal AdminまたはPrivileged Role Adminが必要です):
2つのGraphアプリケーションロールを付与し、管理者の同意を取得します(Global AdminまたはPrivileged Role Adminが必要)。その後、割り当てが反映されたことを確認してください:
admin-consentがConsent validation failedを返す場合は、代わりにサービスプリンシパルでアプリロールを直接付与してください:
ポータルで、Entra管理センターのアプリ登録→対象のアプリ→APIのアクセス許可を確認してください。両方の権限が、緑色のチェックマーク付きで許可済みと表示されるはずです。

ステップ2 — Windows VM、証明書、ポートを準備する
VM上で以下を実行します(メディアプラットフォームのネイティブコードに必要ですが、Windows Serverにはデフォルトで含まれていません):
同じポートをWindowsファイアウォールでも開放し、証明書の拇印を控えておいてください。ボットはKestrel(443と通知用ポート)およびメディアプラットフォーム(8445)をこの証明書にバインドします。
VM自体の*.cloudapp.azure.com FQDNでLet’s Encrypt証明書を使用できます。別途ドメインを用意する必要はありません。
ステップ3 — ボットをビルドして実行する
Microsoft の microsoft-graph-comms-samples の PublicSamples/EchoBot をベースにします。これは net6.0 を対象とし、.NET SDK でビルドできます(Visual Studio Build Tools は不要です)。
appsettings.json の AppSettings セクションに、AadAppId、AadAppSecret、ServiceDnsName/MediaDnsName(VMのFQDN)、CertificateThumbprint、ポート(通話は443、通知は9441、メディアは8445)を設定します。さらに、以下のElevenLabsブリッジ用にElevenLabsAgentIdとElevenLabsOrigin(wss://api.elevenlabs.io、またはデータレジデンシーのホスト)を追加します。再起動後も動作するよう、Windowsのスケジュールされたタスク/サービスとして実行してください。
タスク スケジューラのデフォルトの**実行時間制限(72時間)**は、長時間実行タスクを通知なしで終了します。起動時に開始したボットは3日後に停止し、通話時に「接続できませんでした」と表示されます。制限を無効化し、失敗時の再起動を追加してください。
標準ポート443への通話では、標準のEchoBotがクラッシュします。HttpHelpers.SetAbsoluteUriが
req.Host.Port.Valueを呼び出しますが、Hostヘッダーに明示的なポートがない場合はnullになります。次のように修正してください。
req.Host.Port ?? (req.IsHttps ? 443 : 80)。
エコーをElevenLabsに置き換える
EchoBotのオーディオ接続ポイントは明確です。SpeechService.AppendAudioBuffer(in)とOnSendMediaBufferEventArgs(out)イベントがあります。そのAzure Speech部分を、同じインターフェースを維持するElevenLabsエージェントWebSocketブリッジに置き換えます。
両側ともPCM 16kHzモノラルなので、base64をそのまま渡せます。エージェントはpcm_16000に設定してください。ElevenLabsのinterruption(バージイン)時、ブリッジはFlushMediaを発行します。これをメディアストリームに接続し、キュー済みのAudioMediaBufferをすべて破棄するようにしてください。そうしないと、エージェントが発信者に重なって話し続けます。メッセージの完全なリファレンスはWebSocketドキュメントにあります。通話終了時の切断とウォーム転送については、以下のセクションを参照してください。
Connect()内のURLは公開エージェントに接続します。プライベートエージェントの場合は、サーバー側で短期間有効な
署名付きURLをリクエストしてください。APIキーを使用してGET /v1/convai/conversation/get-signed-url?agent_id=...を呼び出し、
代わりに返されたURLに接続します。データ
レジデンシーでは、ElevenLabsOriginをレジデンシーの
ホスト(wss://api.eu.residency.elevenlabs.io、.in.、または.sg.)に設定します。署名付きURLリクエストには、
対応するhttps://ホストを使用します。
ステップ4 — Teamsから呼び出せるようにする
-
Azure BotのTeamsチャネルでCallingを有効にし、calling webhookを
https://YOUR_FQDN/api/callingに設定します。ポータルでは、Azure Botリソース → Channels → Microsoft Teams → Callingタブにあります。

Azure Bot → Channels — 接続済みのMicrosoft Teamsチャネル 
Microsoft Teamsチャネル → Calling — ボットのwebhookでCallingを有効化 -
bots[0].supportsCalling: trueとボットのアプリIDを含むTeamsアプリマニフェストを作成し、サイドロードします(Apps → Manage your apps → Upload a custom app)。または、UIを使わずに組織全体へ公開できます。New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip(MicrosoftTeams PowerShellモジュール)。
Teamsでアプリ名を検索して通話すると、ボットが応答し、ElevenLabsエージェントが話します。

1対1の名前指定通話には、電話番号やリソースアカウントは不要です。これらが必要なのはPSTN
ダイヤルインのみです。生オーディオブリッジを有効にするのはCalls.AccessMedia.Allです。
テキストチャット(同じボット)
同じAzure BotはTeamsでテキストにも応答できるため、ユーザーはエージェントに通話することもチャットすることもできます。Callingとメッセージングはボット上で独立したチャネルです。calling webhookは音声を処理し、Bot Frameworkのmessaging endpoint(/api/messages)はチャットを処理します。

ボットのmessaging endpointを、提供元のホストに向けます(メディアボットでも別のサービスでも構いません。Windows VMである必要はありません)。
Bot Framework SDKでエンドポイントを実装し、各メッセージを音声と同じ会話WebSocket経由でテキストモードのエージェントに中継します。user_messageイベントを送信し、agent_responseイベントを読み取ります。まず、エージェントのオーバーライド設定でfirst messageフィールドを有効にしてください。以下のコードではこれを空文字でオーバーライドするため、エージェントの挨拶ではなくユーザーのメッセージへの回答が返されます。
標準的な方法で登録します(CloudAdapter、AddTransient<IBot, ChatBot>()によるボットの登録、/api/messagesコントローラー)。また、マニフェストのボットエントリにチャットスコープを追加します。
このスニペットではメッセージごとに新しい会話を開始するため、各ターンは独立しています。チャットの
メモリーを維持するには、Teamsのconversation.idごとに1つのWebSocketを開いたままにし(ターン間で再利用)、
アイドル状態のセッションを終了します。これにより、エージェントはそのチャット内の過去のメッセージを記憶します。first_message
オーバーライドは、エージェントの
オーバーライド設定で有効にする必要があります。許可されていないオーバーライドを送信すると、サーバーは
会話を終了します。有効にできない場合は、オーバーライドを省略し、各セッションの最初のagent_response(挨拶)を破棄して、
次の応答を返してください。
チャットの返信が届かない場合は、エージェントのAdvanced設定で**agent_response**クライアント
イベントを有効にしてください。テキスト応答はこのイベントを通じて配信されます。
通話終了
ElevenLabsが会話を終了したら(End CallツールによってWebSocketが閉じられたら)、Teams側の通話を切断します。
人間へのウォーム転送
エージェントがカスタムtransfer_to_humanクライアントツールを実行すると、ボットはTeamsユーザーを招待して進行中の通話に追加し(相談を伴う追加)、その後退出します。
相談を伴う転送(replacesCallId)では、両者が同じテナントのTeamsユーザーである必要があります。PSTN転送先にはアプリケーションインスタンスが必要です。まず担当者に状況を伝えるには、エージェントからreasonパラメーターを渡し、ブリッジする前に担当者へ再生してください。
トラブルシューティング
MediaPlatform needs a system with at least 2 cores
MediaPlatform needs a system with at least 2 cores
VMの物理コアが1つしかありません。2つ以上の物理コア(例:D4s_v3)にリサイズして再起動してください。
Unable to load DLL 'NativeMedia'
Unable to load DLL 'NativeMedia'
VC++ Redistributable(vcredist140)とServer-Media-Foundation Windows機能をインストールしてから、ボットを再起動してください。
Incoming call returns 500 / call won't connect
443で発生するEchoBotのポートnullバグです。HttpHelpers.SetAbsoluteUriを修正してください(ステップ3を参照)。また、証明書がCA署名済みで、443で到達可能であることも確認してください。
Calling the bot says 'we couldn't connect you'
Teamsチャネルで正しい/api/calling webhookを使用してCallingが有効になっていること、GraphのCalls.AccessMedia.All権限に同意していること、NSGとWindowsファイアウォールの両方でポート443/8445/9441が開いていることを確認してください。通話が以前は機能していて停止した場合は、VM上でボットプロセスがまだ実行中か確認してください。タスク スケジューラのデフォルトの72時間実行制限により、起動から数日後に停止します(ステップ3の警告を参照)。