Conversations multi-tours avec les outils
Mis à jour le 30 juillet 2026
Quand Claude utilise des outils, une seule requête ne suffit pas toujours. Il faut mettre en place une boucle de conversation qui continue tant que Claude demande à utiliser des outils, et qui s’arrête quand il a sa réponse finale.
Détecter les demandes d’outils
Le champ stop_reason de la réponse indique pourquoi Claude a arrêté de générer du texte. Quand il veut appeler un outil, ce champ vaut "tool_use" :
if response.stop_reason != "tool_use":
break # Claude a termine, plus besoin d'outils
C’est le signal clé : tant que stop_reason vaut "tool_use", il faut continuer la boucle. Ne cherchez pas à deviner autrement — inspecter le contenu du message pour voir s’il ressemble à une demande d’outil est fragile et inutile, puisque l’API vous donne l’information de façon explicite.
La boucle de conversation
Le schéma général est simple : on boucle indéfiniment, on envoie les messages à Claude, on traite les outils demandés, et on recommence jusqu’à ce que Claude fournisse une réponse finale.
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
print(text_from_message(response))
if response.stop_reason != "tool_use":
break
tool_results = run_tools(response)
add_user_message(messages, tool_results)
return messages
À chaque itération :
- On appelle Claude avec les messages et les outils disponibles
- On ajoute la réponse de Claude à l’historique
- Si Claude ne demande pas d’outil, on sort de la boucle
- Sinon, on exécute les outils et on renvoie les résultats
Gérer plusieurs appels d’outils en parallèle
C’est le point que les premières implémentations manquent presque toujours : on traite le premier bloc tool_use rencontré et l’on renvoie son résultat, en ignorant les autres. Claude attend alors des résultats qui n’arriveront jamais, et la conversation se bloque sans message d’erreur. Parcourez tous les blocs et renvoyez autant de tool_result que de tool_use reçus. Claude peut demander plusieurs outils dans une seule réponse. Le contenu du message est une liste de blocs, et chaque bloc de type tool_use correspond à un appel distinct.
La fonction run_tools filtre ces blocs et traite chacun d’eux :
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
# Traiter chaque demande d'outil...
Les blocs de résultats
Chaque bloc tool_use doit recevoir un bloc tool_result correspondant. Le lien entre les deux se fait via l’identifiant (tool_use_id) :
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
Les champs importants :
type: toujours"tool_result"tool_use_id: doit correspondre exactement à l’iddu bloctool_usecontent: le résultat sérialisé en JSONis_error:Truesi l’exécution a échoué
Gestion des erreurs
La règle vaut d’être retenue telle quelle : un tool_use sans tool_result correspondant bloque la conversation. Une exception avalée côté serveur produit exactement ce cas — votre code passe à la suite, Claude attend indéfiniment. Renvoyez toujours un bloc, même pour signaler l’échec : avec is_error: True, le modèle peut reformuler, réessayer autrement ou expliquer la situation à l’utilisateur.
try:
tool_output = run_tool(tool_request.name, tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
except Exception as e:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Error: {e}",
"is_error": True
}
En signalant is_error: True, Claude sait que l’outil a échoué et peut adapter sa réponse en conséquence (retenter, demander des informations supplémentaires, ou informer l’utilisateur).
Routage extensible des outils
Pour gérer plusieurs outils proprement, on crée une fonction de routage qui associe chaque nom d’outil à son implémentation :
def run_tool(tool_name, tool_input):
if tool_name == "get_current_datetime":
return get_current_datetime(**tool_input)
elif tool_name == "another_tool":
return another_tool(**tool_input)
# Ajouter d'autres outils ici
Cette approche permet d’ajouter de nouveaux outils sans modifier la logique principale de la conversation.
Récapitulatif du flux complet
Le cycle complet d’une conversation multi-tours avec outils :
- Envoi du message utilisateur à Claude avec la liste des outils disponibles
- Réponse de Claude contenant du texte et/ou des demandes d’outils
- Exécution de tous les outils demandés et création des blocs de résultats
- Renvoi des résultats sous forme de message utilisateur
- Répétition jusqu’à ce que Claude fournisse une réponse finale (sans demande d’outil)
Ce mécanisme permet à Claude d’enchaîner plusieurs outils sur plusieurs tours pour répondre à des questions complexes, tout en gardant le contexte complet de la conversation.