🎁 Novità Registrati gratis, 10 chiamate le offriamo noi. Fino a $1, senza carta.
Prompt Caching LLM in Python: tutorial con codice funzionante

Prompt Caching LLM in Python: tutorial con codice funzionante

Indice
  1. 0. Setup
  2. 1. La chiamata cache-aware (identica su ogni provider)
  3. 2. Anthropic Claude — marcatori cache_control espliciti
  4. 3. OpenAI GPT-5.x — Caching automatico
  5. 4. Google Gemini — Caching implicito
  6. 5. DeepSeek-v4-flash — Cache automatica su disco
  7. 6. Alibaba Qwen — Hit segnalato, sconto variabile
  8. 7. Benchmark cross-provider (misurato il 25/05/2026)
  9. 8. Checklist pre-lancio
  10. 9. Pattern TTL-aware
  11. 8.1 Carichi legati alla sessione (chat, assistenti IDE)
  12. 8.2 Heartbeat per batch e cron
  13. 8.3 Documenti in cold storage
  14. 10. Cosa aggiunge davvero il gateway
  15. 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:

  1. 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.
  2. 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):

ModelloCache writeCache readRif. no-cacheSconto in letturaTTFT 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 *-pro di Gemini sono modelli reasoning. Con max_tokens piccolo capita spesso di vedere completion_tokens=0, perché il budget viene consumato dal thinking nascosto. Alza max_tokens a ≥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_tokens e 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):

ModelloCosto missCosto hitΔ costoTotale missTotale hitTTFT hit (stream)Cache hit rate
gpt-5.4-nano$0.00131$0.00074−44%2,18 s1,48 s1,00 s5.888 / 6.887 (85%)
gpt-5.4-mini$0.00267$0.00257−4%*3,63 s1,23 s0,73 s6.400 / 6.887 (93%)
gemini-2.5-flash$0.00198$0.00024†−88%2,49 s1,37 sn/d‡7.140 / 7.322 (97%)
gemini-2.5-pro$0.00824$0.00205†−75%2,99 s1,76 sn/d‡6.120 / 7.328 (84%)
deepseek-v4-flash$0.00091$0.00023−74%4,02 s3,71 s2,93 s6.784 / 7.101 (96%)
qwen3-max$0.00553$0.00549−1%§4,80 s2,37 s1,53 s7.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:

ModelloCosto writeCosto readSconto readTTFT 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:

  1. Prima il contenuto stabile — system prompt, knowledge base e schemi dei tool all’inizio di messages.
  2. Il contenuto volatile per ultimo — input dell’utente, documenti recuperati e timestamp in fondo.
  3. Niente variabili dinamiche in system — ora corrente, user ID e seed random distruggono il tuo prefisso.
  4. Logga cached_tokens a ogni chiamata. Se in produzione l’hit rate è sotto il 50%, il tuo prefisso non è davvero stabile. Ispeziona i prompt che vanno in miss.
  5. 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:

  1. Un solo base_url, un solo header di auth, tutti i modelli. Cambi il campo model e la forma della chiamata resta identica. Stesso array messages, stessa struttura del campo usage. Non ti porti dietro cinque SDK per cinque provider.
  2. usage.cost in 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.
  3. Campo cached_tokens uniforme. Anthropic riporta i cache hit come cache_read_input_tokens, OpenAI come prompt_tokens_details.cached_tokens, DeepSeek come prompt_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.

← Torna al blog