Una vez desplegué un agent que leía un system prompt de 40K tokens en cada solicitud. Cientos de solicitudes por hora, el mismo prompt cada vez, facturado al precio completo de entrada cada vez. La solución fueron cuatro líneas. El prompt caching redujo ese costo recurrente a aproximadamente una décima parte. Las lecturas de caché cuestan alrededor del 0,1× el precio base de entrada. Ese es el número titular, y es real.
Pero esto es lo que me golpeó primero: añadí el marcador de caché y nada cambió. cache_read_input_tokens se quedó en cero. El caché no hacía nada en silencio, y yo estaba pagando un precio premium por el privilegio. Así que antes del cómo hacerlo, necesitas la única regla de la que todo lo demás se deriva.
El prompt caching es una coincidencia de prefijos
Eso es todo. La clave de caché son los bytes exactos de tu prompt renderizado hasta cada punto de interrupción de caché. Cualquier cambio en cualquier lugar de ese prefijo invalida el caché para todo lo que viene después. Un solo byte. Una clave JSON reordenada, una marca de tiempo, una herramienta cambiada — cualquiera de esas cosas y estás escribiendo una nueva entrada de caché en lugar de leer la antigua.
El orden de renderizado es fijo: tools, luego system, luego messages. Así que lo que esté más al principio tiene que ser lo más estable que tengas, y lo que cambie por solicitud tiene que vivir al final. Consigue ese orden correcto y la mayoría del caching funciona sin esfuerzo. Equivócate y ninguna cantidad de marcadores te salvará.
Lo que estaba matando mi caché
Era esto, en la parte superior de mi system prompt:
Current date: 2026-06-21 14:32:07
Una marca de tiempo. Cambiaba en cada solicitud, estaba cerca del inicio del prefijo, e invalidaba todo lo que venía después. El prompt completo de 40K era imposible de cachear por culpa de una cadena de 19 caracteres en la que no pensé.
Este es el bug de caching más común que veo. La gente interpola contenido dinámico — fecha actual, nombre de usuario, ID de sesión, un flag de modo — en el system prompt, y envenena el prefijo. La solución es congelar el system prompt e inyectar los bits dinámicos más tarde, en el array de messages, donde no invalidan nada antes de ellos. Un hecho en el turno 5 no toca el caché de los turnos 1 al 4.
Esta es mi lista de verificación de auditoría cuando un caché no acierta. Busca en tu código de construcción de prompts:
datetime.now()/Date.now()en cualquier lugar del system prompt o las herramientasuuid4()o IDs de solicitud al principio del contenidojson.dumps(d)sinsort_keys=True— el orden de iteración de diccionarios de Python puede cambiar los bytes- f-strings interpolando un ID de usuario o sesión en el system prompt
- un conjunto de herramientas construido por usuario, de modo que el bloque de herramientas difiera en cada solicitud
Si cache_read_input_tokens es cero en dos solicitudes que sabes que comparten un prefijo, una de esas es la culpable. Compara los bytes renderizados entre dos solicitudes y lo encontrarás.
Colocando el punto de interrupción
El marcador real es cache_control: {type: "ephemeral"} en un bloque de contenido. El caso más simple, un gran system prompt compartido:
"system": [{
"type": "text",
"text": "<your big stable prompt>",
"cache_control": {"type": "ephemeral"}
}]
Debido a que las herramientas se renderizan antes que el sistema, un marcador en el último bloque del sistema cachea las herramientas y el sistema juntos. Tienes un máximo de cuatro puntos de interrupción por solicitud, así que úsalos con sabiduría.
Para conversaciones de múltiples turnos, pon el punto de interrupción en el último bloque del turno más reciente. Cada nueva solicitud reutiliza toda la conversación anterior como prefijo en caché, y los aciertos se acumulan a medida que crece el chat. Para un preámbulo compartido con una pregunta variable — ejemplos de few-shot más una consulta diferente cada vez — pon el marcador al final de la parte compartida, nunca al final del prompt completo. Si cacheas hasta la pregunta variable, cada solicitud escribe una entrada única y no lee nada.
La economía, porque no es gratuita
Las lecturas son baratas (~0,1×) pero las escrituras cuestan más que una solicitud normal: 1,25× para el TTL predeterminado de 5 minutos, 2× para el TTL de 1 hora. Así que el caching solo es rentable si lees más de lo que escribes. Con el TTL de 5 minutos alcanzas el punto de equilibrio en dos solicitudes. Con el TTL de 1 hora necesitas al menos tres, porque el precio premium de escritura se duplica.
¿Qué TTL? Usa 1 hora solo cuando tu tráfico tenga intervalos de más de cinco minutos. Si las solicitudes llegan más frecuentemente que eso, mantienen el caché caliente por sí solas y el TTL predeterminado de 5 minutos está bien y es más barato. No uses el TTL de 1 hora por defecto — el costo de escritura duplicado silenciosamente se comerá tus ahorros si el tráfico es constante.
Dos errores que me costaron una tarde de depuración cada uno
El lookback de 20 bloques. Cada punto de interrupción retrocede como máximo 20 bloques de contenido para encontrar una entrada de caché anterior. En un bucle agéntico con muchos pares tool_use/tool_result, un solo turno puede agregar más de 20 bloques — y entonces la siguiente solicitud no puede encontrar el caché anterior y falla silenciosamente. Solución: añade un punto de interrupción intermedio cada ~15 bloques en turnos largos.
Solicitudes concurrentes. Una entrada de caché solo se vuelve legible después de que la primera respuesta comienza a transmitirse. Envía diez solicitudes paralelas con el mismo prefijo y las diez pagarán el precio completo, porque ninguna puede leer lo que las otras aún están escribiendo. Para fan-out, envía una solicitud, espera el primer token transmitido, luego envía el resto. Leerán el caché que escribió esa primera.
Un truco más reciente que vale la pena conocer
En Opus 4.8 hay una forma limpia de inyectar una instrucción a mitad de conversación sin destruir tu caché: añade un mensaje {"role": "system", ...} al array de messages en lugar de editar el system de nivel superior. Editar el system de nivel superior cambia el prefijo antes de toda tu historia, por lo que cada turno en caché se vuelve a procesar sin caché. Un mensaje con rol de sistema se sienta después del historial y deja el prefijo en caché intacto. También es el canal de operador no falsificable, lo que es un buen beneficio adicional.
Verifica todo con el objeto usage: cache_creation_input_tokens es lo que escribiste, cache_read_input_tokens es lo que leíste a la tarifa económica, input_tokens es el resto sin caché. Si tu agent corrió durante una hora y input_tokens muestra 4K, no te alarmes — el resto vino del caché. Comprueba la suma, no el campo individual.
Cuatro líneas de configuración, una regla que respetar. Respeta el prefijo y la factura lo seguirá.
