Conversations multi-tours avec outils
Mis à jour le 30 juillet 2026
Quand on construit des applications avec plusieurs outils, il faut gérer les scénarios où Claude doit appeler successivement plusieurs outils pour répondre à une seule question. Par exemple, si un utilisateur demande “Quel jour sera-t-on dans 103 jours ?”, Claude doit d’abord obtenir la date actuelle, puis ajouter 103 jours.
Cela crée un schéma de conversation multi-tours où Claude fait plusieurs demandes d’outils avant de fournir une réponse finale. Votre application doit gérer cela automatiquement.
Le schéma multi-tours
L’enchaînement ci-dessous mérite d’être lu attentivement, car il montre une chose que l’exemple à un seul outil ne laisse pas voir : Claude ne planifie pas la séquence à l’avance. Il demande la date du jour, reçoit la réponse, et c’est seulement à ce moment qu’il constate qu’il lui faut aussi un calcul de durée.
Cette propriété est ce qui rend la boucle indispensable — vous ne pouvez pas savoir d’avance combien de tours seront nécessaires — et c’est aussi ce qui rend le système robuste : si le premier outil renvoie une information inattendue, le modèle adapte la suite au lieu de dérouler un plan devenu faux.
- L’utilisateur demande : “Quel jour sera-t-on dans 103 jours ?”
- Claude répond avec un bloc tool_use demandant
get_current_datetime - Votre serveur appelle la fonction et renvoie le résultat
- Claude réalise qu’il a besoin d’encore plus d’informations et demande
add_duration_to_datetime - Votre serveur appelle cette fonction et renvoie le résultat
- Claude a maintenant assez d’informations pour fournir la réponse finale
Construire une boucle de conversation
Pour gérer ce schéma, il faut une boucle qui continue tant que Claude demande des outils :
def run_conversation(messages):
while True:
response = chat(messages)
# Ajouter la réponse de Claude a l'historique
add_assistant_message(messages, response)
# Si Claude ne demande pas d'outil, on sort de la boucle
if response.stop_reason != "tool_use":
break
# Sinon, exécuter les outils et renvoyer les résultats
tool_result_blocks = run_tools(response)
add_user_message(messages, tool_result_blocks)
return messages
Un garde-fou s’impose ici, et il vaut mieux l’écrire dès la première version : bornez le nombre d’itérations. Une boucle qui tourne tant que stop_reason vaut "tool_use" n’a aucune raison structurelle de s’arrêter si un outil renvoie systématiquement un résultat qui ne satisfait pas le modèle — et chaque tour est un appel facturé. Le point clé : on vérifie response.stop_reason. Si c’est "tool_use", Claude attend des résultats d’outils. Si c’est "end_turn", Claude a terminé et on peut sortir de la boucle.
Refactoriser les fonctions utilitaires
Avant d’implémenter la boucle de conversation, il faut mettre à jour les fonctions utilitaires pour gérer correctement les blocs multiples.
Mise à jour des gestionnaires de messages
Les fonctions add_user_message et add_assistant_message doivent maintenant accepter des objets Message complets :
from anthropic.types import Message
def add_user_message(messages, message):
user_message = {
"role": "user",
# Si c'est un objet Message, prendre son contenu ; sinon, utiliser tel quel
"content": message.content if isinstance(message, Message) else message
}
messages.append(user_message)
Cela permet de passer soit une chaîne, soit une liste de blocs, soit un objet Message complet.
Mise à jour de la fonction chat
La fonction chat doit accepter une liste d’outils et retourner l’objet Message complet (pas juste le texte) :
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
"stop_sequences": stop_sequences,
}
# Ajouter les outils seulement s'ils sont fournis
if tools:
params["tools"] = tools
if system:
params["system"] = system
message = client.messages.create(**params)
return message # Retourne l'objet Message complet
Extraire le texte des messages
Ce changement de signature — retourner l’objet complet plutôt que le texte — est la conséquence directe du multi-tours : un message peut contenir du texte et des demandes d’outils, et rendre seulement le texte reviendrait à jeter la moitié de l’information. D’où l’utilitaire ci-dessous, qui refait le chemin inverse quand vous avez besoin d’afficher quelque chose à l’utilisateur.
def text_from_message(message):
return "\n".join(
[block.text for block in message.content if block.type == "text"]
)
Cette fonction trouve tous les blocs texte dans un message et les concatène. C’est utile quand on veut afficher la réponse finale à l’utilisateur.
Résumé des améliorations
| Amélioration | Bénéfice |
|---|---|
| Gestion flexible des messages | Les fonctions utilitaires acceptent différents formats de messages |
| Support des outils dans chat() | La fonction chat peut recevoir et transmettre les schémas d’outils |
| Retour d’objets Message complets | On conserve tous les blocs, pas juste le texte |
| Utilitaire d’extraction de texte | Moyen facile d’obtenir du texte lisible à partir de messages complexes |
Avec ces fondations en place, la boucle de conversation gère automatiquement les appels d’outils multiples, créant une expérience fluide où Claude peut utiliser autant d’outils que nécessaire pour répondre aux questions des utilisateurs.
Exercice : Tracez le flux d'un rappel complet
Simulez le flux complet pour la demande : “Programme un rappel pour mon dentiste jeudi prochain à 15h”
Tour 1 :
- Claude demande
get_current_datetimepour connaître la date actuelle - Votre serveur renvoie
"2024-03-11 10:22:00"
Tour 2 :
- Claude demande
add_duration_to_datetimeavec la date actuelle et la durée jusqu’à jeudi prochain 15h - Votre serveur renvoie
"2024-03-14 15:00:00"
Tour 3 :
- Claude demande
set_reminderavec la date calculée et le message “Rendez-vous dentiste” - Votre serveur renvoie
"Reminder set successfully"
Réponse finale :
- Claude répond : “C’est noté ! Je vous rappellerai votre rendez-vous chez le dentiste jeudi 14 mars à 15h00.”
Combien d’appels API au total ? 4 (1 initial + 3 résultats d’outils). Et stop_reason vaut "tool_use" pour les 3 premiers, puis "end_turn" pour le dernier.