SSE : streamer la progression et les tokens
Pourquoi SSE (et pas WebSocket)
Le copilot et le pipeline streament du serveur vers le client : tokens du LLM, progression node par node. Le besoin est unidirectionnel â Server-Sent Events : du HTTP simple, compatible proxys/CDN, reconnexion native â lĂ oĂč WebSocket paie sa bidirectionnalitĂ© inutile ici. Le front envoie ses actions par des POST classiques.
Le format et le générateur
SSE = une réponse text/event-stream qui ne se termine pas, découpée en blocs event:/data: séparés par une ligne vide :
event: node_completed
data: {"node": "matcher_fast", "matched": 12}
event: text_delta
data: {"text": "Voici les produits"}
CÎté FastAPI : un générateur async branché sur StreamingResponse :
async def event_stream(run) -> AsyncIterator[str]:
async for ev in run: # â ex. graph.astream(...) (module 6)
yield f"event: {ev.type}\ndata: {json.dumps(ev.payload)}\n\n"
@router.post("/extract/stream")
async def extract_stream(...):
return StreamingResponse(event_stream(run), media_type="text/event-stream")
Le contrat d'événements : la vraie difficulté
Le flux SSE est une API publique : le front se cĂąble sur les noms d'Ă©vĂ©nements et la forme des payloads. Dans le projet, les Ă©vĂ©nements suivent le protocole AG-UI (module 8) et les noms des nodes du pipeline (persist_documentâŠ) sont un contrat stable â renommer un node casse la barre de progression du front. Traite tes Ă©vĂ©nements de stream comme des routes : nommĂ©s, documentĂ©s, versionnĂ©s.
Deux dĂ©tails d'ops Ă connaĂźtre : un ping pĂ©riodique garde les connexions vivantes Ă travers les proxys ; et si le client part, le gĂ©nĂ©rateur reçoit une annulation â le code doit y survivre proprement (fermer la gĂ©nĂ©ration LLM, pas la laisser tourner pour personne).
Pourquoi SSE plutĂŽt que WebSocket ici ?