+200 XP

Tool use et structured outputs

Un modèle qui ne sait produire que du texte est une impasse dès l'instant où vous avez besoin qu'il interroge une base de données en direct, appelle une API ou transmette du JSON propre au service suivant de votre pipeline. Le tool use et les structured outputs sont la façon de combler cet écart avec Claude : vous décrivez des capacités, Claude décide quand les invoquer, et vous récupérez des données que votre code peut réellement consommer.

Cette leçon couvre les deux mécaniques sur l'API Messages d'Anthropic, les formes de requête et de réponse que vous allez effectivement envoyer et parser, et la relation entre les deux techniques.

Ce que « tool use » signifie vraiment

Le tool use (parfois appelé function calling) est un protocole, pas de la magie. Claude n'exécute jamais votre code. Vous lui fournissez une liste de définitions d'outils, chacune avec un nom, une description et un JSON Schema pour ses entrées. Quand Claude estime qu'un outil serait utile, il interrompt la génération de texte et émet à la place une requête structurée : « appelle get_weather avec {"location": "Paris"} ». Votre application exécute cette fonction, renvoie le résultat, et Claude continue.

Cet aller-retour, c'est la boucle de tool use. La comprendre, c'est tout l'enjeu.

Les quatre étapes de la boucle

  1. Vous envoyez un message utilisateur plus votre tableau tools.
  2. Claude répond avec stop_reason: "tool_use" et un bloc de contenu tool_use.
  3. Vous exécutez l'outil et renvoyez la réponse sous forme de tool_result.
  4. Claude lit le résultat et produit sa réponse textuelle finale.

Une boucle get_weather concrète

Voici la boucle complète en Python avec le SDK officiel. Lisez-la une fois, puis nous détaillerons chaque forme.

python
import anthropic

client = anthropic.Anthropic()

tools = [{
    "name": "get_weather",
    "description": "Get current temperature for a given city.",
    "input_schema": {
        "type": "object",
        "properties": {
            "location": {"type": "string", "description": "City name, e.g. 'Paris'"}
        },
        "required": ["location"],
    },
}]

def get_weather(location):
    # Dans la vraie vie, appelez ici une API météo.
    return {"location": location, "temp_c": 14, "conditions": "cloudy"}

messages = [{"role": "user", "content": "What's the weather in Paris right now?"}]

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

# Étape 2 : Claude a demandé un outil.
if response.stop_reason == "tool_use":
    tool_call = next(b for b in response.content if b.type == "tool_use")
    result = get_weather(**tool_call.input)

    # Étape 3 : ajoutez la requête de Claude ET votre résultat.
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [{
            "type": "tool_result",
            "tool_use_id": tool_call.id,
            "content": str(result),
        }],
    })

    # Étape 4 : Claude lit le résultat et rédige la réponse.
    final = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    print(final.content[0].text)

Lire la forme de la réponse

Quand Claude veut un outil, response.content est une liste de blocs. Celui qui nous intéresse ressemble à ceci :

json
{
  "type": "tool_use",
  "id": "toolu_01A09q90qw90lq917835lq9",
  "name": "get_weather",
  "input": {"location": "Paris"}
}

Trois champs comptent. name vous indique quelle fonction exécuter. input est déjà un objet parsé conforme à votre schéma, donc aucun parsing de chaîne. `id` est la référence que vous devez renvoyer.

Renvoyer le résultat

Le détail critique que l'on oublie : vous devez ajouter deux messages, dans l'ordre. D'abord le tour assistant de Claude lui-même (l'intégralité de response.content, y compris le bloc tool_use). Ensuite un tour user portant le tool_result. Le tool_use_id de votre résultat doit correspondre exactement à l'id envoyé par Claude, sinon la boucle casse.

Si votre outil échoue, mettez "is_error": true dans le bloc de résultat. Claude verra l'erreur et pourra s'excuser, réessayer avec d'autres entrées, ou choisir un autre outil.

Outils multiples et appels parallèles

Vous n'expédiez presque jamais un seul outil. Passez-en plusieurs dans le tableau tools et Claude route vers le bon en se fondant sur les descriptions. Rédigez ces descriptions comme de la documentation, car elles sont la seule chose que Claude utilise pour décider. « Get current temperature for a given city » bat « weather tool » à tous les coups.

Claude peut aussi demander plusieurs outils dans un même tour (parallel tool use). Dans ce cas, response.content contient plusieurs blocs tool_use. Exécutez-les tous, puis renvoyez l'ensemble des blocs `tool_result` dans un seul message user avant de rappeler l'API.

Pour boucler jusqu'à ce que Claude ait terminé (il peut enchaîner plusieurs outils), encapsulez l'appel dans une boucle `while response.stop_reason == "tool_use"` au lieu d'un simple `if`.

Structured outputs : récupérer du JSON propre

Le tool use résout « fais quelque chose ». Les structured outputs résolvent « donne-moi des données dans une forme exacte ». Imaginez que vous extrayiez des champs d'un e-mail de support et qu'il vous faille {"category": ..., "urgency": ..., "summary": ...} à chaque fois, sans prose, sans balises markdown.

L'astuce la plus fiable consiste à utiliser le mécanisme d'outils lui-même. Définissez un outil qui représente le schéma de sortie souhaité, puis forcez Claude à l'utiliser.

python
import anthropic

client = anthropic.Anthropic()

extract_tool = {
    "name": "record_ticket",
    "description": "Record the structured fields of a support ticket.",
    "input_schema": {
        "type": "object",
        "properties": {
            "category": {"type": "string", "enum": ["billing", "bug", "feature", "other"]},
            "urgency": {"type": "string", "enum": ["low", "medium", "high"]},
            "summary": {"type": "string", "description": "One sentence."},
        },
        "required": ["category", "urgency", "summary"],
    },
}

email = "I was charged twice this month and need this fixed before Friday!"

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=[extract_tool],
    tool_choice={"type": "tool", "name": "record_ticket"},
    messages=[{"role": "user", "content": email}],
)

ticket = next(b.input for b in response.content if b.type == "tool_use")
print(ticket)
# {'category': 'billing', 'urgency': 'high', 'summary': 'Customer charged twice and wants a fix by Friday.'}

Pourquoi ça marche

Le paramètre tool_choice est le levier. Réglez-le sur {"type": "tool", "name": "record_ticket"} et vous forcez Claude à appeler cet outil précis, ce qui l'oblige à produire un input conforme à votre schéma. Ici, vous n'exécutez aucune fonction. Vous lisez simplement b.input, qui est déjà un objet validé. Aucun bloc de code à nettoyer, aucun JSON à moitié formé.

Les contraintes enum travaillent aussi pour vous. Elles empêchent Claude d'inventer un quatrième niveau d'urgence. Plus votre schéma est serré, plus vos sorties le sont.

Autres valeurs utiles de tool_choice :

  • {"type": "auto"} (par défaut) : Claude décide s'il utilise un outil.
  • {"type": "any"} : Claude doit utiliser *un* outil mais choisit lequel.
  • {"type": "tool", "name": "..."} : force un outil précis, comme ci-dessus.

La documentation d'Anthropic traite en détail les cas limites de schéma et le streaming. Voir le guide tool use pour la référence faisant autorité.

Claude Tool Use Explained

Watch on YouTube

Vérification des acquis

1. Dans le protocole de tool use décrit, que se passe-t-il réellement quand Claude « utilise » un outil ?

2. Pourquoi le tool use et les structured outputs ont-ils de la valeur lorsqu'on intègre Claude dans un pipeline logiciel ?

3. Après avoir renvoyé un tool_result à Claude, que se passe-t-il à la dernière étape de la boucle de tool use ?

CHOIX MULTIPLES

4. Sélectionnez TOUS les éléments qu'une définition d'outil doit contenir lorsque vous envoyez votre tableau tools à Claude.

Sélectionnez toutes les réponses correctes.

CHOIX MULTIPLES

5. Sélectionnez TOUTES les affirmations qui décrivent correctement le comportement de la boucle de tool use.

Sélectionnez toutes les réponses correctes.

Le lien avec MCP et l'Agent SDK

Tout ce qui précède, c'est l'API brute. Vous définissez les outils en ligne et les exécutez vous-même. C'est parfait pour une poignée d'outils sur mesure dans un seul service.

Mais vous voudrez vite des outils réutilisables que n'importe quelle surface Claude peut appeler : l'application desktop, Claude Code, votre propre agent. C'est ce que standardise MCP (Model Context Protocol). Un serveur MCP expose des outils (ainsi que des ressources et des prompts) via un protocole défini : vous écrivez un outil une fois et le branchez sur plusieurs clients. Les applications Claude les présentent sous forme de Connectors, et il existe une marketplace de connecteurs pour les plus courants comme Google Drive ou GitHub.

La relation est nette : les outils MCP et les outils d'API sont la *même idée* à des échelles différentes. La liste d'outils d'un serveur MCP finit par devenir des entrées dans un tableau tools, et le handshake appel/résultat reproduit la boucle que vous venez d'apprendre. Découvrez le protocole sur modelcontextprotocol.io.

Le Claude Agent SDK se situe un niveau au-dessus. Il exécute la boucle de tool use pour vous, gère le contexte et prend en charge le comportement d'agent multi-étapes : vous décrivez des outils et des objectifs plutôt que de coder à la main la machinerie while stop_reason == "tool_use". Quand vous construisez un vrai agent, prenez le SDK. Quand vous avez besoin d'un contrôle chirurgical sur un échange unique, descendez à l'API Messages comme montré plus haut. Les deux parlent le même langage de tool use, et c'est précisément pour cela qu'apprendre la boucle brute est payant.

Pièges pratiques

Quelques points qui vous mordront en production :

  • Budget de tokens. Les définitions d'outils vivent dans la fenêtre de contexte. Vingt outils verbeux coûtent de vrais tokens à chaque appel. Gardez des schémas légers et des descriptions affûtées.
  • Validez tout de même les entrées. Claude respecte généralement votre schéma, mais vous exécutez du vrai code avec ces valeurs. Traitez input comme n'importe quelle entrée utilisateur non fiable : vérifiez les types, bornez les plages, assainissez avant d'attaquer une base de données.
  • Un outil forcé ne peut pas aussi « réfléchir à voix haute ». Quand vous forcez tool_choice sur un outil précis, Claude passe directement à l'appel. Si vous avez besoin de raisonnement d'abord, laissez-le sur auto ou découpez en deux appels.
  • Les erreurs sont un signal, pas un échec. Renvoyer is_error: true avec un message utile permet souvent à Claude de s'auto-corriger au tour suivant, ce qui est plus robuste que de faire planter votre boucle.

Points clés

  • Le tool use est une boucle en quatre étapes : envoyer les outils, recevoir un bloc tool_use, exécuter la fonction, renvoyer un tool_result avec le tool_use_id correspondant. Renvoyez le tour assistant de Claude avant le résultat, sinon la boucle casse.
  • Pour du JSON garanti, forcez un outil. Définissez votre sortie comme un schéma d'outil, réglez tool_choice sur cet outil, et lisez l'input déjà parsé. Utilisez enum et required pour verrouiller la forme.
  • Rédigez les descriptions d'outils comme de la doc. Elles sont la seule chose que Claude utilise pour router : soyez précis et concret.
  • Validez toujours les entrées d'outils dans votre code. Claude propose ; votre application exécute. Traitez les entrées comme non fiables.
  • Montez en échelle de façon délibérée : API Messages brute pour un échange sur mesure, MCP pour des outils réutilisables entre applications, l'Agent SDK quand vous voulez que la boucle soit gérée pour vous. Tous parlent le même protocole.

À faire, tiré de cette leçon

Ces actions sont compilées dans le plan d'action du rôle.

  • Forcez un schéma de tool quand vous avez besoin d'un JSON parsé garanti
Voir le plan d'action complet →