Aller au contenu principal

Le workflow complet du Tool Use

Mis à jour le 29 juillet 2026

Objectifs

  • Implémenter le workflow complet en 4 étapes
  • Écrire des messages tool_result correctement structurés
  • Construire un chatbot qui utilise des outils
Boucle d'exécution du Tool Use avec Claude

Rappel des 4 étapes

Jusqu’ici, nous nous sommes arrêtés à mi-parcours : Claude demandait un outil, nous exécutions la fonction, et l’histoire s’achevait là. Le modèle n’a jamais vu le résultat, donc il n’a jamais pu rédiger de réponse pour l’utilisateur. Cette leçon ferme la boucle. Le cycle complet reste celui que vous connaissez :

  1. Fournir les outils et le prompt (requête API)
  2. Claude demande un outil (réponse API)
  3. Exécuter l’outil et renvoyer le résultat (requête API)
  4. Claude formule sa réponse finale (réponse API)

Exemple : recherche Wikipedia

Le support de démonstration sera un assistant capable de répondre à des questions factuelles récentes en consultant Wikipedia — typiquement le genre de question sur laquelle un modèle seul se trompe ou hésite.

1. La fonction de recherche

La fonction cherche un terme, prend le premier article correspondant et tronque le contenu. Ce plafonnement à 5 000 caractères n’est pas cosmétique : chaque caractère renvoyé consomme du contexte et coûte des tokens, et un article Wikipedia complet peut peser plusieurs dizaines de milliers de caractères.

import wikipedia

def get_article(search_term):
    """Recherche un article Wikipedia et retourne son contenu."""
    results = wikipedia.search(search_term)
    if not results:
        return "Aucun article trouve."
    page = wikipedia.page(results[0])
    return page.content[:5000]  # Limiter la taille

2. La définition de l’outil

Un seul paramètre suffit ici, le terme de recherche. Remarquez que la description ne parle pas de troncature ni de bibliothèque Python : elle décrit le service rendu, pas l’implémentation.

tools = [{
    "name": "get_article",
    "description": "Recherche et retourne le contenu d'un article Wikipedia.",
    "input_schema": {
        "type": "object",
        "properties": {
            "search_term": {
                "type": "string",
                "description": "Le terme de recherche pour trouver l'article Wikipedia"
            }
        },
        "required": ["search_term"]
    }
}]

3. Envoyer le prompt avec l’outil

On conserve la liste messages dans une variable, car elle va s’enrichir à chaque tour et devra être renvoyée intégralement à la requête suivante.

messages = [
    {"role": "user", "content": "Qui a gagne le tournoi des Masters 2024 ?"}
]

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1000,
    tools=tools,
    messages=messages
)

4. Gérer la demande d’outil de Claude

C’est l’étape nouvelle. Claude a répondu par un ToolUseBlock ; il faut en extraire les paramètres, exécuter la fonction, puis reconstruire l’historique de conversation avec trois messages : la question initiale de l’utilisateur, la réponse de l’assistant telle quelle — vous repassez response.content sans le modifier —, et enfin un message user porteur du résultat.

# Extraire la demande d'outil
tool_block = next(b for b in response.content if b.type == "tool_use")
tool_name = tool_block.name
tool_input = tool_block.input

# Executer la fonction
article_content = get_article(tool_input["search_term"])

# Construire le message tool_result
messages = [
    {"role": "user", "content": "Qui a gagne le tournoi des Masters 2024 ?"},
    {"role": "assistant", "content": response.content},
    {
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": tool_block.id,
                "content": article_content
            }
        ]
    }
]

5. Obtenir la réponse finale de Claude

Le second appel est en tout point semblable au premier, à ceci près que l’historique contient désormais la matière première. Claude lit l’extrait d’article et rédige une réponse en langage naturel.

final_response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1000,
    tools=tools,
    messages=messages
)

print(final_response.content[0].text)

Structure du message tool_result

Le point qui provoque le plus d’erreurs en début de projet mérite qu’on s’y arrête. Le résultat d’un outil ne voyage pas dans un rôle spécial : c’est un message user dont le content est une liste de blocs.

{
    "role": "user",
    "content": [
        {
            "type": "tool_result",
            "tool_use_id": "toolu_xxx",  # L'ID du ToolUseBlock
            "content": "Le contenu retourne par l'outil"
        }
    ]
}

Trois règles gouvernent ce bloc. Le tool_use_id doit reproduire à l’identique l’id du ToolUseBlock émis par Claude : c’est lui qui apparie la demande et la réponse, et une valeur inventée provoque une erreur d’API. Le content accepte aussi bien une chaîne qu’une liste de blocs de contenu, ce qui autorise par exemple le renvoi d’une image. Enfin, chaque tool_use présent dans la réponse de Claude appelle son tool_result correspondant : si le modèle a demandé deux outils et que vous n’en renvoyez qu’un, la requête est rejetée.

Implémentation complète automatisée

Recopier ce câblage à la main devient vite fastidieux, d’autant que Claude peut enchaîner plusieurs tours d’outils avant d’être satisfait. La fonction ci-dessous généralise le mécanisme : elle boucle tant que stop_reason vaut "tool_use", exécute chaque outil demandé grâce à un dictionnaire qui associe un nom à une fonction Python, et ne rend la main qu’au moment où Claude produit enfin du texte.

def ask_with_tools(question, tools, tool_functions):
    """
    Pose une question a Claude avec des outils disponibles.
    tool_functions : dict mappant nom_outil -> fonction
    """
    messages = [{"role": "user", "content": question}]

    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=4096,
        tools=tools,
        messages=messages
    )

    # Boucle tant que Claude veut utiliser des outils
    while response.stop_reason == "tool_use":
        # Ajouter la réponse de Claude a l'historique
        messages.append({"role": "assistant", "content": response.content})

        # Traiter chaque demande d'outil
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                # Executer l'outil
                func = tool_functions[block.name]
                result = func(**block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(result)
                })

        # Renvoyer les résultats
        messages.append({"role": "user", "content": tool_results})

        # Nouvelle requête
        response = client.messages.create(
            model="claude-sonnet-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )

    # Retourner la réponse textuelle finale
    return response.content[0].text

L’appel devient alors une seule ligne, et toute la mécanique disparaît derrière l’interface.

answer = ask_with_tools(
    "Qui a gagne le tournoi des Masters 2024 ?",
    tools=tools,
    tool_functions={"get_article": get_article}
)
print(answer)

Aller plus loin : un assistant de recherche documentaire

Reprenez ce pattern pour construire un assistant de recherche documentaire. Il accepte un sujet, demande à Claude de proposer des titres d’articles Wikipedia pertinents, vérifie via un outil que ces articles existent réellement, puis sauvegarde les liens retenus dans un fichier markdown. L’intérêt de l’exercice est de faire coopérer la connaissance du modèle, qui suggère les pistes, et l’outil, qui tranche sur ce qui existe vraiment.

Voir un indice

Définissez un outil generate_wikipedia_reading_list qui accepte un topic (string) et une liste de article_titles (array de strings). Claude générera les titres, l’outil vérifiera leur existence sur Wikipedia.