RAG Explicado: O que É e Por que Todo AI Engineer Usa
Guia completo sobre Retrieval-Augmented Generation
•18 min de leitura
RAG (Retrieval-Augmented Generation) é a técnica mais importante para criar chatbots e assistentes úteis. É o que permite o ChatGPT responder sobre seus documentos.
O Problema que RAG Resolve
Sem RAG:
Usuário: "Qual o prazo de devolução da minha loja?"
ChatGPT: "Não tenho informação sobre sua loja específica.
Geralmente varia entre 7-30 dias..."
O modelo não conhece seus dados específicos!
Com RAG:
1. Sistema busca: "política de devolução" nos seus docs
2. Encontra: "Devoluções: até 15 dias após entrega"
3. Passa para o LLM junto com a pergunta
4. LLM responde: "O prazo de devolução é de 15 dias
após a entrega do produto."
Agora o modelo usa suas informações reais!Como RAG Funciona
Fluxo RAG:
┌─────────────────────────────────────────────────────────┐
│ INDEXAÇÃO (offline) │
├─────────────────────────────────────────────────────────┤
│ Documentos → Chunks → Embeddings → Vector Database │
│ │
│ "Manual.pdf" → ["O prazo...", "Para devolver..."] │
│ ↓ │
│ [0.12, -0.34, 0.56, ...] │
│ ↓ │
│ Pinecone/Chroma │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ CONSULTA (runtime) │
├─────────────────────────────────────────────────────────┤
│ 1. Pergunta → Embedding → Busca no Vector DB │
│ │
│ "Qual o prazo?" → [0.11, -0.35, 0.55] → Top 5 chunks │
│ │
│ 2. Chunks relevantes + Pergunta → LLM → Resposta │
│ │
│ Contexto: "Prazo de devolução: 15 dias..." │
│ Pergunta: "Qual o prazo de devolução?" │
│ LLM: "O prazo de devolução é de 15 dias." │
└─────────────────────────────────────────────────────────┘Implementação Básica
1. Instalar Dependências
pip install langchain langchain-openai chromadb2. Carregar e Dividir Documentos
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Carregar PDF
loader = PyPDFLoader("manual_produto.pdf")
documents = loader.load()
# Dividir em chunks
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # Tamanho de cada chunk
chunk_overlap=200, # Sobreposição entre chunks
separators=["\n\n", "\n", ".", " "]
)
chunks = splitter.split_documents(documents)
print(f"Documento dividido em {len(chunks)} chunks")3. Criar Embeddings e Vector Store
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# Criar embeddings
embeddings = OpenAIEmbeddings()
# Criar vector store
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # Salvar em disco
)
# Persistir
vectorstore.persist()4. Criar Chain de Q&A
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# LLM
llm = ChatOpenAI(model="gpt-4", temperature=0)
# Prompt customizado
template = """Use o contexto abaixo para responder a pergunta.
Se não souber a resposta, diga "Não encontrei essa informação".
Contexto:
{context}
Pergunta: {question}
Resposta:"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
# Criar chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # Usa todos os docs de uma vez
retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
chain_type_kwargs={"prompt": prompt}
)
# Usar
response = qa_chain.run("Qual o prazo de devolução?")
print(response)Estratégias de Chunking
Como dividir seus documentos faz MUITA diferença:
# 1. Por tamanho fixo (mais simples)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
# 2. Por sentenças (melhor para textos)
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=50
)
# 3. Por seções/headers (para docs estruturados)
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers = [("#", "Header 1"), ("##", "Header 2")]
splitter = MarkdownHeaderTextSplitter(headers)
# 4. Semântico (avançado)
# Agrupa por significado, não por tamanho
Recomendações:
- chunk_size: 500-1500 tokens
- chunk_overlap: 10-20% do chunk_size
- Teste diferentes tamanhos para seu caso de usoVector Databases
| Database | Tipo | Melhor para |
|---|---|---|
| Chroma | Local/Embedded | Prototipação, projetos pequenos |
| Pinecone | Cloud (managed) | Produção, escala |
| Weaviate | Self-hosted/Cloud | Features avançadas |
| Qdrant | Self-hosted/Cloud | Performance, filtering |
| pgvector | PostgreSQL extension | Já usa Postgres |
RAG vs Fine-tuning
QUANDO USAR RAG:
✅ Dados mudam frequentemente
✅ Precisa citar fontes
✅ Muitos documentos diferentes
✅ Quer controlar que informação é usada
✅ Budget limitado
QUANDO USAR FINE-TUNING:
✅ Quer mudar o "estilo" do modelo
✅ Tarefa muito específica
✅ Dados não mudam
✅ Performance crítica
✅ Tem muitos dados de treino
QUANDO USAR AMBOS:
✅ Fine-tune para estilo/formato
✅ RAG para informações factuaisTécnicas Avançadas
1. Hybrid Search
# Combinar busca semântica + keyword
from langchain.retrievers import BM25Retriever, EnsembleRetriever
# Busca por keywords (BM25)
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 5
# Busca semântica
semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# Combinar (50% cada)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, semantic_retriever],
weights=[0.5, 0.5]
)2. Reranking
# Buscar mais docs, depois reordenar por relevância
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
# Reranker
compressor = CohereRerank()
# Retriever com reranking
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 20})
)
# Busca 20 docs, reordena, retorna top 53. Query Expansion
# Gerar múltiplas versões da query
def expand_query(query: str) -> list[str]:
"""Gera variações da pergunta para melhor retrieval."""
response = llm.invoke(f"""
Gere 3 formas diferentes de fazer esta pergunta:
"{query}"
Retorne apenas as perguntas, uma por linha.
""")
queries = [query] + response.content.strip().split("\n")
return queries
# Buscar com todas as variações
all_docs = []
for q in expand_query(user_query):
docs = retriever.get_relevant_documents(q)
all_docs.extend(docs)
# Remover duplicados e usar4. Self-Query
# LLM extrai filtros da pergunta
from langchain.retrievers.self_query.base import SelfQueryRetriever
metadata_field_info = [
AttributeInfo(
name="category",
description="Categoria do documento",
type="string"
),
AttributeInfo(
name="date",
description="Data do documento",
type="date"
)
]
retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
document_content_description="Documentos da empresa",
metadata_field_info=metadata_field_info
)
# Query: "Políticas de 2024"
# → Busca semântica "políticas" + filtro date >= 2024-01-01Métricas e Avaliação
Como saber se seu RAG está bom?
1. RETRIEVAL METRICS
- Precision@K: % de docs relevantes nos top K
- Recall@K: % de docs relevantes encontrados
- MRR: Posição média do primeiro doc relevante
2. GENERATION METRICS
- Faithfulness: Resposta é fiel ao contexto?
- Answer Relevancy: Responde a pergunta?
- Context Relevancy: Contexto é útil?
3. E2E METRICS
- User satisfaction
- Task completion rate
Ferramentas:
- Ragas (open source)
- LangSmith (LangChain)
- Arize PhoenixChecklist de Produção
Antes de deployar seu RAG:
□ Chunking otimizado para seu conteúdo
□ Overlap suficiente (10-20%)
□ Embeddings de qualidade (text-embedding-ada-002+)
□ K retrieval testado (geralmente 3-5)
□ Prompt com instruções claras
□ Fallback quando não encontra resposta
□ Citação de fontes
□ Monitoramento de queries sem resposta
□ Pipeline de atualização de documentos
□ Testes com queries reais de usuários