A busca lexical usando o algoritmo de classificação BM25 é barata, rápida e muito eficaz para uma ampla gama de consultas. Mas ela tem um ponto cego: consultas que não compartilham tokens com seus documentos. Neste artigo, você vai medir exatamente onde a BM25 falha. Usaremos a API de avaliação de classificação do Elasticsearch (rank_eval) e preencheremos essa lacuna adicionando Jina AI embeddings via Elastic Inference Service (EIS). Você verá a pontuação de recall variar de 0.43 para 0.75 e saberá o porquê.
O que é recall?
O recall mede em uma escala de 0 a 1 quantos dos documentos que seus usuários realmente querem aparecem em algum lugar dos seus resultados de busca. Se uma consulta aparecer em três produtos e sua busca devolver apenas dois deles entre os 10 primeiros, recall@10 = 0.67 nessa consulta. É uma métrica baseada em conjuntos: ela não se importa com a posição dos documentos relevantes dentro desses k resultados. Um documento relevante na posição 10 conta o mesmo que um na posição 1. Ter um recall alto significa que você não está perdendo resultados relevantes.

O diagrama mostra dois conjuntos: todos os documentos relevantes (à esquerda) e o que o BM25 realmente recuperou (top 10, à direita). Apenas a interseção conta para a recuperação, prod_1 e prod_2 foram encontrados, enquanto prod_3, prod_4 e prod_6 foram completamente perdidos. Resultado: Recall@10 = 2/5 = 0.40.
Pré-requisitos
Vamos direto ao ponto para entender melhor como o recall funciona. Esta demonstração usa Python. Você pode acompanhar no notebook complementar (notebook.ipynb), onde cada bloco de código é uma célula pronta para ser executada.
O código fornecido utiliza o seguinte:
- Elasticsearch 9.3+
- Python 3.10+
- Um arquivo
.envcom suas credenciais do Elasticsearch
O conjunto de dados
Usaremos um catálogo de produtos com 1.000 produtos, abrangendo categorias como calçados, eletrônicos, ferramentas e outros.
Cada documento tem quatro campos:
| Campo | Tipo |
|---|---|
| `title` | texto |
| `description` | texto |
| `marca` | palavra-chave |
| `category` | palavra-chave |
O conjunto de dados é carregado a partir de dataset.csv.
O poder e os limites da busca lexical
BM25 é o algoritmo padrão de ranqueamento no Elasticsearch e na maioria dos mecanismos de busca. Ele classifica os documentos de acordo com a frequência com que seus termos de consulta aparecem neles, ajustados ao tamanho do documento e à frequência desses termos em todo o índice. Você tem analisadores na parte superior: normalização de letras minúsculas, stemming e retirada de stopwords. Uma busca por "tênis de corrida" retornará resultados como "Tênis de corrida" e provavelmente também "correr".
Isso funciona bem para uma grande classe de consultas:
- "tênis de corrida" faz a correspondência imediata dos produtos com esses tokens exatos no título.
- "alto-falante Bluetooth" destaca produtos de áudio portáteis porque os tokens aparecem literalmente.
Os resultados são determinísticos e explicáveis: um documento tem classificação alta porque os termos de consulta aparecem nele. Depurar a relevância é simples.
Onde ocorre a falha
Agora, vamos testar essas consultas no mesmo catálogo:
- "rotina de cuidados com a pele": a palavra "rotina" não aparece em nenhum título de produto. O BM25 pode corresponder parcialmente com "cuidados com a pele", mas séruns faciais, óleos corporais e hidratantes são descritos usando termos como "vitamina C", "retinol" ou "iluminador", nenhum dos quais se sobrepõe à consulta. Produtos que formam uma rotina completa de cuidados com a pele ficam espalhados pelo índice, sem nenhum token compartilhado para ancorá-los.
- "acessórios de viagem para pets": é um agrupamento de casos de uso, não uma categoria de produto. Um sling para cães, uma cadeirinha para pets e uma caixa de viagem são todos relevantes, mas as descrições falam sobre portabilidade, segurança e conforto, em vez de "acessórios de viagem". O BM25 corresponde amplamente a palavra "pet", mas não tem sinal para distinguir produtos específicos para viagens do restante do catálogo de pets.
Esse é um problema de recall. Os documentos relevantes existem no seu índice. O BM25 simplesmente não os encontra porque as palavras do usuário e as do documento não coincidem o suficiente.
Adicionar sinônimos ajuda em casos conhecidos. Mas não dá para enumerar todas as formas como o usuário pode expressar uma intenção. É aí que entram os vetores.
Por que medir o recall
Antes de corrigir um problema, você precisa quantificá-lo.
Recall@k mede quantos documentos que seus usuários realmente desejam aparecem nos resultados de busca. Formalmente:
Precision@k mede os k principais resultados e quantos são realmente relevantes:
Alta precisão mostra que os resultados que você retorna são bons. No comércio eletrônico, perder um produto relevante (baixo recall) geralmente é pior do que mostrar um resultado um pouco imperfeito (menor precisão), porque o produto oculto é venda perdida.
A API rank_eval do Elasticsearch permite medir os dois de forma sistemática. Você fornece uma lista de consultas, cada uma com um conjunto de documentos avaliados, e o Elasticsearch calcula as métricas para você em todas elas.
Configuração da avaliação
A API rank_eval precisa de um conjunto de dados de avaliações: um mapeamento das consultas para os documentos relevantes para cada um, junto com uma nota de relevância (0 = não relevante, 1 = relevante, 2 = altamente relevante).
No bloco de notas, esta é a lista de julgamentos:
A combinação é intencional: q1 é uma consulta que o BM25 lida bem (tokens exatos nos títulos dos produtos), enquanto q2, q3 e q4 são consultas orientadas à intenção, nas quais a intenção do usuário é expressa como um conceito, não como palavras-chave específicas de produto.
Medindo o recall de base do BM25
Primeiro, configure o cliente do Elasticsearch e indexe os dados de texto bruto:
Agora, crie a solicitação rank_eval para o BM25. Cada solicitação na lista combina uma consulta com as classificações correspondentes:
Resultado:
0.43 significa que, em todas as quatro consultas, o BM25 encontra apenas 43% dos documentos que deveria. A deficiência se concentra nas consultas baseadas na intenção: "rotina de cuidados com a pele" não inclui séruns faciais e óleos corporais, pois "rotina" nunca aparece nos títulos dos produtos. Já "acessórios de viagem para pets" retorna produtos para pets fora do tópico, enquanto não inclui transportadoras e caixas de transporte descritas em termos de portabilidade e segurança, em vez de "acessórios de viagem".
Esta é a nossa linha de base. Agora, temos um número a superar.
Adicionando busca vetorial com embeddings do Jina
Vector search codifica documentos e consultas como vetores de alta dimensão, tipo de vetor composto por centenas ou milhares de valores numéricos, cada um codificando um recurso específico dos dados que representa. Documentos com significado semelhante acabam próximos uns dos outros no espaço vetorial, mesmo que não compartilhem palavras. "Equipamento de ginástica" e "conjunto de halteres" ficam próximos porque os conceitos estão relacionados. Escolhi o Elasticsearch como meu banco de dados vetorial porque ele faz busca híbrida, oferecendo compreensão semântica e precisão de palavras-chave prontas para uso.
EIS inclui suporte pronto para uso de modelos via API de inferência.
Passo 1: usando embeddings Jina v5 como endpoint de inferência
Se seu cluster tem recursos de GPU (disponíveis no Elastic Cloud e Elasticsearch 9.3+), as incorporações são geradas na GPU, o que é bem mais rápido do que a inferência da CPU e elimina o tradeoff de desempenho que historicamente encarecia os vetores em larga escala.
Por que as incorporações Jina especificamente? jina-embeddings-v5-text é um modelo multilíngue (mais de 119 idiomas) com uma janela de contexto de 32 mil tokens e suporte para adaptadores de Adaptação de Baixa Ordem (LoRA) específicos para cada tarefa. Ele funciona bem para descrições curtas de produtos, prontamente utilizável. Saiba mais sobre o modelo jina-embeddings-v5-text aqui.
Passo 2: criar o índice com um campo semântico
O tipo de campo semantic_text é essencial aqui. É uma abstração de nível mais alto sobre dense_vector: você aponta para um endpoint de inferência, e o Elasticsearch cuida de gerar automaticamente os embeddings.
A propriedade copy_to em title e description significa que o conteúdo de ambos os campos flui para semantic_field para incorporação, de modo que um único vetor captura a representação completa do produto.
Passo 3: indexar os produtos
No momento do índice, o Elasticsearch chama o endpoint de inferência para cada documento e armazena a incorporação resultante em semantic_field. Sem código extra do seu lado.
Busca híbrida: combinando BM25 e vetores com RRF
Adicionar vetores melhora a recuperação, mas usar vetores sozinhos pode prejudicar a precisão em consultas de correspondência exata; "tênis de corrida" ainda deve priorizar correspondências exatas. A busca híbrida mantém o componente léxico especificamente para preservar essa precisão.
A busca híbrida com Fusão de Classificação Recíproca (RRF) mantém o melhor dos dois mundos:
- O BM25 lida com consultas exatas e quase exatas com alta precisão.
- A busca semântica processa consultas baseadas em intenção e multilíngues com alta precisão.
- O RRF combina as duas listas classificadas em uma única classificação.
A fórmula RRF atribui a cada documento uma pontuação baseada em sua classificação em cada lista de resultados:
Um documento bem classificado em ambas as listas recebe uma pontuação combinada maior. O rank_constant controla quanto peso documentos de menor classificação recebem.
Resultado:
O Hybrid melhora substancialmente em relação ao BM25 (0.43) e preserva a precisão para consultas de correspondência exata como "tênis de corrida".
Resultados: Antes e depois
Eis a comparação completa entre as três abordagens:
Resultado:
| Método | Recall@10 |
|---|---|
| BM25 (Léxico) | 0,43 |
| Híbrido (BM25 + Vetores) | 0,75 |

Analisando por consulta:

Conclusão
Ao longo deste artigo, vimos que a busca léxica do BM25 é confiável quando os usuários digitam consultas exatas, mas perde a capacidade de recuperação quando buscam por intenção em vez de palavras-chave. Usando rank_eval, estabelecemos uma linha base reprodutível para medir essa lacuna com números reais. A partir daí, adicionamos um campo semantic_text alimentado por embeddings Jina e rodamos a avaliação novamente. O resultado: a buscar híbrida melhorou a capacidade de recuperação de 0.43 para 0.75 enquanto preservava a precisão nas consultas de correspondência exata, embora a margem real dependa da sua mistura de consultas.
O padrão se estende além deste exemplo: colete julgamentos das consultas reais de seus usuários, execute rank_eval como linha de base, adicione semantic_text e meça novamente. Você saberá exatamente o que melhorou e em quanto.
Próximas etapas
- Aprofunde-se no recall e na busca vetorial: quantização de busca vetorial e recall, de Jeff Vestal
- Adicione o reranking para melhorar ainda mais a precisão nos resultados principais
- Consulte a documentação de busca híbrida do Elasticsearch
- Leia mais sobre a
rank_evalAPI




