RAG - Retrieval-Augmented Generation

RAG (Retrieval-Augmented Generation) é uma abordagem focada em recuperar informações relevantes de uma base de conhecimento e alimentar o contexto do modelo com esses dados antes de gerar a resposta.

Isso garante algumas condições:


O RAG funciona em três etapas principais:

  1. Indexação
    • Dividimos documentos em pedaços menores (chunks). Isso deve ser feito de forma a permitir que o modelo supere as limitações do tamanho do conteudo ao ingerir uma grande quantidade da dados, de forma que a janela de contexto do modelo usado é um limitante nessa fase.
    • Geramos vetores de embeddings para cada pedaço usando um modelo de embeddings, esses modelos trabalham a partir do mapeamento entre os tokens e um espaço vetorial para o chunk, de forma que todos os vetores possam ser posteriormente operados e comparados entre si.
    • Armazenamos esses vetores em um banco vetorial, especializados em armazenamento para vetores de alta dimensionalidade (ex.: Chroma, FAISS).
  2. Recuperação (Retrieval)
    • Quando o usuário faz uma pergunta, geramos o embedding da pergunta.
    • Buscamos no banco vetorial os pedaços mais semelhantes.
  3. Geração (Generation)
    • Passamos os pedaços recuperados como contexto para a LLM.
    • A LLM usa esse contexto para gerar uma resposta mais precisa.

Entendendo o Funcionamento do RAG em Detalhes

O RAG (Retrieval-Augmented Generation) é uma técnica que combina recuperação de informações com geração de texto para melhorar a precisão e relevância das respostas de uma LLM. Ele é dividido em três fases principais: Indexação, Recuperação e Geração.


1. Indexação

Na fase de indexação, preparamos nossa base de conhecimento para ser consultada rapidamente no futuro.

Passos: 1. Divisão em pedaços (chunks) - Documentos grandes são divididos em partes menores (ex.: 200 a 500 caracteres ou tokens). - Isso garante que cada pedaço seja pequeno o suficiente para ser processado e comparado de forma eficiente.

  1. Geração de embeddings
    • Um modelo de embeddings transforma cada pedaço de texto em um vetor numérico de alta dimensão (ex.: 768 ou 1536 dimensões).
    • Esses vetores representam o significado semântico do texto, de forma que textos com significados semelhantes fiquem próximos no espaço vetorial.
    • Exemplos de modelos de embeddings:
      • OpenAI Embeddings (text-embedding-3-small / text-embedding-3-large)
      • Sentence Transformers (ex.: all-MiniLM-L6-v2)
      • Cohere Embeddings
      • HuggingFace Transformers (modelos open-source)
  2. Armazenamento em banco vetorial
    • Os vetores gerados são armazenados em um banco vetorial (Vector Store), como:
      • Chroma (open-source, fácil de usar)
      • FAISS (Facebook AI Similarity Search — muito rápido e eficiente)
      • Pinecone (serviço gerenciado, escalável)
      • Weaviate, Milvus (opções open-source e distribuídas)
    • O banco vetorial mantém a associação entre vetor e conteúdo original.

2. Recuperação (Retrieval)

Quando o usuário faz uma pergunta, o processo é:

  1. Gerar embedding da pergunta
    • Usamos o mesmo modelo de embeddings para transformar a pergunta em um vetor.
  2. Comparar com os vetores armazenados
    • O banco vetorial calcula a similaridade entre o vetor da pergunta e os vetores dos documentos.
    • Os pedaços mais semelhantes são retornados como contexto relevante.

Métricas de comparação mais comuns:

  • Cosine Similarity (similaridade do cosseno) Mede o ângulo entre dois vetores.
    • Vantagem: ignora magnitude, foca apenas na direção (bom para embeddings normalizados).
    • Escala: -1 (opostos) a 1 (iguais).
  • Dot Product (produto interno/escalar) Mede a projeção de um vetor sobre outro.
    • Vantagem: rápido e simples, mas sensível à magnitude.
  • Euclidean Distance (distância euclidiana) Mede a distância “reta” entre dois pontos no espaço vetorial.
    • Vantagem: intuitivo, mas pode ser menos robusto em alta dimensionalidade.

Em muitos casos, Cosine Similarity é preferida para embeddings de texto, pois foca na similaridade semântica e não na magnitude do vetor.


3. Geração (Generation)

  1. Passar contexto para a LLM
    • Os pedaços recuperados são concatenados e enviados junto com a pergunta original para a LLM.
    • Isso é feito no prompt, geralmente com uma instrução como: > “Use apenas as informações abaixo para responder à pergunta.”
  2. Resposta mais precisa
    • A LLM usa o contexto fornecido para gerar uma resposta fundamentada.
    • Isso reduz alucinações e aumenta a confiabilidade.

O processo do RAG em aplicações de produção pode ser dividido em duas fases distintas:

1. Fase de Pré-processamento (Indexação)

Essa fase é executada antes das consultas, geralmente uma única vez ou sempre que novos documentos são adicionados e/ou atualizados.

  1. Coletar documentos.

  2. Dividir em pedaços menores (chunks).

  3. Gerar embeddings para cada pedaço.

  4. Armazenar os embeddings em um banco vetorial.

2. Fase de Execução (Consulta)

Essa fase ocorre em tempo real, sempre que o usuário faz uma pergunta.

Passos: 1. Receber a pergunta do usuário.

  1. Gerar embedding da pergunta.

  2. Buscar no banco vetorial os pedaços mais semelhantes.

  3. Passar o contexto recuperado para a LLM.

  4. Gerar a resposta final.

Existem estrategias auxiliares para melhoria de sistemas RAG, como por exemplo aproveitar o processamento robusto dos tokens para extrair metadados dos textos que auxiliem a estruturação dos dados para recuperação das informações, uma vez que a eficiencia do retorno é muito sensivel ao top-k resultados, podendo deixar uma quantidade relevante de informação fora caso trate de uma area que seja muito densa em informações similares.


Exemplo: RAG Híbrido (Documentos Internos + Busca na Web)

Neste exemplo:

  • Vamos criar um banco vetorial local com documentos internos.
  • Vamos integrar uma busca na web via DuckDuckGo.
  • O agente vai decidir quando usar cada fonte.

from langchain.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, AgentType, Tool
from langchain.memory import ConversationBufferMemory
from langchain_community.tools.ddg_search import DuckDuckGoSearchRun
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document
import os

llm = ChatOpenAI(
    model_name="gpt-4o-mini",
    temperature=0,
    openai_api_key=os.environ.get("OPENAI_API_KEY")
)

docs = [
    Document(page_content="Python é uma linguagem de programação muito utilizada em ciência de dados."),
    Document(page_content="LangChain é uma biblioteca Python para orquestrar LLMs e criar agentes inteligentes."),
    Document(page_content="O RAG é uma técnica que combina recuperação de informações e geração de texto."),
    Document(page_content="O LangChain e o Rag estão sendo utilizados como tema na oficina do GT de Dados para o Iris."),
]
splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
doc_chunks = splitter.split_documents(docs)
embeddings = OpenAIEmbeddings(openai_api_key=os.environ.get("OPENAI_API_KEY"))
db = Chroma.from_documents(doc_chunks, embeddings)

def busca_local(query):
    resultados = db.similarity_search(query, k=2)
    return "\\n".join([r.page_content for r in resultados])

busca_local_tool = Tool(
    name="Busca Local",
    func=busca_local,
    description="Busca informações em documentos internos da empresa."
)

search = DuckDuckGoSearchRun()
busca_web_tool = Tool(
    name="Busca Web",
    func=search.run,
    description="Busca informações atualizadas na internet usando DuckDuckGo."
)

memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

tools = [busca_local_tool, busca_web_tool]
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

print("Agente RAG híbrido pronto! Use agent.run('<mensagem>') para interagir.")

Testando o RAG Híbrido

# Respondida com base interna
print(agent.run("O que é LangChain?"))

# Necessidade de Dados atualizados
print(agent.run("Quem é o atual presidente do Brasil?"))

A combinação de RAG + Agentes permite:

  • RAG → Garante que a LLM tenha contexto específico e confiável.
  • Agentes → Decidem quando e como usar cada fonte de informação.
  • Busca na Web → Fornece dados atualizados.
  • Memória → Mantém o histórico da conversa para respostas mais coerentes.

O RAG resolve o problema de falta de contexto e os agentes resolvem o problema de decisão e orquestração.