Aller au contenu principal

Gestion des sessions WebSocket

Maitriser le cycle de vie des sessions

En production, la gestion robuste des sessions WebSocket est ce qui separe un prototype d’un produit fiable. Les sessions TTS ont des contraintes specifiques (TTL, limites de concurrence) que votre application doit gerer correctement.

Cycle de vie d’une session

Une session WebSocket TTS suit ce cycle :

  1. Ouverture : connexion a wss://api.x.ai/v1/tts avec les parametres
  2. Active : echange de messages text.delta/audio.delta
  3. Fermeture normale : le client ou le serveur ferme la connexion
  4. Fermeture forcee : expiration du TTL (600 secondes)

Detection de la fermeture

Le serveur peut fermer la connexion a tout moment (TTL expire, erreur interne, maintenance). Votre application doit toujours gerer l’evenement de fermeture :

async def managed_session(api_key, language="fr", voice="eve"):
    uri = f"wss://api.x.ai/v1/tts?language={language}&voice={voice}"
    headers = {"Authorization": f"Bearer {api_key}"}

    try:
        async with websockets.connect(uri, extra_headers=headers) as ws:
            yield ws
    except websockets.ConnectionClosed as e:
        print(f"Connexion fermee: code={e.code}, raison={e.reason}")
    except websockets.ConnectionClosedError:
        print("Connexion perdue inopinement")

Reconnexion automatique

Pour les applications longue duree, implementez un mecanisme de reconnexion avec backoff exponentiel :

import asyncio
import random

class TTSSessionManager:
    def __init__(self, api_key, language="fr", voice="eve"):
        self.api_key = api_key
        self.language = language
        self.voice = voice
        self.ws = None
        self.max_retries = 5

    async def connect(self):
        uri = f"wss://api.x.ai/v1/tts?language={self.language}&voice={self.voice}"
        headers = {"Authorization": f"Bearer {self.api_key}"}

        for attempt in range(self.max_retries):
            try:
                self.ws = await websockets.connect(uri, extra_headers=headers)
                print(f"Connecte (tentative {attempt + 1})")
                return
            except Exception as e:
                wait = min(2 ** attempt + random.random(), 30)
                print(f"Echec connexion: {e}. Retry dans {wait:.1f}s")
                await asyncio.sleep(wait)

        raise ConnectionError("Impossible de se connecter apres 5 tentatives")

    async def ensure_connected(self):
        if self.ws is None or self.ws.closed:
            await self.connect()

    async def synthesize(self, text):
        await self.ensure_connected()

        try:
            await self.ws.send(json.dumps({
                "type": "text.delta",
                "delta": text
            }))
            await self.ws.send(json.dumps({"type": "text.done"}))

            audio_chunks = []
            async for msg in self.ws:
                data = json.loads(msg)
                if data["type"] == "audio.delta":
                    audio_chunks.append(base64.b64decode(data["delta"]))
                elif data["type"] == "audio.done":
                    break
                elif data["type"] == "error":
                    raise Exception(data["message"])

            return b"".join(audio_chunks)

        except websockets.ConnectionClosed:
            # Reconnexion et retry
            await self.connect()
            return await self.synthesize(text)

Gestion du TTL

Le TTL de 600 secondes commence au moment de la connexion, pas au dernier message. Votre application doit anticiper l’expiration :

import time

class TTLAwareSession:
    TTL = 600  # secondes
    REFRESH_MARGIN = 60  # reconnecter 60s avant expiration

    def __init__(self):
        self.connected_at = None

    async def connect(self):
        # ... connexion ...
        self.connected_at = time.time()

    def should_refresh(self):
        if self.connected_at is None:
            return True
        elapsed = time.time() - self.connected_at
        return elapsed >= (self.TTL - self.REFRESH_MARGIN)

    async def synthesize(self, text):
        if self.should_refresh():
            await self.connect()  # Nouvelle session
        # ... envoi du texte ...

En vous reconnectant 60 secondes avant l’expiration, vous evitez les coupures en pleine synthese.

Pool de sessions

Pour les applications a fort trafic, maintenez un pool de sessions reutilisables :

class SessionPool:
    def __init__(self, api_key, pool_size=10):
        self.api_key = api_key
        self.pool_size = min(pool_size, 50)  # Max 50 par equipe
        self.available = asyncio.Queue()
        self.active_count = 0

    async def acquire(self):
        if not self.available.empty():
            session = await self.available.get()
            if not session.should_refresh():
                return session

        if self.active_count < self.pool_size:
            session = TTSSessionManager(self.api_key)
            await session.connect()
            self.active_count += 1
            return session

        # Attendre qu'une session se libere
        return await self.available.get()

    async def release(self, session):
        await self.available.put(session)

Limitez la taille du pool a une fraction de la limite de 50 sessions pour laisser de la marge aux autres services de votre application.

Metriques a surveiller

Pour une gestion saine des sessions en production, surveillez :

  • Nombre de sessions actives : ne doit pas depasser 50
  • Taux de reconnexion : un taux eleve indique un probleme reseau ou de TTL
  • Latence du premier audio.delta : mesure la reactivite du service
  • Erreurs WebSocket : codes de fermeture anormaux (1006, 1011)
class MetricsCollector:
    def __init__(self):
        self.connections = 0
        self.reconnections = 0
        self.errors = 0
        self.first_audio_latencies = []

    def record_connection(self):
        self.connections += 1

    def record_reconnection(self):
        self.reconnections += 1

    def record_first_audio(self, latency_ms):
        self.first_audio_latencies.append(latency_ms)

Points cles a retenir

  • Le TTL d’une session est de 600 secondes a partir de la connexion
  • Implementez la reconnexion automatique avec backoff exponentiel
  • Rafraichissez la session avant l’expiration du TTL (marge de 60 secondes)
  • Utilisez un pool de sessions pour les applications a fort trafic
  • Respectez la limite de 50 sessions concurrentes par equipe
  • Surveillez les metriques de connexion en production