Moteur de Synthèse Vocale ElevenLabs
Ajoutez une interface vocale en temps réel à un agent custom alimenté par un LLM. ElevenLabs gère l'audio du microphone, la reconnaissance vocale, la gestion des tours de parole, la synthèse vocale et la lecture dans le navigateur ; votre serveur expose un endpoint WebSocket Speech Engine et renvoie le texte de la réponse LLM en streaming.
Configuration : Consultez le guide d'installation. Pour JavaScript, utilisez uniquement les packages
@elevenlabs/*. Pour plus de détails sur le SDK, lisez la référence SDK JavaScript ou la référence SDK Python.
Quand l'utiliser
Utilisez Speech Engine quand l'utilisateur souhaite :
- Ajouter la voix à une application de chat existante ou à un pipeline LLM custom
- Ajouter la voix à OpenClaw, Hermes ou un runtime d'agent similaire tout en gardant la logique de l'agent sur le serveur du développeur
- Construire un serveur WebSocket hébergé par le développeur pour des conversations vocales ElevenLabs
- Renvoyer les réponses OpenAI, Anthropic, Gemini ou LLM custom en audio parlé
- Gérer les interruptions utilisateur pendant le streaming d'une réponse LLM
- Construire un client navigateur avec
@elevenlabs/reactou@elevenlabs/clienten utilisant un token de conversation émis par le serveur
Utilisez plutôt la skill agents quand l'utilisateur crée ou configure un agent IA conversationnel ElevenLabs hébergé avec des prompts gérés par la plateforme, des outils, des workflows, des numéros de téléphone ou des widgets.
Comment ça marche
Chaque connexion WebSocket Speech Engine représente une conversation.
- Le navigateur envoie l'audio utilisateur à ElevenLabs.
- ElevenLabs transcrit la parole et envoie la transcription complète à votre serveur.
- Votre serveur appelle le LLM avec cet historique de conversation.
- Votre serveur renvoie du texte en streaming par le SDK.
- ElevenLabs convertit la réponse en parole et la joue dans le navigateur.
Le SDK gère le routage WebSocket, la vérification des requêtes, le cycle de vie de la session, le ping/pong, la gestion des tours de parole et des interruptions. sendResponse() / send_response() accepte une chaîne, un itérable asynchrone ou les streams des fournisseurs OpenAI, Anthropic ou Google Gemini.
Flux d'implémentation
- Installez les dépendances serveur et configurez
ELEVENLABS_API_KEYplus la clé du fournisseur LLM. - Exposez votre serveur Speech Engine via une URL HTTPS publique pour le développement local, par exemple avec
ngrok http 3001. - Créez une ressource Speech Engine avec
ws_url/wsUrlpointant vers l'URL publique plus/ws. - Stockez l'ID Speech Engine retourné, par exemple dans
ELEVENLABS_SPEECH_ENGINE_ID. - Démarrez un serveur Speech Engine avec
engine.serve(...)en Python ouspeechEngine.attach(...)en TypeScript. - Émettez des tokens de conversation navigateur depuis un endpoint serveur. Ne mettez jamais
ELEVENLABS_API_KEYdans le code navigateur. - Démarrez la session client avec
conversationToken; définissez optionnellementoverrides.agent.firstMessagesi l'agent doit saluer en premier.
Créer un Speech Engine
Python
import asyncio
import os
from dotenv import load_dotenv
from elevenlabs import AsyncElevenLabs
load_dotenv()
elevenlabs = AsyncElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
async def main():
engine = await elevenlabs.speech_engine.create(
name="My Speech Engine",
speech_engine={"ws_url": os.environ["PUBLIC_WS_URL"]},
)
print(engine.engine_id)
asyncio.run(main())
TypeScript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
const engine = await elevenlabs.speechEngine.create({
name: "My Speech Engine",
speechEngine: { wsUrl: process.env.PUBLIC_WS_URL! },
});
console.log(engine.engineId);
PUBLIC_WS_URL devrait ressembler à https://example.ngrok.app/ws localement ou à votre route WebSocket de production au déploiement.
La requête create peut aussi configurer tts, asr, turn, speech_engine.request_headers / speechEngine.requestHeaders et privacy pour des voix custom, des mots-clés de transcription, la gestion des tours de parole, les en-têtes d'authentification serveur et le comportement d'enregistrement. Consultez les fichiers de référence SDK pour des exemples détaillés.
Exemples de serveur
Python
import asyncio
import os
from dotenv import load_dotenv
from elevenlabs import AsyncElevenLabs
from openai import AsyncOpenAI
load_dotenv()
elevenlabs = AsyncElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
openai = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
async def on_transcript(transcript, session):
stream = await openai.responses.create(
model=os.environ["OPENAI_MODEL"],
instructions="You are a concise, conversational voice assistant.",
input=[
{
"role": "assistant" if message.role == "agent" else message.role,
"content": message.content,
}
for message in transcript
],
stream=True,
)
await session.send_response(stream)
async def main():
engine = await elevenlabs.speech_engine.get(os.environ["ELEVENLABS_SPEECH_ENGINE_ID"])
await engine.serve(
port=3001,
path="/ws",
debug=True,
on_transcript=on_transcript,
)
asyncio.run(main())
TypeScript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { createServer } from "node:http";
import OpenAI from "openai";
import "dotenv/config";
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const httpServer = createServer();
await elevenlabs.speechEngine.attach(
process.env.ELEVENLABS_SPEECH_ENGINE_ID!,
httpServer,
"/ws",
{
debug: true,
async onTranscript(transcript, signal, session) {
const response = await openai.responses.create(
{
model: process.env.OPENAI_MODEL!,
instructions: "You are a concise, conversational voice assistant.",
input: transcript.map((message) => ({
role: message.role === "agent" ? "assistant" : message.role,
content: message.content,
})),
stream: true,
},
{ signal },
);
session.sendResponse(response);
},
},
);
httpServer.listen(3001);
En TypeScript, passez le AbortSignal de onTranscript à la requête LLM pour que les interruptions utilisateur annulent la réponse en cours. En Python, le SDK annule le gestionnaire de transcription précédent quand une nouvelle transcription arrive.
Client navigateur
Créez un endpoint token côté serveur et faites en sorte que le navigateur demande un token avant de démarrer la session microphone. Gardez l'ID Speech Engine et la clé API sur le serveur.
import express from "express";
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";
const app = express();
const elevenlabs = new ElevenLabsClient();
app.get("/api/token", async (_req, res) => {
const response = await elevenlabs.conversationalAi.conversations.getWebrtcToken({
agentId: process.env.ELEVENLABS_SPEECH_ENGINE_ID!,
});
res.json({ token: response.token });
});
Les clients React peuvent utiliser @elevenlabs/react :
import { useConversation } from "@elevenlabs/react";
export function VoiceControls() {
const conversation = useConversation({
onConnect: () => console.log("connected"),
onDisconnect: () => console.log("disconnected"),
onError: (error) => console.error(error),
});
async function startConversation() {
await navigator.mediaDevices.getUserMedia({ audio: true });
const { token } = await fetch("/api/token").then((res) => res.json());
await conversation.startSession({
conversationToken: token,
overrides: {
agent: { firstMessage: "Hello! How can I help you today?" },
},
});
}
return <button onClick={startConversation}>Start conversation</button>;
}
Si une session navigateur WebRTC s'arrête ou affiche des logs /rtc/v1 404, v1 RTC path not found ou could not establish pc connection, épinglez livekit-client à 2.16.1 dans le package.json de l'app jusqu'à ce que le problème de compatibilité LiveKit en amont soit résolu :
{
"overrides": {
"livekit-client": "2.16.1"
}
}
Callbacks et événements
| Événement | Callback TypeScript | Callback Python | Notes |
|---|---|---|---|
user_transcript |
onTranscript(transcript, signal, session) |
on_transcript(transcript, session) |
Historique de conversation complet pour le tour actuel |
init |
onInit(conversationId, session) |
on_init(conversation_id, session) |
L'ID de conversation devient disponible |
close |
onClose(session) |
on_close(session) |
Déconnexion propre |
disconnected |
onDisconnect(session) |
on_disconnect(session) |
Chute WebSocket inattendue |
error |
onError(error, session) |
on_error(error, session) |
Erreur de protocole ou WebSocket |
Les messages de transcription utilisent le rôle "user" ou "agent". Mappez "agent" à "assistant" quand vous passez l'historique à des APIs LLM qui s'attendent à des rôles de style OpenAI.