Implementação básica de RAG: tutorial passo a passo

Este tutorial para principiantes explica como funciona uma implementação básica de RAG, aborda as boas práticas e mostra, passo a passo, como construir um sistema RAG funcional de linha de comandos com Python, ChromaDB e os modelos de embedding e de linguagem da Perplexity.

Basic RAG implementation process diagram: the interface sends input to the orchestrator, which retrieves data from a vector database, builds the input context for the LLM, and returns the output to the interface.

O RAG é um componente essencial da infraestrutura moderna baseada em LLM. Ao permitir que os modelos de IA acedam a dados externos, ajuda a resolver problemas como as alucinações e os dados de treino incompletos. Significa também que as empresas podem utilizar bases de conhecimento internas que contenham informações sensíveis ou proprietárias.

A implementação de RAG pode ser muito complexa. No entanto, um sistema RAG básico é surpreendentemente fácil de construir. Para os principiantes, criar uma arquitetura simples pode esclarecer como o RAG funciona no mundo real e servir de ponto de partida para outras experiências.

Neste artigo, vamos delinear uma implementação básica de RAG, abordar as boas práticas e, em seguida, construir um sistema funcional com Python, ChromaDB e dois modelos da Perplexity para a geração de embeddings e de respostas.

Processo básico de implementação de RAG: uma visão geral

Um sistema RAG básico tem três componentes principais: um orquestrador, uma fonte de informação e um LLM. Num fluxo de trabalho típico, o utilizador introduz um prompt e o orquestrador modifica-o para pesquisar as fontes de informação ligadas. A informação obtida é depois adicionada ao contexto de entrada original e enviada para o LLM, para que este possa gerar uma resposta.

Terá de ter em conta estes passos ao construir a sua implementação de RAG:

  1. Indexação e embedding: Nesta fase, os seus documentos são convertidos numa fonte de informação que o orquestrador consegue pesquisar. Uma das formas mais comuns de o fazer é construir um índice vetorial pesquisável. Vamos utilizar um modelo dedicado da Perplexity para converter os fragmentos dos seus documentos em embeddings. Em seguida, vamos armazenar os embeddings no ChromaDB, uma popular base de dados vetorial de código aberto.
  2. Orquestração: É a aplicação que fica entre a fonte de informação externa (no nosso caso, uma base de dados vetorial) e o LLM. Vamos criar um script Python simples que funcionará como orquestrador.
  3. Recuperação e geração: Quando um utilizador faz uma pergunta através da interface de chat, o orquestrador recebe-a, executa uma pesquisa vetorial e envia a entrada expandida para o LLM. Em vez de construirmos um frontend dedicado, vamos utilizar a linha de comandos para comunicar com o nosso orquestrador em Python.

Boas práticas: o que torna eficaz uma implementação de RAG?

Existem vários tipos de arquitetura RAG, e cada caso de utilização exige uma abordagem diferente. Dito isto, há várias boas práticas que se aplicam em todos os casos e que garantem sistemas precisos, económicos, escaláveis e fáceis de manter.

As seguintes boas práticas aplicam-se a todos os tipos de implementação de RAG:

  • Garanta um pipeline de ingestão limpo: Os seus dados de origem devem estar o mais limpos possível antes da indexação. Deve também utilizar um modelo de embedding adequado ao seu conteúdo.
  • Utilize pesquisa híbrida: Os melhores métodos de recuperação, seja a pesquisa em bases de dados vetoriais, a pesquisa na web através de API, a travessia de grafos de conhecimento, a pesquisa por palavras-chave ou outros, variam consoante o caso de utilização. Escolher a combinação certa é importante para minimizar os custos.
  • Avalie o seu sistema: Após a implementação, deve avaliá-lo segundo três métricas fundamentais: precisão, relevância e latência. A precisão refere-se à correção factual; a relevância, à aplicabilidade da informação obtida às consultas dos utilizadores; e a latência, à velocidade com que a informação é obtida.
  • Trate a informação de forma segura: Se o seu sistema RAG recorrer a informação interna privada, garanta que esta é armazenada de forma segura e que o acesso à interface do sistema é controlado. As chaves de API nunca devem ser partilhadas.

Implementação básica de RAG: tutorial em 7 passos

Neste tutorial, vamos construir uma aplicação de linha de comandos que responde a perguntas sobre um único documento. Vamos utilizar Python, ChromaDB e a API da Perplexity.

O objetivo não é construir um sistema RAG pronto para produção. Trata-se, antes, de um tutorial acessível a principiantes, concebido para ilustrar como funciona na prática uma arquitetura RAG rudimentar.

Passo 1: Instalar o software

Certifique-se de que tem o seguinte instalado no seu computador e de que tem uma conta da API da Perplexity:

  • Python: É a linguagem de programação que vamos utilizar para construir o orquestrador que coordenará o fluxo de dados entre o modelo de embedding, o ChromaDB e a Perplexity.
  • Visual Studio: Vamos utilizar o Visual Studio como editor de código. Depois de o instalar, abra o painel Extensions e transfira a extensão Python.
  • Conta da API da Perplexity: A Perplexity fornecerá tanto o modelo de embedding (que transforma os seus dados em embeddings vetoriais) como o LLM que processará o prompt expandido. Não precisa de uma conta paga para o fazer, embora haja um pequeno custo em créditos.

Passo 2: Criar uma pasta de projeto

Comece por criar uma pasta com o nome rag-project e abra-a no VS Code selecionando File > Open Folder. Em seguida, abra o terminal integrado através de Terminal > New Terminal.

Introduza o seguinte para criar um ambiente virtual Python:

bash
py -3.12 -m venv .venv

Agora, tem de instalar quatro bibliotecas Python: perplexityai, chromadb, python-dotenv e numpy. O SDK da Perplexity permite-lhe ligar-se aos modelos da Perplexity através da API. O ChromaDB constrói a base de dados vetorial e será executado localmente. O Python-dotenv carrega as suas chaves de API de forma segura a partir de um ficheiro local, e o NumPy é necessário durante o processo de ingestão.

Instale-as com o seguinte comando:

bash
.\.venv\Scripts\python.exe -m pip install perplexityai chromadb python-dotenv numpy

Passo 3: Configurar os ficheiros

Adicione os seguintes ficheiros a rag-project (a raiz do projeto) selecionando New File… na barra lateral Explorer:

  • index.py: É a aplicação que vai indexar o seu documento. Pode voltar a executá-la para adicionar mais texto.
  • rag.py: É a aplicação Python que vai funcionar como orquestrador.
  • document.txt: Conterá a documentação que vai converter numa base de dados vetorial.
  • .env: É aqui que vai guardar a sua chave da API da Perplexity.

Em .env, introduza o seguinte:

plaintext
PERPLEXITY_API_KEY=your_api_key_here

Substitua your_api_key_here pela sua chave real. Para isso, terá de aceder ao painel inicial da Perplexity API Console e selecionar Create an API key. Mantenha esta chave de API privada.

Vamos utilizar dois modelos: o pplx-embed-v1-0.6b da Perplexity, para gerar os embeddings da nossa documentação antes de a carregar no ChromaDB, e o GPT-5.6-Luna (acedido através da API da Perplexity), que processará a informação obtida.

Em document.txt, adicione o texto que o seu pipeline de RAG irá recuperar. Se não tiver nenhum documento à mão, utilize Songs of Innocence and of Experience, de William Blake, disponível no Project Gutenberg, para este tutorial. É um texto curto, fácil de compreender e em domínio público.

Passo 4: Escrever o programa de indexação

Copie e cole o código abaixo em index.py. Cada subtítulo explica brevemente o que faz o segmento de código seguinte. Este é o programa que vai converter o seu material de origem em embeddings vetoriais e armazená-los numa base de dados ChromaDB, utilizando o modelo de embedding da Perplexity, pplx-embed-v1-0.6b, e a biblioteca NumPy.

Eis o que o programa faz:

  1. Abre document.txt.
  2. Divide o texto em fragmentos sobrepostos.
  3. Envia esses fragmentos para o modelo de embedding pplx-embed-v1-0.6b da Perplexity.
  4. Descodifica e normaliza os vetores devolvidos com o NumPy, uma biblioteca Python de funções de computação.
  5. Armazena os vetores e os fragmentos originais numa base de dados ChromaDB (verá que esta aparece no diretório do projeto).

Foi configurado para não voltar a indexar o documento sempre que o executa. Se adicionar informação a document.txt, apenas o texto novo será convertido em embeddings.

python
from pathlib import Path from collections import defaultdict, deque from hashlib import sha256 import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and ChromaDB load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_or_create_collection( name="documents", metadata={"hnsw:space": "cosine"} ) # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Read the document and split it into chunks text = Path("document.txt").read_text(encoding="utf-8") words = text.split() chunk_size = 180 overlap = 30 step = chunk_size - overlap chunks = [" ".join(words[i:i + chunk_size]) for i in range(0, len(words), step)] if not chunks: raise ValueError("document.txt is empty.") # 3. Retrieve the chunks already stored in ChromaDB existing = collection.get(include=["documents"]) stored_chunks = defaultdict(deque) existing_ids = set(existing["ids"]) for chunk_id, document in zip(existing["ids"], existing["documents"]): stored_chunks[document].append(chunk_id) # 4. Identify unchanged and new chunks new_chunks = [] new_ids = [] kept_ids = set() unchanged = 0 for chunk in chunks: if stored_chunks[chunk]: chunk_id = stored_chunks[chunk].popleft() kept_ids.add(chunk_id) unchanged += 1 else: chunk_hash = sha256(chunk.encode("utf-8")).hexdigest() chunk_id = f"chunk-{chunk_hash}" suffix = 1 while chunk_id in existing_ids or chunk_id in kept_ids or chunk_id in new_ids: chunk_id = f"chunk-{chunk_hash}-{suffix}" suffix += 1 new_chunks.append(chunk) new_ids.append(chunk_id) kept_ids.add(chunk_id) # 5. Generate embeddings in batches of up to 100 chunks batch_size = 100 for i in range(0, len(new_chunks), batch_size): batch_chunks = new_chunks[i:i + batch_size] batch_ids = new_ids[i:i + batch_size] embeddings = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=batch_chunks, encoding_format="base64_int8" ) vectors = [decode_embedding(item.embedding) for item in embeddings.data] # 6. Store each batch in ChromaDB collection.upsert( ids=batch_ids, documents=batch_chunks, embeddings=vectors ) print(f"Indexed batch {(i // batch_size) + 1}") # 7. Remove outdated chunks old_ids = [chunk_id for chunk_id in existing["ids"] if chunk_id not in kept_ids] if old_ids: collection.delete(ids=old_ids) # 8. Display indexing results print("Indexing complete!") print("Unchanged chunks:", unchanged) print("New or modified chunks:", len(new_chunks)) print("Removed chunks:", len(old_ids)) print("Total stored chunks:", collection.count())

Passo 5. Escrever o orquestrador do sistema RAG

Copie e cole o código abaixo em rag.py. Trata-se de uma implementação de RAG completa, mas mínima, que utiliza o SDK da Perplexity e a funcionalidade de base de dados vetorial local do ChromaDB.

Eis o que acontece quando executa o programa:

  1. Liga-se à Perplexity e à sua nova base de dados ChromaDB.
  2. Verifica se a base de dados contém documentos indexados.
  3. Aceita uma pergunta do utilizador.
  4. Envia a pergunta para o modelo de embedding da Perplexity.
  5. Descodifica e normaliza o embedding resultante com o NumPy.
  6. Pesquisa no ChromaDB os três fragmentos de documento mais semelhantes.
  7. Constrói um prompt com os fragmentos obtidos e a pergunta do utilizador.
  8. Envia o prompt para o GPT-5.6 Luna através da Agent API da Perplexity.
  9. Apresenta a resposta e aguarda outra pergunta.
python
import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and the existing ChromaDB database load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_collection("documents") # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Check that the database contains indexed documents count = collection.count() if count == 0: raise ValueError("The database is empty. Run index.py first.") print("Connected to existing ChromaDB database.") print("Stored chunks:", count) # 3. Accept questions from the user while True: question = input("\nAsk a question (or type exit): ") if question.lower().strip() == "exit": break if not question.strip(): continue # 4. Convert the question into a Perplexity embedding question_response = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=[question], encoding_format="base64_int8" ) question_vector = decode_embedding(question_response.data[0].embedding) # 5. Retrieve the most similar document chunks from ChromaDB results = collection.query( query_embeddings=[question_vector], n_results=min(3, count) ) context = "\n\n".join(results["documents"][0]) # 6. Construct a prompt and send it to the fixed LLM response = ai.responses.create( model="openai/gpt-5.6-luna", instructions=( "Answer using only the supplied document excerpts. " "Do not use external information. " "If the answer is not present, say you don't know." ), input=f"Document excerpts:\n{context}\n\nQuestion: {question}" ) # 7. Display the answer print("\nAnswer:\n", response.output_text)

Passo 6: Executar as aplicações

No terminal do PowerShell do VS Code, introduza o seguinte para converter em embeddings o conteúdo de document.txt:

bash
.\.venv\Scripts\python.exe index.py

Se o documento tiver sido indexado com êxito, deverá ver um resultado semelhante a este:

plaintext
Indexing complete! Unchanged chunks: 0 New or modified chunks: 58 Removed chunks: 0 Total stored chunks: 58

Agora, é altura de executar a aplicação principal do orquestrador. Introduza este comando:

bash
.\.venv\Scripts\python.exe rag.py

Se for executada com êxito, verá o seguinte:

plaintext
Connected to existing ChromaDB database. Stored chunks: 58 Ask a question (or type exit):

Agora, pode fazer perguntas que vão buscar informação à base de dados vetorial que criou, como:

plaintext
Ask a question (or type exit): What animals does William Blake talk about? Answer: The excerpts mention sheep, lambs, birds, beasts, wolves, tigers, and a lion.

Passo 7: Compreender o que construiu

O sistema que construiu contém os dois pipelines fundamentais presentes em todas as arquiteturas RAG: um pipeline de indexação, ou «ingestão», e um pipeline de recuperação e geração.

A sua implementação básica de RAG faz agora tudo o seguinte:

  • Armazenamento vetorial: Os embeddings, gerados a partir do seu ficheiro de conhecimento, são guardados na base de dados ChromaDB no seu ambiente local.
  • Indexação incremental: Os fragmentos de documento inalterados não voltam a ser convertidos em embeddings, e pode atualizar o documento sempre que quiser.
  • Recuperação semântica: O ChromaDB obtém fragmentos com base na semelhança vetorial (a pedido do orquestrador).
  • Geração de respostas fundamentadas: O LLM recebe as passagens obtidas como contexto e incorpora-as numa resposta.
  • Indexação e consulta independentes: Pode fazer perguntas sem voltar a processar todo o texto do seu ficheiro de conhecimento.
  • Ligação a modelos através de API: Todas as respostas são geradas com o GPT-5.6 Luna através da Perplexity, e não com um modelo executado no seu computador.

Próximos passos: como levar esta implementação mais longe

O objetivo deste tutorial é construir uma implementação simples e básica. Abrange alguns dos componentes fundamentais da arquitetura RAG presentes nos sistemas modernos. Se quiser ir mais longe, há várias formas de a desenvolver.

Eis algumas ideias para alargar o âmbito da sua implementação:

  • Experimente o LangChain: O LangChain é um dos frameworks de orquestração de código aberto mais populares. Contém componentes pré-construídos para criar aplicações capazes de executar pipelines de RAG complexos.
  • Expanda a funcionalidade de pesquisa: Combine a pesquisa vetorial densa com a pesquisa lexical (por palavras-chave), como o BM25. Adicionar a pesquisa na web através da Perplexity Search API permitir-lhe-ia também fundamentar as respostas em dados atuais.
  • Adicione um modelo de reclassificação: Integre um modelo de reclassificação que ordene as passagens obtidas em função do prompt inicial e selecione as mais relevantes para incluir no contexto de entrada fornecido ao LLM.
  • Construa um sistema RAG adaptativo: O RAG adaptativo utiliza um LLM ou um classificador (um componente do orquestrador) para determinar que método de recuperação utilizar num sistema RAG modular. Construir um é uma boa forma de explorar implementações de RAG mais sofisticadas.

Embora tudo isto possa parecer complexo, tenha em conta que as principais plataformas de IA, como a Perplexity, podem dar apoio avançado na programação. São excelentes para projetos pessoais e experimentação, e os custos podem manter-se relativamente baixos.

Nota: este tutorial utiliza o GPT-5.6 Luna. A disponibilidade dos modelos pode mudar, pelo que poderá ter de atualizar a chamada à API.