~ / tutoriais /rag-nao-e-so-vector-search-busca-semantica-sql-tools $ _

RAG não é só vector search: combinando busca semântica, SQL e tools no mesmo agente

Lucas Souza Lucas Souza 11 min de leitura Tutoriais
RAG não é só vector search: combinando busca semântica, SQL e tools no mesmo agente

Você fez o trabalho do livro-texto.

Chunkou os documentos. Gerou embeddings com text-embedding-3-large. Subiu pro pgvector, Qdrant, Pinecone — tanto faz. Plugou no agente, deu deploy, abriu o dashboard.

E aí o usuário pergunta: "quantas issues de performance abriram no repositório X mês passado?". O retrieval traz cinco chunks aleatórios sobre performance, nenhum do mês certo, nenhum do repositório certo. O modelo alucina um número. Ticket aberto.

O problema não é o modelo. É que tratar todo retrieval como problema de embedding é o erro mais comum em RAG hoje — e os benchmarks de 2025/2026 deixaram isso explícito. Neste post a gente compara, com número, três abordagens: vector-only, híbrido (BM25 + vetor + RRF) e o stack completo de agente com SQL, vetor e reranker como tools separadas. No fim você sabe qual aplicar em cada caso e o que isso custa em latência.

TL;DR

  • O que é: comparação prática de três arquiteturas de retrieval para RAG, com números reais e código para cada uma.
  • Stack/Modelos: PostgreSQL + pgvector, BM25 (via ts_rank ou Tantivy), embeddings (Voyage/Gemini/OpenAI), Cohere Rerank 3.5 ou Voyage Rerank, Claude/GPT como agente com tool use.
  • Custo/Acesso: infra de Postgres já paga; reranker gerenciado custa centavos por milhar de queries; embeddings via API.
  • Repositório/Link útil: Anthropic — Contextual Retrieval e LlamaIndex SQLAutoVectorQueryEngine.

O preço de tratar tudo como problema de embedding

A ideia de que "RAG = vector search" pegou porque é didática. Funciona em demo. Quebra em produção.

O Tiger Data resumiu o caso clássico em uma pergunta só: "What were the top five most discussed GitHub issues last month related to performance optimization?" (Tiger Data). Embedding não filtra por janela temporal. Não agrega. Não junta com metadado estruturado. Ele faz uma coisa só: aproxima vetor.

Aí você vai medir e descobre que mesmo na parte que é só similaridade semântica o vetor sozinho perde feio para combinações simples. O paper de benchmarking de RAG de 2026 mostra vector puro com 78% de recall@10, BM25 puro com 65%, e a fusão dos dois (RRF) batendo 91% no mesmo dataset (arxiv 2604.01733). A própria Anthropic publicou que vector-only deixa 5,7% das queries sem nenhum chunk relevante no top-20, e que BM25 + embedding contextualizado derruba esse número para 2,9% (Anthropic — Contextual Retrieval).

A leitura é direta: vetor sozinho perde até no terreno onde teoricamente deveria brilhar. Em qualquer pergunta que envolva código de erro, ID, número de pedido ou termo técnico exato — o que é metade do tráfego em RAG corporativo — BM25 mata o vetor.

Híbrido (BM25 + vetor + RRF) — a baseline mínima

Antes de pensar em reranker, em agente, em qualquer coisa fancy: monte hybrid. É barato e move o ponteiro mais que tudo o que vem depois.

A receita é simples:

  1. Roda BM25 e vector search em paralelo, cada um devolve top-K (ex: 50).
  2. Funde com Reciprocal Rank Fusion (RRF): para cada documento, soma 1 / (k + rank) em cada lista.
  3. Devolve o top-N final.

Em pgvector dá pra fazer dentro do próprio Postgres, sem orquestrador externo:

WITH semantic AS (
    SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS rank
    FROM chunks
    WHERE tenant_id = $2
    ORDER BY embedding <=> $1
    LIMIT 50
),
keyword AS (
    SELECT id, ROW_NUMBER() OVER (
        ORDER BY ts_rank(search_tsv, plainto_tsquery('portuguese', $3)) DESC
    ) AS rank
    FROM chunks
    WHERE tenant_id = $2
      AND search_tsv @@ plainto_tsquery('portuguese', $3)
    LIMIT 50
)
SELECT id,
       COALESCE(1.0 / (60 + s.rank), 0) + COALESCE(1.0 / (60 + k.rank), 0) AS rrf_score
FROM semantic s
FULL OUTER JOIN keyword k USING (id)
ORDER BY rrf_score DESC
LIMIT 10;

O 60 é o k clássico do paper original do RRF. O tenant_id no WHERE é o que vector store dedicado normalmente atrapalha — em SQL é trivial.

Os números que isso entrega, medidos em produção por quem postou recentemente: vector puro Recall@50 72%, hybrid 94%, latência subindo só de 80ms para 95ms (bswen — hybrid search vs reranker). 22 pontos de recall por 15ms a mais. Não há feature de IA com retorno tão alto por tão pouco esforço.

SQL como tool — o que reranker nenhum resolve

Hybrid resolve "quero documento parecido com isso". Não resolve "quero o de janeiro do cliente Y". Para isso o agente precisa de uma tool de SQL ao lado da tool de vetor.

A intuição é a mesma do SQLAutoVectorQueryEngine do LlamaIndex: você expõe duas (ou três) tools com descrições que deixam claro o domínio de cada uma, e o LLM roteia. Em Claude tool use, fica assim:

tools = [
    {
        "name": "search_docs_semantic",
        "description": (
            "Busca trechos de documentos por similaridade semântica. "
            "Use quando o usuário pede explicação, conceito, "
            "ou texto livre sem filtros temporais ou de identificador."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "query_issues_sql",
        "description": (
            "Consulta a tabela 'issues' por filtros estruturados: "
            "data, repositório, autor, label, contagem. "
            "Use SEMPRE que a pergunta tiver agregação (top, total, média) "
            "ou janela temporal (último mês, esta semana). "
            "Aceita SELECT somente, com WHERE/GROUP BY/ORDER BY."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"sql": {"type": "string"}},
            "required": ["sql"],
        },
    },
]

Duas coisas importam aqui — e nenhuma é o "ler docs do Anthropic SDK":

  • A descrição da tool é o roteador. Modelo nenhum chama a tool certa se a descrição é genérica. Escreva como se fosse comentário de função: quando usar, quando não usar, exemplos de input.
  • SQL gerado por LLM não vai pro banco direto. Você valida AST (só SELECT), aplica LIMIT máximo, força WHERE tenant_id = ? por código, e roda em uma role read-only. Se isso parece paranoico, você ainda não foi mordido por prompt injection no campo query.

Para a parte de "consulta híbrida que precisa dos dois" — tipo "me dê os tickets do cliente Y mais parecidos com esse problema de checkout" — o agente faz multi-turn: chama query_issues_sql para filtrar cliente_id = Y, recebe a lista de IDs, e chama search_docs_semantic passando esses IDs como filtro. Não precisa nada além de tool calling padrão.

Reranker no final — precisão sem comprometer recall

Hybrid sobe recall. Reranker sobe precision. São coisas diferentes e a ordem importa: reranker só consegue reordenar o que já veio na primeira etapa. Se o documento certo não está nos 50 que o hybrid trouxe, nenhum reranker conserta — e isso é o ponto que o pessoal do bswen martela: rerank antes de consertar recall é sintoma de gente que viu uma keynote da Cohere e foi codar.

Os números fecham essa história:

Estágio Recall@50 Precision@10 Latência
Vector puro 72% 48% 80ms
Hybrid (BM25 + vetor + RRF) 94% 61% 95ms
Hybrid + reranker 94% 78% 285ms

Fonte: bswen. O reranker custa 190ms a mais para entregar +17 pontos de precisão. Em chat síncrono isso pesa; em workflow assíncrono ou em endpoint que já usa streaming de resposta do LLM, some no ruído.

A Cohere reportou no anúncio do Rerank 3.5 que o modelo bate hybrid search puro em +23,4% e BM25 puro em +30,8% num dataset financeiro (Cohere blog). É claro que é benchmark de fabricante — leia com sal —, mas a ordem de magnitude bate com o que outros publicaram.

A regra prática que sai disso:

1. Comece com hybrid. Meça recall@K.
2. Se recall@K < 90%, conserte recall (chunking, embedding, contextual BM25).
3.  com recall acima de 90% adicione reranker.
4. Cacheie reranking por (query, candidate_set_hash).

Contextual retrieval — o multiplicador que muita gente ignora

Antes de fechar, um item que aparece pouco em tutorial e move muito o ponteiro: contextualizar o chunk antes de embeddar e indexar.

A Anthropic propôs isso em setembro de 2024 e mantém atualizado: para cada chunk, você pede ao LLM um parágrafo curto que situa o chunk no documento ("este trecho discute o resultado financeiro do Q2 2024 da empresa X, citado no relatório anual"), prepende essa contextualização ao chunk e embeda/indexa o resultado (Anthropic).

Os números empilham:

  • Contextual Embeddings: failure rate 5,7% → 3,7% (−35%).
  • Contextual Embeddings + Contextual BM25: 5,7% → 2,9% (−49%).
  • Tudo isso + reranker: 5,7% → 1,9% (−67%).

Custo com prompt caching: US$ 1,02 por milhão de tokens de documento processados, segundo o post original. Para um corpus de 10 milhões de tokens isso é US$ 10. Pague.

Como o agente escolhe a tool — não delegue isso pro modelo cego

A parte mais negligenciada da arquitetura de RAG agêntico não é o retrieval, é o roteamento. O modelo só escolhe a tool certa se três coisas estiverem alinhadas:

  1. Descrição rica e específica. Tool com descrição "busca documentos" é roleta russa.
  2. Poucas tools. Acima de 7–10 tools, a precisão de roteamento cai em todo modelo de fronteira (o paper do Berkeley Function Calling Leaderboard mostra isso há um ano).
  3. Eval de roteamento separado da eval de resposta final. Você precisa medir "para esta query, a tool X foi a chamada certa?" como métrica isolada — senão um agente que erra retrieval mas tem LLM bom mascara o problema com resposta plausível.

Esse é exatamente o tipo de pipeline que a gente coloca de pé do zero no Harness Engineering com Claude Code nos dias 16 e 17 de maio: dois turnos de manhã, ao vivo, construindo um app que recebe link de produto, sai pesquisar alternativas em e-commerce, compara preço e devolve recomendação estruturada — usando exatamente esse padrão de busca semântica para encontrar produtos similares, SQL para filtrar por preço/loja, e tools bem definidas para o Claude rotear, com Laravel e NativePHP por baixo.

Limitações e armadilhas

O que costuma queimar quem está montando esse stack:

  • Hybrid em pgvector exige tunar ts_rank. Idioma errado em to_tsvector mata a precisão silenciosamente. Use 'portuguese' (ou outro idioma específico) e teste com queries reais antes de confiar nos números.
  • Reranker em chat síncrono. 200–300ms é aceitável para workflow, é dolorido para chat. Se for chat, ou cacheia agressivo ou usa reranker só quando hybrid devolver muito candidato similar (decida por threshold de score).
  • SQL gerado por LLM sem sandbox. DROP TABLE esperando acontecer. Banco read-only, validação de AST, lista de tabelas permitidas. Não negocia.
  • Tools demais. Se o agente tem search_docs_semantic, search_docs_keyword, search_docs_hybrid e search_docs_rerank como tools separadas, você está fazendo o LLM tomar uma decisão de arquitetura. Esconda isso atrás de uma tool só, com a estratégia escolhida no servidor.
  • Embedding desatualizado. Trocou de modelo? Reindexa. Misturar dimensões/famílias é fonte de bug que ninguém vê até o usuário reclamar.

FAQ rápido

Posso pular hybrid e ir direto para reranker? Não. Reranker só reordena o que veio na primeira etapa. Se o documento certo não está no top-K do retrieval inicial, reranker nenhum salva. Conserte recall com hybrid antes.

BM25 ainda faz sentido em 2026? Sim, e vai continuar fazendo. Embedding não consegue o que BM25 faz com identificadores exatos, códigos de erro, números de série, nomes próprios raros. O paper de benchmarking de 2026 mostra BM25 batendo text-embedding-3-large em quase toda métrica em datasets com texto + tabela (arxiv 2604.01733).

Vale Postgres + pgvector ou banco vetorial dedicado? Para corpus até alguns milhões de chunks com filtros complexos por metadado, Postgres ganha — você junta SQL e vetor na mesma query, sem dois sistemas para sincronizar. Acima disso, ou se a latência de ANN começar a doer, banco dedicado entra. Não comece pelo dedicado.

Posso usar o LLM como reranker ("LLM-as-a-reranker")? Funciona, mas custa caro e é mais lento que reranker dedicado (Cohere, Voyage, Jina). Use LLM-as-a-reranker como baseline para validar o ganho potencial, não como produção.

Conclusão

A linha que separa um RAG de demo de um RAG de produto é essa: o de demo trata retrieval como um problema só, o de produto trata como três problemas (recall amplo, ranking preciso, dados estruturados) e dá uma ferramenta para cada um. Hybrid resolve recall barato. SQL como tool resolve a parte que vetor nunca ia resolver. Reranker no final aperta a precisão. Contextualizar chunk antes de indexar é o multiplicador silencioso que duplica o efeito de tudo que veio antes.

O próximo passo natural depois desse pipeline é avaliar — montar um set de 50–100 queries reais com gabarito e medir recall, precision e taxa de tool-route-correta a cada mudança. RAG sem eval é fé.

Lucas Souza
Lucas Souza

{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.

Você também pode gostar

RAG ou Web Search? Como decidir entre indexar, buscar ao vivo e combinar os dois
Tutoriais

RAG ou Web Search? Como decidir entre indexar, buscar ao vivo e combinar os dois

Quando usar RAG sobre catálogo interno, quando disparar busca na web ao vivo e quando combinar os dois? Matriz de decisão prática aplicada ao caso real de um agente de ofertas, com Claude API, Pinecone e LangChain. Trade-offs de custo, latência e controle sem hype.

· 7 min
Busca híbrida: a receita BM25 + vetor + RRF que resolve SKU, part-number e semântica
Tutoriais

Busca híbrida: a receita BM25 + vetor + RRF que resolve SKU, part-number e semântica

Embedding puro confunde "RX-7000" com "RX-5000". BM25 puro perde sinônimos. A receita certa é rodar os dois em paralelo e fundir os rankings com Reciprocal Rank Fusion. Neste post, a fórmula que sustenta tudo isso, o pipeline completo em Elasticsearch e como aplicar em catálogo de produto que mistura SKU, part-number e busca semântica.

· 13 min
Agente que pesquisa antes de agir: multi-tool + RAG em Laravel com pgvector
Tutoriais

Agente que pesquisa antes de agir: multi-tool + RAG em Laravel com pgvector

Como construir um agente em Laravel que decide quando buscar e quando responder direto. Arquitetura completa com Prism PHP, pgvector e a lógica de orquestração que separa demo de produto.

· 7 min
Alucinação em e-commerce é caro: quando a IA inventa especificação, cupom e estoque
Notícias

Alucinação em e-commerce é caro: quando a IA inventa especificação, cupom e estoque

Air Canada, DPD e Chevrolet mostraram em escala global o custo de deixar o LLM virar fonte de verdade no atendimento. Especificação inventada, cupom que não existe, estoque que não bate — vira chargeback, processo e dano de marca. O caminho técnico passa por retrieval grounded e tool use validando cada promessa.

· 12 min

VirguIA

beer & code assistant

conectando…

Não foi possível iniciar o chat agora.

tocando