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

# 多上下文 WebSocket

> **高级功能**
>
> 使用此多上下文 WebSocket API 编排语音智能体是一项复杂任务，建议由高级开发者使用。如需更易于管理的解决方案，建议了解我们的 [Agents 平台产品](/docs/zh/eleven-agents/overview)，它可简化许多此类难题。

## 概述

构建响应迅速的语音智能体，需要能够动态管理音频流、妥善处理打断，并在多轮对话中保持自然的语音。面向文本转语音（TTS）的多上下文 WebSocket API 专为这些场景设计。

此 API 在[标准 TTS WebSocket 功能](/docs/zh/eleven-api/guides/how-to/websockets/realtime-tts)的基础上引入了“上下文”概念。每个上下文都在单个 WebSocket 连接中作为独立的音频生成流运行。因此你可以：

* 并行管理多段语音（例如，智能体说话时准备回应用户的打断）。
* 通过关闭现有语音上下文并启动新上下文，无缝处理用户插话。
* 保持同一逻辑上下文内话语的韵律一致性。
* 通过选择性关闭不再需要的上下文，优化资源使用。

> **Warning**
>
> 多上下文 WebSocket API 专为语音应用优化，并非用于同时生成多个无关的音频流。因此，每个连接最多支持 5 个并发上下文。

本指南将介绍如何连接多上下文 WebSocket、管理上下文，以及应用构建引人入胜的语音智能体的最佳实践。

### 最佳实践

> **Note**
>
> 这些最佳实践对于使用多上下文 WebSocket API 构建响应迅速、高效的语音智能体至关重要。

#### 使用单个 WebSocket 连接

为每个最终用户会话建立一个 WebSocket 连接。与创建多个连接相比，这可减少开销和延迟。在此单一连接中，可以管理对话不同部分的多个上下文。

#### 分块流式传输响应，按句生成

生成较长响应时，请将文本分成较小的块进行流式传输，并在完整句子结束时使用 `flush: true` 标记。这可提升生成音频的质量和响应速度。

#### 妥善处理打断

将文本流式传输到一个上下文中，直到发生打断；然后创建新上下文并关闭现有上下文。对话流程变化时，此方法可确保平滑过渡。

#### 管理上下文生命周期

及时关闭未使用的上下文。服务器每个连接最多可维持 5 个并发上下文，但不再需要时应关闭上下文。

#### 防止上下文超时

上下文默认会在 20 秒后超时并自动关闭。非活动超时是适用于所有上下文的 WebSocket 级参数，如有需要最多可设为 180 秒。向某个上下文发送空文本消息即可重置超时计时。

### 处理打断

当用户打断智能体时，应[关闭当前上下文](/docs/zh/api-reference/text-to-speech/v-1-text-to-speech-voice-id-multi-stream-input#send.Close-Context)，并[创建新上下文](/docs/zh/api-reference/text-to-speech/v-1-text-to-speech-voice-id-multi-stream-input#send.Initialise-Context)：

```python
async def handle_interruption(websocket, old_context_id, new_context_id, new_response):
    # Close the existing context that was interrupted
    await websocket.send(json.dumps({
        "context_id": old_context_id,
        "close_context": True
    }))
    print(f"Closed interrupted context '{old_context_id}'")

    # Create a new context for the new response
    await send_text_in_context(websocket, new_response, new_context_id)
```

```javascript
function handleInterruption(websocket: WebSocket, oldContextId: string, newContextId: string, newResponse: string) {
  // Close the existing context that was interrupted
  websocket.send(JSON.stringify({
    context_id: oldContextId,
    close_context: true
  }));
  console.log(`Closed interrupted context '${oldContextId}'`);

  // Create a new context for the new response
  sendTextInContext(websocket, newResponse, newContextId);
}
```

### 保持上下文活跃

上下文会在[默认 20 秒无活动后](/docs/zh/api-reference/text-to-speech/v-1-text-to-speech-voice-id-multi-stream-input#request.query.inactivity_timeout)自动超时。如果需要在不生成文本的情况下保持上下文活跃（例如处理延迟期间），可以发送空文本消息来重置超时计时。

```python
async def keep_context_alive(websocket, context_id):
    await websocket.send(json.dumps({
        "context_id": context_id,
        "text": ""
    }))
```

```javascript
function handleInterruption(websocket: WebSocket, contextId: string) {
  // Close the existing context that was interrupted
  websocket.send(JSON.stringify({
    context_id: oldContextId,
    text: ""
  }));
}
```

### 关闭 WebSocket 连接

对话结束后，可以通过[关闭 socket](/docs/zh/api-reference/text-to-speech/v-1-text-to-speech-voice-id-multi-stream-input#send.Close-Socket)清理所有上下文：

```python
async def end_conversation(websocket):
    # This will close all contexts and close the connection
    await websocket.send(json.dumps({
        "close_socket": True
    }))
    print("Ending conversation and closing WebSocket")`
```

```javascript
function endConversation(websocket: WebSocket) {
  // This will close all contexts and close the connection
  websocket.send(JSON.stringify({
    close_socket: true
  }));
  console.log("Ending conversation and closing WebSocket");
}
```

## 完整对话式智能体示例

### 要求

* 拥有 API 密钥的 ElevenLabs 账户（了解如何[查找 API 密钥](/docs/zh/api-reference/authentication)）。
* 设备上已安装 Python 或 Node.js（或其他 JavaScript 运行时）。
* 熟悉 WebSocket 通信。建议阅读[标准 WebSocket 流式传输指南](/docs/zh/eleven-api/guides/how-to/websockets/realtime-tts)，了解基础概念。

### 设置

为所选语言安装必要的依赖项：

```python
pip install python-dotenv websockets
```

```javascript
npm install dotenv ws
for TypeScript, you might also want types:
npm install @types/dotenv @types/ws --save-dev
```

在项目目录中创建 .env 文件来存储 API 密钥：

**`.env`**

```python .env
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here
```

### 语音智能体示例

> **Note**
>
> 此代码仅供示例，不适用于生产环境

```python maxLines=100
import os
import json
import asyncio
import websockets
from dotenv import load_dotenv

load_dotenv()
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
VOICE_ID = "your_voice_id"
MODEL_ID = "eleven_flash_v2_5"

WEBSOCKET_URI = f"wss://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}/multi-stream-input?model_id={MODEL_ID}"

async def send_text_in_context(websocket, text, context_id, voice_settings=None):
    """Send text to be synthesized in the specified context."""
    message = {
        "text": text,
        "context_id": context_id,
    }

    # Only include voice_settings for the first message in a context
    if voice_settings:
        message["voice_settings"] = voice_settings

    await websocket.send(json.dumps(message))

async def continue_context(websocket, text, context_id):
    """Add more text to an existing context."""
    await websocket.send(json.dumps({
        "text": text,
        "context_id": context_id
    }))

async def flush_context(websocket, context_id):
    """Force generation of any buffered audio in the context."""
    await websocket.send(json.dumps({
        "context_id": context_id,
        "flush": True
    }))

async def handle_interruption(websocket, old_context_id, new_context_id, new_response):
    """Handle user interruption by closing current context and starting a new one."""
    # Close the existing context that was interrupted
    await websocket.send(json.dumps({
        "context_id": old_context_id,
        "close_context": True
    }))

    # Create a new context for the new response
    await send_text_in_context(websocket, new_response, new_context_id)

async def end_conversation(websocket):
    """End the conversation and close the WebSocket connection."""
    await websocket.send(json.dumps({
        "close_socket": True
    }))

async def receive_messages(websocket):
    """Process incoming WebSocket messages."""
    context_audio = {}
    try:
        async for message in websocket:
            data = json.loads(message)
            context_id = data.get("contextId", "default")

            if data.get("audio"):
                print(f"Received audio for context '{context_id}'")

            if data.get("is_final"):
                print(f"Context '{context_id}' completed")
    except (websockets.exceptions.ConnectionClosed, asyncio.CancelledError):
        print("Message receiving stopped")

async def conversation_agent_demo():
    """Run a complete conversational agent demo."""
    # Connect with API key in headers
    async with websockets.connect(
        WEBSOCKET_URI,
        max_size=16 * 1024 * 1024,
        additional_headers={"xi-api-key": ELEVENLABS_API_KEY}
    ) as websocket:
        # Start receiving messages in background
        receive_task = asyncio.create_task(receive_messages(websocket))

        # Initial agent response
        await send_text_in_context(
            websocket,
            "Hello! I'm your virtual assistant. I can help you with a wide range of topics. What would you like to know about today?",
            "greeting"
        )

        # Wait a bit (simulating user listening)
        await asyncio.sleep(2)

        # Simulate user interruption
        print("USER INTERRUPTS: 'Can you tell me about the weather?'")

        # Handle the interruption by closing current context and starting new one
        await handle_interruption(
            websocket,
            "greeting",
            "weather_response",
            "I'd be happy to tell you about the weather. Currently in your area, it's 72 degrees and sunny with a slight chance of rain later this afternoon."
        )

        # Add more to the weather context
        await continue_context(
            websocket,
            " If you're planning to go outside, you might want to bring a light jacket just in case.",
            "weather_response"
        )

        # Flush at the end of this turn to ensure all audio is generated
        await flush_context(websocket, "weather_response")

        # Wait a bit (simulating user listening)
        await asyncio.sleep(3)

        # Simulate user asking another question
        print("USER: 'What about tomorrow?'")

        # Create a new context for this response
        await send_text_in_context(
            websocket,
            "Tomorrow's forecast shows temperatures around 75 degrees with partly cloudy skies. It should be a beautiful day overall!",
            "tomorrow_weather"
        )

        # Flush and close this context
        await flush_context(websocket, "tomorrow_weather")
        await websocket.send(json.dumps({
            "context_id": "tomorrow_weather",
            "close_context": True
        }))

        # End the conversation
        await asyncio.sleep(2)
        await end_conversation(websocket)

        # Cancel the receive task
        receive_task.cancel()
        try:
            await receive_task
        except asyncio.CancelledError:
            pass

if __name__ == "__main__":
    asyncio.run(conversation_agent_demo())

```

```javascript maxLines=100
// Import required modules
import dotenv from "dotenv";
import fs from "fs";
import WebSocket from "ws";

// Load environment variables
dotenv.config();
const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY;
const VOICE_ID = "your_voice_id";
const MODEL_ID = "eleven_flash_v2_5";

const WEBSOCKET_URI = `wss://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}/multi-stream-input?model_id=${MODEL_ID}`;

// Function to send text in a specific context
function sendTextInContext(websocket, text, contextId, voiceSettings = null) {
  const message = {
    text: text,
    context_id: contextId,
  };

  // Only include voice_settings for the first message in a context
  if (voiceSettings) {
    message.voice_settings = voiceSettings;
  }

  websocket.send(JSON.stringify(message));
}

// Function to continue an existing context with more text
function continueContext(websocket, text, contextId) {
  websocket.send(
    JSON.stringify({
      text: text,
      context_id: contextId,
    })
  );
}

// Function to flush a context, forcing generation of buffered audio
function flushContext(websocket, contextId) {
  websocket.send(
    JSON.stringify({
      context_id: contextId,
      flush: true,
    })
  );
}

// Function to handle user interruption
function handleInterruption(websocket, oldContextId, newContextId, newResponse) {
  // Close the existing context that was interrupted
  websocket.send(
    JSON.stringify({
      context_id: oldContextId,
      close_context: true,
    })
  );

  // Create a new context for the new response
  sendTextInContext(websocket, newResponse, newContextId);
}

// Function to end the conversation and close the connection
function endConversation(websocket) {
  websocket.send(
    JSON.stringify({
      close_socket: true,
    })
  );
}

// Function to run the conversation agent demo
async function conversationAgentDemo() {
  // Connect to WebSocket with API key in headers
  const websocket = new WebSocket(WEBSOCKET_URI, {
    headers: {
      "xi-api-key": ELEVENLABS_API_KEY,
    },
    maxPayload: 16 * 1024 * 1024,
  });

  // Set up event handlers
  websocket.on("open", () => {
    // Initial agent response
    sendTextInContext(
      websocket,
      "Hello! I'm your virtual assistant. I can help you with a wide range of topics. What would you like to know about today?",
      "greeting"
    );

    // Simulate wait time (user listening)
    setTimeout(() => {
      // Simulate user interruption
      console.log("USER INTERRUPTS: 'Can you tell me about the weather?'");

      // Handle the interruption
      handleInterruption(
        websocket,
        "greeting",
        "weather_response",
        "I'd be happy to tell you about the weather. Currently in your area, it's 72 degrees and sunny with a slight chance of rain later this afternoon."
      );

      // Add more to the weather context
      setTimeout(() => {
        continueContext(
          websocket,
          " If you're planning to go outside, you might want to bring a light jacket just in case.",
          "weather_response"
        );

        // Flush at the end of this turn
        flushContext(websocket, "weather_response");

        // Simulate wait time (user listening)
        setTimeout(() => {
          // Simulate user asking another question
          console.log("USER: 'What about tomorrow?'");

          // Create a new context for this response
          sendTextInContext(
            websocket,
            "Tomorrow's forecast shows temperatures around 75 degrees with partly cloudy skies. It should be a beautiful day overall!",
            "tomorrow_weather"
          );

          // Flush and close this context
          flushContext(websocket, "tomorrow_weather");
          websocket.send(
            JSON.stringify({
              context_id: "tomorrow_weather",
              close_context: true,
            })
          );

          // End the conversation
          setTimeout(() => {
            endConversation(websocket);
          }, 2000);
        }, 3000);
      }, 500);
    }, 2000);
  });

  // Handle incoming messages
  websocket.on("message", (message) => {
    try {
      const data = JSON.parse(message);
      const contextId = data.contextId || "default";

      if (data.audio) {
        //do stuff
      }

      if (data.is_final) {
        console.log(`Context '${contextId}' completed`);
      }
    } catch (error) {
      console.error("Error parsing message:", error);
    }
  });

  // Handle WebSocket closure
  websocket.on("close", () => {
    console.log("WebSocket connection closed");
  });

  // Handle WebSocket errors
  websocket.on("error", (error) => {
    console.error("WebSocket error:", error);
  });
}

// Run the demo
conversationAgentDemo();
```

## 后续步骤

#### [ElevenAgents](/docs/zh/eleven-agents/quickstart)

使用完整的 ElevenAgents 平台构建可用于生产环境的语音智能体。

#### [了解音频流式传输](/docs/zh/eleven-api/concepts/audio-streaming)

了解 WebSocket 流式传输的底层工作原理，以及影响延迟的因素。