Como usar o Jev para escolher o próximo exercício em um app de estudos?

O Jev é o primeiro modelo público da categoria System One Models, criada pela TypeSafe AI para automatizar decisões dentro de software em vez de gerar texto. Num app de estudos, você manda o histórico de acertos e erros do aluno no campo state e faz uma pergunta do tipo Choice com o catálogo de exercícios no criteria. A resposta volta tipada: a opção escolhida em choice, a probabilidade de cada opção em probabilities e um confidence de 0 a 1. Com isso dá pra alternar entre reforço e avanço sem uma árvore de ifs engessada 🙂
Fala aí, beleza? Toda trilha de estudos começa bonitinha e termina como uma árvore de if de 400 linhas que ninguém mais tem coragem de mexer
Aluno errou dois de álgebra? volta pro módulo 3
Acertou três seguidos? pula pro 5
Aí entra um tópico novo no catálogo e você descobre que metade das regras não faz mais sentido…
O Jev é uma tentativa bem diferente de resolver isso
Ele é o primeiro modelo público da categoria System One Models, que a própria TypeSafe AI apresentou no blog dela como um caminho pra automatizar decisões dentro de softwares em vez de gerar texto
Ou seja: ele não te devolve um parágrafo explicando o que o aluno deveria estudar, ele devolve uma decisão tipada com probabilidade calibrada, dentro do conjunto de opções que VOCÊ definiu
O conceito vem do Sistema 1 do Daniel Kahneman, aquela parte da cognição ligada a decisões rápidas e automáticas
E é exatamente isso que uma trilha adaptativa precisa: uma decisão pequena, rápida e repetida milhares de vezes por dia
Neste post a gente monta a chamada que recebe o histórico do aluno e devolve qual exercício vem a seguir, sempre dentro de um catálogo válido
Bora ver na prática?
O que você precisa antes de começar
Antes do código, o básico pra não travar no meio:
1. Uma conta TypeSafe com chave na variável TYPESAFE_API_KEY
Aqui vai um aviso importante: a TypeSafe saiu do stealth com o Jev em early access em 15 de setembro de 2026, abriu o acesso pra todo mundo sem fila em 20 de setembro e, em 22 de setembro, pausou temporariamente novos cadastros por excesso de demanda
Contas já criadas continuam funcionando normalmente, e a empresa diz que trabalha pra reabrir
Se você não tem conta ainda, existem outros caminhos: o Jev está no Vercel AI Gateway com o modelo typesafe-ai/jev (acessível pela API experimental_evaluate do AI SDK) e no OpenRouter com os IDs ~typesafe/jev-latest e typesafe/jev-1.13
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
2. Python 3.10 ou superior e o SDK oficial
pip install typesafe-sdk
# ou, se tu curte uv:
uv add typesafe-sdk
Tem SDK de JavaScript também, o @typesafe-ai/sdk, caso teu app de estudos seja Node/Next
3. Do lado do teu app: catálogo e histórico
Essa parte é tua e é a que mais gente subestima
Você precisa de um catálogo de exercícios com ids estáveis (nada de gerar id na hora) e de algum histórico de acertos e erros por aluno
Se o teu banco só guarda "nota final do módulo", o Jev vai decidir com pouca informação e a distribuição de probabilidades vai sair espalhada
O modelo é bom, mas ele não adivinha o que tu não mandou =)
Sobre custo: o Jev cobra só por token de entrada, a saída é gratuita
São US$ 0,042 por 1 milhão de tokens de entrada e US$ 0 por 1 milhão de tokens de saída
Isso muda o jeito de pensar o design da chamada, e já já você vai ver por quê
Passo a passo: montando a decisão de próximo exercício
- Modele o
statecom o histórico do aluno
O state é o conteúdo que o Jev vai avaliar
Ele aceita string, objeto JSON ou array de textos, e a ideia é agrupar num único bloco as informações relacionadas que precisam ser comparadas entre si
No nosso caso: tópico atual, tentativas recentes, acertos, erros e tempo gasto
state = {
"aluno_id": "a_10482",
"trilha": "Matemática Básica",
"topico_atual": "frações",
"tentativas_recentes": [
{"exercicio": "frac_soma_01", "acertou": True, "tempo_s": 41},
{"exercicio": "frac_soma_02", "acertou": True, "tempo_s": 38},
{"exercicio": "frac_denom_dif_01", "acertou": False, "tempo_s": 122},
{"exercicio": "frac_denom_dif_02", "acertou": False, "tempo_s": 97},
{"exercicio": "frac_simplificacao_01", "acertou": True, "tempo_s": 55},
],
"topicos_concluidos": ["operações básicas", "múltiplos e divisores"],
"observacao": "errou duas vezes seguidas quando os denominadores eram diferentes"
}
O erro comum deste passo: mandar só o último resultado
Se tu envia apenas {"ultimo": "errou"}, a decisão vira um coin flip caro
Trilha adaptativa boa olha padrão, não olha o último clique
- Monte a pergunta Choice com o catálogo inteiro
O Jev tem três tipos de pergunta (as primitivas): Noul (sim/não com a probabilidade de ser sim), Choice (escolhe uma opção de um conjunto definido por você) e Score (posição numa escala de níveis ordenados)
Pra "qual o próximo exercício", a primitiva certa é o Choice
Na Choice existem dois campos que importam: instructions, que é a pergunta feita sobre o state, e criteria, que é o mapa das opções possíveis
Cada descrição de opção pode ser string, objeto ou array, dizendo o que aquela opção cobre, o que ela NÃO cobre e exemplos
from typesafe_sdk import TypeSafeClient, Choice
proximo_exercicio = Choice(
instructions=(
"Considerando o histórico de acertos e erros do aluno, qual exercício "
"do catálogo deve ser apresentado a seguir?"
),
criteria={
"frac_denom_dif_03": {
"cobre": "soma de frações com denominadores diferentes, nível fácil",
"nao_cobre": "multiplicação e divisão de frações",
"exemplo": "1/2 + 1/3"
},
"frac_denom_dif_video": {
"cobre": "revisão em vídeo do MMC aplicado a denominadores diferentes",
"nao_cobre": "exercício avaliativo, é conteúdo de reforço",
},
"frac_simplificacao_02": {
"cobre": "simplificação de frações, nível intermediário",
"nao_cobre": "soma com denominadores diferentes",
},
"frac_mult_01": {
"cobre": "multiplicação de frações, primeiro contato",
"nao_cobre": "soma e subtração",
},
"dizimas_01": {
"cobre": "introdução a números decimais, avanço de tópico",
"nao_cobre": "qualquer conteúdo de frações",
},
}
)
Uma Choice aceita até 255 opções, e a documentação orienta passar a lista completa em vez de uma lista curta, já que cada opção custa poucos tokens
Lembra do preço lá de cima? é por isso que compensa mandar o catálogo inteiro: você paga pouquíssimo pelas opções extras e ganha uma distribuição de probabilidade MUITO mais informativa
O erro comum deste passo: pré-filtrar o catálogo com a tua regra antiga e mandar só 3 opções
Aí você trocou a árvore de ifs por… a mesma árvore de ifs, com um modelo no final 😛
- Chame o modelo
No SDK Python a chamada é client.system_one(state=..., questions=...)
O questions é um mapa de ids escolhidos por você
client = TypeSafeClient() # lê a chave da variável TYPESAFE_API_KEY
response = client.system_one(
state=state,
questions={"proximo_exercicio": proximo_exercicio}
)
O SDK chama jev-latest por padrão
Se você prefere bater direto na API, é um POST em https://api.typesafe.ai/v1/systemone com Authorization: Bearer <API_KEY> e Content-Type: application/json, e o corpo com três campos de topo: state, model e questions
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-latest",
"state": { "topico_atual": "frações" },
"questions": {
"proximo_exercicio": {
"type": "choice",
"instructions": "Qual exercício deve vir a seguir?",
"criteria": { "frac_denom_dif_03": "...", "dizimas_01": "..." }
}
}
}'
O erro comum deste passo: esquecer o campo model na chamada crua
O alias recomendado é jev-latest, mas também dá pra fixar um ID versionado tipo jev-1.13.0
E fica atento que a notação muda conforme o lugar: na doc da TypeSafe o versionado é jev-1.13.0, já no OpenRouter o modelo aparece listado como typesafe/jev-1.13
E se você quiser conferir o que a tua conta pode usar, existe um GET /v1/models
- Leia a resposta tipada
A resposta de um Choice traz três coisas: a opção selecionada em choice, uma probabilidade pra cada opção em probabilities e um confidence pra opção escolhida
resultado = response.choices["proximo_exercicio"]
print(resultado.choice) # ex: "frac_denom_dif_video"
print(resultado.confidence) # ex: 0.81
print(resultado.probabilities) # dict com a probabilidade de cada id do catálogo
A resposta também fica acessível em response.answers["proximo_exercicio"], e existem coleções por tipo (response.choices, response.scores, response.nouls)
O erro comum deste passo: tratar choice como texto livre e sair fazendo if "video" in choice
Ele é o id de uma opção que VOCÊ definiu, então use igualdade e um mapa de ids, como você faria com um enum
- Aplique a política em cima da probabilidade
Esse é o passo que separa "chamei uma API" de "tenho uma trilha adaptativa"
O confidence é um número de 0 a 1 calculado a partir de como a distribuição de probabilidades está espalhada: quanto mais concentrada em um resultado, maior a confiança
if resultado.confidence >= 0.7:
servir(resultado.choice)
else:
# distribuição espalhada: o modelo está em dúvida
# cai numa política sua, tipo revisar o tópico atual
servir(politica_de_reforco(state))
O erro comum deste passo: ignorar probabilities e olhar só o choice
A lista completa de probabilidades é o ouro aqui, e é dela que sai a parte interessante do post
Reforço ou avanço: usando probabilidade em vez de regras engessadas
A sacada é parar de perguntar "o aluno passou no critério X?" e começar a perguntar "o quanto o modelo está convicto?"
Quando a distribuição está bem concentrada num exercício, é sinal de que o histórico aponta claramente pra um caminho
Quando ela está espalhada entre quatro ou cinco opções, o sinal é outro: não tem evidência suficiente pra avançar, e avançar no escuro é como o aluno desiste do app
Uma leitura possível pra transformar saída em política:
| Leitura da saída | O que isso costuma indicar | Ação na trilha |
|---|---|---|
confidence alta, probabilidade concentrada num exercício de avanço |
histórico consistente no tópico | serve o exercício direto |
confidence alta, probabilidade concentrada num conteúdo de reforço |
erro com padrão claro | serve o reforço e repete a medição depois |
confidence baixa, probabilidade espalhada |
state pobre ou aluno oscilando |
revisão do tópico atual em vez de avançar |
| duas opções com probabilidade quase igual | exercícios equivalentes pro momento | desempate por critério teu (recência, tempo de sessão) |
Os limiares aí são ilustrativos, viu? a doc explica o campo confidence e sugere usá-lo pra aplicar threshold sem fazer a conta na mão, mas quem define o número do teu produto é você, calibrando com dados reais dos teus alunos
Várias perguntas na mesma requisição
Aqui o negócio fica bom
O questions é um mapa, então várias perguntas podem ir na MESMA requisição: todas enxergam o mesmo state, são avaliadas de forma independente e voltam sob o id que você escolheu
Ou seja, dá pra medir domínio, prontidão e próximo passo de uma vez só
from typesafe_sdk import TypeSafeClient, Choice, Score, Noul
response = client.system_one(
state=state,
questions={
"dominio_do_topico": Score(
instructions="Qual o nível de domínio do aluno no tópico atual?",
criteria={
"iniciante": "erra o conceito base com frequência",
"em_desenvolvimento": "acerta os casos simples, erra variações",
"consolidado": "acerta variações com tempo estável",
"avancado": "acerta rápido, inclusive casos difíceis"
}
),
"pronto_para_avancar": Noul(
instructions="O aluno está pronto para avançar para o próximo tópico da trilha?"
),
"proximo_exercicio": proximo_exercicio,
}
)
nivel = response.scores["dominio_do_topico"]
pronto = response.nouls["pronto_para_avancar"]
sugestao = response.choices["proximo_exercicio"]
O Score devolve um valor ponderado pela probabilidade entre os níveis que você definiu
E tem um detalhe que a doc faz questão de avisar: distribuições diferentes podem gerar o mesmo score
Um aluno metade iniciante e metade avançado pode cair no mesmo número de um aluno estavelmente mediano, e o tratamento pedagógico dos dois é completamente diferente
Por isso leia probabilities e confidence junto com o score, sempre
Variações que saem de graça desse desenho
- Revisão espaçada: inclua no catálogo opções de revisão de tópicos já concluídos e coloque no
statehá quanto tempo cada um foi visto - Ajuste de dificuldade: em vez de um exercício, o Choice pode escolher a FAIXA de dificuldade, e o teu app sorteia dentro dela
- Desempate: quando duas opções vêm com probabilidade parecida, use um critério do produto (variedade de formato, tempo de sessão restante) em cima do
probabilities
Esse padrão de "state rico + Choice com catálogo completo + leitura da distribuição" não é exclusivo de educação, aliás
É o mesmo desenho que aparece quando você usa o Jev para priorizar leads no funil, trocando aluno por lead e exercício por próxima ação comercial
Problemas comuns na trilha adaptativa (e como prevenir)
Sintoma: a decisão cai quase sempre no mesmo exercício, independente do aluno
Causa: criteria com opções sem descrição que as diferencie de verdade
Se as cinco opções são só ids soltos, o modelo não tem como saber o que cada uma cobre
Solução e prevenção: descreva cada opção com o que ela cobre, o que ela NÃO cobre e um exemplo
A descrição de opção aceita string, objeto ou array justamente pra isso
Sintoma: confidence cronicamente baixa, em quase toda chamada
Causa: state pobre (histórico curto demais) ou catálogo com opções sobrepostas, onde dois exercícios cobrem quase a mesma coisa
Solução e prevenção: enriqueça o state com padrões, não só resultados (tempo por questão, tipo de erro recorrente) e faça uma faxina no catálogo, fundindo ou diferenciando exercícios irmãos
Sintoma: o app sugere um exercício que nem existe mais no currículo
Causa: catálogo desatualizado no criteria
A saída do Jev fica limitada ao conjunto tipado que você informou (é por isso que a TypeSafe descreve o modelo como incapaz de alucinar), então se um id morto está no criteria, ele é uma resposta perfeitamente válida pro modelo
Solução e prevenção: gere o criteria a partir da mesma fonte de verdade que o app usa pra servir exercício, nunca de uma constante copiada à mão
Sintoma: comportamento estranho em casos de borda (aluno sem histórico, tópico com uma opção só)
Causa: limitação conhecida da versão do modelo
Solução e prevenção: a documentação mantém uma página de limitações da versão atual, a Jev 1.13 jaggedness, com pontos que devem ser corrigidos em versões posteriores
Vale ler antes de culpar o teu código
E quando você precisar de estabilidade de comportamento entre deploys, fixe um ID versionado como jev-1.13.0 em vez do alias jev-latest
Tome cuidado com isso em produção: jev-latest é ótimo pra prototipar, mas uma trilha adaptativa que muda de opinião sozinha depois de um update de modelo gera chamado no suporte 😀
Vídeo: o contexto de automação por trás desse tipo de decisão
Decisão automatizada raramente vive sozinha, ela quase sempre está dentro de um fluxo maior
Pra pegar esse contexto de automação, se liga neste vídeo do canal sobre o n8n e pra onde essas ferramentas estão caminhando:
Próximo passo
Recapitulando a lógica toda, que é bem mais simples do que parece:
staterico, com o histórico de acertos e erros agrupado num bloco só, não o último resultado isolado- Choice com o catálogo completo no
criteria, cada opção descrevendo o que cobre e o que não cobre (cabem até 255 opções, e cada uma custa poucos tokens) - Leitura de
probabilitieseconfidence, que é o que te dá o dial entre reforço e avanço sem regra engessada
O próximo passo prático é rodar a chamada com um aluno real do teu banco, um que você já conhece bem, e comparar a sugestão do modelo com o que a tua regra atual entregaria hoje
Se bater, ótimo
Se divergir, olha o probabilities e pergunta: quem está errado, o modelo ou a regra? na minha cabeça é aí que mora a parte divertida
E se você quiser levar isso pro fluxo de desenvolvimento, a doc tem uma página específica sobre usar o Jev com agentes de programação
Até o próximo post!
Perguntas frequentes
Dá pra usar o Jev sem ter conta na TypeSafe agora?
Novos cadastros estão pausados desde 22 de setembro de 2026 por excesso de demanda, mas contas já criadas seguem funcionando normalmente. Se tu ainda não tem conta, dá pra acessar o Jev pelo Vercel AI Gateway (modelo typesafe-ai/jev, via experimental_evaluate) ou pelo OpenRouter, com os IDs ~typesafe/jev-latest e typesafe/jev-1.13.
Quantas opções de exercício cabem numa mesma pergunta Choice?
Uma pergunta Choice aceita até 255 opções. A documentação orienta passar o catálogo completo em vez de uma lista curta, já que cada opção custa poucos tokens de entrada.
O Jev pode sugerir um exercício que não existe no catálogo?
Não. Como ele responde dentro do conjunto tipado que você define em criteria, a saída fica limitada às opções informadas. É por aí que a própria TypeSafe, no site dela, descreve o modelo como incapaz de alucinar.
Quanto custa decidir o próximo exercício de cada aluno com o Jev?
A cobrança é só sobre tokens de entrada, US$ 0,042 por 1 milhão de tokens, e a saída é gratuita. Isso significa que o custo real depende de quanto histórico você manda no state, não da resposta que o Jev devolve.
Dá pra pedir dificuldade e próximo exercício na mesma chamada?
Sim, questions é um mapa e aceita várias perguntas ao mesmo tempo, todas avaliando o mesmo state de forma independente. Cada resposta volta sob o id que você escolheu, então dá pra ter uma Choice pro exercício e um Score pro nível de dificuldade na mesma requisição.
O SDK do Jev funciona em Node, ou só em Python?
Tem SDK oficial pros dois lados. Em Python é o typesafe-sdk, que exige Python 3.10 ou superior, e em JavaScript é o @typesafe-ai/sdk, então dá pra integrar direto num app de estudos feito em Node ou Next.
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.

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.
