Aller au contenu principal

Streaming SSE

Réponses en temps réel avec Server-Sent Events

Par défaut, l’API Chat Completions attend que le modèle ait terminé sa réponse complète avant de vous la renvoyer. Pour les réponses longues, cela peut signifier plusieurs secondes d’attente. Le streaming résout ce problème en envoyant les tokens au fur et à mesure de leur génération.

Le protocole utilisé est Server-Sent Events (SSE), un standard HTTP qui permet au serveur de pousser des données vers le client en continu sur une connexion persistante.

Streaming vs non-streaming

Mode non-streaming (par défaut)

Le client envoie la requête et attend la réponse complète :

Client ──── requête ────► Serveur
         (attend 3-10s)
Client ◄── réponse complète ── Serveur

Avantages : réponse atomique, facile à traiter, un seul objet JSON. Inconvénient : latence perçue élevée — l’utilisateur ne voit rien pendant la génération.

Mode streaming

Le serveur envoie les tokens un par un (ou par petits groupes) :

Client ──── requête ────► Serveur
Client ◄── chunk 1 ─────── Serveur  (après ~200ms)
Client ◄── chunk 2 ─────── Serveur  (après ~250ms)
Client ◄── chunk 3 ─────── Serveur  (après ~300ms)
...
Client ◄── [DONE] ──────── Serveur

Avantages : latence perçue réduite drastiquement, meilleure expérience utilisateur. Inconvénient : reconstruction du message côté client, gestion de l’état plus complexe.

Le protocole SSE en détail

Chaque événement SSE a la forme suivante dans le flux HTTP :

data: {"id":"cmpl-abc","choices":[{"delta":{"content":"Bon"}}],"usage":null}

data: {"id":"cmpl-abc","choices":[{"delta":{"content":"jour"}}],"usage":null}

data: {"id":"cmpl-abc","choices":[{"delta":{"content":" !"}}],"usage":null}

data: [DONE]

Points importants :

  • Chaque ligne commence par data: suivi d’un objet JSON
  • Le champ delta (et non message) contient le fragment de texte
  • Le dernier événement est data: [DONE] pour signaler la fin du flux
  • Les lignes vides séparent les événements

Activer le streaming en Python

Avec le SDK Mistral, le streaming s’active en appelant chat.stream() au lieu de chat.complete() :

from mistralai import Mistral
import os

client = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))

stream = client.chat.stream(
    model="mistral-large-latest",
    messages=[
        {"role": "user", "content": "Expliquez le fonctionnement d'un moteur électrique."}
    ]
)

for chunk in stream:
    content = chunk.data.choices[0].delta.content
    if content:
        print(content, end="", flush=True)

print()  # Retour à la ligne final

Le flush=True force l’affichage immédiat de chaque fragment dans le terminal.

Structure d’un chunk de streaming

Chaque chunk reçu diffère légèrement d’une réponse non-streaming :

# Non-streaming : response.choices[0].message.content
# Streaming     : chunk.data.choices[0].delta.content

# Le premier chunk contient souvent le rôle
# chunk.data.choices[0].delta.role == "assistant"

# Les chunks suivants contiennent le texte par fragments
# chunk.data.choices[0].delta.content == "Bon"

# Le dernier chunk contient finish_reason
# chunk.data.choices[0].finish_reason == "stop"

Reconstruire la réponse complète

En streaming, vous devez accumuler les fragments pour obtenir la réponse finale :

from mistralai import Mistral
import os

client = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))

stream = client.chat.stream(
    model="mistral-large-latest",
    messages=[{"role": "user", "content": "Listez 5 langages de programmation."}]
)

full_response = ""
finish_reason = None

for chunk in stream:
    delta = chunk.data.choices[0].delta
    if delta.content:
        full_response += delta.content
        print(delta.content, end="", flush=True)

    if chunk.data.choices[0].finish_reason:
        finish_reason = chunk.data.choices[0].finish_reason

print(f"\n\nFinish reason : {finish_reason}")
print(f"Réponse complète ({len(full_response)} caractères)")

Quand utiliser le streaming

Cas d’usageRecommandation
Chatbot interactifStreaming (meilleure UX)
Traitement batchNon-streaming (plus simple)
API backend pour frontendStreaming (SSE vers le navigateur)
Pipeline de donnéesNon-streaming (traitement atomique)
Extraction structuréeNon-streaming (parsing JSON plus fiable)

Points clés à retenir

  • Le streaming réduit la latence perçue en envoyant les tokens au fil de la génération
  • Le protocole SSE est un standard HTTP simple et robuste
  • En streaming, utilisez delta.content au lieu de message.content
  • Accumulez les fragments côté client pour reconstruire la réponse complète
  • Privilégiez le streaming pour les interfaces utilisateur, le non-streaming pour le traitement automatisé