RAG: por que "só jogar tudo no LLM" não funciona
"Mas por que não jogo o documento todo no contexto do LLM?" é a primeira pergunta de quem começa a trabalhar com IA em produção. A resposta envolve três limites duros — cutoff de treino, janela de contexto, alucinação — que juntos formam a parede onde RAG (Retrieval-Augmented Generation) se encaixa como solução. Este é o mapa do território.
Os três limites que criam a necessidade de RAG
| Limite | O que é | Por que quebra na prática |
|---|---|---|
| Cutoff de treino | Modelo só sabe o que existia até a data em que o dataset foi congelado | Pergunta sobre evento de ontem? Ele inventa. Dados internos do seu sistema? Nunca viu. |
| Janela de contexto | Limite físico de tokens por request (200k, 1M, 2M — depende do modelo) | Sua base tem 10GB. Mesmo modelo de 2M tokens não encaixa. E custo cresce linear no input. |
| Alucinação | LLM gera texto plausível, não verdadeiro. Sem âncora factual, inventa. | Respostas confiantes e erradas. Em suporte, compliance ou medicina, isso é inaceitável. |
A arquitetura em dois estágios
RAG separa duas responsabilidades. Primeiro, recupera os trechos relevantes à pergunta a partir de uma base indexada. Depois, gera a resposta usando esses trechos como contexto. O modelo vira um raciocinador sobre evidência fornecida, não uma enciclopédia.
Pipeline de indexação (offline)
Antes que qualquer pergunta seja feita, a base precisa ser preparada. Isso roda em batch — hora em hora, diário, ou via event-driven quando documentos mudam.
80% do resultado vem do pipeline de ingestão. Parser ruim (PDF mal extraído), chunks do tamanho errado (muito grande dilui sinal, muito pequeno corta contexto), embedding fraco (genérico para domínio técnico) — qualquer um dos três quebra o RAG. Na prática, a maior parte do tempo de engenharia de RAG está no ingestion, não no generate.
RAG vs Fine-tuning: quando usar cada um
📋 Time quer atualizar respostas com base em documentos que mudam toda semana
Atualizar RAG = re-indexar. Barato, rápido, sem downtime. Fine-tuning para base que muda semanalmente é retraining contínuo — caro e frágil.
Alt: Fine-tuning —
Alt: Longo contexto direto —
📋 Produto precisa de tom específico ("responda como um médico sênior, formal e cauteloso")
Tom e formato são skill, não fato. Fine-tuning com exemplos de estilo grava o comportamento no modelo. RAG para tom é prompt-engineering frágil.
Alt: System prompt detalhado —
Alt: RAG —
📋 Assistente de suporte com base de conhecimento interna + tom de marca
LoRA captura tom da marca e formato das respostas (skill). RAG fornece conteúdo atualizado da base (fato). Separação clara de responsabilidades.
Por que naive RAG (top-k + embedding) falha em produção
O tutorial de RAG que você leu na internet (embed + Pinecone + top-k=5 + prompt) bate em ~40-60% de precisão em bases reais. Para chegar em 85%+, precisa de pipeline sério: chunking inteligente, hybrid search (BM25+vector), reranking, query transformation, metadados estruturados.
| Problema | Sintoma | Mitigação |
|---|---|---|
| Chunks do tamanho errado | Resposta falta contexto (chunk pequeno) ou dilui o sinal (chunk grande) | Recursive chunking + contextual retrieval (Anthropic) |
| Embedding ignora intenção | "Cancelar conta" e "Criar conta" têm similaridade alta | Hybrid search (BM25 + vector) com RRF |
| Top-k fixo | Documento denso devolve 5 chunks quase idênticos | MMR (maximal marginal relevance) para diversidade |
| Sem reranking | Posição 1-5 tem ruído misturado com relevante | Cross-encoder rerank (Cohere, Voyage, Jina) |
| Query ambígua | "Como isso funciona?" sem contexto volta tudo genérico | HyDE, query expansion, multi-query |
Código mínimo: naive RAG em Python
Exemplo pedagógico — serve para ver o fluxo, não para produção.
# Pipeline mínimo: docs → embeddings → query → LLM
from anthropic import Anthropic
import numpy as np
from sentence_transformers import SentenceTransformer
client = Anthropic()
embedder = SentenceTransformer("BAAI/bge-m3")
# 1. Indexação (offline)
docs = [
"Para cancelar conta, acesse Configurações → Conta → Encerrar.",
"Para criar conta nova, vá em cadastrar.com/novo e preencha email.",
"Senha esquecida? Use o link 'recuperar' na tela de login.",
]
doc_vecs = embedder.encode(docs, normalize_embeddings=True)
# 2. Retrieval (online)
def retrieve(query: str, k: int = 2) -> list[str]:
q_vec = embedder.encode(query, normalize_embeddings=True)
scores = doc_vecs @ q_vec # cosine (vetores já normalizados)
top_idx = np.argsort(-scores)[:k]
return [docs[i] for i in top_idx]
# 3. Generate
def answer(query: str) -> str:
context = "\n\n".join(retrieve(query))
msg = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{
"role": "user",
"content": f"Responda a partir do contexto. Se não estiver no contexto, diga que não sabe.\n\nContexto:\n{context}\n\nPergunta: {query}"
}],
)
return msg.content[0].text
print(answer("Esqueci minha senha, o que faço?"))Rode isso, veja funcionar, e entenda: é o degrau zero. Em produção você troca o embedder genérico por domain-tuned, adiciona reranker, metadata filtering, chunking com overlap contextual, citações, e um evaluation harness que mede recall@k e faithfulness a cada deploy.
Perguntas típicas
❓ RAG funciona com modelo local (Llama, Mistral)?
❓ Janela de contexto grande (1M+ tokens) mata RAG?
❓ Preciso de vector DB dedicado (Pinecone, Weaviate, Qdrant) ou pgvector serve?
❓ E se a pergunta exige sintetizar 30 documentos? Top-k=5 não basta.
Take-aways. RAG = separar fato (externo, atualizável) de raciocínio (modelo). Pipeline tem dois estágios (retrieve + generate) com métricas próprias. Naive RAG é degrau zero, não produção. Qualidade vem do ingestion (parser + chunker + embedder + metadata), não do generate. Os próximos módulos vão fundo em cada peça — chunking, hybrid search, reranking e evaluation.
Próximos passos sugeridos
Discussão
Carregando…