用langchain+streamlit应用RAG实现个人知识库助手搭建

news2024/11/26 2:30:43

RAG原理概述

RAG(Retrieval-Augmented Generation) 是一种结合了信息检索和生成式人工智能技术的模型架构,旨在让模型生成更有根据和更准确的回答。通俗来讲,它让模型不只是凭借自己的“记忆”(预训练数据)生成答案,还能即时去“查资料”,再结合查到的信息生成答案。它解决了模型只依赖自己有限的知识,回答不准确或过时的问题。

RAG的工作原理可以分为两部分:

  1. 检索(Retrieval)

    • 当你向模型提出一个问题时,RAG 先会去一个外部知识库(比如一堆文档、维基百科、数据库等)“查找”相关内容。这就像你向搜索引擎询问某个问题,它会先给你返回一系列相关的网页或文章。
    • 为了查找效率高,它把这些文档事先处理成计算机能够理解的“向量”(数字表示),方便进行快速匹配。
  2. 生成(Generation)

    • 模型在查到的信息基础上进行“生成”,即使用一个类似于 GPT 这样的生成模型,结合检索到的内容,生成一个有逻辑、准确的回答。
    • 比如你问一个问题,它查到相关的几段信息后,模型会把这些信息和自己的知识结合,生成一个自然语言的答案。

为什么 RAG 这么特别?

RAG 的关键好处是增强了生成模型的准确性。单纯的生成模型有时会“瞎猜”,尤其在面对最新的知识或较为专业的问题时。而 RAG 可以实时“查资料”,大大提高回答的准确性和时效性。

简单类比

  • 单纯的生成模型:像一个很聪明的学生,知识广泛,但记忆有限,没办法查书,可能会给出一些不太准确的回答。
  • RAG 模型:像一个知道自己不知道一切的学生,碰到不会的问题,可以去图书馆查资料再回答,答案自然更可靠。

应用场景

  • 客服系统:能够根据公司文档或常见问题库生成精准的回答。
  • 搜索引擎升级:提供比传统搜索引擎更智能的回答,既有检索又有生成。
  • 医疗、法律等专业领域:实时查阅最新的相关文档,生成符合当前标准的建议。

总的来说,RAG 是一种让模型不仅“会想”,还“会查”的技术,能提升模型回答问题的准确性和实时性。
在这里插入图片描述

实战项目简介

我搭建的是一个Prompt技术的AI学习助手(基于自己搭建的和Prompt技术有关的文章与书籍)。

学习写Prompt需要一边写一边实践,否则就会“脑子说我会了,手说我废了”。平时看到好的Prompt,也会把它积累下来,也许哪天就能用上;在实践的时候,随着不断地修改,Prompt也会更新迭代;网上有很多学习Prompt技术好的资料(比如吴恩达出的Prompt Cookbook, 这个项目知识库里就有这本书),但是没有那么多时间去一一啃过,而且这类重实践的技术肯定是随学随用最好了,一边提问,大模型可以基于它本身的生成能力,以及知识库文档的内容,回答我的问题,同时根据我的需求帮我生成好的Prompt。

目前仅做了针对GPT、GLM生成文本类的Prompt,还没有加入Midjourney、stable diffusion的Prompt.

Embedding 模型:智谱清言配套的Embeding 模型
向量型数据库:Chroma
LLM : 调用ChatGLM-4的API
工具链:Langchain
前端:streamlit
知识库数据:
《面向开发者的LLM教程第一章:Engineering for Developers》(md文件)
《Prompt cookbook》(PDF文件)

安装依赖:

项目实战

一、知识库搭建

一般知识库的数据构建、清洗处理、转换成向量、存储到向量数据库都是提前离线做好的。经过一段时间再更新知识库内容时,还要把上面这些步骤再做一遍,一个上线的项目,这个过程可能是半个月或者一个月进行一次。

知识库的搭建包括以下几个部分:

  1. 数据预处理:收集相关的文本数据。数据源可以是结构化的数据库、非结构化的文本(如网页、PDF、Word 文档)、API 返回的内容、维基百科条目、社交媒体内容等。原始数据通常不适合直接使用,因此需要对数据进行清洗和预处理,如去掉无关的内容(广告、噪音)、标准化格式(统一编码、去除重复等)

  2. 数据分割:对于非结构化的长文档,需要将其分割成更小的片段(例如段落、句子)。片段的大小要合理,既保证能被检索到,又能让生成模型获取足够的信息来生成相关的回答。通常通过分段规则,如按段落或固定长度的字符数进行分割。

  3. 文本嵌入(向量化):预训练模型选择:选择适合的预训练模型来生成文本嵌入。常用的模型有 BERT、Sentence-BERT、OpenAI 的文本嵌入模型等。模型的选择会影响到后续的检索效果。
    嵌入计算:将每一个片段转化为向量表示。这一步是将自然语言转化为固定维度的向量,以便后续通过向量相似度进行检索。
    向量归一化:对生成的向量进行归一化处理,确保向量在检索时能够正确计算相似度(如使用余弦相似度或欧几里得距离)。

  4. 构建索引+向量入库:将所有文本片段的向量保存到向量数据库中,以便进行快速检索。常用的向量数据库有 Pinecone、FAISS、Milvus 等。这些数据库支持高效的相似度搜索,能够快速返回最相关的文档。
    传统索引(可选):有时会将文本进行关键词索引(倒排索引),使用搜索引擎如 Elasticsearch、Whoosh 等。关键词搜索和向量搜索可以结合使用,以提高检索的准确性。

在搭建RAG应用的时候,一般都是用嵌入模型来构建词向量,此时有两个选择,一是选择在线大模型配套的Embedding模型 API,很多公司都有提供接口,不过有些是要付费的;再者也可以选择使用本地的Embedding模型,比如FlagEmbedding、BGE等等,目前已经有很多对中文语料进行Embedding表现得不错的开源模型,hugging face上有很多开源的中文Embedding模型可供选择。

开源中文Embedding模型排行榜

本项目中我使用的是智谱清言的Embedding模型 API

主流的向量数据库有:Chroma、Weaviate、Qdrant等等,这里我使用的是Chroma,它是一个轻量级向量数据库,拥有丰富的功能和简单的 API,具有简单、易用、轻量的优点,但功能相对简单且不支持GPU加速。

1.1 数据预处理

首先在项目目录下面新建一个文件夹/data_base/knowledge_db,把用到的资料放进knowledge_db里。

1.1.1 数据加载读取

首先来读取数据,由于我的知识库里文本类型是PDF和Markdown文件,我们可以使用 LangChain 的 PyMuPDFLoader 来读取知识库的 PDF 文件,用UnstructuredMarkdownLoader来读取Markdown文件。
首先看PDF文件的加载:

from langchain.document_loaders.pdf import PyMuPDFLoader

# 创建PyMuPDFLoader实例,输入为要加载的 pdf 文档路径
loader = PyMuPDFLoader("/root/data_base/knowledge_db/Prompt_cookbook.pdf")
# 加载PDF文件
pdf_pages = loader.load()

文档加载后储存在 pdf_pages 变量中,pdf_pages是一个list,PDF有多少页,list的长度就有多少。
在这里插入图片描述pdf_pages列表里的每一个元素就是一页PDF的文档,这个元素的变量类型是langchain_core.documents.base.Document,文档变量类型包含两个属性:page_content 包含该文档的内容, meta_data 为文档相关的元数据。
一般我们都是从page_content里面取到文本的数据。

在这里插入图片描述Markdown文件的加载也是一样步骤的,这次用 UnstructuredMarkdownLoader模块:

from langchain.document_loaders.markdown import UnstructuredMarkdownLoader

loader = UnstructuredMarkdownLoader("/root/knowledge_db/1. 简介 Introduction.md")
md_pages = loader.load()
1.1.2 数据清洗

我们期望知识库的数据尽量是有序的、优质的、精简的,因此我们要删除低质量的、甚至影响理解的文本数据。这部分主要就是用python里的文本处理操作。小伙伴们可以按照自己项目使用的文档特点进行处理,文本处理不会的就去问chatgpt,不用死记硬背正则化那些的。

可以看到文本里还是有一些多余的字符,比如‘\n\n’,这样的地方全部换成单个’\n’。

md_page.page_content = md_page.page_content.replace('\n\n', '\n')

处理之后的结果比较干净了:
在这里插入图片描述

1.1.3 整合PDF和Markdown文件处理

由于知识库里的文档很多,而且格式不统一,我们可以根据文件后缀是.md还是.pdf来分类批量读取到内容,然后放进一个空列表里:

# 获取知识库knowledge_db文件夹下所有文件路径,储存在file_paths里,
file_paths = []
folder_path = '/root/knowledge_db'
for root, dirs, files in os.walk(folder_path):
    for file in files:
        file_path = os.path.join(root, file)
        file_paths.append(file_path)
from langchain.document_loaders.pdf import PyMuPDFLoader
from langchain.document_loaders.markdown import UnstructuredMarkdownLoader

# 遍历文件路径并把实例化的loader存放在loaders里
loaders = []

for file_path in file_paths:

    file_type = file_path.split('.')[-1]
    if file_type == 'pdf':
        loaders.append(PyMuPDFLoader(file_path))
    elif file_type == 'md':
        loaders.append(UnstructuredMarkdownLoader(file_path))

# 下载文件并存储到text列表里
texts = []

for loader in loaders: texts.extend(loader.load())

texts列表里的元素都是langchain_core.documents.base.Document对象,每一个Document对象里都有page_content和meta_data。

1.2 文档分块(Chunks)

由于单个文档的长度往往会超过模型支持的上下文,导致检索得到的知识太长超出模型的处理能力,因此,在构建向量知识库的过程中,我们往往需要对文档进行分割,将单个文档按长度或者按固定的规则分割成若干个 chunk,然后将每个 chunk 转化为词向量,存储到向量数据库中。

在检索时,我们会以 chunk 作为检索的元单位,也就是每一次检索到 k 个 chunk 作为模型可以参考来回答用户问题的知识,这个 k 是我们可以自由设定的。
Langchain 中文本分割器都根据 chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)进行分割。

图示如下:
在这里插入图片描述Langchain中还有其他很多的文档分割方法,这里我使用的是RecursiveCharacterTextSplitter(): 按字符串分割文本,递归地尝试按不同的分隔符进行分割文本。
在RAG应用中文档分块是非常重要的一个环节,分割得不合适会非常影响答案的质量。如何选择分割方式,往往具有很强的业务相关性——针对不同的业务、不同的源数据,往往需要设定个性化的文档分割方式。RecursiveCharacterTextSplitter()这个方法是比较通用的,可以先基于这个跑一个baseline,再去优化。

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 使用递归字符文本分割器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50
)
split_docs=text_splitter.split_text(pdf_page.page_content)

split_docs里存储的就是切割之后的文本块了,split_docs也是一个list,和前文一样,list中的每一个元素都是一个Document对象,每个Document对象里page_content内容就是被切割成好的chunk内容。

在这里插入图片描述

1.3 文本嵌入(向量化)

文本嵌入就是将知识库

这里用的是智谱AI原生的API,langchain内部目前暂时没有直接可用的embeddings模型,所以我们得手动把Embeddings模型封装到langchain的工具链里面。

首先看智谱的Embeddings模型如何调用。在智谱的官网上有详细的教程(另外:它的一系列接口文档都可以好好看一下),本项目我用的是模型是Embedding-2
在这里插入图片描述在这里插入图片描述看响应示例里返回的结果,文本经过Embedding后得到的高维向量,在“data”的embedding里面,这就是我们存入向量数据库的内容。
我们通过response.data[0].embedding拿到向量内容。
有时Embedding可能失败,那么可以加一个if判断Embedding是否成功,如果没有成功,那么返回一个0向量,逻辑实现如下。

client=ZhipuAI(api_key='your API_KEY')
query='请介绍一下Prompt工程是什么?'
response = client.embeddings.create(model='embedding-2', input=query)
if hasattr(response, 'data') and response.data:
 return response.data[0].embedding
return [0] * 1024  # 如果获取嵌入失败,返回零向量

不管对知识库文本内容还是用户输入的问题内容,其实都需要进行Embedding处理,所以这里我写了一个类,里面定义了两个函数,embed_documents():用于处理知识库里的文本内容,逻辑如下——准备一个空列表, 将分割好的知识库文本列表传入,遍历每一条数据拿它们embedding处理后的向量,添加到空列表里,这个函数最终返回的就是chunks转换成向量后的列表。
在这里插入图片描述

**embed_query()**用于对用户输入的问题内容进行向量化处理。
在这里插入图片描述

(在这里有一个优化的空间:如果你的知识库规模很大,在这里可以设计成异步处理,使用线程池,先挖个坑,以后有空填上)

from zhipuai import ZhipuAI

class EmbeddingGenerator:
    def __init__(self, model_name):
        self.model_name = model_name
        self.client = ZhipuAI(api_key='你的API_KEY')

    def embed_documents(self, texts):
        embeddings = []
        for text in texts:
            response = self.client.embeddings.create(model=self.model_name, input=text)
            if hasattr(response, 'data') and response.data:
                embeddings.append(response.data[0].embedding)
            else:
                # 如果获取嵌入失败,返回一个零向量
                embeddings.append([0] * 1024)  # 假设嵌入向量维度为 1024
        return embeddings


    def embed_query(self, query):
        # 使用相同的处理逻辑,只是这次只为单个查询处理
        response = self.client.embeddings.create(model=self.model_name, input=query)
        if hasattr(response, 'data') and response.data:
            return response.data[0].embedding
        return [0] * 1024  # 如果获取嵌入失败,返回零向量

以上就是RAG的前置数据准备了,一般知识库文本嵌入的工作是离线处理的。

1.4 向量入库

Langchain 集成了超过 30 个不同的向量存储库,这里我选择用langchain里的 Chroma。

首先实例化一个我们在上一步里写好的EmbeddingGenerator,并定义使用的embedding模型为"embedding-2".

embedding_generator = EmbeddingGenerator(model_name="embedding-2")

接着定义持久化路径,这就是向量数据库的路径地址,而且后续我们的操作里要让它持续保存到磁盘上。

persist_directory = '../../data_base/vector_db/chroma'

实例化一个Chroma数据库对象,documents参数定义我们要传入的文本列表、embedding参数这里填入我们实例化好的Embedding生成器embedding_generator ,persist_directory这个参数填入刚才定义的持久化路径,这允许我们将persist_directory目录持久地保存到磁盘上,再加上vectordb.persist(),这样保证在项目运行过程中,我们创建的vectordb随时都可以用。

from langchain.vectorstores.chroma import Chroma

vectordb = Chroma.from_documents(
    documents=split_docs,
    embedding=embedding_generator,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)

vectordb.persist()

查看向量数据库里的向量数目:
在这里插入图片描述
可以测试一下加载的向量数据库,使用一个问题 query 进行向量检索。如下代码会在向量数据库中根据相似性进行检索,返回前 k 个最相似的文档。(这里记得要安装一下 OpenAI 开源的快速分词工具 tiktoken 包:pip install tiktoken)
在这里插入图片描述

二、构建RAG

接下来构建LLM,并且把它接入工具链中

2.1 创建LLM

这里我用的是langchain来调用智谱AI的API

三、部署streamlit

四、评估与优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2187655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java中的依赖注入(Dependency Injection, DI)详解

Java中的依赖注入(Dependency Injection, DI)是软件工程中的一种重要设计模式。它有助于提高系统的可测试性、可维护性和灵活性。通过依赖注入,组件不再负责创建它们所需的对象,而是通过外部的设置来提供这些对象。这种方式也与控…

无人机+无人车+机器狗:综合管控系统技术详解

无人机、无人车、机器狗的综合管控系统技术是一个集成了多种先进技术和设备的复杂系统,旨在实现高效、精准、协同的作业与管理。以下是对该系统技术的详细解析: 一、系统概述 综合管控系统通过集成无人机、无人车和机器狗等智能设备,结合物…

OSDU轻量化单机部署

首先更新系统 sudo apt update sudo apt upgrade -y安装docker sudo apt install -y docker.io sudo systemctl start docker sudo systemctl enable docker安装minikube curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 …

vmware Workstation16设置批量虚拟机开机自启 vmAutoStart

文章目录 前言解压压缩包一、使用步骤1.获取虚拟机所在目录2.获取vmware所在目录3.测试启动4.开机自启 二、gitee总结 前言 vmware workstation16不支持虚拟机开机自启,通常的办法是写脚本,但是有个问题就是不能启动多台虚拟机,因为有时候会…

Python | Leetcode Python题解之第455题分发饼干

题目&#xff1a; 题解&#xff1a; class Solution:def findContentChildren(self, g: List[int], s: List[int]) -> int:g.sort()s.sort()m, n len(g), len(s)i j count 0while i < m and j < n:while j < n and g[i] > s[j]:j 1if j < n:count 1i …

Spring框架使用Api接口实现AOP的切面编程、两种方式的程序示例以及Java各数据类型及基本数据类型的默认值/最大值/最小值列表

一、Spring框架使用Api接口-继承类实现AOP的切面编程示例 要使用Spring框架AOP&#xff0c;除了要导入spring框架包外&#xff0c;还需要导入一个织入的包org.aspectj&#xff0c;具体maven依赖如下&#xff1a; <dependency><groupId>org.springframework</gr…

JAVA-异常(通俗易懂)

目录 一、异常的概念 1.算术异常 2.数组越界异常 3.空指针异常 二、异常体系结构 三、异常的分类 1. 编译时异常 2. 运行时异常 四、异常处理 1.防御式编程 2.异常的抛出 3.异常的捕获 4.try-catch捕获并处理 5.finally 五、异常的处理流程 六. 自定义异常类…

ade20k 街景图像【数据集】及其【论文出处】ADE20K数据集 超过25000张图像的语义分割数据集

ade20k 街景图像【数据集】及其【论文出处】ADE20K数据集介绍 是一个包含超过25000张图像的语义分割数据集&#xff0c;这些图像被密集注释&#xff0c;覆盖室内和室外场景。 它由MIT发布&#xff0c;包含100个事物类别和50个物质类别&#xff0c; 用于训练和验证的图像数量分别…

(16)MATLAB仿真Nakagami-m分布1

文章目录 前言一、Nakagami分布二、MATLAB建模代码三、仿真结果画图四、总结 前言 Nakagami衰落模型最初是由于该模型与短波电离层传播的经验结果相匹配而提出的。它还用于仿真来自多个干扰源的情况&#xff0c;因为多个独立且同分布&#xff08;i.i.d&#xff09;的瑞利分布随…

线程池的实现和讲解:解决多线程并发服务器创建销毁线程消耗过大的问题

1.前言 多进程/线程并发服务器、多路I/O转接服务器的简单实现-CSDN博客 原先的多线程并发服务器&#xff0c;有多少个客户端连接服务器就有多少个线程&#xff0c;CPU需要在多个线程之间来回切换处理客户端的请求&#xff0c;系统消耗比较大(每次创建和消耗线程在操作系统内部…

linux学习--第七天(多路复用IO)

多路复用IO -阻塞IO与非阻塞IO -IO模型 IO的本质时基于操作系统接口来控制底层的硬件之间数据传输&#xff0c;并且在操作系统中实现了多种不同的IO方式&#xff08;模型&#xff09;比较常见的有下列三种&#xff1a; 1.阻塞型IO模型 2.非阻塞型IO模型 3.多路复用IO模型 -阻…

开源2+1链动模式AI智能名片O2O商城小程序源码:线下店立体连接的超强助力器

摘要&#xff1a;本文将为您揭示线下店立体连接的重大意义&#xff0c;您知道吗&#xff1f;线上越火&#xff0c;线下就得越深入经营。现代门店可不再只是卖东西的地儿&#xff0c;还得连接KOC呢&#xff01;咱们来看看门店要做的那些超重要的事儿&#xff0c;还有开源21链动模…

Authentication Lab | CVE-2019-7644 - JWT Signature Disclosure

关注这个靶场的其他相关笔记&#xff1a;Authentication Lab —— 靶场笔记合集-CSDN博客 0x01&#xff1a;JWT Signature Disclosure 前情提要 本关的考点是 JWT&#xff08;Json Web Token&#xff09;漏洞&#xff0c;JWT 是一个用于跨域认证的技术。如果你不了解 JWT&…

计算机视觉——图像修复综述篇

目录 1. Deterministic Image Inpainting 判别器图像修复 1.1. sigle-shot framework (1) Generators (2) training objects / Loss Functions 1.2. two-stage framework 2. Stochastic Image Inpainting 随机图像修复 2.1. VAE-based methods 2.2. GAN-based methods …

攻防世界----->easyre-153

做题笔记。 下载 查壳。 UPX&#xff0c;---脱壳。 32ida打开。 先运行一下&#xff1a; 查找字符校位。 管道父子&#xff1f;有点像此前做的那个进程互斥。。。 分析&#xff1a; 跟进lol &#xff1f; 查看汇编窗口看看。(因为一个函数只存在一个打印函数&#xff0c;就很…

集合框架01:集合的概念、Collection体系、Collection接口

1.集合的概念 集合是对象的容器&#xff0c;定义了多个对象进行操作的常用方法。可实现数组的功能。 集合和数组的区别&#xff1a; 1.数组长度固定&#xff0c;集合长度不固定&#xff1b; 2.数组可以存储基本类型和引用类型&#xff0c;集合只能存储引用类型&#xff1b; …

读数据湖仓06数据集成

1. 数据湖仓中的数据集成 1.1. 数据湖仓的总体目标是为每一个人提供支持&#xff0c;包括从普通职员到CEO 1.2. 有了作为基础设施的基础数据&#xff0c;企业等组织才能实现真正的数据驱动 1.3. 提供组织所需的数据&#xff0c;最关键的一环在于提供集成的数据基础 1.3.1. 只…

信息安全工程师(32)认证技术方法

前言 认证技术方法是用于验证用户、设备或系统身份的各种技术手段和方法&#xff0c;旨在确保只有经过验证的实体才能访问系统资源&#xff0c;从而保护数据和系统的安全。 一、常见认证技术方法 密码认证 描述&#xff1a;用户通过输入预先设置的密码进行身份验证。优点&#…

The 14th Jilin Provincial Collegiate Programming Contest

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 #define ll long long #define pii pair<int, int> #define ld lo…

C语言 | Leetcode C语言题解之第455题分发饼干

题目&#xff1a; 题解&#xff1a; int cmp(int* a, int* b) {return *a - *b; }int findContentChildren(int* g, int gSize, int* s, int sSize) {qsort(g, gSize, sizeof(int), cmp);qsort(s, sSize, sizeof(int), cmp);int m gSize, n sSize;int count 0;for (int i …