Modelos de Linguagem de Grande Escala (LLMs) como GPT-4 e Claude são extraordinariamente poderosos, mas sofrem de uma limitação fundamental: seu conhecimento é congelado no momento do treinamento. Eles não podem acessar seus documentos internos, seu banco de dados ou informações em tempo real. A Geração Aumentada por Recuperação (RAG) resolve exatamente esse problema, combinando o poder generativo dos LLMs com a capacidade de recuperar informações de fontes externas.
O Problema: Limitações dos LLMs
Antes de falar sobre RAG, é importante entender por que precisamos dele.
- Conhecimento estático: Um LLM só conhece o que viu durante o treinamento. Se você perguntar sobre um evento que ocorreu após seu corte, ele não pode responder.
- Alucinações: Quando um LLM não sabe a resposta, ele tende a inventar uma, gerando informações plausíveis mas completamente falsas.
- Sem acesso a dados privados: Um LLM genérico não tem acesso à documentação interna da sua empresa, tickets ou código-fonte.
RAG aborda todos esses três problemas fornecendo ao modelo contexto relevante recuperado de fontes externas no momento da consulta.
O que é RAG?
Geração Aumentada por Recuperação é uma arquitetura que enriquece o prompt enviado a um LLM com informações recuperadas de uma base de conhecimento externa. Em vez de depender apenas do conhecimento paramétrico do modelo, RAG busca informações relevantes primeiro e depois as injeta no prompt, permitindo que o modelo gere respostas precisas e fundamentadas.
Como RAG Funciona em Detalhe
A arquitetura RAG consiste em duas fases principais: Indexação (offline) e Recuperação + Geração (online).
Fase 1: Indexação (Ingestão de Documentos)
A fase de indexação prepara seus documentos para busca semântica. Ela consiste em quatro etapas.
1. Carregamento de Documentos
Os documentos podem vir de qualquer fonte: arquivos PDF, páginas web, bancos de dados, arquivos Markdown, APIs. O Document Loader lê esses documentos e os converte em texto estruturado.
2. Divisão de Texto (Chunking)
Os LLMs têm uma janela de contexto limitada, e os documentos podem ser muito longos. O Text Splitter divide os documentos em fragmentos menores chamados chunks. A qualidade do chunking é crítica: chunks muito pequenos perdem contexto, enquanto chunks muito grandes diluem a relevância.
As estratégias mais comuns são:
- Divisão Recursiva por Caracteres: Divide recursivamente o texto usando separadores como
\n\n,\n,., respeitando a estrutura do documento. - Divisão Semântica: Usa embeddings para encontrar pontos de quebra naturais no texto.
- Sobreposição de Chunks: Inclui sobreposição entre chunks consecutivos para preservar o contexto nas fronteiras.
3. Embedding
Cada chunk é transformado em um vetor numérico (embedding) por meio de um modelo de embedding (como o text-embedding-3-small da OpenAI). Esses vetores capturam o significado semântico do texto: frases com significados semelhantes terão vetores próximos no espaço multidimensional.
4. Vector Store
Os vetores são salvos em um Vector Store (ou banco de dados vetorial), como ChromaDB, Pinecone, Weaviate ou FAISS. Este banco de dados é otimizado para busca por similaridade: dada uma consulta, ele encontra os vetores mais semelhantes (e portanto os chunks de texto mais relevantes).
Fase 2: Recuperação + Geração
Quando o usuário faz uma pergunta:
- A pergunta é transformada em um embedding usando o mesmo modelo de embedding.
- O Vector Store encontra os chunks mais semelhantes via busca por similaridade (tipicamente similaridade de cosseno ou distância euclidiana).
- Os chunks recuperados são inseridos no prompt como contexto.
- O LLM gera uma resposta baseada no contexto fornecido.
Construindo um Pipeline RAG com LangChain
LangChain é o framework Python (e JavaScript) mais popular para construir aplicações alimentadas por LLMs. Ele fornece abstrações de alto nível para cada componente do pipeline RAG.
Instalação
pip install langchain langchain-openai langchain-community chromadb
Passo 1: Carregar Documentos
LangChain fornece dezenas de Document Loaders para diferentes fontes de dados.
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # Load a PDF pdf_loader = PyPDFLoader("docs/manual.pdf") pdf_docs = pdf_loader.load() # Load a web page web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # Load all .md files from a directory dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
Passo 2: Dividir Documentos em Chunks
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"Original documents: {len(all_docs)}, Chunks: {len(chunks)}")
O parâmetro chunk_overlap é crucial: ele cria sobreposição entre chunks consecutivos para que o contexto não seja perdido nas fronteiras.
Passo 3: Criar Embeddings e Vector Store
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
Passo 4: Criar o Retriever
O retriever é o componente que, dada uma consulta, busca os chunks mais relevantes do vector store.
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("How does authentication work?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
Passo 5: Construir a Cadeia RAG
Agora vamos juntar tudo com um LLM e um template de prompt.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" Answer the question based only on the provided context. If the context does not contain enough information, say you don't know. Context: {context} Question: {question} Answer: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("How does authentication work in the system?") print(response)
Técnicas Avançadas de RAG
O pipeline básico funciona bem, mas existem várias técnicas para melhorar significativamente a qualidade das respostas.
Recuperação Multi-Query
Às vezes a consulta do usuário é ambígua ou não está alinhada com a linguagem usada nos documentos. O Multi-Query Retriever gera automaticamente variantes da pergunta original para capturar múltiplas perspectivas.
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("What are the security best practices?")
Compressão Contextual
Nem todo conteúdo em um chunk é relevante para a consulta. O Contextual Compression Retriever usa um LLM para extrair apenas as partes pertinentes de cada chunk recuperado.
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
Busca Híbrida
A busca puramente semântica nem sempre é ideal. A Busca Híbrida combina busca semântica (embeddings) com busca lexical (BM25, correspondência de palavras-chave) para alcançar melhores resultados.
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
RAG Conversacional (com Memória)
Para construir um chatbot RAG que lembra o contexto da conversa, é necessário adicionar memória que reformula as perguntas do usuário levando em conta o histórico da conversa.
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "Given the chat history and the user's latest question, " "reformulate the question so it is understandable without the history."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
Boas Práticas
- Escolha o tamanho certo do chunk: Experimente com diferentes tamanhos (500-1500 tokens). Chunks menores para respostas precisas, maiores para contexto mais amplo.
- Use metadados dos documentos: Adicione fonte, data e categoria como metadados aos chunks. Isso permite filtrar resultados durante a recuperação.
- Avalie a qualidade: Use frameworks como RAGAS para medir métricas como faithfulness, relevancy e context precision.
- Gerencie atualizações de documentos: Implemente um pipeline de re-ingestão para manter o vector store sincronizado com suas fontes de dados.
- Adicione um re-ranker: Após a recuperação inicial, use um modelo de re-ranking (como Cohere Rerank) para reordenar os resultados com base na relevância real.
Conclusão
RAG se tornou a arquitetura padrão para construir aplicações de IA que precisam de acesso a conhecimento específico e atualizado. LangChain simplifica enormemente a implementação, fornecendo abstrações para cada componente do pipeline.
Próximos passos:
- Experimente localmente: Comece com ChromaDB e alguns documentos para se familiarizar com o pipeline.
- Explore o LangSmith: Use o LangSmith para monitorar e depurar suas cadeias em produção.
- Experimente diferentes modelos de embedding: Compare modelos como
text-embedding-3-small,text-embedding-3-largee modelos open-source do Sentence Transformers. - Consulte a documentação: A documentação do LangChain é um recurso excelente e constantemente atualizado.