Como usar o Jev como cérebro de um NPC no seu jogo

diagrama mostrando o Jev como cérebro de NPC recebendo estado do jogo e devolvendo decisões
Resposta rápida

O Jev é o primeiro modelo da classe System One da TypeSafe AI: você manda um estado e perguntas tipadas, ele devolve decisões com probabilidades, sem gerar texto livre. Pra usar o Jev como cérebro de NPC o padrão é direto: serializa o estado do jogo (vida, posição, inimigos próximos, munição) no campo state, define a lista fechada de ações numa pergunta Choice, chama POST https://api.typesafe.ai/v1/systemone com model jev-latest e executa o valor que vier em choice. O campo confidence vira o interruptor do fallback determinístico quando a distribuição sai espalhada demais 🙂

Fala aí, beleza? NPC bom não precisa falar bonito, ele precisa escolher a ação certa no momento certo

E é justamente aí que a maioria dos projetos se enrola: joga o estado do jogo num LLM de geração, pede pra ele "responder só com o nome da ação" e reza pra não vir um parágrafo educado no meio do combate

Aí você escreve parser, regex, retry, fallback pra quando a IA inventa uma ação que não existe…

O Jev nasceu justamente pra esse buraco. Ele é o primeiro modelo da classe System One da TypeSafe AI: recebe um estado e perguntas tipadas, devolve decisões com probabilidades e não tem cabeça de geração de texto. É modelo de decisão, ponto

A ideia deste post é essa: encaixar o Jev como camada de decisão tipada dentro do seu game loop, com a lista de ações fechada ANTES da chamada e uma probabilidade pra cada uma

Bora ver na prática?

O que você precisa antes de começar

Antes de sair codando, se liga no checklist:

  • Conta na TypeSafe e uma chave de API, que sai do console em console.typesafe.ai
  • SDK Python (pip install typesafe-sdk ou uv add typesafe-sdk, exige Python >= 3.10, instala com hífen e importa com underscore, te explico isso lá no passo 3) ou o SDK JavaScript/TypeScript, pacote @typesafe-ai/sdk, que vem com ESM, CommonJS e declarações TS
  • A variável de ambiente TYPESAFE_API_KEY, que o client Python lê sozinho do ambiente
  • Um jogo cujo loop consiga expor o estado em JSON (ou pelo menos montar um dicionário com os campos que importam)

Um aviso honesto sobre acesso: o Jev foi lançado em 15/09/2026 com lista de espera, abriu pra todos em 20/09/2026 (com US$ 5 de crédito em contas novas) e em 22/09/2026 a TypeSafe pausou temporariamente novos cadastros por excesso de demanda, dizendo que trabalha pra reabrir

Contas já criadas seguem funcionando normalmente. Se você ainda não tem a sua, pode ser que precise esperar a reabertura

Tome cuidado com isso aqui: a API do Jev não é compatível com o formato OpenAI

Não existe /chat/completions, não existe array messages, não existe temperature. Apontar um cliente OpenAI pro endpoint da TypeSafe simplesmente não funciona, então nem tente reaproveitar aquele wrapper que você já tinha 😛

Domine o Jev e coloque decisões de IA dentro do seu sistema
Pré-inscrição Curso Jev

Domine o Jev e coloque decisões de IA dentro do seu sistema

Você vai aprender a usar o Jev, o System One Model da TypeSafe AI, pra automatizar decisões com resposta tipada e confiança medida, sem depender de chat nem de alguém revisando cada passo. Entre na lista de espera para garantir a condição de lançamento!

Passo a passo: do estado do jogo à ação do NPC

  1. Modele o state do NPC

O campo state é o conteúdo a avaliar, e ele aceita string, objeto JSON ou array com contexto relacionado. Ou seja: você não precisa transformar seu mundo numa frase em português, pode mandar estrutura de dados mesmo

{
  "npc": {
    "id": "guarda_03",
    "vida": 34,
    "vida_max": 100,
    "municao": 2,
    "posicao": { "x": 12.5, "y": 0, "z": -4.2 },
    "cobertura_proxima": true
  },
  "inimigos_proximos": [
    { "tipo": "jogador", "distancia": 6.1, "visivel": true, "arma": "shotgun" },
    { "tipo": "drone", "distancia": 14.8, "visivel": false, "arma": null }
  ],
  "aliados_vivos": 1,
  "ultima_acao": "atirar"
}

Repare que eu mandei só o que muda a decisão: vida, munição, distância, visibilidade, cobertura

O erro comum deste passo: despejar o dump inteiro do mundo dentro do state. Além de estourar o orçamento de tokens (falo disso lá embaixo), estado demais dilui o sinal e a distribuição de probabilidades sai mais espalhada

  1. Defina a lista fechada de ações como uma pergunta Choice

Aqui mora o pulo do gato. A primitiva Choice escolhe UMA opção de um conjunto que você definiu antes, então o NPC nunca vai "inventar" uma ação que seu código não sabe executar

O formato tem type igual a "choice", um campo instructions e um campo criteria, que é um mapa de opção para descrição (e aceita null quando a opção dispensa detalhe)

{
  "type": "choice",
  "instructions": "Escolha a próxima ação tática deste NPC guarda, considerando vida, munição, cobertura e os inimigos próximos.",
  "criteria": {
    "atacar": "Avançar e atirar no inimigo visível mais próximo. Exige munição e vida em nível seguro.",
    "buscar_cobertura": "Mover-se para a cobertura próxima e interromper o fogo. Indicado com vida baixa ou sob fogo direto.",
    "recarregar": "Recarregar a arma. Indicado quando a munição está baixa e não há inimigo a curta distância.",
    "pedir_ajuda": "Chamar aliados. Indicado quando está em desvantagem numérica e há aliado vivo.",
    "patrulhar": null
  }
}

O limite é generoso: máximo de 255 opções por Choice. Na prática, se o seu NPC tem 200 ações possíveis, o problema não é o Jev, é o seu design 😀

O erro comum deste passo: opções que se sobrepõem. Se atacar e avancar_atirando significam quase a mesma coisa, a probabilidade se divide entre as duas, o confidence cai e você acha que o modelo ficou burro. Descrição de critério vaga dá no mesmo

  1. Monte a requisição

A API System One tem um único endpoint HTTP: POST https://api.typesafe.ai/v1/systemone, com os headers Authorization (Bearer) e Content-Type: application/json

O corpo tem exatamente três campos no nível raiz: state, model e questions (um mapa de id da pergunta para o objeto da pergunta). O alias do modelo é jev-latest

curl -X POST https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "state": { "npc": { "vida": 34, "municao": 2 }, "inimigos_proximos": [] },
    "model": "jev-latest",
    "questions": {
      "acao": {
        "type": "choice",
        "instructions": "Escolha a próxima ação tática deste NPC guarda.",
        "criteria": {
          "atacar": "Avançar e atirar no inimigo visível mais próximo.",
          "buscar_cobertura": "Mover-se para a cobertura e interromper o fogo.",
          "recarregar": "Recarregar a arma quando a munição está baixa.",
          "patrulhar": null
        }
      }
    }
  }'

Em Python, o mesmo payload montado na mão fica assim:

import os
import json
import requests

URL = "https://api.typesafe.ai/v1/systemone"

def decidir(state, questions):
    payload = {
        "state": state,
        "model": "jev-latest",
        "questions": questions,
    }
    resp = requests.post(
        URL,
        headers={
            "Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}",
            "Content-Type": "application/json",
        },
        data=json.dumps(payload),
    )
    resp.raise_for_status()
    return resp.json()

Se preferir o SDK oficial, o caminho é importar Choice, Noul, Score e TypeSafeClient de typesafe_sdk. O client lê TYPESAFE_API_KEY do ambiente, chama jev-latest por padrão e a chamada sai por client.system_one(...)

E calma, não são dois pacotes diferentes: você instala como typesafe-sdk (com hífen) e importa como typesafe_sdk (com underscore). É o mesmo bicho, só a convenção normal do Python 😀

O erro comum deste passo: insistir no vício de LLM. Nada de messages, nada de temperature, nada de system prompt. O contexto todo vive no state e a intenção vive no instructions de cada pergunta

  1. Leia a resposta e execute a ação

A resposta da Choice traz três coisas: choice (a opção escolhida), probabilities (uma probabilidade por opção) e confidence (número de 0 a 1)

# r = objeto de resposta da pergunta "acao", conforme o retorno da API
acao_escolhida = r["choice"]
probs = r["probabilities"]
conf = r["confidence"]

EXECUTORES = {
    "atacar": npc.atacar,
    "buscar_cobertura": npc.buscar_cobertura,
    "recarregar": npc.recarregar,
    "pedir_ajuda": npc.pedir_ajuda,
    "patrulhar": npc.patrulhar,
}

EXECUTORES[acao_escolhida]()

E olha que massa: como a lista é fechada, esse dicionário de executores NUNCA vai dar KeyError por ação inventada

O probabilities também abre uma porta divertida pra jogo: em vez de sempre pegar o choice, você pode sortear a ação usando as probabilidades como peso, e aí o NPC deixa de ser previsível sem virar aleatório burro

O erro comum deste passo: tratar o retorno como verdade absoluta e jogar fora o probabilities. Essa distribuição é o material mais rico que você tem pra tunar comportamento depois

  1. Aplique um limiar de confidence com fallback determinístico

O confidence é calculado pelo formato da distribuição: concentrada numa opção significa confiança alta, espalhada significa confiança baixa. Ele existe exatamente pra você aplicar limiar no código

LIMIAR = 0.7

if conf >= LIMIAR:
    EXECUTORES[acao_escolhida]()
else:
    # árvore de comportamento clássica assume o volante
    EXECUTORES[fallback_behavior_tree(npc, mundo)]()

O fallback_behavior_tree aí não tem IA nenhuma, e é esse o ponto: é a sua regra determinística de sempre (o if/else ou a árvore de comportamento que você já tinha), devolvendo o nome de uma ação da MESMA lista fechada do Choice

Tipo: vida baixa e cobertura por perto, vai de buscar_cobertura; sem munição e nenhum inimigo colado, recarregar; nada disso, patrulhar

Como o retorno do fallback vive no mesmo dicionário EXECUTORES, o resto do seu loop nem fica sabendo quem decidiu

Ou seja: a IA decide quando tem opinião formada, e a sua máquina de estados de sempre decide quando o cenário está ambíguo. O melhor dos dois mundos

O erro comum deste passo: chutar o limiar antes de olhar dado. Logue confidence e probabilities em arquivo por algumas sessões e escolha o número olhando a distribuição real do SEU jogo

  1. Adicione perguntas auxiliares na mesma requisição

O questions é um mapa, então você manda várias perguntas de uma vez sobre o mesmo state. Além da Choice da ação, existem mais duas primitivas:

  • Score: nota em níveis ordenados. O criteria é um array ordenado de descrições de nível, do extremo baixo pro alto, e a resposta traz score (que pode até cair entre dois níveis), probabilities por nível e confidence. Perfeito pra um medidor de agressividade. O mínimo é 2 níveis e a API aceita até 10
  • Noul: sim/não. A resposta é um número entre 0 e 1 com a probabilidade de "sim", e o criteria é opcional, trazendo descrições pra true e false. Serve redondo pra "esse NPC deve recuar?"
agressividade = r_score["score"]       # nível ordenado
deve_recuar = r_noul                   # probabilidade de "sim", de 0 a 1

if deve_recuar > 0.8:
    npc.recuar()
else:
    npc.velocidade = base * fator(agressividade)

Agora o detalhe que MUITA gente erra: todas as perguntas da mesma requisição são avaliadas em paralelo e isoladamente contra o mesmo state

O resultado de uma pergunta não vira contexto escondido que altera outra. Não existe "se o Noul der true, então a Choice vai considerar isso"

O erro comum deste passo: escrever instructions encadeando perguntas ("caso a resposta anterior seja sim…"). Não funciona. Se você precisa de encadeamento, ele é seu: lê a primeira resposta, decide no código e faz uma segunda chamada

  1. Encaixe a chamada no game loop sem travar o frame

Chamada de rede dentro do frame é receita de stutter. Nenhum modelo do mundo salva isso

O padrão que funciona é desacoplar a decisão do render:

  • Chame em eventos (o jogador entrou no campo de visão, o NPC levou dano, acabou a munição) ou num tick lento (a cada X segundos por NPC)
  • Guarde a última decisão em cache e execute ela enquanto a próxima não chega
  • Use o cliente assíncrono: o SDK Python expõe AsyncTypeSafeClient justamente pra isso
import asyncio

async def cerebro_do_npc(npc, mundo, intervalo=1.5):
    while npc.vivo:
        estado = serializar_estado(npc, mundo)
        resposta = await pedir_decisao(estado)
        npc.decisao_atual = resposta
        await asyncio.sleep(intervalo)

O erro comum deste passo: chamar a API todo frame pra cada NPC da cena. Além de derrubar o FPS, você multiplica custo por 60 sem ganhar comportamento nenhum, porque o estado do jogo mal mudou entre um frame e outro

Além do combate: outros usos do Jev em NPCs

Combate é o exemplo óbvio, mas o padrão "estado dentro, decisão tipada fora" vale pra praticamente qualquer NPC:

  • Comerciante decidindo aceitar uma troca: pergunta Noul, com criteria descrevendo o que é um negócio bom e um negócio ruim pra ele. A resposta vem como probabilidade de "sim", então você pode até usar o valor como margem de negociação em vez de um corte seco
  • Guarda escalando suspeita: pergunta Score, com os níveis ordenados do "nada notado" até o "alarme geral". Como o score pode cair entre dois níveis, dá pra animar a transição em vez de pular de estado de forma seca
  • Diretor de dificuldade escolhendo o próximo evento: uma Choice com o estado da sessão (tempo jogado, mortes, recursos do jogador) e opções tipo emboscada, item, descanso ou evento narrativo

Esse mesmo desenho de Choice com criteria bem escrito aparece em contextos completamente diferentes de jogo, tipo qualificar leads com o Jev, e vale a leitura pra ver o padrão fora do game loop

E se a ação precisar de argumentos?

Boa pergunta. "Atacar" é fácil, mas e "mover para (x, z)" ou "usar item Y"?

Existe um cookbook oficial de function calling da TypeSafe que resolve exatamente isso: uma Choice escolhe a função, uma descrição por função entra nos critérios, e perguntas extras cuidam dos argumentos, uma pergunta por argumento

Como tudo é avaliado em paralelo, a resposta de todos os argumentos volta junto. Aí o seu código lê apenas as respostas da função escolhida e ignora o resto

É um desperdício pequeno de tokens em troca de uma chamada só. Pra NPC, vale demais

Vídeo: IA lendo seu projeto por dentro

Pra começar do zero com IA analisando código e estrutura de projeto, este vídeo do canal mostra uma skill que faz a IA ler seu código e desenhar a arquitetura sozinha, com o Archify

Custo, limites e alternativas de hospedagem

Orçamento de tokens:

São dois números que você precisa ter na cabeça: 64k cobre o state mais todas as perguntas somadas, e 32k se aplica ao state mais a pergunta mais longa

Na prática isso é MUITA folga pro estado de um NPC, mas é o motivo pra você não mandar o mundo inteiro serializado. Mande o recorte que importa pra decisão

Preço:

O Jev custa US$ 0,042 por 1 milhão de tokens de input, e o output não é cobrado

Como o custo mora no input, o que define sua conta é o tamanho do state vezes o número de chamadas. Estado enxuto e tick lento por NPC fazem mais diferença no boleto do que qualquer micro otimização de prompt

Limites de taxa:

Os rate limits do Jev estão sendo ajustados dinamicamente e podem mudar sem aviso enquanto a demanda segue alta (planos custom/enterprise têm limites maiores)

Traduzindo pro seu jogo: trate erro de limite como caso normal, não como exceção. O fallback determinístico do passo 5 já te cobre aqui também

Onde rodar:

Além da API direta da TypeSafe, o Jev está disponível em mais dois lugares:

  • Vercel AI Gateway, com o model id typesafe-ai/jev, exposto no AI SDK 7 pela API experimental_evaluate
  • Cloudflare Workers AI, via POST https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run, com model typesafe/jev e um input contendo state e questions

Se o seu backend de jogo já vive num desses dois, é caminho natural

E aqueles números gigantes de performance?

Aqui vale o cuidado de sempre com número divulgado por fabricante:

Métrica Alegação da própria TypeSafe Teste independente
Velocidade 193,6x mais rápido 2,0x a 3,6x mais rápido na mediana
Custo 444,6x mais barato 4,7x a 7,5x mais barato que os dois modelos pequenos mais baratos

Os 193,6x e 444,6x são claim self-reported da TypeSafe em avaliações de workflow, comparando com modelos de fronteira

O teste independente mediu vantagem bem menor. Continua sendo vantagem, e boa, só não é mágica

Um detalhe técnico que explica por que as probabilidades servem de verdade pra tomar decisão: o Jev é treinado com RLCD (Reinforcement Learning for Calibrated Decisions), abordagem da própria TypeSafe que calibra as probabilidades pra refletirem a taxa real de acerto

Conclusão

O padrão é esse e ele cabe em uma linha: estado dentro, decisão tipada fora, confidence como interruptor do fallback

Você deixa de parsear texto livre, deixa de tratar ação inventada e passa a trabalhar com uma lista fechada de opções e uma distribuição de probabilidade em cima delas

Meu próximo passo sugerido pra você: não comece com dez NPCs

Começa com um NPC só e uma única Choice de 3 ou 4 ações, loga probabilities e confidence num arquivo por algumas sessões e ajusta os criteria até a distribuição fazer sentido com o que você esperava

Só depois disso expande pra Score, Noul e argumentos

E se o volume de decisões do seu jogo crescer muito, vale olhar a comparação entre Jev e um classificador próprio antes de escalar

Até o próximo post! 😀

Perguntas frequentes

O Jev serve pra controlar NPC sem gerar texto livre?

Serve, e é exatamente pra isso que ele foi pensado. O Jev é modelo de decisão da classe System One: recebe o state do NPC e perguntas tipadas (Choice, Score ou Noul) e devolve escolha com probabilidade, sem parágrafo educado no meio do combate.

Quantas ações um NPC pode ter numa pergunta Choice do Jev?

O limite é de até 255 opções por Choice, o que cobre praticamente qualquer árvore de decisão de NPC. Se o seu personagem tem 200 ações possíveis o Jev aguenta, o gargalo ali costuma ser o design do próprio jogo.

Dá pra usar um cliente OpenAI pra chamar o Jev no meu NPC?

Não dá. A API do Jev não é compatível com o formato OpenAI: não existe /chat/completions, array messages nem temperature. É preciso usar o endpoint próprio da TypeSafe ou o SDK oficial.

Quanto custa usar o Jev pra decisão de NPC em tempo real?

O input custa US$ 0,042 por 1 milhão de tokens, e o output não é cobrado. Como o state de um NPC costuma ser pequeno e o retorno é só a decisão, o custo por chamada tende a ser baixo.

Ainda dá pra criar conta nova no Jev pra testar em um NPC?

Depende do momento. Em 22/09/2026 a TypeSafe pausou temporariamente novos cadastros por excesso de demanda, mas contas já criadas continuam funcionando normalmente enquanto o acesso não reabre.

Como usar o confidence do Jev pra decidir se confio na ação do NPC?

O confidence é um número de 0 a 1 calculado a partir do formato da distribuição de probabilidades. Distribuição concentrada numa opção indica confiança alta, distribuição espalhada indica confiança baixa, então dá pra aplicar um limiar no código antes de executar a ação escolhida.




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