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

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 UnstructuredMarkdownLoaderloader = 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 ZhipuAIclass EmbeddingGenerator:def __init__(self, model_name):self.model_name = model_nameself.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)  # 假设嵌入向量维度为 1024return embeddingsdef 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].embeddingreturn [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 Chromavectordb = 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.rhkb.cn/news/442433.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python | Leetcode Python题解之第456题132模式

题目: 题解: class Solution:def find132pattern(self, nums: List[int]) -> bool:candidate_i, candidate_j [-nums[0]], [-nums[0]]for v in nums[1:]:idx_i bisect.bisect_right(candidate_i, -v)idx_j bisect.bisect_left(candidate_j, -v)if…

如何实现 C/C++ 与 Python 的通信?

在现代编程中,C/C与Python的通信已经成为一种趋势,尤其是在需要高性能和灵活性的场景中。本文将深入探讨如何实现这两者之间的互通,包括基础和高级方法,帮助大家在混合编程中游刃有余。 C/C 调用 Python(基础篇&#…

APP自动化搭建与应用

APP自动化环境搭建 用于做APP端UI自动化,adb连接手机设备。 需要的工具java编辑器:jdk、Android-sdk软件开发工具组、appium的python客户端、nodes.js、夜神模拟器、apk包、uiautomatorviewer 第一步:安装sdk,里面包含建立工具bu…

QD1-P6 HTML常用标签:列表

本节视频 https://www.bilibili.com/video/BV1n64y1U7oj?p6 ‍ 本节学习HTML列表标签。HTML 列表有多种形式&#xff0c;最重要的有两种&#xff1a; 有序列表无序列表 一、有序列表 1.1 写法 <ol><li>首先</li><li>其次</li><li>最…

Shell入门基础学习笔记

目录 第1章 Shell概述 第2章 Shell解析器 第3章 Shell脚本入门 第4章 Shell中的变量 4.1 系统变量 4.2 自定义变量 4.3 特殊变量&#xff1a;$n 4.4 特殊变量&#xff1a;$# 4.5 特殊变量&#xff1a;$*、$ 4.6 特殊变量&#xff1a;$&#xff1f; 第5章 运算符 …

数据结构-4.5.KMP算法(旧版上)-朴素模式匹配算法的优化

朴素模式匹配算法最坏的情况&#xff1a; 一.实例&#xff1a; 第一轮匹配失败&#xff0c;开始下一轮的匹配&#xff1a; 不断的操作&#xff0c;最终匹配成功&#xff1a; 如上述图片所述&#xff0c;朴素模式匹配算法会导致时间开销增加&#xff0c; 优化思路&#xff1a;主…

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

手撕数据结构 —— 顺序表(C语言讲解)

目录 1.顺序表简介 什么是顺序表 顺序表的分类 2.顺序表的实现 SeqList.h中接口总览 具体实现 顺序表的定义 顺序表的初始化 顺序表的销毁 打印顺序表 ​编辑 检查顺序表的容量 尾插 尾删 ​编辑 头插 头删 查找 在pos位置插入元素 删除pos位置的值 ​…

【JavaEE】【多线程】Thread类讲解

目录 Thread构造方法Thread 的常见属性创建一个线程获取当前线程引用终止一个线程使用标志位使用自带的标志位 等待一个线程线程休眠线程状态线程安全线程不安全原因总结解决由先前线程不安全问题例子 Thread构造方法 方法说明Thread()创建线程对象Thread(Runnable target)使用…

初始Redis

Mysql最大的问题在于,访问速度比较慢 而Redis是内存中存储数据的中间件,可以作为数据库使用,比较快,和Mysql相比,存储空间有限 Redis是在分布式系统中,才能发挥威力的,在单机程序,直接通过变量存储数据的方式,是比使用redis更优的选择 那么要求更大更快,就可以把redis和mysq…

修改银河麒麟操作系统V10(SP1)网卡名称为ethx

修改银河麒麟桌面操作系统V10&#xff08;SP1&#xff09;网卡名称为ethx 步骤一&#xff1a;查看当前网卡信息步骤二&#xff1a;修改GRUB配置文件步骤三&#xff1a;更新GRUB配置步骤四&#xff1a;编辑网络接口文件步骤五&#xff1a;重启机器 &#x1f496;The Begin&#…

【Kubernetes】常见面试题汇总(五十五)

目录 121. POD 创建失败&#xff1f; 122. POD 的 ready 状态未进入&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于【Kube…

数据结构-排序1

1.排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序…

【项目安全设计】软件系统安全设计规范和标准(doc原件)

1.1安全建设原则 1.2 安全管理体系 1.3 安全管理规范 1.4 数据安全保障措施 1.4.1 数据库安全保障 1.4.2 操作系统安全保障 1.4.3 病毒防治 1.5安全保障措施 1.5.1实名认证保障 1.5.2 接口安全保障 1.5.3 加密传输保障 1.5.4终端安全保障 资料获取&#xff1a;私信或者进主页。…

『网络游戏』窗口基类【06】

创建脚本&#xff1a;WindowRoot.cs 编写脚本&#xff1a; 修改脚本&#xff1a;LoginWnd.cs 修改脚本&#xff1a;LoadingWnd.cs 修改脚本&#xff1a;ResSvc.cs 修改脚本&#xff1a;LoginSys.cs 运行项目 - 功能不变 本章结束

【数据管理】DAMA-元数据专题

导读&#xff1a;元数据是关于数据的组织、数据域及其关系的信息&#xff0c;是描述数据的数据。在数据治理中&#xff0c;元数据扮演着至关重要的角色&#xff0c;是数据治理的基础和支撑。以下是对数据治理中元数据专题方案的详细介绍&#xff1a; 目录 一、元数据的重要性 …

Qt教程(001):Qt概述与安装

文章目录 一、Qt概述1.1 什么是Qt1.2 Qt优点1.3 Qt发展史1.4 支持的平台1.5 成功案例1.6 下载安装1.7 QtCreator介绍 一、Qt概述 1.1 什么是Qt Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的&…

如何高效使用Prompt与AI大模型对话

一、如何与人工智能对话 在人工智能的世界里&#xff0c;提示词&#xff08;Prompt&#xff09;就像是一把钥匙&#xff0c;能够解锁AI智能助手的潜力&#xff0c;帮助你更高效地获取信息、解决问题。但如何正确使用这把钥匙&#xff0c;却是一门艺术。本文将带你了解提示词的…

如何通过视觉分析检测车辆逆行行为

随着交通网络的快速扩展和车辆数量的持续增加&#xff0c;城市交通管理面临着前所未有的挑战。交通事故的多发原因之一是车辆逆行&#xff0c;这种行为不仅严重威胁其他车辆和行人的安全&#xff0c;也加重了交通拥堵问题。因此&#xff0c;如何有效监控并预防车辆逆行成为城市…

【Verilog学习日常】—牛客网刷题—Verilog进阶挑战—VL45

异步FIFO 描述 请根据题目中给出的双口RAM代码和接口描述&#xff0c;实现异步FIFO&#xff0c;要求FIFO位宽和深度参数化可配置。 电路的接口如下图所示。 双口RAM端口说明&#xff1a; 端口名 I/O 描述 wclk input 写数据时钟 wenc input 写使能 waddr input 写…