Como migrar uma árvore de if/else para decisões do Jev sem perder controle?

Migrar if/else para o Jev funciona quando você separa o que é regra determinística de verdade do que é julgamento humano disfarçado de regra. O Jev é um modelo de decisão tipada da TypeSafe AI: recebe um state e perguntas Choice, Score ou Noul, e devolve a decisão com probabilidades e um valor de confidence de 0 a 1, sem gerar prosa. O roteiro é: etiquetar cada nó, virar os nós de julgamento em perguntas estreitas, recompor a decisão em código, rodar o caminho novo em paralelo com o antigo, definir threshold por ação e só então desligar o legado por partes.
Fala aí, beleza? Todo projeto que já rodou uns anos tem aquele arquivo que ninguém quer abrir
Aquela função de 400 linhas, cheia de if, elif, flag booleana com nome de sigla e um comentário de 2019 dizendo "não mexer, quebra o faturamento"
O problema é que essa árvore é feita de duas coisas MUITO diferentes misturadas no mesmo bloco. Parte dela é regra determinística de verdade: limite de valor, janela de data, ordem de execução. Isso é código e vai continuar sendo código. A outra parte é julgamento humano que alguém tentou espremer em condicional: "se a mensagem parece uma reclamação grave", "se o cliente parece elegível", "se o motivo do contato é cobrança"
Essa segunda parte é onde o if/else apodrece, porque julgamento não cabe em str.contains()
O Jev é o primeiro modelo da classe que a TypeSafe AI chama de System One: ele recebe um estado e perguntas tipadas, e devolve decisões tipadas com probabilidades calibradas, sem gerar prosa. Não é chatbot, não escreve texto livre, responde a pergunta que você fez em um formato que o seu código consegue consumir
O plano aqui é um roteiro de migração por partes, com os dois caminhos rodando em paralelo antes de você desligar qualquer coisa. Bora?
O que você precisa antes de começar a migração
Antes do código, o acesso. E aqui tem um detalhe importante de setembro de 2026: o cadastro direto no console da TypeSafe abriu pra todo mundo em 20/09/2026 e foi pausado em 22/09/2026 por excesso de demanda
Contas criadas antes disso continuam funcionando normalmente, e a empresa diz estar trabalhando pra reabrir
Se você não pegou a janela, o Jev segue acessível por gateways:
- OpenRouter, que lista
typesafe/jev-1.13e o aliasjev-latest - Vercel AI Gateway, que expõe o modelo pelo id
typesafe-ai/jev
Do lado do SDK, escolhe o seu veneno:
# Python 3.10 ou superior
pip install typesafe-sdk
# ou: uv add typesafe-sdk
# Node.js 20 ou mais novo
npm install @typesafe-ai/sdk
Os dois leem a variável TYPESAFE_API_KEY do ambiente. O SDK Python expõe TypeSafeClient, AsyncTypeSafeClient, Choice, Noul e Score, e usa jev-latest por padrão. O de JavaScript chama systemOne com o state e as perguntas tipadas, e vem com ESM, CommonJS e declarações TypeScript
Se preferir bater direto no HTTP, existe um endpoint só:
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "state": "...", "model": "jev-latest", "questions": { } }'
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
E do lado do código legado?
Duas coisas que a galera esquece e depois sofre:
Log das decisões atuais. Você precisa saber o que o if/else está decidindo hoje, caso a caso, senão não tem como comparar nada depois
Um conjunto de casos rotulados. Nem que seja umas centenas de casos com a resposta certa escrita por uma pessoa. É com isso que você vai calibrar a confiança lá na frente, e não tem atalho pra isso
Dimensionando o custo de rodar os dois em paralelo
O orçamento de contexto do jev-1.13 é de 64k no total, cobrindo o state mais todas as perguntas somadas, e 32k pro state mais a pergunta individual mais longa
O preço listado é de US$ 0,042 por 1 milhão de tokens de entrada, com tokens de saída gratuitos
A TypeSafe informa tempo de resposta ponta a ponta de 70ms a 500ms, e a página do jev-1.13 no OpenRouter mostra latência P50 de 0,26 segundo
Ou seja: rodar o caminho novo em paralelo com o antigo por um tempo não é aquele terror de orçamento que costuma travar esse tipo de migração
Roteiro passo a passo para migrar a árvore sem perder controle
A documentação de arquitetura da TypeSafe é bem direta sobre o princípio que sustenta tudo aqui: mantenha fluxo de controle, regras determinísticas e efeitos colaterais no código, e coloque o modelo só onde o sistema precisa de senso comum programável ou de interpretar dado não estruturado
Guarda essa frase, porque cada passo abaixo é ela aplicada
- Mapeie a árvore e etiquete cada nó como determinístico ou julgamento. Abre o arquivo, lista cada condição e escreve ao lado: "isso é regra" ou "isso é alguém tentando adivinhar intenção". Comparação de valor, janela de data, ordem de execução, efeito colateral: tudo regra, fica onde está.
if "urgente" in texto.lower(),if motivo in LISTA_DE_PALAVRAS_CHAVE: julgamento disfarçado, candidato à migração
O erro comum deste passo: querer migrar comparação numérica e de data por empolgação. A própria página de jaggedness do jev-1.13 avisa que o modelo é fraco em precisão numérica e lê datas como texto, não como quantidades ordenadas, o que torna comparação, distância e janelas de data pouco confiáveis. A recomendação oficial é fazer aritmética de data no código. Respeita isso
- Monte o state com a informação relevante. O campo
stateé obrigatório e aceita string, objeto ou array, seja texto puro ou dado estruturado tipo log de conversa ou estado da aplicação
state = {
"mensagem_cliente": ticket.texto,
"canal": ticket.canal,
"historico_recente": [m.texto for m in ticket.ultimas_mensagens],
"plano_contratado": conta.plano,
}
O erro comum deste passo: despejar o objeto inteiro do domínio no state "por garantia". Além de gastar contexto à toa, você entope a pergunta de ruído. A orientação da doc é dar a cada pergunta só o contexto que ela precisa
- Traduza cada nó de julgamento em uma pergunta estreita e tipada. São três tipos de pergunta na API, descritos na referência de primitives:
- Choice: escolhe uma opção de um conjunto definido, aceita até 255 opções e devolve a opção escolhida, a probabilidade de cada opção e um valor de confiança
- Score: posiciona o estado numa escala ordenada de 2 a 10 níveis descritos em palavras, e devolve um valor ponderado por probabilidade (que pode cair entre níveis), a distribuição por nível e a confiança
- Noul: pergunta sim/não que devolve a probabilidade de sim
A doc manda quebrar julgamentos amplos em perguntas estreitas, com instruções e critérios explícitos
O erro comum deste passo: escrever uma pergunta gigante que é o if inteiro traduzido pro português. Outro clássico é negação e escopo implícito: o jev-1.13 é declaradamente literal, sofre com tarefas que exigem níveis extras de indireção e responde a pergunta que você escreveu, não a que você quis dizer. Palavras de escopo, negações e condições implícitas são lidas ao pé da letra. Se você escrever "o cliente não está insatisfeito, certo?", boa sorte 😛
- Agrupe as perguntas independentes no mesmo mapa
questions. Cada entrada do mapa é uma pergunta tipada nomeada por você, e os três tipos compartilham as propriedadestypeeinstructions, cada um acrescentando seus próprios critérios
import os, requests
payload = {
"state": state,
"model": "jev-latest", # ou "jev-1.13" pra fixar a versao
"questions": {
"motivo_contato": {
"type": "choice",
"instructions": "Classifique o motivo principal do contato considerando apenas a ultima mensagem do cliente.",
# as opcoes do Choice entram aqui, conforme a referencia de primitives
},
"gravidade": {
"type": "score",
"instructions": "Avalie a gravidade da reclamacao descrita pelo cliente.",
# os niveis ordenados do Score entram aqui
},
"pede_cancelamento": {
"type": "noul",
"instructions": "O cliente esta pedindo explicitamente o cancelamento do servico?",
},
},
}
r = requests.post(
"https://api.typesafe.ai/v1/systemone",
headers={
"Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}",
"Content-Type": "application/json",
},
json=payload,
)
resposta = r.json()
Curso RecomendadoDesenvolva sistemas mais inteligentes, rápidos e robustos com JevConhecer cursoPor que juntar tudo numa requisição só? Porque as perguntas de uma mesma requisição são avaliadas em paralelo e isoladas contra o mesmo state, e a TypeSafe afirma que acrescentar perguntas quase não muda o tempo de resposta
Na prática isso quer dizer que migrar cinco nós da árvore de uma vez sai, em tempo de resposta, bem parecido com migrar um só
E "isoladas contra o mesmo state" é literal: todas olham o mesmo estado que você montou, mas nenhuma enxerga a resposta da outra
O erro comum deste passo: encadear pergunta que depende da resposta da anterior no mesmo lote. Elas são isoladas, uma não enxerga a outra. Dependência entre decisões é coisa do seu código
- Recomponha a decisão em código. Aqui é onde você junta tudo: as respostas do modelo e as checagens determinísticas que ficaram. Respostas de Choice e Score trazem a propriedade
probabilities(a distribuição) e a propriedadeconfidence, um número de 0 a 1 derivado do formato dessa distribuição. No Noul, o próprio valor já descreve a distribuição de dois resultados
def decidir(resposta, conta, agora):
motivo = resposta["motivo_contato"]
# regra determinística continua sendo regra, no código
dentro_da_janela = conta.contratado_em <= agora - timedelta(days=30)
if not dentro_da_janela:
return "fora_de_politica"
if motivo["confidence"] < 0.6:
return "revisao_humana"
return ROTAS[motivo["value"]]
O fluxo recomendado pela documentação é exatamente esse: montar um state, fazer as perguntas independentes juntas, combinar as respostas com checagens determinísticas no código e então rotear o caso pra ação ou pra revisão
O erro comum deste passo: olhar só a opção escolhida e jogar probabilities e confidence no lixo. Esses dois campos são o motivo de você estar usando um modelo de decisão tipada em vez de um classificador cuspindo uma string
- Rode o caminho novo em paralelo com o
if/else, registrando as divergências. Importante ser honesto aqui: não existe modo shadow nativo pra isso. Você monta a comparação no seu código ou na sua ferramenta de observabilidade
decisao_legado = arvore_antiga(ticket)
decisao_nova = decidir(chamar_jev(ticket), conta, agora)
if decisao_legado != decisao_nova:
log.info("divergencia", extra={
"ticket": ticket.id,
"no_da_arvore": "motivo_contato",
"legado": decisao_legado,
"novo": decisao_nova,
"confidence": resposta["motivo_contato"]["confidence"],
})
return decisao_legado # o legado ainda manda, beleza?
Repara no return: durante o paralelo, quem responde ao usuário é o código antigo. O novo só observa
O erro comum deste passo: comparar só o resultado final. Se dois caminhos com lógicas diferentes chegam no mesmo lugar por sorte, você comemora um empate falso. Compare nó a nó, e registre a confiança junto, senão você não tem dado pra calibrar depois
- Defina o threshold por ação, não global. A documentação de confiança é explícita: o limite não é um número único, ações diferentes no mesmo sistema devem ser barradas em níveis diferentes conforme a consequência do erro
O padrão de roteamento por confiança da doc usa um exemplo bancário bem claro: consultar saldo é aceitável com confiança 0.6, enquanto aprovar uma transferência exige confiança acima de 0.85, e abaixo disso o sistema pede confirmação ao usuário
E a documentação de confiança indica três faixas de comportamento pro seu código usar:
- alta confiança: age automaticamente
- média: prossegue com cautela (pedir confirmação, marcar pra revisão ou buscar mais informação)
- baixa: não age (roteia pra humano, pede esclarecimento ou cai em outro sistema)
O erro comum deste passo: copiar 0.85 pra tudo porque parece seguro. Aí a sua rota de baixa consequência vira uma fila de revisão manual gigante e o time desiste da migração na segunda semana
- Calibre com os seus dados. A TypeSafe recomenda testar os limites de confiança plotando confiança contra acurácia nos seus próprios dados, e escalar os casos incertos pra uma pessoa ou pra um modelo de raciocínio mais caro
É aqui que aquele conjunto rotulado do começo paga a fatura. Sem ele, o seu threshold é chute com cara de número
O erro comum deste passo: calibrar em cima dos casos fáceis. Se a sua amostra só tem caso óbvio, a curva vai mentir bonito pra você
- Desligue o ramo antigo por partes, com fallback. Um ramo de cada vez, começando pelos de menor consequência, e mantendo o caminho legado acessível por flag. Se o novo caminho cair ou devolver confiança baixa demais em volume, você volta pro antigo sem deploy de emergência às 23h
O erro comum deste passo: o big bang. Deletar a função inteira num commit só é a forma mais rápida de descobrir, em produção, aquele elif de 2019 que ninguém entendia mas que segurava um caso real
Que tipo de if/else vale a pena migrar (e quais não)
Essa é a parte que decide se a migração vai dar certo, mais do que qualquer código
| Tipo de nó | Migrar? | Por quê |
|---|---|---|
| Classificar motivo de contato | Sim | Julgamento sobre texto livre, cabe num Choice com opções bem definidas |
| Elegibilidade que depende de interpretar texto | Sim | O critério existe, mas mora numa frase escrita por humano |
| Severidade de reclamação | Sim | Escala ordenada em palavras, que é exatamente o desenho do Score |
| Selecionar um trecho depois do regex achar os candidatos | Sim | Padrão do cookbook: regex acha, modelo escolhe, código normaliza |
| Janela de data (últimos 30 dias, vencido há X) | Não | O modelo lê data como texto, não como quantidade ordenada |
| Comparação numérica e limite de valor | Não | Precisão numérica é fraqueza declarada, e Score não reconstrói número exato por interpolação |
| Ordem de execução e efeitos colaterais | Não | Isso é fluxo de controle, e fluxo de controle é do código |

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.O caso do regex vale um parágrafo extra porque é o melhor exemplo do espírito da coisa. No cookbook de extração pré-parseada, o regex acha os candidatos (e-mails, telefones, valores), o modelo apenas seleciona o trecho pedido e o código normaliza o valor verbatim
Ninguém pede pro modelo "extrair o telefone". Cada peça faz o que faz bem
E se o seu caso é volume altíssimo de uma classificação só, muito repetitiva, vale pensar no trade-off entre usar o Jev ou treinar um classificador próprio antes de sair migrando a árvore inteira
Vídeo: pensando decisões como dev
Antes de sair transformando if em pergunta tipada, vale dar um passo atrás e pensar em como a gente decide as coisas no dia a dia de dev
Pra começar do zero nesse assunto de decisão, este vídeo do canal mostra o tema em "Tome suas Próprias decisões como Dev!"
A ponte com o post é direta: separar o que é regra do que é julgamento é uma decisão de projeto, não um detalhe de implementação
Problemas comuns na migração e como resolver
A decisão nova diverge do if/else em casos óbvios
Causa provável: pergunta ampla demais, ou condição implícita que você achou que estava clara. O jev-1.13 responde a pergunta que você escreveu, não a que você quis dizer
Solução: quebra em perguntas estreitas, com critério explícito no instructions. Em vez de "esse ticket é urgente?", pergunta o que você quer dizer com urgente, item por item
Como prevenir: escreva a pergunta e mostre pra outra pessoa do time sem explicar nada. Se ela interpretar diferente de você, o modelo também vai
Confiança alta em resposta errada
Causa provável: threshold único aplicado em ações de consequência muito diferente. A doc bate nessa tecla: ações diferentes devem ser barradas em níveis diferentes
Solução: threshold por ação e recalibração com os seus dados rotulados, plotando confiança contra acurácia
Como prevenir: antes de subir, escreva pra cada ação migrada qual é o custo de errar. Custo alto, barra alta, e caso incerto sobe pra humano ou pra um modelo de raciocínio mais caro
Resultado parece instável entre execuções
Causa provável: variação no state, não no modelo. O System One é projetado pra devolver respostas estáveis em avaliações repetidas, e no cookbook de self-consistency o desvio padrão médio de probabilidade por pergunta foi de 0,0102, abaixo de todas as condições de LLM comparadas, inclusive temperatura 0
Solução: compare os states das duas execuções byte a byte. Quase sempre tem um timestamp, um campo ordenado de jeito diferente ou um histórico que cresceu no meio
Como prevenir: logue o state junto com a resposta. Sempre. É barato e resolve esse tipo de investigação em cinco minutos
Estouro de contexto
Causa provável: state gordo demais. O orçamento do jev-1.13 é de 64k cobrindo o state mais todas as perguntas somadas, e 32k pro state mais a pergunta individual mais longa
Solução: dar a cada pergunta só o contexto que ela precisa, como a doc orienta. Nem toda pergunta do lote precisa do histórico completo da conversa
Como prevenir: monte o state por caso de uso, não um objeto único reaproveitado em toda chamada
Como observar e desligar o if/else antigo com segurança
A fase final é chata e é a que salva a migração. Se você não instrumentar, vai desligar o legado no feeling
- Instrumente a comparação numa ferramenta de observabilidade. O Jev está disponível como juiz nos Evaluators do Langfuse: você adiciona a chave TypeSafe como conexão nas configurações do projeto, define o state e as perguntas tipadas na UI do evaluator, e cada pergunta escreve um score nas observações correspondentes
O erro comum deste passo: deixar a comparação só no log de texto do servidor. Daqui a duas semanas você vai querer filtrar por nó da árvore e por faixa de confiança, e grep não vai te salvar. Vale montar desde já a base pra acompanhar as decisões do Jev em produção
- Se a sua stack é JavaScript, avalie de dentro dela. No Vercel AI Gateway o Jev é chamado pelo id de modelo
typesafe-ai/jev, viaexperimental_evaluatedo AI SDK (exige AI SDK 7 ou superior), com os tipos de perguntachoice,scoreeboolean
A confiança de Choice e Score vem em result.providerMetadata.typesafe.confidence
O erro comum deste passo: ler só o valor da decisão e ignorar o providerMetadata. Sem a confiança, o seu roteamento por faixa não existe
- Defina o critério pra considerar um ramo migrado. Sugestão de critério objetivo: taxa de divergência com o legado estável e explicada (você sabe POR QUE cada divergência aconteceu, e em boa parte delas o novo caminho está certo e o antigo errado), curva de confiança contra acurácia desenhada com dados rotulados, e threshold definido pra aquela ação específica
O erro comum deste passo: tratar toda divergência como erro do modelo. Em lógica encardida, uma parte das divergências é bug antigo aparecendo pela primeira vez 😀
- Desligue na ordem certa: baixa consequência primeiro. Ramo que classifica pra fila interna antes, ramo que dispara e-mail pro cliente depois, ramo que mexe em dinheiro por último, e mesmo assim com confirmação humana na faixa do meio
O erro comum deste passo: começar pelo ramo mais dolorido porque é o que mais incomoda. O ramo mais dolorido costuma ser o de maior consequência, e você não quer aprender a calibrar threshold ali
Próximo passo: escolha um ramo e comece por ele
O princípio que segura tudo isso é um só, e é o que a própria documentação de arquitetura da TypeSafe prega: código no comando, modelo só nos pontos de julgamento
Fluxo de controle, regra determinística e efeito colateral continuam sendo seus. O Jev entra onde o sistema precisa de senso comum programável ou de interpretar dado não estruturado, devolve Choice, Score ou Noul com probabilidade e confiança, e o seu código decide o que fazer com isso
O próximo passo concreto é pequeno de propósito: escolhe UM ramo, o mais encardido entre os de menor consequência, escreve uma pergunta tipada pra ele e roda em paralelo por um período, registrando divergência e confiança, antes de encostar no legado
E se você usa agente de código no dia a dia, existe skill oficial da TypeSafe pra ajudar a escrever essas perguntas. No Claude Code é assim:
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
Depois é só invocar com /typesafe:typesafe-ai. Em outros agentes, roda npx skills add typesafe-ai/skills --skill typesafe-ai
Pra atualizar no Claude Code, reinicia o Claude Code ou roda /reload-plugins, e a atualização automática fica em /plugin, Marketplaces, typesafe-ai, Enable auto-update
Um ramo. Em paralelo. Sem desligar nada ainda
É assim que aquela função de 400 linhas encolhe sem levar o faturamento junto…
Até o próximo post! =)
Perguntas frequentes
Dá pra migrar se/então de data e valor numérico pro Jev?
Não é recomendado. A própria página de jaggedness do jev-1.13 informa que o modelo é fraco em precisão numérica e lê datas como texto, não como quantidades ordenadas. Comparação, distância e janela de data continuam no código, isso não é julgamento, é regra determinística.
Quantas perguntas dá pra mandar numa mesma requisição ao migrar vários ifs de uma vez?
Quantas couberem no orçamento de contexto do jev-1.13, que é 64k pro state mais todas as perguntas somadas. As perguntas de uma mesma requisição rodam em paralelo e isoladas contra o mesmo state, e a TypeSafe afirma que acrescentar perguntas quase não muda o tempo de resposta.
Preciso reescrever tudo pro Jev de uma vez ou dá pra ir por partes?
Dá e deve ser por partes. O fluxo recomendado na documentação é montar o state, fazer as perguntas em lote, combinar as respostas com checagens determinísticas no código e só então rotear o caso, sempre com o if/else antigo rodando junto até você validar.
O Jev decide sozinho quando a confiança está baixa?
Não, quem decide é o seu código, olhando o campo de confiança que vem na resposta. Como mostrei no passo do threshold, a documentação de confiança indica três faixas: alta confiança age automaticamente, média prossegue com cautela (pedir confirmação, marcar para revisão ou buscar mais informação) e baixa não age (roteia para humano, pede esclarecimento ou cai em outro sistema).
Quanto custa rodar o Jev em paralelo com a árvore de if/else antiga durante a migração?
O preço é US$ 0,042 por 1 milhão de tokens de entrada, com tokens de saída gratuitos. Com tempo de resposta ponta a ponta entre 70ms e 500ms segundo a TypeSafe, rodar os dois caminhos lado a lado por um tempo não pesa no orçamento nem trava a resposta.
Como acesso o Jev se o cadastro direto no console está pausado?
O signup direto no console da TypeSafe abriu em 20/09/2026 e foi pausado em 22/09/2026 por excesso de demanda, mas contas criadas antes seguem ativas. Enquanto isso, o Jev continua acessível pelo OpenRouter (typesafe/jev-1.13 e o alias jev-latest) e pelo Vercel AI Gateway (typesafe-ai/jev).
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.
