Appels d'outils en streaming : fini les attentes muettes
Sur cette page
Objectifs
À l’issue de cette leçon, vous serez capable de :
- Lire la séquence d’événements d’un appel d’outil en streaming
- Afficher l’outil choisi dès que son nom arrive
- Savoir quand activer le streaming fin des entrées (
eager_input_streaming)
Huit secondes devant une roue qui tourne
Votre agent met huit secondes à répondre, et l’utilisateur regarde une roue de chargement pendant les huit. Pendant ce temps, Claude choisit un outil, écrit son entrée, votre code exécute l’outil, puis Claude rédige la réponse. Sans streaming, votre code ne voit chaque réponse qu’une fois terminée : l’interface n’a rien à montrer. Avec le streaming, la réponse arrive morceau par morceau, sous forme d’événements envoyés par le serveur (server-sent events), et l’écran peut dire ce qui se passe : « Recherche du projet… » dès que Claude a choisi l’outil.
La forme d’un appel d’outil en streaming
Un bloc tool_use arrive en trois temps :
content_block_startouvre le bloc. Il contient déjà le nom de l’outil, mais une entrée vide (input: {}), simple marque de place.content_block_deltaarrive une ou plusieurs fois, avec undeltade typeinput_json_deltaportant un fragmentpartial_jsonde l’entrée JSON.content_block_stopferme le bloc.
Puis l’événement message_delta porte stop_reason: "tool_use" : c’est le signal pour exécuter l’outil.
Les fragments se coupent n’importe où, au milieu d’une clé, d’une valeur ou d’une séquence d’échappement, et certains peuvent être vides. Règle : accumulez les fragments dans un tampon, et n’analysez le JSON qu’au content_block_stop. Si Claude fait plusieurs appels dans le même tour, chaque bloc a son propre index : gardez un tampon par index, sinon les fragments s’entremêlent.
Afficher l’outil choisi dès son ouverture
Avec un SDK officiel, client.messages.stream(...) fait l’accumulation pour vous. Votre code n’a qu’à réagir à l’ouverture du bloc, puis récupérer la réponse complète avec stream.get_final_message(), qui renvoie le même type d’objet que client.messages.create(...) : le reste de la boucle ne change pas.
STATUTS = {
"consulter_projet": "Recherche du projet…",
"consulter_permis": "Vérification du permis…",
}
with client.messages.stream(
model="claude-opus-5-5",
max_tokens=16000,
tools=OUTILS,
messages=messages,
) as stream:
for event in stream:
if event.type == "content_block_start" and event.content_block.type == "tool_use":
afficher_statut(STATUTS.get(event.content_block.name, "Traitement en cours…"))
reponse = stream.get_final_message()
afficher_statut est votre fonction d’affichage. Les modèles actuels peuvent aussi produire des blocs de réflexion, que ce code ignore. Connaître la couche brute reste utile : c’est elle que vous déboguerez le jour où quelque chose cloche sur le réseau.
Le streaming fin des entrées volumineuses
Par défaut, l’API met en mémoire tampon et valide chaque paramètre avant d’en émettre les fragments. Pour une petite entrée, c’est invisible ; pour un outil qui reçoit un fichier entier, un bloc de code ou un document, rien n’arrive tant que le paramètre n’est pas complet. Ajouter eager_input_streaming: true sur un outil défini par vous lève cette mise en tampon : les fragments arrivent dès qu’ils sont générés.
La contrepartie : l’API ne valide plus l’entrée. L’entrée accumulée peut être coupée par max_tokens ou contenir du JSON invalide, et les analyseurs tolérants des SDK peuvent renvoyer un objet tronqué sans lever d’erreur. Validez donc l’entrée contre le schéma avant d’exécuter l’outil, vérifiez stop_reason (max_tokens, refusal) avant de lancer quoi que ce soit, et renvoyez une erreur à Claude si l’entrée est invalide. N’activez ce mode que sur une requête en streaming, et jamais sur les outils serveur.
À vous de jouer
Une ligne d’état.
- Dans votre agent, remplacez
messages.create(...)parmessages.stream(...)etget_final_message(). - Associez à chaque outil une phrase lisible pour l’utilisateur, affichée au
content_block_start. - Mesurez le délai avant le premier affichage, avant et après.
Sources officielles : Streaming messages (documentation Claude : événements,
input_json_delta) · Fine-grained tool streaming (documentation Claude :eager_input_streaming, validation à votre charge)
Points clés à retenir
- Un appel d’outil en streaming arrive avec le nom d’abord, puis l’entrée en fragments
input_json_delta - Un tampon par index de bloc, et l’analyse du JSON seulement à
content_block_stop - Dès
content_block_start, affichez l’outil choisi en mots compréhensibles ;get_final_message()garde le reste de la boucle inchangé eager_input_streamingpour les entrées volumineuses, à condition de valider vous-même l’entrée