Alejandro Rioja.
AI Agents Operations

Prompt caching con la Claude API: riduci i costi di input senza cambiare modello

Alejandro Rioja
Alejandro Rioja
9 min di lettura
TL;DR

Il prompt caching riduce il costo degli input grandi e stabili — il tuo system prompt, le definizioni dei tool, gli esempi few-shot — a circa il 10% del prezzo normale di input sulle richieste ripetute. Il meccanismo è un prefix match: metti un marcatore cache_control alla fine del contenuto stabile e tieni tutto ciò che è volatile dopo di esso. L'errore che azzera il tasso di cache hit è lasciare che un timestamp o un UUID fluttuino nel prefisso.

Newsletter gratuita

Ogni mercoledì. 28.400+ operatori. Zero riempitivo.

Table of contents

Open Table of contents

Cosa fa davvero il prompt caching

Ogni chiamata alla Claude API invia dei token. Senza caching, ogni token nella tua richiesta — system prompt, definizioni dei tool, esempi few-shot e il messaggio dell’utente — viene tariffato alla normale tariffa di input. Con il caching, un prefisso di quei token viene memorizzato sui server di Anthropic dopo la prima richiesta. Sulle richieste successive che condividono quell’esatto prefisso, paghi un prezzo di lettura dalla cache invece di rielaborarli da zero.

La differenza di costo è concreta:

Una volta superato il punto di pareggio — cosa che avviene in fretta su qualsiasi agente eseguito più di poche volte al giorno — ogni cache hit aggiuntivo è uno sconto del ~90% su quei token.

L’invariante del prefix-match

È l’unica regola da cui discende tutto il resto: la chiave di cache è un prefix match del tuo prompt renderizzato.

I server di Anthropic memorizzano il contenuto renderizzato dall’inizio del prompt fino al marcatore cache_control. Perché alla richiesta successiva si verifichi un cache hit, ogni token dall’inizio del prompt fino a quel marcatore deve essere identico — byte per byte.

L’ordine di rendering per il prefix matching è: tools → system → messages. Quindi prima viene calcolato l’hash dell’array dei tool, poi del blocco system, poi dei messaggi in ordine.

Cosa significa in pratica: il contenuto stabile deve venire per primo. Se il tuo system prompt fa riferimento a qualcosa di dinamico — la data corrente, un ID utente, un trace ID della richiesta — e questo appare prima del marcatore cache_control, la cache farà miss a ogni richiesta perché il prefisso continua a cambiare.

Su cosa mettere un marcatore di cache

I bersagli a maggior leva sono:

1. Il tuo system prompt

I system prompt sono di solito il blocco stabile più grande. Una persona dettagliata per l’agente, un elenco di regole comportamentali, un insieme di istruzioni sul formato dell’output — tutto questo è identico a ogni invocazione dello stesso agente. Marcalo:

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  system: [
    {
      type: "text",
      text: `You are a content operations agent for alejandrorioja.com.
Your job is to draft blog posts in Alejandro's voice: direct, practitioner, 
first-person, numbered lists, honest caveats. No hedging. No filler. 
Every section must earn its place.

[... 2000 more tokens of stable instructions ...]`,
      cache_control: { type: "ephemeral" },
    },
  ],
  messages: [
    {
      role: "user",
      content: "Draft a post about prompt caching.",
    },
  ],
});

Il cache_control: { type: "ephemeral" } sul blocco system dice a Claude di mettere in cache tutto fino a quel blocco incluso. L’array messages è volatile — diverso a ogni richiesta — e resta fuori dal confine della cache.

2. Le definizioni dei tool

Se il tuo agente usa dei tool, quelle definizioni possono essere consistenti. Uno schema di tool ben documentato, con descrizione, nomi dei parametri e valori enum, può arrivare a 500–1.000 token per tool. Con 5 tool, sono fino a 5.000 token che paghi per rielaborare a ogni chiamata:

typescript
const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  tools: [
    {
      name: "search_airtable",
      description: "Search the Airtable content queue...",
      input_schema: { type: "object", properties: { query: { type: "string" } } },
    },
    // ... more tools ...
    {
      name: "post_to_kit",
      description: "Schedule a broadcast via the Kit API...",
      input_schema: { /* ... */ },
      // Mark the last tool to cache the entire tools array
    } as Anthropic.Tool & { cache_control: { type: "ephemeral" } },
  ],
  system: "...",
  messages: [...],
});

Marca l’ultimo tool nell’array. Il prefix match coprirà l’intero array dei tool a partire da quel punto.

3. Gli esempi few-shot nei messaggi

Se passi esempi few-shot statici come messaggi iniziali nell’array messages, anche quelli possono essere messi in cache. Strutturali come i primi N messaggi e marca l’ultimo turno di esempio:

typescript
const messages: Anthropic.MessageParam[] = [
  {
    role: "user",
    content: [
      {
        type: "text",
        text: "Here are examples of posts in my voice:\n\n[Example 1...]\n\n[Example 2...]",
        cache_control: { type: "ephemeral" },
      } as Anthropic.TextBlockParam & { cache_control: { type: "ephemeral" } },
    ],
  },
  {
    role: "assistant",
    content: "Understood. I'll follow that voice.",
  },
  // The actual user turn follows — this is volatile, no cache marker
  {
    role: "user",
    content: actualUserRequest,
  },
];

Cosa NON mettere in cache (gli invalidatori silenziosi)

Sono le cose che sembrano stabili ma non lo sono — e azzerano il tuo hit rate in silenzio. La API non ti avvisa. Vedrai semplicemente cache_creation_input_tokens a ogni richiesta e ti chiederai perché.

I timestamp nel system prompt. L’errore più comune in assoluto:

typescript
// This invalidates the cache on every request
const system = `You are an agent. Current time: ${new Date().toISOString()}`;

Sposta i timestamp nel messaggio dell’utente, dove è giusto che stiano:

typescript
// Stable system prompt — cacheable
const system = `You are an agent. Use the current time provided by the user.`;

// Volatile user message — not cached
const userMessage = `Current time: ${new Date().toISOString()}. Run the daily brief.`;

UUID casuali e trace ID. Stesso problema. Se inietti un trace ID nel blocco system per il logging, ogni richiesta ottiene un prefisso nuovo.

Serializzazione JSON non deterministica. Se serializzi un oggetto dentro il system prompt e l’ordine delle chiavi non è garantito, la stringa renderizzata può differire anche quando i dati sottostanti sono gli stessi. Serializza con un ordine delle chiavi stabile oppure usa una template string.

Selezione dinamica dei few-shot. Se scegli gli esempi few-shot in base alla query corrente e li metti nel prefisso in cache, hai reso il prefisso “stabile” dipendente dalla query. O ti impegni a usare esempi fissi per il layer di cache, oppure sposti gli esempi dinamici nel turno di messaggio non messo in cache.

Verificare il tuo tasso di cache hit

Ogni risposta include i metadati di utilizzo. Controllali:

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

console.log({
  inputTokens: response.usage.input_tokens,
  cacheRead: response.usage.cache_read_input_tokens,
  cacheWrite: response.usage.cache_creation_input_tokens,
  outputTokens: response.usage.output_tokens,
});

Alla prima richiesta: cache_creation_input_tokens sarà diverso da zero, cache_read_input_tokens sarà 0. Questa è la scrittura.

In caso di cache hit: cache_read_input_tokens sarà diverso da zero, cache_creation_input_tokens sarà 0. Questa è la lettura.

Se vedi cache_creation_input_tokens a ogni richiesta, il tuo prefisso sta cambiando. Aggiungi un’istruzione di log che stampi i primi 200 caratteri del tuo system prompt renderizzato prima di ogni chiamata: un timestamp che fluttua salterà fuori immediatamente.

Il TTL di 1 ora: quando vale il costo di scrittura extra

Il TTL predefinito è di 5 minuti. Se il tuo agente gira a bassa frequenza — meno di una volta ogni 5 minuti — pagherai costi di cache write sulla maggior parte delle richieste senza ottenere letture.

typescript
// Opt into a 1-hour TTL
cache_control: { type: "ephemeral", ttl: "1h" }

La scrittura a 1 ora costa ~2× il prezzo base di input invece di 1,25×. La matematica: se colpisci la cache 3 o più volte all’ora, il TTL di 1 ora ti fa risparmiare. Se il tuo agente gira una volta al giorno (come il mio daily brief), nemmeno il TTL di 1 ora aiuta — paghi costi di scrittura ogni volta. In quel caso il vantaggio del caching è modesto, a meno che il system prompt non sia enorme.

Il mio agente di daily brief ha un system prompt da 3.000 token ma gira una volta al giorno. Il caching non aiuta. Il mio agente per la newsletter gira decine di volte per sessione mentre redige le bozze — il caching fa risparmiare parecchio.

Pre-warming: rendere economica la prima richiesta

Se sai che è in arrivo un picco di traffico — un batch job, il lancio di una API — puoi pre-riscaldare la cache con una richiesta fittizia a basso costo:

typescript
// Pre-warm: write the cache at near-zero output cost
await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1, // minimal output
  system: [{ type: "text", text: stableSystemPrompt, cache_control: { type: "ephemeral" } }],
  messages: [{ role: "user", content: "ping" }],
});

// Now the real requests read from cache

Questo è utile soprattutto per l’elaborazione batch, quando avvii molte richieste parallele e vuoi che ognuna colpisca una cache calda invece di gareggiare per scriverla.

Il prompt caching nei loop agentici

In un loop agentico multi-turno, la cronologia della conversazione cresce a ogni turno. La cache è abbastanza intelligente da gestirlo: usa una finestra di lookback di 20 blocchi, trovando il prefisso corrispondente più lungo tra gli ultimi 20 content block.

L’implicazione pratica: tieni il tuo contenuto stabile (system prompt, definizioni dei tool) ancorato all’inizio. La cronologia della conversazione che cresce in coda all’array dei messaggi non spezzerà il prefix match dei blocchi stabili — sono prima del contenuto volatile, e il prefix match parte dall’alto.

In pratica, i miei agenti strutturano i turni così:

code
System (cached) → Tools (cached) → Few-shot (cached) → Turn 1 → Turn 2 → ... → Current turn

La cache copre tutto fino al marcatore dei few-shot. La cronologia dei turni che cresce dopo di esso viene rielaborata ogni volta, ma va bene così — quei token sono specifici della sessione e piccoli rispetto al prefisso stabile.

Come si vede sulla fattura

Prendi un agente ad alta frequenza: 100 chiamate al giorno, system prompt da 4.000 token, pricing di Sonnet.

Senza caching:

Con caching (TTL di 5 minuti, ipotizzando 50 chiamate/ora nei picchi):

È circa una riduzione del 90% su quei token di input. Su scala — 1.000 chiamate al giorno — la differenza si moltiplica ulteriormente. E questo si aggiunge a qualsiasi risparmio dal routing dei modelli derivante dalla matematica Haiku vs Sonnet: il caching funziona a ogni tier.

La conclusione dell’operatore

Il prompt caching è l’ottimizzazione di costo più facile nella Claude API: un campo aggiuntivo sui content block che stai già scrivendo. Il vincolo è la disciplina attorno alla stabilità del prefisso — niente di dinamico prima del marcatore di cache. Se riesci a tenere il tuo system prompt, i tool e qualsiasi esempio statico liberi da contenuto volatile, pagherai il ~10% del normale costo di input su ogni cache hit. Per gli agenti ad alta frequenza con prompt grandi e stabili, questa è una leva più grande del cambiare tier di modello.


Correlati: La matematica dei costi degli AI agent: quando Haiku batte Sonnet · Agenti event-triggered vs schedulati · I 5 strumenti AI che uso davvero per gestire la mia attività

Continua a leggere

Ricevi il manuale dell'IA nella tua casella di posta

Ogni mercoledì. 28.400+ operatori. Zero riempitivo.

↵ per tutti i risultati esc esc per chiudere