Le streaming des réponses
Sur cette page
- Le problème d’expérience utilisateur
- Comment ça marche : les événements SSE
- Implémentation basique avec stream=True
- La méthode simplifiée : client.messages.stream()
- Récupérer le message complet après streaming
- Gérer les événements individuels
- Quand utiliser le streaming
- Résumé
- À vous de jouer
- Points clés à retenir
Le problème d’expérience utilisateur
Quand vous envoyez une requête à Claude, le modèle peut mettre entre 10 et 30 secondes à générer sa réponse complète. Sans streaming, votre utilisateur fixe un écran vide pendant tout ce temps, sans savoir si quelque chose se passe.
Le streaming résout ce problème : au lieu d’attendre la réponse entière, vous recevez les tokens au fur et à mesure qu’ils sont générés. L’utilisateur voit le texte apparaître progressivement, exactement comme dans l’interface chat de Claude.
Comment ça marche : les événements SSE
Le streaming ne rend pas la génération plus rapide — le dernier token arrive au même moment dans les deux modes. Ce qu’il change, c’est le premier token : l’utilisateur voit la réponse commencer en une seconde au lieu de fixer un écran vide pendant vingt. À vitesse égale, la perception est radicalement différente, et c’est cette perception qui fait la qualité d’une interface conversationnelle.
Techniquement, le streaming utilise le protocole Server-Sent Events (SSE). Le serveur envoie un flux d’événements structurés, chacun contenant une partie de la réponse.
Voici les types d’événements dans l’ordre où ils arrivent :
| Événement | Description |
|---|---|
message_start | Début du message, contient les métadonnées (id, modèle, usage) |
content_block_start | Début d’un bloc de contenu (texte, outil, etc.) |
content_block_delta | Fragment du bloc en cours : text_delta pour le texte, thinking_delta et signature_delta pour la réflexion, input_json_delta pour un appel d’outil |
content_block_stop | Fin du bloc de contenu |
message_delta | Métadonnées de fin (stop_reason, usage de sortie) |
message_stop | Fin du message |
Un message typique produit cette séquence :
Sur Sonnet 5.5, qui réfléchit par défaut, un bloc de réflexion passe souvent avant le bloc de texte, avec son texte vide par défaut. Des événements ping peuvent s’intercaler, et une erreur (par exemple overloaded_error en période de forte charge) arrive dans le flux sous forme d’événement error.
Implémentation basique avec stream=True
La façon la plus directe d’activer le streaming est de passer stream=True à messages.create. On reçoit alors un itérateur d’événements, que le cours officiel affiche tels quels pour les découvrir :
from anthropic import AnthropicVertex
client = AnthropicVertex(project_id="votre-projet", region="global")
model = "claude-sonnet-5-5"
stream = client.messages.create(
model=model,
max_tokens=4096,
messages=[{"role": "user", "content": "Décris en une phrase une base de données imaginaire."}],
stream=True,
)
for event in stream:
if event.type == "content_block_delta" and event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
Le filtre sur event.delta.type est indispensable avec les modèles actuels : les deltas de réflexion n’ont pas d’attribut text. Le paramètre flush=True force l’affichage immédiat de chaque fragment, sans attendre un saut de ligne.
La méthode simplifiée : client.messages.stream()
Le SDK Python fournit un context manager qui simplifie considérablement le streaming. Plutôt que de gérer chaque événement SSE manuellement, vous utilisez stream.text_stream :
from anthropic import AnthropicVertex
client = AnthropicVertex(project_id="votre-projet", region="global")
with client.messages.stream(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[
{"role": "user", "content": "Écris un court poème sur la mer."}
]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print() # Saut de ligne final
stream.text_stream est un itérateur qui vous donne uniquement les fragments de texte, sans vous encombrer avec les événements de structure. C’est le bon niveau d’abstraction pour 90 % des usages : la mécanique SSE de la section précédente reste utile à comprendre — elle explique ce que le SDK fait pour vous — mais vous n’aurez presque jamais à la manipuler directement (start, stop, delta).
Récupérer le message complet après streaming
Parfois, vous avez besoin à la fois du streaming (pour l’affichage) et du message complet (pour le traiter ensuite). La méthode get_final_message() vous donne le message reconstitue après la fin du flux :
from anthropic import AnthropicVertex
client = AnthropicVertex(project_id="votre-projet", region="global")
with client.messages.stream(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[
{"role": "user", "content": "Donne-moi 3 idées de projets Python pour débutants."}
]
) as stream:
# Affichage en temps réel
for text in stream.text_stream:
print(text, end="", flush=True)
# Récupération du message complet
message = stream.get_final_message()
print()
print(f"\nTokens d'entrée : {message.usage.input_tokens}")
print(f"Tokens de sortie : {message.usage.output_tokens}")
print(f"Raison d'arrêt : {message.stop_reason}")
Le message retourné est identique à ce que vous auriez obtenu sans streaming : c’est un objet Message complet avec tout le contenu, l’usage de tokens, et les métadonnées.
Gérer les événements individuels
Si vous avez besoin d’un contrôle plus fin (par exemple pour mettre à jour une barre de progression ou détecter le début d’un bloc outil), vous pouvez écouter chaque type d’événement :
from anthropic import AnthropicVertex
client = AnthropicVertex(project_id="votre-projet", region="global")
with client.messages.stream(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[
{"role": "user", "content": "Explique la photosynthèse."}
]
) as stream:
for event in stream:
if event.type == "content_block_start":
print(f"[Début du bloc {event.content_block.type}]")
elif event.type == "content_block_delta":
if event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
elif event.type == "content_block_stop":
print("\n[Fin du bloc]")
elif event.type == "message_stop":
print("\n[Message terminé]")
Quand utiliser le streaming
- Applications interactives : chatbots, assistants, interfaces conversationnelles
- Réponses longues : articles, analyses détaillées, génération de code
- Feedback utilisateur : montrer que le système travaille
Le streaming n’ajoute aucun coût supplémentaire et ne change pas le contenu de la réponse. La seule vraie contrepartie est architecturale : votre code aval doit accepter de travailler avec une réponse incomplète, ou attendre get_final_message(). C’est pourquoi le réflexe utile est de streamer vers l’affichage et de traiter sur le message final — chaque consommateur reçoit la forme qui lui convient. C’est purement une amélioration de l’expérience utilisateur.
Résumé
| Approche | Usage |
|---|---|
client.messages.stream() + text_stream | Cas général, simple et efficace |
Itération sur stream (événements bruts) | Contrôle fin sur chaque événement |
stream.get_final_message() | Récupérer le message complet après streaming |
C’est aussi ce que montre le cours officiel : stream=True pour découvrir les événements, puis client.messages.stream() et text_stream pour l’affichage, et get_final_message() pour enregistrer le message complet, par exemple en base de données. La documentation conseille aussi le streaming pour toute requête longue : le SDK refuse une requête non diffusée dont la durée estimée dépasse dix minutes.
À vous de jouer
Voir les événements. Lancez l’exemple stream=True en affichant event.type pour chaque événement : repérez le bloc de réflexion, le bloc de texte, et le message_delta qui porte stop_reason.
Streamer et enregistrer. Écrivez une fonction stream_and_save(messages) qui affiche la réponse au fil de l’eau avec text_stream, puis renvoie get_final_message() ; ajoutez ce message à l’historique d’une conversation.
Sources officielles : Response streaming (Claude Academy) · Streaming messages (documentation Claude : types d’événements et de deltas)
Points clés à retenir
- Le streaming n’accélère pas la génération, il fait arriver le premier fragment tout de suite.
- Ordre des événements :
message_start, blocs de contenu (start, deltas, stop),message_delta,message_stop;pingeterrorpeuvent s’intercaler. - Avec
stream=True, on filtre lestext_delta;client.messages.stream()ettext_streamfont ce tri. get_final_message()rend le message complet pour l’enregistrer ou le traiter.