Como integrar o Jev sem ficar preso à TypeSafe (e poder trocar de IA de decisão depois)

Pra integrar o Jev sem ficar preso à TypeSafe, isole a decisão atrás de uma interface sua (ex: DecisionProvider com decide(estado, perguntas)), defina tipos de resposta neutros (escolha, nota e probabilidade de sim) e guarde os casos de teste versionados no seu próprio repositório. O adaptador do Jev chama POST https://api.typesafe.ai/v1/systemone com model, state e questions e traduz o mapa answers pros seus tipos. Com isso, trocar pela Decisions API da OpenAI (ou o contrário) vira um adaptador novo e uma variável de ambiente, não um refactor
O erro não é escolher o Jev
O erro é espalhar o formato dele pelo código inteiro 😛
Fala aí, beleza? Se liga no contexto: o Jev é o modelo de decisão da TypeSafe AI, apresentado em 15 de setembro de 2026 como o primeiro modelo público da linha System One
Ele não é um chatbot: recebe um contexto e perguntas tipadas e devolve valores estruturados (uma escolha, uma nota ou uma probabilidade), nunca texto livre
Se tu ainda tá decidindo se faz sentido sair do prompt de chat, vale ler sobre Jev vs LLM de chat antes de seguir
Agora o ponto: duas semanas depois do Jev, a OpenAI anunciou a Decisions API no DevDay, em 29 de setembro de 2026
E já apareceram outros modelos de decisão, como o CLM-8B (Stanford e NVIDIA) e o Strands Decider 2B (AWS), segundo este levantamento sobre modelos de decisão
Ou seja, o mercado tá mudando MUITO rápido
Então faz todo sentido integrar o Jev de um jeito que te deixe trocar de fornecedor depois sem destroçar o projeto por completo haha
Bora ver na prática?
O que você precisa antes de começar
Primeiro, um jeito de chamar o Jev. Tem três caminhos documentados:
- API própria da TypeSafe: chave de API e o endpoint
POST https://api.typesafe.ai/v1/systemone, descrito na documentação da API da TypeSafe - OpenRouter: o Jev está lá com o ID
typesafe/jev-1.13, disponível pra qualquer pessoa com chave do OpenRouter e cobrado na conta do OpenRouter (tem a página do Jev na doc do OpenRouter) - LiteLLM (opcional): documenta acesso ao Jev via pass-through
O que importa entender agora é outra coisa…
Por que um wrapper de chat não serve pro Jev?
O Jev é chamado por um endpoint próprio de avaliação, fora do padrão /chat/completions
Então aquela camada genérica de "chat com qualquer LLM" que tu já tem no projeto não encaixa aqui
A entrada é diferente (estado + perguntas) e a saída também (valores tipados por pergunta)
Por isso a gente vai criar uma camada nova, pensada pra decisão =)
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
Passo 1: defina a interface de decisão do seu domínio
Antes de escrever qualquer fetch, define o contrato que o resto da aplicação vai enxergar
Pensa assim: se você conhece o padrão de repository pra banco de dados, é bem semelhante
O código de negócio pede "me dá essa decisão" e não faz ideia se quem responde é o Jev, a OpenAI ou um modelo rodando local
// decisao/contrato.ts
export type Estado = {
texto?: string;
json?: unknown;
imagens?: string[]; // nem todo fornecedor aceita
};
export type Pergunta =
| { id: string; tipo: "escolha"; texto: string; opcoes: string[] }
| { id: string; tipo: "nota"; texto: string; niveis: string[] }
| { id: string; tipo: "probabilidade_sim"; texto: string };
export interface DecisionProvider {
readonly nome: string;
readonly aceitaImagem: boolean;
decide(estado: Estado, perguntas: Pergunta[]): Promise<Record<string, Resposta>>;
}
Repara que os nomes são SEUS: escolha, nota, probabilidade_sim
O Resposta vem no próximo passo
Erro comum deste passo:
Copiar os nomes do Jev pra interface (Noul, state, questions e etc)
Parece inofensivo, mas aí o seu código de negócio já nasce falando o dialeto da TypeSafe, e qualquer troca de fornecedor passa a mexer em tudo
Passo 2: padronize o formato de resposta com tipos neutros
O Jev tem três tipos de pergunta:
- Choice: escolhe entre opções nomeadas e devolve as probabilidades de cada opção e a confiança
- Score: usa uma rubrica ordenada e devolve nota ponderada por probabilidade, legenda de níveis, distribuição e confiança
- Noul: devolve um número de 0 a 1 que é a probabilidade de "sim"
A ideia é ter um tipo interno pra cada um desses, sem herdar o nome de ninguém:
// decisao/contrato.ts (continuação)
export type Escolha = {
tipo: "escolha";
opcao: string;
probabilidades: Record<string, number>;
confianca: number;
};
export type Nota = {
tipo: "nota";
valor: number; // nota ponderada por probabilidade
distribuicao: Record<string, number>;
confianca: number;
};
export type ProbabilidadeSim = {
tipo: "probabilidade_sim";
probabilidade: number; // 0 a 1, já é a chance de "sim"
};
export type Resposta = Escolha | Nota | ProbabilidadeSim;
E por que tanto cuidado com nome?
Porque essa armadilha é real: o Jev chama esse último tipo de Noul e a OpenAI chama o mesmo conceito de predicate
Mesma coisa, dois nomes diferentes. Se o seu código usa Noul em todo canto, trocar pra OpenAI vira caça ao tesouro 😀
Erro comum deste passo:
Tratar o valor do Noul como confiança e inventar um campo confianca extra no ProbabilidadeSim
No Noul, o número de 0 a 1 JÁ é a probabilidade de "sim", não existe um campo de confiança separado
Se tu cria um campo que o fornecedor não manda, alguém vai preencher com valor fake e a lógica de negócio vai decidir em cima disso… Tome cuidado!
Passo 3: escreva o adaptador do Jev
Agora sim, a parte que fala com a TypeSafe
A requisição tem três campos obrigatórios no nível superior: model, state e questions
O state é a informação a avaliar (texto ou JSON) e o questions é o que decidir
A resposta traz o identificador do modelo, os campos de uso (usage) e um mapa answers indexado pelos mesmos ids usados em questions
Se liga no código: ele usa só os campos confirmados na documentação
A tradução campo a campo de cada pergunta e de cada resposta mora num arquivo separado, o jev-schema.ts, e o adaptador só cuida do transporte e da correspondência por id
// decisao/jev.ts
import { DecisionProvider, Estado, Pergunta, Resposta } from "./contrato";
import { montarPerguntasJev, traduzirRespostaJev } from "./jev-schema";
const JEV_URL = "https://api.typesafe.ai/v1/systemone";
export class JevProvider implements DecisionProvider {
readonly nome = "jev";
readonly aceitaImagem = false; // state é texto ou JSON
constructor(private apiKey: string, private modelo: string) {}
async decide(estado: Estado, perguntas: Pergunta[]) {
if (estado.imagens?.length) {
throw new Error("JevProvider não recebe imagem: envie o estado em texto ou JSON");
}
const res = await fetch(JEV_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: this.modelo,
state: estado.json ?? estado.texto,
questions: montarPerguntasJev(perguntas), // ids iguais aos seus
}),
});
if (!res.ok) throw new Error(`Jev respondeu ${res.status}`);
const data = await res.json(); // { model, answers, usage }
const saida: Record<string, Resposta> = {};
for (const p of perguntas) {
const bruta = data.answers?.[p.id];
if (bruta === undefined) {
throw new Error(`Jev não devolveu resposta para "${p.id}"`);
}
saida[p.id] = traduzirRespostaJev(p, bruta);
}
return saida;
}
}
Curso RecomendadoDesenvolva sistemas mais inteligentes, rápidos e robustos com JevConhecer cursoE o que faz o jev-schema.ts?
Ele é o ÚNICO arquivo do projeto que conhece o schema da TypeSafe: o montarPerguntasJev transforma cada Pergunta numa pergunta Choice, Score ou Noul no formato de questions da documentação da API da TypeSafe, e o traduzirRespostaJev faz o caminho de volta, de cada item de answers pra Escolha, Nota ou ProbabilidadeSim
No caso do Noul a tradução é a mais simples de todas: o número de 0 a 1 vai direto pro campo probabilidade, sem inventar confiança nenhuma
Se a TypeSafe mudar o schema amanhã, só esse arquivo muda 🙂
O valor do model vem por parâmetro de propósito: assim tu não espalha id de modelo pelo código e confere o id certo na doc do canal que estiver usando
E o usage? Vale logar, porque é ele que te deixa comparar custo entre fornecedores depois
Erro comum deste passo:
Perder a correspondência entre os ids de questions e as chaves de answers
Quem itera as respostas "na ordem" em vez de buscar pelo id acaba misturando a resposta de uma pergunta com outra
Por isso o loop acima percorre as SUAS perguntas e busca cada uma pelo id, estourando erro se faltar alguma
Passo 4: deixe o segundo fornecedor pronto (mesmo sem usar)
Esse passo parece exagero, mas é ele que prova que a interface tá certa
O candidato óbvio é a Decisions API da OpenAI:
- Beta público aberto a todos os devs, anunciado em 6 de outubro de 2026
- Endpoint
POST /v1/decisions - Só suporta o modelo
gpt-6-luna - Aceita texto e imagem como entrada
- Devolve três formatos:
predicate(probabilidade entre 0 e 1),choice(uma opção de uma lista fixa) escore(média ponderada por probabilidade de níveis ordenados)
O esboço fica assim:
// decisao/openai-decisions.ts
import { DecisionProvider, Estado, Pergunta, Resposta } from "./contrato";
// a parte que muda de fornecedor pra fornecedor: o nome de cada tipo
const TIPO_OPENAI: Record<Pergunta["tipo"], string> = {
escolha: "choice",
nota: "score",
probabilidade_sim: "predicate", // o mesmo conceito do Noul no Jev
};
export class OpenAIDecisionsProvider implements DecisionProvider {
readonly nome = "openai-decisions";
readonly aceitaImagem = true; // texto e imagem
readonly modelo = "gpt-6-luna"; // único modelo suportado
async decide(estado: Estado, perguntas: Pergunta[]): Promise<Record<string, Resposta>> {
// POST /v1/decisions
// payload e nomes de campos: conferir na doc oficial da OpenAI antes de implementar
throw new Error("OpenAIDecisionsProvider ainda não implementado");
}
}
Não escrevi o payload de propósito: o formato exato de requisição e resposta tu confere na doc oficial da OpenAI na hora de implementar
O ponto aqui é ver que a classe encaixa na MESMA interface e que predicate vira o mesmo ProbabilidadeSim que o Noul vira no adaptador do Jev
O resto da aplicação nem fica sabendo 🙂
Erro comum deste passo:
Assumir que os dois aceitam a mesma entrada
A OpenAI aceita imagem e o state do Jev é texto ou JSON
Por isso a interface tem o aceitaImagem: ou o seu fluxo prevê imagem e escolhe um fornecedor que aceita, ou o adaptador recusa de forma explícita (como o JevProvider faz), nunca descarta a imagem em silêncio
Passo 5: mantenha os casos de teste no seu repositório
Aqui mora a parte que mais gente esquece
Sem casos de teste, trocar de fornecedor é no feeling: "parece que tá decidindo igual" haha
Guarda os casos num arquivo versionado do próprio projeto, com estado, perguntas e resposta esperada (ou faixa aceitável):
[
{
"id": "ticket-reembolso-01",
"estado": { "texto": "Cliente pede reembolso logo após a compra, produto não foi usado" },
"perguntas": [
{ "id": "categoria", "tipo": "escolha", "texto": "Qual a categoria do ticket?", "opcoes": ["reembolso", "suporte_tecnico", "outro"] },
{ "id": "urgente", "tipo": "probabilidade_sim", "texto": "O ticket é urgente?" }
],
"esperado": {
"categoria": { "opcao": "reembolso" },
"urgente": { "min": 0, "max": 0.4 }
}
}
]
Os valores do exemplo são ilustrativos: a faixa aceitável quem define é você, conforme o risco da decisão no seu produto

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 um runner simples que roda contra QUALQUER adaptador:
// decisao/avaliar.ts
import casos from "./casos/decisoes.json";
import { DecisionProvider } from "./contrato";
export async function avaliar(provider: DecisionProvider) {
let falhas = 0;
for (const caso of casos as any[]) {
const respostas = await provider.decide(caso.estado, caso.perguntas);
for (const [id, esperado] of Object.entries<any>(caso.esperado)) {
const r = respostas[id];
const ok =
r.tipo === "escolha" ? r.opcao === esperado.opcao :
r.tipo === "nota" ? r.valor >= esperado.min && r.valor <= esperado.max :
r.probabilidade >= esperado.min && r.probabilidade <= esperado.max;
if (!ok) {
falhas++;
console.log(`[${provider.nome}] ${caso.id}/${id} falhou`, r);
}
}
}
return falhas;
}
Pra quem quer uma ferramenta de evals em cima disso, o Langfuse publicou um guia de uso do Jev para evals em 18 de setembro de 2026
É uma opção massa, mas a fonte da verdade continua sendo o arquivo no seu repo
Erro comum deste passo:
Deixar o dataset só dentro de uma ferramenta externa
Aí a troca de fornecedor (ou de ferramenta de eval) vira migração de dados, e o que era uma tarde vira uma sprint…
Passo 6: escolha o fornecedor por configuração
Último passo: o fornecedor sai de uma variável de ambiente e é montado num lugar só, uma factory
// decisao/factory.ts
import { DecisionProvider } from "./contrato";
import { JevProvider } from "./jev";
import { OpenAIDecisionsProvider } from "./openai-decisions";
export function criarProvider(): DecisionProvider {
switch (process.env.DECISION_PROVIDER) {
case "jev":
return new JevProvider(process.env.TYPESAFE_API_KEY!, process.env.JEV_MODEL!);
case "openai":
return new OpenAIDecisionsProvider();
default:
throw new Error(`DECISION_PROVIDER inválido: ${process.env.DECISION_PROVIDER}`);
}
}
Antes de virar a chave em produção, roda o avaliar() do passo 5 com os dois valores de DECISION_PROVIDER e compara as falhas
E sobre velocidade: a TypeSafe afirma que o Jev conclui decisões entre 70 e 500 milissegundos
Isso é dado da própria empresa, então mede no SEU caso (com o seu estado, as suas perguntas e a sua rede) antes de montar timeout ou SLA em cima desse número
Erro comum deste passo:
Espalhar if (fornecedor === "jev") pela aplicação
Cada if desses é um pedacinho do fornecedor vazando de volta pro código de negócio, ou seja, tudo que a gente evitou nos passos anteriores
Troca de fornecedor tem que ser: um adaptador novo, um case na factory e uma variável de ambiente. Só isso
Jev x Decisions API da OpenAI: o que o seu adaptador precisa traduzir
Essa tabela não é pra dizer qual é melhor, é pra mostrar o que cada adaptador traduz e qual tipo interno recebe isso:
| Item | Jev (TypeSafe) | Decisions API (OpenAI) | Tipo interno sugerido |
|---|---|---|---|
| Endpoint | POST https://api.typesafe.ai/v1/systemone |
POST /v1/decisions |
DecisionProvider.decide() |
| Modelo | typesafe/jev-1.13 (no OpenRouter) |
gpt-6-luna (único suportado) |
Configuração do adaptador |
| Entrada | state em texto ou JSON + questions |
Texto e imagem | Estado (com aceitaImagem) |
| Escolha entre opções | Choice | choice | Escolha |
| Nota em níveis ordenados | Score | score | Nota |
| Probabilidade de sim (0 a 1) | Noul | predicate | ProbabilidadeSim |
| Preço de entrada | US$ 0,042 por milhão de tokens | US$ 0,10 por milhão de tokens | Log de usage por fornecedor |
| Preço de saída | Grátis | Grátis | (não precisa rastrear) |
| Disponibilidade | API própria, OpenRouter, LiteLLM pass-through | Beta público | Variável DECISION_PROVIDER |
Repara na linha do Noul x predicate: é literalmente o motivo de existir o passo 2 😀
Quando essa arquitetura vale o esforço (e quando é exagero)
Nem todo projeto precisa de interface, dois adaptadores e suíte de casos, né?
Vale a pena quando:
- O produto está em produção e depende da decisão: se o Jev decide algo que afeta usuário real, conseguir trocar de fornecedor rápido é seguro de vida
- Você quer comparar custo entre fornecedores: com US$ 0,042 (preço do Jev no OpenRouter) contra US$ 0,10 (anúncio da Decisions API) por milhão de tokens de entrada (saída grátis nos dois), a diferença em volume alto justifica medir, e medir só é justo com a mesma suíte de testes
- A equipe quer testar outros modelos depois: CLM-8B, Strands Decider 2B e o que mais aparecer viram só mais um adaptador
Se tu ainda nem sabe por onde começar dentro do produto, vale pensar em qual decisão migrar primeiro antes de montar toda essa estrutura
É exagero quando:
- Protótipo descartável: se o código vai pro lixo em uma semana, chama o Jev direto e segue a vida
- Script pontual: classificar uma planilha uma vez não precisa de factory nem de adaptador reserva
O critério é simples: se trocar de fornecedor um dia vai doer, a interface vale. Se não vai, é next, next e finish haha
Próximo passo: comece pela interface, não pelo fornecedor
Recapitulando o que faz a escolha do modelo de decisão ser reversível:
- Uma interface sua (
DecisionProvider), escrita em termos do seu negócio - Tipos de resposta neutros (
Escolha,Nota,ProbabilidadeSim), sem Noul nem predicate no código de negócio - Casos de teste versionados no seu repositório, rodando contra qualquer adaptador
- Um adaptador por fornecedor e uma factory escolhendo por configuração
O próximo passo concreto é bem pequeno: escreve a interface e cinco casos de teste do seu domínio ANTES de qualquer chamada ao Jev
Depois pluga o adaptador e roda a suíte
O mercado de modelos de decisão acabou de nascer, e em poucas semanas já tem Jev, Decisions API e modelos menores aparecendo
Nesse cenário, manter a porta aberta é a decisão mais barata que tu pode tomar agora =)
Até o próximo post!
Perguntas frequentes
Como integrar o Jev sem travar meu código no formato da TypeSafe?
Criando uma camada própria com tipos neutros (escolha, nota, probabilidade_sim) entre o seu código de negócio e o adaptador do Jev. O adaptador conversa com o formato da TypeSafe (state, questions, answers), mas o resto da aplicação nunca vê esses nomes. Assim, se precisar trocar de fornecedor, só reescreve o adaptador.
É possível usar o Jev pelo OpenRouter em vez da API própria da TypeSafe?
Sim, o Jev está disponível no OpenRouter com o ID typesafe/jev-1.13, pra qualquer pessoa com chave de API do OpenRouter. Nesse caso a cobrança acontece na conta do OpenRouter, e não direto na TypeSafe. A configuração específica fica na página de documentação do Jev no próprio OpenRouter.
Qual a diferença entre o Noul do Jev e o predicate da Decisions API da OpenAI?
Nenhuma na prática: são o mesmo conceito com nomes diferentes. No Jev, o Noul devolve um número de 0 a 1 que já é a probabilidade de ‘sim’. Na OpenAI, esse formato se chama predicate. É exatamente esse tipo de divergência de nome que a interface neutra do passo 2 evita propagar pelo código.
Preciso de um wrapper de chat genérico pra chamar o Jev?
Não, e tentar usar um wrapper de chat aqui só complica as coisas. O Jev é chamado por um endpoint próprio de avaliação (POST para /v1/systemone), fora do padrão /chat/completions. A entrada também é diferente: em vez de mensagens, são os campos state e questions.
Quanto custa usar o Jev comparado com a Decisions API da OpenAI?
Segundo a página do Jev no OpenRouter, ele cobra US$ 0,042 por milhão de tokens de entrada, com tokens de saída grátis. Pelo anúncio da Decisions API da OpenAI, ela cobra US$ 0,10 por milhão de tokens de entrada, também com saída grátis. A diferença de preço de entrada é relevante se o seu volume de chamadas de decisão for alto.
Dá pra trocar o Jev por outro modelo de decisão depois, como o CLM-8B ou o Strands Decider?
Dá, e é justamente pra isso que serve a interface de domínio do passo 1. Depois do Jev já surgiram outros modelos de decisão, como o CLM-8B (Stanford e NVIDIA) e o Strands Decider 2B (AWS), conforme o levantamento sobre modelos de decisão citado no post. Se o seu código de negócio só conhece os tipos neutros, basta escrever um novo adaptador pra esses modelos sem tocar no resto do projeto.
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.
