Objetivo: ao final, o consultor usa prompt caching sem invalidá-lo por acidente, entrega resposta ancorada na fonte com citations, e controla profundidade de raciocínio por effort em vez de budget_tokens.
tools → system → messages), e qualquer byte diferente invalida tudo depois dele — data, UUID e JSON não ordenado no prefixo destroem o cache sem que nada falhe. Confira em usage.cache_read_input_tokens.budget_tokens retorna 400 nos modelos atuais. O controle de profundidade de raciocínio é output_config.effort (low → max) com adaptive thinking, e o conteúdo do raciocínio só volta com display: "summarized".response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
system=[
{
"type": "text",
"text": CATALOGO_RICEFW_MERIDIANO, # ~40 mil tokens, estável
"cache_control": {"type": "ephemeral"}, # marca o fim do prefixo estável
}
],
messages=[{"role": "user", "content": pergunta}], # varia a cada chamada
)
print(response.usage.cache_creation_input_tokens) # gravou
print(response.usage.cache_read_input_tokens) # leu (é o que você quer ver > 0)
print(response.usage.input_tokens) # pagou integral
| Item | Valor |
|---|---|
| Custo de leitura | ~0,1× do preço de entrada |
| Custo de gravação | 1,25× (TTL de 5 min) · 2× (TTL de 1 hora) |
| Ponto de equilíbrio | 2 chamadas com TTL de 5 min · 3 com TTL de 1 hora |
| Máximo de marcações | 4 por requisição |
| Prefixo mínimo | 512 tokens no Opus 5 · 1024 no Opus 4.8 e Sonnet 5 |
| Ordem de montagem | tools → system → messages |
Prefixo abaixo do mínimo não cacheia e não avisa — cache_creation_input_tokens volta zero e pronto.
| Padrão no prefixo | Por quê |
|---|---|
datetime.now() no system prompt |
Prefixo diferente em toda chamada |
| UUID ou id de requisição no começo | Idem |
json.dumps(d) sem sort_keys=True |
Serialização não determinística |
| Nome ou id do usuário no system prompt | Um cache por usuário; nada é compartilhado |
Bloco condicional (if flag: system += ...) |
Cada combinação vira um prefixo distinto |
| Lista de tools montada por usuário | Tools são a posição zero — invalida tudo |
| Trocar de modelo no meio | Cache é por modelo |
A regra de arquitetura que resolve quase tudo: congele o system prompt e a lista de tools; ponha o que varia depois da última marcação. Data, modo, nome do usuário e contexto dinâmico vão para a mensagem do usuário, não para o system.
E o diagnóstico é sempre o mesmo: se cache_read_input_tokens fica zero entre chamadas que deveriam compartilhar prefixo, compare os bytes das duas requisições. O invalidador está lá.
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{
"type": "document",
"source": {"type": "text", "media_type": "text/plain", "data": TEXTO_FS_17},
"title": "FS RICEFW 17 — Relatório de Pendências de Faturamento",
"citations": {"enabled": True},
},
{"type": "text", "text": "Quais regras de alçada esta FS define para liberação?"},
],
}],
)
for bloco in response.content:
if bloco.type == "text":
print(bloco.text)
for c in (bloco.citations or []):
print(f" ↳ {c.document_title}: “{c.cited_text}”")
Como funciona:
citations: {"enabled": True} vai em cada bloco document — todos ou nenhum.citations.cited_text, document_index, document_title e a localização: char_location para texto (índices de caractere) ou page_location para PDF (página, começando em 1).output_config.format — a combinação retorna 400.Por que isso importa em consultoria: um entregável com citação é verificável pelo cliente sem confiar em você. A afirmação "a FS 17 exige dupla aprovação acima de 50 mil" vira "a FS 17 exige dupla aprovação acima de 50 mil — página 12, trecho citado". A segunda sobrevive a uma auditoria; a primeira sobrevive até alguém conferir.
E note a ligação com a aula 3.4.2: lá o problema era resumo fluente que omite. Citations é o mecanismo que ataca isso na origem — cada afirmação carrega o ponteiro para a fonte, e o revisor confere a fonte em vez de julgar a redação.
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
thinking={"type": "adaptive", "display": "summarized"},
output_config={"effort": "high"}, # low | medium | high | xhigh | max
messages=[{"role": "user", "content": pergunta_dificil}],
)
| Parâmetro | Situação hoje |
|---|---|
thinking: {"type": "adaptive"} |
O modo atual. O Claude decide quando e quanto pensar |
thinking: {"budget_tokens": N} |
Removido — retorna 400 nos modelos atuais |
output_config.effort |
low · medium · high · xhigh · max. Padrão: high |
thinking.display |
Padrão "omitted" — blocos vêm vazios. "summarized" devolve o resumo |
| Prompt "pense passo a passo" | Desnecessário e às vezes contraproducente |
Como escolher o effort:
| Nível | Quando |
|---|---|
low |
Classificação, extração, tarefa curta e sensível a latência |
medium |
Passo abaixo do padrão para economizar em volume |
high |
Padrão. Serve para a maioria |
xhigh |
Código e trabalho agêntico difícil |
max |
Só quando correção importa mais que custo |
Dois pontos de atenção:
max_tokens limita raciocínio + resposta juntos. Com thinking ligado e teto apertado, você pode receber uma resposta quase toda de raciocínio e o texto final cortado. Em xhigh ou max, deixe folga real.display mudou em relação a modelos anteriores. Interface que mostrava o raciocínio e agora mostra uma pausa longa e nada: falta display: "summarized".Regra Wayon Entregável analítico produzido por API sobre documento de cliente — análise de escopo, leitura de contrato, comparação de especificações — vai com citations habilitado, e as citações vão junto na entrega. Sem citação, a análise é opinião com aparência de estudo. Aplicação que usa prompt caching sobre documento de cliente monitora
cache_read_input_tokens: cache que nunca acontece é custo silencioso que aparece na fatura antes de aparecer no relatório.
📖 Prompt caching · Citations · Adaptive thinking
cache_read_input_tokens igual a zero em todas as chamadas, apesar de o system prompt ser "o mesmo". Qual é a causa mais provável?Correto. O cache é casamento de prefixo por byte, e essas são as causas clássicas de invalidação silenciosa.
cache_control foi colocado no bloco errado e a API ignorou a marcaçãoMarcação em bloco não cacheável não zera a leitura em todas as chamadas — o sintoma aponta prefixo instável.
Explicaria falhas ocasionais, não zero constante em toda chamada.
Não são: output_config.format com citations retorna 400.
Funciona por acidente e quebra em produção; há caminho melhor.
Correto. A incompatibilidade é explícita na documentação.
thinking: {"type": "enabled", "budget_tokens": 8000}. O que acontece nos modelos atuais?output_config.effort com adaptive thinkingCorreto. É uma das mudanças que o material antigo não reflete.
Já foi removido nos modelos atuais — não é aviso de depreciação.
effort equivalenteNão há conversão; a requisição é recusada.
effort está baixo demais e o modelo não está pensandoCom effort baixo haveria menos raciocínio, mas não blocos sistematicamente vazios.
thinking.display está no padrão "omitted" — os blocos vêm vazios até você pedir "summarized"Correto. O padrão mudou em relação a modelos anteriores.
O raciocínio acontece e é cobrado; o que muda é a visibilidade.