in Informatica, Intelligenza Artificiale, Programmazione

Retrieval-Augmented Generation (RAG) in Python: Rivoluzionare i Modelli Linguistici con la Conoscenza Esterna

Nel mondo in rapida evoluzione dell’Intelligenza Artificiale, i Large Language Models (LLM) come GPT-3.5, GPT-4, Llama e Gemini hanno dimostrato capacità sorprendenti nella generazione di testo coerente e contestualmente rilevante. Tuttavia, questi modelli, pur essendo pre-addestrati su enormi dataset, presentano alcune limitazioni intrinseche:

  1. Conoscenza Stagnante: La loro conoscenza è limitata ai dati su cui sono stati addestrati. Non possono accedere a informazioni recenti o specifiche che non erano presenti nel loro corpus di training.
  2. Allucinazioni: A volte possono generare informazioni errate o inventate, poiché non hanno un meccanismo per verificare la verità di ciò che producono.
  3. Mancanza di Trasparenza: È difficile capire da dove provengano le informazioni che generano, rendendo difficile la verifica e l’affidabilità in contesti critici.

È qui che entra in gioco la Retrieval-Augmented Generation (RAG). RAG è un’architettura potente che combina la capacità generativa degli LLM con la precisione e l’aggiornabilità dei sistemi di recupero delle informazioni. In pratica, un sistema RAG permette a un LLM di consultare una base di conoscenza esterna (documenti, database, web) per recuperare informazioni pertinenti prima di generare una risposta. Questo approccio migliora significativamente l’accuratezza, la pertinenza e l’affidabilità delle risposte generate.

Cos’è la Retrieval-Augmented Generation (RAG)?

Immagina di porre una domanda a un esperto. Invece di rispondere solo in base alla sua memoria, l’esperto consulta prima libri, articoli o banche dati per trovare le informazioni più accurate e aggiornate, e poi formula la sua risposta basandosi su queste fonti. Questo è esattamente ciò che fa un sistema RAG per un LLM.

Il processo RAG si articola tipicamente in due fasi principali:

  1. Retrieval (Recupero): Data una query (domanda), il sistema cerca e recupera i documenti o frammenti di testo più rilevanti da una vasta base di conoscenza esterna. Questa base di conoscenza è solitamente indicizzata e vettorializzata per consentire ricerche efficienti.
  2. Generation (Generazione): I documenti recuperati, insieme alla query originale, vengono forniti come input al Large Language Model. L’LLM utilizza queste informazioni aggiuntive per generare una risposta più informata, accurata e contestualizzata.

Perché RAG è Importante?

  • Accuratezza Migliorata: Riduce le “allucinazioni” fornendo all’LLM fatti concreti su cui basare le risposte.
  • Conoscenza Aggiornata: Permette ai modelli di accedere a informazioni recenti o specifiche che non erano parte del loro dataset di training.
  • Trasparenza e Affidabilità: Poiché le risposte sono basate su documenti specifici, è possibile citare le fonti o fornire riferimenti, aumentando la fiducia dell’utente.
  • Costo-Efficienza: Evita la necessità di ri-addestrare o eseguire il fine-tuning completo di un LLM per ogni nuovo set di dati, un processo estremamente costoso e dispendioso in termini di risorse.
  • Personalizzazione: Permette di adattare l’LLM a domini specifici con dati proprietari, senza compromettere le sue capacità generali.

Componenti Chiave di un Sistema RAG

Un tipico sistema RAG in Python, utilizzando librerie e strumenti moderni, è composto dai seguenti elementi:

  1. Data Source (Fonte Dati): I documenti, testi, database, pagine web, ecc., da cui l’LLM recupererà le informazioni.
  2. Document Loader (Caricatore Documenti): Strumento per caricare i dati dalla fonte (es. file PDF, CSV, testo semplice, pagine web).
  3. Text Splitter (Divisore di Testo): I documenti grandi vengono suddivisi in “chunk” (pezzi) più piccoli e gestibili. Questo è cruciale perché gli LLM hanno un limite di contesto (numero massimo di token che possono elaborare in una singola richiesta).
  4. Embedding Model (Modello di Embedding): Un modello che converte i chunk di testo in “embedding” (vettori numerici ad alta dimensione). Questi vettori catturano il significato semantico del testo, in modo che testi semanticamente simili abbiano vettori vicini nello spazio vettoriale.
  5. Vector Store/Database (Archivio Vettoriale/Database): Un database specializzato per archiviare gli embedding e consentire ricerche efficienti basate sulla similarità vettoriale (es. Chroma, FAISS, Pinecone, Weaviate, Milvus).
  6. Retriever (Recuperatore): L’componente che, data una query, interroga il Vector Store per trovare i chunk di testo più simili semanticamente (e quindi più rilevanti).
  7. LLM (Large Language Model): Il modello generativo che riceve la query e i chunk recuperati per produrre la risposta finale.
  8. Prompt Template (Modello di Prompt): Definisce come la query originale e i documenti recuperati devono essere formattati e presentati all’LLM.

Implementazione di un Sistema RAG in Python

Ora, passiamo all’azione e vediamo come implementare un sistema RAG di base in Python. Useremo le librerie langchain e ollama per interagire con un LLM locale (Llama 3 in questo esempio), e chromadb come nostro archivio vettoriale.

Prerequisiti:

Prima di iniziare, assicurati di avere installato Ollama e di aver scaricato un modello LLM, come Llama 3.

  1. Installa Ollama: Segui le istruzioni su ollama.com.
  2. Scarica un modello: Apri il terminale e digita: Bashollama run llama3 Questo scaricherà e avvierà il modello Llama 3.

Installazione delle Librerie Python:

Bash

pip install langchain langchain-community langchain-chroma python-dotenv beautifulsoup4 pypdf
  • langchain: Framework per lo sviluppo di applicazioni basate su LLM.
  • langchain-community: Contiene integrazioni per vari LLM, database vettoriali, ecc.
  • langchain-chroma: Integrazione con ChromaDB.
  • python-dotenv: Per gestire le variabili d’ambiente (es. chiavi API, se usassi LLM basati su cloud).
  • beautifulsoup4: Per il parsing HTML (se carichi da web).
  • pypdf: Per caricare file PDF.

Struttura del Codice:

Creeremo un file rag_system.py e, opzionalmente, un file .env se avessimo credenziali API.

Python

# rag_system.py

import os
from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader, PyPDFLoader, WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain.chains import RetrievalQA
from langchain_community.llms import Ollama
from langchain_core.prompts import PromptTemplate

# --- 1. Caricamento Variabili d'Ambiente (se necessario) ---
# Se usi LLM che richiedono chiavi API (es. OpenAI), caricale qui.
# Per Ollama locale, non è strettamente necessario, ma è buona pratica.
load_dotenv()

# --- 2. Preparazione della Base di Conoscenza ---

# Definisci la directory per i documenti e il percorso per l'archivio vettoriale
DOCS_DIR = "documents"
VECTOR_DB_PATH = "chroma_db"

# Crea la directory per i documenti se non esiste
os.makedirs(DOCS_DIR, exist_ok=True)

# Crea alcuni documenti di esempio nella directory 'documents'
# Se hai già i tuoi file PDF o TXT, puoi saltare questo passaggio
# e assicurarti che siano nella cartella 'documents'.

# Esempio 1: File di testo
with open(os.path.join(DOCS_DIR, "articolo_su_ai.txt"), "w") as f:
    f.write("""L'intelligenza artificiale (IA) è un campo dell'informatica che si occupa della creazione di macchine intelligenti.
    Si focalizza su come i computer possano imitare e persino superare le capacità cognitive umane, come l'apprendimento, la risoluzione dei problemi, il riconoscimento dei pattern e la comprensione del linguaggio naturale.
    Tra le principali branche dell'IA troviamo il Machine Learning, il Deep Learning, la Computer Vision e l'Elaborazione del Linguaggio Naturale (NLP).
    L'IA ha applicazioni in molti settori, dalla medicina alla finanza, dall'automazione industriale ai sistemi di raccomandazione.
    I Large Language Models (LLM) sono un tipo di IA che eccelle nella generazione e comprensione del testo.
    """)

# Esempio 2: Simulazione di un file PDF (dovresti avere un vero PDF per PyPDFLoader)
# Per semplicità, useremo un TextLoader per un file PDF simulato.
# Per un vero PDF, assicurati di avere il file 'esempio.pdf' nella directory 'documents'.
# loader = PyPDFLoader(os.path.join(DOCS_DIR, "esempio.pdf"))
# documents = loader.load()
# print(f"Caricati {len(documents)} pagine dal PDF.")

# Esempio 3: Caricamento da URL (richiede beautifulsoup4)
# loader = WebBaseLoader("https://www.wikipedia.org/wiki/Intelligenza_artificiale")
# web_docs = loader.load()
# print(f"Caricati {len(web_docs)} documenti dal web.")

print(f"Verifica la directory '{DOCS_DIR}' per i tuoi documenti.")

def create_vector_db(docs_directory: str, vector_db_path: str):
    """
    Carica documenti, li divide in chunk, crea embedding e li salva in ChromaDB.
    """
    print("Caricamento documenti...")
    all_documents = []
    for filename in os.listdir(docs_directory):
        filepath = os.path.join(docs_directory, filename)
        if filename.endswith(".txt"):
            loader = TextLoader(filepath, encoding='utf-8')
            all_documents.extend(loader.load())
        elif filename.endswith(".pdf"):
            try:
                loader = PyPDFLoader(filepath)
                all_documents.extend(loader.load())
            except Exception as e:
                print(f"Errore durante il caricamento del PDF {filename}: {e}")
        # Puoi aggiungere altri tipi di loader qui (es. CSV, Docx)

    if not all_documents:
        print(f"Nessun documento trovato nella directory '{docs_directory}'. Assicurati di avere file supportati.")
        return None

    print(f"Numero totale di documenti caricati: {len(all_documents)}")

    print("Divisione dei documenti in chunk...")
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
        length_function=len,
        is_separator_regex=False,
    )
    chunks = text_splitter.split_documents(all_documents)
    print(f"Numero totale di chunk creati: {len(chunks)}")

    print("Creazione degli embedding e salvataggio in ChromaDB...")
    # Usiamo OllamaEmbeddings per generare embedding localmente
    # Assicurati che Ollama sia in esecuzione e che tu abbia scaricato un modello di embedding (es. nomic-embed-text)
    # Esegui: ollama run nomic-embed-text
    embeddings = OllamaEmbeddings(model="nomic-embed-text") # Puoi cambiare il modello se ne hai un altro

    # Inizializza ChromaDB e aggiungi i chunk
    # Se il database esiste già, LangChain lo caricherà automaticamente.
    # Se vuoi ricostruirlo, cancella la cartella `vector_db_path`
    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=vector_db_path
    )
    print(f"Archivio vettoriale creato/aggiornato e salvato in '{vector_db_path}'.")
    return vectorstore

# Crea o carica il database vettoriale
vectorstore = create_vector_db(DOCS_DIR, VECTOR_DB_PATH)

if vectorstore is None:
    print("Impossibile procedere senza un database vettoriale. Termino.")
    exit()

# --- 3. Inizializzazione del Modello LLM ---

print("Inizializzazione del modello LLM (Ollama - Llama 3)...")
llm = Ollama(model="llama3") # Assicurati che 'llama3' sia scaricato e in esecuzione con Ollama

# --- 4. Configurazione della Catena RAG ---

# Crea un retriever dall'archivio vettoriale
retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # Recupera i 3 chunk più rilevanti

# Definisci il prompt template per l'LLM
# Questo template indica all'LLM come usare le informazioni recuperate
prompt_template = """
Utilizza le seguenti informazioni di contesto per rispondere alla domanda alla fine.
Se non conosci la risposta, dì che non la sai, non cercare di inventare una risposta.

{context}

Domanda: {question}
Risposta:
"""
PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# Crea la catena RetrievalQA
# Questo è il cuore del sistema RAG
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff", # "stuff" concatena tutti i documenti in un unico prompt
    retriever=retriever,
    return_source_documents=True, # Utile per vedere quali documenti sono stati usati
    chain_type_kwargs={"prompt": PROMPT}
)

# --- 5. Interazione con il Sistema RAG ---

print("\n--- Sistema RAG pronto! Digita la tua domanda o 'esci' per terminare. ---")

while True:
    query = input("\nLa tua domanda: ")
    if query.lower() == 'esci':
        break

    print("Ricerca e generazione della risposta in corso...")
    result = qa_chain.invoke({"query": query})

    print("\n--- Risposta ---")
    print(result["result"])

    print("\n--- Fonti Utilizzate ---")
    if result["source_documents"]:
        for i, doc in enumerate(result["source_documents"]):
            print(f"Documento {i+1} (Source: {doc.metadata.get('source', 'N/A')}):")
            print(doc.page_content[:200] + "...") # Mostra solo i primi 200 caratteri
    else:
        print("Nessuna fonte specifica utilizzata per questa risposta.")

print("\nGrazie per aver usato il sistema RAG!")

Spiegazione del Codice:

  1. load_dotenv(): Carica le variabili d’ambiente da un file .env. Utile per chiavi API o configurazioni.
  2. Preparazione della Base di Conoscenza:
    • DOCS_DIR: La directory dove salveremo i nostri documenti.
    • Vengono creati alcuni file di testo di esempio. In un’applicazione reale, questa cartella conterrebbe i tuoi veri file (PDF, TXT, ecc.).
    • create_vector_db(): Questa funzione è il cuore della fase di Retrieval.
      • Document Loaders: TextLoader e PyPDFLoader vengono usati per caricare i dati dai file. WebBaseLoader può caricare da URL.
      • RecursiveCharacterTextSplitter: Divide i documenti lunghi in chunk più piccoli. chunk_size definisce la dimensione massima di ogni pezzo, e chunk_overlap permette ai pezzi adiacenti di sovrapporsi leggermente per mantenere il contesto.
      • OllamaEmbeddings: Questo è il modello che trasforma ogni chunk di testo in un embedding (un vettore numerico). Usiamo un modello di embedding locale tramite Ollama (es. nomic-embed-text). Assicurati di averlo scaricato con ollama run nomic-embed-text.
      • Chroma: Un database vettoriale leggero e facile da usare che viene inizializzato con i chunk e i loro embedding. Il parametro persist_directory fa sì che il database venga salvato su disco, così non devi ricostruirlo ogni volta.
  3. Inizializzazione del Modello LLM:
    • Ollama(model="llama3"): Carica un Large Language Model locale (Llama 3 in questo caso) tramite l’API di Ollama. Assicurati che llama3 sia stato scaricato (ollama run llama3) e che il server Ollama sia in esecuzione.
  4. Configurazione della Catena RAG:
    • vectorstore.as_retriever(): Trasforma l’archivio vettoriale in un “retriever”. Questo oggetto sarà in grado di cercare i documenti più pertinenti data una query. search_kwargs={"k": 3} indica di recuperare i 3 documenti più simili.
    • PromptTemplate: Definisce la struttura del prompt che verrà inviato all’LLM. Include segnaposto per il context (i documenti recuperati) e la question (la query dell’utente). È fondamentale per guidare l’LLM.
    • RetrievalQA.from_chain_type(): Crea la catena RAG vera e propria.
      • llm: Il modello linguistico che useremo.
      • chain_type="stuff": Un metodo per passare i documenti recuperati all’LLM. “stuff” li concatena tutti in un singolo stringa e la passa al prompt. Altre opzioni includono “map_reduce”, “refine”, ecc., per gestire documenti molto grandi.
      • retriever: L’oggetto che recupererà i documenti.
      • return_source_documents=True: Fa sì che la catena restituisca anche i documenti da cui è stata tratta la risposta, utile per la trasparenza.
      • chain_type_kwargs={"prompt": PROMPT}: Applica il nostro PromptTemplate personalizzato.
  5. Interazione con il Sistema RAG:
    • Un semplice ciclo while permette all’utente di porre domande.
    • qa_chain.invoke({"query": query}): Questo è il punto in cui la magia del RAG accade. LangChain:
      1. Prende la query.
      2. La passa al retriever per trovare i documenti pertinenti.
      3. Formatta la query e i source_documents recuperati usando il PROMPT.
      4. Invia il prompt completo all’llm.
      5. Restituisce la risposta generata dall’LLM e i documenti sorgente.
    • La risposta e le fonti utilizzate vengono stampate a console.

Come Eseguire il Codice:

  1. Salva il codice come rag_system.py.
  2. Assicurati che il server Ollama sia in esecuzione (ollama serve in un terminale separato, oppure ollama run llama3 se non lo hai già fatto).
  3. Scarica il modello di embedding, ad esempio nomic-embed-text: Bashollama pull nomic-embed-text
  4. Esegui lo script Python: Bashpython rag_system.py

Quando il programma si avvierà, creerà la cartella documents (se non esiste) con un file di testo di esempio. Poi creerà il database vettoriale (chroma_db) processando i documenti. Una volta pronto, potrai porre le tue domande.

Esempio di Interazione:

Verifica la directory 'documents' per i tuoi documenti.
Caricamento documenti...
Numero totale di documenti caricati: 1
Divisione dei documenti in chunk...
Numero totale di chunk creati: 1
Creazione degli embedding e salvataggio in ChromaDB...
Archivio vettoriale creato/aggiornato e salvato in 'chroma_db'.
Inizializzazione del modello LLM (Ollama - Llama 3)...

--- Sistema RAG pronto! Digita la tua domanda o 'esci' per terminare. ---

La tua domanda: Cos'è l'IA e quali sono le sue applicazioni?
Ricerca e generazione della risposta in corso...

--- Risposta ---
L'intelligenza artificiale (IA) è un campo dell'informatica che si occupa della creazione di macchine intelligenti. Si focalizza su come i computer possano imitare e persino superare le capacità cognitive umane, come l'apprendimento, la risoluzione dei problemi, il riconoscimento dei pattern e la comprensione del linguaggio naturale. Tra le principali branche dell'IA troviamo il Machine Learning, il Deep Learning, la Computer Vision e l'Elaborazione del Linguaggio Naturale (NLP). L'IA ha applicazioni in molti settori, dalla medicina alla finanza, dall'automazione industriale ai sistemi di raccomandazione.

--- Fonti Utilizzate ---
Documento 1 (Source: documents/articolo_su_ai.txt):
L'intelligenza artificiale (IA) è un campo dell'informatica che si occupa della creazione di macchine intelligenti.
Si focalizza su come i computer possano imitare e persino superare le capacità cognitive umane, come l'appren...

Considerazioni Avanzate

Questa implementazione è un punto di partenza. Un sistema RAG robusto può essere molto più sofisticato:

  • Valutazione del RAG: Come misurare l’efficacia del recupero e della generazione? Metriche come RAGAS possono aiutare.
  • Strategie di Chunking: Sperimentare con diverse dimensioni di chunk e sovrapposizioni.
  • Retriever Avanzati: Oltre alla semplice ricerca di similarità, si possono usare retrievers ibridi (basati su keyword + vettori), o multi-query retriever.
  • Reranking: Dopo il recupero iniziale, si possono utilizzare modelli più piccoli per “rerankare” i documenti, selezionando i più rilevanti.
  • Database Vettoriali in Produzione: Per applicazioni su larga scala, database come Pinecone, Weaviate o Milvus offrono scalabilità e funzionalità avanzate.
  • Data Ingestion Pipeline: Automatizzare il processo di caricamento, splitting ed embedding dei documenti.
  • Generazione Interattiva: Permettere all’utente di fornire feedback sulle fonti o sulla risposta.
  • Agenti RAG: Utilizzare l’architettura degli agenti per consentire all’LLM di decidere autonomamente quando e come utilizzare gli strumenti di recupero.
  • Integrazione con API: Non limitarsi ai documenti, ma recuperare informazioni da API esterne.

Conclusione

La Retrieval-Augmented Generation rappresenta un passo fondamentale verso modelli linguistici più affidabili, aggiornati e trasparenti. Combinando la potenza generativa degli LLM con la precisione dei sistemi di recupero delle informazioni, RAG apre nuove possibilità per applicazioni AI in contesti aziendali, di ricerca e personali. Con librerie come LangChain e l’emergere di LLM locali come quelli di Ollama, implementare questi sistemi avanzati è diventato più accessibile che mai per gli sviluppatori Python. Sperimenta con questo codice base e inizia a esplorare il potenziale illimitato del RAG!

  • Articoli Correlati per Tag :