跳至主要内容

使用 Chroma、Ollama 和 Gradio 构建 DeepSeek R1 RAG 聊天机器人

学习如何使用 DeepSeek-R1 搭配 Ollama、LangChain 和 Chroma 构建本地 RAG 聊天机器人。
· 12分钟 阅读

使用 AI 探索

检索增强生成(RAG) 已成为一种强大的方法,它通过从外部来源检索并综合知识,构建能够生成精准、可靠且具备上下文相关性的答案的 AI 应用。

在本教程中,我将逐步讲解如何使用 DeepSeek-R1,并以一本关于 LLM 基础的书作为知识库,构建一个基于 RAG 的聊天机器人。完成本教程后,您将能够创建一个本地 RAG 应用,它可以回答书中的问题,并通过 Gradio 界面与用户交互。

为什么在 RAG 中使用 DeepSeek-R1?

DeepSeek-R1 非常适合基于 RAG 的系统,原因在于其优化的性能、先进的向量检索能力,以及从本地到可扩展部署等不同环境中的灵活性。以下是其高效的几个原因:

  1. 高性能检索: DeepSeek-R1 能以低延迟处理大规模文档集合。
  2. 细粒度相关性排序: 通过计算语义相似度,确保准确检索到关键段落。
  3. 成本与隐私优势: 您可以在本地运行 DeepSeek-R1,避免 API 费用并保障敏感数据安全。
  4. 易于集成: 可轻松与 Chroma 等向量数据库集成。
  5. 离线能力: 下载模型后,即使在无网络环境下也能构建可用的检索系统。

概览:使用 DeepSeek-R1 构建 RAG 聊天机器人

我们的演示项目聚焦于使用 DeepSeek-R1 和 Gradio 构建一个 RAG 聊天机器人。

RAG Chatbot pipeline

流程从加载并将 PDF 拆分为文本块开始,随后为这些文本块生成向量嵌入。这些嵌入被存储在 Chroma 数据库中以便高效检索。当用户提交查询时,系统会检索最相关的文本块,并使用 DeepSeek-R1 基于检索到的上下文生成答案。

步骤 1:前置条件

在开始之前,请确保已安装以下工具和库:

  • Python 3.8+
  • Langchain
  • Chromadb
  • Gradio

运行以下命令以安装必要的依赖:

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

安装完成上述依赖后,运行以下导入命令:

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

步骤 2:使用 PyMuPDFLoader 加载 PDF

我们将使用 LangChain 的 PyMuPDFLoader 从 Tong Xiao 和 Jingbo Zhu 所著的《Foundations of LLMs》PDF 版本中提取文本——这是一部数学内容较多的书,这意味着我们的聊天机器人应能很好地解释 LLM 背后的数学原理。您可以在 arXiv 上找到这本书。

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

documents = loader.load()

加载文档后,我们就可以开始将文本划分为更小的块以便后续处理。

步骤 3:将文档拆分为更小的块

我们会将提取到的文本拆分为较小且相互重叠的块,以便更好地进行上下文检索。您可以在 RecursiveCharacterTextSpilitter() 函数中根据系统情况调整块大小和重叠大小。

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

chunks = text_splitter.split_documents(documents)

现在,我们已经得到了可用于转换为嵌入的文本块。

步骤 4:使用 DeepSeek-R1 生成嵌入

我们将使用基于 DeepSeek-R1 的 Ollama Embeddings 来生成文档嵌入。根据文档大小,生成嵌入可能需要一定时间,因此最好进行并行化处理以加快速度。

注意:model="deepseek-r1" 默认指 7B 参数规模模型。您可以根据需要更改为 8B、14B、32B、70B 或 671B。在以下模型名称中用模型规模替换 X: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))

上述函数通过 Ollama 初始化 DeepSeek-R1,以生成高维语义嵌入,这些嵌入随后将用于基于相似度的文档检索。

generate_embedding() 函数接收文档块的文本并生成其嵌入。最后,ThreadPoolExecutor() 并发地将 generate_embedding() 应用于每个块,将嵌入收集为列表,相比顺序执行可更快完成处理。

步骤 5:将嵌入存储在 Chroma 向量存储中

我们将把嵌入及其对应的文本块存储在高性能向量数据库 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
    )

我们按照以下步骤来存储嵌入:

1. 初始化 Chroma 客户端并重置集合:

  • Client(Settings()) 初始化 Chroma 客户端以管理向量存储。
  • 使用 client.delete_collection() 删除与您的集合同名的已存在集合以避免报错。最后,使用 client.create_collection() 创建新集合以存储文档块及其嵌入。

2. 遍历文档块:

  • 遍历每个文档块及其对应嵌入,并使用唯一的字符串 ID 进行标识。

3. 将块和嵌入添加到 Chroma:

  • 对于每个块,collection.add() 会存储:
    • 其文档内容(chunk.page_content)
    • 元数据({'id': idx})用于引用该块
    • 对应的嵌入向量用于检索
    • 用于标识条目的唯一ID 字符串

此设置可确保每个文档块都被正确索引,以便进行高效的向量化检索。

步骤 6:初始化检索器

我们将初始化 Chroma 检索器,确保其对查询使用同一套 DeepSeek-R1 嵌入。

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

Chroma 检索器连接到 "foundations_of_llms" 集合,并通过 Ollama 使用 DeepSeek-R1 嵌入对用户查询进行编码。它基于向量相似度检索最相关的文档块,以生成具备上下文的响应。

步骤 7:定义 RAG 流程

接下来,我们将检索最相关的文本块,并将其格式化后交给 DeepSeek-R1 以生成答案。

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

retrieve_context 函数使用 DeepSeek-R1 对用户查询进行嵌入,并通过 Chroma 检索器检索最相关的文档块。随后将检索到的块内容合并为一个上下文字符串以供后续处理。

步骤 8:向 DeepSeek-R1 请求具备上下文的答案

现在我们已经有了问题和检索到的上下文。接下来,通过 Ollama 将其发送给 DeepSeek-R1 获取最终答案。

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

为了得到最终答案,我们先将用户问题和检索到的上下文组合为结构化提示,然后通过 Ollama 将该提示发送给 DeepSeek-R1 以获取响应。为使最终输出更易阅读,我们会移除不必要的标签并返回最终答案。

步骤 9:构建 Gradio 界面

我们的 RAG 流程已就绪。现在,我们将使用 Gradio 创建一个交互式界面,供用户就其知识库(本例为《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()

ask_question() 函数使用 Chroma 检索器获取相关上下文,并通过 DeepSeek-R1 生成最终答案。使用 gr.Interface() 构建的 Gradio 界面,使用户能够进行交互式提问,并获得具备上下文、依据充分的答案。

恭喜!您已经拥有一个本地运行的聊天机器人,可以讨论与 LLM 相关的任何内容。

RAG application with DeekSeek-R1 and Gradio

优化建议

上述演示覆盖了一个非常基础的 RAG 实现,还可以进一步优化以提升效率。可尝试如下方法:

  • 调整块大小: 调整 chunk_size 和 chunk_overlap 参数,以在性能与检索质量之间取得平衡。
  • 使用更小的模型版本: 如果 DeepSeek-R1 资源占用较高,您可以通过 Ollama 使用不同版本(deepseek-r1:7b、deepseek-r1:8b 或 deepseek-r1:14b)。
  • 使用 Faiss 扩展: 对于更大的文档,考虑集成 Faiss 以实现更快的检索。
  • 批处理: 如果嵌入生成较慢,可对文本块进行批量处理以提升效率。

结语

在本教程中,我们构建了一个基于 RAG 的本地聊天机器人,使用 DeepSeek-R1 和 Chroma 进行检索,确保基于大型知识库的问题得到准确且富含上下文的回答。

如需进一步了解 DeepSeek,推荐阅读以下博客:

Aashi Dutt

作者

Aashi Dutt

我是一名 Google Developers 机器学习(生成式 AI)领域的专家、Kaggle 三项专家,以及 Women Techmakers 大使,拥有 3 年以上的技术从业经验。2020 年我共同创办了一家健康科技初创公司,目前在佐治亚理工学院攻读计算机科学硕士,专攻机器学习。

主题