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

fluxo de decisão do Jev deduplicação comparando dois registros para saber se são a mesma pessoa
Resposta rápida

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-sdk no Python, ou npm install @typesafe-ai/sdk se 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
Curso Recomendado

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

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

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

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

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

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

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

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

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

  1. Guarde a confiança de cada decisão, junto com a opção escolhida e as probabilidades por opção
  1. Defina um corte de aceite automático: acima do limiar, a fusão dos registros acontece sem humano no meio
  1. Mande o resto pra revisão: tudo abaixo do corte, mais tudo que caiu em incerto ou other, vira fila pra uma pessoa bater o olho
  1. 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

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



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