【手把手包会】Chroma + Ollama + gradio 搭建本地RAG应用

1,312 阅读15分钟

喜欢的朋友记得关注、点赞、转发、收藏,你们的支持就是我最大的动力源泉。

今天课程较长,希望大家可以耐心看完,因为内容干货非常多,实现很多功能,包括页面展示、本地知识库等,如果能完全掌握,基本可以实现本地私人专属知识库,从而解决了专业信息生成不正确或误导性信息的问题。

本期课程旨在介绍如何使用 Chroma + Ollama + gradio 搭建本地RAG应用。RAG应用是一种利用额外数据增强 LLM 知识的技术,通过从外部数据库获取相关上下文信息,并在请求LLM生成响应时呈现给它,以解决生成不正确或误导性信息的问题。

首先看看不使用本地知识库的例子,我想查一下Modular Accelerated Xecution (MAX) 的意思,现在提问Qwen2:

~$ ollama run qwen2
>>> Modular Accelerated Xecution (MAX)是什么?
MODULAR ACCELERATED EXECUTION, 或者简称为 MAX,是一个用于在 FPGA(现场可编程门阵列)上加速计算的软件库或框架。FPGA
是一类可编程硬件设备,能够被重新配置以执行各种类型的计算任务。使用 MAX,开发者和工程师可以利用 FPGA 的高并行处理能力
来加速特定类型的数据处理、机器学习、图形渲染等应用。

MAX 通常包含了一系列的算法优化、数据结构实现以及底层硬件接口函数,使用户能够更高效地在 FPGA 上部署他们的计算工作负载
。通过 MAX,用户可以将密集型或高带宽数据处理任务从 CPU 或 GPU 转移到 FPGA,从而获得显著的速度提升和能效比。

具体来说,MAX 的特点包括:

1. **算法优化**:针对特定类型的数学运算、图像处理或机器学习算法进行了优化。
2. **可移植性**:提供一套抽象层,使得同样的代码可以在不同的 FPGA 平台上运行,并且能够平滑地迁移至新的硬件架构。
3. **易用性**:通过封装底层的硬件细节和提供高级接口,使得非 FPGA 专业人员也能够快速上手并进行开发。
4. **性能加速**:利用 FPGA 的并行处理能力显著提升计算速度,特别是在重复计算任务或数据密集型的应用场景中。

MAX 可以应用于诸如深度学习推理、实时视频分析、信号处理、高性能计算等领域,是实现硬件加速的重要工具。

>>> Send a message (/? for help)

结果肯定不是我想要的,因为Qwen2还没有覆盖这个领域的知识。

接下来,正式开始我的课程:

概念介绍

先简单了解下这些术语:

大型语言模型(LLM)

大型语言模型(LLM)是基于大规模文本语料库(如图书、网页内容等)进行训练的深度学习模型,旨在掌握广泛的自然语言处理能力。尽管这类模型能够推断出大量的信息,但其知识边界严格受限于截止到某一特定时间点的训练数据集。

LangChain

LangChain 是一个面向开发者的框架,专注于构建以大型语言模型(LLM)为核心的应用程序。该框架提供了一系列丰富的接口和模块化组件,极大地简化了开发者在设计和实现基于 LLM 的解决方案过程中的复杂度。

Ollama

Ollama 是一款开放源代码的框架,旨在使大型语言模型能够在本地计算机环境中轻松部署和运行,从而为研究者和开发者提供了一个无需依赖云端服务即可探索先进语言模型功能的平台。

RAG(Retrieval-Augmented Generation)

RAG(Retrieval-Augmented Generation)是一种增强型技术,它通过整合来自外部数据库的实时或相关上下文信息来丰富大型语言模型(LLM)的知识库。这种策略确保了在生成输出时,模型不仅能够访问其训练期间获得的信息,还能结合最新的数据,从而减少输出错误或误导性信息的可能性。RAG 通过动态检索机制实现了对 LLM 输出质量的有效提升。

Chroma

Chroma是一个开源的向量数据库系统,主要用于处理大规模的向量数据集。这种类型的数据库特别适用于机器学习、自然语言处理和其他涉及高维向量数据的应用场景中。

工作流程

获取RAG检索内容并分块

网页内容解析:利用BS(Beautiful Soup)和WebBaseLoader工具来解析网页内容。通过指定标签、类名、ID等属性,精确地定位并提取所需的内容片段。

文本分割:采用
RecursiveCharacterTextSplitter对提取的文本进行分割。通过设定合适的chunk_size和chunk_overlap参数,确保每个分块既能包含足够的信息,又能在一定程度上保留上下文的连贯性。适当的分词设置有助于减少重要信息的断裂,从而显著提升RAG(Retrieval-Augmented Generation)的效果。

内容嵌入向量数据库

向量数据库存储:将分割后的文本块存储至Chroma向量数据库中。Chroma是一款高性能的向量存储系统,适用于大规模的向量数据管理。

向量嵌入:使用OllamaEmbeddings进行文本向量嵌入。该方法支持多种嵌入模型的选择,例如nomic-embed-text(注意:该模型不支持中文)。对于需要建立中文文档库的情况,建议尝试使用herald/dmeta-embedding-zh等专门针对中文优化的模型。

设置Prompt规范输出

定义Prompt模板:通过PromptTemplate定义输入变量和模板,严格规定LLM(Language Model)回答问题的具体格式。当模型无法找到确切答案时,应明确指示其直接回答“不知道”,避免进行不必要的解释或推测。

基于Langchain实现检索问答

检索与回答:利用RetrievalQA框架从向量数据库中检索相关的信息,并基于检索结果生成答案。这一过程充分利用了向量数据库中存储的文本信息,实现了对用户提问的有效回应。

环境配置

  1. 依据Ollama使用指南完成大模型的本地下载和运行

    安装阿里Qwen2

    ollama pull qwen2

    安装 Embedding Model bge-m3,这是我目前用的

    ollama pull bge-m3

  2. 安装相关库

    安装相关库

    pip install langchain langchain-community langchain_core bs4 gradio

如果程序运行中报错,必然是缺少什么库,针对性安装即可,程序本身没问题,因为我测试通过了。

代码详解

1.导入库

import bs4
import gradio as gr
from langchain_community.llms import Ollama
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain.chains import RetrievalQA
from langchain_core.prompts import PromptTemplate

解释:

  • bs4: Beautiful Soup 是一个用于解析 HTML 和 XML 文档的 Python 库。
  • gradio: 一个库,用于快速创建和分享机器学习模型的用户界面。
  • langchain_community: LangChain 社区版本的模块,提供了一系列工具和功能来与语言模型交互。llms: 语言模型模块,例如 Ollama。callbacks: 用于监控和调试模型运行时行为的回调函数。document_loaders: 文档加载器,例如 WebBaseLoader 用于加载网页内容。vectorstores: 向量数据库模块,例如 Chroma。embeddings: 文本嵌入模块,例如 OllamaEmbeddings。
  • langchain_text_splitters: 文本分割器,例如 RecursiveCharacterTextSplitter。
  • langchain_chains: 链式结构模块,例如 RetrievalQA。
  • langchain_core.prompts: 提示模板模块,例如 PromptTemplate。

2.初始化 Ollama 语言模型

def init_ollama_llm(model_name, temperature, top_p):
    """
    初始化Ollama语言模型实例。
    
    :param model_name: 模型名称
    :param temperature: 控制生成文本的随机性
    :param top_p: 用于控制采样过程中的多样性
    :return: Ollama实例
    """
    return Ollama(
        model=model_name,
        temperature=temperature,
        top_p=top_p,
        callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])
    )

解释:

  • model: 指定要使用的 Ollama 模型名称。
  • temperature: 温度参数,控制模型生成文本的随机性。较高的温度值会导致更随机的结果。
  • top_p: 核采样参数,控制模型在生成过程中考虑的概率最高的词汇比例。
  • callback_manager: 回调管理器,用于处理模型运行过程中的输出。这里使用 StreamingStdOutCallbackHandler 来实现实时输出到标准输出。

3.加载并分割网页内容

def content_web(url):
    """
    从给定的网页URL中加载内容,并将其分割成较小的文本块。
    
    :param url: 目标网页的URL
    :return: 分割后的文档列表
    """
    loader = WebBaseLoader(
        web_paths=(url,),
        bs_kwargs=dict(
            parse_only=bs4.SoupStrainer(
                class_=("theme-doc-markdown markdown",),  # 可以根据需要调整class_或id
                # id=("article-root",)  # 示例中的注释
            )
        ),
    )
    documents = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200
    )
    splits = text_splitter.split_documents(documents)
    print(splits)
    return splits

解释:

  • WebBaseLoader: 负责从指定 URL 加载网页内容。web_paths: 一个元组,包含要加载的网页 URL。bs_kwargs: BeautifulSoup 的参数,用于解析 HTML。parse_only: 使用 SoupStrainer 指定只解析特定的 HTML 元素。这里指定了类名 theme-doc-markdown markdown,可以根据实际网页结构调整。
  • RecursiveCharacterTextSplitter: 将文档分割成更小的段落,以便更好地处理。chunk_size: 每个文档块的最大字符数。chunk_overlap: 每个文档块之间的重叠字符数,以保持上下文连贯性。

4.构建 Chroma 向量数据库

def chroma_retriever_store_content(splits):
    """
    将分割后的文档嵌入到向量空间,并存储到Chroma向量数据库中。
    
    :param splits: 从网页中提取并分割的文档
    :return: Chroma检索器
    """
    embeddings = OllamaEmbeddings(model="bge-m3")
    vectorstore = Chroma.from_documents(
        documents=splits,
        embedding=embeddings
    )
    retriever = vectorstore.as_retriever()
    return retriever

解释:

  • OllamaEmbeddings: 为文本生成向量表示。
  • Chroma.from_documents: 使用给定的文档和嵌入模型创建 Chroma 向量数据库。
  • as_retriever: 返回一个检索器对象,可以用来从向量数据库中检索相关的文档。

5.定义 Prompt 模板

def rag_prompt():
    """
    定义Prompt模板。
    
    :return: PromptTemplate实例
    """
    return PromptTemplate(
        input_variables=['context', 'question'],
        template=(
            "你是ucjmhfeng的个人智能小助手,\n"
            "请用简体中文回答我的问题: {question}\n"
            "上下文: {context}\n"
            "答案:"
        ),
    )

解释:

  • PromptTemplate: 定义了一个提示模板,用于引导模型生成答案。input_variables: 模板中可以替换的变量,这里是 context 和 question。template: 模板字符串,其中 {context} 和 {question} 是占位符,会在运行时被替换。

6.RAG 主逻辑

def ollama_rag_chroma_web_content(web_url, question, temperature, top_p):
    """
    从指定的网页中检索相关信息,并基于这些信息回答问题。
    
    :param web_url: 目标网页的URL
    :param question: 用户提出的问题
    :param temperature: 控制生成文本的随机性
    :param top_p: 用于控制采样过程中的多样性
    :return: 分割后的文档列表和答案
    """
    llm = init_ollama_llm('qwen2', temperature, top_p)
    splits = content_web(web_url)
    retriever = chroma_retriever_store_content(splits)
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        retriever=retriever,
        chain_type_kwargs={"prompt": rag_prompt()}
    )
    answer = qa_chain.invoke({"query": question})["result"]
    return splits, answer

解释:

  • 调用前面定义的函数来:初始化 Ollama 语言模型。加载并分割网页内容。创建 Chroma 向量数据库。
  • 创建 RetrievalQA 链,将语言模型和检索器结合起来,设置提示模板。
  • 使用 invoke 方法来生成答案。

7.创建 Gradio 用户界面

# 创建Gradio界面
demo = gr.Interface(
    fn=ollama_rag_chroma_web_content,
    inputs=[
        gr.Textbox(label="web_url", value="https://docs.modular.com/max/", info="爬取内容的网页地址"),
        "text",
        gr.Slider(0, 1, step=0.1, label="Temperature"),
        gr.Slider(0, 1, step=0.1, label="Top P")
    ],
    outputs=["text", "text"],
    title="Ollama+RAG 实例",
    description="输入网页的URL,然后提问, 获取答案"
)

demo.launch()

解释:

  • gr.Interface: 创建一个 Gradio 接口,允许用户输入数据并通过 fn 参数指定的函数获取输出。inputs: 输入字段,包括一个文本框(用于输入 URL)、一个文本输入框(用于提问)和两个滑动条(用于调整温度和 top_p 值)。outputs: 输出字段,包括两个文本输出框,分别显示分割后的文档和答案。title 和 description: 设置接口的标题和描述。
  • launch: 启动 Gradio 接口,使其可在线访问。

总结

这段代码实现了从网页中提取内容、构建向量数据库、使用 RAG 方法生成答案,并通过 Gradio 提供了一个用户界面。整个流程涵盖了数据加载、预处理、特征工程、模型训练和部署等环节,是一个典型的检索增强生成(RAG)应用案例。

附上代码完整版

import bs4
import gradio as gr
from langchain_community.llms import Ollama
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain.chains import RetrievalQA
from langchain_core.prompts import PromptTemplate

def init_ollama_llm(model_name, temperature, top_p):
    """
    初始化Ollama语言模型实例。
    
    :param model_name: 模型名称
    :param temperature: 控制生成文本的随机性
    :param top_p: 用于控制采样过程中的多样性
    :return: Ollama实例
    """
    return Ollama(
        model=model_name,
        temperature=temperature,
        top_p=top_p,
        callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])
    )

def content_web(url):
    """
    从给定的网页URL中加载内容,并将其分割成较小的文本块。
    
    :param url: 目标网页的URL
    :return: 分割后的文档列表
    """
    loader = WebBaseLoader(
        web_paths=(url,),
        bs_kwargs=dict(
            parse_only=bs4.SoupStrainer(
                class_=("theme-doc-markdown markdown",),  # 可以根据需要调整class_或id
                # id=("article-root",)  # 示例中的注释
            )
        ),
    )
    documents = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200
    )
    splits = text_splitter.split_documents(documents)
    print(splits)
    return splits

def chroma_retriever_store_content(splits):
    """
    将分割后的文档嵌入到向量空间,并存储到Chroma向量数据库中。
    
    :param splits: 从网页中提取并分割的文档
    :return: Chroma检索器
    """
    embeddings = OllamaEmbeddings(model="bge-m3")
    vectorstore = Chroma.from_documents(
        documents=splits,
        embedding=embeddings
    )
    retriever = vectorstore.as_retriever()
    return retriever

def rag_prompt():
    """
    定义Prompt模板。
    
    :return: PromptTemplate实例
    """
    return PromptTemplate(
        input_variables=['context', 'question'],
        template=(
            "你是ucjmhfeng的个人智能小助手,\n"
            "请用简体中文回答我的问题: {question}\n"
            "上下文: {context}\n"
            "答案:"
        ),
    )

def ollama_rag_chroma_web_content(web_url, question, temperature, top_p):
    """
    从指定的网页中检索相关信息,并基于这些信息回答问题。
    
    :param web_url: 目标网页的URL
    :param question: 用户提出的问题
    :param temperature: 控制生成文本的随机性
    :param top_p: 用于控制采样过程中的多样性
    :return: 分割后的文档列表和答案
    """
    llm = init_ollama_llm('qwen2', temperature, top_p)
    splits = content_web(web_url)
    retriever = chroma_retriever_store_content(splits)
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        retriever=retriever,
        chain_type_kwargs={"prompt": rag_prompt()}
    )
    answer = qa_chain.invoke({"query": question})["result"]
    return splits, answer

# 创建Gradio界面
demo = gr.Interface(
    fn=ollama_rag_chroma_web_content,
    inputs=[
        gr.Textbox(label="web_url", value="https://docs.modular.com/max/", info="爬取内容的网页地址"),
        "text",
        gr.Slider(0, 1, step=0.1, label="Temperature"),
        gr.Slider(0, 1, step=0.1, label="Top P")
    ],
    outputs=["text", "text"],
    title="Ollama+RAG 实例",
    description="输入网页的URL,然后提问, 获取答案"
)

demo.launch()

运行代码

我用的**Visual Studio Code,**点击“在浏览器中打开”,输入需要提问的问题和参数,点击查询:

上图右边上半部分是我添加的显示功能,主要是用于显示被分割后的网页原始信息。

上图右边下半部分是回答的结果,因为程序内我提示用简体中文回答我,所以结果直接以中文形式反馈。

MAX 是一个统一的 API 和工具集,旨在帮助构建和部署高性能人工智能管道。它从基础开始构建,并使用第一原理方法和现代编译技术确保其可编程性和未来所有 AI 模型和硬件加速器的可扩展性。

创建 MAX 的目的是解决困扰行业的 AI 工具碎片化问题。统一的工具包设计用于帮助世界构建高性能 AI 管道并将其部署到任何硬件上,同时提供最佳的成本与性能比。

MAX 包括以下内容:

1. **MAX Engine**:一种先进的图编译器和运行库,能够在各种硬件上以惊人的推理速度执行 PyTorch、ONNX 和 TensorFlow1 模型。
2. **MAX Serve**:一个用于 MAX Engine 的服务封装器,提供与现有 AI 服务系统(如 Triton)的完全互操作性,并在现有的容器基础设施(如 Kubernetes)中无缝部署。  
3. **Mojo**:为 AI 开发人员构建的第一编程语言,具有从基础开始的尖端编译技术,可为任何硬件提供无与伦比的性能和可编程性。
4. **MAX Graph **:用于使用 MAX Engine 构建高速推理图的低级 API。它结合了 Mojo 的高性能和可编程性以及 MAX Engine 的最新图编译技术。

预览版发布:MAX SDK 现已作为预览版本提供。现在即可安装 MAX。有关仍在开发中的内容详情,请参阅路线图和已知问题。

如何使用 MAX:

MAX 不要求您迁移整个 AI 管道和服务基础设施到新的系统。它与您的当前状态相匹配,并允许您逐步升级。

您可以继续使用现有的模型、库和服务基础设施,通过最小的迁移即可从 MAX 中捕获即时价值。然后,在准备好时,可以将 AI 管道的其他部分迁移到 MAX 以获得更多的性能、可编程性和硬件可移植性。

添加性能与可移植性:

您可以通过使用 Python 或 C API 来替换当前的 PyTorch、ONNX 或 TensorFlow 推理调用为 MAX Engine 推理调用来开始。这个简单的更改可以将您的模型执行速度提高至 5 倍(从而减少您的延迟)。随着时间的推移,您可以捕获更多价值,并通过加速和简化这些其他 AI 管道部分的开发。

有关我们当前正在工作的详细信息,请参阅 MAX 路线图。开始使用:

分享您的想法:让我们知道您的看法!您需要哪些额外库来简化 AI 开发和部署?在 Discord 和 GitHub 上与我们交谈。

完美!到这里大家应该可以理解程序的总体意思,多学多练多写,共同学习、共同进步。

喜欢的朋友记得关注、点赞、转发、收藏,你们的支持就是我最大的动力源泉。