> 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.

# Azure Communication Services

## 概要

この方法では、エージェントに**電話番号**を割り当てます。発信者がその番号に電話をかけると、[Azure Communication Services](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)（ACS）が**双方向メディアストリーミング**で応答し、小さなブリッジが標準の[エージェントWebSocketプロトコル](/docs/ja/eleven-agents/libraries/web-sockets)を使用して、ACSとElevenLabsエージェント間でPCMオーディオを中継します。これはコンタクトセンター／IVRのパターンであり、[SIPトランキング](/docs/ja/eleven-agents/phone-numbers/sip-trunking)のデプロイと同じ構成で、ACSがキャリアとなります。

Teamsにも2通りの方法で接続できます。Calling Planを持つTeamsユーザーはACS番号に直接発信できます。また、**Teams Phone Extensibility**を番号の前段に置くことで、Teamsリソースアカウントへの通話をACSにルーティングできます。

> **Note**
>
> ACSがPSTN番号を提供しているのは、[限られた 国](https://learn.microsoft.com/en-us/azure/communication-services/concepts/numbers/sub-eligibility-number-capability)のみです。
> お住まいの地域で番号を利用できない場合は、代わりに[SIP トランキング](/docs/ja/eleven-agents/phone-numbers/sip-trunking)対応のSIPプロバイダー、または[Graph通話 ボット](/docs/ja/eleven-agents/phone-numbers/microsoft-teams/graph-media-bot)を使用してください。

## 仕組み

![発信者がACS番号に電話をかけると、ACSがEvent Grid経由でIncomingCallをブリッジに送信します。ブリッジは双方向PCM 16kメディアストリーミングで応答し、WebSocket経由でElevenLabsエージェントに中継します](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/e77a27148e217fc7dc06c25007c1c141913fc831cbfe8a2f57405137646271af/assets/images/conversational-ai/teams-acs-architecture.svg)

オーディオは両方の経路で**PCM 16kHzモノラル**（エージェントの入出力形式は`pcm_16000`）のため、リサンプリングなしでbase64としてそのまま渡されます。

ブリッジは次のルートを公開します。

| ルート                      | 用途                                                                                |
| ------------------------ | --------------------------------------------------------------------------------- |
| `POST /api/incomingCall` | Event Grid Webhook：サブスクリプションを検証してから、メディアストリーミング付きで`answer_call`を実行                |
| `POST /api/callbacks`    | Call Automationのライフサイクルイベント（`CallConnected`、`CallDisconnected`、`AddParticipant*`） |
| `GET\|WS /ws`            | ACSメディアストリーミングソケット ↔ ElevenLabs                                                   |
| `POST /api/outboundCall` | 任意：応答者をエージェントに接続する発信通話を開始                                                         |

## 要件

1. **有料の**Azureサブスクリプション（MCA／EA／従量課金制）— 無料版、試用版、スポンサーシップのサブスクリプションでは番号を購入できません。
2. [Azure Communication Servicesリソース](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/create-communication-resource)。
3. パブリックWebSocketを備えたブリッジ用HTTPSホスト（Azure Container Apps、App Service、またはVM）。
4. 両方の経路で**PCM 16000Hz**に設定した[ElevenLabsエージェント](/docs/ja/eleven-agents/quickstart)：**Voice**タブでTTS出力形式を、**Advanced**タブでユーザー入力オーディオ形式を設定します。

## 権限とロール

| スコープ           | ロール／権限                        | 理由                                               |
| -------------- | ----------------------------- | ------------------------------------------------ |
| Azure RBAC     | リソースグループの**共同作成者**            | ACSリソース、Container App、Event Gridサブスクリプションを作成するため |
| Azureサブスクリプション | サブスクリプションの**所有者または共同作成者**     | 電話番号を購入するため（それ以外の場合、購入オプションは無効です）                |
| 請求             | **MCA／EA／従量課金制**のサブスクリプションタイプ | 無料、試用、スポンサーシップ、Devサブスクリプションでは番号を購入できません          |

> **Note**
>
> **共同作成者**権限の場合（所有者ではない場合）、`az containerapp up`はマネージドIDのACRプル
> ロール割り当てを作成できません。代わりにレジストリの管理者ユーザーを有効にしてアタッチしてください。手順2の警告を参照してください。

## 手順1 — ACSリソースと番号をプロビジョニングする

```bash
RG=my-rg
# Register providers (once)
az provider register -n Microsoft.Communication --wait
az provider register -n Microsoft.EventGrid --wait

# Create the ACS resource
az communication create --name my-acs --resource-group $RG \
  --location global --data-location unitedstates
```

リソース内で番号を購入します（ポータル → ACSリソース → **Phone numbers → Get**、または[phone-numbers SDK](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number)）。通話に**応答する**エージェントでは、**着信通話**対応の番号で十分です。`/api/outboundCall`も使用する場合は、**発信**機能も追加してください。

![通話機能とともにアクティブな番号を一覧表示するACSリソースのPhone numbersブレード](/docs/_fern-img/9d14034ee9f81e2ebcf64e30371f914ef6f87a8da30ac250e702662f2e5e1cc1.webp)

CLIから確認するには（`az extension add --name communication`が必要）、また、ブリッジが`ACS_CONNECTION_STRING`として使用する接続文字列を取得するには、次を実行します。

```bash
CONN=$(az communication list-key -n my-acs -g $RG --query primaryConnectionString -o tsv)
az communication phonenumber list --connection-string "$CONN" --query "[].phoneNumber"
```

## 手順2 — ブリッジをデプロイする

ブリッジは、`azure-communication-callautomation`を使用する小規模なFlask + `flask-sock`アプリです。着信フローの中核は次のとおりです。

**`bridge.py (excerpt)`**

```python title="bridge.py (excerpt)"
from azure.communication.callautomation import (
    CallAutomationClient, MediaStreamingOptions, StreamingTransportType,
    MediaStreamingContentType, MediaStreamingAudioChannelType, AudioFormat,
)

@app.route("/api/incomingCall", methods=["POST"])
def incoming_call():
    for event in request.get_json():
        # Event Grid subscription validation handshake
        if event.get("eventType") == "Microsoft.EventGrid.SubscriptionValidationEvent":
            return jsonify({"validationResponse": event["data"]["validationCode"]})

        if event.get("eventType") == "Microsoft.Communication.IncomingCall":
            client = CallAutomationClient.from_connection_string(ACS_CONNECTION_STRING)
            client.answer_call(
                incoming_call_context=event["data"]["incomingCallContext"],
                callback_url=f"https://{HOST}/api/callbacks",
                media_streaming=MediaStreamingOptions(
                    transport_url=f"wss://{HOST}/ws",
                    transport_type=StreamingTransportType.WEBSOCKET,
                    content_type=MediaStreamingContentType.AUDIO,
                    audio_channel_type=MediaStreamingAudioChannelType.MIXED,
                    start_media_streaming=True,
                    enable_bidirectional=True,
                    audio_format=AudioFormat.PCM16_K_MONO,
                ),
            )
    return jsonify({"status": "ok"})
```

`/ws`ソケットでは、PCM16を双方向に中継します。ACSの`AudioData`フレームを`{"user_audio_chunk": "<base64>"}`としてElevenLabsに転送し、エージェントのオーディオを`{"Kind":"AudioData","AudioData":{"Data":"<base64>"},"StopAudio":null}`として返送します。ACSが最初に送信するフレームは`AudioMetadata`（ネゴシエートされた形式）です。ログに記録して無視してください。ElevenLabs側では標準の[エージェントWebSocketプロトコル](/docs/ja/eleven-agents/libraries/web-sockets)を使用します。

> **Note**
>
> ACSでは方向ごとにJSONの大文字・小文字が異なります。受信フレーム、つまりACSが**送信する**フレームはキャメルケース（`kind`、
> `audioData.data`）ですが、送信フレーム、つまりACSが**期待する**フレームはパスカルケース（`Kind`、`AudioData.Data`、
> `StopAudio`）です。2つのケースを区別してください。以下のリレーではこの違いを反映しています。

**`bridge.py — media relay`**

```python title="bridge.py — media relay"
import asyncio, json, os, queue, threading, websockets
from flask_sock import Sock

sock = Sock(app)
AGENT_ID = os.environ["ELEVENLABS_AGENT_ID"]
# US default; data residency: wss://api.eu.residency.elevenlabs.io, .in., or .sg.
EL_ORIGIN = os.environ.get("ELEVENLABS_ORIGIN", "wss://api.elevenlabs.io")
EL_WS = f"{EL_ORIGIN}/v1/convai/conversation?agent_id={AGENT_ID}"

@sock.route("/ws")
def media_stream(ws):
    loop = asyncio.new_event_loop()
    el = {"ws": None}
    to_acs = queue.Queue()  # outbound frames; only this handler thread touches `ws`

    async def el_session():
        async with websockets.connect(EL_WS) as elws:
            el["ws"] = elws
            await elws.send(json.dumps({"type": "conversation_initiation_client_data"}))
            async for msg in elws:
                data = json.loads(msg)
                kind = data.get("type")
                if kind == "audio":  # agent audio -> caller
                    b64 = data["audio_event"]["audio_base_64"]
                    to_acs.put({"Kind": "AudioData", "AudioData": {"Data": b64}, "StopAudio": None})
                elif kind == "ping":
                    await elws.send(json.dumps({"type": "pong", "event_id": data["ping_event"]["event_id"]}))
                elif kind == "interruption":  # barge-in
                    to_acs.put({"Kind": "StopAudio", "AudioData": None, "StopAudio": {}})

    threading.Thread(target=lambda: loop.run_until_complete(el_session()), daemon=True).start()

    # Keep all ACS-socket I/O on this one thread: receive with a short timeout,
    # then drain any audio the ElevenLabs thread queued. Sending from the other
    # thread would race flask-sock and corrupt the stream.
    try:
        while True:
            raw = ws.receive(timeout=0.02)  # None when no frame arrived this tick
            if raw:
                evt = json.loads(raw)
                if evt.get("kind") == "AudioData" and el["ws"]:  # caller audio -> agent
                    asyncio.run_coroutine_threadsafe(
                        el["ws"].send(json.dumps({"user_audio_chunk": evt["audioData"]["data"]})), loop)
            while not to_acs.empty():
                ws.send(json.dumps(to_acs.get_nowait()))
    except Exception:
        pass  # ACS socket closed
```

> **Note**
>
> このリレーは意図的に最小限にしています。本番環境では、ロギング、再接続、正常な
> 終了処理を追加してください。メッセージの完全なリファレンスは[WebSocket ドキュメント](/docs/ja/eleven-agents/libraries/web-sockets)にあります。

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

Azure Container Appsにデプロイし、パブリックFQDNを取得します。

```bash
az containerapp up --name acs-el-bridge --resource-group $RG \
  --source . --ingress external --target-port 8080 \
  --env-vars ELEVENLABS_AGENT_ID=$AGENT_ID \
    ELEVENLABS_ORIGIN=wss://api.elevenlabs.io

FQDN=$(az containerapp show -n acs-el-bridge -g $RG \
  --query properties.configuration.ingress.fqdn -o tsv)
```

次に、アプリで`BRIDGE_PUBLIC_HOST=$FQDN`とACS接続文字列（シークレットとして）を設定します。

> **Warning**
>
> **共同作成者**権限の場合（所有者ではない場合）、`az containerapp up`はマネージドIDのACR
> プルロールを作成できません。レジストリの管理者ユーザーを有効化し（`az acr update --admin-enabled true`）、
> `az containerapp registry set`でアタッチしてから、`az containerapp update --image ...`を実行してください。

## 手順3 — IncomingCallをブリッジにルーティングする

ACSリソース上に、`IncomingCall`をブリッジへPOSTするEvent Gridサブスクリプションを作成します。ブリッジの検証ハンドシェイク（上記）により、サブスクリプションは自動的に完了します。

```bash
ACS_ID=$(az communication show -n my-acs -g $RG --query id -o tsv)
az eventgrid event-subscription create \
  --name acs-incomingcall \
  --source-resource-id "$ACS_ID" \
  --endpoint "https://$FQDN/api/incomingCall" \
  --endpoint-type webhook \
  --included-event-types Microsoft.Communication.IncomingCall

# Verify — should print "Succeeded"
az eventgrid event-subscription show --name acs-incomingcall \
  --source-resource-id "$ACS_ID" --query provisioningState -o tsv
```

サブスクリプションはACSリソースの**Events**ブレードに表示されます。

![Microsoft.Communication.IncomingCallでフィルタリングされたacs-incomingcall Webhookサブスクリプションを一覧表示するACSリソースのEventsブレード](/docs/_fern-img/f310aed9637e06b81906ca188350e3e721aaa5f4de69af2b3b6e7d157817a1ea.webp)

番号に電話をかけると、エージェントが応答します。

## Teamsへの接続

* \*\*直接発信：\*\*Teams PhoneとCalling Planを持つTeamsユーザーは、他の外線番号と同様にACS番号へ発信できます。
* **Teamsリソースアカウント（TPE）：**[Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart)でTeamsリソースアカウントをACSリソースにバインドすると、リソースアカウントへの通話で同じ`IncomingCall` → ブリッジのフローが実行されます。

## 通話終了

エージェントが会話を終了すると（たとえば、**End Call**ツールを使用した場合）、ElevenLabsはWebSocketを閉じます。発信者が切断された回線に残らないよう、ACS側の通話を切断します。

```python
CallAutomationClient.from_connection_string(ACS_CONNECTION_STRING) \
    .get_call_connection(call_connection_id).hang_up(is_for_everyone=True)
```

## 担当者へのウォーム転送

ElevenLabsネイティブの転送ツールは、ElevenLabsがテレフォニーを管理している場合にのみ適用されます。そのため、ここではエージェントが**カスタムクライアントツール**（例：`transfer_to_human`）を起動し、ブリッジが`add_participant`で担当者をライブ通話に**追加**することで処理します。ブラインド転送ではなくウォーム転送です。

```python
conn = client.get_call_connection(call_connection_id)
conn.add_participant(
    PhoneNumberIdentifier(human_number),
    source_caller_id_number=PhoneNumberIdentifier(your_outbound_number),
    invitation_timeout=30,
)
# then mute the bot and skip the end-of-call hangup so the human's leg survives
```

ACSは`AddParticipantSucceeded`／`AddParticipantFailed`コールバックを`/api/callbacks`に送信します。エージェントが引き継ぎの案内を話せるよう、`client_tool_result`を返してください。エージェント側の設定については、[システムツール](/docs/ja/eleven-agents/customization/tools/system-tools/transfer-to-number)を参照してください。

> **Tip**
>
> ツールの実行直後に（`add_participant`を呼び出す前に）転送ガードを設定してください。そうしないと、ELの高速な
> WebSocket切断と通話切断が競合し、担当者が参加する前に通話が切断される可能性があります。

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

#### IncomingCallがブリッジに届かない

Event Gridサブスクリプションがプロビジョニング済みであること（`provisioningState: Succeeded`）、および
ブリッジの`/api/incomingCall`が検証エコーを返したことを確認してください。番号に着信通話機能があり、サブスクリプションと
同じACSリソース内にあることも確認してください。サブスクリプションの
**Filters**タブでは、イベントタイプに**Incoming Call**が含まれている必要があります。

![イベントタイプがIncoming CallにフィルタリングされているイベントサブスクリプションのFiltersタブ](/docs/_fern-img/c7026e86d96551d713e4fd7d5753062f643fcd6a67b455866bb8f1bbb6b57eb1.webp)

#### 国際電話番号で\`CreateCallFailed\`／\`AddParticipantFailed\`が発生する

一部の宛先（例：インド）へのACS発信は制限されている、または断続的に失敗することがあります。サポート対象の宛先を使用するか、担当者側の回線をSIP／Operator番号にしてください。ブリッジのロジックには影響しません。これは発信側回線におけるキャリアレベルの障害です。

#### 音声が歪む、または速度がおかしい

両側ともPCM 16kHzモノラルである必要があります。エージェントの入出力形式を`pcm_16000`に設定してください。ブリッジは`conversation_initiation_metadata`からネゴシエートされた形式をログに記録します。

#### 番号を購入できない／自国で番号を利用できない

番号の購入には有料のサブスクリプションタイプ（MCA／EA／PAYG）が必要です。ACSがお住まいの国で番号を提供していない場合は、代わりに[SIP](/docs/ja/eleven-agents/phone-numbers/sip-trunking)プロバイダーを使用してください。

## 便利なリンク

* [ACS Call Automationの概要](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/call-automation)
* [ACSオーディオストリーミング](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/audio-streaming-concept)
* [通話制御アクション（転送／参加者の追加）](https://learn.microsoft.com/en-us/azure/communication-services/how-tos/call-automation/actions-for-call-control)
* [Teams Phone Extensibility](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/tpe/teams-phone-extensibility-quickstart)
* [エージェントWebSocketプロトコル](/docs/ja/eleven-agents/libraries/web-sockets)
* [ElevenLabs SIPトランキング](/docs/ja/eleven-agents/phone-numbers/sip-trunking)