Certa vez coloquei em produção um agent que lia um system prompt de 40K tokens em cada solicitação. Centenas de solicitações por hora, o mesmo prompt toda vez, cobrado ao preço integral de entrada toda vez. A correção tinha quatro linhas. O prompt caching reduziu esse custo recorrente para aproximadamente um décimo. As leituras de cache custam cerca de 0,1× o preço base de entrada. Esse é o número de destaque, e é real.
Mas eis o que me pegou primeiro: adicionei o marcador de cache e nada mudou. cache_read_input_tokens ficou em zero. O cache estava silenciosamente não fazendo nada, e eu estava pagando um prêmio pelo privilégio. Então, antes do como fazer, você precisa da única regra da qual todo o resto deriva.
O prompt caching é uma correspondência de prefixo
É só isso. A chave de cache são os bytes exatos do seu prompt renderizado até cada ponto de interrupção de cache. Qualquer mudança em qualquer lugar nesse prefixo invalida o cache para tudo que vem depois. Um byte. Uma chave JSON reordenada, um timestamp, uma ferramenta trocada — qualquer um desses e você está escrevendo uma nova entrada de cache em vez de ler a antiga.
A ordem de renderização é fixa: tools, depois system, depois messages. Então o que vem primeiro tem que ser o mais estável que você tem, e o que muda por solicitação tem que ficar no final. Acerte essa ordenação e a maior parte do caching funciona por si só. Erre e nenhuma quantidade de marcadores vai te salvar.
O que estava matando meu cache
Era isso, no topo do meu system prompt:
Current date: 2026-06-21 14:32:07
Um timestamp. Ele mudava em cada solicitação, ficava perto do início do prefixo, e invalidava tudo que vinha depois. O prompt inteiro de 40K era impossível de cachear por causa de uma string de 19 caracteres em que eu não pensei.
Esse é o bug de caching mais comum que eu vejo. As pessoas interpolam conteúdo dinâmico — data atual, nome de usuário, ID de sessão, um flag de modo — no system prompt, e isso envenena o prefixo. A correção é congelar o system prompt e injetar os bits dinâmicos mais tarde, no array de messages, onde eles não invalidam nada antes deles. Um fato no turno 5 não toca o cache dos turnos 1 a 4.
Aqui está minha lista de verificação de auditoria quando um cache não acerta. Faça grep no seu código de construção de prompts por:
datetime.now()/Date.now()em qualquer lugar no system prompt ou ferramentasuuid4()ou IDs de solicitação no início do conteúdojson.dumps(d)semsort_keys=True— a ordem de iteração de dicionários Python pode mudar os bytes- f-strings interpolando um ID de usuário ou sessão no system prompt
- um conjunto de ferramentas construído por usuário, de modo que o bloco de ferramentas difere em cada solicitação
Se cache_read_input_tokens é zero em duas solicitações que você sabe compartilharem um prefixo, uma delas é a culpada. Faça diff dos bytes renderizados entre duas solicitações e você vai encontrá-la.
Colocando o ponto de interrupção
O marcador real é cache_control: {type: "ephemeral"} em um bloco de conteúdo. Caso mais simples, um grande system prompt compartilhado:
"system": [{
"type": "text",
"text": "<your big stable prompt>",
"cache_control": {"type": "ephemeral"}
}]
Como as ferramentas são renderizadas antes do sistema, um marcador no último bloco do sistema faz cache das ferramentas e do sistema juntos. Você tem no máximo quatro pontos de interrupção por solicitação, então use-os com sabedoria.
Para conversas de múltiplos turnos, coloque o ponto de interrupção no último bloco do turno mais recente. Cada nova solicitação reutiliza toda a conversa anterior como prefixo em cache, e os acertos se acumulam à medida que o chat cresce. Para um preâmbulo compartilhado com uma pergunta variável — exemplos few-shot mais uma consulta diferente a cada vez — coloque o marcador no final da parte compartilhada, nunca no final do prompt inteiro. Se você fizer cache até a pergunta variável, cada solicitação escreve uma entrada única e não lê nada.
A economia, porque não é de graça
Leituras são baratas (~0,1×) mas escritas custam mais do que uma solicitação normal: 1,25× para o TTL padrão de 5 minutos, 2× para o TTL de 1 hora. Então o caching só vale a pena se você lê mais do que escreve. Com o TTL de 5 minutos você atinge o ponto de equilíbrio em duas solicitações. Com o TTL de 1 hora você precisa de pelo menos três, porque o prêmio de escrita é dobrado.
Qual TTL? Use 1 hora apenas quando seu tráfego tiver intervalos maiores que cinco minutos. Se as solicitações chegam com mais frequência que isso, elas mantêm o cache aquecido por conta própria e o TTL padrão de 5 minutos está ótimo e é mais barato. Não use o TTL de 1 hora por padrão — o custo de escrita dobrado vai silenciosamente consumir suas economias se o tráfego for constante.
Duas armadilhas que me custaram uma tarde de depuração cada
O lookback de 20 blocos. Cada ponto de interrupção percorre no máximo 20 blocos de conteúdo para trás para encontrar uma entrada de cache anterior. Em um loop agêntico com muitos pares tool_use/tool_result, um único turno pode adicionar mais de 20 blocos — e então a próxima solicitação não consegue encontrar o cache anterior e falha silenciosamente. Correção: coloque um ponto de interrupção intermediário a cada ~15 blocos em turnos longos.
Solicitações concorrentes. Uma entrada de cache só se torna legível depois que a primeira resposta começa a ser transmitida. Dispare dez solicitações paralelas com o mesmo prefixo e todas as dez pagarão o preço integral, porque nenhuma pode ler o que as outras ainda estão escrevendo. Para fan-out, envie uma solicitação, aguarde o primeiro token transmitido, depois dispare o restante. Elas vão ler o cache que a primeira escreveu.
Um truque mais recente que vale conhecer
No Opus 4.8 há uma forma limpa de injetar uma instrução no meio de uma conversa sem destruir seu cache: adicione uma mensagem {"role": "system", ...} ao array de messages em vez de editar o system de nível superior. Editar o system de nível superior muda o prefixo antes de todo o seu histórico, então cada turno em cache reprocessa sem cache. Uma mensagem com papel de sistema fica depois do histórico e deixa o prefixo em cache intacto. É também o canal de operador não falsificável, o que é um bônus agradável.
Verifique tudo com o objeto usage: cache_creation_input_tokens é o que você escreveu, cache_read_input_tokens é o que você leu à taxa barata, input_tokens é o restante sem cache. Se seu agent rodou por uma hora e input_tokens mostra 4K, não entre em pânico — o resto veio do cache. Verifique a soma, não o campo único.
Quatro linhas de configuração, uma regra a respeitar. Respeite o prefixo e a fatura vai seguir.
