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 :
- Ouverture : connexion a
wss://api.x.ai/v1/ttsavec les parametres - Active : echange de messages
text.delta/audio.delta - Fermeture normale : le client ou le serveur ferme la connexion
- 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