Aller au contenu principal

Appeler l'API avec les Tools

Mis à jour le 29 juillet 2026

Envoyer les tools à l’API Mistral

Vous avez défini vos fonctions et leurs spécifications JSON. Il faut maintenant envoyer tout cela à l’API Mistral et, surtout, comprendre ce que le modèle renvoie. Nous sommes aux étapes 2 et 3 du processus : le modèle reçoit la requête utilisateur accompagnée des outils, et produit un appel de fonction — ou refuse d’en produire un, ce qui est tout aussi instructif.

La première requête

import os
import json
from mistralai import Mistral

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

# Les outils définis dans la leçon précédente
tools = [
    {
        "type": "function",
        "function": {
            "name": "retrieve_payment_status",
            "description": "Récupère le statut d'un paiement à partir de l'identifiant de transaction.",
            "parameters": {
                "type": "object",
                "properties": {
                    "transaction_id": {
                        "type": "string",
                        "description": "Identifiant unique de la transaction (ex: T1001)"
                    }
                },
                "required": ["transaction_id"]
            }
        }
    }
]

# Message de l'utilisateur
messages = [
    {"role": "user", "content": "Quel est le statut du paiement T1001 ?"}
]

# Appel à l'API avec les tools
response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools
)

Comprendre la réponse du modèle

La réponse contient un objet message qui peut prendre deux formes, et votre code doit être écrit pour les deux dès le premier jour. Dans le premier cas, le modèle répond directement parce qu’aucune fonction n’est nécessaire : content porte du texte, tool_calls vaut None.

# response.choices[0].message.content contient la réponse texte
# response.choices[0].message.tool_calls est None
print(response.choices[0].message.content)
# → "Bonjour ! Comment puis-je vous aider ?"

Dans le second, le modèle veut appeler une fonction. La situation s’inverse : content est vide ou nul, et tool_calls porte la charge utile.

# response.choices[0].message.content est None ou vide
# response.choices[0].message.tool_calls contient les appels
message = response.choices[0].message

if message.tool_calls:
    for tool_call in message.tool_calls:
        print(f"Fonction : {tool_call.function.name}")
        print(f"Arguments : {tool_call.function.arguments}")
        print(f"ID : {tool_call.id}")

Sortie typique :

Fonction : retrieve_payment_status
Arguments : {"transaction_id": "T1001"}
ID : call_abc123xyz

Parser la réponse et extraire les tool_calls

Voici le code complet pour extraire proprement les informations d’un tool_call :

message = response.choices[0].message

if message.tool_calls and len(message.tool_calls) > 0:
    tool_call = message.tool_calls[0]

    # Extraire le nom de la fonction
    function_name = tool_call.function.name

    # Parser les arguments JSON en dictionnaire Python
    function_params = json.loads(tool_call.function.arguments)

    # Récupérer l'identifiant unique de l'appel
    tool_call_id = tool_call.id

    print(f"Le modèle veut appeler : {function_name}")
    print(f"Avec les paramètres : {function_params}")
    print(f"ID de l'appel : {tool_call_id}")
else:
    # Le modèle a répondu directement
    print(f"Réponse : {message.content}")

Trois informations sortent de ce bloc, et la troisième est celle qu’on oublie. Le nom de la fonction sert au dispatch, les arguments parsés serviront à l’appel, mais le tool_call_id est ce qui permettra au modèle d’associer votre résultat au bon appel lorsque vous le lui renverrez. Perdez-le, et le tour de conversation devient irrécupérable.

Quand le modèle demande des informations manquantes

Il arrive que l’utilisateur ne fournisse pas assez d’informations. Le modèle a alors une porte de sortie élégante : ne pas appeler de fonction du tout et poser une question clarificatrice.

messages = [
    {"role": "user", "content": "Quel est le statut de mon paiement ?"}
]

response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools
)

message = response.choices[0].message
print(message.content)
# → "J'ai besoin de l'identifiant de transaction pour vérifier le statut.
#     Pourriez-vous me fournir le numéro de transaction ?"

Ce comportement est exactement celui que vous recherchez. Le modèle sait que transaction_id figure dans required ; plutôt que d’inventer un identifiant plausible et de déclencher une requête sur des données inexistantes, il rend la main à l’utilisateur. C’est aussi la meilleure démonstration de l’utilité du champ required vu à la leçon précédente.

Gestion du message système

Le message système vous permet de cadrer ce comportement plutôt que de l’espérer.

messages = [
    {
        "role": "system",
        "content": "Vous êtes un assistant de suivi de paiements. "
                   "Répondez en français. "
                   "Utilisez les outils disponibles pour répondre aux questions "
                   "sur les transactions. Si l'utilisateur ne fournit pas "
                   "l'identifiant de transaction, demandez-le poliment."
    },
    {
        "role": "user",
        "content": "Quel est le statut du paiement T1001 ?"
    }
]

Lisez-le ligne à ligne : la première phrase fixe le rôle, la deuxième le ton et la langue de l’assistant, la troisième précise quand recourir aux fonctions, la dernière donne une instruction explicite sur un cas limite — l’information manquante. Ce sont les trois usages pour lesquels un message système est recommandé, et ils suffisent la plupart du temps.

Code complet de l’étape 2-3

Voici le code complet et réutilisable pour envoyer une requête avec des tools. Il encapsule tout ce qui précède et normalise la réponse en un dictionnaire à deux formes, ce qui vous évitera de disperser des if message.tool_calls dans toute votre application.

import os
import json
from mistralai import Mistral

def call_mistral_with_tools(
    messages: list,
    tools: list,
    model: str = "mistral-large-latest",
    tool_choice: str = "auto"
) -> dict:
    """Envoie une requête à Mistral avec des tools et retourne la réponse parsée."""
    client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

    response = client.chat.complete(
        model=model,
        messages=messages,
        tools=tools,
        tool_choice=tool_choice
    )

    message = response.choices[0].message

    if message.tool_calls and len(message.tool_calls) > 0:
        return {
            "type": "tool_call",
            "message": message,
            "tool_calls": [
                {
                    "id": tc.id,
                    "function_name": tc.function.name,
                    "arguments": json.loads(tc.function.arguments)
                }
                for tc in message.tool_calls
            ]
        }

    return {
        "type": "text",
        "message": message,
        "content": message.content
    }

Remarquez que la compréhension de liste itère sur tous les tool_calls, et non sur le premier seulement. Le modèle peut en produire plusieurs d’un coup ; une leçon ultérieure y sera consacrée, mais votre fonction utilitaire est déjà prête à les recevoir.

Points clés à retenir

  • Envoyez les tools et les messages à client.chat.complete() pour activer le function calling
  • Vérifiez toujours si message.tool_calls existe avant d’essayer de parser
  • Le tool_call_id est obligatoire pour renvoyer le résultat au modèle
  • Les arguments sont une chaîne JSON qu’il faut parser avec json.loads()
  • Le modèle peut choisir de répondre directement s’il n’a pas besoin de fonction ou s’il manque des informations