Prompt Caching LLM in Python: tutorial con codice funzionante
Indice
- 0. Setup
- 1. La chiamata cache-aware (identica su ogni provider)
- 2. Anthropic Claude — marcatori cache_control espliciti
- 3. OpenAI GPT-5.x — Caching automatico
- 4. Google Gemini — Caching implicito
- 5. DeepSeek-v4-flash — Cache automatica su disco
- 6. Alibaba Qwen — Hit segnalato, sconto variabile
- 7. Benchmark cross-provider (misurato il 25/05/2026)
- 8. Checklist pre-lancio
- 9. Pattern TTL-aware
- 8.1 Carichi legati alla sessione (chat, assistenti IDE)
- 8.2 Heartbeat per batch e cron
- 8.3 Documenti in cold storage
- 10. Cosa aggiunge davvero il gateway
- FAQ
TL;DR — Un solo OpenAI SDK, un solo base_url, tutti i principali LLM. I numeri di questo articolo sono misurati sul gateway Synthorai in produzione il 2026-05-25, con un system prompt stabile di circa 7.300 token. Il gateway qui fa una cosa semplice e senza fronzoli: un unico endpoint, un unico header di auth e un campo usage.cost che ti evita di mantenere una matrice di prezzi per ogni vendor. La matematica dei Transformer dietro al caching è spiegata nella Parte 1: Principi del caching; le scelte di design provider per provider sono nella Parte 2: Confronto tra provider.
Serie: Parte 3 di 5 · In precedenza: Parte 1 — Principi del caching · Parte 2 — Confronto e valutazione dei provider · Prossima: Parte 4 — Il miglior LLM per ogni caso d’uso · Parte 5 — Integrazione con LangChain
0. Setup
pip install openai
# common.py — reused across every example
import os, time
from openai import OpenAI
oai = OpenAI(
api_key=os.environ["SYNTHORAI_KEY"],
base_url="https://synthorai.io/v1",
)
Il gateway parla il formato di OpenAI per ogni modello che espone (GPT, Claude, Gemini, DeepSeek, Qwen). Cambi il campo model, non l’SDK. L’autenticazione usa Authorization: Bearer <key>.
ID dei modelli con cache disponibili sul gateway pubblico (snapshot 2026-05): claude-haiku-4-5, claude-sonnet-4-5 / 4-6, claude-opus-4-5 / 4-6 / 4-7, gpt-5.4-mini, gpt-5.4-nano, gpt-5.2, gpt-5.5-pro, gemini-2.5-flash, gemini-2.5-pro, gemini-3.1-pro-preview, deepseek-v4-flash, qwen3-max, qwen3.5-flash. La lista completa e aggiornata è su GET /v1/models.
1. La chiamata cache-aware (identica su ogni provider)
Non devi attivare nulla. Per qualunque modello che supporta il prompt caching a monte, il gateway si limita a inoltrare i metadati della risposta. Due campi ti dicono cosa è successo:
resp = oai.chat.completions.create(
model="gpt-5.4-mini",
max_tokens=128,
messages=[
{"role": "system", "content": LONG_STABLE_PROMPT}, # ~7K tokens
{"role": "user", "content": "First question"},
],
)
print(resp.usage.prompt_tokens_details.cached_tokens) # cache hit count
print(resp.usage.cost) # USD, gateway-computed
cached_tokens è il numero di token di input che hanno trovato riscontro nella cache di prefisso a monte. usage.cost è il prezzo di questa singola chiamata in USD, calcolato dal gateway: non serve tenere un listino prezzi locale per ogni provider.
Due regole che discendono dall’architettura e valgono per ogni provider:
- Contenuto stabile all’inizio, contenuto volatile alla fine. Il prefisso viene confrontato a partire dal token zero; basta cambiare un byte all’inizio per invalidare l’intero prefisso.
- Tieni i dati dinamici fuori dal system prompt. Timestamp correnti, session ID e UUID delle richieste azzerano tutti la cache.
Quello che segue sono solo esempi dello stesso pattern applicato ai singoli vendor.
2. Anthropic Claude — marcatori cache_control espliciti
Claude appartiene alla famiglia dei marcatori espliciti: l’API di Anthropic non fa caching automatico. Per ottenere un cache hit devi marcare fino a quattro breakpoint cache_control nel tuo array system o messages. Le letture dalla cache costano circa il 10% della tariffa di input, mentre le scritture costano il 125% (un sovrapprezzo del 25%).
Il modo più pulito per usare cache_control tramite il gateway è puntare l’SDK ufficiale anthropic all’endpoint Anthropic-native del gateway (il path OpenAI-compat /chat/completions al momento non propaga i marcatori cache_control: per il caching di Claude usa /v1/messages).
import os
from anthropic import Anthropic
anth = Anthropic(
api_key=os.environ["SYNTHORAI_KEY"],
base_url="https://synthorai.io/", # SDK appends /v1/messages
)
msg = anth.messages.create(
model="claude-sonnet-4-5",
max_tokens=512,
system=[
{"type": "text", "text": SYSTEM_INSTRUCTIONS,
"cache_control": {"type": "ephemeral"}}, # BP 1: never changes
{"type": "text", "text": TOOL_DESCRIPTIONS,
"cache_control": {"type": "ephemeral"}}, # BP 2: rarely changes
{"type": "text", "text": RETRIEVED_DOCUMENTS}, # changes per call — not cached
],
messages=[{"role": "user", "content": question}],
)
print(msg.usage)
# Usage(input_tokens=18, output_tokens=64,
# cache_creation_input_tokens=0, cache_read_input_tokens=8123,
# cost=...)
Opzioni di TTL. {"type": "ephemeral"} usa di default un TTL scorrevole di 5 minuti (ogni hit sposta in avanti la scadenza). Per carichi con pause di inattività superiori a 5 minuti, richiedi il TTL di 1 ora sullo stesso marcatore:
"cache_control": {"type": "ephemeral", "ttl": "1h"}
Breakpoint a livelli. Fino a quattro marcatori ti permettono di mettere in cache in modo indipendente le parti che “non cambiano mai”, quelle che “cambiano di rado” e quelle che “cambiano per ogni task”: il meglio della categoria per carichi agentici e RAG, dove le sezioni del prompt cambiano con frequenze diverse. Anche quando l’ultimo livello (per esempio i documenti recuperati) cambia tra una chiamata e l’altra, i livelli precedenti fanno comunque hit.
Scelta del modello. ID Claude disponibili sul gateway al 2026-05: claude-haiku-4-5, claude-sonnet-4-5 / 4-6, claude-opus-4-5 / 4-6 / 4-7. Haiku per chat economiche; Sonnet per uso generico, con il pattern di caching agentico più solido; Opus per i task di ragionamento più difficili.
Riferimento misurato per cache hit / write / no-cache (2026-05-25, system prompt di circa 7.976 token, max_tokens=64):
| Modello | Cache write | Cache read | Rif. no-cache | Sconto in lettura | TTFT su hit (stream) |
|---|---|---|---|---|---|
claude-haiku-4-5 | $0.00916 | $0.00086 | $0.00725 | −88% | 1.31 s |
claude-sonnet-4-5 | $0.02713 | $0.00247 | $0.02175 | −89% | 1.76 s |
claude-sonnet-4-6 | $0.02736 | $0.00253 | $0.02198 | −88% | 1.81 s |
claude-opus-4-5 | $0.04522 | $0.00409 | $0.03624 | −89% | 2.08 s |
claude-opus-4-6 | $0.04522 | $0.00411 | $0.03625 | −89% | 2.55 s |
claude-opus-4-7 | $0.06545 | $0.00609 | $0.05259 | −88% | 2.30 s |
Lo sconto resta uniforme su tutta la famiglia. Il sovrapprezzo in scrittura è di circa il 25% rispetto al no-cache (la tariffa documentata da Anthropic); il pareggio si raggiunge con un solo cache hit.
3. OpenAI GPT-5.x — Caching automatico
OpenAI mette in cache in automatico qualsiasi richiesta con un prefisso abbastanza lungo. Nessuna modifica al codice, nessun marker.
def ask_gpt(question: str):
t0 = time.perf_counter()
resp = oai.chat.completions.create(
model="gpt-5.4-mini",
max_tokens=64,
messages=[
{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": question},
],
)
return resp, time.perf_counter() - t0
r1, t1 = ask_gpt("Which export formats are supported?")
r2, t2 = ask_gpt("How long is the refund window for annual plans?")
print(t1, r1.usage.prompt_tokens_details.cached_tokens, r1.usage.cost)
# 3.63 0 0.00267
print(t2, r2.usage.prompt_tokens_details.cached_tokens, r2.usage.cost)
# 1.23 6400 0.00257
Lo stesso prompt da 6.887 token per due volte. Alla seconda chiamata il 93% del system prompt colpisce la cache e la latenza totale scende da 3,6 s a 1,2 s. Qui il costo cambia appena, perché lo sconto della cache è compensato da un completion più lungo alla prima chiamata — vedi §7 per numeri più puliti nel confronto tra provider.
Con gpt-5.4-nano lo sconto si vede meglio (44% di costo in meno sul cache hit). Per le UI di chat, dove conta solo il time-to-first-token, i numeri che interessano sono quelli dello streaming:
def ttft(model, question):
t0 = time.perf_counter()
stream = oai.chat.completions.create(
model=model, max_tokens=64,
messages=[
{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": question},
],
stream=True, stream_options={"include_usage": True},
)
for ev in stream:
if ev.choices and ev.choices[0].delta and ev.choices[0].delta.content:
return time.perf_counter() - t0 # first content token
TTFT misurato sul passaggio con cache: 0,73 s per gpt-5.4-mini, 1,00 s per gpt-5.4-nano.
4. Google Gemini — Caching implicito
Anche la cache di Gemini è automatica quando passi dal gateway. Non c’è nessuno step di create cachedContent da eseguire.
r = oai.chat.completions.create(
model="gemini-2.5-flash",
max_tokens=128,
messages=[
{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": "Summarize section 6 in two bullets."},
],
)
print(r.usage.prompt_tokens_details.cached_tokens, r.usage.cost)
Un cache hit misurato su gemini-2.5-flash con un system prompt di ~7.300 token: 7.140 token in cache (97%), il costo scende da $0.00198 a $0.00024 — 88% in meno su quel passaggio.
Due insidie da conoscere:
- Le varianti
*-prodi Gemini sono modelli reasoning. Conmax_tokenspiccolo capita spesso di vederecompletion_tokens=0, perché il budget viene consumato dal thinking nascosto. Alzamax_tokensa ≥256 per qualsiasi cosa rivolta all’utente. - Il TTL della cache implicita è breve e non è specificato ufficialmente. Nel nostro test, un hit tra due chiamate a 5 s di distanza è andato a buon fine; una terza chiamata ~10 s dopo a volte mancava la cache. Non costruire logica che dà per scontato l’hit; controlla
cached_tokense degrada in modo graduale.
5. DeepSeek-v4-flash — Cache automatica su disco
La cache automatica di DeepSeek resta valida più a lungo delle cache residenti in memoria GPU degli altri provider. La forma della chiamata è la stessa:
r1 = oai.chat.completions.create(
model="deepseek-v4-flash", max_tokens=128,
messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": "Q1"}],
)
# r1.usage.cost = $0.00091, cached_tokens = 0
r2 = oai.chat.completions.create(
model="deepseek-v4-flash", max_tokens=128,
messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": "Q2"}],
)
# r2.usage.cost = $0.00023, cached_tokens = 6784 → 74% saved
TTFT in streaming sul passaggio con cache: 2,93 s. DeepSeek non è l’opzione a latenza più bassa di questo gruppo: i vantaggi stanno nel costo e nel fatto che la cache resta calda anche su intervalli dell’ordine dell’ora.
6. Alibaba Qwen — Hit segnalato, sconto variabile
r = oai.chat.completions.create(
model="qwen3-max", max_tokens=128,
messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": "Q1"}],
)
print(r.usage.prompt_tokens_details.cached_tokens, r.usage.cost)
# 7040 0.00549
Un dettaglio emerso durante il nostro test: cached_tokens segnala un hit (7.040 su 7.234 = 97%), ma usage.cost non è calato sul passaggio con cache (resta ≈ $0,0055). L’hit sulla cache a monte è quindi avvenuto (TTFT più rapido, 1,53 s contro 3,03 s a freddo), ma alla data del test il campo di costo del gateway per questo provider non rifletteva ancora lo sconto della tariffa con cache. Se il costo su Qwen è un vincolo, tieni d’occhio cached_tokens e fai riferimento alle pagine di pricing a monte finché la cosa non si normalizza.
7. Benchmark cross-provider (misurato il 25/05/2026)
Singola esecuzione sequenziale. System prompt stabile da 7.284 caratteri (~6.900–7.300 token a seconda del tokenizer). max_tokens=64. Una chiamata in miss seguita subito da una chiamata in hit.
I provider con cache automatica (nessun marker richiesto):
| Modello | Costo miss | Costo hit | Δ costo | Totale miss | Totale hit | TTFT hit (stream) | Cache hit rate |
|---|---|---|---|---|---|---|---|
gpt-5.4-nano | $0.00131 | $0.00074 | −44% | 2,18 s | 1,48 s | 1,00 s | 5.888 / 6.887 (85%) |
gpt-5.4-mini | $0.00267 | $0.00257 | −4%* | 3,63 s | 1,23 s | 0,73 s | 6.400 / 6.887 (93%) |
gemini-2.5-flash | $0.00198 | $0.00024† | −88% | 2,49 s | 1,37 s | n/d‡ | 7.140 / 7.322 (97%) |
gemini-2.5-pro | $0.00824 | $0.00205† | −75% | 2,99 s | 1,76 s | n/d‡ | 6.120 / 7.328 (84%) |
deepseek-v4-flash | $0.00091 | $0.00023 | −74% | 4,02 s | 3,71 s | 2,93 s | 6.784 / 7.101 (96%) |
qwen3-max | $0.00553 | $0.00549 | −1%§ | 4,80 s | 2,37 s | 1,53 s | 7.040 / 7.234 (97%) |
* La completion della chiamata in miss di gpt-5.4-mini era di 44 token contro i 19 dell’hit — il delta di costo mescola lo sconto della cache con la differenza di lunghezza della completion. Qui il segnale più pulito è il calo della latenza (3,63 → 1,23 s).
† Costo del pass in streaming (dove cached_tokens veniva riportato); il pass non-stream a volte restituiva cached_tokens=null per Gemini e il costo non calava. I metadati del gateway per Gemini sono attualmente incoerenti — fidati di cached_tokens quando è presente.
‡ I modelli reasoning Gemini *-pro / *-flash spesso emettono zero token di contenuto con max_tokens piccolo, quindi il TTFT non ha senso con quel budget. Alza max_tokens se misuri questo dato in produzione.
§ Vedi §6 — l’hit sulla cache upstream è avvenuto (la latenza è calata), ma il campo usage.cost del gateway non ha riflesso lo sconto per qwen3-max in questa data.
Claude di Anthropic è guidato da marker espliciti; i numeri stanno in una tabella separata perché lo sconto è opt-in tramite cache_control (vedi §2 per il pattern). Stesso prompt, misurando scrittura vs lettura della cache:
| Modello | Costo write | Costo read | Sconto read | TTFT hit (stream) |
|---|---|---|---|---|
claude-haiku-4-5 | $0.00916 | $0.00086 | −88% | 1,31 s |
claude-sonnet-4-5 | $0.02713 | $0.00247 | −89% | 1,76 s |
claude-sonnet-4-6 | $0.02736 | $0.00253 | −88% | 1,81 s |
claude-opus-4-5 | $0.04522 | $0.00409 | −89% | 2,08 s |
claude-opus-4-6 | $0.04522 | $0.00411 | −89% | 2,55 s |
claude-opus-4-7 | $0.06545 | $0.00609 | −88% | 2,30 s |
I tuoi numeri varieranno in base alla region, all’ora del giorno e a quanto sono “caldi” i prefissi degli altri tenant. Singola esecuzione, singola data — non citarli come verità assoluta da benchmark.
8. Checklist pre-lancio
Prima di rilasciare un prompt cache-aware:
- Prima il contenuto stabile — system prompt, knowledge base e schemi dei tool all’inizio di
messages. - Il contenuto volatile per ultimo — input dell’utente, documenti recuperati e timestamp in fondo.
- Niente variabili dinamiche in
system— ora corrente, user ID e seed random distruggono il tuo prefisso. - Logga
cached_tokensa ogni chiamata. Se in produzione l’hit rate è sotto il 50%, il tuo prefisso non è davvero stabile. Ispeziona i prompt che vanno in miss. - Non fidarti di un singolo pass in hit. I TTL sono brevi; progetta per
hit_rate ∈ [0, 1)invece che per un “hit sempre”.
9. Pattern TTL-aware
Il fallimento più comune in produzione non è “mi sono dimenticato di abilitare la cache” — è “il mio hit rate è al 12% perché le mie richieste non arrivano davvero dentro la finestra del TTL”.
8.1 Carichi legati alla sessione (chat, assistenti IDE)
La cadenza naturale è ben al di sotto del TTL. Struttura bene il prompt e la cache resta calda da sola — non serve altro.
8.2 Heartbeat per batch e cron
Immagina un report giornaliero alle 09:00 che chiama il modello 50 volte in una raffica di 3 minuti: la prima scrittura in cache alle 09:00 è sprecata, perché durante la notte la cache si è raffreddata. La soluzione è inviare un “ping” da 1 token con il prefisso in cache ogni TTL/2 a partire dalle 08:55, così da tenerla calda:
def keepalive():
oai.chat.completions.create(
model="gpt-5.4-mini",
max_tokens=1,
messages=[
{"role": "system", "content": LONG_STABLE_PROMPT},
{"role": "user", "content": "."},
],
)
Il costo per ping è dato dagli input token × tariffa cached; per il nostro prefisso da 7K token su gpt-5.4-mini sono circa $0,0026 — molto meno di far pagare al batch job il prefill completo sulle prime 50 chiamate reali.
8.3 Documenti in cold storage
Per i documenti consultati in modo sporadico (una volta l’ora nell’arco della giornata), le cache in memoria sono fredde quasi sempre. Al momento in cui scriviamo, il gateway non espone un endpoint per creare una cache esplicita hosted: per esigenze di TTL lungo usa deepseek-v4-flash (basata su disco, nella pratica sopravvive a intervalli di alcune ore) oppure chiama direttamente l’API nativa cachedContent di Google, al di fuori del gateway.
10. Cosa aggiunge davvero il gateway
Sarebbe disonesto sostenere che il gateway “fa il caching al posto tuo”. Il caching avviene a livello del modello, e il gateway espone ciò che c’è già. Rispetto all’uso diretto dell’SDK nativo di ogni vendor, aggiunge però tre cose:
- Un solo base_url, un solo header di auth, tutti i modelli. Cambi il campo
modele la forma della chiamata resta identica. Stesso arraymessages, stessa struttura del campousage. Non ti porti dietro cinque SDK per cinque provider. usage.costin USD per ogni chiamata. Il gateway calcola il costo in dollari con le tariffe upstream correnti e lo include in ogni risposta. Non devi mantenere una matrice di prezzi nel codice, né iscriverti alle notifiche di variazione prezzi di ogni vendor.- Campo
cached_tokensuniforme. Anthropic riporta i cache hit comecache_read_input_tokens, OpenAI comeprompt_tokens_details.cached_tokens, DeepSeek comeprompt_cache_hit_tokens. Il gateway li normalizza nel formato OpenAI, così il tuo codice di observability non deve ramificare in base al provider.
Questo è tutto ciò che offre. Il resto — quando fare caching, come strutturare i prompt, quale modello scegliere — è materia del prossimo articolo.
Prossimo: Parte 4 — Come scegliere il miglior LLM per caso d’uso: Chat, API e AI Agent — una matrice decisionale che abbina il tipo di workload al modello + strategia di caching ottimali, con i conti sui costi.
FAQ
Perché usare l’SDK di OpenAI per modelli non-OpenAI?
Il gateway parla il formato wire di OpenAI per ogni provider che espone. L’SDK ufficiale openai ti dà risposte tipizzate, retry automatici e helper per lo streaming: non ha senso scriversi a mano cinque client HTTP.
Il caching funziona con le risposte in streaming?
Sì. L’oggetto usage nell’ultimo chunk riporta i conteggi delle cache hit (se passi stream_options={"include_usage": True}). Il guadagno di latenza si vede soprattutto in streaming, perché è il TTFT quello che l’utente percepisce.
Quale provider ha lo sconto sulla cache più profondo per il mio workload?
Ai prezzi di 2026-05 e con un hit rate del 70%+, gemini-2.5-flash e deepseek-v4-flash sono i più economici nella tabella del §7. gpt-5.4-mini vince sul TTFT. Per lo sconto del 90% documentato da Claude, marca fino a quattro breakpoint cache_control (vedi §2). Esegui lo stesso benchmark sul tuo prompt: è un lavoro di un giorno, non una migrazione di settimane.
Quando servono i marcatori cache_control?
Solo quando chiami Anthropic Claude, vedi §2. Per OpenAI/Gemini/DeepSeek/Qwen l’upstream mette in cache automaticamente qualsiasi prefisso abbastanza lungo, quindi non serve nessun marcatore; con quei provider il campo viene ignorato silenziosamente.
Quanto sono aggiornati questi numeri? Misurati il 2026-05-25 sul gateway pubblico. Prendili come un singolo dato: prezzi e latenza cambiano a ogni ciclo di release.
E Anthropic Claude?
Claude è supportato dal gateway tramite marcatori cache_control espliciti: usa l’SDK anthropic con base_url="https://synthorai.io/" (l’SDK aggiunge /v1/messages). Il path OpenAI-compat /chat/completions oggi non propaga i marcatori; per il caching di Claude in particolare usa il path nativo Anthropic mostrato nel §2.
Fonti e verifica: tutti i numeri sono misurati su https://synthorai.io/v1 il 2026-05-25 con l’SDK openai 2.38.0. Pagine di pricing dei vendor: Anthropic Prompt Caching · OpenAI Prompt Caching · Google Gemini Context Caching · DeepSeek KV Cache Guide · Alibaba Bailian Context Cache.