Chuyển đến nội dung chính

Chatbot RAG DeepSeek R1 với Chroma, Ollama và Gradio

Tìm hiểu cách xây dựng chatbot RAG cục bộ bằng DeepSeek-R1 với Ollama, LangChain và Chroma.
Đã cập nhật 5 thg 10, 2026  · 12 phút đọc

Khám phá cùng AI

ChatGPTClaudePerplexity

Retrieval-augmented generation (RAG) đã nổi lên như một cách tiếp cận mạnh mẽ để xây dựng ứng dụng AI có thể tạo ra câu trả lời chính xác, có căn cứ và phù hợp ngữ cảnh bằng cách truy xuất và tổng hợp tri thức từ các nguồn bên ngoài. 

Trong hướng dẫn này, tôi sẽ giải thích từng bước cách xây dựng một chatbot dựa trên RAG bằng DeepSeek-R1 và một cuốn sách về nền tảng của LLM làm cơ sở tri thức. Kết thúc hướng dẫn, bạn sẽ có thể tạo một ứng dụng RAG cục bộ có khả năng trả lời câu hỏi từ cuốn sách và tương tác với người dùng qua giao diện Gradio.

Vì sao dùng DeepSeek-R1 với RAG?

DeepSeek-R1 là lựa chọn lý tưởng cho hệ thống RAG nhờ hiệu năng tối ưu, năng lực tìm kiếm vector nâng cao và tính linh hoạt trên nhiều môi trường, từ cài đặt cục bộ đến triển khai mở rộng. Dưới đây là một vài lý do khiến nó hiệu quả:

  1. Khả năng truy xuất hiệu năng cao: DeepSeek-R1 xử lý bộ sưu tập tài liệu lớn với độ trễ thấp.
  2. Xếp hạng mức độ liên quan chi tiết: Đảm bảo truy xuất chính xác các đoạn văn bằng cách tính độ tương đồng ngữ nghĩa.
  3. Tiết kiệm chi phí và đảm bảo riêng tư: Bạn có thể chạy DeepSeek-R1 cục bộ để tránh phí API và giữ an toàn cho dữ liệu nhạy cảm.
  4. Dễ tích hợp: Dễ dàng tích hợp với các cơ sở dữ liệu vector như Chroma.
  5. Khả năng ngoại tuyến: Với DeepSeek-R1, bạn có thể xây dựng hệ thống truy xuất hoạt động ngay cả khi không có internet sau khi tải mô hình.

Tổng quan: Xây dựng chatbot RAG với DeepSeek-R1

Dự án demo của chúng tôi tập trung vào xây dựng chatbot RAG bằng DeepSeek-R1 và Gradio.

Quy trình Chatbot RAG

Quy trình bắt đầu bằng việc tải và chia một tệp PDF thành các đoạn văn bản nhỏ, sau đó tạo embedding cho các đoạn này. Những embedding này được lưu trong cơ sở dữ liệu Chroma để truy xuất hiệu quả. Khi người dùng gửi truy vấn, hệ thống sẽ truy xuất các đoạn văn bản liên quan nhất và dùng DeepSeek-R1 để tạo câu trả lời dựa trên ngữ cảnh đã truy xuất.

Bước 1: Yêu cầu tiên quyết

Trước khi bắt đầu, hãy đảm bảo rằng chúng ta đã cài đặt các công cụ và thư viện sau:

  • Python 3.8+
  • Langchain
  • Chromadb
  • Gradio

Chạy các lệnh sau để cài đặt các phụ thuộc cần thiết:

!pip install langchain chromadb gradio ollama pymypdf
!pip install -U langchain-community

Sau khi cài đặt xong các phụ thuộc trên, hãy chạy các lệnh import sau:

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

Bước 2: Tải PDF bằng PyMuPDFLoader

Chúng ta sẽ dùng PyMuPDFLoader của LangChain để trích xuất văn bản từ phiên bản PDF của cuốn Foundations of LLMs của Tong Xiao và Jingbo Zhu—đây là cuốn sách nặng về toán, nghĩa là chatbot của chúng ta nên có khả năng giải thích tốt phần toán học đằng sau LLM. Bạn có thể tìm cuốn sách trên arXiv. 

# Load the document using PyMuPDFLoader
loader = PyMuPDFLoader("/path/to/Foundations_of_llms.pdf")

documents = loader.load()

Sau khi tài liệu được tải, chúng ta có thể bắt đầu chia văn bản thành các đoạn để xử lý tiếp.

Bước 3: Chia tài liệu thành các đoạn nhỏ

Chúng ta sẽ chia văn bản đã trích xuất thành các đoạn nhỏ có chồng lấn để truy xuất ngữ cảnh tốt hơn. Bạn có thể thay đổi kích thước đoạn và mức chồng lấn tùy theo hệ thống trong hàm RecursiveCharacterTextSpilitter().

# Split the document into smaller chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)

chunks = text_splitter.split_documents(documents)

Giờ đây, chúng ta đã có các đoạn văn bản sẵn sàng để chuyển thành embedding.

Bước 4: Tạo embedding bằng DeepSeek-R1

Chúng ta sẽ sử dụng Ollama Embeddings dựa trên DeepSeek-R1 để tạo embedding cho tài liệu. Tùy vào kích thước tài liệu, việc tạo embedding có thể mất thời gian, nên ưu tiên xử lý song song để nhanh hơn.

Lưu ý: model="deepseek-r1" mặc định dùng mô hình 7B tham số. Bạn có thể đổi sang 8B, 14B, 32B, 70B hoặc 671B. Thay X trong tên mô hình sau bằng kích thước mô hình: 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))

Hàm trên khởi tạo DeepSeek-R1 qua Ollama để tạo embedding ngữ nghĩa chiều cao, sau này sẽ dùng cho truy xuất tài liệu dựa trên độ tương đồng.

Hàm generate_embedding() nhận văn bản của một đoạn và tạo embedding cho nó. Cuối cùng, ThreadPoolExecutor() áp dụng generate_embedding() cho từng đoạn đồng thời, thu thập embedding vào một danh sách để xử lý nhanh hơn so với tuần tự.

Bước 5: Lưu embedding vào Chroma Vector Store

Chúng ta sẽ lưu embedding và các đoạn văn bản tương ứng vào cơ sở dữ liệu vector hiệu năng cao, 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
    )

Chúng ta bắt đầu bằng các bước sau để lưu embedding:

1. Khởi tạo client Chroma và đặt lại bộ sưu tập:

  • Client(Settings()) khởi tạo client Chroma để quản lý vector store. 
  • Xóa mọi bộ sưu tập trùng tên bằng client.delete_collection() để tránh lỗi. Cuối cùng, dùng client.create_collection() để tạo bộ sưu tập mới lưu các đoạn tài liệu và embedding của chúng.

2. Duyệt qua các đoạn tài liệu:

  • Duyệt từng đoạn tài liệu và embedding tương ứng bằng ID chuỗi duy nhất của nó.

3. Thêm đoạn và embedding vào Chroma:

  • Với mỗi đoạn, collection.add() lưu:
    • Nội dung tài liệu (chunk.page_content)
    • Metadata ({'id': idx}) để tham chiếu đoạn
    • Vector embedding tương ứng để truy xuất
    • Một chuỗi ID duy nhất để nhận diện mục ghi

Thiết lập này đảm bảo mỗi đoạn tài liệu được lập chỉ mục chính xác để truy xuất theo vector hiệu quả.

Bước 6: Khởi tạo retriever

Chúng ta sẽ khởi tạo retriever của Chroma, đảm bảo nó dùng cùng embedding DeepSeek-R1 cho các truy vấn.

# Initialize retriever using Ollama embeddings for queries
retriever = Chroma(collection_name="foundations_of_llms", client=client, embedding_function=embedding_function).as_retriever()

Retriever của Chroma kết nối tới bộ sưu tập "foundations_of_llms" và dùng embedding DeepSeek-R1 qua Ollama để nhúng truy vấn của người dùng. Nó truy xuất các đoạn tài liệu liên quan nhất dựa trên độ tương đồng vector để tạo phản hồi theo ngữ cảnh.

Bước 7: Định nghĩa pipeline RAG

Tiếp theo, chúng ta sẽ truy xuất các đoạn văn bản phù hợp nhất và định dạng chúng để DeepSeek-R1 tạo câu trả lời.

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

Hàm retrieve_context nhúng truy vấn của người dùng bằng DeepSeek-R1 và truy xuất các đoạn tài liệu liên quan nhất qua retriever của Chroma. Sau đó, nó ghép nội dung các đoạn đã truy xuất thành một chuỗi ngữ cảnh để xử lý tiếp.

Bước 8: Gửi truy vấn tới DeepSeek-R1 để nhận câu trả lời theo ngữ cảnh

Giờ chúng ta đã có câu hỏi và ngữ cảnh đã truy xuất. Tiếp theo, gửi chúng tới DeepSeek-R1 qua Ollama để nhận câu trả lời cuối cùng.

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

Để có câu trả lời cuối cùng, chúng ta bắt đầu bằng cách kết hợp câu hỏi của người dùng và ngữ cảnh đã truy xuất thành một prompt có cấu trúc. Sau đó gửi prompt này tới mô hình DeepSeek-R1 qua Ollama để nhận phản hồi. Để đầu ra gọn gàng, ta loại bỏ các thẻ không cần thiết và trả về câu trả lời cuối.

Bước 9: Xây dựng giao diện Gradio

Chúng ta đã có pipeline RAG. Giờ sẽ dùng Gradio để tạo giao diện tương tác cho người dùng đặt câu hỏi liên quan tới cơ sở tri thức của nó (trong trường hợp này là 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()

Hàm ask_question() truy xuất ngữ cảnh liên quan bằng retriever của Chroma và tạo câu trả lời cuối qua DeepSeek-R1. Giao diện Gradio, được xây dựng với gr.Interface(), cho phép người dùng đặt câu hỏi tương tác và nhận câu trả lời chính xác, có căn cứ theo ngữ cảnh.

Chúc mừng! Bạn đã có một chatbot chạy cục bộ, sẵn sàng thảo luận mọi điều liên quan đến LLM.

Ứng dụng RAG với DeekSeek-R1 và Gradio

Tối ưu hóa

Bản demo trên bao quát một triển khai RAG rất cơ bản, có thể tối ưu thêm để hiệu quả hơn. Dưới đây là vài điều để thử:

  • Điều chỉnh kích thước đoạn: Điều chỉnh các tham số chunk_size và chunk_overlap để cân bằng giữa hiệu năng và chất lượng truy xuất.
  • Dùng phiên bản mô hình nhỏ hơn: Nếu DeepSeek-R1 tiêu tốn tài nguyên, bạn có thể dùng các phiên bản khác (deepseek-r1:7b, deepseek-r1:8b hoặc deepseek-r1:14b) qua Ollama.
  • Mở rộng bằng Faiss: Với tài liệu lớn, hãy cân nhắc tích hợp Faiss để truy xuất nhanh hơn.
  • Xử lý theo lô: Nếu việc tạo embedding chậm, hãy gom các đoạn thành lô để tăng hiệu quả.

Kết luận

Trong hướng dẫn này, chúng ta đã xây dựng một chatbot cục bộ dựa trên RAG bằng DeepSeek-R1 và Chroma cho phần truy xuất, đảm bảo các câu trả lời chính xác, phong phú về ngữ cảnh dựa trên một cơ sở tri thức lớn.

Để tìm hiểu thêm về DeepSeek, tôi khuyến nghị các bài viết sau:


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.

Chủ đề
Trí tuệ Nhân tạo
Mô hình Ngôn ngữ Lớn

Học AI với những khóa học này!

Khóa học

Retrieval Augmented Generation (RAG) với LangChain

3 giờ
21.3K
Tìm hiểu các phương pháp tiên tiến để tích hợp dữ liệu bên ngoài với LLMs bằng cách sử dụng Retrieval Augmented Generation (RAG) với LangChain.
Xem chi tiếtRight Arrow
Bắt Đầu Khóa Học
Xem thêmRight Arrow
Liên quan

blog

Claude Opus 4.6: Tính năng, Điểm chuẩn, Bài kiểm tra thực hành và hơn thế nữa

Mô hình mới nhất của Anthropic dẫn đầu ở mã hóa tác tử và lập luận phức tạp. Thêm vào đó, nó có cửa sổ ngữ cảnh 1M.
Matt Crabtree's photo

Matt Crabtree

10 phút

Xem ThêmXem Thêm