antonio leandro

claude, na prática

Batch processing

tutorial · 02 · ferramentas · Anthropic · · ~30 min de leitura do original

a tese

abrir mão da resposta imediata vale metade do preço: o mesmo modelo, a mesma request, cobrada a 50% em troca de você entregar a fila inteira e aceitar esperar até 24 horas por ela

o que fica

  1. O desconto de 50% não vem de um modelo mais fraco: é o mesmo modelo e a mesma request, cobrados pela metade em troca do direito de agendar a execução.
  2. Um batch cabe em 100.000 requests ou 256 MB, o que vier primeiro, e expira em 24 horas — o que não rodou até lá volta como `expired` e não é cobrado.
  3. Os resultados voltam fora de ordem, e o `custom_id` é a única forma de casar resposta com pedido.
  4. A validação dos `params` é assíncrona: erro de forma só aparece quando o batch inteiro termina, então vale testar uma request na Messages API antes de submeter cem mil.
  5. Prompt caching funciona dentro do batch e os descontos empilham, mas o hit é best-effort: a faixa observada vai de 30% a 98%.
  6. O beta de saída estendida, com `max_tokens` de até 300.000, existe só no Batches — uma geração dessas pode levar mais de uma hora e não cabe numa conexão síncrona.

o problema

A API síncrona cobra de todo mundo o preço de quem tem pressa. Você abre uma conexão, o servidor reserva capacidade para responder em segundos, e o preço por token embute essa reserva. Só que boa parte da carga real não tem pressa nenhuma: rodar uma eval com milhares de casos, moderar um dia inteiro de conteúdo gerado por usuário, resumir um dump de produtos, gerar descrições para um catálogo. Nada disso precisa de resposta agora, e mesmo assim paga a tarifa de quem precisa.

Quem tenta contornar isso acaba escrevendo o mesmo orquestrador de sempre: pool de workers, controle de concorrência, backoff quando chega 429, checkpoint para não reprocessar o que já passou, planilha de progresso. É infraestrutura de fila, não é o problema que você queria resolver, e ainda por cima ela compete com o tráfego de produção pelo mesmo rate limit.

a ideia

Separar urgência de custo e transformar isso em contrato. Carga previsível e sem prazo é exatamente a que o provedor consegue encaixar nos vales de demanda, então ele devolve essa flexibilidade em desconto: 50% sobre tudo, entrada e saída. Em Opus 5, US$ 2,50 por milhão de tokens de entrada e US$ 12,50 de saída.

A troca é explícita. Você entrega a lista inteira de uma vez, abre mão de saber quando cada item roda e de receber os resultados na ordem em que pediu. Em troca, some a fila que você teria de escrever, o rate limit é outro — o uso do Batches não consome o da Messages API — e a conta cai pela metade. O teto do contrato é 24 horas: passou disso, o que sobrou expira.

como funciona

Um batch é um POST para /v1/messages/batches com uma lista requests. Cada item tem um custom_id (1 a 64 caracteres, casando ^[a-zA-Z0-9_-]{1,64}$) e um objeto params idêntico ao corpo que você mandaria para a Messages API. A resposta traz um id, processing_status: "in_progress" e um request_counts com contadores de processing, succeeded, errored, canceled e expired.

Daí é poll no endpoint de retrieve até processing_status virar ended. Nesse momento aparece um results_url apontando para um arquivo .jsonl, uma linha por request, cada uma com custom_id e result.type em quatro estados: succeeded, errored, canceled, expired. Os três últimos não são cobrados. A recomendação é fazer stream do arquivo em vez de baixar tudo, porque ele pode ser grande. Erro de forma no corpo (invalid_request_error) exige consertar antes de reenviar; erro de servidor pode ser retentado direto.

Quase tudo da Messages API entra: vision, system, multi-turn, extended thinking, tool use incluindo server tools, e a maioria dos betas. Cada request roda isolada, então dá para misturar tipos diferentes no mesmo batch. Três parâmetros são recusados com erro de validação: stream: true, porque o resultado é arquivo e não fluxo; speed, porque fast mode ajusta latência síncrona; e max_tokens: 0, porque uma entrada de cache efêmera escrita dentro do batch expiraria antes da request seguinte.

Com server tools, o worker roda o mesmo loop agêntico do lado do servidor — e, como não há conexão aberta para segurar, roda mais iterações por turno antes de devolver stop_reason: "pause_turn". Se vier pause_turn, o turno não acabou: você continua submetendo o conteúdo pausado numa request seguinte. web_search é adicionalmente throttled por organização, com retry automático.

o que isso custou

Previsibilidade. A maioria dos batches termina em menos de uma hora, mas isso é observação, não garantia: sob demanda alta, mais requests expiram no limite de 24 horas. Um batch submetido é imutável — para mudar qualquer coisa, cancele e reenvie, e o cancelamento nem é imediato, deixa o batch em canceling e pode devolver resultados parciais.

O cache é best-effort justamente porque a execução é concorrente e fora de ordem: entradas de 5 minutos podem morrer no meio do batch, e a saída é usar a duração de 1 hora. Por causa da concorrência, um batch pode passar um pouco do spend limit configurado no workspace. E os dados ficam armazenados: request e resposta por até 29 dias, com endpoint de DELETE se você quiser antes.

onde isso aparece hoje

É o modo que sustenta eval em escala — a contrapartida operacional do que Demystifying evals for AI agents pede quando fala em rodar centenas de casos por mudança de prompt. É também onde Prompt caching deixa de ser otimização de latência e vira só economia, com os dois descontos empilhados.

O par oposto é Streaming Messages: mesma API, mesma request, escolha de canal — token a token para quem tem um humano esperando, arquivo .jsonl para quem não tem. E o beta de saída estendida mostra que a assincronia abriu capacidade nova, não só desconto: 300.000 tokens de max_tokens num único turno é algo que nenhuma conexão HTTP aberta ia aguentar.

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