Sur cette page
- Le modèle n'exécute jamais une fonction : il demande à l'appeler, avec des arguments ; votre programme décide.
- Une fonction se décrit par un nom, une phrase qui dit à quoi elle sert, et ses paramètres en schéma JSON.
- Le résultat repart au modèle, qui s'en sert pour répondre, ou demande un autre appel.
- Tout ce qu'un outil rapporte est une donnée, jamais une consigne : une page lue peut contenir un piège.
- Un outil qui écrit, paie ou envoie demande l'accord d'une personne avant d'agir.
Objectif
Donner à un modèle une fonction qui lit le statut d'une facture, le laisser demander l'appel, exécuter la fonction dans votre programme, et obtenir sa réponse finale. Les cinq temps sont les mêmes chez les quatre éditeurs : la demande avec les outils, la demande d'appel, l'exécution par votre code, le renvoi du résultat, la réponse.
Les factures sont fabriquées. Les appels au modèle suivent la documentation de chaque éditeur, lue le 7 octobre 2026, et n'ont pas encore été exécutés par Meridal ; la fonction locale, elle, a tourné.
Données fournies
Une fonction qui lit le statut d'une facture dans un tableau fabriqué, à la place de votre logiciel de facturation. Elle ne fait que lire.
outils.pyFACTURES = { # données fabriquées : à la place, votre logiciel de facturation
"2026-0412": {"statut": "payée", "le": "2026-10-02"},
"2026-0413": {"statut": "en attente", "échéance": "2026-10-31"},
}
def statut_facture(numero):
"""Le statut de paiement d'une facture. Une lecture seulement : l'outil ne modifie rien."""
f = FACTURES.get(numero)
if f is None:
return f"Aucune facture {numero}."
return ", ".join(f"{cle} : {valeur}" for cle, valeur in f.items())essai_outil.pyfrom outils import statut_facture
for numero in ("2026-0412", "2026-0413", "2026-9999"):
print(numero, "→", statut_facture(numero))uv run essai_outil.py2026-0412 → statut : payée, le : 2026-10-022026-0413 → statut : en attente, échéance : 2026-10-312026-9999 → Aucune facture 2026-9999.
Essayé le 7 octobre 2026 avec Python 3.12.7.
Consignes
La demande d'appel arrive comme un élément function_call ; le résultat repart en function_call_output, avec le même call_id. OpenAI conseille d'activer toujours le mode strict, qui exige que chaque objet refuse les champs en plus.
demander.pyfrom dotenv import load_dotenv
load_dotenv()
import json
from openai import OpenAI
from outils import statut_facture
client = OpenAI()
tools = [{
"type": "function",
"name": "statut_facture",
"description": "Donne le statut de paiement d'une facture à partir de son numéro.",
"parameters": {
"type": "object",
"properties": {
"numero": {"type": "string", "description": "Numéro de la facture, par exemple 2026-0412"},
},
"required": ["numero"],
"additionalProperties": False,
},
"strict": True,
}]
input_list = [{"role": "user", "content": "La facture 2026-0412 est-elle payée ?"}]
response = client.responses.create(model="gpt-6-luna", tools=tools, input=input_list)
input_list += response.output
for item in response.output:
if item.type == "function_call" and item.name == "statut_facture":
numero = json.loads(item.arguments)["numero"]
input_list.append({"type": "function_call_output", "call_id": item.call_id,
"output": statut_facture(numero)})
response = client.responses.create(model="gpt-6-luna", tools=tools, input=input_list)
print(response.output_text)uv run demander.pyPas encore exécuté par Meridal. Ce qu'on attend : une phrase qui dit que la facture 2026-0412 est payée, le 2 octobre 2026.
La demande d'appel arrive dans un bloc tool_use ; le résultat repart dans un bloc tool_result, au message suivant, un par appel.
demander.pyfrom dotenv import load_dotenv
load_dotenv()
import anthropic
from outils import statut_facture
client = anthropic.Anthropic()
tools = [{
"name": "statut_facture",
"description": "Donne le statut de paiement d'une facture à partir de son numéro.",
"input_schema": {
"type": "object",
"properties": {
"numero": {"type": "string", "description": "Numéro de la facture, par exemple 2026-0412"},
},
"required": ["numero"],
},
}]
messages = [{"role": "user", "content": "La facture 2026-0412 est-elle payée ?"}]
response = client.messages.create(model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": response.content})
resultats = [{"type": "tool_result", "tool_use_id": b.id, "content": statut_facture(**b.input)}
for b in response.content if b.type == "tool_use"]
messages.append({"role": "user", "content": resultats})
final = client.messages.create(model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages)
print("".join(b.text for b in final.content if b.type == "text"))uv run demander.pyPas encore exécuté par Meridal. Ce qu'on attend : une phrase qui dit que la facture 2026-0412 est payée, le 2 octobre 2026.
La demande d'appel est une étape function_call ; le résultat repart en function_result, rattaché à l'échange précédent par previous_interaction_id.
demander.pyfrom dotenv import load_dotenv
load_dotenv()
from google import genai
from outils import statut_facture
client = genai.Client()
declaration = {
"type": "function",
"name": "statut_facture",
"description": "Donne le statut de paiement d'une facture à partir de son numéro.",
"parameters": {
"type": "object",
"properties": {
"numero": {"type": "string", "description": "Numéro de la facture, par exemple 2026-0412"},
},
"required": ["numero"],
},
}
interaction = client.interactions.create(model="gemini-3.8-flash", input="La facture 2026-0412 est-elle payée ?", tools=[declaration])
appel = next(s for s in interaction.steps if s.type == "function_call")
resultat = statut_facture(**appel.arguments)
final = client.interactions.create(
model="gemini-3.8-flash",
input=[{"type": "function_result", "name": appel.name, "call_id": appel.id,
"result": [{"type": "text", "text": resultat}]}],
tools=[declaration],
previous_interaction_id=interaction.id,
)
print(final.output_text)uv run demander.pyPas encore exécuté par Meridal. Ce qu'on attend : une phrase qui dit que la facture 2026-0412 est payée, le 2 octobre 2026.
Les demandes d'appel arrivent dans tool_calls ; chaque résultat repart en message de rôle tool, avec l'identifiant de l'appel.
demander.pyfrom dotenv import load_dotenv
load_dotenv()
import json
import os
from mistralai.client import Mistral
from outils import statut_facture
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
tools = [{
"type": "function",
"function": {
"name": "statut_facture",
"description": "Donne le statut de paiement d'une facture à partir de son numéro.",
"parameters": {
"type": "object",
"properties": {
"numero": {"type": "string", "description": "Numéro de la facture, par exemple 2026-0412"},
},
"required": ["numero"],
},
},
}]
messages = [{"role": "user", "content": "La facture 2026-0412 est-elle payée ?"}]
response = client.chat.complete(model="mistral-large-latest", messages=messages, tools=tools)
messages.append(response.choices[0].message)
for appel in response.choices[0].message.tool_calls or []:
arguments = json.loads(appel.function.arguments)
messages.append({"role": "tool", "name": appel.function.name,
"content": statut_facture(**arguments), "tool_call_id": appel.id})
final = client.chat.complete(model="mistral-large-latest", messages=messages, tools=tools)
print(final.choices[0].message.content)uv run demander.pyPas encore exécuté par Meridal. Ce qu'on attend : une phrase qui dit que la facture 2026-0412 est payée, le 2 octobre 2026.
Critères de réussite
Indications
Chez Anthropic, une erreur 400 avec tool_choice
CauseClaude Opus 5.5 et Sonnet 5.5 refusent tool_choice à any ou tool.
À faireLaissez auto, la valeur par défaut.
Le modèle répond sans appeler la fonction
CauseLa description ne dit pas assez quand s'en servir, ou la question ne la demande pas.
À faireRéécrivez la description comme une consigne : ce que fait la fonction, et quand l'appeler.
Correction commentée
Ouvrir la correction
Ce qui se passePremier appel : le modèle lit la question et la description de la fonction, et répond par une demande d'appel, statut_facture avec numero à 2026-0412. Votre programme exécute la fonction et obtient « statut : payée, le : 2026-10-02 ». Second appel : le modèle reçoit ce résultat et rédige la réponse.
Ce qui change d'un éditeur à l'autreLes noms : parameters ou input_schema, function_call, tool_use ou tool_calls, et la façon de rattacher le résultat à la demande. Le mécanisme ne change pas.
Ce qui reste à vousLa décision d'exécuter. C'est ce qui permet de refuser un appel, de demander une confirmation, ou de journaliser chaque action.
Extensions
- Les résultats sont des données : un outil qui lit une page web ou un courriel peut rapporter un texte qui se fait passer pour une consigne. Anthropic demande de traiter tout résultat d'outil comme non fiable ; OpenAI, de faire passer les contenus extérieurs par les messages de l'utilisateur, jamais par les consignes du développeur.
- Un outil qui agit : avant de payer, d'écrire ou d'envoyer, une personne valide ; Google demande de confirmer avec l'utilisateur les appels lourds de conséquences, comme une commande.
- Peu d'outils à la fois : moins de vingt pour OpenAI, dix à vingt pour Google ; chaque description se paie en tokens lus.
- Valider avant d'exécuter : vérifier le numéro reçu, son format, et que la personne a le droit de le consulter, avant d'appeler le logiciel.
- La suite : un agent qui enchaîne plusieurs outils, et les serveurs MCP qui les partagent entre assistants : serveurs MCP existants.
Lectures suivantes
Rien avant : c'est un point de départ.
L'appel d'outils
Premiers pas en développement · étape 6 sur 8
Limites et sources
Les appels suivent la documentation des éditeurs du 7 octobre 2026 et n'ont pas encore été exécutés par Meridal ; la fonction locale a tourné avec Python 3.12.7.
Les factures et leurs statuts sont fabriqués.
- OpenAI, Function calling ; Anthropic, Tool use ; Google, Function calling ; Mistral, Function calling.
- Le relevé complet :
apprendre/recherches/2026-10-07-dev-appels-modeles.json.
Écrit par Meridal, avec l'aide de l'IA ; chaque fait vérifié à sa source et relu avant publication. Texte et schémas sous licence CC BY 4.0 : reprenez-les en citant Meridal. Le code sous licence MIT.
Ne plus recopier d'un logiciel à l'autre
1 100 € à 3 400 €, puis sur devis