Baja la factura de tokens de tu agente Claude con prompt caching: tutorial práctico (2026) — Cesar Ayala
← Todos los artículos

Baja la factura de tokens de tu agente Claude con prompt caching: tutorial práctico (2026)

El prompt caching reutiliza un prefijo ya procesado, así pagas ~0.1x del precio de input por la parte estable (system, tools, contexto) en lugar del precio completo cada turno. Pon cache_control {"type":"ephemeral"} al final de ese prefijo, deja la pregunta volátil después, corre dos veces y confirma que usage.cache_read_input_tokens sea distinto de cero.

El problema: tu agente vuelve a pagar el mismo prompt cada turno

Te voy a contar dónde se va el dinero antes de arreglarlo. La Messages API de Claude es stateless: cada turno reenvía el system prompt completo, las definiciones de tools y todo el contexto acumulado como input fresco, y te lo cobra al precio completo de input. No importa que ese bloque sea idéntico al del turno anterior. Lo pagas otra vez. Y otra. Y otra.

Hagamos el número crudo. Un prefijo estable de 50K tokens (system + tools + contexto compartido) en claude-opus-4-8, que cuesta $5 por millón de tokens de input (precio a 2026, confirma el actual), te sale en ~$0.25 de input cada turno. Un agente que corre 40 turnos en una sesión ya quemó ~$10 solo en reenviar algo que nunca cambió. Eso es sangrado puro.

El prompt caching reutiliza ese prefijo ya procesado, así que pagas ~0.1x por la parte estable. Esos ~$0.25 de lectura bajan a ~$0.025. Diez veces más barato sobre el bloque que se repite, y de paso baja la latencia de prefill.

Seré honesto desde el arranque: el caching solo ayuda cuando hay un prefijo estable. Si tu prompt cambia desde el primer token, esto no te sirve de nada — y peor, si pones el marcador ahí solo pagas la prima de escritura sin lecturas. Si tu agente vive en el mundo de costo y latencia en producción, esta es de las palancas más directas que tienes.

Lo que vas a construir en este tutorial: identificar el prefijo, poner un solo marcador cache_control, verificar el hit y esquivar los errores que lo rompen en silencio.

Cómo funciona realmente el prompt caching (prefix match, orden de render, breakpoints)

Hay una sola regla de la que se desprende todo lo demás, y si la entiendes el resto es mecánico: el caching es un prefix match. Cualquier cambio de un byte en cualquier parte del prefijo invalida todo lo que viene después. Por eso el orden importa tanto.

El orden de render es tools -> system -> messages. Siempre. Así que un breakpoint sobre el último bloque de system cachea tools + system juntos, porque ambos quedan antes de él en el prefijo renderizado.

La regla operativa: pon el contenido estable PRIMERO (system prompt congelado, lista de tools determinista) y el contenido volátil DESPUÉS del último breakpoint (la pregunta del usuario, timestamps, estado por turno). El marcador cache_control: {"type": "ephemeral"} va sobre un content block — un bloque de texto de system, un tool, o un bloque de mensaje — al final del prefijo estable. Si no necesitas control fino, puedes pasar un cache_control a nivel raíz en messages.create() y Claude lo coloca solo en el último bloque cacheable.

Tres límites que tienes que tener en la cabeza:

  • Máximo 4 breakpoints por petición.
  • TTL de 5 minutos por defecto, o {"type": "ephemeral", "ttl": "1h"} para una hora.
  • Mínimo cacheable por modelo: claude-opus-4-8 = 4096 tokens, claude-sonnet-4-6 = 2048, claude-haiku-4-5 = 4096. Por debajo del mínimo no cachea en silenciocache_creation_input_tokens se queda en 0 y no hay error. Esto agarra a mucha gente desprevenida.
Marcadorcache_control {"type":"ephemeral"} al final del prefijo estable
Orden de rendertools -> system -> messages
Breakpointsmáximo 4 por petición
Economíalectura ~0.1x · escritura 1.25x (5 min) / 2x (1h)
Mínimo prefijoopus-4-8 = 4096 · sonnet-4-6 = 2048 · haiku-4-5 = 4096 tok
Verificarusage.cache_read_input_tokens distinto de 0

La economía: lectura 0.1x vs escritura 1.25x, y cuándo conviene

Aquí decides si te conviene. Una lectura de caché cuesta ~0.1x el precio base de input. Una escritura cuesta 1.25x (TTL de 5 minutos) o 2x (TTL de 1 hora). O sea: la primera petición que escribe el caché paga un pequeño sobreprecio, y a partir de ahí cada lectura es casi gratis comparada con el precio completo.

El break-even depende del TTL:

  • TTL de 5 min: ya sales ganando en la segunda petición. La primera escribe (1.25x) y la segunda lee (0.1x): 1.25 + 0.1 = 1.35x contra 2.0x de dos peticiones sin caché.
  • TTL de 1 hora: necesitas ~3 peticiones. 2.0 (escritura) + 0.1 + 0.1 = 2.2x contra 3.0x sin caché. El costo de escritura duplicado pide más lecturas para amortizarse.

El ejemplo concreto en claude-opus-4-8: una lectura de un prefijo cacheado de 50K tokens te sale en ~$0.025 en lugar de ~$0.25 al precio completo de input ($5/MTok, confirma el actual). Referencia de precios (por MTok input/output, confirma el actual): claude-opus-4-8 $5/$25, claude-sonnet-4-6 $3/$15, claude-haiku-4-5 $1/$5.

¿Cuándo NO usar el TTL de 1 hora? Solo cuando tu tráfico tiene huecos de más de 5 minutos. Si tienes tráfico continuo, el caché de 5 minutos se mantiene caliente solo y no necesitas pagar la escritura al doble. Esto se conecta con el panorama más amplio del costo de la IA: la decisión de TTL es una de las que más mueve la factura mensual.

Sin caché1
Escritura (1ra petición)1.25
Lectura (~0.1x)0.1
2 peticiones cacheadas1.35
2 peticiones sin caché2

Paso a paso: agrega caching a un agente Claude (claude-opus-4-8)

Manos a la obra. Primero, así se ve una llamada sin caché — el system se reenvía completo cada turno al precio total:

from anthropic import Anthropic

client = Anthropic()

SYSTEM_PROMPT = """Eres el agente de soporte de Nixbly. (... contexto grande, congelado,
políticas, ejemplos few-shot: miles de tokens que NO cambian entre turnos ...)"""

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system=SYSTEM_PROMPT,  # se reenvía y se cobra completo cada turno
    messages=[{"role": "user", "content": "¿Cómo cancelo mi suscripción?"}],
)

Ahora la versión con caché. La clave: pasa system como una lista de bloques de texto, porque eso es lo que te deja pegar cache_control a un bloque específico.

Paso 1 — identifica el prefijo estable: el system prompt congelado + las definiciones de tools + cualquier contexto grande compartido que NO cambie entre turnos.

Paso 2 — pon cache_control: {"type": "ephemeral"} en el ÚLTIMO bloque de ese prefijo (aquí, el bloque final de system), para que tools + system se cacheen juntos.

Paso 3 — deja el contenido volátil DESPUÉS del breakpoint, sin marcador (la pregunta real del usuario, el estado por turno).

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system=[
        {
            "type": "text",
            "text": SYSTEM_PROMPT,  # prefijo estable
            "cache_control": {"type": "ephemeral"},  # breakpoint al FINAL del prefijo
        }
    ],
    messages=[
        # contenido volátil: va DESPUÉS del breakpoint, sin marcador
        {"role": "user", "content": "¿Cómo cancelo mi suscripción?"}
    ],
)
print(resp.usage)

Paso 4 — ejecuta la misma petición dos veces dentro de la ventana de 5 minutos, para que la segunda llamada pueda leer lo que escribió la primera.

Paso 5 — lee usage en ambas respuestas: la primera escribe (cache_creation_input_tokens > 0), la segunda lee (cache_read_input_tokens > 0).

La opción más simple, si no necesitas control fino: pasa cache_control a nivel raíz en messages.create() y se coloca solo en el último bloque cacheable. Esta es prima hermana de structured outputs en producción: otra técnica de la API de Claude que se activa con un campo y cambia mucho en producción.

  1. Identifica el prefijo establesystem + tools + contexto que NO cambia entre turnos
  2. Pon cache_control al finalsobre el último bloque del prefijo (ej. el bloque final de system)
  3. Deja lo volátil despuésla pregunta del usuario y el estado por turno, sin marcador
  4. Ejecuta dos vecesdentro de la ventana de 5 min para que la 2da lea lo que escribió la 1ra
  5. Confirma el hitcache_read_input_tokens distinto de cero en la 2da respuesta

Verifica el hit de caché (y lee bien los campos de usage)

Un miss silencioso se ve idéntico a un hit hasta que revisas usage. Por eso este paso no es opcional. Son tres campos y cada uno significa algo distinto:

  • usage.cache_read_input_tokens = tokens servidos desde el caché en esta petición (pagaste ~0.1x).
  • usage.cache_creation_input_tokens = tokens escritos al caché en esta petición (pagaste ~1.25x para 5 min, ~2x para 1 hora).
  • usage.input_tokens = el remanente no cacheado, cobrado al precio completo. Ojo: esto NO es el tamaño total del prompt.

El total del prompt es la suma de los tres:

total = (
    resp.usage.input_tokens
    + resp.usage.cache_creation_input_tokens
    + resp.usage.cache_read_input_tokens
)

Si tu agente corrió por horas pero input_tokens muestra 4K, lo demás vino del caché. Eso es exactamente lo que quieres ver.

El diagnóstico: si cache_read_input_tokens es 0 a lo largo de peticiones repetidas con el mismo prefijo, hay un invalidador silencioso rompiendo el prefijo — haz un diff de los bytes del prompt renderizado entre dos peticiones para encontrarlo. Y si cache_creation_input_tokens se queda en 0 incluso en la primera petición, tu prefijo está por debajo del mínimo del modelo (1024 tokens en claude-opus-4-8): es demasiado corto para cachear.

Los errores que rompen el caché en silencio

Aquí está el valor que la documentación deja regado: las cosas que producen un cache_read de 0 sin ningún error. Las he pisado todas en producción.

  • Invalidadores silenciosos en el system prompt: un datetime.now(), un uuid4(), o un id por petición interpolado en el prefijo lo cambia en cada llamada, así que NADA cachea. Mira el clásico:
# MAL: el timestamp en el system cambia el prefijo cada llamada -> cache_read siempre 0
system = [{
    "type": "text",
    "text": f"Eres el agente de Nixbly. Fecha actual: {datetime.now()}",
    "cache_control": {"type": "ephemeral"},
}]

# BIEN: system congelado; el timestamp va DESPUÉS del breakpoint, en el mensaje
system = [{
    "type": "text",
    "text": "Eres el agente de Nixbly.",
    "cache_control": {"type": "ephemeral"},
}]
messages = [{
    "role": "user",
    "content": f"[Fecha: {datetime.now()}] ¿Cómo cancelo mi suscripción?",
}]
  • JSON no determinista: json.dumps sin sort_keys=True (o iterar un set) hace que los bytes del prefijo cambien entre corridas. Serializa de forma determinista, siempre con las llaves ordenadas.
  • No cambies tools ni el modelo a media conversación: los tools renderizan primero (posición 0) y un cambio de modelo es otro caché distinto; cualquiera de los dos fuerza una reconstrucción completa.
  • Lookback de 20 bloques: un breakpoint camina hacia atrás máximo 20 content blocks para encontrar una entrada previa de caché. En loops de agente largos (muchos pares tool_use/tool_result) agrega un breakpoint intermedio cada ~15 bloques.
  • Fan-out concurrente paga todo completo: el caché no es legible hasta que la primera respuesta empieza a hacer streaming. Si disparas 10 peticiones en paralelo, las 10 pagan precio completo. Espera el primer token de una, y luego dispara el resto.

Y el límite honesto otra vez: el caching baja costo y latencia solo sobre un prefijo estable. No hace nada por un prompt que cambia desde el primer token — no le pongas cache_control ahí, solo pagarías la prima de escritura con cero lecturas. Confirma los precios y mínimos actuales en la documentación de Anthropic.

Cachea bien

  • System prompt congelado, sin timestamps ni IDs
  • Tools deterministas, JSON con sort_keys=True
  • Mismo modelo durante toda la conversación
  • Breakpoint intermedio cada ~15 bloques en loops largos

Lo rompe en silencio

  • datetime.now() o uuid4() en el system prompt
  • json.dumps sin ordenar llaves, o iterar un set
  • Cambio de modelo a media conversación
  • Set de tools que varía por petición

Preguntas frecuentes

¿Por qué cache_read_input_tokens siempre es 0? Hay un invalidador silencioso (timestamp, UUID o JSON sin ordenar en el prefijo) o tu prefijo está por debajo del mínimo del modelo (1024 tokens en claude-opus-4-8). Haz diff de los bytes del prompt entre dos peticiones.

¿El caching cambia la salida del modelo? No. Solo cambia cómo se cobra el prefijo estable y qué tan rápido corre el prefill. La respuesta es la misma.

¿Cuántos breakpoints puedo usar? Máximo 4 por petición. Uno sobre el último bloque de system alcanza para la mayoría de los agentes.

¿TTL de 5 minutos o de 1 hora? Por defecto 5 min para tráfico continuo; 1 hora solo para tráfico a ráfagas con huecos de más de 5 minutos — pero la escritura cuesta 2x en lugar de 1.25x.

¿Vale la pena cachear un prompt que cambia cada turno? No. Sin prefijo reutilizable solo pagas la prima de escritura con cero lecturas. Deja cache_control fuera.

¿Qué modelos y qué mínimos? claude-opus-4-8 = 4096 tokens, claude-sonnet-4-6 = 2048; claude-haiku-4-5 = 4096. Por debajo del mínimo no cachea en silencio. Confirma los valores actuales en la documentación.

Para cerrar

Toda la jugada en una línea: congela el prefijo, pon cache_control al final, deja la pregunta después, ejecuta dos veces y confirma que cache_read_input_tokens sea mayor a cero. El payoff es directo: la parte estable baja del precio completo de input a ~0.1x, y la latencia de prefill baja también.

Estos números se mueven, así que confirma siempre los precios y los mínimos por modelo contra la documentación de Anthropic: Prompt caching y Pricing. Y si quieres entender mejor qué es el agente cuya factura estás bajando, o ver más guías de agentes de IA, ahí los dejo.

Mídelo, no lo adivines. Lee el usage, encuentra tu invalidador silencioso, y deja de pagar dos veces por lo mismo. Nos vemos en el siguiente.