Bonnes Pratiques et Production
De la preuve de concept à la production
Vous avez maintenant toutes les briques pour construire des systèmes de function calling avec Mistral. Cette dernière leçon rassemble les bonnes pratiques, les pièges courants et une checklist pour passer en production avec confiance.
Checklist de déploiement
Avant de mettre en production un système de function calling, vérifiez chaque point :
Architecture
- 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
Sécurité
- 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)
Fiabilité
- 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
Monitoring
- 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)
Optimiser les descriptions de fonctions
La qualité des descriptions a un impact direct sur la précision. Voici les patterns qui fonctionnent :
# 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 impacte la précision et le coût :
# 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 3.2 (rapide, économique)
model = "mistral-small-latest"
# Cas de développement : prototypage, tests
# -> Mistral Medium 3.1 (bon compromis)
model = "mistral-medium-latest"
En production, commencez avec le modèle le plus capable, puis descendez progressivement pour trouver le meilleur rapport coût/performance.
Structurer le message système
Un bon message système fait la différence entre un assistant médiocre et un assistant excellent :
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. Optimisez :
# 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 :
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
Implémentez un monitoring complet :
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, voici les directions à explorer :
- Agents autonomes : combiner le function calling avec une boucle d’agent qui planifie et exécute des tâches complexes
- API Agents Mistral : utiliser
client.beta.agents.create()pour des agents persistants avec état - RAG + Function Calling : le modèle décide quand interroger une base vectorielle
- Streaming : afficher les réponses en temps réel pendant que les fonctions s’exécutent
- Multi-modal : combiner le function calling avec 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