Blog
RAGLLMSearchPostgreSQL

RAG em produção: busca híbrida, eval e quando não usar

·10 min de leitura

A maioria dos tutoriais de RAG para em “embute chunks, similaridade de cosseno, cola no prompt”. Isso funciona no notebook. Em produção falha em silêncio: a resposta soa confiante, a citação está errada, a latência dispara na segunda-feira e ninguém sabe se foi a última mudança de prompt ou o último reindex.

Este estudo trata da camada de engenharia depois do demo: desenho de retrieval, busca híbrida, harness de avaliação e a decisão de não usar RAG.

O que o RAG realmente compra

RAG não é “o modelo conhece seus documentos”. É injeção de contexto limitada: na hora da resposta você busca um conjunto pequeno de passagens e pede ao modelo que se mantenha nelas. Você troca:

  • Prós: conhecimento mais fresco sem fine-tune completo, fontes auditáveis, isolamento por tenant no índice.
  • Contras: erro de retrieval vira erro de resposta; orçamento de tokens limita o que entra; embedding e chunking fazem parte do produto.
Times que entregam tratam índice, retriever e montagem de prompt como um subsistema com métricas, não três scripts soltos.

Chunking é decisão de produto

Não existe tamanho universal de chunk. O que você otimiza depende de como o usuário pergunta.

| Estratégia | Bom quando | Modo de falha | |------------|------------|---------------| | Janelas fixas de tokens | Docs uniformes, perguntas estilo FAQ | Parte tabelas e procedimentos no meio do passo | | Parágrafo / heading-aware | Políticas, runbooks, KB em markdown | Seções curtas sem contexto local | | Parent–child (retrieve pequeno, ler grande) | PDFs longos, texto legal ou spec | Mais storage e join na leitura | | Chunks com metadata (produto, região, versão) | Suporte multi-catálogo | Metadata ruim → escopo errado em silêncio |

Overlap ajuda continuidade; também multiplica o índice e pode duplicar parágrafos contraditórios de versões diferentes do doc. Versionar documentos fonte e reindexar de forma idempotente (apagar vetores antigos do document_id, depois inserir) vence caos append-only.

Regra prática: registre os chunk ids que entraram no prompt. Quando o suporte diz “o bot mentiu sobre garantia”, você reproduz esse trace em minutos.

Drift de embedding sem drama

Mesmo texto, novo modelo de embedding, novo espaço vetorial — vetores antigos e novos não devem coexistir no mesmo índice a menos que haja dual-write e cutover explícito. Checklist de produção:

1. Fixar id do modelo de embedding na config (text-embedding-3-small, etc.). 2. Job de reindex observável: documentos/hora, falhas, guardas de dimensão. 3. Limiares de similaridade são por corpus, não copiados de artigo. Limiar 0,55 não significa nada sem a distribuição de scores do seu índice.

Quando a qualidade cai após update do vendor, assuma que o retrieval se moveu antes de reescrever o system prompt.

Busca híbrida: quando vetor sozinho não basta

Retrieval denso puro perde tokens exatos: SKUs, códigos de erro, nomes internos de projeto, “seção 4.2.1”. Padrões híbridos que aguentam:

  • Denso + BM25 (ou tsvector no Postgres) com reciprocal rank fusion ou pesos aprendidos.
  • Pré-filtro em metadata (organization_id, product_line, doc_status=published) antes da similaridade — RAG multi-tenant que pula isso vaza contexto entre orgs de formas sutis.
  • Reescrita de query (HyDE, sub-queries) só depois de medir recall baseline; LLM extra adiciona latência e novos modos de falha.
Em Postgres + pgvector, mantenha uma função de retrieval com escopo no SQL (filtro de org dentro da função, não só no app). Código de aplicação não deve ser o único lugar que impõe tenancy.

Grounding e abstinência

Retriever que não acha nada útil não deve disparar completion criativa. Padrões que passam em auditoria:

  • Ramo explícito “sem fontes relevantes”: handoff humano ou pergunta de esclarecimento, não chute.
  • Exigir que o modelo cite chunk ids ou títulos de fonte em ferramentas internas; rejeitar respostas que citam ids fora do bloco de contexto (checagem programática onde couber).
  • Limitar tamanho do contexto injetado; truncar com estrutura (“Fonte 1: …”) para referência consistente.
Grounding é política, não frase mágica no system message.

Avaliação antes e depois de cada mudança

Se você só avalia lendo dez chats no Slack, vai shippar regressão. Loop mínimo de eval:

1. Golden set: perguntas reais (redigidas) + doc ids de suporte esperados ou rubrica de resposta. 2. Métricas de retrieval: recall@k, MRR em pares q→doc rotulados; separado da qualidade de geração. 3. Métricas de geração: LLM-as-judge com spot-check humano, ou só humano em domínios de risco. 4. Gate de CI: golden set pequeno em PRs que mexem em chunking, embeddings ou SQL de retrieval.

Regressão em recall@5 é blocker; ajustar prompt para “soar melhor” com recall em queda é como times perdem confiança.

Quando não usar RAG

RAG é ferramenta errada quando:

  • O conhecimento é pequeno e estável — cabe no system prompt ou schema de tool compilado.
  • Você precisa de cálculo determinístico (preço, elegibilidade) — use código ou motor de regras; LLM explica o resultado.
  • SLO de latência apertado e corpus enorme — considere resumos pré-computados, roteamento para sub-agentes ou modelos pequenos fine-tuned em snapshot congelado.
  • Classificação de segurança impede misturar passagens num prompt — particione índices e roteie a query antes.
Fine-tune e RAG são complementares: fine-tune tom e formato de tool; RAG carrega fatos que mudam toda semana.

Operar o stack

O que times sênior instrumentam:

  • p95 de latência de retrieval e duração de batch de embedding
  • Taxa de hit vazio e abaixo do limiar por org
  • Contagem de tokens do contexto montado vs limite do modelo
  • Traces ligando request_id → chunk ids → id da completion
Alertas em pico de hit vazio muitas vezes antecedem queda de NPS.

Encerramento

RAG em produção é engenharia de busca + política + observabilidade, com um LLM no fim. Chunking, retrieval híbrido, filtros seguros por tenant, abstinência e golden set separam demo de algo que jurídico e ops rodam.

Se você já opera um stack específico de produto (voz, inbox, workers no Kafka), mantenha uma espinha de retrieval e pendure canais nela — retriever duplicado por superfície é onde a divergência começa.