Messages WebSocket : texte et audio
Sur cette page
Leçon corrigée le 3 novembre 2026 d’après la page Text to Speech de xAI : deltas de 60 000 caractères et non 15 000 ; ajout de l’annulation (
text.clear,audio.clear) et de la table de prononciation (session.update,session.updated), absentes de l’ancienne version. Source : Text to Speech.
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 quatre types de messages au serveur : text.delta et text.done pour le texte, text.clear pour annuler, session.update pour configurer.
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’à 60 000 caractères, et le texte total n’a pas de limite. 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.
text.clear : annuler la séquence en cours
text.clear annule la séquence en cours : le serveur arrête de générer l’audio, jette ce qu’il avait en mémoire et répond par audio.clear. La connexion est aussitôt prête pour la séquence suivante, sans nouvelle poignée de main WebSocket — xAI chiffre ce gain à environ 600 ms pour un client éloigné. C’est le mécanisme d’interruption (« barge-in ») quand l’utilisateur coupe la parole.
{"type": "text.clear"}
session.update : corriger la prononciation
session.update installe, ou remplace, la table replace de la connexion : chaque expression trouvée dans le texte est remplacée par sa forme parlée avant la synthèse, sans changer le texte envoyé ni le nombre de caractères facturés. Envoyez-la avant le premier text.delta pour qu’elle couvre toute la connexion ; une mise à jour reçue en cours de séquence ne s’applique qu’à la séquence suivante.
{"type": "session.update", "replace": {"Corsen": "Corsène"}}
Messages serveur vers client
Le serveur répond avec cinq types de messages : audio.delta, audio.done et error, détaillés ci-dessous, plus audio.clear, qui confirme une annulation, et session.updated, qui renvoie la table replace en vigueur.
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
- Quatre messages client :
text.delta(envoyer du texte),text.done(fin du texte),text.clear(annuler) etsession.update(tablereplace) - Cinq messages serveur :
audio.delta(morceau audio),audio.done(fin),audio.clear(annulation confirmée),session.updatedeterror - 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é à 60 000 caractères
- Le chaining LLM streaming vers TTS streaming offre la latence minimale