//Caso de uso

Busca interna que entende a pergunta

A pior resposta de uma busca interna não é a lista vazia. É a lista cheia de coisas parecidas quando a resposta não existe.

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.

Quer dominar decisões com IA em português? O curso da comunidade está em pré-venda.

Garantir pré-venda por R$ 499,00

Pré-venda: R$ 499,00 · Após o lançamento: R$ 799,00