Corso
Retrieval-augmented generation (RAG) si è affermato come un approccio potente per creare applicazioni di IA che generano risposte precise, fondate e pertinenti al contesto recuperando e sintetizzando conoscenze da fonti esterne.
In questo tutorial ti spiego passo dopo passo come costruire un chatbot basato su RAG usando DeepSeek-R1 e un libro sulle fondamenta degli LLM come base di conoscenza. Al termine, sarai in grado di creare un'applicazione RAG locale capace di rispondere a domande tratte dal libro e interagire con gli utenti tramite un'interfaccia Gradio.
Perché usare DeepSeek-R1 con RAG?
DeepSeek-R1 è perfetto per sistemi basati su RAG grazie alle prestazioni ottimizzate, alle avanzate capacità di ricerca vettoriale e alla flessibilità in ambienti diversi, dalle configurazioni locali ai deployment scalabili. Ecco perché funziona bene:
- Recupero ad alte prestazioni: DeepSeek-R1 gestisce grandi collezioni di documenti con bassa latenza.
- Ranking di rilevanza fine-grained: garantisce il recupero accurato dei passaggi calcolando la similarità semantica.
- Vantaggi in termini di costi e privacy: puoi eseguire DeepSeek-R1 in locale per evitare costi API e mantenere al sicuro i dati sensibili.
- Integrazione semplice: si integra facilmente con database vettoriali come Chroma.
- Funzionalità offline: con DeepSeek-R1 puoi creare sistemi di retrieval che funzionano anche senza connessione internet, una volta scaricato il modello.
Panoramica: creare un chatbot RAG con DeepSeek-R1
Il nostro progetto demo si concentra sulla creazione di un chatbot RAG usando DeepSeek-R1 e Gradio.

Il processo inizia caricando e dividendo un PDF in blocchi di testo, per poi generare embedding per quei blocchi. Gli embedding vengono salvati in un database Chroma per un recupero efficiente. Quando un utente invia una query, il sistema recupera i blocchi di testo più rilevanti e usa DeepSeek-R1 per generare una risposta basata sul contesto recuperato.
Passaggio 1: Prerequisiti
Prima di iniziare, assicuriamoci di avere installati i seguenti strumenti e librerie:
- Python 3.8+
- Langchain
- Chromadb
- Gradio
Esegui i seguenti comandi per installare le dipendenze necessarie:
!pip install langchain chromadb gradio ollama pymypdf
!pip install -U langchain-community
Una volta installate le dipendenze, esegui i seguenti import:
import ollama
import re
import gradio as gr
from concurrent.futures import ThreadPoolExecutor
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_community.embeddings import OllamaEmbeddings
from chromadb.config import Settings
from chromadb import Client
from langchain.vectorstores import Chroma
Passaggio 2: Carica il PDF con PyMuPDFLoader
Useremo il PyMuPDFLoader di LangChain per estrarre il testo dalla versione PDF del libro Foundations of LLMs di Tong Xiao e Jingbo Zhu—è un libro ricco di matematica, quindi il nostro chatbot dovrebbe saper spiegare bene la matematica alla base degli LLM. Puoi trovare il libro su arXiv.
# Load the document using PyMuPDFLoader
loader = PyMuPDFLoader("/path/to/Foundations_of_llms.pdf")
documents = loader.load()
Una volta caricato il documento, possiamo iniziare a suddividere il testo in blocchi per l'elaborazione successiva.
Passaggio 3: Dividi il documento in blocchi più piccoli
Divideremo il testo estratto in blocchi più piccoli e sovrapposti per migliorare il recupero del contesto. Puoi variare la dimensione del blocco e la sovrapposizione in base al tuo sistema nella funzione RecursiveCharacterTextSpilitter().
# Split the document into smaller chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(documents)
Ora abbiamo i blocchi di testo estratto, pronti per essere convertiti in embedding.
Passaggio 4: Genera gli embedding con DeepSeek-R1
Useremo Ollama Embeddings basato su DeepSeek-R1 per generare gli embedding dei documenti. A seconda della dimensione del documento, la generazione degli embedding può richiedere tempo, quindi è preferibile parallelizzarla per velocizzare l'elaborazione.
Nota: model="deepseek-r1" per impostazione predefinita usa il modello con 7B parametri. Puoi modificarlo in 8B, 14B, 32B, 70B o 671B. Sostituisci X nella seguente sintassi con la dimensione: model="deepseek-r1:X"
# Initialize Ollama embeddings using DeepSeek-R1
embedding_function = OllamaEmbeddings(model="deepseek-r1")
# Parallelize embedding generation
def generate_embedding(chunk):
return embedding_function.embed_query(chunk.page_content)
with ThreadPoolExecutor() as executor:
embeddings = list(executor.map(generate_embedding, chunks))
La funzione sopra inizializza DeepSeek-R1 tramite Ollama per generare embedding semantici ad alta dimensionalità, che verranno poi usati per il recupero dei documenti basato sulla similarità.
La funzione generate_embedding() prende il testo di un blocco di documento e genera il relativo embedding. Infine, ThreadPoolExecutor() applica generate_embedding() a ciascun blocco in parallelo, raccogliendo gli embedding in una lista per un'elaborazione più veloce rispetto all'esecuzione sequenziale.
Passaggio 5: Archivia gli embedding nel vector store Chroma
Salveremo gli embedding e i corrispondenti blocchi di testo in un database vettoriale ad alte prestazioni, Chroma.
# Initialize Chroma client and create/reset the collection
client = Client(Settings())
client.delete_collection(name="foundations_of_llms") # Delete existing collection (if any)
collection = client.create_collection(name="foundations_of_llms")
# Add documents and embeddings to Chroma
for idx, chunk in enumerate(chunks):
collection.add(
documents=[chunk.page_content],
metadatas=[{'id': idx}],
embeddings=[embeddings[idx]],
ids=[str(idx)] # Ensure IDs are strings
)
Per memorizzare gli embedding seguiamo questi passaggi:
1. Inizializza il client Chroma e reimposta la collection:
Client(Settings())inizializza il client Chroma per gestire il vector store.- Elimina qualsiasi collection esistente con nome simile usando
client.delete_collection()per evitare errori. Infine usaclient.create_collection()per creare una nuova collection in cui salvare i blocchi di documento e i relativi embedding.
2. Itera sui blocchi di documento:
- Itera su ciascun blocco e sul relativo embedding utilizzando un ID univoco in formato stringa.
3. Aggiungi blocchi ed embedding a Chroma:
- Per ogni blocco,
collection.add()salva: - Il contenuto del documento (
chunk.page_content) - Metadati (
{'id': idx}) per riferirsi al blocco - Il relativo vettore di embedding per il retrieval
- Una stringa ID univoca per identificare la voce
Questa configurazione garantisce che ogni blocco sia indicizzato correttamente per un recupero efficiente basato su vettori.
Passaggio 6: Inizializza il retriever
Inizializzeremo il retriever di Chroma, assicurandoci che usi gli stessi embedding DeepSeek-R1 per le query.
# Initialize retriever using Ollama embeddings for queries
retriever = Chroma(collection_name="foundations_of_llms", client=client, embedding_function=embedding_function).as_retriever()
Il retriever di Chroma si connette alla collection "foundations_of_llms" e usa gli embedding DeepSeek-R1 tramite Ollama per incorporare le query degli utenti. Recupera i blocchi di documento più rilevanti in base alla similarità vettoriale per risposte consapevoli del contesto.
Passaggio 7: Definisci la pipeline RAG
Ora recupereremo i blocchi di testo più rilevanti e li formatteremo per permettere a DeepSeek-R1 di generare le risposte.
def retrieve_context(question):
# Retrieve relevant documents
results = retriever.invoke(question)
# Combine the retrieved content
context = "\n\n".join([doc.page_content for doc in results])
return context
La funzione retrieve_context incorpora la query dell'utente usando DeepSeek-R1 e recupera i blocchi di documento più pertinenti tramite il retriever Chroma. Poi combina il contenuto dei blocchi recuperati in un'unica stringa di contesto per l'elaborazione successiva.
Passaggio 8: Interroga DeepSeek-R1 per risposte contestuali
Ora abbiamo la domanda e il contesto recuperato. Invia tutto a DeepSeek-R1 tramite Ollama per ottenere la risposta finale.
def query_deepseek(question, context):
# Format the input prompt
formatted_prompt = f"Question: {question}\n\nContext: {context}"
# Query DeepSeek-R1 using Ollama
response = embedding_function.chat(
model="deepseek-r1",
messages=[{'role': 'user', 'content': formatted_prompt}]
)
# Clean and return the response
response_content = response['message']['content']
final_answer = re.sub(r'<think>.*?</think>', '', response_content, flags=re.DOTALL).strip()
return final_answer
Per ottenere la risposta finale, combiniamo la domanda dell'utente e il contesto recuperato in un prompt strutturato. Poi inviamo questo prompt al modello DeepSeek-R1 tramite Ollama per ricevere una risposta. Per rendere l'output più pulito, rimuoviamo i tag non necessari e restituiamo la risposta finale.
Passaggio 9: Crea l'interfaccia Gradio
La pipeline RAG è pronta. Ora useremo Gradio per creare un'interfaccia interattiva con cui gli utenti possano fare domande relative alla base di conoscenza (in questo caso Foundations of LLMs).
def ask_question(question):
# Retrieve context and generate an answer using RAG
context = retrieve_context(question)
answer = query_deepseek(question, context)
return answer
# Set up the Gradio interface
interface = gr.Interface(
fn=ask_question,
inputs="text",
outputs="text",
title="RAG Chatbot: Foundations of LLMs",
description="Ask any question about the Foundations of LLMs book. Powered by DeepSeek-R1."
)
interface.launch()
La funzione ask_question() recupera il contesto pertinente tramite il retriever di Chroma e genera la risposta finale con DeepSeek-R1. L'interfaccia Gradio, costruita con gr.Interface(), consente agli utenti di porre domande in modo interattivo e ricevere risposte accurate e ben fondate sul contesto.
Complimenti! Ora hai un chatbot in locale pronto a discutere tutto ciò che riguarda gli LLM.
Ottimizzazioni
La demo sopra copre un'implementazione molto basilare di RAG, che può essere ottimizzata ulteriormente per l'efficienza. Ecco alcune cose da provare:
- Regolazione della dimensione dei blocchi: modifica i parametri
chunk_sizeechunk_overlapper bilanciare prestazioni e qualità del retrieval. - Versioni di modello più piccole: se DeepSeek-R1 è troppo pesante in termini di risorse, puoi usare versioni diverse (deepseek-r1:7b o deepseek-r1:8b o deepseek-r1:14b) tramite Ollama.
- Scala con Faiss: per documenti più grandi, valuta l'integrazione con Faiss per un recupero più veloce.
- Elaborazione in batch: se la generazione degli embedding è lenta, suddividi i blocchi in batch per migliorare l'efficienza.
Conclusione
In questo tutorial abbiamo costruito un chatbot locale basato su RAG usando DeepSeek-R1 e Chroma per il retrieval, che garantisce risposte accurate e ricche di contesto a partire da una grande base di conoscenza.
Per saperne di più su DeepSeek, ti consiglio questi blog:
Sono una Google Developers Expert in ML (Gen AI), una Kaggle 3x Expert e una Women Techmakers Ambassador con oltre 3 anni di esperienza nel tech. Ho co-fondato una startup health-tech nel 2020 e sto conseguendo un master in informatica al Georgia Tech, con specializzazione in machine learning.



