Exécuter et Retourner le Résultat
Mis à jour le 29 juillet 2026
L’étape que vous contrôlez entièrement
Vous avez reçu un tool_call du modèle. Il vous dit quelle fonction appeler et avec quels arguments — rien de plus. C’est maintenant votre code qui prend la main : il exécute la fonction, récupère le résultat et le renvoie au modèle sous la forme d’un message de type tool. Cette leçon couvre la totalité de cet aller-retour, et c’est le seul moment du cycle où vous décidez seul.
Exécuter la fonction côté client
Reprenons le dictionnaire de dispatch de la leçon 4 et branchons-le sur la réponse du modèle.
import json
from functools import partial
# Fonctions déjà définies
available_functions = {
"retrieve_payment_status": partial(retrieve_payment_status, df),
"retrieve_payment_date": partial(retrieve_payment_date, df),
}
# Le modèle a retourné un tool_call
tool_call = response.choices[0].message.tool_calls[0]
function_name = tool_call.function.name
function_params = json.loads(tool_call.function.arguments)
# Exécuter la fonction
if function_name in available_functions:
result = available_functions[function_name](**function_params)
print(f"Résultat : {result}")
# → '{"status": "payé"}'
else:
result = json.dumps({"error": f"Fonction {function_name} non disponible"})
Deux mécanismes se jouent ici. Le premier est le **function_params, qui décompresse le dictionnaire issu du JSON en arguments nommés : si le modèle a généré {"transaction_id": "T1001"}, l’appel équivaut exactement à retrieve_payment_status(transaction_id="T1001"). Le second est le else, moins spectaculaire mais indispensable. Un modèle peut halluciner un nom de fonction qui n’existe pas dans votre dispatch ; sans cette vérification, votre application lève un KeyError et la conversation s’arrête net. Avec elle, l’erreur redevient une donnée que le modèle saura formuler.
Construire le message tool
Le résultat doit repartir vers le modèle dans un format précis — un message de rôle "tool" :
tool_message = {
"role": "tool",
"name": function_name,
"content": result, # Doit être une chaîne (string)
"tool_call_id": tool_call.id
}
| Champ | Rôle |
|---|---|
role | "tool" — identifie le message comme un résultat de fonction |
name | Le nom de la fonction exécutée |
content | Le résultat sous forme de chaîne JSON |
tool_call_id | L’identifiant du tool_call original — permet au modèle d’associer le résultat au bon appel |
Renvoyer le résultat et obtenir la réponse finale
Le modèle est sans mémoire d’un appel à l’autre : il ne se souvient pas de vous avoir demandé quoi que ce soit. Vous devez donc lui reconstruire la conversation entière, y compris sa propre demande d’appel, avant de lui présenter le résultat.
# 1. Ajouter le message assistant (avec le tool_call)
messages.append(response.choices[0].message)
# 2. Ajouter le résultat de la fonction
messages.append({
"role": "tool",
"name": function_name,
"content": result,
"tool_call_id": tool_call.id
})
# 3. Rappeler le modèle avec tout le contexte
final_response = client.chat.complete(
model="mistral-large-latest",
messages=messages,
tools=tools
)
# 4. Afficher la réponse finale
print(final_response.choices[0].message.content)
# → "Le paiement T1001 a le statut : payé."
L’ordre des deux append n’est pas négociable : le message assistant porteur du tool_call doit précéder le message tool qui y répond. Inverser les deux produit un historique incohérent que l’API rejette.
Le flux complet en un seul bloc
Voici l’intégralité du processus, de la question utilisateur à la réponse finale :
import os
import json
from mistralai import Mistral
from functools import partial
# Configuration
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
model = "mistral-large-latest"
# Fonctions et outils (définis précédemment)
available_functions = {
"retrieve_payment_status": partial(retrieve_payment_status, df),
"retrieve_payment_date": partial(retrieve_payment_date, df),
}
# Étape 1-2 : Question utilisateur + appel API
messages = [
{"role": "user", "content": "Quel est le statut du paiement T1001 ?"}
]
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
# Étape 3-4 : Extraire le tool_call et exécuter
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_params = json.loads(tool_call.function.arguments)
# Exécution côté client
result = available_functions[function_name](**function_params)
# Étape 5 : Renvoyer le résultat au modèle
messages.append(message)
messages.append({
"role": "tool",
"name": function_name,
"content": result,
"tool_call_id": tool_call.id
})
final_response = client.chat.complete(
model=model, messages=messages, tools=tools
)
print(final_response.choices[0].message.content)
else:
print(message.content)
La structure des messages à chaque étape
Il vaut la peine d’inspecter la liste messages une fois le cycle terminé, car c’est la meilleure façon de déboguer un function calling qui se comporte mal. Elle contient quatre messages.
# Message 1 : la question utilisateur
{"role": "user", "content": "Quel est le statut du paiement T1001 ?"}
# Message 2 : la réponse du modèle avec le tool_call
{"role": "assistant", "tool_calls": [{"id": "call_abc123", ...}]}
# Message 3 : le résultat de la fonction
{"role": "tool", "name": "retrieve_payment_status",
"content": '{"status": "payé"}', "tool_call_id": "call_abc123"}
# Message 4 (implicite) : la réponse finale du modèle
# → "Le paiement T1001 a le statut : payé."
Suivez l’identifiant call_abc123 : il naît dans le message 2 et se retrouve dans le message 3. C’est ce fil qui relie la demande à sa réponse. Cette structure est le pattern fondamental du function calling ; toutes les variations que vous rencontrerez ensuite, multi-tour ou parallèle, ne font que la répéter ou la dupliquer.
Attention au format du content
Une erreur revient plus souvent que toutes les autres, et elle tient au type du champ content : il doit toujours être une chaîne de caractères.
# Correct — chaîne JSON
{"content": '{"status": "payé"}'}
# Correct — texte simple
{"content": "Le statut est payé"}
# Incorrect — dictionnaire Python (provoquera une erreur)
{"content": {"status": "payé"}}
La différence entre le premier et le troisième exemple tient aux apostrophes qui enveloppent le JSON. Si votre fonction retourne un dictionnaire, convertissez-le avec json.dumps() avant de l’envoyer — c’est précisément pourquoi les fonctions de la leçon 4 se terminaient toutes par un json.dumps().
Points clés à retenir
- L’exécution de la fonction se fait côté client, dans votre code — jamais par le modèle
- Le résultat est renvoyé dans un message de rôle
"tool"avec letool_call_idcorrespondant - Le
contentdu message tool doit être une chaîne de caractères (string JSON) - Après avoir renvoyé le résultat, vous rappelez
chat.complete()pour que le modèle formule la réponse finale - Vérifiez toujours que la fonction demandée existe dans votre dictionnaire de dispatch