Implementazione RAG di base: tutorial passo passo

Questo tutorial per principianti spiega come funziona un'implementazione RAG di base, illustra le best practice e guida passo passo nella creazione di un sistema RAG funzionante a riga di comando con Python, ChromaDB e i modelli di embedding e linguistici di 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.

La RAG è un componente essenziale delle moderne infrastrutture basate su LLM. Consentendo ai modelli di IA di accedere a dati esterni, aiuta a risolvere problemi come le allucinazioni e l'incompletezza dei dati di addestramento. Inoltre, permette alle aziende di utilizzare basi di conoscenza interne che contengono informazioni sensibili o proprietarie.

L'implementazione della RAG può essere molto complessa. Tuttavia, un sistema RAG di base è sorprendentemente facile da costruire. Per chi è alle prime armi, creare un'architettura semplice può chiarire come funziona la RAG nel mondo reale e offrire un trampolino di lancio per ulteriori sperimentazioni.

In questo articolo illustreremo un'implementazione RAG di base, esamineremo le best practice e poi costruiremo un sistema funzionante usando Python, ChromaDB e due modelli di Perplexity per l'embedding e la generazione delle risposte.

Processo di implementazione RAG di base: una panoramica generale

Un sistema RAG di base ha tre componenti principali: un orchestratore, una fonte di informazioni e un LLM. In un tipico flusso di lavoro, l'utente inserisce un prompt e l'orchestratore lo modifica per effettuare ricerche nelle fonti di informazioni collegate. Le informazioni recuperate vengono quindi aggiunte al contesto di input originale e inviate all'LLM, che può così generare una risposta.

Quando costruisci la tua implementazione RAG, dovrai tenere conto di queste fasi:

  1. Indicizzazione ed embedding: in questa fase, i tuoi documenti vengono convertiti in una fonte di informazioni in cui l'orchestratore può effettuare ricerche. Uno dei metodi più comuni per farlo è creare un indice vettoriale ricercabile. Useremo un modello dedicato di Perplexity per convertire i blocchi dei tuoi documenti in embedding. Poi memorizzeremo gli embedding con ChromaDB, un diffuso database vettoriale open source.
  2. Orchestrazione: si tratta dell'applicazione che si colloca tra la fonte di informazioni esterna (nel nostro caso, un database vettoriale) e l'LLM. Creeremo un semplice script Python che fungerà da orchestratore.
  3. Recupero e generazione: quando un utente pone una domanda tramite l'interfaccia di chat, l'orchestratore la raccoglie, esegue una ricerca vettoriale e passa l'input arricchito all'LLM. Invece di creare un frontend dedicato, useremo la riga di comando per inviare prompt al nostro orchestratore Python.

Best practice: cosa rende efficace un'implementazione RAG?

Esistono diversi tipi di architettura RAG e i singoli casi d'uso richiedono approcci differenti. Detto questo, alcune best practice valgono in ogni caso e garantiscono sistemi accurati, convenienti, scalabili e di facile manutenzione.

Le seguenti best practice si applicano a tutti i tipi di implementazione RAG:

  • Garantisci una pipeline di acquisizione pulita: i dati di origine dovrebbero essere il più puliti possibile prima dell'indicizzazione. Dovresti inoltre usare un modello di embedding adatto ai tuoi contenuti.
  • Usa la ricerca ibrida: i metodi di recupero migliori variano in base al caso d'uso, che si tratti di ricerca in un database vettoriale, ricerca web tramite API, attraversamento di grafi di conoscenza, ricerca per parole chiave o altri metodi. Scegliere il giusto mix è importante per ridurre al minimo i costi.
  • Valuta il tuo sistema: dopo l'implementazione, dovresti valutarlo secondo tre parametri fondamentali: accuratezza, pertinenza e latenza. L'accuratezza si riferisce alla correttezza fattuale, la pertinenza all'attinenza delle informazioni recuperate rispetto alle query degli utenti e la latenza alla velocità con cui le informazioni vengono recuperate.
  • Gestisci le informazioni in modo sicuro: se il tuo sistema RAG attinge a informazioni interne private, assicurati che siano archiviate in modo sicuro e che l'accesso all'interfaccia del sistema sia controllato. Le chiavi API non devono mai essere condivise.

Implementazione RAG di base: tutorial in 7 passaggi

In questo tutorial costruiremo un'applicazione a riga di comando che risponde a domande su un singolo documento. Useremo Python, ChromaDB e l'API di Perplexity.

L'obiettivo non è costruire un sistema RAG pronto per la produzione. Si tratta piuttosto di un tutorial adatto ai principianti, pensato per illustrare come funziona in pratica un'architettura RAG elementare.

Passaggio 1: installa il software

Assicurati di avere installato sul tuo computer quanto segue e di disporre di un account API di Perplexity:

  • Python: è il linguaggio di programmazione che useremo per creare l'orchestratore, che coordinerà il flusso di dati tra il modello di embedding, ChromaDB e Perplexity.
  • Visual Studio: useremo Visual Studio come editor di codice. Dopo averlo installato, apri il pannello Estensioni e scarica l'estensione Python.
  • Account API di Perplexity: Perplexity fornirà sia il modello di embedding (che trasforma i tuoi dati in embedding vettoriali) sia l'LLM che elaborerà il prompt arricchito. Per farlo non serve un account a pagamento, anche se verrà addebitato un piccolo costo in crediti.

Passaggio 2: crea una cartella di progetto

Inizia creando una cartella denominata rag-project e aprila in VS Code selezionando File > Apri cartella. Poi apri il terminale integrato tramite Terminale > Nuovo terminale.

Inserisci quanto segue per creare un ambiente virtuale Python:

bash
py -3.12 -m venv .venv

Ora devi installare quattro librerie Python: perplexityai, chromadb, python-dotenv e numpy. L'SDK di Perplexity ti consentirà di connetterti ai modelli di Perplexity tramite API. ChromaDB crea il database vettoriale e verrà eseguito in locale. Python-dotenv carica in modo sicuro le tue chiavi API da un file locale, mentre NumPy è necessario durante il processo di acquisizione.

Installale con il seguente comando:

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

Passaggio 3: configura i file

Aggiungi i seguenti file a rag-project (la radice del progetto) selezionando Nuovo file… nella barra laterale Esplora risorse:

  • index.py: è l'applicazione che indicizzerà il tuo documento. Puoi eseguirla di nuovo per aggiungere altro testo.
  • rag.py: è l'applicazione Python che fungerà da orchestratore.
  • document.txt: conterrà la documentazione che convertirai in un database vettoriale.
  • .env: è il file in cui conserverai la tua chiave API di Perplexity.

In .env inserisci quanto segue:

plaintext
PERPLEXITY_API_KEY=your_api_key_here

Sostituisci your_api_key_here con la tua chiave effettiva. Per ottenerla, vai alla dashboard principale della Perplexity API Console e seleziona Create an API key. Mantieni privata questa chiave API.

Useremo due modelli: pplx-embed-v1-0.6b di Perplexity, per creare gli embedding della nostra documentazione prima di caricarla su ChromaDB, e GPT-5.6-Luna (accessibile tramite l'API di Perplexity), che elaborerà le informazioni recuperate.

In document.txt aggiungi il testo che la tua pipeline RAG recupererà. Se non hai un documento a portata di mano, per questo tutorial usa Songs of Innocence and of Experience di William Blake, disponibile su Project Gutenberg. È un testo breve, facile da comprendere e di pubblico dominio.

Passaggio 4: scrivi il programma di indicizzazione

Copia e incolla il codice qui sotto in index.py. Ogni sottotitolo fornisce una breve spiegazione di ciò che fa il segmento di codice successivo. Questo è il programma che convertirà il materiale di origine in embedding vettoriali e li memorizzerà in un database ChromaDB usando il modello di embedding di Perplexity, pplx-embed-v1-0.6b, e la libreria NumPy.

Ecco cosa fa il programma:

  1. Apre document.txt.
  2. Suddivide il testo in blocchi sovrapposti.
  3. Invia questi blocchi al modello di embedding pplx-embed-v1-0.6b di Perplexity.
  4. Decodifica e normalizza i vettori restituiti usando NumPy, una libreria Python di funzioni di calcolo.
  5. Memorizza i vettori e i blocchi originali in un database ChromaDB (lo vedrai comparire nella directory del progetto).

È configurato in modo da non reindicizzare il documento ogni volta che lo esegui. Se aggiungi informazioni a document.txt, verrà creato l'embedding solo del nuovo testo.

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())

Passaggio 5: scrivi l'orchestratore per il sistema RAG

Copia e incolla il codice qui sotto in rag.py. Si tratta di un'implementazione RAG completa ma minimale che usa l'SDK di Perplexity e la funzionalità di database vettoriale locale di ChromaDB.

Ecco cosa succederà quando eseguirai il programma:

  1. Si connette a Perplexity e al tuo nuovo database ChromaDB.
  2. Verifica che il database contenga documenti indicizzati.
  3. Accetta una domanda dall'utente.
  4. Invia la domanda al modello di embedding di Perplexity.
  5. Decodifica e normalizza l'embedding risultante usando NumPy.
  6. Cerca in ChromaDB i tre blocchi di documento più simili.
  7. Costruisce un prompt contenente i blocchi recuperati e la domanda dell'utente.
  8. Invia il prompt a GPT-5.6 Luna tramite l'Agent API di Perplexity.
  9. Mostra la risposta e attende un'altra domanda.
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)

Passaggio 6: esegui le applicazioni

Nel terminale PowerShell di VS Code, inserisci quanto segue per creare gli embedding del contenuto di document.txt:

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

Se il documento è stato indicizzato correttamente, dovresti vedere un output simile a questo:

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

Ora è il momento di eseguire l'applicazione principale dell'orchestratore. Inserisci questo comando:

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

Se l'esecuzione va a buon fine, vedrai quanto segue:

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

Ora puoi porre domande che attingeranno al database vettoriale che hai creato, ad esempio:

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.

Passaggio 7: comprendi ciò che hai costruito

Il sistema che hai costruito contiene le due pipeline fondamentali presenti in ogni architettura RAG: una pipeline di indicizzazione, o di “acquisizione”, e una pipeline di recupero e generazione.

Ora la tua implementazione RAG di base fa tutto ciò che segue:

  • Archiviazione vettoriale: gli embedding, generati a partire dal tuo file di conoscenza, vengono salvati nel database ChromaDB nel tuo ambiente locale.
  • Indicizzazione incrementale: i blocchi di documento invariati non vengono sottoposti di nuovo a embedding e puoi aggiornare il documento quando vuoi.
  • Recupero semantico: ChromaDB recupera i blocchi in base alla similarità vettoriale (su richiesta dell'orchestratore).
  • Generazione di risposte fondate: l'LLM riceve i brani recuperati come contesto e li integra in una risposta.
  • Indicizzazione e interrogazione indipendenti: puoi porre domande senza dover rielaborare tutto il testo contenuto nel tuo file di conoscenza.
  • Connessione al modello tramite API: tutte le risposte vengono generate usando GPT-5.6 Luna tramite Perplexity, anziché un modello eseguito sul tuo computer.

Prossimi passi: come portare oltre questa implementazione

L'obiettivo di questo tutorial è creare un'implementazione semplice e di base, che copre alcuni dei componenti fondamentali dell'architettura RAG presenti nei sistemi moderni. Se vuoi andare oltre, ci sono vari modi per svilupparla ulteriormente.

Ecco alcune idee per ampliare la portata della tua implementazione:

  • Sperimenta con LangChain: LangChain è uno dei framework di orchestrazione open source più diffusi. Contiene componenti predefiniti per creare app in grado di eseguire pipeline RAG complesse.
  • Amplia le funzionalità di ricerca: combina la ricerca vettoriale densa con la ricerca lessicale (per parole chiave), come BM25. Aggiungere la ricerca web tramite la Perplexity Search API ti consentirebbe inoltre di basare le risposte su dati aggiornati.
  • Aggiungi un modello di reranking: integra un modello di reranking che classifichi i brani recuperati in base al prompt iniziale e selezioni i più pertinenti da includere nel contesto di input fornito all'LLM.
  • Crea un sistema RAG adattivo: la RAG adattiva usa un LLM o un classificatore (un componente all'interno dell'orchestratore) per determinare quale metodo di recupero utilizzare in un sistema RAG modulare. Costruirne uno è un ottimo modo per esplorare implementazioni RAG più sofisticate.

Anche se tutto questo può sembrare complesso, tieni presente che le principali piattaforme di IA come Perplexity possono offrire un'assistenza avanzata alla programmazione. Sono ideali per progetti personali e sperimentazioni, e i costi possono essere mantenuti relativamente bassi.

Nota: questo tutorial usa GPT-5.6 Luna. La disponibilità dei modelli può cambiare, quindi potrebbe essere necessario aggiornare la chiamata API.