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

diagrama de timeout e fallback no Jev em fluxo síncrono de decisão
Resposta rápida

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 ID typesafe-ai/jev e no Workers AI da Cloudflare como typesafe/jev
  • Uma chave válida, enviada em Authorization: Bearer
  • O endpoint: POST https://api.typesafe.ai/v1/systemone, com Content-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) e model (ex: jev-latest ou um ID versionado como jev-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
Pré-inscrição Curso Jev

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.




Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 45min

Blog | Mais populares