← Voltar ao curso
Nível 3 — Automação e escala · Módulo 3.8 — RAG e busca agêntica

3.8.2 · BM25 e busca híbrida

5 min de vídeo TEC

Objetivo: ao final, o consultor identifica onde a busca semântica falha em contexto SAP, combina busca lexical e semântica, e conhece os ganhos medidos de contextual retrieval e reranking.

O que você precisa levar desta aula

  1. Busca semântica falha justamente no que projeto SAP mais procura: código de erro, número de nota, transação, tabela, campo, número de documento. Identificador exato tem identidade, não significado — e é o BM25 que o encontra.
  2. Busca híbrida não é escolha, é soma. Lexical e semântica erram em lugares diferentes; a consulta vai para os dois índices e os resultados são combinados.
  3. Os ganhos são medidos e cumulativos: embedding contextualizado −35% de falha, somando BM25 contextualizado −49%, acrescentando reranking −67%.

Onde a busca semântica falha em SAP

O consultor procura Exemplo Semântica BM25
Código de mensagem ME 083 Falha Acha
Número de nota SAP Note 2345678 Falha Acha
Código de transação ME21N, VA02 Falha Acha
Nome de tabela EKKO, EKPO Falha Acha
Nome técnico de campo MATNR, LIFNR Falha Acha
Número de documento 4500012345 Falha Acha
Nome de objeto Z ZMM_REL_PED Falha Acha
"Como funciona a liberação de pedido?" Acha Falha se o documento usar outras palavras
"Qual o processo de bloqueio de fatura?" Acha Falha se o documento disser "retenção"

As oito primeiras linhas são a razão de este módulo existir para a Wayon. Um assistente de documentação SAP que só tenha busca semântica falha na consulta mais frequente que o consultor faz: colar um código de erro e perguntar o que é.

E não é que a semântica erre por pouco. Ela pode devolver, com alta similaridade, o trecho sobre ME 084 — parecidíssimo em forma, e resposta errada.

Busca híbrida

from rank_bm25 import BM25Okapi
import numpy as np

# Índice lexical
bm25 = BM25Okapi([p.lower().split() for p in pedacos])

def buscar_hibrido(pergunta, k=20):
    # Semântico
    q = vo.embed([pergunta], model="voyage-4", input_type="query").embeddings[0]
    sem = np.dot(vetores, q)

    # Lexical
    lex = bm25.get_scores(pergunta.lower().split())

    # Normalizar cada lista para 0–1 antes de somar: as escalas não são comparáveis
    def norm(x):
        x = np.asarray(x, dtype=float)
        return (x - x.min()) / (x.max() - x.min() + 1e-9)

    combinado = 0.5 * norm(sem) + 0.5 * norm(lex)
    return [pedacos[i] for i in np.argsort(combinado)[-k:][::-1]]

O detalhe que quebra implementações apressadas: as duas pontuações não são comparáveis em escala. Similaridade de cosseno vive entre −1 e 1; BM25 não tem teto. Somar direto faz o BM25 dominar. Normalizar antes é obrigatório.

O peso 0,5 / 0,5 é ponto de partida. Em acervo SAP, carregado de identificadores, subir o peso do BM25 costuma ajudar — e isso se mede, com o dataset e o grader do módulo 3.6, não se estima.

Contextual retrieval

PROMPT_CONTEXTO = """<documento>
{documento}
</documento>

Aqui está o trecho que queremos situar dentro do documento acima:

<trecho>
{trecho}
</trecho>

Escreva uma frase curta (50 a 100 tokens) situando este trecho no documento, para
melhorar a recuperação em busca. Responda apenas com a frase."""


def contextualizar(documento: str, trecho: str) -> str:
    r = client.messages.create(
        model="claude-haiku-4-5",     # tarefa simples, volume alto: modelo barato
        max_tokens=150,
        messages=[{"role": "user",
                   "content": PROMPT_CONTEXTO.format(documento=documento, trecho=trecho)}],
    )
    contexto = next(b.text for b in r.content if b.type == "text")
    return f"{contexto.strip()}\n\n{trecho}"

Isso roda uma vez por pedaço, na indexação — não a cada consulta. Ainda assim, é uma chamada por pedaço: num acervo de 20 mil pedaços, são 20 mil chamadas. Duas coisas tornam isso viável:

Os ganhos, medidos

Configuração Taxa de falha na recuperação Redução vs. base
RAG base (embedding + BM25 comuns) 5,7%
+ Embedding contextualizado 3,7% −35%
+ BM25 contextualizado também 2,9% −49%
+ Reranking 1,9% −67%

Esses números são de uma medição publicada pela Anthropic, em corpus dela — não são garantia para o acervo do Meridiano. Servem para dimensionar o esforço: cada estágio acrescenta trabalho e custo, e o retorno é conhecido em ordem de grandeza. Se o seu pipeline já está bom o suficiente para o caso, parar antes é uma decisão legítima.

Reranking

resultados = vo.rerank(
    query=pergunta,
    documents=candidatos,      # os 20 da busca híbrida
    model="rerank-2.5",
    top_k=5,
)
melhores = [r.document for r in resultados.results]

O reranker vê a pergunta e o pedaço juntos, o que a busca vetorial não faz — ela compara vetores calculados separadamente. Por isso ele é mais preciso e mais caro: roda só sobre os candidatos que a busca já filtrou.

A alternativa que reduz o pipeline

Modelos de embedding contextualizado (voyage-context-4, 120 mil tokens de contexto) produzem o vetor do pedaço já considerando o documento inteiro, através de contextualized_embed() em vez de embed(). Isso elimina a etapa de gerar contexto com o Claude:

Caminho Etapas na indexação Custo
Contextual retrieval manual Fatiar → 1 chamada ao Claude por pedaço → embeddar Chamada por pedaço, mitigada por cache
Embedding contextualizado Fatiar → embeddar com contexto Sem chamada ao Claude

Menos peça, menos coisa para manter, menos coisa para quebrar. Vale medir os dois no seu acervo — mas comece pelo mais simples.

Regra Wayon Assistente de documentação técnica entregue a cliente SAP roda busca híbrida, nunca só semântica. Código de erro, nota SAP, transação, tabela e número de documento são a consulta mais frequente do consultor, e são exatamente onde a busca semântica falha. A escolha dos pesos entre lexical e semântico é medida com o dataset do módulo 3.6, não estimada — e o número medido vai na proposta, como na aula 3.6.5.

📖 Contextual Retrieval · Embeddings · Prompt caching — aula 3.7.4

Quiz — 4 questões

1.Um consultor busca "ME 083" num assistente com busca puramente semântica. Por que o resultado tende a ser ruim?
  • a)Porque o pedaço com ME 083 provavelmente ficou fora do índice

    Está no índice; o problema é a busca não o encontrar.

  • b)Porque códigos de erro são filtrados na tokenização do modelo de embedding

    Não há filtragem — eles são vetorizados como qualquer texto.

  • c)Porque "ME 083" é identificador exato, e a semântica compara significado — o vetor fica próximo do de qualquer outro código

    Correto, e é o motivo de a semântica poder devolver ME 084 com alta similaridade.

Ver resposta e por quê
a) Está no índice; o problema é a busca não o encontrar.
b) Não há filtragem — eles são vetorizados como qualquer texto.
c) Correto, e é o motivo de a semântica poder devolver ME 084 com alta similaridade.
2.Ao combinar as pontuações de BM25 e da busca semântica, o que é obrigatório antes de somar?
  • a)Normalizar as duas pontuações para a mesma escala — cosseno vai de −1 a 1, BM25 não tem teto

    Correto. Somar direto faz o BM25 dominar o resultado.

  • b)Converter as pontuações de BM25 em vetores, para a comparação ser homogênea

    BM25 não produz vetores e não precisa produzir.

  • c)Nada — os dois algoritmos já devolvem pontuação entre 0 e 1

    BM25 não devolve valor limitado, e é justamente essa a origem do problema.

Ver resposta e por quê
a) Correto. Somar direto faz o BM25 dominar o resultado.
b) BM25 não produz vetores e não precisa produzir.
c) BM25 não devolve valor limitado, e é justamente essa a origem do problema.
3.Segundo a medição publicada pela Anthropic, qual é a redução de falha de recuperação do pipeline completo — embedding contextualizado, BM25 contextualizado e reranking — frente ao RAG base?
  • a)Cerca de 35% — o ganho vem quase todo do embedding contextualizado

    35% é só o primeiro estágio; os outros dois somam mais.

  • b)Cerca de 67%, levando a falha de 5,7% para 1,9%

    Correto, e os estágios são cumulativos: −35%, −49%, −67%.

  • c)Cerca de 90%, levando a falha a menos de 1%

    A falha medida cai a 1,9%, não abaixo de 1%.

Ver resposta e por quê
a) 35% é só o primeiro estágio; os outros dois somam mais.
b) Correto, e os estágios são cumulativos: −35%, −49%, −67%.
c) A falha medida cai a 1,9%, não abaixo de 1%.
4.Por que o reranker roda depois da busca, e não no lugar dela? ---
  • a)Porque ele só aceita como entrada resultados de busca híbrida

    Ele aceita qualquer lista de documentos; a razão é de custo e escala.

  • b)Porque ele avalia pergunta e pedaço juntos, o que é mais preciso e mais caro — só compensa sobre os candidatos já filtrados

    Correto. Rodá-lo sobre o acervo inteiro seria inviável.

  • c)Porque ele exige que os pedaços já tenham sido contextualizados na indexação

    Contextualização ajuda, mas não é pré-requisito do reranker.

Ver resposta e por quê
a) Ele aceita qualquer lista de documentos; a razão é de custo e escala.
b) Correto. Rodá-lo sobre o acervo inteiro seria inviável.
c) Contextualização ajuda, mas não é pré-requisito do reranker.