Timeout e fallback no Jev: o que fazer quando a decisão não chega?

Fallback no Jev é decidir, antes de subir o fluxo, o que acontece quando a decisão não chega. O Jev é síncrono: um POST em https://api.typesafe.ai/v1/systemone que devolve decisões tipadas com probabilidades, então ele entra no caminho crítico como qualquer dependência externa. O desenho tem quatro peças: orçamento de tempo com timeout explícito no cliente, política de retry consciente (backoff exponencial em 429 e 529, como manda a doc oficial), separação entre "falhou" e "respondeu com baixa confiança", e um desfecho padrão escrito para cada pergunta: valor seguro, regra simples determinística ou fila de reprocessamento
Fala aí, beleza? A chamada que decide o seu fluxo é uma dependência externa como qualquer outra, e com o Jev não é diferente
O Jev é o primeiro System One Model público da TypeSafe AI, anunciado em early access em 15/09/2026. Ele não gera texto livre: recebe um estado e devolve decisões tipadas com probabilidades, todas calculadas em paralelo em vez de token a token
E é exatamente aí que mora o detalhe que muita gente esquece
Por ser síncrono e ficar no caminho crítico do fluxo, ele tem o mesmo poder de derrubar sua request que um banco lento ou um gateway fora do ar. Se a decisão não chega, alguém precisa decidir no lugar dele
Esse alguém é o seu código. E é melhor que seja de propósito, não por acidente
O que você precisa antes de começar
Lista curta, sem enrolação:
- Acesso ao Jev: o acesso direto pela TypeSafe segue em early access por lista de espera desde 15/09/2026, sem data anunciada de disponibilidade geral. Fora isso, o modelo também está listado no OpenRouter como
jev-1.13, no AI Gateway da Vercel com o IDtypesafe-ai/jeve no Workers AI da Cloudflare comotypesafe/jev - Uma chave válida, enviada em
Authorization: Bearer - O endpoint:
POST https://api.typesafe.ai/v1/systemone, comContent-Type: application/json - Um payload com os campos
state(string, objeto ou array),questions(um mapa de objetos Question tipados, com as chaves que VOCÊ define) emodel(ex:jev-latestou um ID versionado comojev-1.13.0) - A decisão de negócio: qual é o desfecho seguro do seu fluxo quando não tem resposta
Esse último item não é código, é produto
E sem ele nenhum try/except do mundo fica bom, porque você vai escrever um fallback que ninguém sabe justificar quando der ruim 🙂

Domine o Jev e coloque decisões de IA dentro do seu sistema
Você vai aprender a usar o Jev, o System One Model da TypeSafe AI, pra automatizar decisões com resposta tipada e confiança medida, sem depender de chat nem de alguém revisando cada passo. Entre na lista de espera para garantir a condição de lançamento!
Como desenhar timeout e fallback em volta do Jev, passo a passo
1. Defina o orçamento de tempo do fluxo e derive o timeout
Começa pelo porquê: o número que importa não é "quanto o Jev demora", é "quanto tempo o meu fluxo tem pra responder"
Se o seu endpoint precisa devolver algo em 1,5 segundo, a decisão não pode consumir 1,4. Você fatia o orçamento e dá uma parte pra chamada, guardando o resto pro que vem depois (gravar, publicar evento, responder)
import requests
URL = "https://api.typesafe.ai/v1/systemone"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
ORCAMENTO_FLUXO = 1.5 # segundos que o seu handler tem no total
TIMEOUT_JEV = 0.8 # fatia reservada pra decisão
payload = {
"model": "jev-1.13.0",
"state": estado_do_pedido,
"questions": {
"aprovar_automatico": question_aprovar, # seu objeto Question tipado
"risco": question_risco,
},
}
r = requests.post(URL, json=payload, headers=HEADERS, timeout=TIMEOUT_JEV)
No SDK Python da TypeSafe existe um parâmetro timeout no cliente: quando você fornece um http_client, ele herda o http_client.timeout, caso contrário usa o padrão do SDK
Ou seja: sem http_client e sem valor explícito, você fica na mão do padrão do SDK. Define explícito e acabou a dúvida
O erro comum deste passo: deixar o timeout implícito e descobrir o valor real no primeiro pico de tráfego, com a request do usuário pendurada
2. Escolha a política de retry conscientemente
Os client SDKs oficiais da TypeSafe repetem a chamada com backoff por padrão e honram o header retry-after quando a resposta traz esse header
Isso é ótimo em job de background e é uma cilada em request síncrona apertada, porque cada tentativa come o seu orçamento
A doc traz RetryPolicy(max_retries=0) pra desativar os retries. Regra prática que eu uso pra pensar: orçamento apertado, zero retry e fallback rápido; orçamento folgado, backoff exponencial
E a orientação oficial pra 429 Too Many Requests e 529 Overloaded é justamente essa: refazer a requisição com backoff exponencial, nunca com retry imediato
import time
class JevIndisponivel(Exception):
pass
# padrão aqui é zero retry, porque o orçamento do nosso fluxo é apertado
# em job de background, sobe tentativas e deixa o backoff trabalhar
def chamar_jev(payload, timeout, tentativas=1, espera_inicial=0.2):
espera = espera_inicial
for tentativa in range(tentativas):
try:
r = requests.post(URL, json=payload, headers=HEADERS, timeout=timeout)
except requests.Timeout:
raise JevIndisponivel("timeout do cliente")
if r.status_code in (429, 529):
if tentativa == tentativas - 1:
raise JevIndisponivel(f"sobrecarga: {r.status_code}")
retry_after = r.headers.get("retry-after")
time.sleep(float(retry_after) if retry_after else espera)
espera *= 2 # backoff exponencial
continue
if r.status_code >= 400:
# erros usam códigos HTTP padrão + corpo JSON descrevendo o problema
raise JevIndisponivel(r.json())
return r.json()
A lógica é a mesma que vale pra qualquer API de modelo no caminho crítico, inclusive o que já comentamos sobre retry com espera crescente quando a chamada estoura
O erro comum deste passo: empilhar retry do SDK com retry seu, sem perceber. Aí uma "tentativa" vira seis e o timeout do fluxo inteiro estoura sem ninguém entender por quê
3. Agrupe as perguntas numa chamada só
Esse passo é resiliência disfarçada de otimização
Cada pergunta é pontuada sozinha contra o state, então uma requisição com todas as perguntas devolve exatamente as mesmas respostas que N requisições de uma pergunta. A resposta de uma não depende das outras da requisição
No cookbook de perguntas paralelas da TypeSafe, com 13 perguntas sobre um mesmo documento, agrupar saiu 12,2x mais barato e 10,0x mais rápido que as chamadas individuais, sem mudança nas respostas. Numa das execuções, a latência média de ida e volta ficou em 111 ms
Menos chamadas = menos chances de estourar o orçamento de tempo e de bater no rate limit. Simples assim
O erro comum deste passo: disparar uma chamada por pergunta em paralelo achando que fica mais rápido, e colher 429 porque os limites são medidos em tokens por segundo E em requisições por minuto
4. Separe "falhou" de "respondeu com baixa confiança"
São dois mundos diferentes e merecem caminhos diferentes
Quando a chamada falha, você não tem informação nenhuma. Quando ela responde com confiança baixa, você tem MUITA informação: o modelo te disse, em número, que aquele caso é ambíguo
Respostas de Choice e Score trazem um campo confidence entre 0 e 1, calculado pela TypeSafe a partir da distribuição de probabilidades. Distribuição mais achatada significa menor confiança
from dataclasses import dataclass
@dataclass
class Resultado:
status: str # "ok" | "incerto" | "indisponivel"
valor: object
def decidir(payload, chave, limiar):
try:
data = chamar_jev(payload, timeout=TIMEOUT_JEV)
except JevIndisponivel as e:
registrar_falha(e)
return Resultado("indisponivel", None)
answer = extrair_answer(data, chave) # a answer da chave que você definiu
if answer.confidence < limiar:
return Resultado("incerto", answer)
return Resultado("ok", answer)
O erro comum deste passo: jogar os dois casos no mesmo except e perder a chance de mandar o caso ambíguo pra revisão humana, que é o desfecho mais barato de todos
5. Escreva o valor seguro por omissão
Agora a parte que é decisão de produto
Pra cada chave de questions do seu payload, responde uma pergunta: se essa decisão não chegar, qual desfecho causa o menor dano?
Não é "qual é o mais provável", é "qual dói menos". Bloquear um pedido legítimo dói; liberar um pedido fraudulento dói MUITO mais. O padrão sai disso, não do que é mais cômodo de codar
PADRAO_SEGURO = {
"aprovar_automatico": False, # na dúvida, não aprova sozinho
"prioridade": "normal", # opção mais inócua
"risco": "revisar", # cai direto pra humano
}
resultado = decidir(payload, "aprovar_automatico", limiar=0.85)
if resultado.status == "ok":
valor = resultado.valor
else:
valor = PADRAO_SEGURO["aprovar_automatico"]
O erro comum deste passo: devolver None e deixar a regra de negócio lá na frente interpretar isso como "não" por coincidência da linguagem. O padrão tem que ser explícito e ter nome, senão vira bug silencioso
6. Degrade pra uma regra simples determinística
Valor fixo resolve, mas às vezes é grosseiro demais
Se você tinha uma heurística antes de colocar o modelo no fluxo (e quase sempre tinha: aquele if feio com três condições), ela não precisa morrer. Ela vira o plano B
def regra_simples(pedido):
if pedido.valor > 500:
return False
if pedido.cliente_novo and pedido.pagamento == "boleto":
return False
return True
if resultado.status == "indisponivel":
valor = regra_simples(pedido)
marcar_como_decidido_por("regra_simples")
O marcar_como_decidido_por não é frescura: sem isso você não consegue auditar depois quantas decisões do dia saíram do caminho degradado
E olha, plano B nem sempre é lógica nova. Às vezes é voltar para o modelo anterior que já rodava redondo no seu fluxo
O erro comum deste passo: deixar a regra simples apodrecer. Ela foi escrita uma vez, nunca mais foi olhada, e no dia em que roda de verdade toma decisão com critério de dois anos atrás 😅
7. Enfileire pra reprocessar e feche o caso depois
Esse passo vale pra tudo que NÃO precisa de resposta agora
O usuário recebe o desfecho padrão na hora, e o caso vai pra uma fila do seu lado, na infra que você já usa. Depois, com calma e sem o relógio correndo, o worker refaz a chamada com timeout folgado e retry com backoff
if resultado.status in ("indisponivel", "incerto"):
fila.publicar({
"pedido_id": pedido.id,
"payload": payload,
"motivo": resultado.status,
"decidido_por": "padrao_seguro",
})
O custo joga a favor aqui: o Jev 1.13 está listado no OpenRouter a US$ 0,042 por 1 milhão de tokens de entrada e US$ 0 por 1 milhão de tokens de saída. Reprocessar um punhado de casos não é o que vai pesar na conta
O erro comum deste passo: publicar na fila e nunca reconciliar. Se o worker decide diferente do padrão que já foi aplicado, alguém tem que corrigir o estado ou, no mínimo, avisar. Fila sem fechamento é só um log caro
Qual fallback escolher para cada tipo de decisão
O Jev trabalha com três primitivas de pergunta: Noul (booleano), Choice (escolha entre opções fixas) e Score (nota sobre níveis ordenados)
Cada uma tem um jeito natural de degradar:
| Primitiva | Fallback quando a decisão não chega | Por que funciona |
|---|---|---|
| Noul (booleano) | O lado conservador do par, sempre o mesmo | Booleano não tem meio termo: você escolhe qual dos dois erros é mais barato |
| Choice (opções fixas) | A opção mais inócua do conjunto | Sempre existe uma opção que só adia o problema em vez de causar dano |
| Score (níveis ordenados) | A faixa que dispara revisão humana | Nota é feita pra ter faixa de corte, então a faixa do meio já é o caminho do humano |
E tem um detalhe que muda o desenho: o limiar de confiança não é um número universal
A doc traz um padrão de roteamento por confiança em que confiança alta age sozinha, confiança baixa vai pra humano, e o limiar depende do risco da ação. Marcar um e-mail como promoção e cancelar uma assinatura não podem compartilhar o mesmo corte
Outra coisa importante: transformar uma faixa intermediária de probabilidade num desfecho explícito de revisão humana é lógica de aplicação, feita no seu código sobre a probabilidade que já veio
Nenhuma pergunta nova, nenhuma segunda chamada
Ou seja, escalonar por incerteza não custa latência nem token extra. É if 🙂
Falhas comuns e o que fazer em cada uma
429 Too Many Requests
Sintoma: a chamada volta rápido, mas com erro, geralmente em pico de tráfego
Causa: os rate limits são medidos em duas dimensões, tokens por segundo e requisições por minuto, e passar de qualquer um dos dois já retorna 429. Esses limites são ajustados dinamicamente e podem mudar sem aviso, então não dá pra calibrar seu código em cima de um valor fixo
Solução imediata: refazer com backoff exponencial, respeitando retry-after quando a resposta trouxer o header. Se o orçamento do fluxo não permite esperar, cai pro padrão seguro e manda pra fila
Como prevenir: agrupar as perguntas numa chamada só (menos requisições por minuto e menos token desperdiçado repetindo o state) e, quando o volume justificar, avaliar limites maiores em planos custom/enterprise
529 Overloaded
Sintoma: erro sem relação com o seu volume, aparece de repente
Causa: sobrecarga do lado do serviço
Solução imediata: mesma orientação oficial do 429, backoff exponencial em vez de retry imediato
Como prevenir: esse aqui você não previne, você absorve. É o caso clássico pra ter o desfecho padrão pronto e o caminho de fila funcionando de verdade, não só escrito no código
Timeout do cliente
Sintoma: nada volta e o seu handler pendura
Causa: timeout não definido, ou definido acima do orçamento do fluxo, ou retry empilhado consumindo o tempo que sobrava
Solução imediata: tratar o timeout como indisponivel e aplicar o padrão seguro, igual no passo 4
Como prevenir: timeout explícito no cliente e número de tentativas coerente com o orçamento. Uma conta que ajuda: tentativas x timeout + esperas do backoff tem que caber no orçamento, senão o cálculo é fantasia
Estouro de orçamento de contexto
Sintoma: erro com corpo JSON descrevendo o problema, normalmente quando o state cresceu (histórico completo, documento inteiro colado)
Causa: o Jev 1.13 tem orçamentos separados: 32k para o state mais a pergunta mais longa, e 64k para o state mais todas as perguntas somadas. No OpenRouter, a janela declarada do jev-1.13 é de 32.000 tokens
Solução imediata: enxugar o state pro que a decisão realmente precisa. Na prática, quase todo state inchado tem campo que ninguém usa pra decidir nada
Como prevenir: fixar um ID de modelo versionado como jev-1.13.0 em vez de jev-latest em fluxo crítico, e conferir o que a sua conta pode usar com GET /v1/models, que devolve os nomes disponíveis com descrição e data de lançamento
Tome cuidado com o latest aqui: é ótimo pra experimentar e é péssimo quando a sua lógica de fallback foi calibrada em cima do comportamento de uma versão específica
Vídeo: IA lendo seu código e desenhando a arquitetura
Se você tá começando agora a montar fluxos com IA no meio da arquitetura, esse vídeo do canal é um bom ponto de partida
Ele mostra a skill Archify fazendo a IA ler o seu código e desenhar a arquitetura sozinha, o que ajuda demais a enxergar onde ficam as dependências externas do seu sistema antes de sair colocando plano B em cada uma
Conclusão
Recapitulando o que realmente importa aqui
Timeout e fallback no Jev não começam no try/except, começam numa pergunta de produto: o que acontece com o cliente quando a decisão não chega? Enquanto essa resposta não existir escrita em algum lugar, o seu código vai improvisar, e improviso em produção tem nome feio
O desenho é esse: orçamento de tempo definido, timeout explícito, retry escolhido de propósito (com backoff em 429 e 529), perguntas agrupadas numa chamada, "falhou" separado de "respondeu com baixa confiança", valor seguro por omissão, regra simples pra degradar e fila pra fechar o caso depois
Próximo passo prático, e é bem chatinho mas paga: abre o seu payload, olha cada chave de questions e escreve do lado o desfecho padrão daquela pergunta
Depois derruba a chamada de propósito no ambiente de testes e olha o que acontece com o fluxo inteiro
É o único jeito de saber se o seu plano B existe ou se ele só mora no README…
até o próximo post! 😀
Perguntas frequentes
O que acontece se o Jev não responder dentro do timeout que eu defini no meu código?
A responsabilidade é sua: o timeout é definido no cliente, então estourar esse prazo é uma exceção que o seu fluxo precisa capturar e tratar. Não existe um comportamento automático do Jev pra isso, por isso o fallback tem que estar explícito no código, com um desfecho de negócio já decidido pra quando a resposta não chega.
Preciso desativar o retry automático do SDK da TypeSafe em toda chamada síncrona?
Não necessariamente, mas em fluxo com orçamento de tempo apertado faz sentido considerar, porque cada tentativa consome parte desse orçamento. O SDK Python permite isso com RetryPolicy(max_retries=0), enquanto em job de background sem pressa de latência o backoff padrão do SDK costuma ser mais seguro.
Dá pra usar o campo confidence do Jev pra decidir o fallback automaticamente?
Sim, e é exatamente esse o padrão descrito como confidence-gated routing na documentação da TypeSafe: confiança alta deixa a decisão agir sozinha, confiança baixa manda pra revisão humana. O limiar não é um número fixo, ele depende do risco da ação, e esse escalonamento é lógica no seu código, sem precisar de uma segunda chamada à API.
Qual erro o Jev retorna quando eu estouro o limite de requisições?
A API devolve 429 Too Many Requests quando você passa dos limites de tokens por segundo ou de requisições por minuto, os dois medidos separadamente. A orientação oficial pra esse caso, assim como pra 529 Overloaded, é repetir a chamada com backoff exponencial em vez de tentar de novo na hora.
Enviar várias perguntas numa chamada só ao Jev aumenta o risco de estourar o timeout?
Pelo contrário, agrupar reduz o número de idas e vindas pela rede, e cada pergunta continua sendo pontuada de forma independente contra o mesmo state. No cookbook de perguntas paralelas da TypeSafe, agrupar 13 perguntas saiu 12,2x mais barato e 10,0x mais rápido do que fazer 13 chamadas separadas, sem mudar nenhuma resposta.
Existe um valor de timeout padrão que eu preciso saber de cor pra usar o Jev?
A documentação não publica esse número, então tratar ele como conhecido é chute. O parâmetro timeout do cliente herda o http_client.timeout quando você fornece um, caso contrário usa o padrão do SDK, mas o caminho seguro é você mesmo definir o valor explícito de acordo com o orçamento do seu fluxo.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
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.

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.

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.
