> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Graph通話ボット

## 概要

この方式では、エージェントを**呼び出し可能なTeams ID**として利用します。ユーザーは名前で検索して1対1で通話でき、エージェントはリアルタイムに応答します。電話番号、PSTN、Communications Creditsは不要です。名前で呼び出せる唯一の方式であり、運用には最も手間がかかります。

**Microsoft Graphリアルタイムメディアボット**（Cloud Communications通話プラットフォーム）を使用します。メディアSDK（`Microsoft.Skype.Bots.Media`）は**Windows Server上の.NET専用**であり、Teams通話で生のオーディオを扱うLinuxまたは.NET以外の方法はありません。

> **Note**
>
> Teams内で**名前で**呼び出せるのはこの方式だけです。より簡単に設定するには[ウィジェット タブ](/docs/ja/eleven-agents/phone-numbers/microsoft-teams/widget-tab)を、電話番号が必要な場合は
> [ACS](/docs/ja/eleven-agents/phone-numbers/microsoft-teams/azure-communication-services)を使用してください。

## 仕組み

![Teamsユーザーが名前でボットに通話すると、Teamsは通話をWindows VM上のメディアボットにルーティングし、メディアボットは生のPCM 16kオーディオをWebSocket経由でElevenLabsエージェントにブリッジします](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/f411ec0b44d4c5ca1a61598fbd1e6162e46a1c74179eebb4b673ee1428823e84/assets/images/conversational-ai/teams-media-bot-architecture.svg)

ボットは**アプリケーションホスト型メディア**で応答し、毎秒50オーディオフレーム（20 ms PCM 16 kHz）を受信します。これをWebSocket経由でElevenLabsエージェントにブリッジし、エージェントのオーディオを通話にストリーミングで返します。

## 要件

1. **Azure Bot**登録とアプリ（Entraアプリ登録）。
2. 管理者の同意を得た**Graphアプリケーション権限**：`Calls.AccessMedia.All`（生のメディア）と`Calls.Initiate.All`。
3. パブリックIPと開放されたメディアポートを備えた**Windows Server VM**（**物理コア2つ以上**、例：`Standard_D4s_v3`）。
4. メディア／シグナリングエンドポイント用のパブリックFQDNに設定された**CA署名TLS証明書**（メディアプラットフォームは自己署名証明書を拒否します）。
5. 両方向とも**PCM 16000 Hz**に設定した[ElevenLabsエージェント](/docs/ja/eleven-agents/quickstart)：**Voice**タブでTTS出力形式、**Advanced**タブでユーザー入力オーディオ形式を設定します。

> **Warning**
>
> `D2s_v3`（2 vCPU＝**物理コア1つ**）では、`MediaPlatform needs a system with at least 2
>   cores`というエラーで失敗します。物理コアが2つ以上のサイズ（例：`D4s_v3`）を使用してください。

## 権限とロール

| スコープ                      | ロール／権限                                                                                                     | 理由                                     |
| ------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| Entra                     | **Application Administrator**                                                                              | アプリ登録とAzure Botを作成する                   |
| Entra                     | **Global Administrator / Privileged Role Administrator**                                                   | Graph通話権限に管理者の同意を付与する（アプリ権限は自己同意できません） |
| Microsoft Graph（アプリケーション） | `Calls.AccessMedia.All`, `Calls.Initiate.All`                                                              | 1対1の通話に応答し、生のメディアにアクセスする               |
| Azure RBAC                | リソースグループの**Contributor**                                                                                   | Windows VMとAzure Botを作成する              |
| Teams管理者                  | [**カスタムアプリのアップロード**](https://admin.teams.microsoft.com/policies/manage-apps)を許可し、ボットの**Calling**チャネルを有効化する | アプリをサイドロードして通話を受ける                     |

## ステップ1 — ボットとGraph権限を登録する

アプリ登録とそれに紐付けたAzure Botを作成し、通話権限を付与して同意します（同意にはGlobal AdminまたはPrivileged Role Adminが必要です）：

```bash
APPID=$(az ad app create --display-name "ElevenLabs Teams Agent" \
  --sign-in-audience AzureADMyOrg --query appId -o tsv)
az ad sp create --id "$APPID"
# create a client secret and record it
az ad app credential reset --id "$APPID" --display-name bot --query password -o tsv

# Azure Bot bound to the app
az bot create --resource-group $RG --name el-teams-agent-bot \
  --app-type SingleTenant --appid "$APPID" --tenant-id $TENANT \
  --endpoint "https://YOUR_FQDN/api/messages" --sku S1
```

2つのGraphアプリケーションロールを付与し、**管理者の同意**を取得します（Global AdminまたはPrivileged Role Adminが必要）。その後、割り当てが反映されたことを確認してください：

```bash
# Graph app roles: Calls.AccessMedia.All, Calls.Initiate.All
az ad app permission add --id "$APPID" --api 00000003-0000-0000-c000-000000000000 \
  --api-permissions a7a681dc-756e-4909-b988-f160edc6655f=Role \
    284383ee-7f6e-4e40-a2a8-e85dcb029101=Role
az ad app permission admin-consent --id "$APPID"

# Verify — should print both role ids
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$APPID')/appRoleAssignments" \
  --query "value[].appRoleId" -o tsv
```

> **Note**
>
> `admin-consent`が`Consent validation failed`を返す場合は、代わりにサービスプリンシパルでアプリロールを直接付与してください：
>
> ```bash
> GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)
> BOT_SP=$(az ad sp show --id "$APPID" --query id -o tsv)
> for ROLE in a7a681dc-756e-4909-b988-f160edc6655f 284383ee-7f6e-4e40-a2a8-e85dcb029101; do
>   az rest --method POST \
>     --url "https://graph.microsoft.com/v1.0/servicePrincipals/$GRAPH_SP/appRoleAssignedTo" \
>     --body "{\"principalId\": \"$BOT_SP\", \"resourceId\": \"$GRAPH_SP\", \"appRoleId\": \"$ROLE\"}"
> done
> ```

ポータルで、[Entra管理センター](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/RegisteredAppsListBlade)の**アプリ登録→対象のアプリ→APIのアクセス許可**を確認してください。両方の権限が、緑色のチェックマーク付きで**許可済み**と表示されるはずです。

![Calls.AccessMedia.AllとCalls.Initiate.Allが許可されていることを示すアプリ登録のAPIアクセス許可ブレード
](/docs/_fern-img/4abc1e220d3fc03845594aa4f835656425cc1c39448dae30dace98e672fd8fb1.webp)

## ステップ2 — Windows VM、証明書、ポートを準備する

```bash
az vm create -g $RG -n teams-media-bot --image Win2022Datacenter \
  --size Standard_D4s_v3 --admin-username azureuser --admin-password '<strong-pw>' \
  --public-ip-sku Standard --public-ip-address-dns-name elevenmediabot
az vm open-port -g $RG -n teams-media-bot --port 80,443,8445,9441 --priority 300
```

VM上で以下を実行します（メディアプラットフォームのネイティブコードに必要ですが、Windows Serverにはデフォルトで含まれていません）：

```powershell
# VC++ runtime + Media Foundation feature (required by NativeMedia.dll)
choco install -y vcredist140
Install-WindowsFeature Server-Media-Foundation

# CA cert for the VM's FQDN via win-acme (HTTP-01), then import to LocalMachine\My
& wacs.exe --target manual --host <vm-fqdn>.cloudapp.azure.com `
  --validation selfhosting --store pfxfile --pfxfilepath C:\bot\certs --accepttos
```

同じポートを**Windowsファイアウォール**でも開放し、証明書の**拇印**を控えておいてください。ボットはKestrel（443と通知用ポート）およびメディアプラットフォーム（8445）をこの証明書にバインドします。

> **Tip**
>
> VM自体の`*.cloudapp.azure.com` FQDNでLet's Encrypt証明書を使用できます。別途ドメインを用意する必要はありません。

## ステップ3 — ボットをビルドして実行する

Microsoft の [`microsoft-graph-comms-samples`](https://github.com/microsoftgraph/microsoft-graph-comms-samples) の **PublicSamples/EchoBot** をベースにします。これは `net6.0` を対象とし、.NET SDK でビルドできます（Visual Studio Build Tools は不要です）。

```powershell
git clone --depth 1 https://github.com/microsoftgraph/microsoft-graph-comms-samples.git C:\bot\samples
cd C:\bot\samples\Samples\PublicSamples\EchoBot\src
dotnet build EchoBot.sln -c Release
```

`appsettings.json` の `AppSettings` セクションに、`AadAppId`、`AadAppSecret`、`ServiceDnsName`/`MediaDnsName`（VMのFQDN）、`CertificateThumbprint`、ポート（通話は`443`、通知は`9441`、メディアは`8445`）を設定します。さらに、以下のElevenLabsブリッジ用に`ElevenLabsAgentId`と`ElevenLabsOrigin`（`wss://api.elevenlabs.io`、またはデータレジデンシーのホスト）を追加します。再起動後も動作するよう、Windowsのスケジュールされたタスク／サービスとして実行してください。

> **Warning**
>
> タスク スケジューラのデフォルトの\*\*実行時間制限（72時間）\*\*は、長時間実行タスクを通知なしで終了します。起動時に開始したボットは3日後に停止し、通話時に「接続できませんでした」と表示されます。制限を無効化し、失敗時の再起動を追加してください。
>
> ```powershell
> $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
>   -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable
> Set-ScheduledTask -TaskName EchoBot -Settings $s
> ```

> **Warning**
>
> 標準ポート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ブリッジに置き換えます。

**`SpeechService.cs — ElevenLabs bridge (core)`**

```csharp title="SpeechService.cs — ElevenLabs bridge (core)"
public class SpeechService
{
    private readonly AppSettings _settings;
    private readonly ILogger _logger;
    private ClientWebSocket _ws;
    private bool _started;
    private bool _connecting;

    public event EventHandler<MediaStreamEventArgs> SendMediaBuffer; // agent audio -> call
    public event EventHandler FlushMedia;                            // barge-in: drop queued audio

    public SpeechService(AppSettings settings, ILogger logger) { _settings = settings; _logger = logger; }

    // Caller audio -> ElevenLabs
    public async Task AppendAudioBuffer(AudioMediaBuffer buffer)
    {
        if (!_started)
        {
            if (_connecting) return;            // a connect attempt is already in flight
            _connecting = true;
            try { await Connect(); _started = true; }
            catch (Exception ex) { _logger.Error(ex, "ElevenLabs connect failed; retry on next frame"); return; }
            finally { _connecting = false; }
        }
        if (_ws?.State != WebSocketState.Open || buffer.Length <= 0) return;
        var pcm = new byte[buffer.Length];
        Marshal.Copy(buffer.Data, pcm, 0, (int)buffer.Length);
        var msg = JsonSerializer.Serialize(new { user_audio_chunk = Convert.ToBase64String(pcm) });
        await _ws.SendAsync(Encoding.UTF8.GetBytes(msg), WebSocketMessageType.Text, true, default);
    }

    private async Task Connect()
    {
        _ws = new ClientWebSocket();
        // ElevenLabsOrigin: wss://api.elevenlabs.io, or a residency host (.eu./.in./.sg.)
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await _ws.ConnectAsync(new Uri(url), default);
        await _ws.SendAsync(Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(new { type = "conversation_initiation_client_data" })),
            WebSocketMessageType.Text, true, default);
        _ = Task.Run(ReceiveLoop);
    }

    private async Task ReceiveLoop()
    {
        var buf = new byte[32768]; var sb = new StringBuilder();
        while (_ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await _ws.ReceiveAsync(buf, default); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            var type = doc.RootElement.GetProperty("type").GetString();
            if (type == "audio")            // ElevenLabs audio -> call
                Emit(Convert.FromBase64String(doc.RootElement
                    .GetProperty("audio_event").GetProperty("audio_base_64").GetString()));
            else if (type == "ping")
                await _ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new {
                    type = "pong", event_id = doc.RootElement.GetProperty("ping_event").GetProperty("event_id").GetInt32() })),
                    WebSocketMessageType.Text, true, default);
            else if (type == "interruption")  // barge-in: drop any agent audio still queued
                FlushMedia?.Invoke(this, EventArgs.Empty);
        }
    }

    // slice PCM into 20 ms / 640-byte frames the media platform expects
    private void Emit(byte[] pcm)
    {
        var all = new List<AudioMediaBuffer>(); long tick = DateTime.Now.Ticks;
        for (int off = 0; off < pcm.Length; off += 640)
        {
            var frame = new byte[640];
            Array.Copy(pcm, off, frame, 0, Math.Min(640, pcm.Length - off));
            all.AddRange(Utilities.CreateAudioMediaBuffers(frame, tick, _logger));
            tick += 20 * 10000;
        }
        if (all.Count > 0) SendMediaBuffer?.Invoke(this, new MediaStreamEventArgs { AudioMediaBuffers = all });
    }
}
```

両側ともPCM 16kHzモノラルなので、base64をそのまま渡せます。エージェントは`pcm_16000`に設定してください。ElevenLabsの`interruption`（バージイン）時、ブリッジは`FlushMedia`を発行します。これをメディアストリームに接続し、キュー済みの`AudioMediaBuffer`をすべて破棄するようにしてください。そうしないと、エージェントが発信者に重なって話し続けます。メッセージの完全なリファレンスは[WebSocketドキュメント](/docs/ja/eleven-agents/libraries/web-sockets)にあります。通話終了時の切断とウォーム転送については、以下のセクションを参照してください。

> **Note**
>
> `Connect()`内のURLは**公開**エージェントに接続します。プライベートエージェントの場合は、サーバー側で短期間有効な
> 署名付きURLをリクエストしてください。APIキーを使用して`GET /v1/convai/conversation/get-signed-url?agent_id=...`を呼び出し、
> 代わりに返されたURLに接続します。[データ レジデンシー](/docs/ja/overview/administration/data-residency)では、`ElevenLabsOrigin`をレジデンシーの
> ホスト（`wss://api.eu.residency.elevenlabs.io`、`.in.`、または`.sg.`）に設定します。署名付きURLリクエストには、
> 対応する`https://`ホストを使用します。

## ステップ4 — Teamsから呼び出せるようにする

1. Azure BotのTeamsチャネルで**Calling**を有効にし、**calling webhook**を`https://YOUR_FQDN/api/calling`に設定します。

   ```bash
   az bot msteams create -g $RG -n el-teams-agent-bot \
     --enable-calling --calling-web-hook "https://YOUR_FQDN/api/calling"
   ```

   ポータルでは、Azure Botリソース → **Channels** → **Microsoft Teams** → **Calling**タブにあります。

   ![Microsoft Teamsチャネルが正常と表示されているAzure BotのChannelsブレード](/docs/_fern-img/01115d26ab645dd6ec6569a36a918cd4de0214c79dcb0070d7a3c3a5e544c93b.webp)

   ![Enable callingがオンになり、calling webhookが設定されたTeamsチャネルのCallingタブ](/docs/_fern-img/7c4819f64878bfb152661adc948c1170f9efd48ba3d68401c4a4d4ba075951b0.webp)

2. `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エージェントが話します。

![ElevenLabsエージェントボットとのアクティブなTeams通話](/docs/_fern-img/0f1fad08a091bcd20ffbbf5c3949b5503f423d8eb6298f464a870b3a0a254726.webp)

> **Note**
>
> 1対1の名前指定通話には、電話番号やリソースアカウントは不要です。これらが必要なのはPSTN
> ダイヤルインのみです。生オーディオブリッジを有効にするのは`Calls.AccessMedia.All`です。

## テキストチャット（同じボット）

同じAzure BotはTeamsで**テキスト**にも応答できるため、ユーザーはエージェントに通話することもチャットすることもできます。Callingとメッセージングはボット上で独立したチャネルです。calling webhookは音声を処理し、Bot Frameworkの**messaging endpoint**（`/api/messages`）はチャットを処理します。

![テキストメッセージに応答するElevenLabsエージェントボットとのTeamsチャット](/docs/_fern-img/fb75ae17848916790d29c1c0a703051946902893e46ed62884742db36be453b2.webp)

ボットのmessaging endpointを、提供元のホストに向けます（メディアボットでも別のサービスでも構いません。Windows VMである必要はありません）。

```bash
az bot update -g $RG -n el-teams-agent-bot --endpoint "https://YOUR_FQDN/api/messages"
```

Bot Framework SDKでエンドポイントを実装し、各メッセージを音声と同じ会話WebSocket経由で**テキストモード**のエージェントに中継します。[`user_message`](/docs/ja/eleven-agents/customization/events/client-to-server-events)イベントを送信し、[`agent_response`](/docs/ja/eleven-agents/customization/events/client-events)イベントを読み取ります。まず、エージェントの[オーバーライド](/docs/ja/eleven-agents/customization/personalization/overrides)設定で**first message**フィールドを有効にしてください。以下のコードではこれを空文字でオーバーライドするため、エージェントの挨拶ではなくユーザーのメッセージへの回答が返されます。

**`ChatBot.cs — Teams text chat -> ElevenLabs (text mode)`**

```csharp title="ChatBot.cs — Teams text chat -> ElevenLabs (text mode)"
public class ChatBot : ActivityHandler
{
    private readonly AppSettings _settings;
    public ChatBot(AppSettings settings) => _settings = settings;

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turn, CancellationToken ct)
    {
        var reply = await AskAgent(turn.Activity.Text, ct);
        await turn.SendActivityAsync(MessageFactory.Text(reply), ct);
    }

    private async Task<string> AskAgent(string text, CancellationToken ct)
    {
        using var ws = new ClientWebSocket();
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await ws.ConnectAsync(new Uri(url), ct);
        // Suppress the agent's greeting: with no override, the first agent_response is the
        // configured first message, not the answer to this user_message.
        await Send(ws, new
        {
            type = "conversation_initiation_client_data",
            conversation_config_override = new { agent = new { first_message = "" } },
        }, ct);
        await Send(ws, new { type = "user_message", text }, ct);

        var buf = new byte[16384]; var sb = new StringBuilder();
        while (ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await ws.ReceiveAsync(buf, ct); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            switch (doc.RootElement.GetProperty("type").GetString())
            {
                case "agent_response":
                    return doc.RootElement.GetProperty("agent_response_event")
                              .GetProperty("agent_response").GetString();
                case "ping":
                    await Send(ws, new { type = "pong", event_id = doc.RootElement
                        .GetProperty("ping_event").GetProperty("event_id").GetInt32() }, ct);
                    break;
            }
        }
        return "Sorry, I couldn't reach the agent.";
    }

    private static Task Send(ClientWebSocket ws, object msg, CancellationToken ct) =>
        ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg)),
            WebSocketMessageType.Text, true, ct);
}
```

標準的な方法で登録します（`CloudAdapter`、`AddTransient<IBot, ChatBot>()`によるボットの登録、`/api/messages`コントローラー）。また、マニフェストのボットエントリにチャットスコープを追加します。

```json
"bots": [
  { "botId": "YOUR_APP_ID", "supportsCalling": true, "scopes": ["personal", "team", "groupChat"] }
]
```

> **Note**
>
> このスニペットではメッセージごとに新しい会話を開始するため、各ターンは独立しています。チャットの
> **メモリー**を維持するには、Teamsの`conversation.id`ごとに1つのWebSocketを開いたままにし（ターン間で再利用）、
> アイドル状態のセッションを終了します。これにより、エージェントはそのチャット内の過去のメッセージを記憶します。`first_message`
> オーバーライドは、エージェントの
> [オーバーライド](/docs/ja/eleven-agents/customization/personalization/overrides)設定で有効にする必要があります。許可されていないオーバーライドを送信すると、サーバーは
> **会話を終了します**。有効にできない場合は、オーバーライドを省略し、各セッションの最初の`agent_response`（挨拶）を破棄して、
> 次の応答を返してください。

> **Tip**
>
> チャットの返信が届かない場合は、エージェントの**Advanced**設定で\*\*`agent_response`\*\*[クライアント イベント](/docs/ja/eleven-agents/customization/events/client-events)を有効にしてください。テキスト応答はこのイベントを通じて配信されます。

## 通話終了

ElevenLabsが会話を終了したら（**End Call**ツールによってWebSocketが閉じられたら）、Teams側の通話を切断します。

```csharp
await this.Call.DeleteAsync(); // after a short delay so the goodbye audio finishes
```

## 人間へのウォーム転送

エージェントがカスタム`transfer_to_human`クライアントツールを実行すると、ボットは**Teamsユーザーを招待**して進行中の通話に追加し（相談を伴う追加）、その後退出します。

```csharp
var target = new IdentitySet { User = new Identity { Id = humanObjectId } };
await this.Call.Participants.InviteAsync(target, replacesCallId: null);
// suppress the end-call hangup while transferring, and mute the bot
```

> **Tip**
>
> 相談を伴う転送（`replacesCallId`）では、両者が**同じテナント**のTeamsユーザーである必要があります。PSTN転送先にはアプリケーションインスタンスが必要です。まず担当者に状況を伝えるには、エージェントから`reason`パラメーターを渡し、ブリッジする前に担当者へ再生してください。

## トラブルシューティング

#### \`MediaPlatform needs a system with at least 2 cores\`

VMの物理コアが1つしかありません。2つ以上の物理コア（例：`D4s_v3`）にリサイズして再起動してください。

#### \`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の警告を参照）。

## 便利なリンク

* [通話・会議ボットの概要](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/calls-meetings-bots-overview)
* [リアルタイムメディアの概念](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/real-time-media-concepts)
* [通話ボットを登録する](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/registering-calling-bot)
* [microsoft-graph-comms-samples（EchoBot）](https://github.com/microsoftgraph/microsoft-graph-comms-samples)
* [call：転送（相談を伴う転送）](https://learn.microsoft.com/en-us/graph/api/call-transfer)
* [エージェントWebSocketプロトコル](/docs/ja/eleven-agents/libraries/web-sockets)