Messages WebSocket : texte et audio
Mis à jour le 30 juillet 2026
Protocole d’échange de messages TTS
Une fois la connexion WebSocket établie, la communication se fait par échange de messages JSON. Vous envoyez du texte, le serveur répond avec de l’audio encodé en base64. Comprendre ce protocole est essentiel pour implémenter correctement le streaming TTS.
Messages client vers serveur
Votre application envoie deux types de messages au serveur.
text.delta : envoyer un morceau de texte
Le message text.delta envoie un fragment de texte à convertir. Vous pouvez envoyer le texte par morceaux successifs :
{"type": "text.delta", "delta": "Bonjour, bienvenue dans "}
{"type": "text.delta", "delta": "notre présentation sur la synthèse vocale."}
Chaque delta peut contenir jusqu’à 15 000 caractères. Le serveur commence à générer l’audio dès qu’il reçoit suffisamment de contexte pour produire une prononciation naturelle.
text.done : signaler la fin du texte
Le message text.done indique au serveur que vous avez fini d’envoyer du texte pour cette séquence :
{"type": "text.done"}
Après avoir reçu ce message, le serveur termine la génération audio et envoie un message audio.done. Vous pouvez ensuite recommencer à envoyer de nouveaux deltas pour une nouvelle séquence.
Messages serveur vers client
Le serveur répond avec trois types de messages.
audio.delta : morceau audio
Le message audio.delta contient un fragment audio encodé en base64 :
{
"type": "audio.delta",
"delta": "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2..."
}
Chaque delta est un morceau audio valide que vous pouvez décoder et jouer immédiatement. Les deltas arrivent au fur et à mesure de la génération.
audio.done : fin de la génération
Le message audio.done signale que tout l’audio pour la séquence de texte envoyée a été générée :
{
"type": "audio.done",
"trace_id": "550e8400-e29b-41d4-a716-446655440000"
}
Le trace_id est un identifiant unique de la séquence, utile pour le debugging et le suivi.
error : erreur
En cas de problème, le serveur envoie un message d’erreur :
{
"type": "error",
"message": "Invalid text encoding"
}
Flux de communication complet
Voici le déroulement typique d’une session :
Client Serveur
| |
|-- text.delta ("Bonjour, ") -->|
|-- text.delta ("comment ") --->|
|-- text.delta ("allez-vous?")->|
|<-- audio.delta (chunk 1) -----|
|<-- audio.delta (chunk 2) -----|
|-- text.done ----------------->|
|<-- audio.delta (chunk 3) -----|
|<-- audio.done ----------------|
Le serveur peut commencer à envoyer des audio.delta avant même que vous ayez envoyé text.done. C’est le principe du streaming : la génération commence dès que possible.
Implémentation complète en Python
import asyncio
import websockets
import json
import base64
async def stream_tts(api_key, text, voice="eve", language="fr"):
uri = f"wss://api.x.ai/v1/tts?language={language}&voice={voice}&codec=mp3"
headers = {"Authorization": f"Bearer {api_key}"}
audio_chunks = []
async with websockets.connect(uri, additional_headers=headers) as ws:
# Envoyer le texte par morceaux de 2000 caractères
chunk_size = 2000
for i in range(0, len(text), chunk_size):
chunk = text[i:i + chunk_size]
await ws.send(json.dumps({
"type": "text.delta",
"delta": chunk
}))
# Signaler la fin du texte
await ws.send(json.dumps({"type": "text.done"}))
# Recevoir les morceaux audio
async for message in ws:
data = json.loads(message)
if data["type"] == "audio.delta":
audio_bytes = base64.b64decode(data["delta"])
audio_chunks.append(audio_bytes)
elif data["type"] == "audio.done":
print(f"Generation terminee (trace: {data['trace_id']})")
break
elif data["type"] == "error":
raise Exception(f"Erreur TTS: {data['message']}")
# Assembler l'audio complet
return b"".join(audio_chunks)
Implémentation en JavaScript (Node.js)
const WebSocket = require("ws");
function streamTTS(apiKey, text, voice = "eve", language = "fr") {
return new Promise((resolve, reject) => {
const ws = new WebSocket(
`wss://api.x.ai/v1/tts?language=${language}&voice=${voice}&codec=mp3`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
const audioChunks = [];
ws.on("open", () => {
// Envoyer le texte
ws.send(JSON.stringify({ type: "text.delta", delta: text }));
ws.send(JSON.stringify({ type: "text.done" }));
});
ws.on("message", (raw) => {
const data = JSON.parse(raw);
if (data.type === "audio.delta") {
audioChunks.push(Buffer.from(data.delta, "base64"));
} else if (data.type === "audio.done") {
ws.close();
resolve(Buffer.concat(audioChunks));
} else if (data.type === "error") {
ws.close();
reject(new Error(data.message));
}
});
ws.on("error", reject);
});
}
Gestion des deltas multiples
Pour les textes générés progressivement (par exemple la sortie d’un LLM), envoyez chaque token directement au WebSocket TTS :
async def llm_to_speech(ws_tts, llm_stream):
async for token in llm_stream:
await ws_tts.send(json.dumps({
"type": "text.delta",
"delta": token
}))
await ws_tts.send(json.dumps({"type": "text.done"}))
Cette architecture “LLM streaming vers TTS streaming” offre la latence la plus basse possible : l’utilisateur entend la réponse pendant que le LLM continue à générer.
Points clés à retenir
- Deux messages client :
text.delta(envoyer du texte) ettext.done(fin du texte) - Trois messages serveur :
audio.delta(morceau audio),audio.done(fin),error - L’audio est encodé en base64 dans les messages
audio.delta - Le serveur commence à générer avant de recevoir
text.done - Chaque delta de texte est limité à 15 000 caractères
- Le chaining LLM streaming vers TTS streaming offre la latence minimale