Como rodar muitas decisões do Jev em paralelo sem travar a sua aplicação?

Rodar o Jev em paralelo é menos sobre abrir mil conexões e mais sobre a ordem das decisões: agrupar, limitar, isolar. Como todas as questões de uma requisição enxergam o mesmo state, juntar perguntas do mesmo documento numa chamada só já derruba custo e latência (no cookbook oficial, 13 questões ficaram 12,2x mais baratas e 10,0x mais rápidas). Depois disso você paraleliza entre states diferentes com o cliente assíncrono e um semáforo, respeitando os dois limites da API (250.000 tokens/s e 1.200 requisições/min), com backoff em 429 e efeito colateral idempotente do seu lado
Você manda um lote de alguns milhares de itens pro Jev classificar, e de repente a sua aplicação engasga
Request do usuário travando, timeout na tela, 429 pipocando no log
Aí bate aquela dúvida: será que o modelo é lento? Quase nunca é isso
Na maioria das vezes o gargalo está no SEU lado, no jeito como o cliente dispara as chamadas: tudo de uma vez, sem limite de concorrência, dentro do mesmo request HTTP que o usuário está esperando
Neste post a gente vai destrinchar isso em camadas: agrupar questões na mesma chamada, montar um pool com limite de paralelismo e tratar 429 com backoff, tudo no passo a passo
E depois, numa seção só dela, a camada que realmente destrava a tela: tirar o lote do caminho síncrono do usuário
Bora ver na prática? 🙂
O que você precisa antes de começar
Nada de PC da Nasa aqui, só o básico montado direito
- SDK instalado. No Python:
pip install typesafe-sdk(ouuv add typesafe-sdk), com Python >= 3.10. No JavaScript/TypeScript: o pacote@typesafe-ai/sdk, que expõe a classeTypeSafeCliente o métodosystemOne - Chave de API na variável de ambiente
TYPESAFE_API_KEY. Os SDKs leem essa variável automaticamente quando você não passa a chave no construtor, então dá pra manter o código limpo - Modelo definido:
jev-latestaponta sempre pro mais recente da família, ejev-1.13.0é a versão fixada. Pra lote, pensa bem: versão fixada te dá resultado estável entre execuções - Entender o formato da requisição: três campos principais,
state,modelequestions. Oquestionsé um mapa de questões tipadas, cada uma com umidescolhido por você, e a resposta volta com umaanswerporid
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
Duas coisas que evitam dor de cabeça mais na frente
Primeira: a entrada do Jev é só texto (string ou dado estruturado enviado como state)
Segunda: aquele id que você dá pra cada questão serve só pra localizar a resposta correspondente, ele não é enviado ao modelo nem usado na inferência
Guarda essa segunda, ela volta mais na frente, lá no tópico sobre retry e efeito duplicado
Por que o Jev muda o desenho do lote:
Antes de sair codando, vale entender o que você tem em mãos
O Jev é o primeiro modelo System One da TypeSafe AI: em vez de gerar texto, ele recebe um estado não estruturado e devolve decisões tipadas (enum, booleano, score, categoria) com confiança calibrada
Se você conhece o fluxo de um LLM de chat, inverte a cabeça: aqui não tem streaming de token pra esperar, tem resposta tipada por questão
E tem um detalhe que é o coração deste post: o Jev ingere o state uma vez e avalia todas as questões contra ele em paralelo, numa única passada
Ou seja, já existe paralelismo DENTRO da chamada
Muita gente ignora isso e sai paralelizando por fora, do jeito mais caro possível…
Passo a passo: do disparo ingênuo ao pool controlado
- Faça a chamada base e entenda o contrato
O endpoint é POST https://api.typesafe.ai/v1/systemone, com os headers Authorization: Bearer <API_KEY> e Content-Type: application/json
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @payload.json
O payload.json carrega os três campos: o state com o conteúdo a ser avaliado, o model (jev-latest ou jev-1.13.0) e o questions, o mapa com os ids que você escolheu
O erro comum deste passo: achar que o id da questão influencia a resposta e começar a escrever id gigante tipo questao_para_detectar_se_o_texto_e_spam_em_portugues. Não adianta, ele não vai pro modelo. Use id curto e estável, porque ele vai virar chave no seu banco depois
- Agrupe as questões do mesmo
statenuma chamada só
Essa é a otimização que a maioria pula
Uma chamada com N questões dá as mesmas respostas que N chamadas com uma questão cada
A diferença é que, quando um documento grande domina cada requisição, a chamada em lote envia o documento uma vez só
No cookbook de questões paralelas da TypeSafe, um briefing regulatório de 13 questões sobre o artigo da Wikipédia sobre GDPR ficou 12,2x mais barato e 10,0x mais rápido quando todas as questões foram agrupadas numa única chamada, sem mudança nas respostas
Insano, né? 😀
import os
from typesafe_sdk import TypeSafeClient
# o dicionário de questões tipadas fica no seu módulo,
# cada chave é o id que você escolheu
from questions import QUESTIONS
client = TypeSafeClient() # lê TYPESAFE_API_KEY do ambiente
response = client.system_one(
state=documento,
model="jev-1.13.0",
questions=QUESTIONS,
)
O erro comum deste passo: empilhar questão sem olhar o orçamento de tokens. São dois números diferentes: 64k cobre o state mais todas as questões somadas, e 32k se aplica ao state mais a questão mais longa. Documento grande com 40 questões estoura o primeiro fácil, e aí você precisa quebrar em grupos de questões, não em chamadas de uma questão cada
- Paralelize entre states diferentes, não entre questões do mesmo state
Agora sim: quando você tem mil itens distintos, cada um com seu próprio state, o paralelismo por fora faz sentido
No Python isso é o AsyncTypeSafeClient, com o método system_one recebendo state e questions
import asyncio
from typesafe_sdk import AsyncTypeSafeClient
from questions import QUESTIONS
client = AsyncTypeSafeClient()
async def avaliar(item):
return await client.system_one(
state=item["texto"],
model="jev-1.13.0",
questions=QUESTIONS,
)
async def main(itens):
return await asyncio.gather(*(avaliar(i) for i in itens))
Curso RecomendadoDesenvolva sistemas mais inteligentes, rápidos e robustos com JevConhecer cursoO erro comum deste passo: rodar exatamente esse gather com a lista inteira de 5 mil itens e comemorar
Ele dispara TUDO de uma vez
Não é paralelismo, é enxurrada… e a conta chega em forma de 429
- Coloque um teto no paralelismo com um semáforo
O pool controlado é literalmente isso: um semáforo limitando quantas chamadas ficam em voo ao mesmo tempo
import asyncio
from typesafe_sdk import AsyncTypeSafeClient
from questions import QUESTIONS
client = AsyncTypeSafeClient()
async def avaliar(item, sem):
async with sem:
return await client.system_one(
state=item["texto"],
model="jev-1.13.0",
questions=QUESTIONS,
)
async def rodar_lote(itens, limite):
sem = asyncio.Semaphore(limite)
return await asyncio.gather(
*(avaliar(i, sem) for i in itens),
return_exceptions=True,
)
Repara no return_exceptions=True
Sem ele, um item que falha derruba o gather inteiro e você perde o resultado dos que já tinham voltado
No JS/TS a ideia é a mesma, só muda a roupa: TypeSafeClient com o método systemOne, e você controla o teto processando a fila em fatias em vez de jogar um Promise.all na lista inteira
O erro comum deste passo: chutar o limite como número mágico. Ele não é um número universal, ele depende do tamanho médio do seu state. Item pequeno aguenta mais concorrência, documento gordo estoura tokens por segundo muito antes de estourar requisições por minuto
- Deixe o retry com backoff do SDK ligado
Os SDKs oficiais já fazem retry com backoff por padrão e respeitam o header retry-after quando a resposta traz um
No Python dá pra customizar isso passando uma RetryPolicy, e RetryPolicy(max_retries=0) desliga as tentativas
O erro comum deste passo: desligar o retry "pra ter controle" e não implementar controle nenhum. Só zera a RetryPolicy se você já tem a sua própria camada de fila e reprocessamento. Caso contrário, deixa o SDK trabalhar
Quando aparece 429 Too Many Requests (e como não voltar a ver)
Sintoma: o lote começa lindo, processa uns minutos, e do nada a taxa de erro dispara. 429 Too Many Requests em série, às vezes 529 Overloaded no meio
Causa: os limites de uso da API TypeSafe são medidos em DUAS dimensões, tokens por segundo e requisições por minuto
E uma requisição acima de qualquer um dos dois limites recebe 429
Os limites publicados por conta para o Jev são 250.000 tokens por segundo e 1.200 requisições por minuto
É aqui que quase todo mundo se ferra: dimensiona o pool contando requisição e esquece do token
Se o seu state médio é um documento grande, você bate os 250.000 tokens/s bem antes de chegar perto das 1.200 requisições/min
Solução: a orientação oficial para 429 e 529 é repetir a requisição com backoff exponencial, em vez de tentar de novo imediatamente
Retry imediato em cima de rate limit é jogar gasolina: você aumenta a pressão exatamente no momento em que precisava reduzir
Como prevenir:
- Dimensione o pool pelos dois limites ao mesmo tempo. Estime o tamanho médio do seu
stateem tokens, multiplique pela concorrência pretendida e compare com o teto de tokens por segundo - Não trate esses números como pedra. A própria documentação avisa que os limites estão sendo ajustados dinamicamente e podem mudar sem aviso
- Se o seu volume não cabe mesmo, limites maiores existem em planos custom e enterprise, via contato com
[email protected] - E instrumente isso. Taxa de 429 por minuto e latência por item são o tipo de coisa que você quer no gráfico junto com as decisões do Jev em produção, não descobrindo por reclamação de usuário
Retry sem idempotência: quando a resposta certa vira efeito duplicado
Sintoma: você ativou o retry, o 429 sumiu, a felicidade durou um dia
Aí começam os relatos: registro processado duas vezes, e-mail duplicado pro mesmo usuário, contador de moderação inflado, item aparecendo repetido na fila de revisão
Causa: repetir a chamada ao Jev é seguro, ela é uma avaliação
O problema não é a chamada, é o EFEITO COLATERAL que você executa depois dela
Se o seu código faz "chama o Jev e já manda o e-mail", cada retry que atravessa metade do caminho pode disparar o efeito de novo

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.E atenção num ponto que confunde muita gente: o id da questão é só chave de correlação da resposta
Ele não é, e nunca foi, um mecanismo de deduplicação de requisição
Solução: separa em duas etapas bem marcadas
- Decidir: chama o Jev, pega as respostas tipadas
- Aplicar: grava e só então dispara o efeito
A gravação usa uma chave estável, montada por você, tipo id_do_item + id_da_questao
Com essa chave, a escrita vira idempotente: se o registro já existe com aquela chave, você atualiza e não reaplica o efeito
chave = f"{item['id']}:{'categoria'}"
if not repo.ja_processado(chave):
repo.salvar(chave, resposta)
aplicar_efeito(item, resposta)
Como prevenir: nunca coloque o efeito colateral dentro da mesma função que faz a chamada com retry
Essa separação é o que permite reprocessar backlog sem medo, e é exatamente o padrão que você quer no fluxo de moderar conteúdo enviado por usuários, onde aplicar a mesma punição duas vezes é problema de verdade
Como isolar o lote do caminho que o usuário vê na tela
Aqui vai o padrão que resolve o "minha aplicação travou"
O request HTTP do usuário não deveria esperar mil decisões acontecerem, simples assim
- O request do usuário só enfileira. Ele valida a entrada, grava um job com status pendente e devolve um identificador. Responde em milissegundos
- Um worker consome a fila com o pool limitado que você montou lá no passo 4. Ele roda fora do processo web, no ritmo que a API aguenta, não no ritmo da ansiedade do usuário
- O resultado é persistido por chave estável (aquele
id_do_item + id_da_questao) e consultado depois, por polling ou por notificação
Por que isso importa tanto? Decisão do Jev que alimenta dashboard, triagem interna ou moderação em massa não tem nenhuma obrigação de bloquear o request HTTP
O usuário quer saber que o envio foi aceito, não quer segurar a conexão aberta enquanto 3 mil itens são avaliados
E quando vale fazer inline? Quando é UMA chamada única, com várias questões sobre o mesmo state
É justamente o caso barato e rápido: uma passada só, o documento enviado uma vez, todas as questões avaliadas de forma independente contra aquele mesmo state
Esse cenário cabe dentro do request sem drama
O que não cabe é laço de N chamadas dentro do controller… esse é o clássico que derruba o servidor em produção numa sexta à noite 😛
Três cenários de lote e o padrão certo para cada um
| Cenário | Formato do lote | Padrão recomendado | O que otimizar |
|---|---|---|---|
| A | Um documento grande, muitas perguntas | Agrupar tudo numa chamada só | Custo de tokens de entrada |
| B | Milhares de itens pequenos, mesmas questões | Pool assíncrono com limite de paralelismo, uma chamada por item | Latência total do lote |
| C | Reprocessamento de backlog | Fila, checkpoint por item e escrita idempotente | Taxa de erro e retrabalho |
Cenário A: um documento, muitas perguntas
Contrato, política interna, artigo comprido, relatório
Se você mandar uma chamada por pergunta, o documento inteiro viaja de novo em cada requisição
Agrupando, ele vai uma vez só
E como o Jev custa US$ 0,042 por 1 milhão de tokens de entrada, com tokens de saída não cobrados, o seu custo é basicamente o quanto de state você empurra
Mandar o mesmo documento 13 vezes é pagar 13 vezes por ele, não tem mágica
Só respeite os dois orçamentos: 64k pro state mais todas as questões, e 32k pro state mais a questão mais longa
Cenário B: milhares de itens pequenos
Comentários, tickets, mensagens, linhas de catálogo
Aqui cada item é um state diferente, então agrupar não se aplica: o caminho é o pool assíncrono com semáforo
O que você otimiza é latência total e estabilidade, calibrando o limite de concorrência contra tokens por segundo E requisições por minuto
Começa conservador, mede a taxa de 429, sobe aos poucos
Subir o limite até quebrar não é benchmark, é só quebrar
Cenário C: reprocessamento de backlog
Esse é o mais perigoso, porque você vai rodar o mesmo item de novo por definição
Fila, checkpoint por item e idempotência na escrita
Se o seu processo morre no item 2.847, ele precisa retomar dali sem reaplicar efeito nos 2.846 anteriores
Métrica que importa aqui não é velocidade, é taxa de erro e retrabalho
Tome cuidado com uma coisa: se você fixou jev-1.13.0 no lote original e reprocessa com jev-latest, você pode estar comparando resultado de modelos diferentes sem perceber
Conclusão
A regra que resume o post inteiro é uma ordem de decisões, não um truque
Agrupar antes de paralelizar: questões do mesmo state cabem numa chamada só, e o ganho de custo e latência é grande demais pra ignorar
Limitar antes de acelerar: semáforo no pool, sempre, com os dois limites da API na cabeça
Isolar antes de escalar: lote pesado sai do request do usuário e vai pra fila, com efeito colateral idempotente do seu lado
Próximo passo prático? Mede o que você tem hoje: custo e latência do lote atual, do jeito que ele roda agora
Depois testa o agrupamento por state e compara
E só então mexe no tamanho do pool, checando contra tokens por segundo e requisições por minuto ao mesmo tempo
Faça o teste e se liga nos números antes e depois, porque é aí que a diferença aparece =)
Até o próximo post!
Perguntas frequentes
Quantas requisições por minuto o Jev aguenta antes de retornar 429?
O limite publicado por conta é de 1.200 requisições por minuto e 250.000 tokens por segundo. Passar de qualquer um dos dois retorna HTTP 429 Too Many Requests. Vale lembrar que esses limites são ajustados dinamicamente e podem mudar sem aviso.
Vale a pena disparar uma chamada por questão pra paralelizar mais rápido?
Não, isso é o erro mais comum. Uma chamada com N questões dá exatamente as mesmas respostas que N chamadas com uma questão cada, só que mais lenta e mais cara quando o state é grande. No cookbook da TypeSafe, agrupar 13 questões numa chamada só ficou 12,2x mais barato e 10,0x mais rápido do que separar.
Como lidar com 429 sem travar a aplicação inteira?
A orientação oficial é repetir a requisição com backoff exponencial em vez de tentar de novo na hora. Os SDKs oficiais já fazem esse retry automaticamente e respeitam o header retry-after quando ele vem na resposta. Se quiser controlar isso na mão, dá pra passar uma RetryPolicy no SDK Python, inclusive desligando o retry com RetryPolicy(max_retries=0).
Preciso pagar por token de saída ao rodar muitas questões em paralelo?
Não, o Jev cobra só pelos tokens de entrada, US$ 0,042 por 1 milhão de tokens. Tokens de saída não são cobrados, o que ajuda bastante quando você tem muitas questões tipadas voltando resposta curta.
Dá pra estourar o limite de tokens mesmo rodando poucas questões?
Sim, existem dois orçamentos diferentes pra ficar de olho. O de 64k cobre o state mais todas as questões somadas, e o de 32k vale pro state mais a única questão mais longa daquele grupo. Um state grande com poucas questões enormes estoura o segundo limite mesmo sem chegar perto do primeiro.
O que muda entre jev-latest e jev-1.13.0 num lote grande?
jev-latest sempre aponta pro modelo mais recente da família Jev, enquanto jev-1.13.0 é uma versão fixada. Em lote, fixar a versão evita que o resultado mude entre execuções diferentes do mesmo pipeline.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
Blog | Mais populares

Como montar um workflow de automação combinando decisões do Jev em código
Aprenda a montar um workflow com Jev: decisões tipadas encadeadas em código, alta confiança agindo sozinha e casos incertos escalando para revisão.

Jev decide, LLM escreve: como dividir os papéis dentro de um agente de IA
Jev é o modelo que decide, não escreve: entenda como dividir papéis entre Jev e LLM dentro de um agente de IA e quando usar cada um.

Para quem o Jev serve (e para quem não serve)?
Jev serve pra roteamento, scoring e guardrails em IA, não pra texto ou código. Veja pra quem o Jev serve e quando evitar.
