Como usar o Jev para saber se dois registros são a mesma pessoa ou o mesmo produto

Na deduplicação com Jev você não pede texto pro modelo: você manda os dois registros como estado e faz uma pergunta tipada. O Jev é o modelo de decisão da TypeSafe AI que devolve a opção escolhida, a probabilidade de cada opção e uma confiança de 0 a 1. Dá pra montar um Choice com mesmo, diferente, incerto e other, ler a confiança e usar ela como triagem: acima do corte entra automático, o resto vai pra revisão humana. Custa US$ 0,042 por 1 milhão de tokens de input, e output não é cobrado.
"Maria S. Oliveira" e "MARIA SOUZA OLIVEIRA" são a mesma pessoa?
Se tu já mexeu com base de cliente, base de produto, importação de planilha de parceiro, tu sabe que essa pergunta aparece MILHARES de vezes e que nenhum if resolve ela direito
O caminho clássico é similaridade de string: Levenshtein, Jaro-Winkler, fuzzy matching, um score de 0 a 1 e um corte arbitrário
Funciona até bater no primeiro apelido, na primeira razão social diferente do nome fantasia, no primeiro pai e filho com nome idêntico
Aí entra o Jev, o modelo de decisão tipada da TypeSafe AI, apresentado como o primeiro System One model: tu manda um estado e perguntas tipadas, e ele devolve decisões estruturadas com probabilidades, em vez de texto livre
Ou seja: ele não escreve "acredito que sim, pois os nomes são parecidos…"
Ele devolve a opção, a distribuição de probabilidade e um número de confiança que tu consegue usar como regra de negócio 😀
Bora montar isso na prática?
O que você precisa antes de começar
A lista é curta, prometo
- Conta no console da TypeSafe: em
console.typesafe.ai. O Jev saiu em 15/09/2026 com waitlist, mas em 20/09/2026 a TypeSafe removeu o waitlist, então o cadastro é imediato - Crédito: conta nova ganha US$ 5 de crédito grátis (a TypeSafe estima isso em cerca de 120 milhões de tokens), o que dá MUITO fôlego pra prototipar deduplicação
- Uma chave de API: no console, botão
Create key - A chave exportada na variável de ambiente
TYPESAFE_API_KEY - O SDK:
pip install typesafe-sdkno Python, ounpm install @typesafe-ai/sdkse tu for de Node (precisa de Node.js 20 ou mais novo)
Sobre versão: jev-1.13.0 é a única versão publicada, e os aliases jev-latest e jev-preview apontam pra ela
Tome cuidado com uma coisa antes de desenhar o projeto: o Jev aceita apenas texto
Sem imagem, sem áudio, sem vídeo
Então se teu caso de deduplicação depende de comparar foto de produto, essa parte vai ter que viver fora do Jev
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
Passo a passo: montando o comparador de registros
A ideia do desenho é simples: o par de registros é o ESTADO, e "é a mesma entidade?" é a pergunta
- Instale o SDK e configure a chave
pip install typesafe-sdk
export TYPESAFE_API_KEY="sua-chave-criada-no-console"
O erro comum deste passo: deixar a chave hardcoded no arquivo que vai pro repositório. O SDK lê de TYPESAFE_API_KEY, usa isso e dorme tranquilo
- Monte o estado como um dicionário com os dois registros nomeados
A doc aceita string, objeto JSON ou array de textos no state (em Python: str, dict ou list), e recomenda objeto quando cada parte do estado precisa de um nome descritivo
Deduplicação é exatamente esse caso: tu quer que fique óbvio o que é registro_a e o que é registro_b
from typesafe_sdk import TypeSafeClient, Choice
client = TypeSafeClient()
estado = {
"registro_a": {
"nome": "Maria S. Oliveira",
"email": "[email protected]",
"telefone": "11 98888-1234",
"cidade": "Sao Paulo",
},
"registro_b": {
"nome": "MARIA SOUZA OLIVEIRA",
"email": "[email protected]",
"telefone": "(11) 98888-1234",
"cidade": "São Paulo - SP",
},
}
O erro comum deste passo: mandar a linha inteira do banco, com created_at, updated_at, flag de newsletter, id interno, histórico de pedido e por aí vai
A doc é explícita: a acurácia cai conforme o estado cresce com conteúdo que a pergunta não precisa, então filtre ANTES de enviar
Estado enxuto não é economia de token, é qualidade de decisão
- Escreva a pergunta Choice, com instruções e critérios
A API do Jev tem três primitivas de pergunta: Choice (escolher entre opções), Score (nota em níveis ordenados) e Noul (pergunta de sim ou não)
Pra deduplicação, o Choice é o coração
perguntas = {
"mesma_entidade": Choice(
instructions=(
"Os dois registros descrevem a mesma pessoa do mundo real? "
"Considere variações de grafia, abreviação, nome social e "
"formatação de telefone. Responda 'incerto' quando as "
"evidencias forem insuficientes para decidir."
),
criteria={
"mesmo": None,
"diferente": None,
"incerto": None,
"other": None,
},
)
}
resposta = client.system_one(state=estado, questions=perguntas)
decisao = resposta.choices["mesma_entidade"]
print(decisao)
Repara no "other": None
A documentação recomenda incluir essa opção de escape justamente pro modelo poder dizer que nada se encaixa, em vez de escolher a opção errada mais próxima
O erro comum deste passo: esquecer o escape e obrigar o modelo a escolher entre "mesmo" e "diferente" num par que é lixo puro (registro truncado, campo vazio, nome de teste)
Detalhe pra quem vai longe: um Choice aceita até 255 opções, então dá pra fazer coisa bem mais granular que três caixinhas se o teu domínio pedir
- Leia a confiança e as probabilidades por opção
O Choice devolve a opção escolhida, uma probabilidade pra CADA opção e um valor de confiança de 0 a 1
E de onde vem essa confiança? Do formato da distribuição de probabilidades
Concentrada em uma opção significa confiança alta
Espalhada entre as opções significa confiança baixa
Isso é MUITO mais útil que um score de Levenshtein, porque a distribuição te diz não só a resposta, mas o quanto o modelo está em cima do muro
O erro comum deste passo: tratar a decisão como booleano e jogar a confiança fora. Ela é o ativo mais valioso do request, e daqui a pouco a gente usa ela como fila de revisão
- Rode em lote e olhe o custo
Com o comparador funcionando pra um par, o resto é loop
pares = carregar_pares_candidatos() # sua rotina de blocking/candidatos
for par in pares:
estado = {"registro_a": par.a, "registro_b": par.b}
resposta = client.system_one(state=estado, questions=perguntas)
salvar_decisao(par.id, resposta.choices["mesma_entidade"])
Sobre a conta: o Jev cobra US$ 0,042 por 1 milhão de tokens de input, e tokens de output não são cobrados
Como o output aqui é uma decisão estruturada e o input é um par de registros curtinho, o custo por comparação fica ridículo de baixo
Ainda assim, antes de subir isso pra produção vale montar o teu painel de acerto, custo por decisão e tempo de ciclo, porque sem número tu não sabe se trocou uma heurística ruim por uma cara
O erro comum deste passo: mandar TODO par contra TODO par. Deduplicação séria continua precisando de uma etapa de candidatos antes (bloqueio por email, por domínio, por prefixo de nome), senão o volume explode sozinho
Enriquecendo a decisão: Noul e Score no mesmo request
Agora a parte que muda o jogo do projeto
Todas as perguntas de um mesmo request veem o MESMO estado e são avaliadas em paralelo, então acrescentar perguntas quase não altera o tempo de resposta
Ou seja: se tu já pagou pra mandar o par de registros, aproveita e pergunta tudo que tu quer saber sobre aquele par
- Adicione uma Noul pra checar um sinal forte e objetivo
Noul é a pergunta de sim ou não, e ela devolve o campo .noul, um número de 0 a 1 com a probabilidade de a resposta ser sim
É perfeita pra "os documentos batem?", "os telefones são o mesmo número em formatos diferentes?"
- Adicione um Score pra graduar a força da evidência
O Score devolve um score numérico, uma legenda ligando score a descrição, probabilidades por nível e um valor de confiança
Dá pra usar como "força da evidência de que é a mesma entidade", em níveis que o teu time de dados entenda
- Mande as três no mesmo
questions
from typesafe_sdk import TypeSafeClient, Choice, Noul, Score
perguntas = {
"mesma_entidade": Choice(
instructions="Os dois registros descrevem a mesma pessoa real?",
criteria={
"mesmo": None,
"diferente": None,
"incerto": None,
"other": None,
},
),
"documentos_batem": Noul(
instructions="Os documentos informados nos dois registros sao o mesmo documento?",
),
"forca_da_evidencia": Score(
instructions="Qual a forca da evidencia de que os registros sao a mesma entidade?",
),
}
resposta = client.system_one(state=estado, questions=perguntas)
O erro comum deste passo: quebrar isso em três chamadas separadas
Tu paga o estado três vezes, multiplica a latência por três e ainda corre o risco das respostas olharem contextos diferentes
Um estado, várias perguntas, um request
Esse mesmo desenho de várias perguntas tipadas sobre um estado só é o que sustenta outros usos de decisão, tipo pontuar e priorizar leads no funil, então vale internalizar o padrão
Jev x comparação por similaridade de string: qual usar em cada caso
E não, a resposta não é "joga fora o Levenshtein"
A comparação por string continua ganhando em um monte de cenário, e ignorar isso é queimar dinheiro à toa
| Critério | Jev (decisão tipada) | Similaridade de string (Levenshtein, Jaro-Winkler, fuzzy) |
|---|---|---|
| Tipo de saída | Opção escolhida, probabilidade por opção e confiança de 0 a 1 | Score bruto de distância, sem nenhuma noção de significado |
| Abreviação e apelido | Entende que a forma curta e a longa podem ser a mesma entidade | Enxerga só caracteres diferentes e derruba o score |
| Produto igual com SKU e título diferentes | Consegue tratar como pergunta semântica sobre o mesmo item | Título de marketplace diferente vira distância alta na hora |
| Custo por comparação | US$ 0,042 por 1 milhão de tokens de input, output não cobrado | Praticamente zero, roda na tua própria CPU |
| Latência | 70 a 500 ms por request, segundo a TypeSafe | Microssegundos, local, sem rede |
| Determinismo | Saída probabilística, mesma entrada pode variar na margem | Determinístico, mesma entrada sempre dá o mesmo número |
| Auditoria | Distribuição e confiança explicam o quanto ele hesitou | Fácil de reproduzir, mas o número não explica o porquê |
| Volume trivial (duplicata exata, mesmo email) | Overkill, não precisa chamar modelo | Ganha com folga, é o lugar dele |
Moral da história: regra pra caso trivial, Jev pra caso ambíguo
Normaliza, bloqueia, mata as duplicatas óbvias com string, e manda pro Jev só a zona cinzenta que sobrou
É nela que o projeto inteiro costuma empacar
Quando a decisão probabilística resolve o que a regra não resolve
Se liga nos casos que fazem o pessoal de dados perder o cabelo
Nome social e nome de registro: a pessoa se cadastrou com um nome no e-commerce e o nome do documento é outro. A distância de string entre os dois é enorme, mas é a MESMA pessoa
Razão social x nome fantasia: "Comercio de Alimentos Silva LTDA" e "Padaria do Silva". Nenhum algoritmo de caractere vai casar isso, e qualquer humano casa em um segundo
Produto igual com títulos de marketplace diferentes: "Fone Bluetooth XZ100 Preto Original NF" e "XZ-100 Headphone sem fio preto". Mesmo item, títulos escritos por vendedores diferentes, SKU diferente
Endereço abreviado: "R. Gen. Osório, 45, apto 12" e "Rua General Osorio 45 ap 12". Aqui a regra até sobrevive se tu escrever normalizador pra cada abreviação do Brasil inteiro… boa sorte nessa 😛
E tem o caso INVERSO, que é o mais perigoso de todos
Strings quase idênticas que são pessoas diferentes: pai e filho com o mesmo nome no mesmo endereço, duas lojas da mesma rede na mesma rua, dois produtos da mesma linha que mudam só a voltagem
A similaridade de string vai cravar 0.97 e juntar os dois registros com a maior confiança do mundo
E aí tu fundiu a conta do pai com a do filho, o que é bem pior que ter duplicata 😀
Por que a distância de string não separa esses casos? Porque ela mede FORMA, não IDENTIDADE
Ela não sabe que "Jr" é um marcador que separa pessoas, nem que "220V" separa produtos, nem que "Padaria do Silva" é como a empresa é conhecida
A decisão probabilística funciona aqui porque ela avalia as evidências do par inteiro e ainda te devolve o quanto ela está segura disso
Usando a confiança como fila de revisão humana
Essa é a parte que transforma o experimento em processo de verdade
- Guarde a confiança de cada decisão, junto com a opção escolhida e as probabilidades por opção
- Defina um corte de aceite automático: acima do limiar, a fusão dos registros acontece sem humano no meio
- Mande o resto pra revisão: tudo abaixo do corte, mais tudo que caiu em
incertoouother, vira fila pra uma pessoa bater o olho
- Calibre o limiar contra uma amostra rotulada por humano
Esse passo NÃO é opcional
A ideia de probabilidade calibrada é que o número reportado tende a corresponder à frequência real de acerto, e é exatamente isso que permite usar a confiança como sinal de roteamento
Mas o corte certo (0.8? 0.9? 0.95?) depende do teu domínio e do teu custo de errar, e só uma amostra rotulada te diz onde ele fica
- Use a hesitação a teu favor: lembra que a confiança sai do formato da distribuição, concentrada é alta e espalhada é baixa
Quando a probabilidade está rachada entre "mesmo" e "diferente", isso não é defeito do modelo
É ele te avisando que aquele par é genuinamente ambíguo, e que nenhum humano decidiria com os campos que tu mandou
O erro comum deste passo: fixar o limiar no chute e nunca mais revisar. Base muda, fonte de dados nova entra, e o corte que era bom envelhece
Problemas comuns na deduplicação com o Jev
Os perrengues que aparecem na primeira semana
A acurácia começa boa e vai piorando conforme o projeto cresce
Causa: o estado foi engordando. Alguém adicionou mais um campo, depois o objeto inteiro do ERP, depois o log
Solução: cortar o estado de volta pro mínimo que a pergunta usa
Como prevenir: tenha uma função montar_estado(registro) que seleciona campos explicitamente, em vez de serializar o objeto do banco direto
Estouro do limite de contexto
Sintoma: request falhando em par de registros gigante (descrição longa de produto, histórico colado no campo observação)
Causa: o limite é de 64 mil tokens por request (estado mais perguntas), dos quais 32 mil ficam disponíveis pro estado mais a maior pergunta
Solução: truncar campo de texto livre e mandar só o trecho que importa pra comparação
Como prevenir: medir o tamanho do estado antes de enviar e ter um corte no pipeline
O modelo sempre escolhe a opção mais próxima, mesmo quando o par é lixo
Causa: faltou a opção de escape
Solução: colocar "other": None no dicionário de criteria, como a própria doc recomenda
Como prevenir: adotar isso como padrão de todo Choice que tu escrever, não só nos de deduplicação
Rate limit batendo no meio do lote
Causa: lote grande disparado sem controle de concorrência
Solução: os limites publicados são 250 mil tokens por segundo e 1.200 requests por minuto, então dimensione teu worker abaixo disso e trate retry
Como prevenir: a doc avisa que esses limites são ajustados dinamicamente e NÃO devem ser tratados como garantia, então teu código precisa lidar com erro de limite de qualquer jeito
Confundir "não alucina" com "está certo"
Esse aqui é o mais importante de todos, e o mais fácil de escorregar
Causa: a garantia do Jev é sobre FORMATO, não sobre verdade. A saída fica restrita ao schema definido, ele não devolve uma opção fora da lista que tu passou
Mas a resposta ainda pode estar errada
Solução: continuar tratando a decisão como decisão de modelo, com amostragem de qualidade e revisão humana na faixa de baixa confiança
Como prevenir: não vender internamente o projeto como "a IA que não erra". Conformidade de tipo garantida, correção factual não. Já me ferrei uma vez por prometer certeza demais pro time de negócio, não repita 😅
Próximo passo
O desenho mínimo que funciona é esse, e ele cabe numa linha:
estado enxuto com os dois registros, um Choice com opção de escape, e a confiança virando triagem
O resto (Noul pra sinal objetivo, Score pra graduar evidência) tu pendura no mesmo request sem quase mexer no tempo de resposta
Seu próximo passo é o menos glamouroso e o mais decisivo: pegar umas centenas de pares, rotular na mão, rodar o comparador em cima deles e descobrir ONDE fica teu limiar de aceite automático
Sem isso, tu tem um brinquedo bonito e nenhum processo
E se tua stack já vive na borda, vale saber que o Jev também está no catálogo de modelos da Workers AI da Cloudflare, como typesafe/jev
Bora deduplicar aquela base que tu finge que não existe? 😀
Até o próximo post!
Matheus Battisti
Perguntas frequentes
O Jev também serve pra deduplicar produto, ou só funciona com dados de pessoa?
Serve pra qualquer entidade, porque o Jev não sabe que é pessoa ou produto: ele só vê o estado que tu manda e a pergunta tipada. Troca registro_a e registro_b por dois SKUs, título, categoria e atributo, e o mesmo Choice com mesmo, diferente, incerto e other funciona igual. O que muda é só o que entra no dicionário de estado.
Quanto custa comparar um par de registros com o Jev?
O Jev cobra US$ 0,042 por 1 milhão de tokens de input, e tokens de output não são cobrados. Um par de registro curto usa uma fração minúscula de 1 milhão de tokens, então o custo por comparação fica bem abaixo de um centavo. Conta nova ganha US$ 5 de crédito grátis, o que a TypeSafe estima em cerca de 120 milhões de tokens, ou seja, dá pra rodar um lote gigante de teste antes de gastar de verdade.
Dá pra misturar Choice com Score ou Noul no mesmo request de deduplicação?
Dá sim. As três primitivas (Choice, Score e Noul) podem ser combinadas no mesmo request e todas veem o mesmo estado. Então tu pode, por exemplo, perguntar mesma_entidade com Choice e ao mesmo tempo um Noul tipo precisa_revisao_humana usando o mesmo par de registro, sem pagar latência extra por isso.
O Jev garante que a decisão de deduplicação está correta?
Não. A garantia do Jev é de formato: a resposta sempre respeita o schema que tu definiu, então ele nunca devolve uma opção fora da lista de criteria. Mas isso é conformidade de tipo, não correção factual, ou seja, a opção escolhida ainda pode estar errada, e por isso a confiança existe como sinal de triagem.
Quantos pares de registro cabem em um único request?
O limite de contexto é 64 mil tokens por request somando estado e perguntas, com até 32 mil tokens disponíveis pra estado mais a maior pergunta. Pra deduplicação isso raramente é problema, porque um par de registro filtrado (sem campo que a pergunta não usa) costuma ser bem pequeno. O gargalo real de acurácia não é limite de token, é mandar estado inchado.
O Jev roda só pelo console da TypeSafe ou tem outra forma de acessar?
Além do console em console.typesafe.ai, o Jev também está disponível como modelo no catálogo da Workers AI da Cloudflare, listado como typesafe/jev. A latência divulgada pela TypeSafe fica entre 70 e 500 ms por request, com rate limit de 250.000 tokens por segundo e 1.200 requests por minuto, ajustados dinamicamente.
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.
