Aller au contenu principal

Prompt caching avancé

Au-delà du cache automatique

Vous avez vu les bases du prompt caching dans la leçon 3. Cette leçon approfondit les stratégies avancées pour maximiser le taux de cache dans des scénarios complexes : multi-utilisateurs, multi-documents, et pipelines de traitement.

Architecture de prompt pour maximiser le cache

Le principe du préfixe stable maximal

Le cache porte sur le plus long préfixe commun entre vos requêtes. Plus ce préfixe est long et stable, plus vous économisez :

import openai

client = openai.OpenAI()

# Architecture en couches : du plus stable au plus variable

# Couche 1 : instructions globales (ne changent jamais)
INSTRUCTIONS_GLOBALES = """
Vous êtes un assistant d'analyse documentaire pour un cabinet juridique.
Règles :
- Répondez toujours en français
- Citez les articles de loi pertinents
- Structurez en : Analyse / Risques / Recommandations
- Soyez factuel et précis
"""

# Couche 2 : base de connaissances (change rarement)
BASE_CONNAISSANCES = """
[Insérez ici 5000+ tokens de jurisprudence, articles de loi,
 templates d'analyse — ce contenu change une fois par mois maximum]
"""

# Couche 3 : contexte du dossier (change par client)
def construire_prompt(contexte_dossier: str, question: str) -> str:
    return f"""{INSTRUCTIONS_GLOBALES}

{BASE_CONNAISSANCES}

--- Dossier en cours ---
{contexte_dossier}

--- Question ---
{question}"""

Mesurer le taux de cache effectif

class MoniteurCache:
    """Surveille l'efficacité du prompt caching."""

    def __init__(self):
        self.appels: list[dict] = []

    def enregistrer(self, usage) -> dict:
        total_entree = usage.input_tokens
        tokens_cache = getattr(
            usage.input_tokens_details, "cached_tokens", 0
        )
        taux = tokens_cache / total_entree if total_entree > 0 else 0

        entry = {
            "total_entree": total_entree,
            "tokens_cache": tokens_cache,
            "taux_cache": taux,
        }
        self.appels.append(entry)
        return entry

    @property
    def taux_moyen(self) -> float:
        if not self.appels:
            return 0.0
        return sum(a["taux_cache"] for a in self.appels) / len(self.appels)

    def rapport(self) -> str:
        if not self.appels:
            return "Aucun appel."
        return (
            f"Appels: {len(self.appels)} | "
            f"Taux cache moyen: {self.taux_moyen:.1%} | "
            f"Tokens économisés: "
            f"{sum(a[tokens_cache] for a in self.appels):,}"
        )

moniteur = MoniteurCache()

Patterns avancés

Pattern 1 : cache par cohorte

Regroupez les requêtes qui partagent le même contexte pour maintenir le cache chaud :

from collections import defaultdict

def regrouper_par_contexte(
    requetes: list[dict],
) -> dict[str, list[dict]]:
    """Regroupe les requêtes par préfixe commun."""
    groupes = defaultdict(list)

    for requete in requetes:
        # Clé = hash du contexte partagé
        cle = hash(requete.get("contexte_partage", ""))
        groupes[cle].append(requete)

    return dict(groupes)

def traiter_par_cohorte(requetes: list[dict]):
    """Traite les requêtes regroupées pour maximiser le cache."""
    groupes = regrouper_par_contexte(requetes)

    for cle, groupe in groupes.items():
        # Toutes les requêtes du groupe partagent le même préfixe
        for requete in groupe:
            response = client.responses.create(
                model="gpt-5.3",
                instructions=requete["contexte_partage"],
                input=requete["question"],
            )
            moniteur.enregistrer(response.usage)

Pattern 2 : pré-chauffage du cache

Envoyez une requête initiale pour amorcer le cache avant le trafic réel :

def prechauffer_cache(prompt_systeme: str):
    """Envoie une requête minimale pour amorcer le cache."""
    if len(prompt_systeme) // 4 < 1024:
        print("Prompt trop court pour le cache (< 1024 tokens)")
        return

    response = client.responses.create(
        model="gpt-5.3",
        instructions=prompt_systeme,
        input="Confirmez que vous êtes prêt.",
        max_output_tokens=10,
    )

    usage = response.usage
    print(
        f"Cache amorcé : {usage.input_tokens} tokens en entrée, "
        f"{getattr(usage.input_tokens_details, cached_tokens, 0)} en cache"
    )

Pattern 3 : versionner le prompt pour le cache

import hashlib

class PromptVersionne:
    """Gère les versions du prompt pour contrôler le cache."""

    def __init__(self, contenu: str):
        self.contenu = contenu
        self.version = hashlib.md5(contenu.encode()).hexdigest()[:8]

    def est_meme_version(self, autre: "PromptVersionne") -> bool:
        return self.version == autre.version

# Si la version change, le cache sera invalidé
prompt_v1 = PromptVersionne("Instructions v1...")
prompt_v2 = PromptVersionne("Instructions v2 modifiées...")

if not prompt_v1.est_meme_version(prompt_v2):
    print("Attention : changement de prompt, le cache sera invalidé")
    prechauffer_cache(prompt_v2.contenu)

Économies concrètes

Pour un cas réel de 10 000 appels/jour avec un prompt système de 3 000 tokens :

  • Sans cache : 10 000 x 3 000 = 30M tokens en entrée
  • Avec cache (80 % hit) : 6M tokens plein prix + 24M tokens à -50 %
  • Économie : ~40 % sur les tokens d’entrée

Points clés à retenir

  • Structurez vos prompts en couches du plus stable au plus variable
  • Regroupez les requêtes par contexte commun pour maintenir le cache chaud
  • Pré-chauffez le cache avant les pics de trafic
  • Surveillez le taux de cache avec input_tokens_details.cached_tokens
  • Versionnez vos prompts pour anticiper les invalidations de cache