Cursus
Retrieval-augmented generation (RAG) is uitgegroeid tot een krachtige aanpak om AI-toepassingen te bouwen die precieze, onderbouwde en contextueel relevante antwoorden genereren door kennis uit externe bronnen op te halen en te synthetiseren.
In deze tutorial leg ik stap voor stap uit hoe je een RAG-gebaseerde chatbot bouwt met DeepSeek-R1 en een boek over de basis van LLM’s als kennisbank. Aan het einde van deze tutorial kun je een lokale RAG-app bouwen die vragen uit het boek beantwoordt en met gebruikers interacteert via een Gradio-interface.
Waarom DeepSeek-R1 gebruiken met RAG?
DeepSeek-R1 is een ideale match voor RAG-systemen dankzij de geoptimaliseerde prestaties, geavanceerde vectorzoekmogelijkheden en flexibiliteit in verschillende omgevingen, van lokaal tot schaalbare deployments. Dit zijn enkele redenen waarom het effectief is:
- Hoge ophaalprestaties: DeepSeek-R1 verwerkt grote documentcollecties met lage latentie.
- Fijnmazige relevantieranking: Zorgt voor nauwkeurige passage-ophaal door semantische gelijkenis te berekenen.
- Kosten- en privacyvoordelen: Je kunt DeepSeek-R1 lokaal draaien om API-kosten te vermijden en gevoelige data te beveiligen.
- Eenvoudige integratie: Integreert eenvoudig met vectordatabases zoals Chroma.
- Offline-mogelijkheden: Met DeepSeek-R1 kun je retrievalsystemen bouwen die ook zonder internet werken zodra het model is gedownload.
Overzicht: een RAG-chatbot bouwen met DeepSeek-R1
Ons demoproject richt zich op het bouwen van een RAG-chatbot met DeepSeek-R1 en Gradio.

Het proces begint met het laden en opdelen van een PDF in tekstfragmenten, gevolgd door het genereren van embeddings voor die fragmenten. Deze embeddings worden opgeslagen in een Chroma-database voor efficiënt ophalen. Wanneer een gebruiker een vraag stelt, haalt het systeem de meest relevante tekstfragmenten op en gebruikt DeepSeek-R1 om een antwoord te genereren op basis van de opgehaalde context.
Stap 1: Vereisten
Voordat we beginnen, controleren we of de volgende tools en libraries zijn geïnstalleerd:
- Python 3.8+
- Langchain
- Chromadb
- Gradio
Voer de volgende commando’s uit om de benodigde dependencies te installeren:
!pip install langchain chromadb gradio ollama pymypdf
!pip install -U langchain-community
Zodra bovenstaande dependencies zijn geïnstalleerd, voer je de volgende importcommando’s uit:
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
Stap 2: Laad de PDF met PyMuPDFLoader
We gebruiken LangChains PyMuPDFLoader om tekst te extraheren uit de PDF-versie van het boek Foundations of LLMs van Tong Xiao en Jingbo Zhu—dit is een wiskunde-intensief boek, dus onze chatbot moet de wiskunde achter LLM’s goed kunnen uitleggen. Je vindt het boek op arXiv.
# Load the document using PyMuPDFLoader
loader = PyMuPDFLoader("/path/to/Foundations_of_llms.pdf")
documents = loader.load()
Zodra het document is geladen, kunnen we de tekst in fragmenten verdelen voor verdere verwerking.
Stap 3: Splits het document in kleinere fragmenten
We splitsen de geëxtraheerde tekst in kleinere, overlappende fragmenten voor betere contextophaling. Je kunt de grootte van het fragment en de overlap variëren naar wens binnen de functie RecursiveCharacterTextSpilitter().
# Split the document into smaller chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(documents)
Nu hebben we de tekstfragmenten die klaar zijn om omgezet te worden in embeddings.
Stap 4: Genereer embeddings met DeepSeek-R1
We gebruiken Ollama Embeddings op basis van DeepSeek-R1 om de documentembeddings te genereren. Afhankelijk van de grootte van het document kan het genereren van embeddings tijd kosten, dus het is aan te raden dit te paralleliseren voor snellere verwerking.
Let op: model="deepseek-r1" gebruikt standaard het 7B-parametermodel. Je kunt dit aanpassen naar 8B, 14B, 32B, 70B of 671B. Vervang X in de volgende modelnaam door de modelgrootte: 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))
Bovenstaande functie initialiseert DeepSeek-R1 via Ollama om hoog-dimensionale semantische embeddings te genereren, die later gebruikt worden voor op gelijkenis gebaseerde documentophaal.
De functie generate_embedding() neemt de tekst van een documentfragment en genereert de embedding. Tot slot past ThreadPoolExecutor() generate_embedding() gelijktijdig toe op elk fragment, waarbij embeddings in een lijst worden verzameld voor snellere verwerking dan sequentiële uitvoering.
Stap 5: Sla embeddings op in Chroma Vector Store
We slaan de embeddings en bijbehorende tekstfragmenten op in een high-performance vectordatabase, 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
)
We volgen deze stappen om embeddings op te slaan:
1. Chroma-client initialiseren en collectie resetten:
- Met
Client(Settings())initialiseert je de Chroma-client om de vectorstore te beheren. - Verwijder een bestaande collectie met een vergelijkbare naam met
client.delete_collection()om fouten te voorkomen. Maak vervolgens metclient.create_collection()een nieuwe collectie om de documentfragmenten en hun embeddings op te slaan.
2. Itereer over documentfragmenten:
- Itereer over elk documentfragment en de bijbehorende embedding met een unieke string-ID.
3. Voeg fragmenten en embeddings toe aan Chroma:
- Voor elk fragment slaat
collection.add()het volgende op: - De documentinhoud (
chunk.page_content) - Metadata (
{'id': idx}) om naar het fragment te verwijzen - De bijbehorende embeddingvector voor ophalen
- Een unieke ID-string om het item te identificeren
Deze setup zorgt ervoor dat elk documentfragment correct wordt geïndexeerd voor efficiënt, vectorgebaseerd ophalen.
Stap 6: Initialiseer de retriever
We initialiseren de Chroma-retriever en zorgen dat deze dezelfde DeepSeek-R1-embeddings voor queries gebruikt.
# Initialize retriever using Ollama embeddings for queries
retriever = Chroma(collection_name="foundations_of_llms", client=client, embedding_function=embedding_function).as_retriever()
De Chroma-retriever verbindt met de collectie "foundations_of_llms" en gebruikt DeepSeek-R1-embeddings via Ollama om gebruikersvragen te embedden. Op basis van vectorgelijkenis haalt hij de meest relevante documentfragmenten op voor contextbewuste antwoorden.
Stap 7: Definieer de RAG-pijplijn
Vervolgens halen we de meest relevante tekstfragmenten op en formatteren we ze voor DeepSeek-R1 om antwoorden te genereren.
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
De functie retrieve_context embedt de gebruikersvraag met DeepSeek-R1 en haalt via de Chroma-retriever de meest relevante documentfragmenten op. Vervolgens wordt de inhoud van de opgehaalde fragmenten samengevoegd tot één contextstring voor verdere verwerking.
Stap 8: Stel DeepSeek-R1 contextuele vragen
We hebben nu de vraag en de opgehaalde context. Stuur dit vervolgens naar DeepSeek-R1 via Ollama voor ons definitieve antwoord.
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
Om het definitieve antwoord te krijgen, combineren we eerst de gebruikersvraag en de opgehaalde context in een gestructureerde prompt. Vervolgens sturen we deze prompt naar het DeepSeek-R1-model via Ollama om een reactie te ontvangen. Om de uitvoer netjes te maken, verwijderen we overbodige tags en geven we het eindantwoord terug.
Stap 9: Bouw de Gradio-interface
De RAG-pijplijn staat. Nu gebruiken we Gradio om een interactieve interface te maken waarmee gebruikers vragen kunnen stellen over de kennisbank (in dit geval 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()
De functie ask_question() haalt relevante context op via de Chroma-retriever en genereert het eindantwoord met DeepSeek-R1. De Gradio-interface, gebouwd met gr.Interface(), maakt het mogelijk om interactief vragen te stellen en contextueel nauwkeurige, onderbouwde antwoorden te ontvangen.
Gefeliciteerd! Je hebt nu een lokaal draaiende chatbot die alles over LLM’s kan bespreken.
Optimalisaties
Bovenstaande demo dekt een heel basale RAG-implementatie, die je verder kunt optimaliseren voor efficiëntie. Probeer bijvoorbeeld het volgende:
- Aanpassen van fragmentgrootte: Pas de parameters
chunk_sizeenchunk_overlapaan om performance en ophaalkwaliteit in balans te brengen. - Kleinere modelversies: Als DeepSeek-R1 te zwaar is qua resources, kun je via Ollama andere versies gebruiken (deepseek-r1:7b of deepseek-r1:8b of deepseek-r1:14b).
- Opschalen met Faiss: Voor grotere documenten kun je overwegen Faiss te integreren voor sneller ophalen.
- Batchverwerking: Als het genereren van embeddings traag is, batch de fragmenten om de efficiëntie te verbeteren.
Conclusie
In deze tutorial bouwden we een lokale RAG-chatbot met DeepSeek-R1 en Chroma voor retrieval, wat zorgt voor nauwkeurige, contextueel rijke antwoorden op vragen op basis van een grote kennisbank.
Wil je meer leren over DeepSeek? Dan raad ik deze blogs aan:
Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.


