> This is a page from the ElevenLabs documentation. For an index of the documentation, fetch https://elevenlabs.io/docs/llms.txt.

# Voximplant

> **Warning**
>
> Voximplant 集成目前处于有限可用阶段，需按工作区启用。在路由客户流量前，请联系 ElevenLabs 代表。

## 概览

Voximplant 集成通过 Voximplant Media Streams，将 VoxEngine 场景中的通话经由 WebSocket 直接连接到
ElevenAgents 中的智能体。Voximplant 负责电话通信：它拨打或接听电话，并决定后续操作。ElevenAgents 负责
对话。无需 SIP 中继或中间件。

呼入和呼出通话的工作方式相同：通话接通后，场景会为每通电话向 ElevenLabs 打开一个
WebSocket。对话结束时，ElevenLabs 会向场景发送一个决策事件，说明是将来电者转接给人工、挂断电话，还是将通话视为
超时。

## 集成工作原理

1. VoxEngine 场景接听呼入电话或发起呼出电话。
2. 场景向智能体的 ElevenLabs 端点打开 WebSocket，并使用 ElevenLabs API 密钥作为 bearer token 进行身份验证。
3. 场景通过 `call.sendMediaTo()` 启动媒体流，并传入任意 `customParameters`。
   ElevenLabs 会将每个参数转换为动态变量，并返回自己的 `start`。
4. Voximplant 和 ElevenLabs 在 `media` 消息中交换 8 kHz、16 位线性 PCM 音频。
5. 对话结束时，ElevenLabs 会发送智能体最后一句话的剩余内容、一个
   `elevenlabs-decision` 自定义事件和一条 `stop` 消息。场景随后关闭 WebSocket
   并根据决策执行操作。

## 要求

开始前，请确保具备：

1. 一个 Voximplant 账户，包含应用、电话号码或呼出功能，以及编辑场景的权限。
2. 一个已启用 Voximplant 集成的 ElevenLabs 工作区。
3. 一个 ElevenLabs 智能体和专用 API 密钥。

## 配置 ElevenLabs

#### 创建或选择智能体

在 ElevenAgents 中创建智能体。如果希望通话一接通就说话，请为其设置首条消息。
音频格式由集成设置，因此不会使用智能体配置的格式
处理 Voximplant 通话。

#### 启用结束通话

在 **智能体 → 工具 → 系统工具** 中启用 **结束通话**，让智能体能够结束对话。
智能体结束通话时提供的原因会随决策一起发送到场景。

#### 添加 Voximplant 转接规则（可选）

如需让智能体将来电者转接给人工，请为其添加 **转接至号码** 系统工具，并设置提供商配置为 `voximplant` 的
转接规则。该规则没有目标号码：对话将以 `transfer-to-agent` 决策结束，由场景负责路由来电者。
转接规则通过 API 配置。使用 `PATCH` 请求为智能体添加该工具：

```bash
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/agent_7101k5zvyjhmfg983brhmhkd98n6" \
  -H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "conversation_config": {"agent": {"prompt": {"built_in_tools": {
      "end_call": {"type": "system", "name": "end_call", "description": "",
                   "params": {"system_tool_type": "end_call"}},
      "transfer_to_number": {
        "type": "system", "name": "transfer_to_number", "description": "",
        "params": {
          "system_tool_type": "transfer_to_number",
          "transfers": [],
          "transfer_rules": [{
            "condition": "the caller asks to speak with a human",
            "provider_configs": [
              {"type": "voximplant", "config": {"type": "voximplant_handoff"}}
            ]
          }]
        }
      }
    }}}}
  }'
```

发送智能体完整的 `built_in_tools` 对象，包括其已有的工具，例如 `end_call`。
智能体返回的选项是固定令牌 `voximplant`，因此每个工具只需一条 Voximplant 规则。
为其他提供商配置的电话号码和 SIP URI 不会用于 Voximplant
通话。

> **Note**
>
> 转接规则由 API 管理。请通过上述 API 配置和更新使用这些规则的智能体的 **转接至号码** 工具；
> 控制台的工具编辑器适用于按号码配置的转接列表。

#### 创建专用 API 密钥

在与智能体相同的工作区中创建 API 密钥，并将其范围限定为 ElevenAgents。如需仅接受来自 Voximplant 的
连接，请将密钥允许的 IP 地址限制为 Voximplant 的媒体服务器，Voximplant 在以下地址列出了这些服务器：
`https://api.voximplant.com/getMediaResources?with_mediaservers`。

#### 记录 WebSocket URL

场景会连接到一个包含智能体 ID 的 URL：

| 环境   | URL                                                                                                               |
| ---- | ----------------------------------------------------------------------------------------------------------------- |
| 默认   | `wss://api.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6`                    |
| 数据驻留 | `wss://api.<region>.residency.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6` |

如需固定使用智能体的某个分支，请添加 `?branch_id=<branch_id>`。

> **Info**
>
> 如果 ElevenLabs 账户位于独立的数据驻留环境，请将 `<region>` 替换为区域代码。请参阅
> [数据驻留](/docs/zh/overview/administration/data-residency) 查看可用区域。

## 设置 Voximplant

#### 创建应用

在 [Voximplant 控制面板](https://manage.voximplant.com/applications)中，打开 **Applications**，
然后点击 **Create application**。为应用命名，例如 `elevenlabs-agent`，然后点击
**Create**。

![Voximplant：创建应用](/docs/_fern-files/_img/29af341fa0a32eaaab444ade8305208121487413c5868597386a973b14fe4fe7.webp)

#### 将 API 密钥存为密钥

在应用中，打开 **Secrets** 并点击 **Add secret**。将其命名为 `ELEVENLABS_API_KEY`，把为智能体工作区创建的
API 密钥粘贴到 **Value**，然后点击 **Add secret**。场景通过 `VoxEngine.getSecretValue()` 读取密钥，
因此密钥不会出现在场景代码中。

![Voximplant：添加密钥](/docs/_fern-files/_img/ecf8be823bbc15b263e3f4838684a78de3a3594704be0055b443d15ab93e1485.webp)

#### 创建场景

打开 **Scenarios**，点击 **Create scenario**，将其命名为 `elevenlabs_agent`，粘贴以下代码，
替换 `ELEVENLABS_URL` 中的智能体 ID，然后点击 **Save**。它会接听呼入电话，将其流式传输给智能体，
并在对话结束时根据决策执行操作。对于呼出电话，请在所发起通话的 `CallEvents.Connected` 处理程序中打开
WebSocket。

```javascript maxLines=0
const ELEVENLABS_URL =
  "wss://api.elevenlabs.io/v1/convai/conversation/voximplant/agent_7101k5zvyjhmfg983brhmhkd98n6";

VoxEngine.addEventListener(AppEvents.CallAlerting, ({ call }) => {
  call.answer();
  let decision = null;

  const ws = VoxEngine.createWebSocket(ELEVENLABS_URL, {
    headers: [
      {
        name: "Authorization",
        value: `Bearer ${VoxEngine.getSecretValue("ELEVENLABS_API_KEY")}`,
      },
    ],
  });

  ws.addEventListener(WebSocketEvents.OPEN, () => {
    call.sendMediaTo(ws, {
      encoding: WebSocketAudioEncoding.PCM16_8KHZ,
      customParameters: { customer_name: "Ana", task_id: "task-7" },
    });
    ws.sendMediaTo(call);
  });

  ws.addEventListener(WebSocketEvents.MESSAGE, ({ text }) => {
    const message = JSON.parse(text);
    if (message.customEvent === "elevenlabs-decision") {
      decision = message.payload;
      Logger.write(`ElevenLabs decision: ${JSON.stringify(decision)}`);
    }
  });

  ws.addEventListener(WebSocketEvents.CLOSE, (e) => {
    Logger.write(`ElevenLabs socket closed: code=${e.code} reason=${e.reason}`);
    if (decision?.action === "transfer-to-agent") {
      // Route the caller to a human here, for example with VoxEngine.callPSTN().
      call.say("Connecting you to an agent.", { language: VoiceList.Amazon.en_US_Joanna });
      call.addEventListener(CallEvents.PlaybackFinished, () => call.hangup());
      return;
    }
    call.hangup();
  });

  call.addEventListener(CallEvents.Disconnected, () => {
    ws.close();
    VoxEngine.terminate();
  });
});
```

![Voximplant：编辑器中的场景](/docs/_fern-files/_img/d2e26165f57cae40fc01e8de28052fef09b2f98bd90c0e1a8779423715f61e2e.webp)

#### 将通话路由到场景

打开 **Routing**，点击 **Create rule**，为规则命名，并分配 `elevenlabs_agent`
场景。默认模式 `.*` 会将应用的每通电话通过该场景处理；
如果应用还处理其他通话，请将其限制为特定电话号码。要路由电话号码，请在 **Numbers** 下将其附加到应用。

![Voximplant：创建规则](/docs/_fern-files/_img/bc6830b7dcd66dc0b59595456b1197bdcbb9174f15778da7ba9655ba4b93936e.webp)

#### 拨打测试电话

无需电话号码，也可以通过浏览器呼叫应用：

1. 打开 **Users**，点击 **Create user**，然后创建一个设有密码的用户。

   ![Voximplant：新用户](/docs/_fern-files/_img/4754c9234588fd0caa336e1168e207b265557dc4e6a12b1cf8bced1275b8bae6.webp)

2. 在 **Routing** 中，点击 **Test tools**，然后点击 **Softphone**。以该用户身份登录，允许
   访问麦克风，输入任意目标号码后拨号。智能体会以首条消息接听。

3. 通话结束后，打开 **Call history**。每通电话会在通话记录旁列出一个 WebSocket 分支。
   点击 **View log** 查看会话。

   ![Voximplant：通话历史](/docs/_fern-files/_img/4e2416ad3587bb320300173cf679f7c5d7a5e545550b0a647062a79866dac40c.webp)

   日志会显示 ElevenLabs 发送的决策、WebSocket 关闭以及场景接下来执行的操作。
   在本示例中，来电者请求转人工，因此决策为 `transfer-to-agent`，
   并附带智能体给出的原因。

   ![Voximplant：包含 elevenlabs-decision
   事件的会话日志](/docs/_fern-files/_img/f8b4602fa02cfd3c417557377aaa5dd66e2e88a67e7adf4cb037ccae1840f45c.webp)

> **Warning**
>
> 请根据 `elevenlabs-decision` 事件执行操作，而不是 `MEDIA_ENDED`。Voximplant 会在约 1 秒未收到 ElevenLabs 音频后触发 `MEDIA_ENDED`，
> 而这种情况可能发生在对话过程中。

## 动态变量

`customParameters` 中的每个键都会成为同名动态变量。非字符串值会转换为文本，例如 `true` 会变为
`"true"`。`customParameters` 可以是对象或 JSON 编码的字符串。以 `system__` 或 `integration__` 开头的键为保留键，
会被丢弃。

## 决策事件

对话结束时，ElevenLabs 会在 `stop` 消息之前准确发送一个决策：

```json
{ "customEvent": "elevenlabs-decision", "payload": { "action": "terminate", "reason": "under_18" } }
```

| `action`            | 发送时机                                                        | `reason`                   |
| ------------------- | ----------------------------------------------------------- | -------------------------- |
| `transfer-to-agent` | 智能体通过 Voximplant 转接规则调用 **转接至号码**。                          | 智能体给出的转接原因。                |
| `timeout`           | 对话因静默超时，或在智能体最后一条消息后超时；也可能是智能体在来电者静默 15 秒后结束通话，不计算智能体说话的时间。 | 超时消息，或智能体给出的原因。            |
| `terminate`         | 智能体使用 **结束通话** 结束通话，或者对话正常结束。                               | 智能体使用 **结束通话** 时给出的原因（如有）。 |
| `unexpected`        | 对话因错误而结束。                                                   | 错误说明。                      |

如果场景先发送 `stop` 或关闭 WebSocket，例如来电者挂断电话，
ElevenLabs 不会发送决策。

## 音频

Voximplant 和 ElevenLabs 双向交换 8 kHz、16 位单声道线性 PCM 音频。ElevenLabs 会以接近实时的速度播放智能体音频，
因此当来电者打断智能体时，最多仍会播放约 0.4 秒被打断的回复。

## 身份验证错误

当 `Authorization` 标头缺失或格式错误、API 密钥无效或不允许来自连接 IP 地址、密钥属于其他工作区，
或者密钥所属用户无权访问已启用身份验证的智能体时，ElevenLabs 会以代码 `1008` 和原因 `bad auth token` 关闭
WebSocket。

## 限制与不支持的功能

* 不支持客户端工具和 **播放按键音** 系统工具。**转接至
  号码** 只能通过 Voximplant 转接规则使用；场景决定来电者
  的去向。
* 无法从场景传入配置覆盖项，例如不同的首条消息；
  只能传入动态变量。
* 数据收集结果不会发送到场景。请通过
  [通话后 webhook](/docs/zh/eleven-agents/workflows/post-call-webhooks) 获取。