cours
DeepSeek R1 RAG Chatbot avec Chroma, Ollama et Gradio
La génération augmentée par récupération (RAG) s'est imposée comme une approche puissante pour construire des applications d'IA qui génèrent des réponses précises, fondées et contextuellement pertinentes en récupérant et en synthétisant des connaissances provenant de sources externes.
Dans ce tutoriel, j'expliquerai étape par étape comment construire un chatbot basé sur RAG en utilisant DeepSeek-R1 et un livre sur les fondements des LLM comme base de connaissance. À la fin de ce tutoriel, vous serez en mesure de créer une application RAG locale capable de répondre aux questions du livre et d'interagir avec les utilisateurs via une interface Gradio.
Pourquoi utiliser DeepSeek-R1 avec RAG ?
DeepSeek-R1 est idéal pour les systèmes basés sur RAG en raison de ses performances optimisées, de ses capacités avancées de recherche vectorielle et de sa flexibilité dans différents environnements, qu'il s'agisse d'installations locales ou de déploiements évolutifs. Voici quelques raisons de son efficacité :
- Recherche performante : DeepSeek-R1 traite de grandes collections de documents avec une faible latence.
- Classement fin de la pertinence : Il garantit une récupération précise des passages en calculant la similarité sémantique.
- Coût et avantages en matière de protection de la vie privée : Vous pouvez exécuter DeepSeek-R1 localement pour éviter les frais d'API et sécuriser les données sensibles.
- Intégration facile : Il s'intègre facilement aux bases de données vectorielles telles que Chroma.
- Capacités hors ligne : Avec DeepSeek-R1, vous pouvez construire des systèmes de recherche qui fonctionnent même sans accès à Internet, une fois le modèle téléchargé.
Vue d'ensemble : Construire un Chatbot RAG avec DeepSeek-R1
Notre projet de démonstration se concentre sur la construction d'un chatbot RAG en utilisant DeepSeek-R1 et Gradio.
Le processus commence par le chargement et la division d'un PDF en morceaux de texte, suivis de la génération de enchâssements pour ces morceaux. Ces encastrements sont stockés dans une base de données Chroma pour une récupération efficace. Lorsqu'un utilisateur soumet une requête, le système récupère les morceaux de texte les plus pertinents et utilise DeepSeek-R1 pour générer une réponse basée sur le contexte récupéré.
Étape 1 : Conditions préalables
Avant de commencer, nous devons nous assurer que les outils et bibliothèques suivants sont installés :
- Python 3.8+.
- Langchain
- Chromadb
- Gradio
Exécutez les commandes suivantes pour installer les dépendances nécessaires :
!pip install langchain chromadb gradio ollama pymypdf
!pip install -U langchain-community
Une fois les dépendances ci-dessus installées, exécutez les commandes d'importation suivantes :
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
Étape 2 : Chargez le PDF à l'aide de PyMuPDFLoader
Nous utiliserons le site PyMuPDFLoader
de LangChain pour extraire le texte de la version PDF du livre Foundations of LLMs de Tong Xiao et Jingbo Zhu - il s'agit d'un livre à forte teneur en mathématiques, ce qui signifie que notre chatbot devrait être en mesure d'expliquer correctement les mathématiques qui sous-tendent les LLM. Vous pouvez trouver le livre sur arXiv.
# Load the document using PyMuPDFLoader
loader = PyMuPDFLoader("/path/to/Foundations_of_llms.pdf")
documents = loader.load()
Une fois le document chargé, nous pouvons commencer à diviser le texte en morceaux pour la suite du traitement.
Étape 3 : Diviser le document en petits morceaux
Nous diviserons le texte extrait en petits morceaux qui se chevauchent afin d'améliorer la récupération du contexte. La fonction RecursiveCharacterTextSpilitter()
vous permet de modifier la taille des morceaux et le chevauchement des morceaux en fonction de votre système.
# Split the document into smaller chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(documents)
Nous disposons à présent des morceaux de texte extraits, qui sont prêts à être convertis en embeddings.
Étape 4 : Générer des Embeddings en utilisant DeepSeek-R1
Nous utiliserons Ollama Embeddings basé sur DeepSeek-R1 pour générer les embeddings de documents. En fonction de la taille du document, la génération de l'incorporation peut prendre du temps, il est donc préférable de la paralléliser pour un traitement plus rapide.
Note : model="deepseek-r1"
considère par défaut le modèle de paramètres 7B. Vous pouvez le remplacer par 8B, 14B, 32B, 70B ou 671B. Remplacez X dans le nom du modèle suivant par la taille du modèle : 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 fonction ci-dessus initialise DeepSeek-R1 via Ollama pour générer des encastrements sémantiques de haute dimension, qui seront ensuite utilisés pour la recherche de documents basée sur la similarité.
La fonction generate_embedding()
prend le texte d'un morceau de document et génère son intégration. Enfin, ThreadPoolExecutor()
applique generate_embedding()
à chaque morceau simultanément, en rassemblant les encastrements dans une liste pour un traitement plus rapide par rapport à l'exécution séquentielle.
Étape 5 : Stocker les données intégrées dans le Chroma Vector Store
Nous stockerons les enchâssements et les morceaux de texte correspondants dans une base de données vectorielle très performante, 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
)
Nous commençons par suivre les étapes suivantes pour stocker les encastrements :
1. Initialiser le client Chroma et réinitialiser la collection :
- Le site
Client(Settings())
initialise le client Chroma pour gérer le magasin de vecteurs. - Supprimez toute collection existante similaire à votre nom de collection à l'aide de
client.delete_collection()
pour éviter les erreurs. Enfin, utilisezclient.create_collection()
pour créer une nouvelle collection afin de stocker les morceaux de documents et leurs enchâssements.
2. Interroger les morceaux de documents :
- Interrogez chaque fragment de document et son intégration correspondante à l'aide de sa chaîne d'identification unique.
3. Ajoutez des morceaux et des incorporations à Chroma :
- Pour chaque morceau,
collection.add()
stocke : - Le contenu du contenu du document (
chunk.page_content
) - Métadonnées (
{'id': idx}
) pour référencer le chunk - Son vecteur d'intégration vecteur d'intégration pour la recherche
- A unique ID unique pour identifier l'entrée
Cette configuration garantit que chaque fragment de document est indexé correctement pour une recherche vectorielle efficace.
Étape 6 : Initialiser le Retriever
Nous allons initialiser le récupérateur Chroma, en veillant à ce qu'il utilise les mêmes encastrements DeepSeek-R1 pour les requêtes.
# Initialize retriever using Ollama embeddings for queries
retriever = Chroma(collection_name="foundations_of_llms", client=client, embedding_function=embedding_function).as_retriever()
Le récupérateur Chroma se connecte à la collection "foundations_of_llms" et utilise les encastrements DeepSeek-R1 via Ollama pour intégrer les requêtes des utilisateurs. Il récupère les morceaux de documents les plus pertinents sur la base de la similarité des vecteurs pour des réponses tenant compte du contexte.
Étape 7 : Définir la filière RAG
Ensuite, nous récupérerons les morceaux de texte les plus pertinents et les formaterons pour que DeepSeek-R1 puisse générer des réponses.
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 fonction retrieve_context
intègre la requête de l'utilisateur à l'aide de DeepSeek-R1 et récupère les morceaux de documents les plus pertinents via le récupérateur Chroma. Il combine ensuite le contenu des morceaux récupérés en une seule chaîne de contexte pour la suite du traitement.
Étape 8 : Interroger DeepSeek-R1 pour obtenir des réponses contextuelles
Nous disposons à présent de la question et du contexte récupéré. Ensuite, nous l'envoyons à DeepSeek-R1 via Ollama pour obtenir la réponse 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
Pour obtenir la réponse finale, nous commençons par combiner la question de l'utilisateur et le contexte récupéré dans une invite structurée. Envoyez ensuite cette demande au modèle DeepSeek-R1 via Ollama pour recevoir une réponse. Pour rendre le résultat final présentable, nous supprimons les balises inutiles et renvoyons la réponse finale.
Étape 9 : Construire l'interface Gradio
Nous avons mis en place notre pipeline RAG. Nous allons maintenant utiliser Gradio pour créer une interface interactive permettant aux utilisateurs de poser des questions relatives à sa base de connaissances (ici, Fondements des LLM).
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 fonction ask_question()
récupère le contexte pertinent à l'aide du récupérateur Chroma et génère la réponse finale via DeepSeek-R1. L'interface Gradio, construite avec gr.Interface()
, permet aux utilisateurs de poser des questions de manière interactive et de recevoir des réponses contextuelles précises et fondées.
Félicitations ! Vous disposez désormais d'un chatbot local prêt à discuter de tout ce qui concerne les LLM.
Optimisations
La démonstration ci-dessus couvre une implémentation très basique de RAG, qui peut être optimisée pour plus d'efficacité. Voici quelques exemples à essayer :
- Ajustement de la taille des morceaux : Ajustez les paramètres
chunk_size
etchunk_overlap
pour équilibrer les performances et la qualité de l'extraction. - Versions plus petites du modèle : Si DeepSeek-R1 est trop gourmand en ressources, vous pouvez utiliser différentes versions (deepseek-r1:7b ou deepseek-r1:8b ou deepseek-r1:14b) via Ollama.
- Échelle utilisant Faiss : Pour les documents plus volumineux, envisagez d'intégrer Faiss pour une recherche plus rapide.
- Traitement par lots : Si la génération de l'incorporation est lente, regroupez les morceaux pour améliorer l'efficacité.
Conclusion
Dans ce tutoriel, nous avons construit un chatbot local basé sur RAG en utilisant DeepSeek-R1 et Chroma pour la recherche, ce qui garantit des réponses précises et contextuelles aux questions basées sur une large base de connaissances.
Pour en savoir plus sur DeepSeek, je vous recommande ces blogs :

Je suis un expert Google Developers en ML (Gen AI), un expert Kaggle 3x, et un ambassadeur Women Techmakers avec plus de 3 ans d'expérience dans la technologie. J'ai cofondé une startup dans le domaine de la santé en 2020 et je poursuis un master en informatique à Georgia Tech, avec une spécialisation dans l'apprentissage automatique.
Apprenez l'IA avec ces cours !
cours
Bases de données vectorielles pour les emboîtements avec Pinecone
cursus