Aller au contenu principal
Leçon 4 sur 16 · L'utilisation d'outils (tool use) avec la Messages API

Chatbot avec plusieurs outils

Leçon 4 sur 16 Mis à jour le 5 novembre 2026 Développeur
Sur cette page
  1. Objectifs
  2. Architecture du chatbot
  3. Définir plusieurs outils
  4. La boucle du chatbot
  5. Exemple de conversation
  6. À vous de jouer
  7. Points clés à retenir

Objectifs

  • Construire un chatbot complet avec plusieurs outils
  • Gérer une conversation multi-tours avec des appels d’outils
  • Implémenter un agent conversationnel autonome

Architecture du chatbot

Tout ce que vous avez appris jusqu’ici converge dans cette leçon : un assistant conversationnel qui dispose de plusieurs outils, garde la mémoire de l’échange et décide tour après tour de ce dont il a besoin. Son fonctionnement repose sur deux boucles imbriquées. La boucle externe est celle du dialogue : l’utilisateur pose une question, l’assistant finit par répondre, et l’on recommence. La boucle interne est celle des outils : Claude décide s’il a besoin d’un outil, on l’exécute, on lui renvoie le résultat, et il enchaîne soit sur une nouvelle demande d’outil, soit sur sa réponse. Ce n’est qu’en sortant de cette boucle interne, quand le modèle produit enfin du texte, que la réponse s’affiche et que la main revient à l’utilisateur.

Définir plusieurs outils

Notre assistant personnel disposera de trois capacités : la météo, l’heure et le calcul. Les fonctions restent volontairement simples — la météo est simulée par un dictionnaire — car l’objet de la leçon est l’orchestration, pas l’intégration d’API réelles. Observez tout de même deux détails. La fonction météo renvoie du JSON sérialisé plutôt qu’un dictionnaire Python, parce que le champ content d’un tool_result attend du texte. Et la fonction de calcul s’appuie sur une évaluation d’expression directe : acceptable dans un notebook d’apprentissage, c’est une faille d’exécution de code arbitraire en production, où l’on emploie un évaluateur restreint. Le commentaire dans le code vous le rappelle.

import ast
import json
import operator
from datetime import datetime
from zoneinfo import ZoneInfo

# Fonctions des outils
def get_weather(city):
    """Simule une API météo."""
    weather_data = {
        "Paris": {"temp": 18, "condition": "Nuageux"},
        "Lyon": {"temp": 22, "condition": "Ensoleillé"},
        "Marseille": {"temp": 25, "condition": "Ensoleillé"},
    }
    data = weather_data.get(city, {"temp": None, "condition": "Ville inconnue"})
    return json.dumps(data, ensure_ascii=False)

def get_time(timezone="Europe/Paris"):
    """Retourne l'heure actuelle dans un fuseau horaire IANA."""
    return datetime.now(ZoneInfo(timezone)).strftime("%H:%M:%S")

OPERATIONS = {
    ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
    ast.Div: operator.truediv, ast.Mod: operator.mod, ast.USub: operator.neg,
}

def _evaluate(node):
    if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
        return node.value
    if isinstance(node, ast.BinOp) and type(node.op) in OPERATIONS:
        return OPERATIONS[type(node.op)](_evaluate(node.left), _evaluate(node.right))
    if isinstance(node, ast.UnaryOp) and type(node.op) in OPERATIONS:
        return OPERATIONS[type(node.op)](_evaluate(node.operand))
    raise ValueError("seules les quatre opérations, le modulo et les parenthèses sont acceptés")

def calculate(expression):
    """Évalue une expression arithmétique, sans eval."""
    return str(_evaluate(ast.parse(expression, mode="eval").body))

# Définitions des outils
tools = [
    {
        "name": "get_weather",
        "description": (
            "Donne la météo actuelle d'une ville française : température en degrés Celsius et état du ciel. "
            "À utiliser pour toute question sur le temps qu'il fait maintenant. "
            "Ne donne pas de prévisions."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "Le nom de la ville, par exemple Lyon"}
            },
            "required": ["city"],
        },
    },
    {
        "name": "get_time",
        "description": (
            "Donne l'heure actuelle, au format HH:MM:SS, dans un fuseau horaire. "
            "À utiliser quand l'utilisateur demande l'heure. "
            "Sans fuseau précisé, c'est l'heure de Paris."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "timezone": {
                    "type": "string",
                    "description": "Fuseau horaire IANA, par exemple Europe/Paris ou America/New_York",
                }
            },
            "required": [],
        },
    },
    {
        "name": "calculate",
        "description": (
            "Calcule une expression arithmétique : addition, soustraction, multiplication, division, "
            "modulo et parenthèses. À utiliser pour tout calcul dont le résultat doit être exact. "
            "Renvoie le résultat sous forme de texte."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "L'expression à calculer, par exemple 2500 * 0.15"}
            },
            "required": ["expression"],
        },
    },
]

# Correspondance nom -> fonction
tool_functions = {
    "get_weather": lambda **kwargs: get_weather(kwargs["city"]),
    "get_time": lambda **kwargs: get_time(kwargs.get("timezone", "Europe/Paris")),
    "calculate": lambda **kwargs: calculate(kwargs["expression"]),
}

Le dictionnaire tool_functions mérite une mention particulière. Il fait le pont entre le nom que Claude renvoie et la fonction à exécuter, et il absorbe au passage les paramètres optionnels : get_time fonctionne même si le modèle omet le fuseau horaire, puisque le lambda fournit la valeur de repli.

La calculatrice, elle, n’utilise pas eval. Une expression envoyée par le modèle reste une entrée non fiable : eval exécuterait n’importe quel code Python. Le module ast lit l’expression comme un arbre, et _evaluate n’accepte que des nombres, quatre opérations, le modulo et les parenthèses. Le reste lève une erreur.

La boucle du chatbot

Le code ci-dessous assemble le tout. Le prompt système annonce les capacités disponibles et demande de ne recourir aux outils que lorsque c’est pertinent. La liste conversation accumule tous les messages : questions de l’utilisateur, réponses de l’assistant, blocs tool_use et blocs tool_result. Une erreur d’outil ne fait pas tomber le programme : run_tool la renvoie à Claude dans un tool_result marqué is_error, et Claude peut corriger sa demande ou expliquer le problème.

from anthropic import Anthropic

client = Anthropic()

SYSTEM_PROMPT = """Tu es un assistant personnel utile.
Tu as accès à des outils pour la météo, l'heure et les calculs.
Utilise-les quand c'est pertinent, sinon réponds directement.
Réponds en français."""

def run_tool(block):
    """Exécute un appel d'outil et renvoie le bloc tool_result correspondant."""
    try:
        result = tool_functions[block.name](**block.input)
        return {"type": "tool_result", "tool_use_id": block.id, "content": result}
    except Exception as e:   # outil inconnu, argument manquant, calcul refusé…
        return {"type": "tool_result", "tool_use_id": block.id,
                "content": f"Erreur : {e}", "is_error": True}

def chatbot():
    """Chatbot interactif avec outils."""
    conversation = []
    print("Chatbot démarré ! Tapez « quit » pour quitter.\n")

    while True:
        user_input = input("Vous : ")
        if user_input.lower() == "quit":
            print("Au revoir !")
            break

        conversation.append({"role": "user", "content": user_input})
        response = client.messages.create(
            model="claude-sonnet-5-5",
            max_tokens=4096,
            system=SYSTEM_PROMPT,
            tools=tools,
            messages=conversation,
        )

        # Boucle d'outils : tant que Claude demande un outil
        while response.stop_reason == "tool_use":
            conversation.append({"role": "assistant", "content": response.content})
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    print(f"  [Outil : {block.name}({block.input})]")
                    tool_results.append(run_tool(block))
            conversation.append({"role": "user", "content": tool_results})
            response = client.messages.create(
                model="claude-sonnet-5-5",
                max_tokens=4096,
                system=SYSTEM_PROMPT,
                tools=tools,
                messages=conversation,
            )

        # Réponse finale : on lit les blocs de texte
        assistant_text = "".join(block.text for block in response.content if block.type == "text")
        print(f"Claude : {assistant_text}\n")
        conversation.append({"role": "assistant", "content": response.content})

chatbot()

Exemple de conversation

Une session type fait apparaître les trois comportements que vous devez savoir reconnaître.

Vous : Quelle est la météo à Paris, et combien font 15 % de 2500 ?
  [Outil : get_weather({'city': 'Paris'})]
  [Outil : calculate({'expression': '2500 * 0.15'})]
Claude : Il fait 18 °C à Paris, avec un ciel nuageux. Et 15 % de 2500 font 375.

Vous : Merci ! Quelle heure est-il ?
  [Outil : get_time({})]
Claude : Il est 14:32:45.

Vous : Qui a écrit Les Misérables ?
Claude : Victor Hugo, qui l'a publié en 1862.
Deux boucles imbriquées Deux colonnes : la boucle du dialogue (question de l'utilisateur, réponse finale ajoutée à l'historique, jusqu'à quit) et la boucle des outils (tool_use, exécution, tool_result renvoyé avec le même tableau tools, jusqu'à ce que stop_reason ne vaille plus tool_use). CHATBOT OUTILLÉ Deux boucles imbriquées La boucle du dialogue 1. Question L'utilisateur écrit ; le message rejoint l'historique. 2. Réponse Le texte final s'affiche et rejoint l'historique. 3. Recommencer Jusqu'à « quit ». La boucle des outils 1. tool_use Claude demande un ou plusieurs outils. 2. Exécution Votre code appelle chaque fonction, erreurs comprises. 3. tool_result Les résultats repartent, avec le même tableau tools. 4. Sortie Dès que stop_reason ne vaut plus tool_use. L'historique garde tout : questions, réponses, demandes d'outils et résultats.
D'après le notebook « Chatbot with multiple tools » (tutoriel Tool use d'Anthropic) et la page Handle tool calls (documentation Claude).

La première question déclenche deux outils dans une même réponse, puisque les deux informations sont indépendantes l’une de l’autre : leurs deux résultats repartent ensemble, dans un seul message user. La deuxième illustre l’appel d’un outil sans paramètre obligatoire. Pour la dernière, enfin, Claude répond directement sans utiliser d’outil, car aucun n’est nécessaire — c’est exactement le comportement recherché.

À vous de jouer

Un quatrième outil. Ajoutez au chatbot un quatrième outil, search_product, qui interroge un catalogue fictif, afin qu’il puisse traiter une demande comme « Est-ce que vous avez des écouteurs Bluetooth à moins de 50 euros ? ». La difficulté tient à la cohabitation d’un critère textuel et d’un critère numérique dans le même schéma.

Voir un indice

Définissez un outil avec deux paramètres : query (string) et max_price (number, optionnel). La fonction peut retourner une liste de produits fictifs filtrés par prix.

Sources officielles : Handle tool calls (documentation Claude : résultats, is_error, règles de mise en forme) · Parallel tool use (documentation Claude) · Chatbot with multiple tools (notebook d’origine)

Points clés à retenir

  1. La boucle d’outils : continuez à faire des requêtes tant que stop_reason == "tool_use", avec le même tableau tools
  2. Historique complet : gardez l’intégralité de la conversation (messages + tool_use + tool_result)
  3. Outils multiples dans une réponse : Claude peut demander plusieurs outils simultanément
  4. Choix intelligent : Claude n’utilise les outils que quand c’est pertinent
  5. Réponse finale : quand Claude a toutes les infos, il formule une réponse textuelle naturelle
  6. Erreurs : une erreur d’outil repart vers Claude avec is_error, et une entrée du modèle ne passe jamais par eval

Crédits et licence — Adaptation française du notebook « 06_chatbot_with_multiple_tools » du tutoriel Tool use d’Anthropic (dépôt GitHub anthropics/courses), publié sous licence CC BY-NC 4.0 ; adaptation remaniée et mise à jour par Corsen.