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

Fluxo do Jev escolhendo o próximo exercício em um app de estudos
Resposta rápida

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
Curso Recomendado

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

  1. Modele o state com 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

  1. 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 😛

  1. 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

  1. 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

  1. 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 state há 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:

  • state rico, 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 probabilities e confidence, 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.




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