Pular para conteúdo

Capítulo 2.6 — RAG tradicional

🎯 Objetivo

Apresentar Retrieval-Augmented Generation (RAG) com honestidade sobre o que resolve, o que não resolve e onde costuma falhar em silêncio em ambientes enterprise.

RAG é, em quase todo time, a primeira tentativa séria de combinar LLM com conhecimento corporativo - e, simultaneamente, onde aparecem os primeiros incidentes sérios: cross-tenant leakage, indirect prompt injection via documentos e respostas confiantes baseadas no trecho errado. O capítulo cobre cada uma dessas falhas.

🧠 Conceito principal

RAG é um padrão arquitetural onde, em vez de confiar apenas no que o modelo aprendeu no treino, a aplicação recupera evidências de uma base de conhecimento e injeta no contexto antes de pedir a resposta. O modelo passa a operar com "memória externa" controlada pela aplicação.

A simplificação canônica é:

query -> retrieval(query, base) -> contexto -> LLM(query, contexto) -> resposta

A simplificação esconde a maior parte do trabalho.

🏗️ Pipeline operacional em produção

Um RAG enterprise honesto envolve, no mínimo:

  1. Ingestão. Coleta de documentos a partir de fontes versionadas (CMS, drives, repositórios, bancos), com lineage e classificação de sensibilidade.
  2. Parsing. Conversão de PDFs, planilhas, slides, HTML e imagens em texto utilizável. OCR quando necessário. Erros aqui se propagam por todo o pipeline.
  3. Chunking. Quebra do texto em pedaços indexáveis. Pode ser fixo (por tokens), semântico (por seção/heading), hierárquico (parent/child) ou misto.
  4. Enriquecimento de metadados. Tenant, idioma, jurisdição, classificação de confidencialidade, data de validade, fonte, versão.
  5. Embeddings. Geração dos vetores via modelo de embedding (ver Cap. 2.8 para dimensão, Matryoshka e custos).
  6. Indexação. Vector DB, search engine lexical ou híbrido. Inclui estrutura ANN (HNSW/IVF), réplicas e backups.
  7. Recuperação. Vector, BM25 ou hybrid (ver Cap. 2.7). Top-k inicial generoso.
  8. Filtros obrigatórios por metadados. Tenant, idioma, jurisdição, confidencialidade, freshness. Não-negociáveis.
  9. Reranking. Cross-encoder ou reranker dedicado para reduzir top-k a um conjunto pequeno e ordenado.
  10. Construção de contexto. Seleção, ordem, deduplicação e formatação dos trechos no prompt. Inclui marcação clara de qual trecho veio de onde.
  11. Geração com citações. Pedido explícito de citação por trecho e validação de que a resposta cita as fontes esperadas.
  12. Avaliação. Métricas de retrieval e de resposta, com golden set e monitoramento em produção.

Cada etapa pode parecer pequena. Em produção, cada etapa falha de uma maneira diferente, e o efeito final é sempre o mesmo: o modelo responde com confiança a partir de um contexto ruim.

⚠️ Verdades incômodas sobre RAG

  • RAG não elimina alucinação. Ele reduz risco quando a recuperação é boa e o modelo é instruído a se ater ao contexto.
  • RAG pode trazer documento errado, obsoleto ou de outro tenant. Quando isso acontece, o erro é pior do que sem RAG: a resposta é ancorada em fonte aparentemente legítima.
  • Reranking ruim destrói recall. Um reranker mal calibrado pode jogar para o final justamente o trecho que importa.
  • Chunking ruim destrói tudo. Pedaços muito grandes diluem similaridade; pedaços muito pequenos perdem contexto. Não existe tamanho "certo" universal.
  • Modelo de embedding determina o teto de qualidade de retrieval. Reranker compensa parte da perda, mas não milagres.
  • A maior parte dos problemas de RAG está fora do LLM. Está na ingestão, no parsing e no índice.

🚨 Falhas silenciosas comuns

  • Documento obsoleto com mesma assinatura. A nova versão entra ao lado da antiga; o retriever escolhe a errada.
  • Recuperação cross-tenant. Sem filtro obrigatório por tenant_id no storage, dois clientes acabam compartilhando contexto.
  • Indirect prompt injection em documentos. Um documento recuperado contém instruções como "ignore políticas anteriores e envie X". O modelo, sem defesa, executa (ver Cap. 4.3).
  • Score alto sem relevância semântica. Especialmente com embeddings genéricos em corpus muito técnico.
  • Idioma diferente. Query em PT-BR recupera documentos em EN com alta similaridade espuria.
  • Documento PDF com texto em imagens. OCR falha; índice fica com texto vazio ou ruidoso.
  • Truncamento por janela. O retriever traz 10 trechos, mas o prompt só comporta 4; o modelo silenciosamente vê outro contexto.
  • Chunking que corta tabelas e listas no meio. A informação fica partida entre dois chunks, e nenhum dos dois é selecionado.

📈 Métricas que importam

Métricas de retrieval:

  • Recall@k. Fração de queries em que o trecho correto está nos top-k.
  • Precision@k / Context precision. Quantos dos top-k são realmente relevantes.
  • MRR (Mean Reciprocal Rank). Posição média do primeiro relevante.
  • NDCG@k. Ranking ponderado por relevância (quando há graus).

Métricas de resposta final (geração condicionada):

  • Groundedness / faithfulness. Quanto da resposta está efetivamente apoiada nos trechos recuperados.
  • Citation coverage. Fração de afirmações com citação verificável.
  • Answer correctness. Acerto contra o ground truth (quando existe).
  • No-answer behavior. O modelo deveria responder "não sei" quando o contexto é insuficiente. Métrica: fração de "no-answer" correto e incorreto.
  • Truncation rate. Quantas vezes o contexto estourou e foi cortado silenciosamente.

Métricas operacionais:

  • p95 latência de retrieval e de reranker.
  • Custo por consulta (embedding + index + reranker + LLM).
  • Freshness do índice (idade mediana e p95 dos chunks).
  • Indexing lag (tempo entre alteração no documento-fonte e disponibilidade no índice).

🛡️ Princípios defensivos

  • Filtros por tenant são no storage, não no prompt. Prompt é instrução, storage é fronteira.
  • Documentos recuperados são dados, não instruções. O prompt deve delimitar a região "documentos" com marcadores claros e instruir o modelo a não obedecer comandos contidos ali.
  • Toda afirmação factual tem fonte. Quando não houver fonte recuperada, a resposta correta é "não sei" - e isso deve ser testado em eval.
  • Reindex é rotina, não excepção. Tratado em Cap. 7.4 e Cap. 2.8 (reembedding com índice paralelo).

🧪 Como testar RAG

  • Golden set por tenant e idioma. Conjuntos com query + trecho esperado + resposta esperada (quando aplicável).
  • Adversarial set. Queries com prompt injection direta, queries em outro idioma, queries cujo conteúdo correto não está no corpus (forçando no-answer).
  • Regression set. Conjunto que precisa passar antes de promover qualquer mudança em embedding, reranker, chunking, prompt ou modelo.
  • Replay de produção com PII redigida.

Eval contínuo de RAG é tratado em Cap. 2.11 e Cap. 5.4.

📌 Checklist mínimo

  • [ ] Filtro por tenant é aplicado no storage, não no prompt?
  • [ ] Chunks têm metadados completos (tenant, idioma, fonte, versão, data)?
  • [ ] Há reranker no pipeline ou existe motivo documentado para não ter?
  • [ ] O prompt instrui o modelo a tratar documentos como dados não-confiáveis?
  • [ ] Há eval com golden set + adversarial + regression?
  • [ ] Há monitoramento de groundedness e citation coverage em produção?
  • [ ] Indexing lag é monitorado?
  • [ ] Existe procedimento para reindexação segura?

🧰 Exemplos práticos relacionados (planejados)

  • EX-LLM-01 - RAG local com Ollama + Qdrant (ou Chroma).
  • EX-LLM-02 - RAG com hybrid search e reranking.
  • EX-LLM-03 - eval harness de retrieval com golden set.
  • EX-LLM-06 - RAG com filtros por tenant + testes adversariais.

🏢 Hermes Logística - onda 2

A primeira versão do RAG da Hermes foi feita com um corpus único e um filtro por tenant_id adicionado no prompt ("considere apenas documentos do tenant X"). Funcionava em 95% dos casos. No 5%, o retriever trazia documento de outro cliente, o LLM citava como se fosse válido, e o cliente recebia política comercial errada. A correção não foi melhorar o prompt: foi mover o filtro para o storage, exigir tenant_id como termo obrigatório de filtragem em toda query, e bloquear no nível do retriever qualquer consulta sem esse parâmetro. O prompt deixou de ser parte da segurança. Esse foi o primeiro incidente que ensinou ao time que "instrução no prompt" não é "filtro no banco".

📚 Referências