Nelle lezioni precedenti la preparazione della RAG ci ha portato ad esplorare tutto il procedimento di creazione. Dallo scaricamento dei documenti, alla loro elaborazione ed immissione in un database vettoriale fino alla formulazione di una risposta a fronte della domanda di un utente.
In realtà, supponendo di aver recuperato tutti i documenti che ci servono ed aver completato il database, non è necessario ricostruirlo ogni volta ma dobbiamo conservare lo stesso ormai pronto.
Per questo possiamo suddividere il nostro lavoro in due parti:
- preparazione della RAG composto da document loading, test splitting e embedding, storing nel database vettoriale;
- attivazione della generazione via modello utilizzando della fase precedente solo ed esclusivamente il database vettoriale senza rigenerarne da capo i vettori.
RAG: conservazione e condivisione del database
Supponendo quindi di aver completato il nostro database, la messa in produzione della RAG consisterà nell'attivazione del codice di generazione collegato ad esso. Chroma, la tecnologia per database che abbiamo usato, offre la possibilità di salvare un database vettoriale su disco, in una cartella, e ricaricarlo prima di avviare di nuovo l'applicazione RAG.
Si può gestire la persistenza di un database vettoriale Chroma semplicemente utilizzando l'argomento persist_directory in fase di costruzione.
Potremmo salvarlo alla fine della costruzione della RAG con:
db = Chroma.from_documents(chunks, persist_directory="./chroma_db", embedding=embeddings)
il che salverà tutti i vettori nella cartella chroma.db e all'avvio dell'applicazione RAG ricarichiamo il database con:
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
Notare che in questo caso non importiamo i chunk in quanto i vettori non verranno più prodotti dai documenti ma ricaricati dalla cartella su disco.
Questo è l'approccio alla messa in produzione per cui non dovremo più rianalizzare i documenti - cosa che costerebbe molto tempo e risorse - ma ci basta riavviare un database già pronto. Il database può inoltre, in questo modo, essere copiato, caricato su Cloud, spedito, etc.
E' fondamentale ricordare che il modello di embedding deve essere lo stesso sia in fase di costruzione del database vettoriale che in produzione, altrimenti le metriche non sarebbero più confrontabili compromettendo il funzionamento delle operazioni di ricerca.
Considerando poi che a Chroma esistono varie alternative per soluzioni di livello più enterprise possiamo pensare a database vettoriali raggiungibili via rete come Pinecone, Milvus, Weaviate e altri direttamente contattabili da remoto per i quali il salvataggio su disco non ricade più sotto la nostra responsabilità.
# importazione dei pacchetti
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# definizione del prompt
prompt = ChatPromptTemplate.from_template(
"""
Rispondi alle domande dell'utente utilizzando solo ed esclusivamente quello che hai trovato nel contesto
costruito sui documenti della RAG.
In questo caso il contesto è il seguente: {contesto}
La domanda dell'utente è questa: {domanda}
Se non conosci la risposta ammetti di non saper rispondere
"""
)
# LLM
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
api_key="sk-..."
)
# Embeddings, caricamento del database e chain
embeddings = OpenAIEmbeddings(model="text-embedding-3-small",
api_key="sk-...")
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
chain = prompt | llm
# loop di Question&Answering
while True:
domanda = input("Domanda (digitare stop per uscire): ")
if domanda.lower().strip() == "stop":
print("Chiusura della connessione con la RAG")
break
documenti = db.similarity_search(domanda, k=5)
if not documenti:
print("Non so rispondere")
continue
contesto = "\n\n".join(doc.page_content for doc in documenti)
risposta = (prompt | llm).invoke({
"contesto": contesto,
"domanda": domanda
})
print(f"Utente: {domanda}")
print(f"RAG: {risposta.content}\n\n")
Lanciando l'applicazione vedremo apparire la risposta del modello. Sono tutte domande la cui risposta si trova in quello che abbiamo salvato dalla pagina scaricata da Wikipedia su Roma tranne la seconda che volutamente chiede chi ha fondato Firenze (ma di questa non sapremo la risposta):
Domanda (digitare stop per uscire): Chi ha fondato Roma?
Utente: Chi ha fondato Roma?
RAG: Roma è stata fondata secondo la tradizione il 21 aprile 753 a.C. da Romolo.
Domanda (digitare stop per uscire): Chi ha fondato Firenze?
Utente: Chi ha fondato Firenze?
RAG: Il contesto fornito non contiene informazioni sulla fondazione di Firenze. Pertanto, non posso rispondere a questa domanda.
Domanda (digitare stop per uscire): Che c'entra l'Euratom con Roma?
Utente: Che c'entra l'Euratom con Roma?
RAG: L'Euratom è stato fondato a Roma, che è anche il luogo di fondazione della Comunità economica europea. Inoltre.... il suo ruolo come città globale.
Domanda (digitare stop per uscire): Cosa sai del territorio di Roma?
Utente: Cosa sai del territorio di Roma?
RAG: Il territorio di Roma è ampio, con una superficie di 1287,36 km², rendendola il comune più vasto d'Italia e uno dei più estesi ..., escludendo l'enclave della Città del Vaticano.
Domanda (digitare stop per uscire): stop
Chiusura della connessione con la RAG
A questo punto possiamo dire che il test è superato. La RAG ha saputo rispondere ad ogni domanda, bypassando quella su Firenze su cui ha ammesso di non essere competente. Alla fine, tra l'altro, con un ciclo abbiamo creato anche un meccanismo di Question&Answer (Q&A) che permette di vedere un'applicazione che funziona di continuo e che può così dimostrare una reale e maggiore utilità.
Conclusioni
Questo esempio mostra bene come in realtà il lavorare con le RAG sia davvero composto di due anime. Tralasciando, per il momento, la fase di costruzione del database che ormai consideriamo acquisita, qui vediamo al lavoro un loop di domanda-risposta che funziona alla stessa maniera che se fosse basato su un database tradizionale:
- acquisiamo la domanda;
- interroghiamo il database;
- costruiamo una risposta;
- restituiamo la risposta.
Tutto continua finché l'utente non interrompe il programma o decide di uscire (digitando stop). Scopriamo in questo caso come, in fin dei conti, il database vettoriale, sebbene con una particolare logica matematica alla base, non si comporti altro che da database ovvero da tecnologia di conservazione ottimizzata e strutturata dei dati.
Se vuoi aggiornamenti su RAG: messa in produzione inserisci la tua email nel box qui sotto: