Lernpfad
Retrieval-augmented Generation (RAG) hat sich als leistungsfähiger Ansatz für die Entwicklung von KI-Anwendungen erwiesen, die präzise, fundierte und kontextbezogene Antworten generieren, indem sie Wissen aus externen Quellen abrufen und zusammenführen.
In diesem Tutorial erkläre ich Schritt für Schritt, wie man einen RAG-basierten Chatbot mit DeepSeek-R1 und einem Buch über die Grundlagen der LLMs als Wissensbasis baut. Am Ende dieses Tutorials wirst du in der Lage sein, eine lokale RAG-Anwendung zu erstellen, die in der Lage ist, Fragen aus dem Buch zu beantworten und mit den Benutzern über eine Gradio-Schnittstelle zu interagieren.
Warum DeepSeek-R1 mit RAG verwenden?
DeepSeek-R1 ist aufgrund seiner optimierten Leistung, der erweiterten Vektorsuchfunktionen und der Flexibilität in verschiedenen Umgebungen - von lokalen Installationen bis hin zu skalierbaren Einsätzen - ideal für RAG-basierte Systeme geeignet. Hier sind einige Gründe, warum sie effektiv ist:
- Leistungsstarker Abruf: DeepSeek-R1 verarbeitet große Dokumentensammlungen mit geringer Latenzzeit.
- Feinkörniges Relevanz-Ranking: Durch die Berechnung der semantischen Ähnlichkeit wird das genaue Auffinden von Passagen sichergestellt.
- Kosten- und Datenschutzvorteile: Du kannst DeepSeek-R1 lokal ausführen ausführen, um API-Gebühren zu vermeiden und sensible Daten zu schützen.
- Einfache Integration: Es lässt sich leicht mit Vektordatenbanken wie Chroma.
- Offline-Fähigkeiten: Mit DeepSeek-R1 kannst du Retrievalsysteme aufbauen, die auch ohne Internetzugang funktionieren, sobald das Modell heruntergeladen ist.
Überblick: Bau eines RAG Chatbots mit DeepSeek-R1
Unser Demoprojekt konzentriert sich auf den Aufbau eines RAG-Chatbots mit DeepSeek-R1 und Gradio.
Der Prozess beginnt mit dem Laden und Zerlegen einer PDF-Datei in Textabschnitte, gefolgt von der Erzeugung von Einbettungen für diese Abschnitte. Diese Einbettungen werden in einer Chroma-Datenbank gespeichert, um sie effizient abrufen zu können. Wenn ein Nutzer eine Anfrage stellt, ruft das System die relevantesten Textabschnitte ab und verwendet DeepSeek-R1, um eine Antwort auf der Grundlage des abgerufenen Kontexts zu generieren.
Schritt 1: Voraussetzungen
Bevor wir beginnen, müssen wir sicherstellen, dass wir die folgenden Tools und Bibliotheken installiert haben:
- Python 3.8+
- Langchain
- Chromadb
- Gradio
Führe die folgenden Befehle aus, um die notwendigen Abhängigkeiten zu installieren:
!pip install langchain chromadb gradio ollama pymypdf
!pip install -U langchain-community
Sobald die oben genannten Abhängigkeiten installiert sind, führst du die folgenden Importbefehle aus:
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
Schritt 2: Das PDF mit PyMuPDFLoader laden
Wir werden LangChains PyMuPDFLoader
nutzen, um den Text aus der PDF-Version des Buches Foundations of LLMs von Tong Xiao und Jingbo Zhu zu extrahieren - es ist ein mathematiklastiges Buch, was bedeutet, dass unser Chatbot in der Lage sein sollte, die Mathematik hinter LLMs gut zu erklären. Du kannst das Buch auf arXiv finden.
# Load the document using PyMuPDFLoader
loader = PyMuPDFLoader("/path/to/Foundations_of_llms.pdf")
documents = loader.load()
Sobald das Dokument geladen ist, können wir damit beginnen, den Text für die weitere Verarbeitung in Abschnitte zu unterteilen.
Schritt 3: Das Dokument in kleinere Teile aufteilen
Wir teilen den extrahierten Text in kleinere, sich überschneidende Abschnitte auf, um den Kontext besser wiederzufinden. Du kannst die Größe des Chunks und die Überlappung der Chunks in der Funktion RecursiveCharacterTextSpilitter()
je nach deinem System variieren.
# Split the document into smaller chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(documents)
Jetzt haben wir die extrahierten Textabschnitte, die in Einbettungen umgewandelt werden können.
Schritt 4: Einbettungen mit DeepSeek-R1 generieren
Wir verwenden Ollama Embeddings, die auf DeepSeek-R1 basieren, um die Dokumenteneinbettungen zu erzeugen. Je nach Größe des Dokuments kann die Erstellung der Einbettung einige Zeit in Anspruch nehmen, daher ist es besser, sie zu parallelisieren, um die Verarbeitung zu beschleunigen.
Hinweis: model="deepseek-r1"
berücksichtigt standardmäßig das 7B-Parameter-Modell. Du kannst ihn je nach Bedarf in 8B, 14B, 32B, 70B oder 671B ändern. Ersetze X im folgenden Modellnamen durch die Modellgröße: 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))
Die obige Funktion initialisiert DeepSeek-R1 über Ollama, um hochdimensionale semantische Einbettungen zu generieren, die später für die ähnlichheitsorientierte Dokumentensuche verwendet werden.
Die Funktion generate_embedding()
nimmt den Text eines Dokumentenstücks und erzeugt dessen Einbettung. Schließlich wendet ThreadPoolExecutor()
generate_embedding()
auf jeden Chunk gleichzeitig an und sammelt die Einbettungen in einer Liste, um sie schneller zu verarbeiten als bei der sequentiellen Ausführung.
Schritt 5: Einbettungen im Chroma-Vektor-Speicher speichern
Wir speichern die Einbettungen und die entsprechenden Textabschnitte in einer leistungsstarken Vektordatenbank, 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
)
Wir beginnen mit den folgenden Schritten, um Einbettungen zu speichern:
1. Initialisiere den Chroma-Client und setze die Sammlung zurück:
- Die
Client(Settings())
initialisiert den Chroma-Client, um den Vektorspeicher zu verwalten. - Lösche alle bestehenden Sammlungen, die deinem Sammlungsnamen ähneln, mit
client.delete_collection()
, um Fehler zu vermeiden. Zum Schluss erstellst du mitclient.create_collection()
eine neue Sammlung, in der du die Dokumentenstücke und ihre Einbettungen speicherst.
2. Iteriere durch Dokumentenabschnitte:
- Iteriere über jedes Dokumentenstück und die dazugehörige Einbettung mit Hilfe seiner eindeutigen String-ID.
3. Füge Chunks und Embeddings zu Chroma hinzu:
- Für jeden Chunk speichert
collection.add()
: - Die Inhalt des Dokuments (
chunk.page_content
) - Metadaten (
{'id': idx}
), um den Chunk zu referenzieren - Sein entsprechender Einbettungsvektor für den Abruf
- A einzigartige ID String zur Identifizierung des Eintrags
Auf diese Weise wird sichergestellt, dass jedes Dokument korrekt indiziert wird, um eine effiziente vektorbasierte Suche zu ermöglichen.
Schritt 6: Initialisiere den Retriever
Wir initialisieren den Chroma-Retriever und stellen sicher, dass er die gleichen DeepSeek-R1-Einbettungen für Abfragen verwendet.
# Initialize retriever using Ollama embeddings for queries
retriever = Chroma(collection_name="foundations_of_llms", client=client, embedding_function=embedding_function).as_retriever()
Der Chroma-Retriever verbindet sich mit der Sammlung "foundations_of_llms" und verwendet DeepSeek-R1-Einbettungen über Ollama, um Benutzeranfragen einzubetten. Es findet die relevantesten Dokumententeile auf der Basis von Vektorähnlichkeit für kontextbezogene Antworten.
Schritt 7: Definiere die RAG-Pipeline
Als Nächstes suchen wir die wichtigsten Textabschnitte heraus und formatieren sie für DeepSeek-R1, um Antworten zu generieren.
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
Die Funktion retrieve_context
bettet die Benutzerabfrage mit DeepSeek-R1 ein und ruft die wichtigsten relevanten Dokumentenstücke über den Chroma Retriever ab. Dann kombiniert es den Inhalt der abgerufenen Chunks zu einem einzigen Kontextstring für die weitere Verarbeitung.
Schritt 8: Abfrage von DeepSeek-R1 für kontextbezogene Antworten
Jetzt haben wir die Frage und den gesuchten Kontext. Als Nächstes schickst du sie über Ollama an DeepSeek-R1, um unsere endgültige Antwort zu erhalten.
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
Um die endgültige Antwort zu erhalten, kombinieren wir zunächst die Frage des Nutzers und den ermittelten Kontext zu einer strukturierten Aufforderung. Sende diese Aufforderung dann über Ollama an das DeepSeek-R1-Modell, um eine Antwort zu erhalten. Um die endgültige Ausgabe vorzeigbar zu machen, entfernen wir unnötige Tags und geben die endgültige Antwort zurück.
Schritt 9: Erstelle die Gradio-Schnittstelle
Wir haben unsere RAG-Pipeline eingerichtet. Jetzt werden wir Gradio nutzen, um eine interaktive Schnittstelle zu schaffen, über die Nutzer Fragen zur Wissensbasis stellen können (in diesem Fall zu den Grundlagen 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()
Die Funktion ask_question()
ruft mithilfe des Chroma-Retrievers den relevanten Kontext ab und generiert die endgültige Antwort über DeepSeek-R1. Die Gradio-Benutzeroberfläche, die mit gr.Interface()
erstellt wurde, ermöglicht es den Nutzern, interaktiv Fragen zu stellen und kontextgenaue, fundierte Antworten zu erhalten.
Herzlichen Glückwunsch! Du hast jetzt einen lokal laufenden Chatbot, mit dem du über alles diskutieren kannst, was mit LLMs zu tun hat.
Optimierungen
Die obige Demo zeigt eine sehr einfache Implementierung von RAG, die weiter optimiert werden kann, um effizienter zu werden. Hier sind ein paar Dinge zum Ausprobieren:
- Anpassung der Brockengröße: Passe die Parameter
chunk_size
undchunk_overlap
an, um ein Gleichgewicht zwischen Leistung und Abrufqualität herzustellen. - Kleinere Modellversionen: Wenn DeepSeek-R1 zu ressourcenintensiv ist, kannst du andere Versionen (deepseek-r1:7b oder deepseek-r1:8b oder deepseek-r1:14b) über Ollama verwenden.
- Skaliere mit Faiss: Für größere Dokumente solltest du die Integration von Faiss für ein schnelleres Auffinden.
- Stapelverarbeitung: Wenn das Einbetten langsam ist, kannst du die Chunks stapeln, um die Effizienz zu verbessern.
Fazit
In diesem Tutorial haben wir einen RAG-basierten lokalen Chatbot gebaut, der DeepSeek-R1 und Chroma für das Retrieval nutzt und auf der Grundlage einer großen Wissensdatenbank genaue, kontextreiche Antworten auf Fragen gibt.
Um mehr über DeepSeek zu erfahren, empfehle ich diese Blogs:

Ich bin ein Google Developers Expert in ML (Gen AI), ein Kaggle 3x Expert und ein Women Techmakers Ambassador mit mehr als 3 Jahren Erfahrung im Tech-Bereich. Ich habe 2020 ein Startup im Bereich Gesundheitstechnologie mitbegründet und mache einen Master in Informatik an der Georgia Tech, der sich auf maschinelles Lernen spezialisiert.