Gestion des sessions WebSocket
Mis à jour le 30 juillet 2026
Maîtriser le cycle de vie des sessions
En production, la gestion robuste des sessions WebSocket est ce qui sépare un prototype d’un produit fiable. Les sessions TTS ont des contraintes spécifiques (TTL, limites de concurrence) que votre application doit gérer correctement.
Cycle de vie d’une session
Une session WebSocket TTS suit ce cycle :
- Ouverture : connexion à
wss://api.x.ai/v1/ttsavec les paramètres - Active : échange de messages
text.delta/audio.delta - Fermeture normale : le client ou le serveur ferme la connexion
- Fermeture forcée : expiration du TTL (600 secondes)
Détection de la fermeture
Le serveur peut fermer la connexion à tout moment (TTL expiré, erreur interne, maintenance). Votre application doit toujours gérer l’événement 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, additional_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 durée, implémentez un mécanisme 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, additional_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 évitez les coupures en pleine synthèse.
Pool de sessions
Pour les applications à fort trafic, maintenez un pool de sessions réutilisables :
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 à une fraction de la limite de 50 sessions pour laisser de la marge aux autres services de votre application.
Métriques à surveiller
Pour une gestion saine des sessions en production, surveillez :
- Nombre de sessions actives : ne doit pas dépasser 50
- Taux de reconnexion : un taux élevé indique un problème réseau ou de TTL
- Latence du premier audio.delta : mesure la réactivité 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 clés à retenir
- Le TTL d’une session est de 600 secondes à partir de la connexion
- Implémentez la reconnexion automatique avec backoff exponentiel
- Rafraîchissez la session avant l’expiration du TTL (marge de 60 secondes)
- Utilisez un pool de sessions pour les applications à fort trafic
- Respectez la limite de 50 sessions concurrentes par équipe
- Surveillez les métriques de connexion en production