Como usar o Jev para ordenar os resultados da busca interna do seu produto?

Busca interna boa é feita em duas etapas: o buscador recupera candidatos, o ranking decide a ordem. O Jev, o System One Model da TypeSafe AI, entra na segunda: você pergunta com uma primitiva Score (array ordenado de níveis, até 10) ou Choice (até 255 opções) e recebe uma decisão tipada com probabilities e confidence de 0 a 1. Normaliza com score / (len(criteria) - 1), combina as dimensões com pesos no seu código e ordena. Todas as perguntas do mesmo state vão numa requisição só, porque a cobrança é por token de input (US$ 0,042 por 1M, saída US$ 0,00).
Sabe aquela busca interna que devolve 40 resultados e o item certo aparece na posição 7?
O buscador não está quebrado, ele só está fazendo o trabalho dele: casar palavra-chave, filtrar, devolver candidatos
Quem decide ordem é outra etapa, e é justamente essa etapa que quase ninguém monta
O Jev é o primeiro modelo público da TypeSafe AI e foi apresentado como um "System One Model": em vez de devolver prosa, ele devolve decisão tipada com probabilidades calibradas
Traduzindo pro nosso problema: dá pra pedir uma nota de relevância item a item e receber um número utilizável, não um parágrafo que você vai ter que parsear na raça
Neste post a gente monta o fluxo inteiro: shortlist, pergunta, normalização, pesos e corte, com o ranking final montado no SEU código 🙂
O que você precisa antes de começar
A parte de ambiente é curta:
- Python >= 3.10
- SDK instalado com
uv add typesafe-sdkoupip install typesafe-sdk - a variável
TYPESAFE_API_KEYno ambiente, que é de onde o cliente do SDK lê a chave
Se tu não quer SDK, a API é um POST direto em https://api.typesafe.ai/v1/systemone, com header Authorization Bearer e Content-Type: application/json
O cliente chama jev-latest por padrão
Esse alias aponta hoje pra versão atual, a jev-1.13.0 (o jev-preview aponta pra mesma coisa), e aqui vai o primeiro tome cuidado do post: jev-latest se move quando sai versão nova, o que pode mudar as respostas
Pra brincar em dev, beleza
Pra ranking em produção, fixa a versão
E o pré-requisito de produto?
Esse é o que mais gente esquece: o Jev não substitui o seu buscador
Curso de Jev: Sistemas Mais Inteligentes, Rápidos e Robustos
Desenvolva sistemas mais inteligentes, rápidos e robustos com Jev
Você precisa já ter um retriever devolvendo uma shortlist, seja BM25, busca vetorial, Elastic, Postgres full text, o que for
O modelo entra depois, pontuando os candidatos que o retriever trouxe
Recuperar é uma coisa, ordenar é outra, e misturar as duas é o motivo de metade das buscas internas ruins por aí
Passo a passo: ranking da busca interna com o Jev
- Gere a shortlist com o buscador que você já tem
Não manda o catálogo inteiro pro modelo, manda o topo do que o retriever achou
Pra ter uma referência de tamanho: o cookbook de re-ranking da TypeSafe monta shortlists BM25 de 30 passagens para 40 consultas do dataset CLERC, e com o re-ranking a acurácia top-1 sai de 5% para 18% e a top-10 de 38% para 62%
O erro comum deste passo: querer ranquear tudo. Ranking é refino, não é recuperação
- Monte o state com a consulta e o candidato
O state é o contexto que o modelo enxerga pra responder
A janela do Jev 1.13 é de 32 mil tokens, e esse orçamento vale para o state mais a pergunta mais longa da requisição
# cliente e classes (AsyncTypeSafeClient, Score, Choice, Noul) vêm do typesafe-sdk
# confere os nomes exatos de import e de campo no quickstart oficial
client = AsyncTypeSafeClient() # lê TYPESAFE_API_KEY do ambiente
def monta_state(consulta, item):
return f"""Consulta do usuário: {consulta}
Título do resultado: {item['titulo']}
Seção: {item['secao']}
Trecho: {item['trecho'][:1200]}"""
O erro comum deste passo: despejar o documento inteiro no state. Custo é por token de entrada, então documento inteiro é dinheiro queimado em texto que não decide nada
- Escreva a pergunta Score com os níveis do baixo pro alto
A primitiva Score usa um array ordenado de descrições de níveis, do extremo baixo ao extremo alto, com até 10 níveis
Três já funciona bem, e honestamente três descritos com clareza rende mais que dez vagos
RELEVANCIA = [
"não tem nada a ver com a consulta",
"fala do assunto, mas não responde a dúvida",
"responde exatamente o que foi perguntado",
]
E como a gente vai combinar mais de uma dimensão lá na frente, já escreve as outras duas agora, no mesmo formato:
ESPECIFICIDADE = [
"fala do tema de forma genérica",
"cobre parte do caso específico da consulta",
"trata exatamente o caso específico que foi perguntado",
]
ATUALIDADE = [
"descreve um comportamento que não existe mais no produto",
"está desatualizado em alguns pontos",
"reflete a versão atual do produto",
]
Repara que são três listas independentes, cada uma julgando UMA coisa só
E todas as três vão virar pergunta na mesma requisição, já já te mostro o porquê
O erro comum deste passo: escrever níveis tipo "ruim, médio, bom". Isso não diz NADA pro modelo nem pro colega que vai mexer depois. Descreve o comportamento observável de cada nível
- Agrupe todas as perguntas do mesmo state numa requisição só
Essa é a dica que mais muda a sua conta no fim do mês
Uma chamada com N perguntas dá as mesmas respostas que N chamadas com uma pergunta cada, e a recomendação oficial é mandar junto tudo que compartilha o mesmo state
No cookbook de perguntas paralelas, uma chamada com 13 perguntas sobre o mesmo artigo custou US$ 0,000497 contra US$ 0,006090 de 13 chamadas separadas, ou seja, 12,2x mais barato, e levou 0,27 s contra 2,71 s somados, 10x mais rápido
resposta = await client.system_one(
state=monta_state(consulta, item),
relevancia=Score(criteria=RELEVANCIA),
especificidade=Score(criteria=ESPECIFICIDADE),
atualidade=Score(criteria=ATUALIDADE),
)
O próprio cookbook de re-ranking avisa isso na cara: ele fez uma pergunta por par só pra ficar didático, e uma aplicação real faria várias perguntas sobre o mesmo par em uma única chamada
Curso RecomendadoDesenvolva sistemas mais inteligentes, rápidos e robustos com JevConhecer cursoO erro comum deste passo: montar um for que dispara uma chamada por dimensão. Mesmo state, mesma requisição
- Normalize o score
A resposta de um Score é uma posição na escala, e ela pode cair entre dois níveis
Junto dela vêm dois campos que você vai usar: probabilities, com a probabilidade de cada nível, e confidence, um número de 0 a 1 que resume a forma dessa distribuição
Guarda esse confidence junto do score normalizado, porque é ele que decide lá na frente o que sobe sozinho e o que vai pra revisão
Escala de quatro níveis devolve de 0 a 3, de três níveis devolve de 0 a 2
Pra deixar tudo numa régua de 0 a 1:
def normaliza(score, criteria):
return score / (len(criteria) - 1)
O erro comum deste passo: somar direto uma escala de 3 níveis com uma de 5 e achar que a segunda é "mais importante". Ela só tem número maior, e o seu ranking vira loteria
- Combine as dimensões com pesos no seu código
Aqui entra o padrão de composite scoring: quebra o julgamento em dimensões independentes, normaliza cada uma de 0 a 1 e combina com pesos
O exemplo da doc faz 0,6 × 0,62 + 0,3 × 0,64 + 0,1 × 1,0 = 0,664, arredondado pra 0,66
def score_final(rel, esp, atu):
return 0.6 * rel + 0.3 * esp + 0.1 * atu
Repara onde os pesos moram: no seu repositório, versionados, revisáveis em PR
É o mesmo raciocínio de quando você usa scores calibrados pra priorizar leads: o modelo julga cada dimensão, a política de negócio fica com você
O erro comum deste passo: pedir pro modelo "dar a nota final considerando tudo". Aí você perde a capacidade de debugar por que o item X subiu
- Ordene e aplique o corte
ranqueados = sorted(itens, key=lambda i: i["final"], reverse=True)
CORTE = 0.35 # número seu, calibre numa amostra real de consultas
visiveis = [i for i in ranqueados if i["final"] >= CORTE]
O erro comum deste passo: cortar antes de olhar a distribuição. Roda com uma amostra de consultas reais e escolhe o corte vendo o que some da tela
Usando Choice para ranquear muitos itens em uma requisição só
O fluxo de cima é par a par: uma consulta contra um candidato
Pra catálogo grande existe um caminho mais direto, que é jogar os itens como opções de uma pergunta Choice e deixar o modelo distribuir probabilidade entre eles
Uma pergunta Choice aceita até 255 opções, e cada opção adicionada custa poucos tokens
Por isso a doc recomenda passar a lista completa em vez de mandar um recorte: cortar opção pra "economizar" é economia de centavo que estraga o resultado
E aqui entra a terceira primitiva do Jev, que ainda não apareceu por aqui: a Noul
Pra fechar o combo: Choice escolhe dentro de uma lista de opções, Score dá nota numa escala que você descreve, e Noul devolve a probabilidade de uma afirmação ser verdadeira
No nosso caso a Noul responde uma pergunta bem prática: esse documento responde a consulta, sim ou não?
resposta = await client.system_one(
state=f"Consulta: {consulta}\n\n" + linhas_numeradas,
melhor_linha=Choice(options=ids_das_linhas),
documento_responde=Noul("o documento responde a consulta do usuário"),
)
O cookbook de busca linha a linha mostra exatamente isso: 218 IDs de linha dos Termos de Serviço do GitHub são pontuados contra uma consulta em linguagem natural em uma única requisição, com uma pergunta Choice ranqueando os IDs e uma Noul checando se o documento responde
A função find() de lá devolve a probabilidade de exists e um score de relevância por linha
Ou seja: a Choice te dá o ranking, a Noul te dá o "tem resposta aqui ou não tem?"
Comparando com o fluxo par a par do começo do post: o Score dá uma nota própria pra cada candidato, o que é ótimo quando você quer várias dimensões e um corte por nota
A Choice não dá nota individual, ela distribui probabilidade entre as opções e te devolve a ordem, com todos os itens numa requisição só
Regra de bolso: precisa de nota e de pesos? Score. Precisa só da ordem entre muitos itens, numa tacada só? Choice
E o Noul é uma primitiva versátil, é a mesma que serve pra checar tentativa de jailbreak antes do LLM responder
E quando o catálogo passa de 255 itens?
Particiona em lotes
Ranqueia cada lote, pega o topo de cada um e faz uma rodada final entre os finalistas
E fica de olho na janela de 32 mil tokens: o limite prático não é só a contagem de opções, é o quanto de texto você colocou em cada uma. ID curto mais um título enxuto rende muito mais item por requisição que parágrafo inteiro como opção

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.Como usar o confidence para decidir o que sobe e o que não aparece
Todo resultado vem com probabilities, a probabilidade de cada nível, e com confidence, um número de 0 a 1 que resume a forma dessa distribuição pra você aplicar um limiar sem fazer a conta na mão
No Score a confiança vem da distribuição entre os níveis
Distribuição mais achatada significa confiança menor, e isso faz todo sentido: se o modelo espalhou probabilidade entre "não tem nada a ver" e "responde exatamente", ele está em cima do muro
A doc sugere um padrão de três faixas, com limiares diferentes conforme o risco da ação:
| Faixa de confiança | O que a doc recomenda | Como aplicar no ranking |
|---|---|---|
| Alta | agir automaticamente | reordena o resultado normalmente |
| Média | seguir com cautela, confirmar ou revisar | mantém a posição original ou marca pra revisão |
| Baixa | não agir, mandar pra humano ou outro sistema | deixa o ranking do retriever mandar |
Repara na lógica do caso de baixa confiança: você não inventa nota, você simplesmente não mexe
O fallback do ranking com Jev é o ranking que você já tinha, e isso deixa o deploy MUITO menos assustador
E por que dá pra confiar nesse número em vez de tratar como enfeite? Porque modelos System One são treinados pra decisão calibrada, com as probabilidades otimizadas contra resultados pra refletir a incerteza (é o que a doc chama de RLCD, reinforcement learning for calibrated decisions)
Somando a isso, as saídas são restritas ao schema definido antes da chamada, então o modelo não consegue devolver valor inválido nem erro de tipo
Nada de json.loads dentro de try/except rezando pra dar certo 😛
Onde esse ranking com Jev faz diferença no produto
O mapa de casos de uso da TypeSafe cita explicitamente pontuar relevância consulta-candidato, reordenar resultados com comparações par a par e selecionar contexto pra pipelines de RAG
Na prática, alguns lugares onde isso rende rápido:
- Help center e documentação: usuário digita a dúvida com as palavras dele, a doc usa as palavras do produto. Keyword sofre nisso, julgamento de relevância não
- E-commerce: aqui o composite scoring brilha, porque relevância, disponibilidade e margem são dimensões diferentes e cada uma tem peso diferente na sua régua de negócio
- Seleção de contexto pra RAG: em vez de empurrar os top 10 do vetorial pro prompt, pontua e manda só o que passou do corte. Menos contexto irrelevante, resposta melhor
- Reordenação par a par: quando dois candidatos estão empatados no score, uma pergunta comparando os dois resolve o desempate
Um detalhe legal: em todos esses casos o modelo julga, mas a política continua sua. Quer que produto sem estoque nunca apareça no topo? Isso é if no seu código, não é prompt
Custo, latência e os erros que estouram a conta
Sintoma: a conta veio muito mais alta que o esperado
Causa: uma chamada por par, com o state sendo reenviado a cada chamada
O Jev é cobrado apenas por token de entrada, US$ 0,042 por 1M de tokens de input, com saída a US$ 0,00
Output de graça é ótimo, mas input repetido é input pago de novo
Solução: agrupar as perguntas do mesmo state
Os números do cookbook não deixam dúvida: 12,2x mais barato e 10x mais rápido na comparação de 13 perguntas
Sintoma: o ranking mudou sozinho entre um deploy e outro
Causa: jev-latest, que é o padrão do SDK, apontando pra uma versão nova do modelo
O alias se move, e quando ele se move as respostas podem mudar
Solução: fixar a versão em produção (hoje a atual é a jev-1.13.0) e subir versão de propósito, medindo antes e depois
Sintoma: estouro de contexto em alguns itens
Causa: state grande demais pro orçamento de 32 mil tokens, lembrando que esse orçamento cobre o state mais a pergunta mais longa da requisição
Geralmente é aquele item com página gigante que entra na shortlist e arrebenta tudo
Solução: enxugar o state pro trecho que realmente decide. Título, seção e um recorte do corpo costumam bastar pra julgar relevância
Como prevenir isso desde o começo
Define duas coisas antes de escrever a primeira linha:
- um orçamento por busca: quantos itens você aceita ranquear por consulta
- um teto de shortlist: 30 candidatos já é uma referência pública de fluxo que funciona
Depois é só monitorar tokens de input por busca, porque é ali que o custo mora
Conclusão
O ganho aqui não vem de trocar de buscador
Vem de separar recuperação de ordenação: o retriever acha os candidatos, o Jev pontua cada um com escala calibrada, e a decisão final é feita com pesos que vivem no seu código, versionados, testáveis e explicáveis pra quem perguntar por que o item subiu
Próximo passo que eu sugiro: pega uma amostra de consultas reais do seu produto, mede top-1 e top-10 com o ranking atual, roda o fluxo e mede de novo
Sem medir antes, você não tem como saber se melhorou ou se só ficou mais caro 😀
E se tu não quer plugar a API própria agora, o Jev também tem página de modelo na Cloudflare Workers AI e no OpenRouter, dá pra testar por onde já estiver na sua stack
Até o próximo post!
Perguntas frequentes
Dá pra usar o Jev sem trocar o buscador que a empresa já tem?
Dá, e é assim que o fluxo funciona. O Jev não faz recuperação, ele pontua os candidatos que o seu retriever (BM25, busca vetorial, Elastic, Postgres full text, o que for) já trouxe. Você mantém o buscador atual e adiciona uma etapa de ranking em cima.
Quanto custa usar o Jev pra ranquear resultados de busca?
O preço é só por token de entrada: US$ 0,042 por 1 milhão de tokens de input, com output a US$ 0,00. O mesmo valor aparece na página do Jev 1.13 no OpenRouter. Como a conta é por token de entrada, montar um state enxuto (sem despejar o documento inteiro) impacta direto no custo.
Quantos itens dá pra pontuar numa única chamada ao Jev?
Depende da primitiva. Uma pergunta Choice aceita até 255 opções na mesma chamada, e no cookbook de busca linha a linha da TypeSafe, 218 IDs de linha dos Termos de Serviço do GitHub foram pontuados numa requisição só, combinando uma pergunta Choice pra ranquear os IDs e uma Noul checando se o documento responde a consulta.
Como saber se posso confiar na nota que o Jev devolveu antes de usar no ranking?
O modelo devolve um campo confidence de 0 a 1 junto com a resposta, calculado a partir da distribuição de probabilidades entre os níveis do Score. A documentação sugere dividir isso em três faixas (alta, média, baixa) e definir o limiar de acordo com o risco da ação, então dá pra usar confidence baixa como sinal pra revisar manualmente um resultado antes de exibir no topo da busca.
O Jev só funciona pela API própria da TypeSafe?
Não, ele também está disponível via Cloudflare Workers AI e no OpenRouter, na página typesafe/jev. Pra quem já usa um desses provedores no stack, dá pra chamar o Jev por ali em vez de bater direto no endpoint da TypeSafe.
Vale a pena mandar uma pergunta por candidato ou agrupar tudo numa chamada?
Agrupar é a recomendação oficial sempre que as perguntas compartilham o mesmo state. No cookbook de perguntas paralelas, 13 perguntas sobre o mesmo artigo numa chamada custaram US$ 0,000497 contra US$ 0,006090 de 13 chamadas separadas, e o tempo caiu de 2,71 s somados pra 0,27 s.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
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.
