Le streaming permet de recevoir la réponse de Claude progressivement au lieu d’attendre la génération complète du message.
Pour une application interactive, c’est très utile :
- le premier texte apparaît plus vite ;
- l’utilisateur voit la réponse se construire ;
- la latence perçue diminue ;
- les longues générations deviennent plus agréables à suivre.
Mais en production, le streaming introduit une contrainte importante :
Une réponse streamée n’est pas une simple chaîne de caractères reçue morceau par morceau.
Claude envoie une succession d’événements structurés que l’application doit correctement interpréter et assembler.
Cette distinction devient particulièrement importante avec :
- les
content blocks; - les
tool_use; - les
thinking blocks; - les interruptions réseau.
1. Pourquoi le Streaming est différent d’une réponse classique
Sans streaming, l’application envoie une requête et reçoit une réponse complète.
Conceptuellement :
Application
↓
Claude
↓
Message complet
Avec streaming :
Application
↓
Claude
↓
event
event
event
event
event
↓
Message complet
L’application doit reconstruire le message à partir de ces événements.
Elle doit donc savoir :
- quand un message commence ;
- quand un
content blockcommence ; - comment accumuler ses fragments ;
- quand un bloc est terminé ;
- quand le message complet est terminé.
2. Les principaux événements du Streaming
Le module présente la séquence suivante :
message_start
content_block_start
content_block_delta
content_block_stop
message_delta
message_stop
Chacun possède un rôle précis.
3. message_start
message_start indique qu’un nouveau message assistant commence.
Conceptuellement :
message_start
À ce moment, l’application peut initialiser sa structure locale :
message = {
"content": []
}
Mais le message est loin d’être complet.
4. content_block_start
Une réponse Claude peut contenir plusieurs content blocks.
Par exemple :
assistant
├── text
├── tool_use
└── text
ou :
assistant
├── thinking
├── text
└── tool_use
content_block_start indique le début d’un nouveau bloc.
L’événement fournit notamment un index permettant de savoir où ce bloc se situe dans le tableau content.
Conceptuellement :
content_block_start
index = 0
type = text
L’application crée alors ce bloc dans sa représentation locale.
5. content_block_delta
Le contenu du bloc arrive ensuite progressivement via des événements :
content_block_delta
Pour du texte, on peut recevoir :
"Bonjour"
puis :
", voici"
puis :
" le résultat."
L’application assemble :
Bonjour, voici le résultat.
Conceptuellement :
buffer += delta
6. Le même mécanisme s’applique aux données structurées
Un tool_use peut également arriver progressivement.
Supposons que Claude souhaite appeler :
search_knowledge_base
avec :
{
"query": "authentication migration"
}
Les données peuvent être reçues en plusieurs fragments.
Par exemple :
{"query":
puis :
"authentication
puis :
migration"}
Il serait donc dangereux de tenter d’exécuter le tool dès le premier fragment.
7. Ne jamais agir sur un Tool Use partiel
C’est une règle essentielle.
Supposons que l’application reçoive :
{"path": "/prod
et tente immédiatement de parser ou d’exécuter l’appel.
Le JSON est incomplet.
Le résultat final pourrait être :
{
"path": "/production/config.json"
}
Il faut donc attendre la fin du bloc.
Un
tool_usene doit être interprété ou exécuté qu’une fois soncontent_block_stopreçu.
8. content_block_stop
Cet événement indique que le bloc est terminé.
Conceptuellement :
content_block_start
↓
content_block_delta
content_block_delta
content_block_delta
↓
content_block_stop
C’est seulement à ce moment qu’un bloc tool_use peut être considéré comme complet.
Pour un tool :
content_block_stop
↓
parse final JSON
↓
validate arguments
↓
execute tool
Pas avant.
9. Pourquoi c’est important avec JSON
Un JSON partiel peut :
- être syntaxiquement invalide ;
- manquer des champs ;
- contenir une valeur tronquée ;
- changer complètement de sens lorsqu’il est terminé.
Exemple intermédiaire :
{
"amount": 1
La valeur finale pourrait devenir :
{
"amount": 100000
}
Agir avant la fin du bloc serait donc une erreur sérieuse.
10. message_delta
message_delta apporte des informations qui concernent le message dans son ensemble.
Le module mentionne notamment :
stop_reason;- les informations finales d’usage.
Par exemple :
stop_reason = tool_use
indique que Claude termine ce tour parce qu’il souhaite utiliser un ou plusieurs tools.
Autre possibilité :
stop_reason = end_turn
indique que la réponse est terminée normalement.
11. stop_reason == tool_use
Lorsque le stop_reason indique :
tool_use
l’application sait que Claude attend maintenant des résultats de tools.
Conceptuellement :
Claude
↓
stream
↓
tool_use blocks complets
↓
stop_reason = tool_use
↓
Application exécute les tools
Puis :
tool_result
↓
Claude
↓
nouveau tour
12. message_stop
message_stop est l’événement essentiel indiquant que le message assistant est réellement terminé.
La séquence complète devient :
message_start
↓
content_block_start
↓
content_block_delta
↓
content_block_stop
↓
message_delta
↓
message_stop
La règle importante du module est :
Ne considérez pas le tour assistant comme complet tant que
message_stopn’a pas été reçu.
13. Fin du flux réseau ≠ Message complet
C’est probablement le piège le plus important de cette partie.
Supposons que votre boucle réseau se termine :
for event in stream:
process(event)
print("stream ended")
Il serait tentant de considérer :
stream ended
=
message complete
Mais ce raisonnement est incorrect.
Le flux peut s’interrompre à cause :
- d’une coupure réseau ;
- d’un timeout ;
- d’une erreur client ;
- d’une fermeture inattendue de connexion.
Le dernier événement reçu peut donc ne pas être :
message_stop
14. Exemple d’interruption
Supposons que Claude soit en train de produire :
The deployment failed because the authentication...
Puis la connexion se coupe.
Votre application possède :
The deployment failed because the authentication
Mais ce texte n’est pas une réponse complète.
Si vous l’ajoutez dans l’historique :
assistant:
"The deployment failed because the authentication"
vous indiquez à Claude, au prochain appel, qu’il a volontairement produit cette réponse complète.
Ce n’est pas vrai.
15. Ne pas enregistrer un tour incomplet
La stratégie du module est claire :
N’ajoutez le tour assistant à l’historique qu’après réception de
message_stop.
Conceptuellement :
complete = False
for event in stream:
process(event)
if event.type == "message_stop":
complete = True
Puis :
if complete:
messages.append(assembled_message)
Sinon :
discard_partial_message()
16. Pourquoi supprimer le message partiel ?
Parce qu’un historique Claude doit représenter des tours réellement terminés.
Supposons :
User:
Analyze deployment.
Assistant:
I found that the authentication...
Mais le serveur n’a jamais terminé ce message.
Au tour suivant, Claude reçoit un historique artificiellement modifié.
Cela peut :
- perturber la continuité ;
- créer des incohérences ;
- casser des blocs structurés ;
- provoquer des erreurs avec les tools ou thinking blocks.
17. Reprendre depuis le dernier tour complet
En cas d’interruption avant message_stop, le module recommande de :
Discard partial assistant turn
↓
Return to last complete conversation state
↓
Retry request
On ne reprend donc pas arbitrairement depuis le dernier fragment reçu.
On repart du dernier état conversationnel valide.
18. Exemple d’état correct
Avant l’appel :
messages =
user:
Analyze this deployment.
Le streaming commence.
Claude produit partiellement :
assistant:
I found three possible...
Puis la connexion échoue.
L’historique doit rester :
user:
Analyze this deployment.
Pas :
user:
Analyze this deployment.
assistant:
I found three possible...
L’appel peut ensuite être relancé depuis le dernier état complet.
19. Pourquoi le Streaming est plus délicat avec Tool Use
Avec du texte simple, une interruption produit principalement une réponse incomplète.
Avec tool_use, elle peut produire quelque chose de plus dangereux.
Supposons :
tool_use
{
"name": "create_invoice",
"input": {
"customer_id": "123",
"amount":
Connexion interrompue.
Le bloc n’est pas complet.
Il ne faut absolument pas :
- tenter de corriger le JSON ;
- inventer la valeur manquante ;
- exécuter le tool ;
- enregistrer le bloc dans l’historique.
20. Attendre la fin du Content Block
La règle opérationnelle est donc :
content_block_delta
↓
assembler
↓
content_block_stop ?
↓
Non → attendre
Oui → bloc complet
Puis :
bloc complet
↓
si tool_use
↓
parser
↓
validate
↓
execute
21. Plusieurs Content Blocks
Une réponse peut contenir plusieurs blocs.
Par exemple :
index 0 → text
index 1 → tool_use
index 2 → tool_use
L’application doit donc reconstruire chacun indépendamment.
Conceptuellement :
blocks = {}
blocks[0] = ...
blocks[1] = ...
blocks[2] = ...
Les événements utilisent leurs index pour indiquer le bloc concerné.
22. Plusieurs Tool Calls dans un même tour
Supposons que Claude souhaite récupérer simultanément :
get_customer
get_orders
get_support_history
Il peut retourner plusieurs blocs tool_use.
L’application doit :
- attendre que chaque bloc soit terminé ;
- assembler les arguments ;
- exécuter les appels appropriés ;
- retourner les
tool_resultcorrespondants.
23. Ne pas exécuter à partir d’un Delta
Voici un anti-pattern important :
if event.type == "content_block_delta":
if current_block.type == "tool_use":
execute_tool(current_block)
C’est incorrect.
La bonne logique est plutôt :
if event.type == "content_block_delta":
append_delta()
if event.type == "content_block_stop":
if completed_block.type == "tool_use":
parse_and_queue_tool()
24. Le Streaming et les Thinking Blocks
Lorsqu’un modèle retourne des blocs associés au reasoning, ceux-ci peuvent eux aussi être streamés.
L’application doit les traiter comme des content blocks structurés.
La règle étudiée dans l’article précédent continue de s’appliquer :
Les blocs de raisonnement qui doivent être conservés dans une boucle
tool usene doivent pas être arbitrairement modifiés.
Cela signifie qu’il faut reconstruire correctement le bloc complet avant de l’enregistrer.
25. Mauvaise architecture : afficher = enregistrer
Une erreur fréquente consiste à utiliser le même buffer pour :
UI utilisateur
et :
historique API
Or les besoins sont différents.
Pour l’interface, on peut afficher :
Bonjour...
puis :
Bonjour, voici...
puis :
Bonjour, voici le résultat...
Mais l’historique API doit recevoir uniquement le message complet validé.
Il faut donc séparer :
display buffer
et :
conversation state
26. Architecture recommandée
Conceptuellement :
Streaming events
↓
Event assembler
↙ ↘
UI buffer Message buffer
↓
message_stop ?
↙ ↘
No Yes
↓ ↓
Temporary Commit to
conversation
Cette séparation évite de confondre :
- ce que l’utilisateur voit ;
- ce que l’application considère comme l’état officiel de la conversation.
27. Exemple conceptuel de gestion du Stream
assembled_blocks = {}
message_complete = False
for event in stream:
if event.type == "message_start":
initialize_message()
elif event.type == "content_block_start":
start_block(event.index)
elif event.type == "content_block_delta":
append_delta(
index=event.index,
delta=event.delta
)
elif event.type == "content_block_stop":
finalize_block(event.index)
elif event.type == "message_delta":
update_message_metadata(event)
elif event.type == "message_stop":
message_complete = True
Puis :
if message_complete:
commit_message()
else:
discard_message()
L’objectif n’est pas de mémoriser ce code.
Il faut surtout comprendre la machine d’état.
28. La machine d’état mentale
Pour l’examen, pensez :
Start message
↓
Start block
↓
Accumulate deltas
↓
Stop block
↓
Repeat if necessary
↓
Receive message metadata
↓
message_stop
↓
Commit
Si message_stop n’arrive pas :
Do not commit
29. Gestion des erreurs réseau
Une application de production doit prévoir :
- timeout ;
- connexion interrompue ;
- réponse incomplète ;
- retry.
Mais le retry doit repartir d’un état cohérent.
Mauvaise approche :
partial response
+
retry
Bonne approche :
last complete conversation state
+
retry
30. Attention aux effets de bord avec les Tools
Le retry devient plus délicat lorsque certains tools ont déjà été exécutés.
Supposons :
Claude
→ create_payment tool_use
L’application exécute :
create_payment
Puis une coupure intervient avant la suite.
Relancer aveuglément l’ensemble du workflow peut provoquer :
create_payment
une deuxième fois.
Cela peut créer un double paiement.
Le module source insiste surtout sur la reconstruction et la validation du message ; dans une architecture réelle, ce type de tool avec effet de bord doit donc également être protégé côté application.
Par exemple avec :
- idempotency ;
- suivi de l’état ;
- validations ;
Human-in-the-Looplorsque nécessaire.
31. Streaming et actions irréversibles
Une règle de sécurité découle directement du fonctionnement du streaming :
Ne déclenchez jamais une action irréversible à partir d’un fragment de génération.
Il faut attendre :
complete tool_use block
puis appliquer :
schema validation
permissions
business rules
HITL if needed
avant l’exécution.
32. content_block_stop et message_stop ne signifient pas la même chose
C’est un point important.
content_block_stop
Signifie :
ce bloc est terminé
message_stop
Signifie :
le message complet est terminé
Il peut y avoir plusieurs content_block_stop avant un seul message_stop.
Par exemple :
content block 0 stop
content block 1 stop
content block 2 stop
message_stop
33. Exemple complet
Claude doit répondre à :
Find the current customer data and summarize it.
Le stream peut ressembler conceptuellement à :
message_start
content_block_start
index = 0
type = text
content_block_delta
"I'll retrieve the"
content_block_delta
" customer information."
content_block_stop
content_block_start
index = 1
type = tool_use
content_block_delta
{"customer_
content_block_delta
id":"123"}
content_block_stop
message_delta
stop_reason = tool_use
message_stop
À ce stade seulement :
tool_use complet
+
message complet
L’application peut poursuivre correctement la boucle.
34. Ce qu’il faut retenir pour la certification
Principe 1 — Le Streaming envoie des événements
Une réponse n’est pas simplement du texte découpé.
Vous devez traiter :
message_start
content_block_start
content_block_delta
content_block_stop
message_delta
message_stop
Principe 2 — Un Delta est partiel
Ne parsez pas définitivement et n’exécutez pas un tool_use à partir d’un content_block_delta.
Attendez :
content_block_stop
Principe 3 — Un Block terminé n’est pas forcément un Message terminé
content_block_stop
≠
message_stop
Principe 4 — La fin de connexion ne garantit pas la fin du Message
Le critère fiable est :
message_stop received
Principe 5 — Commit uniquement après message_stop
Si la connexion est interrompue avant :
discard partial assistant turn
Puis repartir du dernier état conversationnel complet.
Principe 6 — Séparer affichage et état conversationnel
On peut afficher les tokens progressivement à l’utilisateur.
Mais on ne doit enregistrer dans l’historique que le tour complet.
Pièges fréquents à l’examen
Piège 1
« La boucle de lecture du stream s’est terminée, donc le message Claude est complet. »
Faux.
Il faut vérifier que message_stop a été reçu.
Piège 2
« Dès que le JSON d’un tool_use semble valide, je peux exécuter le tool. »
Non.
Attendez la fin du bloc avec content_block_stop.
Piège 3
« Après une interruption, je conserve le texte partiel dans l’historique et je continue. »
Non.
Le module recommande de supprimer le tour assistant incomplet et de reprendre depuis le dernier état valide.
Piège 4
« content_block_stop signifie que la réponse entière est terminée. »
Non.
Il signifie uniquement que ce content block est terminé.
Piège 5
« Le buffer affiché à l’utilisateur peut directement servir d’historique API. »
Mauvaise conception.
Le contenu affiché peut être partiel alors que l’historique doit rester cohérent.
La règle à mémoriser
Pour le streaming, retenez :
Receive
↓
Assemble
↓
content_block_stop
↓
Bloc exploitable
↓
message_stop
↓
Tour complet
↓
Commit dans l’historique
Et en cas d’interruption :
No message_stop
↓
Do not commit
↓
Discard partial turn
↓
Retry from last complete state
Le streaming améliore l’expérience utilisateur, mais il impose donc une discipline supplémentaire côté application.
L’objectif n’est pas simplement de montrer des tokens plus tôt.
Il faut garantir que l’état de la conversation reste cohérent même lorsqu’une génération ou une connexion est interrompue.
Article suivant
Context Engineering avec Claude : maîtriser la context window, les tokens et les longues sessions
Nous verrons pourquoi les conversations longues et les résultats de tools peuvent progressivement consommer la context window, puis comment utiliser pruning, compaction, clearing, prompt caching et subagents pour maintenir un système Claude efficace en production.

