Bonnes Pratiques et Production
Mis à jour le 29 juillet 2026
De la preuve de concept à la production
Vous avez maintenant toutes les briques pour construire des systèmes de function calling avec Mistral. Ce qui sépare encore votre prototype d’un service exploitable tient moins à des fonctionnalités manquantes qu’à une série de vérifications et de réglages qu’on découvre habituellement en incident. Cette dernière leçon rassemble donc les bonnes pratiques, les pièges courants et une checklist pour passer en production avec confiance.
Checklist de déploiement
Voici le seul endroit de ce cours où la forme de liste s’impose : avant une mise en production, on coche. Quatre domaines sont à couvrir, et chacun répond à une question différente.
L’architecture garantit que la mécanique tient debout. Le contrat de retour en chaîne JSON et la complétude du dispatch sont les deux causes les plus fréquentes de plantage au premier appel réel.
- Toutes les fonctions retournent des chaînes JSON (jamais d’objets Python bruts)
- Le dictionnaire de dispatch est complet et à jour
- Les fonctions gèrent les cas d’erreur et retournent des messages exploitables
- Le message système est clair, précis et testé
- La boucle de conversation gère les appels séquentiels et parallèles
La sécurité part du principe que les arguments viennent d’un utilisateur, jamais de votre application.
- Les arguments sont validés avant exécution (format, type, valeurs)
- Les requêtes SQL utilisent des paramètres (pas de concaténation)
- Une liste blanche limite les fonctions disponibles
- Les actions destructives nécessitent une confirmation
- Le rate limiting est en place par fonction et par utilisateur
- Les résultats sont sanitisés (pas de données sensibles renvoyées au modèle)
La fiabilité traite ce qui échoue sans que ce soit la faute de personne.
- Les erreurs sont renvoyées au modèle dans un format structuré
- Un retry avec backoff est implémenté pour les erreurs transitoires
- Les timeouts sont configurés pour chaque fonction externe
- La taille de l’historique est limitée pour éviter les dépassements de tokens
Le monitoring, enfin, est ce qui vous permettra de diagnostiquer sans deviner.
- Chaque appel de fonction est loggé (nom, arguments, résultat, durée)
- Les erreurs sont alertées (Slack, PagerDuty, etc.)
- Les métriques sont collectées (taux de succès, latence, coût API)
Écrire des descriptions qui font travailler le modèle
La qualité des descriptions a un impact direct sur la précision, et c’est souvent le levier d’amélioration le moins cher. Trois patterns reviennent chez les équipes dont les taux d’appel correct sont bons. Le premier consiste à ancrer la description dans le métier : au lieu de paraphraser le nom de la fonction, on énumère les valeurs de retour possibles et on indique explicitement dans quelle situation l’appeler. Le deuxième glisse des exemples de format dans la description du paramètre, ce qui règle la plupart des erreurs d’extraction d’identifiant. Le troisième remplace une consigne en langage naturel par un enum, seule manière d’interdire réellement une valeur hors liste.
# Pattern 1 : Description avec contexte métier
{
"description": "Récupère le statut d'un paiement. "
"Statuts possibles : payé, en_attente, échoué, remboursé. "
"Utilisez cette fonction quand l'utilisateur demande "
"où en est un paiement ou s'il a été reçu."
}
# Pattern 2 : Paramètres avec exemples
{
"transaction_id": {
"type": "string",
"description": "Identifiant unique au format T suivi de 4 chiffres (ex: T1001, T2345)"
}
}
# Pattern 3 : Enum pour les valeurs limitées
{
"status": {
"type": "string",
"enum": ["payé", "en_attente", "échoué", "remboursé"],
"description": "Filtrer par statut de paiement"
}
}
Choisir le bon modèle
Le choix du modèle arbitre entre précision et coût, et il n’existe pas de réponse universelle : elle dépend du nombre de fonctions exposées et de la complexité de leurs schémas. Un cas complexe, avec beaucoup de fonctions et des paramètres imbriqués, justifie Mistral Large 3, plus fiable dans le choix de la fonction et plus cher. Un cas standard, quelques fonctions simples en production à haut débit, tourne très bien sur Mistral Small 4, rapide et économique. Pour le prototypage et les tests, Mistral Medium 3.5 offre un bon compromis.
# Cas complexes : beaucoup de fonctions, paramètres imbriqués
# -> Mistral Large 3 (plus fiable, plus cher)
model = "mistral-large-latest"
# Cas standards : quelques fonctions simples, production à haut débit
# -> Mistral Small 4 (rapide, économique)
model = "mistral-small-latest"
# Cas de développement : prototypage, tests
# -> Mistral Medium 3.5 (bon compromis)
model = "mistral-medium-latest"
La méthode compte autant que le résultat : en production, commencez avec le modèle le plus capable, mesurez votre taux d’appel correct, puis descendez progressivement pour trouver le meilleur rapport coût/performance. L’inverse — partir du modèle le moins cher — vous prive du point de référence qui permet de savoir si une dégradation vient du modèle ou de vos descriptions.
Structurer le message système
Un bon message système fait la différence entre un assistant médiocre et un assistant excellent. Celui-ci illustre une structure éprouvée : une phrase de rôle, un bloc de règles impératives, puis un rappel des outils disponibles. Les deux règles les plus rentables sont ici « Si l’utilisateur ne fournit pas un identifiant requis, demandez-le poliment » et « Ne devinez jamais un identifiant de transaction » — ensemble, elles suppriment le comportement le plus dommageable qui soit, l’appel de fonction sur un identifiant inventé.
system_message = {
"role": "system",
"content": """Vous êtes un assistant de gestion de paiements pour la société Acme.
Règles :
- Répondez toujours en français
- Formatez les montants en euros avec le symbole et 2 décimales
- Si l'utilisateur ne fournit pas un identifiant requis, demandez-le poliment
- Ne devinez jamais un identifiant de transaction
- Quand une transaction est en échec, proposez de vérifier les détails
Outils disponibles :
- retrieve_payment_status : pour connaître le statut d'un paiement
- retrieve_payment_date : pour connaître la date d'un paiement
- retrieve_payment_details : pour avoir tous les détails d'une transaction
- list_customer_transactions : pour voir toutes les transactions d'un client"""
}
Gérer les coûts en production
Chaque appel de function calling consomme des tokens, et l’addition grimpe par deux canaux qu’on sous-estime : les définitions de tools sont renvoyées à chaque requête, et l’historique s’allonge à chaque tour. Filtrer les fonctions selon le contexte règle le premier point — inutile d’envoyer vos douze outils quand la question porte manifestement sur les paiements. Tronquer l’historique au-delà d’un seuil règle le second. Et lorsque toutes les données nécessaires sont déjà dans le contexte, tool_choice="none" évite un aller-retour supplémentaire.
# 1. Limiter le nombre de fonctions par requête
# Ne passez que les fonctions pertinentes au contexte
def get_relevant_tools(user_input: str, all_tools: list) -> list:
"""Filtre les tools selon le contexte."""
if "paiement" in user_input.lower() or "transaction" in user_input.lower():
return [t for t in all_tools if "payment" in t["function"]["name"]]
if "contact" in user_input.lower() or "client" in user_input.lower():
return [t for t in all_tools if "contact" in t["function"]["name"]]
return all_tools
# 2. Limiter la taille de l'historique
MAX_HISTORY = 20 # messages
# 3. Utiliser tool_choice="none" quand les données sont déjà disponibles
Tester le function calling
Les tests sont essentiels pour éviter les régressions, et ils portent ici sur un objet inhabituel : ce n’est pas la valeur de retour de votre fonction que vous vérifiez, c’est la décision du modèle. Deux familles de tests suffisent à couvrir l’essentiel. La première vérifie qu’une question métier déclenche bien la bonne fonction avec les bons arguments extraits. La seconde, tout aussi importante et souvent oubliée, vérifie qu’une salutation ne déclenche aucun appel — c’est ce test qui détecte les descriptions trop larges, celles qui poussent le modèle à appeler une fonction en toutes circonstances.
def test_payment_status():
"""Teste que le modèle appelle la bonne fonction."""
messages = [{"role": "user", "content": "Statut du paiement T1001 ?"}]
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
message = response.choices[0].message
assert message.tool_calls is not None
assert len(message.tool_calls) == 1
assert message.tool_calls[0].function.name == "retrieve_payment_status"
args = json.loads(message.tool_calls[0].function.arguments)
assert args["transaction_id"] == "T1001"
def test_no_function_for_greeting():
"""Teste que le modèle ne call pas de fonction pour un salut."""
messages = [{"role": "user", "content": "Bonjour !"}]
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
message = response.choices[0].message
assert message.tool_calls is None or len(message.tool_calls) == 0
assert message.content is not None
Monitoring en production
Reste à savoir ce que fait votre système une fois lâché dans la nature. La classe ci-dessous agrège trois signaux qui, ensemble, racontent presque toute l’histoire : le taux de succès global, la latence moyenne, et la ventilation par fonction. C’est cette dernière qui est la plus parlante à l’usage — une fonction dont le compteur d’erreurs dérive isolément pointe vers un problème précis, quand un taux global dégradé ne vous dit rien de l’endroit où chercher.
import time
import logging
logger = logging.getLogger("fc_monitor")
class FunctionCallingMonitor:
def __init__(self):
self.metrics = {
"total_calls": 0,
"successful_calls": 0,
"failed_calls": 0,
"total_latency_ms": 0,
"calls_by_function": {}
}
def record(self, function_name: str, success: bool, latency_ms: float):
self.metrics["total_calls"] += 1
self.metrics["total_latency_ms"] += latency_ms
if success:
self.metrics["successful_calls"] += 1
else:
self.metrics["failed_calls"] += 1
if function_name not in self.metrics["calls_by_function"]:
self.metrics["calls_by_function"][function_name] = {
"count": 0, "errors": 0
}
self.metrics["calls_by_function"][function_name]["count"] += 1
if not success:
self.metrics["calls_by_function"][function_name]["errors"] += 1
def get_success_rate(self) -> float:
if self.metrics["total_calls"] == 0:
return 0.0
return self.metrics["successful_calls"] / self.metrics["total_calls"]
def get_avg_latency(self) -> float:
if self.metrics["total_calls"] == 0:
return 0.0
return self.metrics["total_latency_ms"] / self.metrics["total_calls"]
monitor = FunctionCallingMonitor()
Les prochaines étapes
Maintenant que vous maîtrisez le function calling avec Mistral, plusieurs directions s’ouvrent. La plus naturelle consiste à construire des agents autonomes, en combinant le function calling avec une boucle d’agent qui planifie et exécute des tâches complexes ; l’API Agents Mistral et son client.beta.agents.create() vont plus loin en proposant des agents persistants avec état. Un autre axe associe RAG et function calling : le modèle décide lui-même quand interroger une base vectorielle, au lieu qu’une recherche soit systématiquement déclenchée en amont. Côté expérience utilisateur, le streaming permet d’afficher les réponses en temps réel pendant que les fonctions s’exécutent, ce qui change la perception de la latence sur les chaînes d’appels longues. Enfin, le multi-modal ouvre le function calling à la vision pour analyser des documents.
Points clés à retenir
- La checklist de déploiement couvre l’architecture, la sécurité, la fiabilité et le monitoring
- Des descriptions précises avec exemples et enums améliorent la précision du modèle
- Choisissez le bon modèle selon la complexité et le budget (Large pour la fiabilité, Small pour le débit)
- Le message système est un levier puissant pour guider le comportement
- Testez systématiquement les appels de fonctions et le comportement sans fonction
- Monitorez le taux de succès, la latence et les coûts en production
Testez vos connaissances
Le function calling de bout en bout : cinq vérifications.
1. Quelles sont les cinq étapes du function calling ?
Réponse : Définir les fonctions (schémas), les fournir à l’API, le modèle demande un appel avec ses arguments, votre code exécute et renvoie le résultat, le modèle produit la réponse finale.
2. Qu'est-ce qui fait un bon schéma de fonction ?
Réponse : Des types stricts et des descriptions opérationnelles — nom parlant, quand l’utiliser, format de chaque paramètre : c’est ce texte que le modèle lit pour décider et remplir.
3. Que permettent les appels parallèles ?
Réponse : Le modèle demande plusieurs fonctions en une seule réponse quand elles sont indépendantes — moins d’allers-retours, à condition d’apparier chaque résultat à son appel.
4. À quoi sert tool_choice ?
Réponse : À régler la liberté du modèle : auto (il décide), any (il doit utiliser un outil), ou un outil imposé — ce dernier étant la clé des extractions structurées fiables.
5. Quelles règles de sécurité pour les fonctions exposées ?
Réponse : Valider les arguments reçus, limiter le périmètre de chaque fonction, tracer les appels et garder une validation humaine sur les actions qui engagent — le modèle propose, votre code dispose.
Les deux cas concrets du cours (tracker de paiements, CRM) appliquent exactement ces règles — vos propres intégrations suivront le même moule.