Aula 2: RAG - Busca Inteligente em Documentos
Video da aula
Video em breve. O conteudo completo em texto esta disponivel abaixo.
O que voce vai aprender
- O que e RAG e por que e tao importante
- Como transformar documentos em vetores (embeddings)
- Buscar trechos relevantes com similaridade semantica
- Fazer o LLM responder com base nos seus documentos
- Construir um sistema completo de perguntas e respostas
O que e RAG?
RAG (Retrieval-Augmented Generation) resolve o maior problema dos LLMs: eles nao conhecem seus dados privados. A ideia e simples:
- Indexar: Transformar seus documentos em vetores numericos (embeddings)
- Buscar: Quando o usuario faz uma pergunta, encontrar os trechos mais relevantes
- Gerar: Enviar esses trechos como contexto para o LLM responder
Pergunta do usuario
|
v
[Busca por similaridade nos documentos]
|
v
[Trechos relevantes encontrados]
|
v
[LLM + Contexto = Resposta precisa]
1. Instalando dependencias extras
pip install langchain-community chromadb pypdf sentence-transformers
chromadb: banco de vetores local (sem servidor)pypdf: leitor de PDFssentence-transformers: modelos de embedding gratuitos
2. Carregando um PDF
Crie o arquivo rag.py:
import os
from dotenv import load_dotenv
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
load_dotenv()
# Carregar PDF
loader = PyPDFLoader("documento.pdf") # coloque qualquer PDF aqui
pages = loader.load()
print(f"Carregadas {len(pages)} paginas")
# Dividir em chunks menores
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # ~1000 caracteres por chunk
chunk_overlap=200, # sobreposicao para nao perder contexto
separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(pages)
print(f"Criados {len(chunks)} chunks")
Por que dividir em chunks? Porque enviar um PDF inteiro para o LLM e caro e lento. Em vez disso, enviamos apenas os trechos relevantes.
3. Criando embeddings e armazenando
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
# Modelo de embedding gratuito e local
embeddings = HuggingFaceEmbeddings(
model_name="all-MiniLM-L6-v2"
)
# Criar banco de vetores com os chunks
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # salva em disco
)
print(f"Banco de vetores criado com {vectorstore._collection.count()} documentos")
Na primeira execucao, o modelo de embedding sera baixado (~80MB). Depois fica em cache.
4. Buscando por similaridade
# Buscar os 3 trechos mais relevantes
query = "Qual e o objetivo principal do documento?"
results = vectorstore.similarity_search(query, k=3)
for i, doc in enumerate(results):
print(f"\n--- Resultado {i+1} ---")
print(f"Pagina: {doc.metadata.get('page', '?')}")
print(f"Conteudo: {doc.page_content[:200]}...")
A busca por similaridade compara o vetor da sua pergunta com os vetores dos chunks e retorna os mais proximos semanticamente.
5. RAG completo: pergunta + contexto + LLM
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Template que combina contexto + pergunta
prompt = ChatPromptTemplate.from_template("""
Responda a pergunta baseando-se exclusivamente no contexto fornecido.
Se a resposta nao estiver no contexto, diga "Nao encontrei essa informacao no documento."
Contexto:
{context}
Pergunta: {question}
Resposta:
""")
def ask(question: str) -> str:
"""Busca contexto relevante e gera resposta."""
# 1. Buscar trechos relevantes
docs = vectorstore.similarity_search(question, k=3)
context = "\n\n".join([doc.page_content for doc in docs])
# 2. Montar prompt com contexto
chain = prompt | llm
# 3. Gerar resposta
response = chain.invoke({
"context": context,
"question": question
})
return response.content
# Loop interativo
if __name__ == "__main__":
print("RAG iniciado. Faca perguntas sobre seu documento.\n")
while True:
q = input("Pergunta: ").strip()
if q.lower() in ("sair", "exit"):
break
if not q:
continue
answer = ask(q)
print(f"\nResposta: {answer}\n")
6. Carregando multiplos documentos
Para indexar uma pasta inteira de PDFs:
from pathlib import Path
def load_all_pdfs(folder: str):
"""Carrega todos os PDFs de uma pasta."""
all_chunks = []
for pdf_path in Path(folder).glob("*.pdf"):
loader = PyPDFLoader(str(pdf_path))
pages = loader.load()
chunks = splitter.split_documents(pages)
all_chunks.extend(chunks)
print(f" {pdf_path.name}: {len(pages)} paginas, {len(chunks)} chunks")
return all_chunks
chunks = load_all_pdfs("./documentos")
vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="./chroma_db")
7. Dicas de performance
| Dica | Por que |
|---|---|
chunk_size=1000 | Chunks muito grandes diluem a relevancia |
chunk_overlap=200 | Evita cortar frases no meio |
k=3 na busca | Mais que 5 resultados raramente ajuda |
| Embeddings locais | all-MiniLM-L6-v2 e rapido e gratuito |
temperature=0 | Respostas factuais devem ser deterministicas |
Exercicio pratico
- Baixe um PDF tecnico (documentacao de uma lib, artigo, manual)
- Indexe com o codigo acima
- Faca 5 perguntas e avalie a qualidade das respostas
- Experimente mudar
chunk_sizepara 500 e 2000 e compare os resultados
Proximo passo
Na Aula 3, vamos adicionar memoria ao nosso agente para que ele mantenha o contexto da conversa. Assim ele pode fazer perguntas de acompanhamento como “explique melhor o ponto 2” sem voce repetir o contexto.