Aller au contenu principal
Leçon 10 sur 14 · Text-to-Speech API

Messages WebSocket : texte et audio

Leçon 10 sur 14 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Protocole d’échange de messages TTS
  2. Messages client vers serveur
  3. Messages serveur vers client
  4. Flux de communication complet
  5. Implémentation complète en Python
  6. Implémentation en JavaScript (Node.js)
  7. Gestion des deltas multiples
  8. Points clés à retenir

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) et session.update (table replace)
  • Cinq messages serveur : audio.delta (morceau audio), audio.done (fin), audio.clear (annulation confirmée), session.updated et 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é à 60 000 caractères
  • Le chaining LLM streaming vers TTS streaming offre la latence minimale