Voltar ao blog

Implementando Geração Aumentada por Recuperação (RAG) com LLMs em Python

Implementando Geração Aumentada por Recuperação (RAG) com LLMs em Python

Geração Aumentada por Recuperação (RAG) é uma técnica para melhorar as respostas de chatbots baseados em LLMs (Large Language Models) fornecendo informações específicas e atualizadas ao modelo. Em vez de depender apenas do conhecimento embutido do modelo, o RAG busca documentos ou dados externos relevantes (“recuperação”) e os incorpora ao prompt do LLM (“geração”). Isso ajuda a evitar respostas enganosas ou inventadas — conhecidas como alucinações — quando o modelo não possui informações sobre o assunto (medium.com) (docs.pinecone.io). Neste artigo, veremos como implementar um pipeline de RAG em Python usando LangChain e o banco de dados vetorial Pinecone. Vamos aprender a indexar documentos, gerar embeddings, armazená-los em Pinecone e, finalmente, consultar o conhecimento armazenado para alimentar um chatbot inteligente com um LLM.

O que é RAG (Geração Aumentada por Recuperação)?

A técnica RAG (Retrieval-Augmented Generation) combina duas etapas principais: a recuperação de informações e a geração de texto. Em primeiro lugar, um mecanismo de busca semântica localiza documentos relevantes para a entrada do usuário. Em seguida, essas informações relevantes são “aumentadas” ao prompt do modelo de linguagem, que gera a resposta final. Essa abordagem é crucial para casos em que os LLMs não foram treinados com dados específicos do domínio privado do usuário. Sem RAG, a LLM pode gerar detalhes plausible mas incorretos. Como apontado na documentação do Pinecone, “quando as perguntas envolvem dados privados nos quais o LLM não foi treinado, você pode obter respostas convincentes porém factualmente erradas (alucinações). O RAG previne alucinações ao fornecer ao LLM o conhecimento que ele está perdendo, com base em dados privados armazenados em um banco de dados vetorial como o Pinecone” (docs.pinecone.io).

Os componentes principais de uma solução RAG são:

  • Recuperação (Retrieve): Busca semântica de documentos relevantes. Usamos embeddings para transformar texto em vetores e encontrar conteúdos próximos no espaço vetorial. Embeddings são representações numéricas de texto em um espaço multidimensional, onde textos semanticamente semelhantes produzem vetores próximos (medium.com). Pense neles como coordenadas que capturam o significado do texto em formato de vetor.
  • Banco de Dados Vetorial: Um sistema especializado em armazenar esses embeddings e realizar buscas por similaridade. Os bancos de dados vetoriais são projetados para armazenar e buscar informações vetor multidimensionais com eficiência, retornando itens semanticamente similares (medium.com). Em nosso caso, usaremos o Pinecone para isso.
  • Aumento (Augment): Seleção e formatação do contexto relevante. Após encontrar os documentos mais relevantes, concatenamos seu conteúdo ao prompt de entrada, guiando o LLM com informações específicas.
  • Geração (Generate): Finalmente o LLM recebe a pergunta original junto com o contexto recuperado e produz uma resposta. Com informações adicionais, o modelo reduz consideravelmente as chances de inventar dados.

Por exemplo, ao perguntar “Como configuro o dispositivo X?”, sem RAG o modelo poderia “inventar” passos de configuração. Com RAG, primeiro recuperamos trechos de um manual ou documentação sobre o dispositivo X (através do banco de dados vetorial) e só então fornecemos essa informação ao LLM. Assim, ele responde com precisão baseada no conteúdo real, não em suposições. Em resumo, o RAG atua como um “bibliotecário inteligente” que fornece ao modelo as páginas certas para consultar antes de responder.

Preparando o Ambiente

Para seguir este tutorial, você precisará de:

  • Contas e chaves de API: Uma conta no Pinecone (para o banco vetorial) e no OpenAI (para usar os modelos GPT via API). Guarde as chaves PINECONE_API_KEY e OPENAI_API_KEY.
  • Bibliotecas Python: Vamos usar principalmente pinecone-client para acesso ao Pinecone, langchain para integração com LLMs e vetores, e openai ou as classes de LangChain para chat. Instale as dependências com:
pip install pinecone langchain openai python-dotenv
  • Configuração de variáveis de ambiente: É comum usar o python-dotenv ou exportar variáveis. Por exemplo:
export PINECONE_API_KEY="sua-chave-pinecone" export OPENAI_API_KEY="sua-chave-openai"

Essas chaves permitem que sua aplicação conecte-se ao Pinecone e aos modelos da OpenAI. Também defina uma PINECONE_ENVIRONMENT (por exemplo, "us-west1-gcp") caso solicitado pelo SDK.

Indexando Documentos no Banco Vetorial

A primeira etapa do RAG é construir o índice vetorial a partir dos documentos fonte. Isso envolve:

  1. Carregar e dividir os documentos: Normalmente começamos com textos grandes (como artigos, manuais, FAQs). É recomendado dividir cada documento em chunks menores (por exemplo, parágrafos ou trechos de 500-1000 caracteres) para uma busca mais precisa. Usamos o LangChain para isso:
from langchain.document_loaders import TextLoader from langchain.text_splitter import CharacterTextSplitter # Exemplo: carregar um arquivo texto loader = TextLoader("manual_wondervector5000.txt", encoding="utf-8") documents = loader.load() # Dividir o documento em partes menores splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = splitter.split_documents(documents)
  1. Gerar embeddings dos textos: Cada trecho de texto é transformado em um vetor numérico. Podemos usar o modelo de embeddings da OpenAI (por exemplo, text-embedding-3-small ou text-embedding-ada-002) ou outro de sua preferência. Em LangChain, por exemplo:
from langchain.embeddings.openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings(model="text-embedding-3-small") text_chunks = [chunk.page_content for chunk in chunks] # Calcular os embeddings de todos os trechos vectors = embeddings.embed_documents(text_chunks)

Esse passo cria listas de vetores (um vetor de 1536 dimensões para cada trecho, se usar text-embedding-3-small (docs.langchain.com)).

  1. Criar o índice no Pinecone e inserir vetores: Com os vetores gerados, criamos um índice no Pinecone e upsert (inserimos) cada vetor com um ID e metadados associados (por exemplo, referência ao documento original). Exemplo:
import pinecone # Inicializar Pinecone pinecone.init(api_key=os.getenv("PINECONE_API_KEY"), environment="us-west1-gcp") index_name = "wondervector-index" # Criar índice com dimensão correta (1536) se ainda não existir if index_name not in pinecone.list_indexes(): pinecone.create_index(name=index_name, dimension=1536, metric="cosine") # Conectar ao índice index = pinecone.Index(index_name) # Preparar dados para upsert ids = [f"chunk_{i}" for i in range(len(vectors))] metadatas = [{"source": "wondervector", "text": text_chunks[i]} for i in range(len(vectors))] to_upsert = [(ids[i], vectors[i], metadatas[i]) for i in range(len(vectors))] # Inserir vetores no índice index.upsert(vectors=to_upsert)

Pronto! Agora nosso índice "wondervector-index" no Pinecone contém todos os embeddings dos documentos relevantes. Internamente, o Pinecone organiza esses vetores para permitir buscas muito rápidas por similaridade.

Consultando o Banco Vetorial e Integrando com o LLM

Com o índice criado e populado, é hora de fazer consultas semânticas e usar o LLM para gerar respostas. O fluxo típico é:

  1. Receber a pergunta do usuário: Por exemplo, “Quais são os primeiros passos para configurar o WonderVector5000?”.

  2. Gerar embedding da consulta: Aplicamos o mesmo modelo de embeddings à pergunta para obter um vetor de consulta.

query = "Quais são os primeiros passos para configurar o WonderVector5000?" query_vector = embeddings.embed_query(query)
  1. Buscar documentos relevantes no Pinecone: Enviamos esse vetor ao Pinecone, que retorna os k vetores mais próximos (documentos mais relevantes semanticamente):
k = 3 results = index.query(queries=[query_vector], top_k=k, include_metadata=True) relevant_chunks = [res['metadata']['text'] for res in results['results'][0]['matches']]

Agora relevant_chunks contém o texto dos k fragmentos mais relacionados à consulta.

  1. Formular o prompt para o LLM: Concatenamos o contexto obtido ao prompt do modelo. Uma estratégia simples é incluir o trecho relevante e a pergunta, pedindo ao LLM que responda com base nesse contexto. Exemplo usando o ChatGPT via LangChain:
from langchain.chat_models import ChatOpenAI # Combinar contexto relevante em um único prompt context = "\n\n".join(relevant_chunks) full_prompt = f"Contexto relevante:\n{context}\n\nPergunta: {query}\nResposta:" # Consultar o modelo chat = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0) answer = chat(full_prompt) print(answer.content)

Alternativamente, LangChain oferece chains pré-definidas para RAG, como RetrievalQA, que integram automaticamente a busca vetorial e a geração de texto. Por exemplo:

from langchain.chains import RetrievalQA from langchain.vectorstores import Pinecone # Cria objeto de recuperação usando o índice existente vector_store = Pinecone(index=index, embedding_function=embeddings) qa_chain = RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=vector_store.as_retriever()) result = qa_chain({"query": query}) print(result["result"])

Neste exemplo, o LangChain cuida de colocar o contexto recuperado junto com o prompt do modelo. Em ambos os casos, a resposta do LLM tende a ser muito mais precisa porque ela foi fundamentada nos documentos indexados. Conforme observado na documentação do Pinecone, sem fornecer esse contexto o modelo “soa convincente mas está inteiramente fabricado” (docs.pinecone.io) – uma típica alucinação. Já incluindo o contexto vetorizado, obtemos respostas consistentes com o conteúdo original dos documentos.

Exemplo Prático de Chatbot RAG

Vamos juntar tudo em um exemplo simples de um chatbot que responde perguntas sobre o produto fictício WonderVector5000. Suponha que temos o manual do produto indexado conforme acima. O código a seguir demonstra um fluxo de consulta (simplificado):

# Biblioteca para chat e recuperação from langchain.chat_models import ChatOpenAI from langchain.chains import RetrievalQA from langchain.vectorstores import Pinecone # Configurar modelo de chat llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0) # Recuperador usando Pinecone já inicializado vector_store = Pinecone(index=index, embedding_function=embeddings) qa_chain = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=vector_store.as_retriever()) # Perguntas exemplo perguntas = [ "Quais são os primeiros passos para configurar o WonderVector5000?", "O sincronizador Neural Fandango está dando dor de cabeça. O que devo fazer?" ] for perg in perguntas: resposta = qa_chain({"query": perg}) print(f"Pergunta: {perg}") print(f"Resposta: {resposta['result']}\n")

Em testes desse fluxo, vemos que perguntas sobre tópicos específicos do WonderVector5000 recebem respostas corretas. Por exemplo, a pergunta sobre os primeiros passos de configuração retorna exatamente os passos descritos no manual indexado. Já sem este processo RAG, o modelo tenderia a inventar instruções genéricas, como descreve o Pinecone: “Sua resposta parecia convincente, mas era inteiramente fabricada” (docs.pinecone.io). Com RAG, o modelo utiliza o trecho relevante do documento (como instruções de configuração) para gerar uma resposta precisa.

Assim, criamos um chatbot inteligente que combina busca semântica e geração de texto: o usuário faz uma pergunta, o sistema busca respostas em documentos internos (via Pinecone) e então o LLM usa essas informações reais para responder de forma composta.

Conclusão

Neste guia, vimos como implementar um pipeline completo de Geração Aumentada por Recuperação (RAG) em Python, integrando LLMs com um banco de dados vetorial. Revisamos os conceitos essenciais — embeddings, bancos vetoriais e LLMs — e configuramos o ambiente com Pinecone e LangChain. Em seguida, passamos pela indexação de documentos (divisão em trechos, geração de embeddings e armazenamento no Pinecone) e pela consulta vetorial seguida de geração de respostas. Os exemplos de código mostraram como, na prática, coletar documentos, armazená-los, recuperar contexto relevante para uma pergunta e fornecer esse contexto ao modelo de linguagem.

Com essa abordagem, os chatbots ganham “superpoderes” sobre bases de conhecimento específicas: eles podem acessar reservas de informações privadas ou atualizadas e evitar respostas inventadas. Em cenários empresariais, isso significa chatbots capazes de responder dúvidas de clientes ou colegas com precisão, baseados na documentação real da empresa.

Como próximos passos, você pode experimentar diferentes modelos de embeddings (como os da Hugging Face) ou ajustar o tamanho dos chunks para otimizar performance. Também é possível escalar o sistema para milhares de documentos, explorar filtragens nos metadados e até combinar múltiplas fontes (por exemplo, bancos de dados e APIs). A técnica RAG abre caminho para muitos casos de uso avançados de IA, tornando o poder dos LLMs mais aplicável a domínios especializados.

Recursos para aprofundamento:

Esses materiais ajudarão a explorar mais técnicas de prompt engineering, otimização de busca semântica e aplicações de RAG em projetos reais.

Quer aprender mais sobre Programação?

Você acabou de ganhar 1 hora com a minha equipe para uma call exclusiva! Vamos entender o seu momento e te mostrar o caminho para se tornar um programador de sucesso. Clique no botão abaixo e agende agora mesmo!