WebSocket multi-contexte

Ce guide explique comment créer des agents vocaux en temps réel avec l’API WebSocket multi-contexte.

Avancé

L’orchestration d’agents vocaux avec cette API WebSocket multi-contexte est une tâche complexe, recommandée aux développeurs expérimentés. Pour une solution davantage gérée, consultez notre produit Agents Platform, qui simplifie bon nombre de ces difficultés.

Vue d’ensemble

La création d’agents vocaux réactifs exige de pouvoir gérer dynamiquement les flux audio, traiter les interruptions avec fluidité et préserver une voix naturelle tout au long des tours de conversation. Notre API WebSocket multi-contexte pour Text to Speech (TTS) est spécifiquement conçue pour ces scénarios.

Cette API étend notre fonctionnalité WebSocket TTS standard en introduisant le concept de « contextes ». Chaque contexte fonctionne comme un flux indépendant de génération audio au sein d’une même connexion WebSocket. Vous pouvez ainsi :

  • Gérer plusieurs lignes de parole simultanément, par exemple un agent qui parle tout en préparant une réponse à l’interruption d’un utilisateur.
  • Gérer sans rupture les interventions des utilisateurs en fermant un contexte de parole existant et en en démarrant un nouveau.
  • Préserver la cohérence prosodique des énoncés au sein d’un même contexte logique.
  • Optimiser l’utilisation des ressources en fermant sélectivement les contextes qui ne sont plus nécessaires.

L’API WebSocket multi-contexte est optimisée pour les applications vocales et n’est pas destinée à générer simultanément plusieurs flux audio sans lien entre eux. Chaque connexion est donc limitée à 5 contextes simultanés.

Ce guide vous accompagne pour vous connecter au WebSocket multi-contexte, gérer les contextes et appliquer les bonnes pratiques de création d’agents vocaux engageants.

Bonnes pratiques

Ces bonnes pratiques sont essentielles pour créer des agents vocaux réactifs et efficaces avec notre API WebSocket multi-contexte.

1

Utiliser une seule connexion WebSocket

Établissez une connexion WebSocket pour chaque session d’utilisateur final. Cela réduit la surcharge et la latence par rapport à la création de plusieurs connexions. Au sein de cette même connexion, vous pouvez gérer plusieurs contextes pour différentes parties de la conversation.

2

Diffuser les réponses par fragments, générer des phrases

Pour générer des réponses longues, diffusez le texte en petits fragments et utilisez l’indicateur flush: true à la fin des phrases complètes. Cela améliore la qualité de l’audio généré et la réactivité.

3

Gérer les interruptions avec fluidité

Diffusez le texte dans un contexte jusqu’à ce qu’une interruption survienne, puis créez un nouveau contexte et fermez celui qui existe. Cette approche garantit des transitions fluides lorsque le fil de la conversation change.

4

Gérer le cycle de vie des contextes

Fermez rapidement les contextes inutilisés. Le serveur peut maintenir jusqu’à 5 contextes simultanés par connexion, mais vous devez fermer les contextes lorsqu’ils ne sont plus nécessaires.

5

Éviter l’expiration des contextes

Par défaut, les contextes expirent après 20 secondes et sont fermés automatiquement. Le délai d’inactivité est un paramètre au niveau du WebSocket qui s’applique à tous les contextes et peut aller jusqu’à 180 secondes si nécessaire. Envoyez un message texte vide dans un contexte pour réinitialiser le délai d’expiration.

Gestion des interruptions

Lorsqu’un utilisateur interrompt votre agent, vous devez fermer le contexte actuel et en créer un nouveau :

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)

Maintenir un contexte actif

Les contextes expirent automatiquement après 20 secondes d’inactivité par défaut. Si vous devez maintenir un contexte actif sans générer de texte, par exemple pendant un délai de traitement, vous pouvez envoyer un message texte vide pour réinitialiser le délai d’expiration.

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

Fermer la connexion WebSocket

Lorsque votre conversation se termine, vous pouvez nettoyer tous les contextes en fermant le socket :

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")`

Exemple complet d’agent conversationnel

Prérequis

  • Un compte ElevenLabs avec une clé API, découvrez comment trouver votre clé API.
  • Python ou Node.js, ou un autre environnement d’exécution JavaScript, installé sur votre machine.
  • Une connaissance des communications WebSocket. Nous recommandons de lire notre guide du streaming WebSocket standard pour en maîtriser les concepts fondamentaux.

Configuration

Installez les dépendances nécessaires pour le langage de votre choix :

pip install python-dotenv websockets

Créez un fichier .env dans le répertoire de votre projet afin d’y stocker votre clé API :

.env
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here

Exemple d’agent vocal

Ce code est fourni à titre d’exemple et n’est pas destiné à une utilisation en production
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())

Étapes suivantes