Débat audio sur l'article :
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 | 1× | Base de référence |
| Cache write (TTL 5 min) | 1,25× | One-time, renouvelé si expiry |
| Cache write (TTL 1h) | 2× | 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
- Documentation officielle Anthropic — Prompt Caching
- Arxiv — Don't Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks
- Medium / Joe Njenga — Anthropic Just Fixed the Biggest Hidden Cost in AI Agents (Automatic Prompt Caching)
- ngrok blog — Prompt Caching: 10x cheaper LLM tokens
- Anthropic News — Prompt caching with Claude
Articles connexes
- Optimisation des tokens dans Claude Code
- Comment fonctionnent vraiment les tokens dans Claude Code
- Surveillance des agents IA : les gardes-fous qu'on ne voit pas
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.
Prompt Caching Anthropic : pourquoi cache_read = 0 et comment corriger