Prompt Caching Anthropic : pourquoi cache_read = 0 et comment corriger

Vous avez cache_read_input_tokens à 0 sur tous vos appels Claude ? Voici le diagnostic, l'antipattern RAG qui bloque le cache, et la correction en SQL et TypeScript.

Débat audio sur l'article :

# DEBATE-027 : Prompt Caching Anthropic — quand le cache de lecture reste à zéro **Source :** drafts/blog/DRAFT-027-prompt-caching-anthropic-cache-read.md **Speakers :** Mathieu (homme), Vivienne (femme) **Duration :** ~9 minutes **Date :** 2026-03-04 **Note :** Termes anglophones traduits en français. Ton neutre, sans émotion. "Cache" conservé comme terme technique accepté en français. --- [Vivienne] Mathieu, tu as découvert un problème dans ta façon d'appeler l'interface d'Anthropic. Un champ dans les métriques t'a alerté. [Mathieu] Oui. En analysant la consommation de jetons de mes pipelines multi-agents, j'ai trouvé un champ systématiquement à zéro. Jetons de cache lus : zéro. Sur tous mes appels. Depuis le premier jour. [Vivienne] Et ce champ devrait avoir une valeur non nulle. [Mathieu] Exactement. Ce champ mesure combien de jetons ont été relus depuis le cache plutôt que retraités. Une valeur à zéro signifie que le cache ne fonctionne pas. Aucune économie réalisée. [Vivienne] Explique ce qu'est le cache de contexte d'Anthropic. [Mathieu] C'est une fonctionnalité de l'interface de programmation Anthropic qui permet de réutiliser des blocs de contexte entre plusieurs requêtes successives. Concrètement, si tu envoies le même message système de cinq mille jetons à chaque appel, tu peux éviter de le faire retraiter à chaque fois. [Vivienne] Le cache est stocké où. [Mathieu] Côté serveurs Anthropic. Pas localement, pas dans ta base de données. Tu n'y as aucun accès direct. Ce qui compte, c'est la structure de tes appels. [Vivienne] Et la durée de vie de ce cache. [Mathieu] Cinq minutes par défaut. Mais chaque lecture réinitialise cette durée. En pratique, dans une session interactive où les requêtes s'enchaînent toutes les trente à soixante secondes, le cache reste vivant indéfiniment. [Vivienne] Parlons des chiffres économiques. [Mathieu] Un jeton entrant normal coûte une unité de référence. Un jeton mis en cache lors de l'écriture coûte un virgule vingt-cinq fois plus — c'est un surcoût ponctuel. Mais chaque lecture depuis le cache ne coûte que zéro virgule dix unité. Soit quatre-vingt-dix pourcent d'économie. [Vivienne] Le retour sur investissement est atteint rapidement. [Mathieu] Dès le deuxième appel. Le surcoût de l'écriture est effacé par la première lecture. Sur vingt appels avec le même message système, l'économie nette dépasse quatre-vingt-huit pourcent. [Vivienne] Alors pourquoi ton cache était-il à zéro. [Mathieu] Il y a une règle critique dans la documentation Anthropic, mentionnée discrètement. Le cache ne s'applique que si le bloc de contexte dépasse mille vingt-quatre jetons. En dessous de ce seuil, aucune écriture en cache ne se produit. Silencieusement. Sans erreur. [Vivienne] L'interface ne signale pas le problème. [Mathieu] Non. Les jetons passent normalement, facturés normalement, sans le moindre avertissement. On peut avoir ce problème pendant des semaines sans le détecter si on ne surveille pas le champ spécifique. [Vivienne] Et dans ton architecture, les blocs étaient en dessous du seuil. [Mathieu] Précisément. Dans mon système multi-agents, les instructions de chaque agent sont stockées dans une base vectorielle. À chaque adoption d'un agent, le système chargeait ses instructions en effectuant plusieurs requêtes distinctes — une par section : rôle, processus, bonnes pratiques, format de sortie. [Vivienne] Chaque requête retournait un petit bloc. [Mathieu] Entre cent et trois cents jetons par section. Vingt à quarante blocs au total pour un agent complet. Aucun ne dépassait le seuil de mille vingt-quatre jetons. Résultat : zéro écriture en cache, zéro lecture en cache, sur tous les appels. [Vivienne] Un papier de recherche documente ce problème. [Mathieu] Oui. Un article de recherche de janvier 2026, intitulé "Ne cassez pas le cache : une évaluation du cache de contexte pour les tâches agents longue durée", documente précisément ce pattern dans les systèmes agents à plusieurs tours. L'injection de contenu dynamique en tête ou le morcellement du contexte invalide le cache sur toute la séquence. C'est l'erreur la plus fréquente dans les architectures agents. [Vivienne] Comment as-tu corrigé le problème. [Mathieu] En consolidant toutes les sections d'un agent en un seul appel SQL qui retourne un unique bloc de texte. J'ai créé une fonction en langage de base de données qui assemble toutes les sections dans le bon ordre : rôle, responsabilités, processus, bonnes pratiques, cas limites, format de sortie. [Vivienne] Et le résultat de cet appel unique. [Mathieu] Un bloc de deux mille à cinq mille jetons selon la richesse des instructions de l'agent. Bien au-dessus du seuil. Le bloc devient éligible au cache. [Vivienne] La consolidation SQL suffit-elle. [Mathieu] C'est nécessaire mais pas suffisant. Il faut aussi structurer le message système dans le bon ordre. Le cache d'Anthropic est basé sur les préfixes : il cache les blocs dans l'ordre, et une lecture depuis le cache exige que le début de la séquence soit identique à la requête précédente. [Vivienne] Si on injecte du contenu dynamique en tête, le cache ne fonctionne pas. [Mathieu] Exactement. Si ton message système commence par la date du jour, l'identifiant de session, ou des résultats de recherche sémantique, tu invalides le cache sur tout ce qui suit. L'architecture optimale sépare clairement les couches stables des couches dynamiques. [Vivienne] Décris cette architecture. [Mathieu] Quatre couches. La première contient les règles globales — le fichier de configuration principal et les règles générales. Environ huit mille jetons, stables par session, marqués pour le cache. La deuxième contient le contexte de l'agent — ses instructions consolidées. Deux mille à cinq mille jetons, stables par session, marqués pour le cache. [Vivienne] Et les deux couches dynamiques. [Mathieu] La troisième contient les résultats de recherche sémantique — les documents retournés à la volée. Ils varient à chaque requête, donc jamais mis en cache. La quatrième, c'est le message utilisateur, toujours unique. La règle d'or : couches stables en tête, couches dynamiques en queue. [Vivienne] Comment marque-t-on un bloc pour le cache dans le code. [Mathieu] Via le champ contrôle de cache dans la structure du message système de l'interface Anthropic. On passe un objet avec le type "éphémère" pour les blocs qu'on veut mettre en cache. Les blocs dynamiques n'ont pas ce champ. [Vivienne] Comment vérifier que le cache fonctionne après la correction. [Mathieu] En lisant les champs d'utilisation retournés par chaque réponse de l'interface. Il y en a quatre : jetons d'écriture en cache, jetons de lecture depuis le cache, jetons entrants normaux, jetons sortants. [Vivienne] Qu'est-ce qu'on doit observer en session active. [Mathieu] Sur le premier appel : une valeur positive pour les jetons d'écriture en cache, zéro en lecture. À partir du deuxième appel : zéro en écriture, et une valeur élevée en lecture — l'équivalent du message système complet. Si la lecture reste à zéro sur tous les appels, il y a un problème. [Vivienne] La comparaison avant et après est parlante. [Mathieu] Nette. Avant la correction : lectures depuis le cache à zéro, coût par appel à cent pourcent de la base. Après : lectures depuis le cache à cinq mille jetons et plus, coût par appel réduit à environ dix pourcent. Sur vingt appels, l'économie atteint quatre-vingt-huit pourcent. [Vivienne] Claude Code, l'outil en ligne de commande, gère-t-il le cache automatiquement. [Mathieu] Oui. Claude Code applique automatiquement le cache sur son message système — le fichier de configuration et les règles. On en bénéficie sans configuration particulière dans l'interface interactive. [Vivienne] Et depuis début 2026, il y a une évolution pour les scripts. [Mathieu] Depuis février 2026, l'interface d'Anthropic propose un cache automatique pour les scripts utilisant le kit de développement. Le système détecte et cache automatiquement les parties statiques du message système sans annotation manuelle. [Vivienne] Mais ça a des limites. [Mathieu] Oui. Pour les architectures bases vectorielles multi-agents avec des instructions consolidées par fonction SQL, l'instrumentation manuelle reste la voie la plus fiable pour contrôler exactement ce qui est mis en cache. [Vivienne] Il y a un cas particulier pour les tâches longues avec des pauses. [Mathieu] Oui. Si tes agents tournent toutes les dix à quinze minutes, la durée de vie par défaut de cinq minutes expirera entre chaque exécution. Dans ce cas, il faut utiliser la durée de vie étendue d'une heure. Le coût de l'écriture monte à deux fois la référence au lieu d'un virgule vingt-cinq, mais chaque lecture reste à zéro virgule dix. [Vivienne] Quels sont les antipatterns à éviter. [Mathieu] Cinq principaux. Premier : injecter du contenu dynamique avant les blocs stables. Deuxième : marquer les résultats de recherche sémantique avec le contrôle de cache — ils changent à chaque requête, donc on paie l'écriture sans jamais relire. [Vivienne] Et les trois suivants. [Mathieu] Troisième : supposer que le cache fonctionne sans vérifier les métriques. C'est l'erreur que j'ai faite pendant des semaines. Quatrième : oublier le seuil de mille vingt-quatre jetons — si tes blocs font huit cents jetons, le cache ne s'appliquera jamais. Et cinquième : changer de modèle entre les appels. Le cache est isolé par modèle. Passer d'un modèle à un autre dans la même session génère une nouvelle clé de cache — aucune lecture garantie. [Vivienne] Le message central de l'article. [Mathieu] Le cache de contexte d'Anthropic est puissant mais il ne fonctionne pas par magie. Il exige deux conditions précises : des blocs de plus de mille vingt-quatre jetons, et une structure qui place le contenu stable avant le contenu dynamique. [Vivienne] Et le point de départ pour quiconque veut vérifier. [Mathieu] Surveiller le champ "jetons de lecture depuis le cache" sur chaque appel. Si c'est à zéro sur tous tes appels, tu sais où chercher. Et la bonne nouvelle : la correction est souvent plus simple qu'on ne le pense. ---


Il y a quelques semaines, j'analysais en détail la consommation de tokens de mes pipelines multi-agents construits avec Claude Code. Je cherchais des gisements d'optimisation — latence, coûts, efficacité du contexte. Et là, en parcourant les métriques retournées par l'API Anthropic, un champ attire mon attention :

cache_read_input_tokens: 0

Systématiquement. Sur tous mes appels. Depuis le début.

Je savais que le prompt caching existait. Je pensais en bénéficier automatiquement. J'avais tort — et cette erreur me coûtait plusieurs dizaines de pourcents sur chaque appel API. Voici ce que j'ai compris, ce que j'avais raté, et comment j'ai corrigé le tir.


Avant de continuer la lecture, je vous invite à vous inscrire à ma newsletter pour connaître en avant-première les futurs sujets traités chaque semaine.



Le prompt caching Anthropic : ce que c'est vraiment

Le prompt caching est une fonctionnalité de l'API Anthropic qui permet de réutiliser des blocs de contexte entre plusieurs requêtes. Concrètement : si vous envoyez le même system prompt de 5 000 tokens à chaque appel, vous pouvez éviter de le faire processer et facturer à chaque fois.

Le cache est stocké côté serveurs Anthropic — pas localement, pas dans votre base de données. Vous n'y avez aucun accès direct. Ce qui compte, c'est la structure de vos appels.

Le cycle de vie du cache

Le TTL (durée de vie) d'un bloc en cache est fixé à 5 minutes. Mais chaque cache hit réinitialise ce TTL. En pratique, dans une session interactive où les requêtes s'enchaînent toutes les 30 à 60 secondes, le cache reste vivant indéfiniment :

Requête t=0  → cache write  (TTL: 5 min)
Requête t=3  → cache HIT    (TTL reset: 5 min)
Requête t=6  → cache HIT    (TTL reset: 5 min)
...          → cache HIT    en continu

La structure économique

C'est là que les chiffres deviennent intéressants :

Type de token Coût relatif Notes
Input normal Base de référence
Cache write (TTL 5 min) 1,25× One-time, renouvelé si expiry
Cache write (TTL 1h) Pour les jobs batch longue durée
Cache read 0,10× 90 % d'économie

Le point d'équilibre est atteint dès le 2e appel : le surcoût du write (25 %) est effacé par la première lecture (90 % d'économie). Sur 20 appels avec le même system prompt, l'économie nette dépasse 88 %.

Sur une session longue avec un system prompt de 8 000 tokens, la réduction globale peut atteindre 60 à 80 % sur les tokens d'entrée. Pour un agent qui tourne des dizaines d'itérations par session, c'est structurellement significatif.


Ce que j'avais raté : le seuil minimum de 1 024 tokens

Voici le détail qui change tout, et que la documentation officielle mentionne discrètement : Anthropic n'applique le cache que si le bloc dépasse 1 024 tokens.

En dessous de ce seuil, aucun cache write n'est effectué. Silencieusement. Sans erreur. Les tokens sont facturés normalement.

Mon problème était exactement là. Et c'est un problème silencieux — l'API ne lève aucune erreur. Les tokens passent sans cache, facturés normalement, sans le moindre avertissement.

Mon antipattern : N petites requêtes RAG

Dans mon architecture multi-agents, les instructions de chaque agent sont stockées dans une base pgvector (RAG). À chaque adoption d'un agent, le système charge ses instructions en effectuant plusieurs requêtes SQL séparées — une par section (rôle, process, best_practices, output_format...).

Résultat : chaque requête retourne un petit bloc de 100 à 300 tokens. Le système prompt est assemblé de ces fragments, chacun bien en dessous du seuil de 1 024 tokens.

Avant (antipattern) :
SELECT * FROM rag WHERE agent_name = 'X' AND section_type = 'role'
→ 141 tokens   ← < 1024, pas de cache

SELECT * FROM rag WHERE agent_name = 'X' AND section_type = 'process'
→ 287 tokens   ← < 1024, pas de cache

SELECT * FROM rag WHERE agent_name = 'X' AND section_type = 'output_format'
→ 198 tokens   ← < 1024, pas de cache

Total : 20-40 blocs, aucun caché → cache_read = 0 sur tous les appels

La recherche académique confirme ce pattern : un paper arxiv de janvier 2026, "Don't Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks", documente précisément ce problème dans les systèmes agentiques multi-turn. L'injection de contenu dynamique en tête — ou le morcellement du contexte — invalide le cache sur toute la séquence. C'est l'erreur la plus fréquente dans les architectures agents.


La correction : consolider en un seul bloc cacheable

La solution est simple à comprendre, un peu plus à mettre en place : consolider toutes les sections d'un agent en un seul appel SQL qui retourne un unique bloc de texte.

La fonction get_agent_context()

CREATE OR REPLACE FUNCTION get_agent_context(p_agent_name TEXT)
RETURNS TEXT LANGUAGE sql STABLE AS $$
  SELECT string_agg(
    '## ' || section_title || E'\n' || content,
    E'\n\n'
    ORDER BY
      CASE section_type
        WHEN 'role'             THEN 1
        WHEN 'responsibilities' THEN 2
        WHEN 'process'          THEN 3
        WHEN 'best_practices'   THEN 4
        WHEN 'edge_cases'       THEN 5
        WHEN 'output_format'    THEN 6
        WHEN 'reference'        THEN 7
        ELSE 8
      END, id
  )
  FROM rag_agent_instructions
  WHERE agent_name = p_agent_name
    AND active = true
    AND metadata->>'type' = 'agent';
$$;

-- Usage : 1 appel → 1 bloc cacheable
SELECT get_agent_context('mgrr-agent-architect');
-- Retourne ~3 000-5 000 tokens → cache_control éligible ✓

Avec cette fonction, un seul appel SQL retourne le contexte complet de l'agent — 2 000 à 5 000 tokens selon la richesse des instructions. Le bloc dépasse largement le seuil de 1 024 tokens. Il devient cacheable.

Après (pattern correct) :
SELECT get_agent_context('X')
→ 3 500 tokens   ← > 1024, cache write ✓

Requête suivante avec le même agent :
→ cache_read_input_tokens: 3 500   ← 90 % d'économie

L'architecture en couches : ce qui se cache et ce qui ne doit pas

La consolidation des instructions agents est une condition nécessaire, mais pas suffisante. Il faut également structurer le system prompt dans le bon ordre.

Le cache Anthropic est prefix-based : il cache les blocs dans l'ordre, et un cache hit exige que le début de la séquence soit identique à la requête précédente. Si vous injectez du contenu dynamique en tête de votre system prompt, vous invalidez le cache sur tout ce qui suit.

L'architecture optimale est celle-ci :

┌─────────────────────────────────────────────────┐
│ COUCHE 1 — Global Rules (cache_control ✓)       │
│ CLAUDE.md + rules/*.md                          │
│ (~8 000 tokens, stable par session)             │
├─────────────────────────────────────────────────┤
│ COUCHE 2 — Agent Context (cache_control ✓)      │
│ get_agent_context(agent_name)                   │
│ (~2 000-5 000 tokens, stable par session)       │
├─────────────────────────────────────────────────┤
│ COUCHE 3 — Résultats RAG dynamiques (SANS cache)│
│ Chunks, documents, learned-skills récents       │
│ (varie par requête → jamais cacher)             │
├─────────────────────────────────────────────────┤
│ COUCHE 4 — Message utilisateur (SANS cache)     │
│ Toujours unique                                 │
└─────────────────────────────────────────────────┘

La règle d'or : couches 1 et 2 en tête et stables → cache hit assuré. Couches 3 et 4 en queue → jamais marquées cache_control.

En code TypeScript avec l'API Anthropic SDK :

const response = await anthropic.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 8096,
  system: [
    {
      type: "text",
      text: globalRules,          // CLAUDE.md + rules/*.md — très stable
      cache_control: { type: "ephemeral" }
    },
    {
      type: "text",
      text: agentContext,         // get_agent_context() — stable par session
      cache_control: { type: "ephemeral" }
    },
    {
      type: "text",
      text: ragResults            // Résultats RAG — dynamiques, SANS cache_control
    }
  ],
  messages: conversation
});

Comment vérifier que le cache fonctionne

Ne supposez pas que le cache est actif. Vérifiez-le via les champs usage de chaque réponse :

const response = await anthropic.messages.create({ /* ... */ });

console.log({
  cache_write:  response.usage.cache_creation_input_tokens,
  cache_read:   response.usage.cache_read_input_tokens,
  input_normal: response.usage.input_tokens,
  output:       response.usage.output_tokens,
});

Ce que vous devez observer en session active :

Appel cache_write cache_read input_normal
1er appel 8 000+ 0 faible
2e appel 0 8 000+ faible
3e+ appels 0 8 000+ faible

Comparaison avant/après correction :

Métrique Avant (antipattern RAG) Après (get_agent_context)
cache_read_input_tokens 0 5 000+
Coût par appel (input tokens) 100 % ~10 %
Économie sur 20 appels 0 % ~88 %

Si cache_read_input_tokens = 0 sur tous vos appels, deux causes possibles : soit les blocs sont trop petits (< 1 024 tokens), soit du contenu dynamique est injecté avant le contenu stable.


Cas particulier : Claude Code CLI et caching automatique

Claude Code gère automatiquement la pose des cache_control sur son system prompt (CLAUDE.md + règles). Vous en bénéficiez sans configuration particulière dans l'interface interactive.

Depuis février 2026, l'API Anthropic propose un automatic prompt caching pour les scripts SDK : le système détecte et cache automatiquement les parties statiques de votre system prompt sans cache_control explicite. Cette évolution cible directement les agents de longue durée — comme le formule Anthropic :

"Running a coding agent for 50 turns with a 10,000-token system prompt previously resulted in paying for 500,000 tokens of the same instructions."

Pour autant, le caching automatique reste limité aux cas simples. Pour les architectures RAG multi-agents avec des instructions consolidées par fonction SQL, l'instrumentation manuelle reste la voie la plus fiable pour contrôler exactement ce qui est mis en cache.

En revanche, pour les scripts custom via l'API SDK — hooks, pipelines batch, agents autonomes — si vous n'utilisez pas le caching automatique, vous devez instrumenter manuellement. C'est précisément là que se trouvait mon problème : mes scripts de chargement des instructions agents ignoraient complètement le cache_control.

Note pour les jobs batch avec longues pauses : si vos workers tournent toutes les 10-15 minutes, le TTL par défaut de 5 minutes expirera entre chaque run. Utilisez le TTL étendu de 1 heure (cache_control: { type: "ephemeral", ttl: "1h" }) — le coût du write monte à 2× (au lieu de 1,25×), mais chaque lecture reste à 0,10×.


Les antipatterns à éviter

Maintenant que vous connaissez la mécanique, voici les erreurs classiques :

1. Injecter du contenu dynamique avant les blocs statiques
Si votre system prompt commence par la date du jour, l'ID de session, ou des résultats de recherche web, vous invalidez le cache sur tout ce qui suit. Mettez toujours le contenu stable en premier.

2. Marquer le contenu RAG avec cache_control
Les résultats de recherche sémantique changent à chaque requête. Les marquer en cache génère un cache write (coût 1,25×) immédiatement suivi d'un cache miss au prochain appel — vous payez plus cher que sans cache.

3. Supposer que le cache fonctionne sans vérifier
C'est l'erreur que j'ai faite pendant des semaines. Loguez toujours cache_read_input_tokens et cache_creation_input_tokens dans vos métriques.

4. Oublier le seuil de 1 024 tokens
Si vos blocs font 800 tokens, le cache ne s'appliquera jamais. Consolidez ou complétez vos instructions jusqu'à dépasser le seuil.

5. Changer de modèle entre les appels
Le cache est isolé par modèle. Passer de claude-sonnet-4-6 à claude-opus-4-6 dans la même session génère un nouveau cache key — cache miss assuré. Gardez un modèle unique par pipeline si vous voulez profiter du cache.


Conclusion

Le prompt caching Anthropic est une fonctionnalité puissante, mais elle ne fonctionne pas "par magie". Elle exige deux conditions précises : des blocs de plus de 1 024 tokens, et une structure de prompt qui place le contenu stable avant le contenu dynamique.

Dans mon cas, l'architecture RAG multi-agents que j'avais construite produisait exactement l'opposé — des dizaines de petits blocs sous le seuil, aucun cache hit. Une fonction SQL de consolidation et une restructuration de l'ordre des couches dans le system prompt ont suffi à corriger ça.

Ce que j'aurais aimé savoir dès le départ : loguez vos métriques d'usage API dès le premier jour. cache_read_input_tokens à zéro, c'est le signal d'alarme. Et la bonne nouvelle, c'est que la correction est souvent plus simple qu'on ne le pense.

Vous avez des questions sur la configuration du cache ou votre architecture RAG ? Répondez à cette newsletter ou laissez un commentaire — je suis curieux de savoir si vous avez rencontré le même antipattern.


Références


Articles connexes



Qui suis je ?

Je suis Mathieu GRENIER, CTO d'Easystrat une startup de Montpellier, en France. Je manage une équipe d'une dizaine d'ingénieurs (Graphistes, IA, frontend, backend, devOps, AWS) en remote depuis le Japon.

J'ai aussi mon activité de freelance, où je conseille des entrepreneurs dans leurs projets d'application.

Avec mon expérience personnelle de plus de 15 ans en ESN, j'ai pu travailler pour un large panel d'entreprises de différentes tailles. Ma compréhension des problèmes métiers est une de mes grandes forces et permet à mes clients de pouvoir se projeter plus facilement.

L'essentiel de mon travail consiste à canaliser l'énergie des entrepreneurs sur l'essence même de leur projet.

La technologie, les méthodes, le management sont le cœur de mes compétences.

Vous pouvez me faire confiance sur ces points là.

Si vous voulez me parler d'un de vos projets, n'hésitez pas à m'envoyer un email avec vos disponibilités à : contact@mathieugrenier.fr


Tous les articles de ce blog sont écrits par moi, même si je peux m'aider de l'IA pour illustrer mes propos. Mais jamais je ne fournis d'articles 100% IA.

Mathieu Grenier 4 mars 2026
Partager cet articlE