Prompt caching
Objetivos de aprendizagem
- Explicar o invariante do caching: prefix match sobre a ordem
tools → system → messages. - Usar
cache_control: {"type": "ephemeral"}e respeitar o máximo de 4 breakpoints. - Identificar invalidadores silenciosos: timestamp no system, JSON não ordenado, tool set variável.
- Saber que o mínimo cacheável varia por modelo (512–4096 tokens) e que prefixos menores não cacheiam silenciosamente.
- Verificar o funcionamento do cache via
usage.cache_read_input_tokens.
O invariante: prefix match
Prompt caching é um casamento de prefixo. A chave do cache deriva dos bytes exatos do prompt renderizado até cada breakpoint. A ordem de renderização é sempre:
flowchart LR
T[1. tools - definições de ferramentas] --> S[2. system - prompt de sistema]
S --> M[3. messages - histórico da conversa]
M --> V[conteúdo variável da requisição]
style T fill:#1a7f5a,color:#fff
style S fill:#1a7f5a,color:#fff
style M fill:#1a7f5a,color:#fff
Qualquer byte alterado em uma posição invalida o cache dali para a frente. Consequências diretas:
- Mudar/reordenar uma ferramenta (posição 0) invalida tudo, inclusive system e histórico.
- Um breakpoint no último bloco do system cacheia tools + system juntos.
- Conteúdo estável deve vir fisicamente antes do conteúdo volátil; o que muda a cada requisição vai para o final.
cache_control e breakpoints
O marcador é cache_control: {"type": "ephemeral"} em um bloco de conteúdo (TTL padrão de 5 minutos; opcional "ttl": "1h"). São permitidos no máximo 4 breakpoints por requisição, colocados nas fronteiras de estabilidade (ex.: fim do system compartilhado; fim do último turno em conversas multi-turn). Escrita em cache custa ~1,25× o preço de input (2× para TTL de 1h); leitura custa ~0,1× — economia de até 90% no trecho cacheado.
import anthropic
client = anthropic.Anthropic()
LARGE_SYSTEM = "Você é um analista de contratos. Políticas da empresa:\n" + ("- regra...\n" * 200)
def ask(question: str):
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
system=[{
"type": "text",
"text": LARGE_SYSTEM, # estável, byte-idêntico
"cache_control": {"type": "ephemeral"}, # breakpoint no fim do prefixo estável
}],
messages=[{"role": "user", "content": question}], # variável: depois do breakpoint
)
u = response.usage
print("write:", u.cache_creation_input_tokens,
"| read:", u.cache_read_input_tokens,
"| uncached:", u.input_tokens)
return next(b.text for b in response.content if b.type == "text")
ask("O contrato 12 permite rescisão unilateral?") # 1ª chamada: cache write
ask("E o contrato 15, qual é a multa?") # 2ª chamada: cache read (~0.1x)
Invalidadores silenciosos
A falha de caching mais cara em produção é silenciosa: as requisições continuam funcionando, só que 10× mais caras. Os suspeitos clássicos ao auditar o código que monta o prompt:
| Padrão | Por que quebra o cache |
|---|---|
datetime.now() / timestamp interpolado no system | O prefixo muda a cada requisição — nada após o timestamp é reutilizável. |
| UUID / request ID no início do conteúdo | Cada requisição vira um prefixo único. |
json.dumps(...) sem sort_keys=True, iteração de set | Serialização não determinística: os bytes mudam mesmo com o mesmo conteúdo. |
Tool set variável por usuário/modo (tools=build_tools(user)) | Tools renderizam na posição 0: qualquer variação invalida tudo. |
Seções condicionais no system (if flag: system += ...) | Cada combinação de flags é um prefixo distinto. |
Correção geral: mover o trecho dinâmico para depois do último breakpoint (fim das messages), torná-lo determinístico, ou removê-lo.
Mínimo cacheável por modelo
Prefixos abaixo do mínimo do modelo não cacheiam, silenciosamente — sem erro, apenas cache_creation_input_tokens: 0. O mínimo varia de 512 a 4096 tokens conforme o modelo (ex.: 512 em claude-opus-5; 1024 em Opus 4.8/Sonnet 4.5; 4096 em Opus 4.6/Haiku 4.5 — e o valor não é monotônico entre gerações). Um prompt de 3K tokens cacheia em um modelo e não cacheia em outro.
Verificação: usage
| Campo | Significado |
|---|---|
usage.cache_creation_input_tokens | Tokens escritos no cache nesta requisição (~1,25×). |
usage.cache_read_input_tokens | Tokens servidos do cache (~0,1×). É o campo que prova que o cache funciona. |
usage.input_tokens | Apenas o restante não cacheado (preço cheio). Tamanho total do prompt = soma dos três. |
Se cache_read_input_tokens permanece 0 em requisições repetidas com prefixo supostamente idêntico, há um invalidador silencioso: logue os payloads e faça diff dos bytes entre requisições adjacentes. Verifique de novo a cada mudança no código de montagem do prompt — regressões de caching não geram erro, só conta mais alta.
Escopo na prova: o exam guide marca "detalhes de implementação de prompt caching" como fora de escopo além de saber que existe — mas custo/arquitetura de prompt (system estável primeiro, conteúdo volátil no fim, verificação via usage) aparecem em questões de arquitetura. Domine o modelo mental do prefixo e os campos de usage.
Pegadinhas da prova
- Distrator típico: "adicione
cache_controle pronto" com um timestamp no system — o marcador não salva um prefixo que muda a cada requisição. - Distrator típico: cachear "as últimas mensagens" com o system variável antes — o cache é de prefixo; não existe cachear o meio/fim com o começo mudando.
- Distrator típico: confirmar economia olhando latência ou "ausência de erro" — a prova do cache é
usage.cache_read_input_tokens> 0. - Distrator típico: prompt de 400 tokens "cacheável" — abaixo do mínimo do modelo (512–4096), o marcador é ignorado silenciosamente.
- Distrator típico: ignorar que trocar o tool set por requisição invalida também system e messages (tools vêm primeiro no prefixo).
Resumo em 5 linhas
- Caching é prefix match na ordem
tools → system → messages; qualquer byte alterado invalida dali em diante. cache_control: {"type": "ephemeral"}(TTL 5 min; opcional 1h), máximo de 4 breakpoints por requisição.- Invalidadores silenciosos: timestamp/UUID no system, JSON sem
sort_keys, tool set variável, seções condicionais. - Mínimo cacheável por modelo: 512–4096 tokens; abaixo disso o marcador é ignorado sem erro.
- Prova de funcionamento:
usage.cache_read_input_tokens> 0 — verifique após cada mudança na montagem do prompt.
Documentação oficial
Questões de fixação
1. Um agente adiciona cache_control ao system de 8K tokens, mas cache_read_input_tokens é sempre 0. O system começa com f"Hoje é {datetime.now()}. Você é...". Qual é o diagnóstico?
Gabarito: C. Timestamp no início do system é o invalidador silencioso clássico: cada requisição tem um prefixo único, então nunca há leitura. A inverte o problema (existe mínimo, não máximo relevante aqui). B — caching não requer header beta. D — os TTLs disponíveis são 5 min e 1h, e o sintoma "sempre 0" indica invalidação, não expiração.
2. Uma aplicação monta tools dinamicamente por usuário (cada um vê 3 das 10 ferramentas) e cacheia o system de 5K tokens. O custo não caiu. Por quê?
Gabarito: B. A ordem do prefixo é tools → system → messages: variar as tools muda a posição 0 e invalida tudo que vem depois, incluindo o system marcado. A é falsa — definições de ferramenta são cacheáveis e fazem parte do prefixo. C — exceder 4 breakpoints gera erro de requisição, não custo silencioso. D — o enunciado não diz isso, e o isolamento relevante é por workspace/organização, não uma chave por usuário.
3. Como confirmar, com evidência objetiva, que o prompt caching está funcionando em produção?
Gabarito: D. Os campos de usage são a única evidência objetiva: leitura > 0 prova reutilização. A é indireta — latência varia por muitos fatores. B não existe: falhas de caching são silenciosas (a requisição funciona, só custa mais). C — o modelo não tem acesso a essa informação de billing; auto-relato não é evidência.
4. Um prompt compartilhado de 450 tokens recebe cache_control, mas cache_creation_input_tokens retorna 0 em todos os modelos testados, sem erro. Qual é a explicação mais provável?
Gabarito: A. Abaixo do mínimo do modelo, nada é escrito e nenhum erro é emitido — exatamente o sintoma descrito. B mistura conceitos: tool_choice não tem relação com caching. C — ephemeral é o tipo atual (não existe persistent). D é regra inventada: um único breakpoint é suficiente.