Aller au contenu principal

Exécuter et Retourner le Résultat

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. Maintenant c’est votre code qui prend la main : vous exécutez la fonction, récupérez le résultat et le renvoyez au modèle via un message de type tool.

Exécuter la fonction côté client

Reprenons le dictionnaire de dispatch de la leçon 4 :

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"})

Le pattern **function_params décompresse le dictionnaire JSON en arguments nommés. Si le modèle a généré {"transaction_id": "T1001"}, cela équivaut à appeler retrieve_payment_status(transaction_id="T1001").

Construire le message tool

Le résultat doit être renvoyé au modèle dans un format spécifique — 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
}

Chaque champ est important :

  • 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

Il faut maintenant reconstruire la liste de messages complète et la renvoyer au modèle :

# 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é."

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

Après l’exécution complète, votre liste messages contient 4 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é."

Cette structure est le pattern fondamental du function calling. Toutes les variations (multi-tour, parallèle) en découlent.

Attention au format du content

Le champ content du message tool 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é"}}

Si votre fonction retourne un dictionnaire, convertissez-le avec json.dumps() avant de l’envoyer.

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 le tool_call_id correspondant
  • Le content du 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