Aller au contenu principal

Prompt caching avancé

Mis à jour le 28 juillet 2026

Au-delà du cache automatique

Vous avez vu les bases du prompt caching dans la leçon 3. Le cache y apparaissait comme un mécanisme automatique dont on bénéficie sans rien faire de particulier, et c’est vrai tant que votre application envoie des requêtes bien rangées. La réalité de la production est plus désordonnée : plusieurs utilisateurs frappent la même API en même temps, chacun avec son dossier, ses documents, ses questions. Le préfixe commun se réduit alors à peau de chagrin et le taux de cache s’effondre sans que personne ne s’en aperçoive, puisque rien ne casse — la facture monte, simplement.

Multi-utilisateurs, multi-documents, pipelines de traitement : nous reprendrons ces scénarios complexes un par un pour y maximiser le taux de cache. L’idée directrice tient en une phrase : le cache ne se subit pas, il se conçoit.

Architecture de prompt pour maximiser le cache

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. La conséquence pratique est qu’un prompt doit être organisé du plus stable au plus variable, comme un livre dont les premières pages ne changent jamais et dont seul le dernier chapitre est réécrit à chaque édition.

L’exemple ci-dessous applique ce principe à un assistant d’analyse documentaire pour un cabinet juridique. Trois couches se succèdent : les instructions globales, figées ; la base de connaissances, révisée une fois par mois ; le dossier et la question, qui changent à chaque appel.

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}"""

L’erreur classique consiste à placer le nom du client ou la date du jour en tête du prompt système, pour « personnaliser ». Un seul caractère variable en position initiale suffit à rendre le préfixe unique, donc à réduire le taux de cache à zéro sur l’ensemble des couches situées derrière. Tout ce qui varie descend.

Encore faut-il vérifier que la théorie se traduit en chiffres. Le moniteur suivant lit input_tokens_details.cached_tokens sur chaque réponse et accumule les mesures, ce qui vous donne un taux moyen exploitable plutôt qu’une impression.

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()

Trois patterns qui font la différence

Le premier pattern concerne l’ordre de traitement. Quand vous traitez un lot de requêtes hétérogènes, les faire passer dans l’ordre d’arrivée fait alterner les préfixes et empêche le cache de rester chaud. Le regroupement par contexte partagé résout le problème : on rassemble d’abord, on exécute ensuite, et chaque groupe profite du préfixe amorcé par sa première requête.

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.6-terra",
                instructions=requete["contexte_partage"],
                input=requete["question"],
            )
            moniteur.enregistrer(response.usage)

Le deuxième pattern traite du démarrage à froid. La première requête d’une série paie le plein tarif puisque rien n’est encore en cache ; si cette première requête est celle d’un utilisateur réel un lundi matin à neuf heures, c’est lui qui subit la latence. Une requête d’amorçage envoyée avant l’ouverture du service déplace ce coût là où il ne dérange personne. Notez la garde en début de fonction : en dessous de 1024 tokens, le cache ne s’applique pas et le pré-chauffage serait une dépense pure.

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.6-terra",
        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"
    )

Le troisième pattern rend visible ce qui invalide le cache. Corriger une virgule dans un prompt système de 5 000 tokens semble anodin, et pourtant cette virgule vide le cache pour tout le monde. En attachant une empreinte au contenu, vous détectez le changement au déploiement plutôt que le lendemain sur la facture, et vous pouvez enchaîner immédiatement sur un pré-chauffage.

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)

Ce que cela représente en volume

Prenons un cas réel de 10 000 appels par jour avec un prompt système de 3 000 tokens. Sans cache, vous facturez 10 000 x 3 000, soit 30M de tokens en entrée chaque jour. Avec un taux de succès de 80 %, 6M de tokens restent au plein tarif et 24M passent à moitié prix, ce qui ramène l’économie à environ 40 % sur les tokens d’entrée. Le chiffre paraît modeste rapporté à un appel isolé ; sur une année de production, il change la structure de coût de l’application.

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