J'ai un jour déployé un agent qui lisait un system prompt de 40 000 tokens à chaque requête. Des centaines de requêtes par heure, le même prompt à chaque fois, facturé au prix d'entrée complet à chaque fois. La correction tenait en quatre lignes. Le prompt caching a réduit ce coût récurrent à environ un dixième. Les lectures de cache coûtent environ 0,1× le prix d'entrée de base. C'est le chiffre phare, et il est réel.
Mais voici ce qui m'a frappé en premier : j'ai ajouté le marqueur de cache et rien n'a changé. cache_read_input_tokens restait à zéro. Le cache ne faisait rien en silence, et je payais une prime pour le privilège. Donc avant le mode d'emploi, vous avez besoin de l'unique règle dont tout le reste découle.
Le prompt caching est une correspondance de préfixe
C'est tout. La clé de cache est constituée des octets exacts de votre prompt rendu jusqu'à chaque point d'arrêt de cache. Tout changement n'importe où dans ce préfixe invalide le cache pour tout ce qui suit. Un seul octet. Une clé JSON réordonnée, un horodatage, un outil remplacé — n'importe lequel de ces éléments et vous écrivez une nouvelle entrée de cache au lieu de lire l'ancienne.
L'ordre de rendu est fixe : tools, puis system, puis messages. Donc ce qui se trouve en premier doit être ce que vous avez de plus stable, et ce qui change par requête doit se trouver tout à la fin. Obtenez cet ordre correct et la plupart du caching fonctionne naturellement. Ratez-le et aucun nombre de marqueurs ne vous sauvera.
Ce qui tuait mon cache
C'était ceci, en haut de mon system prompt :
Current date: 2026-06-21 14:32:07
Un horodatage. Il changeait à chaque requête, se trouvait près du début du préfixe, et invalidait tout ce qui venait après. Le prompt entier de 40 000 tokens était impossible à mettre en cache à cause d'une chaîne de 19 caractères à laquelle je n'avais pas pensé.
C'est le bug de caching le plus courant que je vois. Les gens interpolent du contenu dynamique — date actuelle, nom d'utilisateur, ID de session, un flag de mode — dans le system prompt, et cela empoisonne le préfixe. La solution est de geler le system prompt et d'injecter les éléments dynamiques plus tard, dans le tableau messages, où ils n'invalident rien avant eux. Un fait au tour 5 ne touche pas le cache des tours 1 à 4.
Voici ma liste de contrôle d'audit quand un cache ne se déclenche pas. Recherchez dans votre code de construction de prompts :
datetime.now()/Date.now()n'importe où dans le system prompt ou les outilsuuid4()ou des IDs de requête en début de contenujson.dumps(d)sanssort_keys=True— l'ordre d'itération des dictionnaires Python peut faire varier les octets- des f-strings interpolant un ID utilisateur ou de session dans le system prompt
- un ensemble d'outils construit par utilisateur, de sorte que le bloc d'outils diffère à chaque requête
Si cache_read_input_tokens est à zéro sur deux requêtes dont vous savez qu'elles partagent un préfixe, l'une d'elles est la coupable. Comparez les octets rendus entre deux requêtes et vous la trouverez.
Placer le point d'arrêt
Le marqueur réel est cache_control: {type: "ephemeral"} sur un bloc de contenu. Cas le plus simple, un grand system prompt partagé :
"system": [{
"type": "text",
"text": "<your big stable prompt>",
"cache_control": {"type": "ephemeral"}
}]
Comme les outils se rendent avant le système, un marqueur sur le dernier bloc du système met en cache les outils et le système ensemble. Vous disposez d'un maximum de quatre points d'arrêt par requête, donc utilisez-les judicieusement.
Pour les conversations multi-tours, placez le point d'arrêt sur le dernier bloc du tour le plus récent. Chaque nouvelle requête réutilise l'intégralité de la conversation précédente comme préfixe mis en cache, et les succès s'accumulent à mesure que le chat grandit. Pour un préambule partagé avec une question variable — des exemples few-shot plus une requête différente à chaque fois — placez le marqueur à la fin de la partie partagée, jamais à la fin du prompt entier. Si vous mettez en cache jusqu'à la question variable, chaque requête écrit une entrée unique et ne lit rien.
L'économie, parce que ce n'est pas gratuit
Les lectures sont bon marché (~0,1×) mais les écritures coûtent plus qu'une requête normale : 1,25× pour le TTL par défaut de 5 minutes, 2× pour le TTL d'une heure. Le caching n'est donc rentable que si vous lisez plus que vous n'écrivez. Avec le TTL de 5 minutes, vous atteignez le seuil de rentabilité à deux requêtes. Avec le TTL d'une heure, il vous en faut au moins trois, car la prime d'écriture est doublée.
Quel TTL ? Utilisez 1 heure uniquement lorsque votre trafic présente des intervalles de plus de cinq minutes. Si les requêtes arrivent plus fréquemment que cela, elles maintiennent le cache chaud d'elles-mêmes et le TTL par défaut de 5 minutes convient et coûte moins cher. Ne choisissez pas le TTL d'une heure par défaut — le coût d'écriture doublé grignotera silencieusement vos économies si le trafic est régulier.
Deux pièges qui m'ont chacun coûté un après-midi de débogage
Le lookback de 20 blocs. Chaque point d'arrêt remonte au maximum 20 blocs de contenu pour trouver une entrée de cache précédente. Dans une boucle agentique avec de nombreuses paires tool_use/tool_result, un seul tour peut ajouter plus de 20 blocs — et la requête suivante ne trouve plus le cache précédent et échoue silencieusement. Solution : placez un point d'arrêt intermédiaire tous les ~15 blocs dans les longs tours.
Requêtes concurrentes. Une entrée de cache ne devient lisible qu'après que la première réponse commence à être diffusée en continu. Envoyez dix requêtes parallèles avec le même préfixe et les dix paieront le prix plein, car aucune ne peut lire ce que les autres sont encore en train d'écrire. Pour le fan-out, envoyez une requête, attendez le premier token diffusé, puis envoyez les autres. Ils liront le cache que la première a écrit.
Une astuce plus récente qui vaut la peine d'être connue
Sur Opus 4.8, il existe un moyen propre d'injecter une instruction en milieu de conversation sans détruire votre cache : ajoutez un message {"role": "system", ...} au tableau messages au lieu de modifier le system de niveau supérieur. Modifier le system de niveau supérieur change le préfixe avant tout votre historique, de sorte que chaque tour en cache est retraité sans cache. Un message avec le rôle système se place après l'historique et laisse le préfixe en cache intact. C'est aussi le canal opérateur infalsifiable, ce qui est un beau bonus.
Vérifiez tout avec l'objet usage : cache_creation_input_tokens est ce que vous avez écrit, cache_read_input_tokens est ce que vous avez lu au tarif réduit, input_tokens est le reste non mis en cache. Si votre agent a tourné pendant une heure et que input_tokens affiche 4 000, ne paniquez pas — le reste provenait du cache. Vérifiez la somme, pas le champ individuel.
Quatre lignes de configuration, une règle à respecter. Respectez le préfixe et la facture suivra.
