mozak.tech Engenharia de IA Corporativa 76%

Parte II — Acesso Programático e Instrução de Modelos

2.4 — Economia de Tokens: Cache, Métricas e Controle de Custos (Token Economics)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Calcular o custo real de um pipeline de LLM considerando tokens de input, output, cache hit e cache miss

Implementar Prompt Caching da Anthropic com cache_control: ephemeral em system prompts e documentos de referência

Monitorar uso de tokens por request com a classe MetricasUso e identificar onde o custo está concentrado

Aplicar estratégias de otimização: seleção dinâmica de modelo, caching de prefixo, compressão de contexto

Interpretar as métricas de cache_creation_input_tokens vs cache_read_input_tokens para avaliar efetividade do cache

Por que isso importa

O custo de API de LLM não é fixo — varia 100x dependendo de como você usa os modelos. Uma equipe que processa 10 milhões de tokens por dia com Claude Sonnet sem otimização paga cerca de $30/dia ($900/mês). Com Prompt Caching ativo em system prompts longos que repetem, esse mesmo volume pode cair para $12/dia — 60% de redução.

Em 2024, a Anthropic reportou que equipes usando Prompt Caching economizaram em média 73% em tokens de sistema. Para um pipeline que usa um system prompt de 8.000 tokens (documentação técnica, regras de negócio, exemplos) em cada request, o caching elimina o custo desses 8.000 tokens em todas as chamadas subsequentes dentro da janela de 5 minutos.

Mas o custo não é só de tokens. É também de latência. Um request com 10.000 tokens de contexto sem caching leva 800ms de TTFT (time to first token) em Claude Sonnet. O mesmo request com cache hit leva 200ms — 4x mais rápido. Em aplicações interativas, isso é a diferença entre UX aceitável e UX que faz o usuário desistir.

Esta unidade te ensina a medir, entender e otimizar ambas as dimensões: custo e latência.

Conceitos Fundamentais

A Anatomia do Custo de um Request LLM

Todo request tem exatamente dois componentes de custo:

Custo total = (tokens_input × preço_input) + (tokens_output × preço_output)

Para Claude Sonnet 4.6 (preços de 2025): - Input: $3.00 por 1M tokens - Output: $15.00 por 1M tokens

FinOps: Quatro Preços Distintos e Alavancas de Custo

Uma chamada de API de LLM tem até quatro linhas de custo separadas: input (tokens do prompt), output (tokens gerados — tipicamente 3–5× mais caro que input), cache_creation (criação de cache do prefixo — 1.25× o preço de input), cache_read (leitura de cache — 0.1× o preço de input). Alavancas de redução de output: defina max_tokens explicitamente, peça respostas concisas no prompt, prefira saída estruturada (JSON com campos curtos) a texto livre. Break-even do cache: se o prefixo é lido mais de ~2 vezes dentro do TTL, o cache já pagou o custo de criação. Batch API: 50% de desconto para jobs assíncronos com até 24h de latência — ideal para cargas offline de alto volume.

Por que output é 5x mais caro? Geração de tokens é mais custosa computacionalmente que processamento de input. O modelo precisa executar forward pass para cada token gerado sequencialmente.

Implicação prática: Reduza tokens de output quando possível. “Responda em JSON com campos X, Y, Z” é mais eficiente que “Explique detalhadamente X, Y e Z”. Para tarefas de classificação, max_tokens=50 é suficiente — não use 500.

O Que É Prompt Caching

Fundamento: KV-Cache da Atenção

A cada token do prompt, o modelo calcula vetores Key (K) e Value (V) para aquele token — são eles que os outros tokens consultam na atenção. Se o prefixo do prompt não mudou, esses K e V já foram calculados na requisição anterior e podem ser reutilizados. O prompt caching funciona assim: o provedor guarda os K e V do prefixo em memória por alguns minutos. Na requisição seguinte com o mesmo prefixo, pula o recálculo e cobra menos. Consequência de design: qualquer alteração nos tokens antes do ponto de cache invalida o cache inteiro — por isso o padrão é colocar o conteúdo estático (system prompt, documentos de referência) no início e o conteúdo dinâmico (pergunta do usuário) no fim.

Prompt Caching é um mecanismo da Anthropic onde partes do prompt que não mudam entre requests são processadas uma vez e o KV-cache (Key-Value cache) da atenção é guardado por 5 minutos.

Sem caching:

Request 1: [system: 8000 tokens] + [user: 50 tokens] → paga 8050 tokens de input

Request 2: [system: 8000 tokens] + [user: 60 tokens] → paga 8060 tokens de input

Request 3: [system: 8000 tokens] + [user: 45 tokens] → paga 8045 tokens de input

Total 3 requests: 24.155 tokens de input

Com caching:

Request 1: [system: 8000 tokens] + [user: 50 tokens]

  → cache_creation: 8000 tokens (paga 25% a mais na criação)

  → tokens efetivos: 8050 tokens input + overhead de criação



Request 2: [system: cache hit] + [user: 60 tokens]

  → cache_read: 8000 tokens (paga 10% do preço normal!)

  → tokens efetivos: 50 (user) + 480 (8000 × 6% custo do cache read)



Request 3: [system: cache hit] + [user: 45 tokens]

  → similar ao request 2

Resultado: requests 2 e 3 são ~90% mais baratos em input. O breakeven é atingido depois de 2 requests com cache hit.

Preços de Cache da Anthropic

Cache creation: 1.25× o preço normal de input

Cache read: 0.1× o preço normal de input (10% = 90% de desconto!)

Para Claude Sonnet ($3/1M input): - Cache creation: $3.75/1M tokens (25% mais caro que normal) - Cache read: $0.30/1M tokens (90% mais barato que normal)

Quando compensa usar caching? Se você usa o mesmo prefixo em N requests dentro de 5 minutos:

Sem cache: N × 8000 × $3/1M = N × $0.024

Com cache: 8000 × $3.75/1M (criação) + (N-1) × 8000 × $0.30/1M (leituras)

         = $0.030 + (N-1) × $0.0024



Break-even: N onde com_cache < sem_cache

$0.030 + (N-1) × $0.0024 < N × $0.024

$0.030 - $0.0024 < N × ($0.024 - $0.0024)

$0.0276 < N × $0.0216

N > 1.28



→ Com 2 ou mais requests no mesmo cache TTL, já economiza

O Header cache_control

Para ativar caching em um bloco do sistema, você adiciona cache_control: {'type': 'ephemeral'} ao conteúdo que quer cachear. O modelo cria um “checkpoint” no KV-cache até aquele ponto.

# Estrutura para system com cache

system = [

    {

        'type': 'text',

        'text': 'Seu system prompt longo aqui...',

        'cache_control': {'type': 'ephemeral'}  # marca o ponto de cache

    }

]

O cache é criado até o último bloco marcado com cache_control. Blocos depois do marcador não são cacheados — geralmente o conteúdo dinâmico (a pergunta do usuário) fica fora do cache.

Estratégias de Otimização Além do Caching

1. Seleção dinâmica de modelo por complexidade

def escolher_modelo(complexidade: str) -> str:

    return {

        'simples': 'claude-haiku-4-5-20251001',    # classificação, extração simples

        'media': 'claude-haiku-4-5-20251001',       # summarização curta

        'complexa': 'claude-sonnet-4-6',            # raciocínio multi-step

        'critica': 'claude-opus-4-8',               # análise de alta qualidade

    }.get(complexidade, 'claude-haiku-4-5-20251001')

Autocompletar de código, classificação de sentimentos, extração de campos simples: Haiku (12x mais barato que Sonnet).

2. Compressão de contexto Para histórico de conversa longo, em vez de passar todo o histórico:

# INEFICIENTE: passa 50 turnos de histórico

messages = historico_completo + [novo_turno]



# EFICIENTE: sumariza histórico antigo, mantém últimos 5 turnos verbatim

resumo = sumarizar_historico(historico_antigo)

messages = [

    {'role': 'user', 'content': f'Contexto anterior: {resumo}'},

    {'role': 'assistant', 'content': 'Entendido.'},

] + ultimos_5_turnos + [novo_turno]

3. Batch API (OpenAI e Anthropic) Para processamento não-urgente (análise de documentos, geração de relatórios): - Anthropic Message Batches API: 50% de desconto, até 24h de latência - OpenAI Batch API: 50% de desconto, até 24h de latência

Processar 1000 análises de documentos com batch em vez de requests individuais reduz custo pela metade.

4. Streaming para percepção de latência Com streaming, o usuário vê tokens chegando progressivamente — a latência percebida é o TTFT (200-500ms), não o tempo total de resposta (2-10s). Para interfaces conversacionais, streaming sempre.

Aprofundamento Técnico

O Response de Uso do Anthropic

Quando você faz um request com caching ativo, o response inclui:

response.usage = {

    'input_tokens': 50,                      # tokens não-cacheados (user message)

    'output_tokens': 120,                    # tokens gerados

    'cache_creation_input_tokens': 8000,     # tokens que criaram cache (primeira vez)

    'cache_read_input_tokens': 0,            # tokens lidos do cache (= 0 na criação)

}



# No segundo request (cache hit):

response.usage = {

    'input_tokens': 60,

    'output_tokens': 115,

    'cache_creation_input_tokens': 0,        # não criou novo cache

    'cache_read_input_tokens': 8000,         # 8000 tokens lidos do cache a 10% do preço

}

Como calcular o custo real com cache:

def custo_usd(uso: dict, modelo: str = 'claude-sonnet-4-6') -> float:

    precos = {

        'claude-sonnet-4-6': {

            'input': 3.0 / 1_000_000,

            'output': 15.0 / 1_000_000,

            'cache_creation': 3.75 / 1_000_000,

            'cache_read': 0.30 / 1_000_000,

        },

        'claude-haiku-4-5-20251001': {

            'input': 0.25 / 1_000_000,

            'output': 1.25 / 1_000_000,

            'cache_creation': 0.30 / 1_000_000,  # 25% mais caro

            'cache_read': 0.025 / 1_000_000,     # 10% do preço normal

        },

    }

    p = precos[modelo]

    return (

        uso.get('input_tokens', 0) * p['input'] +

        uso.get('output_tokens', 0) * p['output'] +

        uso.get('cache_creation_input_tokens', 0) * p['cache_creation'] +

        uso.get('cache_read_input_tokens', 0) * p['cache_read']

    )

Implementando MetricasUso

A classe MetricasUso do starter serve para acumular estatísticas ao longo de múltiplos requests:

from dataclasses import dataclass, field



@dataclass

class MetricasUso:

    """Acumula métricas de múltiplos requests para análise."""

    total_requests: int = 0

    input_tokens: int = 0

    output_tokens: int = 0

    cache_creation_input_tokens: int = 0

    cache_read_input_tokens: int = 0



    def atualizar(self, usage) -> None:

        """Adiciona uso de um response ao acumulado."""

        self.total_requests += 1

        self.input_tokens += usage.input_tokens

        self.output_tokens += usage.output_tokens

        # Campos de cache podem não existir em respostas sem caching

        self.cache_creation_input_tokens += getattr(usage, 'cache_creation_input_tokens', 0)

        self.cache_read_input_tokens += getattr(usage, 'cache_read_input_tokens', 0)



    def custo_total_usd(self, modelo: str = 'claude-sonnet-4-6') -> float:

        return custo_usd({

            'input_tokens': self.input_tokens,

            'output_tokens': self.output_tokens,

            'cache_creation_input_tokens': self.cache_creation_input_tokens,

            'cache_read_input_tokens': self.cache_read_input_tokens,

        }, modelo)



    def eficiencia_cache(self) -> float:

        """Percentual dos tokens de input que vieram do cache."""

        total_input = self.input_tokens + self.cache_read_input_tokens

        if total_input == 0:

            return 0.0

        return self.cache_read_input_tokens / total_input



    def resumo(self) -> dict:

        return {

            'requests': self.total_requests,

            'tokens': {

                'input': self.input_tokens,

                'output': self.output_tokens,

                'cache_created': self.cache_creation_input_tokens,

                'cache_read': self.cache_read_input_tokens,

            },

            'eficiencia_cache': f"{self.eficiencia_cache():.1%}",

            'custo_estimado_usd': round(self.custo_total_usd(), 6),

        }

O Parâmetro system com Cache no Anthropic SDK

A forma mais comum de usar caching é em system prompts longos que repetem entre requests:

import anthropic



client = anthropic.Anthropic()



SYSTEM_PROMPT_LONGO = """

Você é um especialista em análise de código Python.



REGRAS DE ANÁLISE:

1. Sempre identifique complexidade ciclomática

2. Verifique nomenclatura de variáveis segundo PEP8

3. Analise potencial de Memory Leaks

4. Verifique tratamento de exceções

...

[mais 5000 palavras de regras e exemplos]

"""



def analisar_codigo(codigo: str, metricas: MetricasUso) -> str:

    response = client.messages.create(

        model='claude-sonnet-4-6',

        max_tokens=500,

        system=[

            {

                'type': 'text',

                'text': SYSTEM_PROMPT_LONGO,

                'cache_control': {'type': 'ephemeral'},  # cacheia este bloco

            }

        ],

        messages=[

            {'role': 'user', 'content': f'Analise este código:\n\n```python\n{codigo}\n```'}

        ]

    )



    metricas.atualizar(response.usage)

    return response.content[0].text

Na primeira chamada: cache_creation_input_tokens = len(SYSTEM_PROMPT_LONGO em tokens). Na segunda chamada dentro de 5 min: cache_read_input_tokens = mesmo número, com 90% de desconto.

Exemplos Anotados

Exemplo 1: Comparação Com e Sem Caching

import anthropic

import time



client = anthropic.Anthropic()



# System prompt longo que simula documentação técnica (normalmente 2000-8000 tokens)

SYSTEM = """Você analisa código Python. Regras:

1. Complexidade: identifique loops aninhados, recursão excessiva

2. Nomenclatura: PEP8 — snake_case para variáveis, PascalCase para classes

3. Imports: sem imports não usados, sem star imports

4. Exceptions: nunca use except sem tipo específico

5. Type hints: funções públicas devem ter type hints

[... imagine mais 4000 tokens de regras aqui ...]"""



CODIGOS = [

    "def soma(a, b):\n    return a+b",

    "def calc(x,y,z):\n    result=x*y+z\n    return result",

    "class minha_classe:\n    def __init__(self):\n        pass",

]



# === SEM CACHE ===

print("=== Sem cache ===")

total_input_sem_cache = 0

for codigo in CODIGOS:

    r = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=200,

        system=SYSTEM,  # string simples, sem cache_control

        messages=[{'role': 'user', 'content': f'Analise:\n{codigo}'}]

    )

    # Cada request paga o system prompt inteiro

    total_input_sem_cache += r.usage.input_tokens

    print(f"  Input tokens: {r.usage.input_tokens}")



print(f"Total input tokens: {total_input_sem_cache}")

# Output esperado: ~(system_tokens + user_tokens) × 3



# === COM CACHE ===

print("\n=== Com cache ===")

total_input_com_cache = 0

cache_criado = False



for i, codigo in enumerate(CODIGOS):

    r = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=200,

        system=[

            {

                'type': 'text',

                'text': SYSTEM,

                'cache_control': {'type': 'ephemeral'},  # cacheia o system

            }

        ],

        messages=[{'role': 'user', 'content': f'Analise:\n{codigo}'}]

    )



    # Primeiro request: cache_creation_input_tokens > 0

    # Requests seguintes: cache_read_input_tokens > 0 (muito mais barato)

    criacao = getattr(r.usage, 'cache_creation_input_tokens', 0)

    leitura = getattr(r.usage, 'cache_read_input_tokens', 0)



    if i == 0:

        print(f"  Request 1 — Cache creation: {criacao} tokens")

    else:

        print(f"  Request {i+1} — Cache read: {leitura} tokens (90% desconto)")



    # Custo efetivo: input normal + cache_read × 0.1

    tokens_efetivos = r.usage.input_tokens + leitura * 0.1

    total_input_com_cache += tokens_efetivos



print(f"\nCusto efetivo com cache: {total_input_com_cache:.1f} token-equivalentes")

# vs total_input_sem_cache — diferença visível no 2º e 3º request

Exemplo 2: LLMWrapper Completo com Caching e Métricas

import os

import anthropic

from dataclasses import dataclass, field



client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))



@dataclass

class MetricasUso:

    total_requests: int = 0

    input_tokens: int = 0

    output_tokens: int = 0

    cache_creation_input_tokens: int = 0

    cache_read_input_tokens: int = 0



    def atualizar(self, usage) -> None:

        self.total_requests += 1

        self.input_tokens += usage.input_tokens

        self.output_tokens += usage.output_tokens

        # getattr com default 0 para compatibilidade sem caching

        self.cache_creation_input_tokens += getattr(usage, 'cache_creation_input_tokens', 0) or 0

        self.cache_read_input_tokens += getattr(usage, 'cache_read_input_tokens', 0) or 0



    def custo_haiku_usd(self) -> float:

        """Custo com preços de Haiku 4.5."""

        return (

            self.input_tokens * 0.25 / 1_000_000 +

            self.output_tokens * 1.25 / 1_000_000 +

            self.cache_creation_input_tokens * 0.30 / 1_000_000 +  # 1.25× normal

            self.cache_read_input_tokens * 0.025 / 1_000_000       # 0.1× normal

        )



    def economia_cache_usd(self) -> float:

        """Quanto foi economizado pelo cache (vs pagar preço normal)."""

        economia_por_token_cache = (0.25 - 0.025) / 1_000_000  # diferença de preço

        return self.cache_read_input_tokens * economia_por_token_cache





class LLMWrapper:

    """Wrapper com caching de system prompt e rastreamento de métricas."""



    def __init__(self, system: str, modelo: str = 'claude-haiku-4-5-20251001'):

        self.system = system

        self.modelo = modelo

        self.metricas = MetricasUso()



    def chamar(self, prompt: str, max_tokens: int = 300) -> str:

        """Chama o LLM com system cacheado."""

        response = client.messages.create(

            model=self.modelo,

            max_tokens=max_tokens,

            # TODO 1: system com cache_control

            # system=[{'type': 'text', 'text': self.system, 'cache_control': {'type': 'ephemeral'}}]

            system=self.system,  # sem cache por enquanto

            messages=[{'role': 'user', 'content': prompt}]

        )



        # TODO 2: atualizar self.metricas com response.usage

        # self.metricas.atualizar(response.usage)



        return response.content[0].text



    def relatorio(self) -> dict:

        return {

            'total_requests': self.metricas.total_requests,

            'custo_usd': round(self.metricas.custo_haiku_usd(), 6),

            'economia_cache_usd': round(self.metricas.economia_cache_usd(), 6),

            'cache_read_tokens': self.metricas.cache_read_input_tokens,

        }





# Teste: mesmo sistema com 5 perguntas diferentes

SYSTEM = """Você é um especialista em Python. Responda de forma objetiva.

Regra: sempre inclua exemplo de código na resposta.

Regra: máximo 3 frases de explicação.

[imagine um system prompt longo de 2000+ tokens aqui]"""



wrapper = LLMWrapper(SYSTEM)



perguntas = [

    "Como criar uma dataclass em Python?",

    "Quando usar @classmethod vs @staticmethod?",

    "Como funciona o GIL do Python?",

    "O que é um context manager?",

    "Como usar type hints em funções?",

]



for p in perguntas:

    resposta = wrapper.chamar(p)

    print(f"Q: {p[:50]}...")

    print(f"R: {resposta[:100]}...")

    print()



print("=== Relatório de Uso ===")

import json

print(json.dumps(wrapper.relatorio(), indent=2))

Padrões e Armadilhas

Padrões Corretos

Padrão 1: Coloque system prompts estáticos como primeiros blocos cacheáveis

# CORRETO: parte estática antes, parte dinâmica depois

system = [

    {

        'type': 'text',

        'text': REGRAS_ESTATICAS_LONGAS,        # 8000 tokens que não mudam

        'cache_control': {'type': 'ephemeral'}, # ← cacheia até aqui

    },

    {

        'type': 'text',

        'text': f'Contexto do usuário: {user_context}',  # dinâmico, não cacheado

    }

]

Padrão 2: Medir eficiência do cache antes de confiar nele

# Sempre logar cache_creation vs cache_read para saber se está funcionando

if getattr(response.usage, 'cache_read_input_tokens', 0) > 0:

    print("Cache HIT ✓")

elif getattr(response.usage, 'cache_creation_input_tokens', 0) > 0:

    print("Cache CREATED — próximo request será mais barato")

else:

    print("Sem cache — verifique se cache_control foi aplicado corretamente")

Padrão 3: Usar getattr com default 0 para compatibilidade

# O campo cache_creation_input_tokens pode não existir em alguns responses

# (sem caching ativo ou versões antigas do SDK)

criacao = getattr(response.usage, 'cache_creation_input_tokens', 0) or 0

leitura = getattr(response.usage, 'cache_read_input_tokens', 0) or 0

Armadilhas

⚠️ Armadilha 1: Cache com TTL de 5 minutos — não use para dados altamente variáveis

# PROBLEMA: se o "contexto fixo" muda a cada 4 minutos,

# você paga cache_creation em cada request (25% mais caro que sem cache)

system = [

    {

        'type': 'text',

        'text': f'Relatório atual: {relatorio_muda_toda_hora}',

        'cache_control': {'type': 'ephemeral'},  # criado mas nunca lido do cache

    }

]



# CORRETO: só coloca no cache o que é verdadeiramente estático

⚠️ Armadilha 2: Não confundir input_tokens com tokens totais de input

response.usage.input_tokens  # tokens NÃO cacheados (dinâmicos)

response.usage.cache_read_input_tokens  # tokens lidos do cache



# Tokens de input TOTAIS = input_tokens + cache_read_input_tokens

# Para calcular custo, use cada campo com seu preço específico — não some e aplique preço único

tokens_totais_nao_cacheados = response.usage.input_tokens

tokens_do_cache = response.usage.cache_read_input_tokens

# Cada um tem preço diferente!

⚠️ Armadilha 3: cache_control em messages em vez de system

# ERRADO: tentando cachear na mensagem de usuário (não suportado da mesma forma)

messages = [

    {'role': 'user', 'content': [

        {'type': 'text', 'text': doc_longo, 'cache_control': {'type': 'ephemeral'}}

    ]}

]

# O cache de mensagens funciona mas tem semântica diferente — sistema é mais efetivo

# para contexto que repete entre requests

⚠️ Armadilha 4: Não rastrear métricas e descobrir o custo pela fatura

# SEM monitoramento: fatura do mês vem R$ 5000 e você não sabe por quê

response = client.messages.create(...)



# COM monitoramento: você vê o problema em tempo real

response = client.messages.create(...)

logger.info('llm_cost', extra={

    'input_tokens': response.usage.input_tokens,

    'output_tokens': response.usage.output_tokens,

    'cache_read_tokens': getattr(response.usage, 'cache_read_input_tokens', 0),

    'custo_estimado_usd': calcular_custo(response.usage),

})

⚠️ Armadilha 5: Esperar que o cache funcione com system como string simples

# NÃO cria cache — sistema como string não tem cache_control

system='Você é um assistente especialista...'



# CRIA cache — sistema como lista de blocos com cache_control

system=[{'type': 'text', 'text': '...', 'cache_control': {'type': 'ephemeral'}}]
⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

O starter tem 2 TODOs que implementam caching e métricas:

TODO 1 — Em LLMWrapper.chamar(), o parâmetro system está passando como string simples. Substitua por lista de blocos com cache_control. A estrutura é:

system=[{

    'type': 'text',

    'text': self.system,

    'cache_control': {'type': 'ephemeral'}

}]

Isso marca o system prompt inteiro como cacheável. Seção de referência: Conceitos Fundamentais → “O Header cache_control” e Aprofundamento Técnico → “O Parâmetro system com Cache”.

TODO 2 — Ainda em chamar(), após receber o response, chame self.metricas.atualizar(response.usage). Isso passa o objeto usage do response para a classe MetricasUso que acumula input_tokens, output_tokens, cache_creation_input_tokens e cache_read_input_tokens. Seção de referência: Aprofundamento Técnico → “Implementando MetricasUso”.

Após implementar, o main() do starter faz 5 perguntas com o mesmo wrapper e imprime o relatório de métricas. Na primeira chamada você verá cache_creation_input_tokens > 0. Nas chamadas seguintes (dentro de 5 minutos), verá cache_read_input_tokens > 0 e o custo acumulado mostrará a economia.

Experimento adicional: compare o custo de 5 requests COM caching vs sem caching comentando o cache_control. A diferença em tokens pequenos não é dramática, mas imagine escalar para 10.000 requests/dia com um system prompt de 8.000 tokens — o caching economiza $21.600/mês nesses volumes.

Agora você está pronto para o lab.