A busca interna de uma empresa quase sempre funciona assim: alguém digita uma pergunta em linguagem natural, o sistema procura palavras, e devolve dez documentos que contêm essas palavras. Se a resposta estiver no terceiro parágrafo do sexto resultado, a pessoa desiste antes.
E existe um segundo problema, mais silencioso: quando a resposta simplesmente não está no acervo, a busca devolve os dez resultados mais parecidos com a pergunta. A pessoa lê todos, não encontra nada e conclui que a busca é ruim — quando a informação é que não existe.
O Jev, modelo da TypeSafe AI, devolve decisões tipadas com probabilidade em vez de texto. Para busca interna, ele resolve exatamente esses dois pontos: ordenar por relevância de verdade e responder se há resposta.
O desenho em duas camadas
A camada de baixo continua sendo o que você já tem. Busca textual, embeddings, ou os dois: o papel dela é gerar uma lista curta com alta chance de conter a resposta. No experimento oficial de reordenação, essa lista tem 30 candidatos por consulta, montada por BM25, e a passagem correta estava entre os 30 em 100% das 40 consultas testadas.
A camada de cima é a reordenação. Uma pergunta por par consulta-candidato, e o seu código ordena pelo resultado.
from typesafe_sdk import Choice, Noul, Score
consulta = "posso usar o notebook da empresa para estudo fora do horário?"
perguntas = {
# Uma pergunta por candidato: responde a consulta, sim ou não.
**{
f"responde_{i}": Noul(
instructions=f"O trecho `candidatos[{i}]` responde à pergunta do usuário?"
)
for i in range(len(candidatos))
},
# Uma pergunta sobre o acervo inteiro.
"existe_resposta": Noul(
instructions="Algum dos trechos fornecidos responde à pergunta do usuário?"
),
"tipo_de_pergunta": Choice(
instructions="Que tipo de pergunta o usuário fez?",
criteria={
"politica_interna": "Regra da empresa: uso de equipamento, férias, conduta.",
"procedimento": "Como fazer algo: solicitar, aprovar, configurar.",
"dado_ou_numero": "Pede um valor, um prazo ou uma quantidade.",
"fora_de_escopo": "Não é uma pergunta sobre o acervo interno.",
},
),
}
Quando os candidatos são linhas de um mesmo documento, e não documentos separados, o formato muda: em vez de um Noul por candidato, uma pergunta de escolha sobre qual linha responde melhor. É o desenho da receita oficial de busca linha a linha, que pontuou 218 ids de linha dos termos de serviço do GitHub em uma requisição. O teto ali é de 255 opções por pergunta.
O que o código faz com a resposta
Existência primeiro, e essa é a inversão importante. Antes de mostrar qualquer lista, verifique se há resposta. Os limiares do cookbook oficial servem de ponto de partida: existência a partir de 0,70 conta como respondido, abaixo de 0,35 como ausente, e a faixa do meio como parcialmente abordado.
Regras determinísticas depois. tipo_de_pergunta igual a fora_de_escopo
desvia para outra experiência — um formulário de dúvida, um canal de
atendimento — em vez de tentar responder.
Ordenação por último, com as probabilidades dos candidatos.
existe = r.answers["existe_resposta"].noul
if existe < 0.35:
return responder_nao_temos(consulta) # honestidade vale mais que lista
if existe < 0.70:
aviso = "Encontramos material relacionado, mas talvez não a resposta exata."
else:
aviso = None
ordenados = sorted(
range(len(candidatos)),
key=lambda i: r.answers[f"responde_{i}"].noul,
reverse=True,
)
mostrar(ordenados[:5], aviso=aviso, consulta=consulta)
A frase “não temos isso documentado” é um resultado de busca legítimo, e provavelmente o mais valioso que uma base interna pode dar: ela transforma a busca em um inventário do que falta escrever.
Onde entra a pessoa
Menos do que nos outros casos, porque o risco é baixo — mostrar o resultado errado custa alguns segundos de leitura. O limiar acompanha o risco, e aqui ele pode ser generoso.
Onde a pessoa entra mesmo é depois: na fila de consultas que receberam existência baixa. Essa fila é a lista priorizada do que a base de conhecimento precisa cobrir, ordenada pela frequência real das perguntas. É o subproduto mais útil desse desenho, e ele não existe em uma busca comum.
Custo
Assumindo cem mil buscas por mês — suposição deste artigo — e um state de
cerca de oito mil tokens por consulta, que cobre a pergunta e a lista curta de
candidatos: ao preço oficial de US$ 0,042 por milhão de tokens de entrada, com
saída gratuita, e o dólar estimado em R$ 5,40, cada busca sai por cerca de
R$ 0,0018 e o mês por cerca de R$ 181,44. A fórmula está em
quanto custa uma decisão.
Vale registrar como o experimento oficial montou a conta dele: 1.200 chamadas, uma pergunta por par, 1.536.002 tokens de entrada e US$ 0,0645 no total. O próprio cookbook observa que fazer uma pergunta por par é uma escolha didática, e que uma aplicação real agruparia várias perguntas sobre o mesmo par em uma chamada — o que reduz bastante o custo. Esse agrupamento é o padrão de fan-out especulativo.
Onde isso quebra
State grande, que aqui é o risco estrutural. Mandar cinquenta documentos
inteiros no state é a forma mais rápida de piorar a resposta: a documentação
lista state grande cheio de detalhe irrelevante entre os nove modos de falha
declarados, e o detalhe irrelevante age como distrator. Mande trechos, não
documentos; o efeito está em context rot.
Contagem. “Quantos documentos falam sobre isso?” é aritmética, e o modelo não conta de forma confiável. Conte as respostas no seu código.
Teto de opções. Uma pergunta de escolha aceita até 255 opções. Acima disso, divida em passagens e junte os resultados no código.
Relevância não é resposta. É a lição mais importante da receita oficial: uma linha pode ser a mais parecida com a pergunta e ainda assim não responder nada. Por isso a pergunta de existência é separada da ordenação, e por isso ela é consultada primeiro.
Documento desatualizado. O modelo julga o texto, não a validade dele. Data de revisão, versão vigente e revogação são metadados do seu sistema e devem filtrar a lista curta antes da chamada.
A lista completa está em limites do Jev, a receita de reordenação em reranking de busca e os outros fluxos em casos de uso.
Perguntas frequentes
Isso substitui o meu mecanismo de busca?
Não, entra depois dele. O desenho é uma lista curta gerada pelo que você já tem — busca textual ou embeddings — e uma passagem de reordenação em cima dessa lista. O experimento oficial de reordenação usa listas de 30 candidatos por consulta, montadas por BM25.
Quanto a reordenação melhora, de fato?
No experimento oficial com 40 consultas jurídicas e listas de 30 passagens, a acurácia em primeiro lugar foi de 5% para 18% e a de estar entre os dez primeiros foi de 38% para 62%. São números do cookbook da TypeSafe, naquele corpus e com o modelo jev-1.12.
Como dizer ao usuário que a resposta não existe no acervo?
Com uma pergunta de sim ou não à parte, que verifica se o documento contém resposta. Na receita oficial de busca linha a linha, uma consulta sobre arbitragem recebeu existência 0,14 mesmo com a melhor linha marcando 0,86 de relevância: havia texto parecido, não havia resposta.
Quantos candidatos cabem em uma chamada?
Depende do tamanho deles, porque o limite é de tokens: 64 mil por requisição e 32 mil para o state mais a pergunta mais longa. Há também um teto de forma: uma pergunta de escolha aceita no máximo 255 opções.
Quanto custa cem mil buscas por mês?
Assumindo um state de cerca de oito mil tokens por consulta, que cobre a pergunta e a lista curta de candidatos, ao preço oficial de US$ 0,042 por milhão de tokens de entrada e com o dólar estimado em R$ 5,40, cem mil buscas saem por cerca de R$ 181,44.