Streaming avec Claude : gérer les réponses partielles et les interruptions

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 block commence ;
  • 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_use ne doit être interprété ou exécuté qu’une fois son content_block_stop reç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_stop n’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 :

  1. attendre que chaque bloc soit terminé ;
  2. assembler les arguments ;
  3. exécuter les appels appropriés ;
  4. retourner les tool_result correspondants.

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 use ne 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-Loop lorsque 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.

Le phare info – Média indépendant & critique
Sélectionne, organise, contextualise et partage des contenus pertinents autour d’un thème ou d’une problématique, dans une logique de veille, de transmission et de mise en sens.
Pour cet article, l’intelligence artificielle a été utilisée comme un outil d’aide à l’exploration, à la structuration et à la rédaction. Elle permet de confronter plusieurs angles, de repérer certains biais humains possibles et de faire émerger des points de vigilance. Le curateur humain observe aussi les biais possibles de l’IA, vérifie les éléments essentiels, nuance l’analyse, corrige les formulations fragiles et assume la publication.

Articles liés

Fiche de révision certification — Accelerators & IP Contribution

Cette fiche résume les notions essentielles du module Accelerators & IP Contribution. Le fil conducteur est simple : Un build Claude n’est pas terminé lorsqu’il fonctionne....

Étude de cas : corriger un accelerator Claude mal packagé, mal versionné et mal sécurisé

Un système Claude peut fonctionner parfaitement en démonstration et pourtant être impropre à la production. Le cas cumulatif du module illustre précisément cette situation. L’application : s’exécute...

Applications Claude multi-composants : trust boundaries, least privilege et sécurité

Une application Claude moderne n’est souvent pas constituée d’un seul appel API. Elle peut combiner : une API applicative ; Claude ; un agent ; Claude Code ; un MCP...

Comparer les plateformes Claude : latency, compliance, data residency et total cost

Lorsque plusieurs plateformes permettent d’exécuter Claude, comment choisir ? Une comparaison superficielle pourrait se limiter à : la plateforme la moins chère ; celle que l’équipe connaît...

Model versioning avec Claude : pin what ships, eval gates et rollback

Changer de modèle dans une application Claude peut sembler être une modification minime : model = "new-model" Pourtant, en production, ce changement peut modifier : la qualité...

Le sentier du savoir

De la curiosité à la transmission, explorez les étapes qui permettent de transformer l’information en compréhension durable.

Étape 1 — Construire une culture générale solide

Construire une base solide de connaissances pour comprendre le monde. Relier les faits, les disciplines et les repères essentiels.

Étape 2 — Maîtriser la pensée critique et l’analyse : apprendre à penser contre ses propres certitudes

Apprendre à analyser l’information, repérer les biais et questionner les évidences. Penser par soi-même dans un monde saturé de récits.

Étape 3 – Apprendre à argumenter et à convaincre

Structurer sa pensée pour convaincre sans manipuler. Savoir débattre, nuancer et formuler des idées claires.

Étape 4 – Approfondir un ou plusieurs domaines d’expertise

Explorer un ou plusieurs domaines en profondeur. Passer de la curiosité à la compréhension experte.

Etapes 5 : Devenir polyglotte : élargir sa pensée par les langues

Élargir ses horizons par le langage et les cultures. Penser autrement en changeant de langue.

Étape 6 — Comprendre la méthode scientifique et expérimenter

Comprendre la méthode scientifique et l’expérimentation. Distinguer savoirs établis, hypothèses et croyances.

Étape 7 – Écrire, transTransmission : écrire, transmettre, enseigner

Écrire, expliquer, partager ce que l’on a compris. Transformer le savoir en outil collectif.

Étape 8 — Cultiver l’équilibre corps-esprit pour soutenir l’érudition

Cultiver le corps et l’esprit pour soutenir l’érudition dans le temps. Le savoir durable repose aussi sur l’attention et l’équilibre personnel.