درس 7 — Fiabiliser en production

Retries, repli, cache et limites de coûts

5 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Ce qui peut échouer

PanneSignal typiqueRéponse adaptée
Surcharge, quotaHTTP 429, 529 (Claude « overloaded »), RESOURCE_EXHAUSTED sur Vertex AIRetry avec backoff exponentiel + aléa
Erreur serveur transitoireHTTP 500, 503Retry limité
LenteurPas de réponse après N secondesDélai maximal, puis repli
Sortie invalideSchéma non respectéUn nouvel essai avec le message d'erreur, puis repli
Requête invalideHTTP 400Pas de retry : c'est un bug
stateDiagram-v2
  [*] --> Principal
  Principal --> Succes : "réponse valide"
  Principal --> Retry : "429 / 5xx / timeout"
  Retry --> Principal : "essai suivant (backoff)"
  Retry --> Repli : "essais épuisés"
  Principal --> Repli : "sortie invalide 2 fois"
  Repli --> Succes : "modèle de repli OK"
  Repli --> Degrade : "échec"
  Degrade --> [*]
  Succes --> [*]

Retry avec backoff exponentiel

Les SDK officiels (Anthropic, Google Gen AI) intègrent déjà des retries configurables : commencez par eux (max_retries côté client Anthropic, options HTTP côté Google). Si vous devez l'écrire vous-même :

resilience.py
import random, time

class ErreurTransitoire(Exception):
    """429, 5xx, timeout : à relancer."""

def avec_retry(appel, essais=4, base=1.0, plafond=20.0):
    for i in range(essais):
        try:
            return appel()
        except ErreurTransitoire:
            if i == essais - 1:
                raise
            attente = min(plafond, base * 2 ** i) * random.uniform(0.5, 1.0)  # aléa : évite les rafales
            time.sleep(attente)

Modèle de repli

Un repli peut être un autre modèle (version plus légère, autre région, autre fournisseur) ou une réponse dégradée (« le service est momentanément indisponible, voici les liens vers la documentation »).

repli.py
def repondre_resilient(question, principal, secondaire):
    try:
        return avec_retry(lambda: principal(question)), "principal"
    except ErreurTransitoire:
        try:
            return avec_retry(lambda: secondaire(question), essais=2), "secondaire"
        except ErreurTransitoire:
            return {"reponse": "Service momentanément indisponible. Consultez l'intranet RH.",
                    "sources": []}, "degrade"

Quelle erreur ne doit PAS déclencher de retry automatique ?

Cache : ne pas payer deux fois la même réponse

Type de cachePrincipeAttention
Cache exact applicatifClé = hash(modèle + prompt + question normalisée)Invalider quand l'index ou le prompt change (inclure leur version dans la clé)
Mise en cache de prompt (Claude)Le préfixe stable (prompt système, documents) est mis en cache côté APIStructurer le prompt : partie stable en premier
Context caching (Vertex AI / Gemini)Un gros contexte réutilisé est stocké côté GoogleCoût de stockage, durée de vie à fixer
Cache sémantiqueQuestions proches → même réponseRisque de servir une réponse à une question différente : à évaluer
cache.py
import hashlib, json
from functools import lru_cache

VERSION = {"prompt": "v7", "index": "2026-10-01", "modele": "gemini-2.5-flash"}

def cle(question):
    q = " ".join(question.lower().split())
    return hashlib.sha256(json.dumps([VERSION, q]).encode()).hexdigest()

# ponytail: cache en mémoire par processus ; Memorystore/Redis si plusieurs instances
@lru_cache(maxsize=5000)
def _repondre_cache(cle_q, question):
    return repondre_protege(question, appeler_rag)  # leçon précédente

def repondre_avec_cache(question):
    return _repondre_cache(cle(question), question)

Limites de coûts

  1. Par appel : max_tokens (Claude) ou max_output_tokens (Gemini), nombre maximal d'extraits dans le contexte.
  2. Par utilisateur : quota de requêtes par jour.
  3. Global : alertes de budget Cloud Billing sur GCP, limites de dépenses dans la console Anthropic.
  4. Par campagne d'évaluation : le juge coûte aussi ; estimez avant de lancer (cas × critères × répétitions).

Vous ajoutez un cache exact. Quels éléments doivent faire partie de la clé ?

Exercice : estimer le budget d'une campagne d'évaluation

Votre jeu doré compte 30 cas. Chaque cas est exécuté 3 fois. Chaque exécution coûte un appel à l'assistant et deux appels au juge (fidélité, complétude). Combien d'appels LLM au total ? Que proposez-vous pour réduire ce coût en CI sans perdre en sécurité ?

Voir la solution

30 cas × 3 répétitions × (1 assistant + 2 juge) = 270 appels.

Pistes pour la CI :

  • Lancer d'abord les contrôles par code (gratuits) et n'appeler le juge que si le cas passe ces contrôles (un cas déjà en échec n'a pas besoin d'être jugé).
  • Répéter 3 fois seulement les cas marqués instables, une fois les autres.
  • Utiliser un modèle juge plus léger, après avoir vérifié son accord avec l'humain.
  • Mettre en cache les réponses de l'assistant pour une même version de prompt/index/modèle : relancer la CI sans changement ne coûte rien.

Le jeu complet avec répétitions peut tourner chaque nuit, la version allégée à chaque pull request.

Je sais faire

  • Distinguer erreurs transitoires (retry) et erreurs permanentes (pas de retry).
  • Implémenter un backoff exponentiel avec aléa et un nombre d'essais borné.
  • Mettre en place un modèle de repli lui-même évalué.
  • Construire une clé de cache qui inclut versions et périmètre de droits.
  • Borner les coûts par appel, par utilisateur, globalement et par campagne d'évaluation.
التالي