Jev falhou ou demorou demais: como montar o plano B da sua camada de decisão?

Diagrama de fallback do Jev mostrando rota padrão, fila de reprocessamento e regra de emergência
Resposta rápida

Fallback do Jev é o que sua aplicação faz quando a resposta tipada não chega: rota padrão segura, fila para decidir depois e regra de emergência. A separação básica: 401 (chave) e 422 (corpo) são erro seu e não pedem retry, enquanto 429 e 529 pedem backoff exponencial, que os SDKs oficiais já fazem respeitando o header retry-after. Defina o orçamento de tempo, enquadre a pergunta para que escalar seja o caso verdadeiro e registre request_id (x-typesafe-request-id), model resolvido, answers com confidence e usage para reprocessar depois

Uma camada de decisão sem plano B não é uma camada, é um ponto único de falha

Fala aí, beleza? O Jev é o primeiro modelo do que a TypeSafe AI chama de System One Model: tu manda um estado e perguntas tipadas, ele devolve valores tipados com probabilidade calibrada, sem gerar texto

Isso é ótimo porque a saída entra direto no seu if

E é justamente aí que mora o perigo: quando uma resposta tipada vira o eixo de uma decisão de negócio, qualquer 401, 422, 429, 529 ou uma simples demora acima do aceitável vira uma pergunta que alguém vai ter que responder às pressas em produção

O que a sua aplicação faz quando a decisão não chega?

É isso que a gente vai desenhar aqui: rota padrão segura, fila pra decidir depois, regra simples de emergência e o que registrar pra reprocessar

O que você precisa antes de montar o plano B

A parte chata primeiro, que é rápida

O acesso ao Jev está aberto, a lista de espera foi encerrada, então qualquer pessoa entra em console.typesafe.ai e cria uma chave

Depois:

  • pip install typesafe-sdk
  • a variável de ambiente TYPESAFE_API_KEY no ambiente onde o serviço roda (o TypeSafeClient lê ela sozinho)
  • decidir entre TypeSafeClient e AsyncTypeSafeClient: a interface é a mesma, o assíncrono existe pra serviço assíncrono, sem desculpa pra bloquear o loop
  • e o modelo padrão já é o jev-latest, então tu não precisa passar nada pra começar

Se a sua stack não é Python, o endpoint é POST https://api.typesafe.ai/v1/systemone com autenticação Bearer no header Authorization

curl https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @payload.json
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Curso Recomendado

Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos

Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev

Tem um último pré-requisito que não é técnico e é o mais importante de todos: saber qual decisão de negócio essa chamada alimenta

Porque a rota padrão segura depende disso

Sem resposta do modelo, o pedido é aprovado ou barrado? O conteúdo fica publicado ou vai pra revisão? Não dá pra responder isso no meio de um incidente 🙂

Como montar o plano B da camada de decisão, passo a passo

  1. Separe erro seu de erro do serviço

A API retorna status HTTP padrão com corpo JSON descrevendo o problema

Os documentados incluem 401 (chave ausente ou inválida), 422 (falha de validação do corpo da requisição), 429 (Too Many Requests) e 529 (Overloaded)

A divisão mental é essa:

Status De quem é o problema Retry resolve? O que fazer
401 seu não corrigir chave/env e derrubar o deploy quebrado
422 seu não validar o corpo antes de enviar
429 limite sim, com backoff backoff e respeitar retry-after
529 serviço sim, com backoff backoff e circuit breaker

O erro comum deste passo: colocar tudo num except só e mandar repetir

401 repetido três vezes continua sendo 401, tu só gastou o orçamento de tempo do usuário pra chegar no mesmo lugar

  1. Ajuste o RetryPolicy conscientemente

A doc orienta backoff exponencial em 429 e 529 em vez de tentar de novo imediatamente, e os SDKs oficiais já fazem isso por padrão, respeitando o header retry-after quando ele vem

Os defaults do RetryPolicy do SDK Python (quando retry=None) são: max_retries=2, backoff_initial=0.5, backoff_max=5.0, backoff_jitter=0.25, http_statuses com 408, 429 e a faixa 500 a 599, e respect_retry_after=True

Repara numa coisa massa: 401 e 422 não estão nessa lista de status

O SDK já não repete o que não adianta repetir

from typesafe_sdk import TypeSafeClient

client = TypeSafeClient()

# override por chamada: backoff_max de 0.2s e timeout de 1.0s seguram o pior caso
resposta = client.system_one(
    state=estado,
    questions=perguntas,
    retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0),
)

E quando o caminho simplesmente não pode esperar, tu desliga:

resposta = client.system_one(
    state=estado,
    questions=perguntas,
    retry=RetryPolicy(max_retries=0),
)

A política é configurável no cliente e por chamada, então dá pra ter um default generoso no cliente e um override apertado na rota crítica

O erro comum deste passo: usar os mesmos retries em todo lugar

Job noturno e checkout não têm a mesma paciência, óbvio

  1. Defina o orçamento de tempo do caminho, não só o timeout da chamada

Backoff mais retry SOMA latência

Se tu permite 2 retries com espera crescente, o pior caso não é o timeout de uma chamada, é a soma de todas elas mais as esperas

Então a pergunta certa não é "quanto tempo o Jev demora", é "quanto tempo esse caminho de decisão pode gastar no total antes de eu seguir sem ele"

Escreve esse número em algum lugar, de preferência em constante com nome

Quem já apanhou de lentidão do GPT-6 Astra em produção sabe que orçamento de tempo indefinido vira timeout do usuário, não do serviço

E se tu quiser aprofundar só a parte de timeout e fallback no Jev, tem material dedicado pra isso

O erro comum deste passo: definir timeout generoso "por segurança"

Timeout generoso não é segurança, é enfileirar usuário na sua própria aplicação

  1. Escreva a rota padrão segura ANTES da chamada

Rota padrão segura é o que a aplicação decide sozinha quando não tem resposta

Aqui tem um truque de modelagem que o cookbook de cascata da TypeSafe já entrega: enquadre a pergunta de escalonamento pra que o caso "escalar" seja o caso verdadeiro

Por quê? Porque assim a ausência de resposta cai naturalmente pro lado conservador, sem você ter que inventar uma segunda lógica invertida no except

DECISAO_PADRAO = "escalar"  # conservador de propósito

def decidir(estado, perguntas):
    try:
        resposta = client.system_one(state=estado, questions=perguntas)
    except Exception:
        registrar_falha(estado, perguntas)
        return DECISAO_PADRAO
    return aplicar_regras(resposta)

O padrão do cookbook é esse: o modelo propõe, o CÓDIGO decide

O erro comum deste passo: deixar a rota padrão implícita num return None que vira False lá na frente e libera tudo caladinho 😛

  1. Reduza a superfície de falha: N perguntas, uma chamada

Uma chamada system_one responde N perguntas num passe paralelo, e cada pergunta é pontuada isoladamente contra o estado

Ou seja: a resposta de uma não depende do que mais está na requisição

No cookbook de perguntas paralelas, juntar tudo numa chamada única saiu 12,2x mais barato e 10,0x mais rápido que uma requisição por pergunta, sem mudança nas respostas

Pra plano B isso importa por um motivo extra: cinco chamadas são cinco lugares onde a coisa pode falhar pela metade

Uma chamada falha inteira ou responde inteira, e isso é MUITO mais fácil de tratar

Só fica de olho no orçamento de contexto: 64k cobre o estado mais todas as perguntas somadas, e 32k se aplica ao estado mais a pergunta individual mais longa

O erro comum deste passo: jogar o estado inteiro do usuário no payload "porque sim"

A acurácia cai conforme o estado cresce com conteúdo irrelevante pra decisão, isso está documentado

  1. Regra simples de emergência: circuit breaker

Depois de X falhas seguidas, para de chamar

Manda todo mundo pra rota padrão por um intervalo e tenta de novo depois

import time

falhas = 0
aberto_ate = 0.0
LIMITE = 5
PAUSA = 30  # segundos

def decidir_com_breaker(estado, perguntas):
    global falhas, aberto_ate
    if time.time() < aberto_ate:
        return DECISAO_PADRAO  # circuito aberto: rota padrão explícita
    try:
        r = client.system_one(state=estado, questions=perguntas)
    except Exception:
        falhas += 1
        if falhas >= LIMITE:
            aberto_ate = time.time() + PAUSA
        registrar_falha(estado, perguntas)
        return DECISAO_PADRAO
    falhas = 0
    return aplicar_regras(r)

Repara que aqui NÃO tem return None: o breaker devolve a mesma DECISAO_PADRAO do passo 4, tanto com o circuito aberto quanto na falha

É o mesmo caminho conservador, só que agora sem nem gastar a chamada

Isso protege os dois lados: tu para de queimar tempo do usuário e para de martelar um serviço que já está sobrecarregado

A checagem humana que acompanha isso é a página pública status.typesafe.ai

O erro comum deste passo: circuit breaker que abre e nunca fecha porque ninguém escreveu o caminho de volta

  1. Fila pra decidir depois

Nem toda decisão precisa ser agora

Quando o caminho tolera espera, enfileira o estado e as perguntas exatamente como iriam na requisição, e reprocessa com o MESMO payload

item = {
    "state": estado,
    "questions": perguntas,
    "tentativas": 0,
}
fila.publicar(item)

Como cada pergunta é pontuada isoladamente contra o estado, reprocessar o mesmo payload é uma operação bem previsível

O erro comum deste passo: enfileirar só o ID do registro e reconstruir o estado na hora do reprocessamento

Aí o estado mudou, a decisão sai diferente e ninguém entende por quê

  1. Registre o que permite reprocessar e auditar

Sem isso, plano B vira adivinhação

O mínimo:

registro = {
    "request_id": request_id,        # header x-typesafe-request-id
    "model": model,                  # versão resolvida, ex: jev-1.13.0
    "question_ids": list(perguntas.keys()),
    "answers": answers,              # Choice e Score trazem confidence de 0 a 1
    "input_tokens": input_tokens,
    "output_tokens": output_tokens,
    "state": estado,
}

Detalhe importante: Choice e Score carregam uma confiança entre 0 e 1 derivada da distribuição da resposta, e o Noul NÃO tem campo de confiança separado, porque a distribuição dele tem só dois desfechos (yes e no) e o próprio valor já descreve ela por completo

Então não fica procurando confidence no Noul, não é bug 😀

O erro comum deste passo: logar só a decisão final

Quando alguém perguntar "por que isso foi aprovado dia 12", tu vai querer o model resolvido e o request_id, não um booleano solitário

Três caminhos de decisão e o plano B que cada um pede

Não existe um plano B universal

Existem três classes de caminho, e cada uma pede uma configuração diferente:

Caminho Timeout e retries Rota padrão Fila
Síncrono no pedido do usuário curto, retries mínimos ou zero imediata e conservadora opcional, só pra auditoria
Batch ou job noturno generoso, backoff à vontade não precisa sim, com reprocessamento
Moderação e risco curto escalar pra revisão humana sim, pra fila de revisão

(a) Decisão síncrona no pedido do usuário

Aqui o inimigo é a soma de esperas

RetryPolicy(max_retries=0) ou um override bem apertado, e a rota padrão dispara na hora

(b) Decisão em batch ou job noturno

Tempo aqui é barato

Retries generosos, backoff tranquilo, e o que falhar vai pra fila e é reprocessado com o mesmo payload

Nesse caminho a rota padrão costuma nem existir: é melhor não decidir do que decidir errado sem revisão

(c) Moderação e risco, onde errar barato é bloquear

Rota padrão = escalar pra revisão humana

E tem um detalhe de agregação que o cookbook de cascata deixa explícito: agregue flags por campo com max, pra que uma bandeira vermelha confiante escale em vez de ser diluída na média de dez campos tranquilos

risco = max(flags_por_campo)  # bandeira vermelha confiante não se dilui

E olha, vale lembrar do óbvio que a gente esquece: baixa confiança JÁ deveria descer pra algo mais lento e mais capaz (modelo de raciocínio maior ou pessoa), mesmo quando a API respondeu normalmente

Plano B não é só pra erro de rede, é pra resposta fraca também

Falhas e lentidões mais comuns: sintoma, causa e o que fazer

401: chave ausente ou inválida

Sintoma: todas as chamadas falhando desde o deploy

Causa: TYPESAFE_API_KEY não chegou no ambiente

O que fazer: não é hora de retry, é hora de olhar o deploy

Como prevenir: checagem de env no start do serviço, falhando cedo e alto

422: falha de validação do corpo

Sintoma: uma rota específica quebrou, o resto está de pé

Causa: contrato de pergunta quebrado, payload fora do formato esperado

O que fazer: validar antes de enviar, e tratar como bug, não como instabilidade

Como prevenir: teste que monta o payload real das perguntas em CI

429: Too Many Requests

Sintoma: funciona no pico baixo, quebra no pico alto

Causa: estourou o limite de tokens por segundo ou de requisições por minuto, e esses limites estão sendo ajustados dinamicamente por causa do volume de demanda, podendo mudar sem aviso

O que fazer: backoff exponencial, respeitar retry-after, e agrupar perguntas numa chamada só em vez de disparar N

529: Overloaded

Sintoma: erro intermitente que não tem a ver com o seu código

Causa: lado do serviço

O que fazer: backoff, circuit breaker e uma olhada em status.typesafe.ai antes de sair caçando fantasma no seu log

"Respondeu rápido, mas respondeu errado"

Esse é o mais traiçoeiro, porque não dispara alarme nenhum

A TypeSafe mantém uma página de jaggedness datada pro jev-1.13, com modos de falha conhecidos e um workaround recomendado pra cada um, revisada em 17 de setembro de 2026

Entre eles:

  • ele responde a pergunta que você ESCREVEU, não a que você quis dizer: negações e condições implícitas são lidas ao pé da letra
  • não é calculadora e não conta de forma confiável: aritmética pertence ao código
  • lê datas como texto, não como quantidades ordenadas
  • a acurácia cai conforme o estado cresce com conteúdo irrelevante pra decisão

Plano B também é isso: saber quando uma resposta bem-sucedida não deveria decidir sozinha

Próximo passo: escreva a rota padrão antes de escrever a chamada

A ordem usual é escrever a integração e depois, um dia, tratar o erro

Inverte

Primeiro define o que a aplicação faz sem resposta, depois pluga o Jev nesse esqueleto

O plano de ação prático pra esta semana:

  1. mapear cada chamada em uma das três classes de caminho (síncrona, batch, risco)
  2. definir o orçamento de tempo e a rota padrão de cada uma
  3. passar a registrar request_id e model resolvido desde já, mesmo que ninguém vá olhar hoje

E um argumento a favor de retry e reprocessamento sem culpa: o Jev cobra por token de entrada a US$ 0,042 por milhão, com tokens de saída a US$ 0,00

Uma decisão reprocessada não vai quebrar o orçamento, uma decisão errada silenciosa pode quebrar outras coisas…

Fecha o loop acompanhando a página de jaggedness e o status do serviço, porque camada de decisão sem plano B envelhece mal

Até o próximo post! =)

Perguntas frequentes

O que fazer quando o Jev retorna 529 Overloaded em produção?

529 é problema do serviço, não seu, e a própria doc orienta backoff exponencial em vez de repetir na hora. O SDK Python já faz isso por padrão (max_retries=2, backoff_initial=0.5, backoff_max=5.0) e respeita o header retry-after quando ele vem. Se o caminho não pode esperar, esse é o cenário certo pra cair na rota padrão segura em vez de insistir.

Qual a diferença entre retry e fallback no Jev?

Retry é repetir a mesma chamada esperando que ela funcione da próxima vez, útil em 429 e 529. Fallback é o que a aplicação decide sozinha quando os retries se esgotam ou quando o erro não tem retry que resolva, como 401 e 422. Um plano de Jev fallback maduro usa os dois, retry pra falha temporária e fallback pra quando a resposta simplesmente não vem.

Dá para desligar os retries automáticos do SDK do Jev?

Sim, passando RetryPolicy(max_retries=0) no cliente ou por chamada. Isso é útil quando o orçamento de tempo do caminho é curto e você prefere cair direto na rota padrão segura em vez de esperar o backoff rodar. A política aceita outros parâmetros também, como backoff_max e timeout, pra ajustar caso a caso.

Por que repetir uma chamada que deu 401 ou 422 não resolve nada?

401 é chave ausente ou inválida e 422 é falha de validação do corpo da requisição, os dois são erro seu, não do serviço. Repetir a mesma chamada errada três vezes devolve 401 ou 422 três vezes, só consumindo o orçamento de tempo do usuário. O SDK já reflete isso: esses dois status nem entram na lista padrão de http_statuses que disparam retry.

Como saber se um problema é do Jev ou da minha aplicação?

A TypeSafe AI mantém uma página pública de status em status.typesafe.ai pra acompanhar incidentes do serviço. Se o status estiver normal e você continuar recebendo 401 ou 422, o problema é local, chave ou payload. Se aparecer 429 ou 529 em volume, alinhe com o status antes de sair mexendo no seu código.

O custo de retries no Jev pesa no orçamento da aplicação?

O Jev cobra US$ 0,042 por milhão de tokens de entrada e US$ 0,00 na saída, então cada retry que efetivamente chega no modelo reprocessa tokens de entrada de novo. É outro motivo pra não deixar max_retries alto por padrão em toda rota: o custo de uma chamada malsucedida se multiplica junto com o backoff.



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