Come costruire un sistema RAG in 10 righe di Python senza framework

Sblocca il potere di RAG (Retrieval Augmented Generation) con questo tutorial di codice Python di 10 righe. Immergersi nel recupero di informazioni, costruire il proprio sistema di documenti e sfruttare i LLM per risposte robuste - tutto senza fare affidamento su framework complessi. Ottimizza il tuo contenuto per SEO e coinvolgimento.

14 febbraio 2025

party-gif

Scopri come costruire un potente sistema di generazione aumentata dal recupero (RAG) da zero in soli 10 righe di codice Python, senza fare affidamento su alcun framework esterno. Questo approccio conciso ma completo ti fornirà una profonda comprensione dei componenti principali, permettendoti di creare soluzioni AI robuste e personalizzabili per le tue applicazioni basate su documenti.

Comprendere il concetto di Retrieval Augmented Generation (RAG)

Retrieval Augmented Generation (RAG) è una tecnica utilizzata per il recupero di informazioni dai propri documenti. Comporta un processo in due fasi:

  1. Creazione della base di conoscenza: I documenti vengono suddivisi in sottodocumenti più piccoli e vengono calcolati gli embedding per ogni frammento. Questi embedding e i frammenti originali vengono memorizzati, tipicamente in un archivio vettoriale.

  2. Recupero e generazione: Quando arriva una nuova query dell'utente, viene calcolato un embedding per la query. I frammenti più rilevanti vengono recuperati confrontando l'embedding della query con gli embedding dei frammenti memorizzati. I frammenti recuperati vengono quindi aggiunti alla query originale e inseriti in un modello di linguaggio (LLM) per generare la risposta finale.

Questo setup è chiamato "pipeline di generazione con recupero di informazioni", poiché la generazione dell'LLM è arricchita con le informazioni rilevanti recuperate dalla base di conoscenza.

Sebbene esistano framework come Langchain e LlamaIndex che semplificano l'implementazione di pipeline RAG, i componenti principali possono essere costruiti utilizzando solo Python, un modello di embedding e un LLM. Questo approccio fornisce una migliore comprensione dei concetti di base e consente una maggiore flessibilità nella personalizzazione della pipeline.

Imparare a suddividere i documenti in paragrafi

Quando si costruisce un sistema di generazione con recupero di informazioni (RAG), uno dei passaggi chiave è suddividere i documenti di input in pezzi più piccoli e gestibili. In questo esempio, stiamo suddividendo il documento di input (un articolo di Wikipedia) in paragrafi.

La logica dietro questo approccio è che i paragrafi spesso rappresentano unità logiche di informazioni che possono essere recuperate in modo efficace e utilizzate per arricchire la query dell'utente. Suddividendo il documento in questo modo, possiamo identificare più facilmente le sezioni più rilevanti da includere nella risposta finale.

Il codice per questo passaggio è il seguente:

# Suddividi il documento in paragrafi
chunks = [paragraph.strip() for paragraph in text.split('\n') if paragraph.strip()]

Qui utilizziamo un approccio semplice di divisione del testo di input sui caratteri di nuova riga per estrarre i singoli paragrafi. Rimuoviamo quindi gli eventuali spazi vuoti iniziali o finali da ciascun paragrafo per garantire una rappresentazione pulita.

L'elenco chunks risultante contiene i singoli paragrafi, che possono quindi essere incorporati e memorizzati nell'archivio vettoriale. Ciò ci consente di recuperare in modo efficiente i paragrafi più rilevanti in base alla query dell'utente e di includerli nella risposta finale generata dal modello di linguaggio.

Incorporare documenti e query degli utenti utilizzando Sentence Transformers

Per incorporare i frammenti del documento e la query dell'utente, utilizzeremo la libreria Sentence Transformers. Questa libreria fornisce modelli pre-addestrati in grado di generare embedding di alta qualità per il testo.

Prima di tutto, carichiamo il modello di embedding:

from sentence_transformers import SentenceTransformer
embedding_model = SentenceTransformer('all-mpnet-base-v2')

Successivamente, calcoliamo gli embedding per i frammenti del documento:

chunk_embeddings = embedding_model.encode(chunks, normalize_embeddings=True)

Qui, chunks è un elenco dei frammenti di testo dal documento. L'opzione normalize_embeddings=True garantisce che gli embedding siano normalizzati, il che è importante per il successivo calcolo della similarità.

Per ottenere l'embedding della query dell'utente, basta eseguire:

query_embedding = embedding_model.encode([user_query], normalize_embeddings=True)[0]

Ora abbiamo gli embedding per i frammenti del documento e la query dell'utente, che possiamo utilizzare per il passaggio di recupero.

Recuperare i frammenti rilevanti in base alla similarità del coseno

Per recuperare i frammenti più rilevanti in base alla query dell'utente, dobbiamo prima calcolare la similarità coseno tra l'embedding della query e gli embedding dei frammenti del documento. Ecco come possiamo farlo:

  1. Calcolare l'embedding della query dell'utente utilizzando lo stesso modello di embedding dei frammenti del documento.
  2. Calcolare il prodotto scalare tra l'embedding della query e ciascuno degli embedding dei frammenti del documento. Questo ci fornisce i punteggi di similarità coseno.
  3. Ordinare i frammenti del documento in base ai punteggi di similarità coseno e selezionare i K frammenti più rilevanti.
  4. Recuperare il contenuto testuale dei frammenti selezionati per utilizzarlo come contesto per il modello di linguaggio.

I passaggi chiave sono:

  1. Calcolare l'embedding della query
  2. Calcolare i punteggi di similarità coseno
  3. Ordinare e selezionare i primi K frammenti
  4. Recuperare il contenuto testuale dei frammenti

Questo semplice approccio ci consente di recuperare in modo efficiente le informazioni più rilevanti dalla raccolta di documenti per arricchire la query dell'utente prima di passarla al modello di linguaggio per la generazione.

Aumentare la query dell'utente con i frammenti recuperati e generare la risposta utilizzando OpenAI GPT-4

Per generare una risposta utilizzando i frammenti recuperati e la query dell'utente, creiamo prima un prompt che include i frammenti rilevanti e la query dell'utente. Utilizziamo quindi il modello OpenAI GPT-4 per generare una risposta in base a questo prompt.

Ecco il codice:

# Ottieni la query dell'utente
user_query = "Qual è la capitale della Francia?"

# Recupera i frammenti più rilevanti in base alla query dell'utente
top_chunk_ids = [6, 8, 5]
top_chunks = [chunks[i] for i in top_chunk_ids]

# Crea il prompt
prompt = "Utilizza il seguente contesto per rispondere alla domanda alla fine. Se non conosci la risposta, di' che non la sai e non provare a inventare una risposta.\n\nContesto:\n"
for chunk in top_chunks:
    prompt += chunk + "\n\n"
prompt += f"\nDomanda: {user_query}"

# Genera la risposta utilizzando OpenAI GPT-4
response = openai_client.chat.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": prompt}
    ],
    max_tokens=1024,
    n=1,
    stop=None,
    temperature=0.7,
).choices[0].message.content

print(response)

In questo codice, prima recuperiamo i frammenti più rilevanti in base alla query dell'utente trovando i primi 3 frammenti con i punteggi di similarità più alti. Creiamo quindi un prompt che include questi frammenti e la query dell'utente. Infine, utilizziamo il modello OpenAI GPT-4 per generare una risposta in base a questo prompt.

La risposta generata sarà una risposta concisa e pertinente alla query dell'utente, sfruttando le informazioni dai frammenti recuperati.

Conclusione

In questa guida, abbiamo imparato a costruire un sistema di chat completo con un sistema di recupero di documenti utilizzando solo 10 righe di codice Python. Abbiamo coperto i componenti chiave di una pipeline di Retrieval Augmented Generation (RAG), inclusa la creazione della base di conoscenza, la suddivisione dei documenti, l'embedding, il recupero e la generazione utilizzando un modello di linguaggio di grandi dimensioni.

I punti chiave sono:

  1. Puoi implementare una pipeline RAG di base senza la necessità di framework complessi come Langchain o LlamaIndex. Python puro e alcune librerie sono sufficienti.
  2. Suddividere i tuoi documenti in base alla struttura (ad esempio, in paragrafi) è una strategia semplice ma efficace per la maggior parte dei casi d'uso.
  3. Incorporare i frammenti del documento e la query dell'utente, quindi calcolare i punteggi di similarità, ti consente di recuperare le informazioni più rilevanti per arricchire la query dell'utente.
  4. Integrare i frammenti recuperati con la query dell'utente e inviarli a un modello di linguaggio di grandi dimensioni consente la generazione di una risposta pertinente e informativa.

Mentre questo esempio fornisce una solida base, ci sono molte opportunità per costruire sistemi RAG più robusti e avanzati. Framework come Langchain e LlamaIndex possono essere utili quando si integrano vari archivi vettoriali e modelli di linguaggio. Tuttavia, iniziare con un'implementazione in puro Python può aiutarti a comprendere meglio i concetti e i componenti fondamentali di una pipeline RAG.

Se sei interessato ad esplorare tecniche RAG più avanzate, ti consiglio di dare un'occhiata al mio corso "RAG Beyond Basics", che approfondisce la costruzione di sistemi RAG complessi e pronti per la produzione.

FAQ