Aller au contenu principal

Bonnes pratiques de production

De la preuve de concept au déploiement

Vous maîtrisez maintenant tous les paramètres de l’API Chat Completions. Cette dernière leçon consolide tout ce que vous avez appris en une checklist de production actionnable, et ouvre la voie vers les formations suivantes.

Checklist de déploiement en production

1. Authentification et sécurité

import os

# TOUJOURS : clé API en variable d'environnement
api_key = os.getenv("MISTRAL_API_KEY")
assert api_key, "MISTRAL_API_KEY non définie"

# JAMAIS : clé en dur dans le code
# api_key = "sk-abc123..."  # INTERDIT en production

# TOUJOURS : rotation régulière des clés
# Créez une nouvelle clé, déployez-la, puis révoquez l'ancienne

2. Modèle versionné

# DÉVELOPPEMENT — dernière version
model = "mistral-large-latest"

# PRODUCTION — version fixe pour la reproductibilité
model = "mistral-large-2501"

# Pourquoi ? Un changement de "latest" peut modifier le comportement
# de votre application sans avertissement.

3. Gestion d’erreurs complète

from mistralai import Mistral
from mistralai.exceptions import MistralAPIException
import time
import logging

logger = logging.getLogger(__name__)

class MistralClient:
    """Client de production avec retry, timeout et logging."""

    def __init__(self, api_key: str, max_retries: int = 3):
        self.client = Mistral(api_key=api_key)
        self.max_retries = max_retries

    def complete(self, messages: list, **kwargs) -> dict:
        for attempt in range(self.max_retries):
            try:
                response = self.client.chat.complete(
                    messages=messages,
                    **kwargs
                )

                result = {
                    "content": response.choices[0].message.content,
                    "finish_reason": response.choices[0].finish_reason,
                    "usage": {
                        "prompt": response.usage.prompt_tokens,
                        "completion": response.usage.completion_tokens,
                        "total": response.usage.total_tokens,
                    },
                    "model": response.model,
                    "id": response.id,
                }

                # Alerter si réponse tronquée
                if result["finish_reason"] == "length":
                    logger.warning(f"Réponse tronquée — req_id={response.id}")

                return result

            except MistralAPIException as e:
                if e.status_code == 429:
                    wait = 2 ** attempt
                    logger.warning(f"Rate limit, retry {attempt+1}/{self.max_retries} dans {wait}s")
                    time.sleep(wait)
                elif e.status_code >= 500:
                    logger.error(f"Erreur serveur {e.status_code}, retry {attempt+1}")
                    time.sleep(1)
                else:
                    logger.error(f"Erreur client {e.status_code}: {e.message}")
                    raise

        raise Exception(f"Échec après {self.max_retries} tentatives")

4. Monitoring des coûts

import json
from datetime import datetime, timezone

class CostTracker:
    """Suivi des coûts API en temps réel."""

    def __init__(self, log_path: str = "api_costs.jsonl"):
        self.log_path = log_path
        self.session_tokens = {"prompt": 0, "completion": 0}

    def log(self, response_data: dict):
        usage = response_data["usage"]
        self.session_tokens["prompt"] += usage["prompt"]
        self.session_tokens["completion"] += usage["completion"]

        entry = {
            "ts": datetime.now(timezone.utc).isoformat(),
            "model": response_data["model"],
            "prompt_tokens": usage["prompt"],
            "completion_tokens": usage["completion"],
            "finish_reason": response_data["finish_reason"],
            "id": response_data["id"],
        }

        with open(self.log_path, "a") as f:
            f.write(json.dumps(entry) + "\n")

    def report(self) -> str:
        total = self.session_tokens["prompt"] + self.session_tokens["completion"]
        return (
            f"Session — Prompt: {self.session_tokens['prompt']:,} | "
            f"Completion: {self.session_tokens['completion']:,} | "
            f"Total: {total:,} tokens"
        )

5. Validation des entrées et sorties

def validate_input(user_message: str) -> str:
    """Valide et nettoie l'entrée utilisateur."""
    if not user_message or not user_message.strip():
        raise ValueError("Message vide")

    if len(user_message) > 10_000:
        raise ValueError("Message trop long (max 10 000 caractères)")

    # Nettoyer les caractères de contrôle
    cleaned = "".join(c for c in user_message if c.isprintable() or c in "\n\t")
    return cleaned.strip()

def validate_output(content: str, finish_reason: str) -> dict:
    """Valide la sortie de l'API."""
    warnings = []

    if finish_reason == "length":
        warnings.append("Réponse potentiellement tronquée")

    if not content or len(content.strip()) < 5:
        warnings.append("Réponse anormalement courte")

    if len(content) > 50_000:
        warnings.append("Réponse anormalement longue")

    return {"content": content, "warnings": warnings, "valid": len(warnings) == 0}

6. Gestion de la fenêtre de contexte

def manage_context(messages: list, max_tokens: int = 100_000) -> list:
    """Garde les messages dans la limite de tokens du modèle."""
    # Estimation : 1 token ~ 4 caractères en français
    total_chars = sum(len(m["content"]) for m in messages)
    estimated_tokens = total_chars // 4

    if estimated_tokens < max_tokens * 0.8:
        return messages  # Tout rentre

    # Garder system + premiers et derniers messages
    system = [m for m in messages if m["role"] == "system"]
    history = [m for m in messages if m["role"] != "system"]

    # Couper les messages les plus anciens
    while estimated_tokens > max_tokens * 0.7 and len(history) > 2:
        removed = history.pop(0)
        estimated_tokens -= len(removed["content"]) // 4

    return system + history

Récapitulatif de la formation

Voici ce que vous avez appris dans ce cours :

Fondamentaux — L’endpoint, les rôles, les paramètres de base, la structure de la réponse.

Streaming — SSE, implémentation Python, stop sequences, sécurisation avec safe_prompt et guardrails.

Prompting — System prompts de production, few-shot, structuration XML/Markdown, anti-patterns.

Paramètres avancés — Température et Top P en détail, pénalités de présence et fréquence, N completions, et cette checklist de production.

Prochaines étapes

Pour aller plus loin avec l’API Mistral, explorez ces formations complémentaires sur Corsen Academy :

  • Embeddings et RAG — Recherche sémantique avec nomic-embed-text et bases vectorielles
  • Function calling — Connecter le modèle à vos outils et APIs externes
  • Fine-tuning — Adapter un modèle à votre domaine spécifique
  • Production et évaluation — Métriques, évals automatisées, optimisation des coûts

Configuration de production recommandée

# Configuration type pour une application en production
PRODUCTION_CONFIG = {
    "model": "mistral-large-2501",      # Version fixe
    "max_tokens": 2000,                  # Adapté au cas d'usage
    "temperature": 0.3,                  # Cohérent et factuel
    "top_p": 1.0,                        # Pas touché si temperature ajustée
    "presence_penalty": 0.0,             # Par défaut sauf besoin spécifique
    "frequency_penalty": 0.0,            # Par défaut sauf besoin spécifique
    "safe_prompt": True,                 # Sécurité activée
}

Points clés à retenir

  • Utilisez des modèles versionnés en production, jamais -latest
  • Implémentez retry + backoff + logging dès le premier déploiement
  • Validez les entrées ET les sorties de chaque appel API
  • Suivez les coûts en temps réel avec un tracker de tokens
  • Gérez la fenêtre de contexte pour les conversations longues
  • Activez safe_prompt comme filet de sécurité minimal
  • Testez vos prompts avec des cas limites avant chaque mise en production