Cas Concret : Tracker de Paiements
Un projet complet de A à Z
Dans cette leçon, vous allez construire un tracker de paiements conversationnel complet. L’utilisateur pose des questions en langage naturel sur des transactions, et le système utilise le function calling pour interroger un DataFrame Pandas. Ce cas d’usage est directement inspiré de la documentation officielle Mistral.
Les données
import pandas as pd
import json
import os
from mistralai import Mistral
from functools import partial
# DataFrame de transactions
data = {
"transaction_id": ["T1001", "T1002", "T1003", "T1004", "T1005"],
"customer_id": ["C001", "C002", "C003", "C002", "C001"],
"payment_amount": [125.50, 89.99, 350.00, 45.00, 210.75],
"payment_date": ["2026-03-15", "2026-03-16", "2026-03-17", "2026-03-17", "2026-03-18"],
"payment_status": ["payé", "payé", "en_attente", "payé", "échoué"]
}
df = pd.DataFrame(data)
Les fonctions métier
Définissez 4 fonctions qui couvrent les besoins typiques :
def retrieve_payment_status(df: pd.DataFrame, transaction_id: str) -> str:
"""Récupère le statut d'un paiement."""
if transaction_id in df["transaction_id"].values:
row = df[df["transaction_id"] == transaction_id].iloc[0]
return json.dumps({
"transaction_id": transaction_id,
"status": row["payment_status"]
})
return json.dumps({"error": f"Transaction {transaction_id} introuvable"})
def retrieve_payment_date(df: pd.DataFrame, transaction_id: str) -> str:
"""Récupère la date d'un paiement."""
if transaction_id in df["transaction_id"].values:
row = df[df["transaction_id"] == transaction_id].iloc[0]
return json.dumps({
"transaction_id": transaction_id,
"date": row["payment_date"]
})
return json.dumps({"error": f"Transaction {transaction_id} introuvable"})
def retrieve_payment_details(df: pd.DataFrame, transaction_id: str) -> str:
"""Récupère tous les détails d'un paiement."""
if transaction_id in df["transaction_id"].values:
row = df[df["transaction_id"] == transaction_id].iloc[0]
return json.dumps({
"transaction_id": transaction_id,
"customer_id": row["customer_id"],
"amount": float(row["payment_amount"]),
"date": row["payment_date"],
"status": row["payment_status"]
})
return json.dumps({"error": f"Transaction {transaction_id} introuvable"})
def list_customer_transactions(df: pd.DataFrame, customer_id: str) -> str:
"""Liste toutes les transactions d'un client."""
customer_df = df[df["customer_id"] == customer_id]
if customer_df.empty:
return json.dumps({"error": f"Client {customer_id} introuvable"})
transactions = []
for _, row in customer_df.iterrows():
transactions.append({
"transaction_id": row["transaction_id"],
"amount": float(row["payment_amount"]),
"date": row["payment_date"],
"status": row["payment_status"]
})
return json.dumps({"customer_id": customer_id, "transactions": transactions})
Les spécifications JSON
tools = [
{
"type": "function",
"function": {
"name": "retrieve_payment_status",
"description": "Récupère le statut d'un paiement (payé, en_attente, échoué)",
"parameters": {
"type": "object",
"properties": {
"transaction_id": {
"type": "string",
"description": "Identifiant de la transaction (ex: T1001)"
}
},
"required": ["transaction_id"]
}
}
},
{
"type": "function",
"function": {
"name": "retrieve_payment_date",
"description": "Récupère la date d'un paiement",
"parameters": {
"type": "object",
"properties": {
"transaction_id": {
"type": "string",
"description": "Identifiant de la transaction (ex: T1001)"
}
},
"required": ["transaction_id"]
}
}
},
{
"type": "function",
"function": {
"name": "retrieve_payment_details",
"description": "Récupère tous les détails d'un paiement : montant, date, statut, client",
"parameters": {
"type": "object",
"properties": {
"transaction_id": {
"type": "string",
"description": "Identifiant de la transaction (ex: T1001)"
}
},
"required": ["transaction_id"]
}
}
},
{
"type": "function",
"function": {
"name": "list_customer_transactions",
"description": "Liste toutes les transactions d'un client donné",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Identifiant du client (ex: C001)"
}
},
"required": ["customer_id"]
}
}
}
]
Le dictionnaire de dispatch
available_functions = {
"retrieve_payment_status": partial(retrieve_payment_status, df),
"retrieve_payment_date": partial(retrieve_payment_date, df),
"retrieve_payment_details": partial(retrieve_payment_details, df),
"list_customer_transactions": partial(list_customer_transactions, df),
}
La boucle de conversation
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
model = "mistral-large-latest"
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. "
"Formatez les montants en euros avec 2 décimales."
}
]
def chat(user_input: str) -> str:
"""Tour de conversation complet avec gestion des tool_calls."""
messages.append({"role": "user", "content": user_input})
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
message = response.choices[0].message
# Boucle tant que le modèle veut appeler des fonctions
while message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
fn_name = tool_call.function.name
fn_params = json.loads(tool_call.function.arguments)
if fn_name in available_functions:
result = available_functions[fn_name](**fn_params)
else:
result = json.dumps({"error": f"Fonction {fn_name} inconnue"})
messages.append({
"role": "tool",
"name": fn_name,
"content": result,
"tool_call_id": tool_call.id
})
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
message = response.choices[0].message
messages.append(message)
return message.content
Tester la conversation
# Tour 1
print(chat("Quel est le statut du paiement T1001 ?"))
# -> "Le paiement T1001 a le statut : payé."
# Tour 2
print(chat("Donne-moi tous les détails de cette transaction."))
# -> "Voici les détails du paiement T1001 :
# - Client : C001
# - Montant : 125,50 euros
# - Date : 15 mars 2026
# - Statut : payé"
# Tour 3
print(chat("Quelles sont toutes les transactions du client C002 ?"))
# -> "Le client C002 a 2 transactions :
# - T1002 : 89,99 euros (payé, 16 mars 2026)
# - T1004 : 45,00 euros (payé, 17 mars 2026)"
# Tour 4
print(chat("Y a-t-il des paiements en échec ?"))
# -> "Oui, le paiement T1005 du client C001 (210,75 euros) a échoué."
Points clés à retenir
- Un tracker de paiements complet nécessite 4 fonctions couvrant les cas d’usage principaux
- Le
functools.partiallie le DataFrame aux fonctions pour un dispatch propre - La boucle
while message.tool_callsgère automatiquement les appels séquentiels et parallèles - Le message système guide le modèle sur le ton, la langue et le formatage
- Testez avec des conversations multi-tour pour vérifier que le modèle maintient le contexte