antonio leandro

claude, na prática

Prompt caching

tutorial · núcleo · 03 · contexto · Anthropic ·

a tese

O cache não procura conteúdo estável no seu prompt: ele só acha entradas que requisições anteriores escreveram no breakpoint. Errar a posição do cache_control custa uma escrita por requisição e leitura nenhuma.

o que fica

  1. A escrita no cache acontece só no bloco marcado com cache_control; a leitura anda para trás no máximo 20 blocos procurando entradas que requisições anteriores escreveram, e não conteúdo estável.
  2. Colocar o breakpoint no bloco que muda a cada requisição — timestamp, contexto per-request, mensagem do usuário — faz você pagar escrita sempre e nunca ler nada; o modo automático cai nessa armadilha porque marca o último bloco.
  3. Leitura de cache custa 10% do preço do input base e não é descontada do rate limit; a escrita de 5 minutos custa 1,25x o input base e a de 1 hora custa 2x.
  4. O TTL conta a partir do início da requisição que escreve ou lê, não do fim da resposta: se a resposta leva 4 minutos para streamar, sobra cerca de 1 minuto do cache de 5 minutos.
  5. Prompt abaixo do mínimo de tokens do modelo não é cacheado e a API não devolve erro — o único sinal é cache_creation_input_tokens e cache_read_input_tokens virem os dois em 0.
  6. Go e Swift randomizam a ordem das chaves na serialização JSON, e isso muda o hash dos blocos tool_use e quebra o cache.

o problema

Toda requisição a um modelo de linguagem processa o prompt inteiro do zero. Isso é irrelevante quando o prompt tem 200 tokens e roda uma vez. Vira o custo dominante quando ele carrega vinte exemplos de resposta, um documento longo e um conjunto grande de definições de tools — e quando esse mesmo bloco volta para o servidor a cada passo.

Um loop agêntico é o pior caso. Cada chamada de tool exige uma requisição nova, e cada requisição nova reenvia toda a conversa anterior mais o resultado que acabou de chegar. O que não mudou é reprocessado em toda volta. Quem paga por token vê a conta crescer com o número de turnos; quem se importa com latência vê o time-to-first-token preso ao tamanho do prefixo, não ao trabalho novo.

a ideia

Retomar de um prefixo. Se os primeiros blocos do prompt são idênticos aos de uma requisição recente, o processamento deles já foi feito — dá para continuar dali. A API expõe isso como um marcador, cache_control, colocado num bloco de conteúdo: tudo até aqui é reaproveitável.

A analogia que se sustenta é a de cache de camadas de build. A chave é cumulativa e a ordem é fixa: tools, depois system, depois messages. Mudar algo numa camada invalida ela e todas as seguintes. Editar a descrição de uma tool derruba o cache inteiro. Mudar tool_choice derruba só as messages. Ligar ou desligar web search reescreve o system prompt, então derruba system e messages.

como funciona

Três regras explicam quase todo o comportamento observável:

  1. Escrita só no breakpoint. Marcar um bloco grava exatamente uma entrada: o hash do prefixo que termina ali. Nenhuma entrada é criada em posições anteriores.
  2. Leitura anda para trás. A cada requisição o sistema calcula o hash no seu breakpoint e procura a entrada correspondente. Se não achar, recua um bloco por vez, testando o hash de cada posição anterior.
  3. A janela de lookback é de 20 blocos, contando o próprio breakpoint. Fora disso o sistema desiste — ou recomeça no próximo breakpoint explícito, e você tem até 4.

Daí sai o erro mais caro que a documentação descreve. Prefixo estático nos blocos 1 a 5, bloco 6 com um timestamp e a mensagem do usuário, cache_control no bloco 6. O hash muda toda vez. O lookback percorre 5, 4, 3, 2, 1 e não acha nada, porque nunca houve escrita naquelas posições. Escrita nova a cada requisição, leitura zero — pior que não usar cache, já que a escrita custa 1,25x o input base. A correção é mover o marcador para o bloco 5.

Há dois modos. No automático, um cache_control no topo do corpo da requisição e o breakpoint caminha sozinho para o último bloco cacheável conforme a conversa cresce; serve para chat multi-turno, em que o sufixo novo é sempre menor que a janela. No explícito, você posiciona os marcadores à mão — necessário quando seções mudam em frequências diferentes, ou quando o sufixo varia, porque aí o modo automático cai exatamente na armadilha acima.

O retorno vem em três campos de usage: cache_creation_input_tokens, cache_read_input_tokens e input_tokens, este último contando só o que veio depois do último breakpoint. O total de input é a soma dos três.

Também dá para pré-aquecer o cache: max_tokens: 0 lê o prompt, grava a entrada no breakpoint e retorna com content vazio e stop_reason: "max_tokens", sem cobrar output.

o que isso custou

O casamento é exato, byte a byte, até o bloco marcado. Isso transforma qualquer não-determinismo de serialização em bug de custo: a própria documentação avisa que Go e Swift randomizam a ordem das chaves em JSON e quebram o cache dos blocos tool_use.

O piso de tokens é silencioso. Prompts abaixo do mínimo do modelo — 512 tokens no Opus 5, 1.024 no Sonnet 5, 4.096 no Haiku 4.5 — passam sem cache e sem erro. Você só descobre olhando os campos de usage.

O TTL conta do início da requisição, não do fim da resposta: quatro minutos gerando output consomem quatro dos cinco minutos de vida. Requisições paralelas não compartilham a entrada até a primeira resposta começar. Não existe invalidação manual. Blocos de thinking não podem ser marcados com cache_control, embora sejam cacheados junto do resto quando aparecem em turnos anteriores — e contam como input token quando lidos. O pré-aquecimento com max_tokens: 0 é recusado com streaming, thinking estendido, structured outputs, tool_choice forçado e dentro do Batches.

E o isolamento não é uniforme: por workspace na Claude API, na Claude Platform on AWS e no Microsoft Foundry; só por organização no Bedrock e no Google Cloud.

onde isso aparece hoje

O caching está em todos os modelos Claude ativos e em todas as plataformas; o modo automático é a exceção, indisponível na integração legada do Bedrock. Em cima dele nasceram outras coisas na mesma documentação: o diagnóstico de cache em beta, que compara requisições consecutivas e aponta onde o prefixo divergiu, e as mensagens de system no meio da conversa em Opus 5, Opus 4.8, Fable 5 e Mythos 5, que existem para você acrescentar instrução sem tocar no prefixo já cacheado. O max_tokens: 0 substitui a gambiarra de max_tokens: 1 que a comunidade usava para aquecer cache.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·