IAMaîtriser l'IA générative Plan du corpus
Accueil/Prompt engineering : techniques avancees/Outils et function calling : brancher le modèle sur tes données

Outils et function calling : brancher le modèle sur tes données

Le modèle ne peut pas lire ta base. Mais tu peux lui donner un outil pour la lire, et il saura quand s'en servir. Tout se joue dans la description de l'outil.

Temps de lecture : 15 min | Niveau : Avancé

Ce que tu sauras faire après

  • Expliquer la boucle appel / exécution / résultat sans te tromper d'acteur.
  • Écrire une définition d'outil complète : name, description, input_schema.
  • Reconnaître une description d'outil trop vague et la réparer.
  • Choisir entre laisser le modèle décider et forcer un appel d'outil.
  • Renvoyer une erreur d'outil proprement, sans casser la conversation.

1. Le principe, sans jargon

Tool use (aussi appelé function calling) veut dire : tu décris à l'avance des fonctions que le modèle peut demander à exécuter.

Point crucial et souvent mal compris : le modèle n'exécute rien. Il te renvoie une demande structurée. C'est ton code qui exécute et qui renvoie le résultat.

Définition de la doc : "Tool use (also called function calling) lets Claude call functions that you define or that Anthropic provides. Claude determines when to call a tool based on the user's request and the tool's description. It then returns a structured call that your application executes (client tools) or that Anthropic executes (server tools)."

Deux familles :

  • Client tools : ton code les exécute. Ta base de données, ton API interne, ton système de fichiers.
  • Server tools : exécutés par Anthropic. Recherche web, exécution de code en bac à sable, récupération de page web.

Dans ton métier, ce sont les client tools qui t'intéressent : query_database, run_dbt_model, get_airflow_dag_status, read_schema.


2. La boucle appel / résultat

Quatre temps. Toujours les mêmes.

1. TOI     -> API : la question + la liste des outils disponibles
2. API     -> TOI : stop_reason = "tool_use", avec un bloc tool_use
                     { name: "query_database", input: {...}, id: "toolu_01..." }
3. TOI            : ton code execute vraiment la requete
4. TOI     -> API : la conversation + un bloc tool_result portant le meme tool_use_id
5. API     -> TOI : la reponse finale en texte

En Python, ça donne ça. Note bien : deux appels API, pas un.

import anthropic

client = anthropic.Anthropic()

outils = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a given location.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City and state, e.g. San Francisco, CA"}
            },
            "required": ["location"],
        },
    }
]
messages = [{"role": "user", "content": "Quel temps fait-il a San Francisco ?"}]

# 1er appel : le modele demande l'outil
reponse = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=outils,
    messages=messages,
)
appel = next(b for b in reponse.content if b.type == "tool_use")

# ton code execute reellement
resultat = ma_fonction_meteo(appel.input["location"])

# 2e appel : tu renvoies le resultat
messages += [
    {"role": "assistant", "content": reponse.content},
    {"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": appel.id, "content": resultat}
    ]},
]
finale = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=outils,
    messages=messages,
)

Trois détails à ne pas rater :

  • Le tool_result part dans un message de rôle user. C'est contre-intuitif, mais c'est la structure de l'API.
  • Le tool_use_id doit correspondre exactement à l'id reçu.
  • Tu dois repasser la liste des outils à chaque appel.

Si tu ne veux pas écrire cette boucle toi-même, les SDK proposent un Tool Runner qui exécute tes fonctions et renvoie les résultats automatiquement.


3. L'anatomie d'une définition d'outil

Quatre champs, dont un optionnel.

ChampRôleContrainte
nameLe nom de la fonctionDoit matcher ^[a-zA-Z0-9_-]{1,128}$
descriptionLe champ qui décide de toutTexte libre, détaillé
input_schemaLes paramètres, en JSON SchemaObjet JSON Schema valide
input_examples(Optionnel) exemples d'entrées validesChaque exemple doit valider le schéma

Exemple minimal officiel :

{
  "name": "get_weather",
  "description": "Get the current weather in a given location",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "The city and state, e.g. San Francisco, CA"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "The unit of temperature, either 'celsius' or 'fahrenheit'"
      }
    },
    "required": ["location"]
  }
}

4. La description : le facteur numéro un

La doc ne laisse aucune ambiguïté : "Provide extremely detailed descriptions. This is by far the most important factor in tool performance."

Elle liste ce que la description doit contenir :

  • Ce que l'outil fait.
  • Quand il doit être utilisé, et quand il ne doit pas l'être.
  • Ce que chaque paramètre veut dire et comment il change le comportement.
  • Les limites et pièges, y compris ce que l'outil ne renvoie pas.

Et elle donne une cible chiffrée : "Aim for at least 3–4 sentences for each tool description, more if the tool is complex."

L'article d'ingénierie d'Anthropic va plus loin et dit que des retouches précises sur les descriptions d'outils ont suffi à faire passer un modèle en tête d'un benchmark de code. La description d'outil est un prompt. Traite-la comme tel.

Exemple officiel : bonne description

{
  "name": "get_stock_price",
  "description": "Retrieves the current stock price for a given ticker symbol. The ticker symbol must be a valid symbol for a publicly traded company on a major US stock exchange like NYSE or NASDAQ. The tool will return the latest trade price in USD. It should be used when the user asks about the current or most recent price of a specific stock. It will not provide any other information about the stock or company.",
  "input_schema": {
    "type": "object",
    "properties": {
      "ticker": {
        "type": "string",
        "description": "The stock ticker symbol, e.g. AAPL for Apple Inc."
      }
    },
    "required": ["ticker"]
  }
}

Exemple officiel : mauvaise description

{
  "name": "get_stock_price",
  "description": "Gets the stock price for a ticker.",
  "input_schema": {
    "type": "object",
    "properties": {
      "ticker": {"type": "string"}
    },
    "required": ["ticker"]
  }
}

La doc commente : la bonne description dit quoi, quand, ce qui est retourné et ce que signifie le paramètre. La mauvaise laisse le modèle avec des questions ouvertes.


5. Cas concret : query_database

C'est l'outil que tu vas écrire en premier. On le fait deux fois.

AVANT - description trop vague

{
  "name": "query_database",
  "description": "Interroge la base de donnees.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {"type": "string"}
    },
    "required": ["query"]
  }
}

Ce qui va se passer, dans l'ordre :

  1. Le modèle ne sait pas quel dialecte écrire. Il te sort du T-SQL sur BigQuery.
  2. Il ne sait pas quelles tables existent. Il invente dim_customers alors que la table s'appelle dim_client.
  3. Il ne sait pas s'il a le droit d'écrire. Il tente un DELETE.
  4. Il ne sait pas combien de lignes reviennent. Il lance un SELECT * sur 4 To.
  5. Il ne sait pas quand ne pas utiliser l'outil. Il l'appelle pour répondre à "c'est quoi une CTE ?".

Cinq problèmes, une seule cause : la description.

APRÈS - description correcte

{
  "name": "query_database",
  "description": "Execute une requete SELECT en lecture seule sur l'entrepot BigQuery de production (projet 'acme-dwh', dataset 'marts') et renvoie les lignes au format JSON. Utilise cet outil des que la reponse depend de donnees reelles : chiffres, volumes, dates, listes de valeurs existantes. N'utilise PAS cet outil pour des questions generales sur le SQL, pour expliquer un concept, ou quand la reponse est deja dans la conversation. Le dialecte est GoogleSQL (BigQuery standard), pas Legacy SQL et pas T-SQL. Seules les instructions SELECT et WITH sont autorisees : toute requete contenant INSERT, UPDATE, DELETE, MERGE, CREATE ou DROP est rejetee et renvoie une erreur. La requete doit etre qualifiee en `dataset.table`. Chaque appel est limite a 1000 lignes et a 50 Go scannes ; au-dela, l'outil renvoie une erreur et tu dois ajouter un filtre sur la colonne de partition. L'outil ne renvoie PAS le schema des tables : appelle d'abord 'describe_table' si tu n'es pas sur des colonnes disponibles.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "La requete GoogleSQL complete. Tables qualifiees en dataset.table, par exemple 'marts.fct_commandes'. Inclure toujours un filtre sur la colonne de partition (date_commande) quand la table est partitionnee."
      },
      "max_rows": {
        "type": "integer",
        "description": "Nombre maximum de lignes a renvoyer, entre 1 et 1000. Par defaut 100. Mets une valeur basse quand tu veux seulement verifier la forme du resultat.",
        "minimum": 1,
        "maximum": 1000
      },
      "dry_run": {
        "type": "boolean",
        "description": "Si true, la requete n'est pas executee : l'outil renvoie seulement le volume estime de donnees scannees et la validite syntaxique. Utilise dry_run=true avant toute requete que tu juges couteuse."
      }
    },
    "required": ["query"]
  },
  "input_examples": [
    {"query": "SELECT COUNT(*) AS n FROM marts.fct_commandes WHERE date_commande >= '2026-09-01'", "max_rows": 1},
    {"query": "SELECT canal, SUM(montant_ht) AS ca FROM marts.fct_commandes WHERE date_commande BETWEEN '2026-09-01' AND '2026-09-30' GROUP BY canal", "max_rows": 20},
    {"query": "SELECT * FROM marts.fct_commandes WHERE date_commande >= '2026-01-01'", "dry_run": true}
  ]
}

Pourquoi c'est mieux, point par point

Ce qu'on a ajoutéLe problème que ça résout
Le dialecte explicitePlus de T-SQL sur BigQuery
Le projet et le datasetLe modèle qualifie correctement
"N'utilise PAS pour..."Plus d'appel inutile sur une question de cours
La liste des verbes interditsPlus de tentative d'écriture
La limite de 50 Go + partitionPlus de scan complet
"Ne renvoie PAS le schéma"Le modèle appelle describe_table avant
dry_runLe modèle peut estimer avant d'exécuter
input_examplesLe modèle voit la forme attendue

Le paragraphe le plus rentable est celui qui dit ce que l'outil ne fait pas. C'est exactement ce que recommande la doc : "Any important caveats or limitations, such as what information the tool does not return."


6. Les cinq erreurs classiques

Erreur 1 : description trop courte

Tu viens de voir le cas. Trois à quatre phrases minimum, plus si l'outil est complexe.

Erreur 2 : trop d'outils granulaires

Conseil de la doc : "Consolidate related operations into fewer tools. Rather than creating a separate tool for every action (create_pr, review_pr, merge_pr), group them into a single tool with an action parameter."

Mauvais : get_dag_status, get_dag_last_run, get_dag_failures, get_dag_schedule. Bon : un outil airflow_dag_info avec un paramètre info_type en enum.

Erreur 3 : pas de namespace

Dès que tes outils touchent plusieurs systèmes, préfixe par le service : bigquery_query, airflow_trigger_dag, dbt_run_model. La doc le recommande, et l'article d'ingénierie aussi : "namespacing tools by service (e.g., asana_search, jira_search) ... can help agents select the right tools at the right time."

Erreur 4 : réponses d'outil trop bavardes

Conseil de la doc : "Design tool responses to return only high-signal information. Return semantic, stable identifiers (for example, slugs or UUIDs) rather than opaque internal references, and include only the fields Claude needs."

Concrètement : ne renvoie pas le dump JSON complet de l'API Airflow. Renvoie l'état, la date, et le message d'erreur. Le reste remplit le contexte pour rien.

Erreur 5 : oublier de décrire les paramètres

Chaque propriété du input_schema mérite sa propre description. {"ticker": {"type": "string"}} ne dit rien. Utilise aussi enum, minimum, maximum : ce sont des contraintes que le modèle lit.


7. Contrôler le déclenchement

Par le prompt

La doc dit que la frontière est pilotable : une consigne légère comme "Use the tools to investigate before responding." augmente l'usage ; "Always call a tool first before responding." pousse plus fort ; "Use your judgment about whether to call a tool or respond directly." le rend plus conservateur.

Autre point utile : les modèles récents suivent les instructions au pied de la lettre. "Can you suggest some changes" produit des suggestions, pas des modifications. Si tu veux une action, écris "Change this function...".

Par le paramètre tool_choice

Quatre valeurs :

  • auto : le modèle décide. Défaut quand des outils sont fournis.
  • any : il doit appeler un outil, lequel est son choix.
  • tool : il doit appeler cet outil précis.
  • none : aucun outil. Défaut quand aucun outil n'est fourni.

Restrictions importantes en 2026 :

  • Avec l'ancien extended thinking manuel (thinking: {type: "enabled"}), any et tool renvoient une erreur.
  • Sur Claude Opus 5.5, Fable 5.1 et Mythos 5.1, any et tool renvoient une erreur 400 dans tous les cas. Il faut utiliser auto avec strict tool use (strict: true) ou les sorties structurées.

Note complémentaire de la doc : quand tool_choice vaut any ou tool, l'API force le début de réponse, donc le modèle n'écrit pas de phrase d'introduction avant le bloc tool_use, même si tu le lui demandes.

Appels parallèles

Les modèles récents appellent plusieurs outils en parallèle quand les appels sont indépendants. Tu peux fiabiliser ce comportement avec une consigne. Extrait du prompt officiel :

<use_parallel_tool_calls>
If you intend to call multiple tools and there are no dependencies between the tool
calls, make all of the independent tool calls in parallel. ... However, if some tool
calls depend on previous calls to inform dependent values like the parameters, do NOT
call these tools in parallel and instead call them sequentially. Never use placeholders
or guess missing parameters in tool calls.
</use_parallel_tool_calls>

Utile quand tu veux lire quatre tables d'un coup. À l'inverse, si tes appels parallèles saturent ta base, demande une exécution séquentielle.


8. Gérer les erreurs

Quand ton outil échoue, ne renvoie pas une exception brute. Renvoie un tool_result avec is_error et un message que le modèle peut utiliser pour se corriger.

try:
    lignes = executer_bigquery(appel.input["query"], appel.input.get("max_rows", 100))
    contenu, erreur = json.dumps(lignes, ensure_ascii=False), False
except VolumeTropGrand as e:
    contenu = (
        f"Erreur : la requete scannerait {e.go} Go, la limite est 50 Go. "
        f"Ajoute un filtre sur la colonne de partition date_commande, "
        f"ou relance avec dry_run=true pour estimer avant."
    )
    erreur = True
except ColonneInconnue as e:
    contenu = (
        f"Erreur : la colonne '{e.colonne}' n'existe pas dans {e.table}. "
        f"Colonnes disponibles : {', '.join(e.colonnes_reelles)}."
    )
    erreur = True

bloc_resultat = {
    "type": "tool_result",
    "tool_use_id": appel.id,
    "content": contenu,
    "is_error": erreur,
}

Regarde le deuxième cas : tu renvoies la liste des colonnes réelles. Le modèle corrige tout seul au tour suivant. Un message d'erreur d'outil est un prompt déguisé.


9. Ton template prêt à copier

Template A - Définition d'outil complète

{
  "name": "[SERVICE]_[ACTION]",
  "description": "[PHRASE_1 : ce que fait l'outil, sur quel systeme precis]. [PHRASE_2 : quand l'utiliser - 'Utilise cet outil des que ...']. [PHRASE_3 : quand NE PAS l'utiliser - 'N'utilise PAS cet outil pour ...']. [PHRASE_4 : ce qui est renvoye et dans quel format]. [PHRASE_5 : limites, quotas, ce que l'outil ne renvoie PAS, et quel autre outil appeler dans ce cas].",
  "input_schema": {
    "type": "object",
    "properties": {
      "[PARAM_1]": {
        "type": "[string|integer|boolean|array|object]",
        "description": "[CE_QUE_C_EST + FORMAT_ATTENDU + EXEMPLE_CONCRET]"
      },
      "[PARAM_2]": {
        "type": "string",
        "enum": ["[VALEUR_1]", "[VALEUR_2]"],
        "description": "[CE_QUE_CHAQUE_VALEUR_CHANGE]"
      }
    },
    "required": ["[PARAM_OBLIGATOIRE]"]
  },
  "input_examples": [
    {"[PARAM_1]": "[CAS_SIMPLE]"},
    {"[PARAM_1]": "[CAS_COMPLET]", "[PARAM_2]": "[VALEUR_1]"}
  ]
}

Template B - Prompt système qui encadre les outils

Tu es [ROLE] et tu travailles sur [SYSTEME].

Regles d'utilisation des outils :
- Utilise les outils pour investiguer avant de repondre. Ne devine jamais une
  valeur qui existe dans [LA_SOURCE_DE_VERITE].
- Avant d'ecrire une requete sur une table que tu ne connais pas, appelle
  [OUTIL_DE_SCHEMA].
- Si un outil renvoie une erreur, lis le message, corrige, et reessaie une fois.
  Si la deuxieme tentative echoue, explique le blocage a l'utilisateur.
- [ACTION_DANGEREUSE, ex: declencher un DAG en production] : demande toujours
  confirmation avant.

Template C - Auditer un outil existant

Voici la definition d'un de mes outils. Audite-la.

<definition>
[COLLE_TON_JSON]
</definition>

<contexte>
Systeme reel : [DECRIS_LE_SYSTEME]
Ce que l'outil fait vraiment : [DECRIS_LE_COMPORTEMENT_REEL]
Limites reelles : [QUOTAS, DROITS, VOLUMES]
</contexte>

Pour chaque point, dis si c'est present et suffisant :
1. Ce que l'outil fait
2. Quand l'utiliser
3. Quand NE PAS l'utiliser
4. Ce qui est renvoye, dans quel format
5. Ce qui n'est PAS renvoye
6. Description de chaque parametre
7. Contraintes machines (enum, minimum, maximum)

Puis reecris la definition complete corrigee.

À retenir

  • Le modèle n'exécute jamais rien : il demande, ton code exécute, tu renvoies un tool_result.
  • La description est de loin le facteur numéro un. Vise au minimum 3 à 4 phrases.
  • Dis toujours quand ne pas utiliser l'outil et ce qu'il ne renvoie pas.
  • Regroupe les opérations proches dans un outil unique avec un paramètre action, et préfixe par service.
  • tool_choice vaut auto, any, tool ou none ; any et tool sont refusés sur Opus 5.5, Fable 5.1 et Mythos 5.1.
  • Un message d'erreur d'outil est un prompt : donne au modèle ce qu'il faut pour se corriger.

Sources

Corpus personnel de formation · genere le 26/09/2026 · source : 04-outils-et-function-calling.md