Implémenter les tours multiples
Mis à jour le 30 juillet 2026
Jusqu’ici, nos échanges avec Claude suivaient un schéma simple : une requête, une réponse, terminé. L’utilisation d’outils casse ce schéma. Quand Claude décide d’appeler un outil, sa « réponse » n’est pas une fin — c’est une demande qui attend un résultat, et ce résultat appellera peut-être un nouvel outil, puis un autre. Votre code doit donc devenir une boucle : dialoguer avec le modèle jusqu’à ce qu’il n’ait plus besoin de rien.
La clé pour piloter cette boucle est le champ stop_reason de la réponse. Quand il vaut "tool_use", Claude s’est interrompu parce qu’il attend le résultat d’un outil ; quand il vaut autre chose (typiquement "end_turn"), la conversation est réellement terminée et vous pouvez rendre la main.
La boucle de conversation
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
Lisez cette boucle comme un tour de table : à chaque itération, on envoie l’historique complet à Claude, on ajoute sa réponse à l’historique (c’est indispensable — le prochain appel doit inclure la demande d’outil pour que le résultat ait un sens), puis on regarde stop_reason. Tant que Claude demande des outils, on les exécute et on renvoie les résultats comme un message utilisateur ; dès qu’il conclut, on sort. Notez que les résultats d’outils voyagent bien dans un message de rôle user : du point de vue du protocole, c’est « vous » qui rapportez à Claude ce que l’outil a produit.
Cette boucle n’a pas de limite de tours ici, ce qui est acceptable pour un exercice ; en production, ajoutez un compteur de sécurité (par exemple 10 itérations maximum) pour qu’un enchaînement imprévu ne tourne pas indéfiniment à vos frais.
Gérer plusieurs appels d’outils
Un point que beaucoup découvrent en déboguant : une seule réponse de Claude peut contenir plusieurs demandes d’outils. Si l’utilisateur demande « quelle heure est-il à Paris et à Tokyo ? », Claude peut émettre deux appels dans le même tour. Votre code doit donc traiter la réponse comme une liste, jamais comme un appel unique :
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:
try:
tool_output = run_tool(tool_request.name, tool_request.input)
tool_result_blocks.append({
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
})
except Exception as e:
tool_result_blocks.append({
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Erreur : {e}",
"is_error": True
})
return tool_result_blocks
Deux détails de cette fonction font la différence entre un prototype et un code robuste. D’abord, chaque résultat est relié à sa demande par tool_use_id : c’est ce qui permet à Claude d’apparier ses questions et vos réponses quand il y en a plusieurs — un identifiant manquant ou erroné provoque une erreur d’API. Ensuite, l’exception est capturée et renvoyée au modèle avec is_error: True, plutôt que de faire planter la boucle : Claude lit le message d’erreur et peut réagir intelligemment — reformuler ses arguments, essayer autre chose, ou expliquer le problème à l’utilisateur. Un outil qui échoue n’est pas une fin de conversation, c’est une information.
Routage des outils
Reste à relier le nom d’outil demandé par Claude à votre vraie fonction Python. Un simple aiguillage suffit :
def run_tool(tool_name, tool_input):
if tool_name == "get_current_datetime":
return get_current_datetime(**tool_input)
elif tool_name == "other_tool":
return other_tool_function(**tool_input)
L’intérêt de centraliser ce routage dans une fonction dédiée apparaît dès le deuxième outil : ajouter une capacité à votre application se résume à écrire la fonction, déclarer son schéma, et ajouter une branche ici — la boucle de conversation et la gestion des résultats, elles, ne changent jamais. C’est cette séparation qui rend l’architecture extensible : la mécanique du dialogue est écrite une fois, les outils s’y branchent.
Points clés à retenir
stop_reason == "tool_use"signale que Claude attend un résultat d’outil ; la conversation continue en boucle jusqu’àend_turn- La réponse de Claude doit être ajoutée à l’historique avant d’envoyer les résultats d’outils
- Une réponse peut contenir plusieurs
tool_use: traitez toujours une liste, chaque résultat apparié partool_use_id - Renvoyez les erreurs d’exécution au modèle avec
is_error: Trueau lieu de faire échouer la boucle - Centralisez le routage nom → fonction : les nouveaux outils s’ajoutent sans toucher à la logique de conversation