day29-RAG实操

news/2025/12/9 19:42:49/文章来源:https://www.cnblogs.com/fuminer/p/19328184

RAG

一、RAG技术介绍

RAG,Retrieval-Augmented Generation,也被称作检索增强生成技术,最早在 Facebook AI(Meta AI)在 2020 年发表的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中正式提出,这种方法的核心思想是借助一些文本检索策略,让大模型每次问答前都带入相关文本,以此来改善大模型回答时的准确性。这项技术刚发布时并未引发太大关注,而伴随2022年大模型技术大爆发,RAG技术才逐渐进入人们视野,并且由于早期大模型技术应用均已“知识库问答”为主,而RAG技术是最易上手、并且上限极高的技术,因此很快就成为了大模型技术人必备的技术之一。

1.RAG技术实现原理

时至今日,RAG技术已经是非常庞大的技术体系了,从简单的文档切分、存储、匹配,再到复杂的入GraphRAG(基于知识图谱的检索增强),以及复杂文档解析+多模态识别技术等等等等。

而对于初学者来说,为了更好的上手学习RAG技术,我们首先需要对RAG技术最简单的实现形式有个基础的了解。

一个最简单的RAG技术实现流程如下所示:

假设现在我们有一个偌大的知识库,当想从该知识库中去检索最相关的内容时,最简单的方法是:接收到一个查询(Query),就直接在知识库中进行搜索。这种做法其实是可行的,但存在两个关键的问题:

  1. 假设提问的Query的答案出现在一篇文章中,去知识库中找到一篇与用户输入相关的文章是很容易的,但是我们将检索到的这整篇文章直接放入Prompt中并不是最优的选择,因为其中一定会包含非常多无关的信息,而无效信息越多,对大模型后续的推理影响越大。
  2. 任何一个大模型都存在最大输入的Token限制,一个流程中可能涉及多次检索,每次检索都会产生相应的上下文,无法容纳如此多的信息。

image-20250319143643125

解决上述两个问题的方式是:把存放着原始数据的知识库(Knowledge)中的每一个raw data,切分成一个一个的小块,这些小块可以是一个段落,也可以是数据库中某个索引对应的值。这个切分过程被称为“分块”(chunking),如下述流程所示:

image-20250319143832715

以第一个原始数据为例(raw data 1),通过一些特定的方法进行切分,一个完整的内容会被分割成 chunk1 ~ chunk4。采取相同的方法,继续对raw data 2raw data 3直至raw data n进行切分。完成这一过程后,我们最终得到的是一个充满分块数据(chunks)的新的知识库(repository),其中每一项都是一个单独的chunk。例如,如果原始文档共有10个,那么经过切分,可能会产生出100个chunks。

完成这一转化后,当再次接收到一个查询(Query)时,就会在更新后的知识库(repository)中进行搜索,这时检索的范围就不再是某个完整的文档,而是其中的某一个部分,返回的是一个或多个特定的chunk,这样返回的信息量就会更小且更精确。随后,这些被检索到的chunk会被加入到Prompt中,作为上下文信息与用户原始的Query共同输入到大模型进行处理,以生成最终的回答。

在上述将原始数据(raw data)转化为chunk的过程中,就会包含构建RAG的第一部分开发工作:这包括如果做数据清洗,如去除停用词、标点符号等。此外,还涉及如何选择合适的split方法来进行数据切分的一系列技术。

接下来面临的问题是,尽管所有数据已经被切割成一个个chunk,其存储形式还是以字符串形式存在,如果想从repository中匹配到与输入的query相关的chunks,比较两句话是否相似,看一句话中相同字有几个,这显然是行不通的。我们需要获取的是句子所蕴含的深层含义,而非仅仅是表面的字面相似度。因此,大家也能想到,在NLP中去计算文本相似度的有效的方法就是Embedding,即将这些chunks转换成向量(vector)形式。所以流程会丰富如下:

image-20250319144103496

Embedding 是由向量模型(⽣成的,它会根据不同的算法⽣成⾼维度的向量数据,代表着数据的不同特征,这些特征代表了数据的不同维度。例如,对于⽂本,这些特征可能包括词汇、语法、语义、情感、情绪、主题、上下⽂等。对于⾳频,这些特征可能包括⾳调、节奏、⾳⾼、⾳⾊、⾳量、语⾳、⾳乐等。

在这个流程中,会先将用户输入的 Query 转化成 Vector,然后再去与知识库中的向量进行相似度比较,检索出相似的Vector,最终返回其对应的Chunk(字符串形式的文本),再执行后续的流程。所以在这个过程中,就会产生构建RAG的第二部分的开发工作:如果将chunk转化成Vector及以何种形式进行存储。同时,我们要考虑的是:如何去计算向量之间的相似度?如果去和知识库中的向量一个一个比较,这个时间复杂度是非常高的,那么其解决办法又是什么呢?我们继续看下述流程:

image-20250319144224706

如上所示,解决搜索效率和计算相似度优化算法的答案就是:向量数据库。同时也产生了构建RAG的第三部分工作:我们要去了解和学习如何选择、使用向量数据库。

最终整体流程就如上图所示,一个基础的RAG架构会只要包含以下几方面的开发工作:

  1. 如何将原始数据转化成chunks;
  2. 如何将chunks转化成Vector;
  3. 如何选择计算向量相似度的算法;
  4. 如何利用向量数据库提升搜索效率;
  5. 如何把找到的chunks与原始query拼接在一起,产生最终的Prompt;

在以上5点开发任务中,我们确实是可以利用已经训练好的Embedding模型,开源的向量数据库等去直接解决某一类问题,所以我们前面才说一个基础架构的RAG系统搭建起来其实很简单,但搭建并不意味着直接就能用,毕竟RAG的核心是检索,检索出来的内容的准确率是衡量一个RAG系统的最基础的标准。目前没有任何一套理论、任何一套解决方案能够百分之百的指导着我们构建出一个最优的RAG系统。不同的需求,不同的数据,其构建方法也会大相径庭,需要我们在实践的过程中不断地去尝试,不断地去积累相关的经验,才能够将其真正落地。

我们需要围绕给定的文档(往往是非常长的文档)先进行切分,然后将切分的文档转化为计算机能识别的形式,也就是将其转化为一个数值型向量(也被称为词向量),然后当用户询问问题的时候,我们再将用户的问题转化为词向量,并和段落文档的词向量进行相似度匹配,借此找出和当前用户问题最相关的原始文档片段,然后将用户的问题和匹配的到的原文片段都带入大模型,进行最终的问答。由此便可实现一次完整的文档检索增强执行流程。

具体执行过程如下所示:

image-20250924100539992

2 相关核心概念和操作

2.1 向量数据库

向量数据库(Vector Database),也叫矢量数据库,主要用来存储和处理向量数据。

在数学中,向量是有大小和方向的量,可以使用带箭头的线段表示,箭头指向即为向量的方向,线段的长度表示向量的大小。两个向量的距离或者相似性可以通过欧式距离或者余弦距离得到。

图像、文本和音视频这种非结构化数据都可以通过某种变换或者嵌入学习转化为向量数据存储到向量数据库中,从而实现对图像、文本和音视频的相似性搜索和检索。这意味着您可以使用向量数据库根据语义或上下文含义查找最相似或相关的数据。

向量数据库的主要特点是高效存储与检索。利用索引技术和向量检索算法能实现高维大数据下的快速响应。

2.2 向量嵌入Vector Embeddings

对于传统数据库,搜索功能都是基于不同的索引方式加上精确匹配和排序算法等实现的。本质还是基于文本的精确匹配,这种索引和搜索算法对于关键字的搜索功能非常合适,但对于语义搜索功能就非常弱。

例如,如果你搜索 “小狗”,那么你只能得到带有“小狗” 关键字相关的结果,而无法得到 “柯基”、“金毛” 等结果,因为 “小狗” 和“金毛”是不同的词,传统数据库无法识别它们的语义关系,所以传统的应用需要人为的将 “小狗” 和“金毛”等词之间打上小狗特征标签进行关联,这样才能实现语义搜索。

同样,当你在处理非结构化数据时,你会发现非结构化数据的特征数量会迅速增加,处理过程会变得十分困难。比如我们处理图像、音频、视频等类型的数据时,这种情况尤为明显。就拿图像来说,可以标注的特征包括颜色、形状、纹理、边缘、对象、场景等多个方面。然而,这些特征数量众多,而且依靠人工进行标注的难度很大。因此,我们需要一种自动化的方式来提取这些特征,而Vector Embedding技术就能够实现这一目标。

Vector Embedding 是由专门的向量模型生成的,它会根据不同的算法生成高维度的向量数据,代表着数据的不同特征,这些特征代表了数据的不同维度。例如,对于文本,这些特征可能包括词汇、语法、语义、情感、情绪、主题、上下文等。对于音频,这些特征可能包括音调、节奏、音高、音色、音量、语音、音乐等。

2.3 相似性测量

如何衡量向量之间的相似性呢?有三种常见的向量相似度算法:欧几里德距离、余弦相似度和点积。

  • 点积(内积): 两个向量的点积是一种衡量它们在同一方向上投影的大小的方法。如果两个向量是单位向量(长度为1),它们的点积等于它们之间夹角的余弦值。因此,点积经常被用来计算两个向量的相似度。
  • 余弦相似度: 这是一种通过测量两个向量之间的角度来确定它们相似度的方法。余弦相似度是两个向量点积和它们各自长度乘积的商。这个值的范围从-1到1,其中1表示完全相同的方向,-1表示完全相反,0表示正交。
  • 欧氏距离: 这种方法测量的是两个向量在n维空间中的实际距离。虽然它通常用于计算不相似度(即距离越大,不相似度越高),但可以通过某些转换(如取反数或用最大距离归一化)将其用于相似度计算。

像我们最常用的余弦相似度,其代码实现也非常简单,如下所示:

import numpy as npdef cosine_similarity(A, B):# 使用numpy的dot函数计算两个数组的点积# 点积是向量A和向量B在相同维度上对应元素乘积的和dot_product = np.dot(A, B)# 计算向量A的欧几里得范数(长度)# linalg.norm默认计算2-范数,即向量的长度norm_A = np.linalg.norm(A)# 计算向量B的欧几里得范数(长度)norm_B = np.linalg.norm(B)# 计算余弦相似度# 余弦相似度定义为向量点积与向量范数乘积的比值# 这个比值表示了两个向量在n维空间中的夹角的余弦值return dot_product / (norm_A * norm_B)

2.4 相似性搜素

既然我们知道了可以通过比较向量之间的距离来判断它们的相似度,那么如何将它应用到真实的场景中呢?如果想要在一个海量的数据中找到和某个向量最相似的向量,我们需要对数据库中的每个向量进行一次比较计算,但这样的计算量是非常巨大的,所以我们需要一种高效的算法来解决这个问题。

高效的搜索算法有很多,其主要思想是通过两种方式提高搜索效率:

1)减少向量大小——通过降维或减少表示向量值的长度。

2)缩小搜索范围——可以通过聚类或将向量组织成基于树形、图形结构来实现,并限制搜索范围仅在最接近的簇中进行。

我们首先来介绍⼀下大部分算法共有的核心概念,也就是kmeans聚类。

K-Means聚类

我们可以在保存向量数据后,先对向量数据先进行聚类。例如下图在二维坐标系中,划定了 4 个聚类中心,然后将每个向量分配到最近的聚类中心,经过聚类算法不断调整聚类中心位置,这样就可以将向量数据分成 4 个簇。每次搜索时,只需要先判断搜索向量属于哪个簇,然后再在这一个簇中进行搜索,这样就从 4 个簇的搜索范围减少到了 1 个簇,大大减少了搜索的范围。

image-20250628090809249

HNSW

除了聚类以外,也可以通过构建树或者构建图的方式来实现近似最近邻搜索。这种方法的基本思想是每次将向量加到数据库中的时候,就先找到与它最相邻的向量,然后将它们连接起来,这样就构成了一个图。当需要搜索的时候,就可以从图中的某个节点开始,不断的进行最相邻搜索和最短路径计算,直到找到最相似的向量。

image-20250628091249234

2.5 Embedding models

对于Embedding Models我们只需要学会如何去使用就可以,是因为有非常多的模型供应商,如OpenAI、Hugging Face国内的有百川、千帆都提供了标准接口并集成在LangChian框架中,这意味着:Embedding Models已经有人帮我们训练好了,我们只要按照其提供的接口规范,将自然语言文本传入进去,就能得到其对应的向量表示。这显然是非常简单的。

如何使用Baichuan Text Embeddings

要使用相关的Embedding模型,首先需要获取API密钥。您可以通过以下步骤获取:

  • 访问百川智能官方网站或者[阿里百炼][https://bailian.console.aliyun.com/?spm=5176.29597918.nav-v2-dropdown-menu-0.d_main_2_0.2abf7b08IPY2jd&scm=20140722.M_10838657._.V_1#/model-market/detail/text-embedding-v3]
  • 注册并创建一个账户
  • 在控制台中申请并获取API密钥

2.6 代码构建简易RAG

下述的所有代码实操,都可以基于langchain的虚拟环境进行。

pip install chromadb

pip install requests

import uuid
import chromadb
import requests
import os
from openai import OpenAI#创建数据库,类似创建一个文件夹
client = chromadb.PersistentClient(path="./db/chroma_demo")
#创建数据集合(库表)
collection = client.get_or_create_collection(name="collection_v2")#构建一个数据切分的函数(原始知识库文件内容进行分块处理)
def file_chunk_list(filePath):#1.进行原始文件数据的读取with open(filePath,'r',encoding='utf-8') as fp:data = fp.read()#2.进行数据切分chunk_list = data.split('\n\n')return chunk_list#构建一个向量转换的函数实现(阿里百炼向量模型)
def embedding_by_api(text):#1.创建向量模型客户端client = OpenAI(api_key="skxxxx712",  # 如果您没有配置环境变量,请在此处用您的API Key进行替换base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"  # 百炼服务的base_url)#2.向量模型调用completion = client.embeddings.create(model="text-embedding-v3",input=text,dimensions=1024,encoding_format="float")return completion.data[0].embedding#构建一个模型调用的函数
def generate_by_api(prompt):ds_api_key = "sk-4b79xxxx366ebb425b3"# 实例化客户端client = OpenAI(api_key=ds_api_key, base_url="https://api.deepseek.com")# 调用 deepseek-r1 模型response = client.chat.completions.create(model="deepseek-reasoner", #调用推理模型deepseek-r1 模型标识/名称,存在推理过程messages=[{"role": "user", "content": prompt}])# 最终回复return response.choices[0].message.content

代码集成:

#下面代码只需要执行一次即可#1.数据分块处理
documents = file_chunk_list("./中医问诊.txt")#2.将分块结果进行向量转换
embeddings = [] #存放对每一个分块结果进行向量转换后的结果
for doc in documents:doc_emd = embedding_by_api(doc)embeddings.append(doc_emd)#3.将生成的向量结果添加到向量数据库
ids = []
for _ in documents:ids.append(str(uuid.uuid4()))collection.add(ids=ids,documents=documents,embeddings=embeddings
)
#该组代码可以多次测试执行#4.接受用户提问,去向量数据库中进行检索
qs = '我好像是感冒了,症状是头痛、轻微发烧、肢节酸痛、打喷嚏和流鼻涕。'
qs_embedding = embedding_by_api(qs)#将用户提问转换成向量表示
#去向量数据库中检索和用户提问向量相似度最高的1个结果
res = collection.query(query_embeddings=[qs_embedding,],query_texts=qs,n_results=1)
result = res['documents'][0]
context = '\n'.join(result)#5.进行提示词构建
prompt = f'''你是一个中医问答机器人,任务是根据参考信息回答用户问题,如果你参考信息不足以回答用户问题,请回复不知道,切记不要去杜撰和自由发挥任何内容和信息,请用中文回答,参考信息:{context},来回答问题:{qs},'''#6.进行模型调用
result = generate_by_api(prompt)
print(result)

3.RAG核心作用

那RAG这样的一个检索增强流程到底有什么用呢?这就不得不从当代大模型本身的三项技术缺陷开始说起了。

缺陷一:大模型幻觉

相信大家在使用大模型的时候,都会遇到大模型无中生有胡编乱造答案的情况,这就是所谓的大模型幻觉。而其中,第一代DeepSeek R1模型的幻觉是非常严重的,平均七次回答中就会有一次的回答存在幻觉,这可以说是第一版R1模型最大的短板。

而大型语言模型之所以会产生幻觉,主要是因为它们的训练方式和内在机制决定了它们并不具备真正理解和验证事实的能力。模型在训练过程中,通过分析大规模文本数据来学习不同词语和句子之间的概率关系,也就是在某种程度上掌握“在什么上下文中,什么样的回答听起来更合理”。然而,模型并没有接入实时的知识库或事实核查工具,当它遇到陌生的问题、模糊的描述或者上下文不完整的输入时,就会基于概率和语料库中似是而非的关联去“编造”一个看似正确的答案。由于这些输出往往语法流畅、逻辑连贯,人类读者很容易误以为它是真实可信的内容,这就是我们通常说的“模型幻觉”。

缺陷二:有限的最大上下文

而除此之外,大模型在实际应用中还会另一个“障碍”,那就是最大上下文限制。由于大模型的本质其实是一个算法,不管是让大模型“知道”有哪些外部工具,还是要给大模型进行“背景设置”,或者是要给模型添加历史对话消息,以及本次对话的输出,都需要占用这个上下文窗口。这就使得我们在一次对话中能够给大模型灌输的知识(文本)其实是有限的。

大型语言模型的最大上下文限制,是由它们的架构和计算方式决定的。每次生成回答时,模型需要把输入文本转换成固定长度的数字序列(称为token),并在内部一次性加载到模型的“上下文窗口”中进行处理。这个窗口的大小是有限的,不同模型一般在几千到几万token之间。如果输入内容超出这个长度,模型要么截断最前面的部分,要么丢弃部分信息,这就会造成对话历史、长文档或先前提到的重要细节的遗失。因为它无法跨越上下文窗口无限地保留信息,所以在面对长对话或者大量背景知识时,模型常常出现上下文断裂、回答不连贯或者忽略先前条件的情况。

早些时候的大模型普遍是8k最大上下文,相当于是8-10页中文PDF,伴随着大模型预训练技术的不断发展,顶尖的大模型,如Gemini 2.5 Pro和GPT-4.1等模型,已经达到了1M的最大上下文长度,相当于是一千页的PDF,相当于1.5本《红楼梦》,而普通的模型,也基本达到64K或128K最大上下文,相当于60-100也左右的PDF。

但是,模型上下文的增长也是有限度的,对于开发者来说,能够一次性输入的信息都会有限制。

缺陷三:模型专业知识与时效性知识不足

大型语言模型虽然在通用领域展现出令人瞩目的语言理解和生成能力,但其在特定领域的专业知识掌握往往存在明显局限。其根本原因在于,模型的训练依赖于预先收集的大规模语料,这些语料覆盖面虽广,却很难保证在所有专业领域中具有足够的深度和准确性。

某些领域,如医学、法律或前沿科技,知识更新速度快且门槛较高,公开可获取的高质量数据本身就有限,模型难以在此基础上形成系统性和权威性的认知。此外,模型训练通常在固定的时间点结束,因此其所掌握的知识具有天然的时效性,无法实时反映新近出现的研究成果、政策变化或行业动态。这种静态的知识存储模式,决定了大模型在面对最新或高度专业化的问题时,往往难以提供全面、精确的解答。

基于此,我们再回顾RAG的技术实现流程,就不难发现其背后的技术价值了:如果我们能在每次对话的时候,为当前模型输入最精准的问题相关的文档,那就能拓展模型的知识边界,无论是提升模型专业知识的准确性、给模型灌输一些时效性的知识、或者消除模型幻觉,都将大有助益,而在其他一些对话场景中,无论是需要围绕海量的文本搭建本地问答知识库、还是在构建无限上下文的聊天机器人,RAG技术都是最佳解决方案。

4.Rag&langchain

4.1 Embedding models

LangChain 设计了一个 Embeddings 类。该类是一个专为与文本嵌入模型进行交互而设计的类。有许多嵌入模型提供商(如OpenAI、BaiChuan、QianFan、Hugging Face等)这个类旨在为它们提供一个标准接口。需要安装必要的库pip install langchain_community

代码示例:

from langchain_community.embeddings import BaichuanTextEmbeddings
import os# 设置API密钥
key = "xxxxx"
embeddings = BaichuanTextEmbeddings(api_key=key)# 示例文本
text_1 = "今天天气不错"
text_2 = "今天阳光很好"# 获取单个文本的嵌入
query_result = embeddings.embed_query(text_1)
print("单个文本嵌入结果:", query_result[:5])  # 只打印前5个元素# 获取多个文本的嵌入
doc_result = embeddings.embed_documents([text_1, text_2])
print("多个文本嵌入结果:", [vec[:5] for vec in doc_result])  # 每个向量只打印前5个元素

4.2 data loaders数据加载器

在实际场景中,私有数据通常也并不是单一的,可以来自多种不同的形式,可以是上百个.csv文件,可以是上千个.json文件,也可以是上万个.pdf文件,同时如果对接到具体的业务,可以是某一个业务流程外放的API,可以是某个网站的实时数据等多种情况。

所以LangChain首先做的就是:将常见的数据格式和数据来源使用LangChain的规范,抽象出一个一个的单独的集成模块,称为文档加载器(Document loaders),用于快速加载某种形式下的文本数据。如下图所示:

image-20250319151221286

这意味着,我们可以通过调用LangChain抽象好的方法直接处理私有数据,无需手动编写中间的处理流程,并且每一种文档的加载器,在LangChain官方文档中都有基本的调用示例供我们快速上手使用,具体位置如下:https://python.langchain.com/docs/integrations/document_loaders/

我们以加载txt文件为示例:

将文件作为文本读入,并将其全部放入一个文档中,这是最简单的一个文档加载程序,使用方式如下:

from langchain.document_loaders import TextLoaderdocs = TextLoader('./中医问诊.txt', encoding="utf-8").load()
docs

对于TextLoader,使用.page_content.metadata去访问数据。

加载csv文件为示例:

逗号分隔值(CSV)文件是⼀种使用逗号分隔值的定界文本文件。文件的每一行是⼀个数据记录。每个记录由⼀个或多个字段组成,字段之间用逗号分隔。LangChain 实现了⼀个 CSV 加载器,可以将 CSV 文件加载为⼀系列 Document 对象。CSV 文件的每⼀行都会被翻译为⼀个文档。

from langchain_community.document_loaders.csv_loader import CSVLoaderfile_path = ("透视表-篮球赛.csv"
)loader = CSVLoader(file_path=file_path,encoding="UTF-8")
data = loader.load()for record in data[:2]:print(record)

加载pdf文件为示例:

这⾥我们使用pypdf 将PDF加载为文档数组,其中每个文档包含页面内容和带有 page 编号的元数据。

pip install pypdf

from langchain_community.document_loaders import PyPDFLoader
file_path = ("pytorch.pdf")
loader = PyPDFLoader(file_path)
#加载并分割 PDF 文件。将其按页分割成多个部分。返回的结果是一个包含每一页内容的列表 pages。
pages = loader.load_and_split()
print(pages[0])

4.3 Text Splitters

分块(Chunking),其实现形式上是将长文档拆分为较小的块的过程,目的是在检索时能够准确地找到最直接和最相关的段落。由于文章通常包含大量不相关信息,在进行分块之前,也常常需要进行一些预处理工作,如文本清洗、停用词处理等。

转回到核心内容来看,一个有效的分块策略,可以确保搜索结果精确地反映用户查询的实际需求。如果分块过小或过大,都可能导致搜索结果不准确或提取不到最相关的内容。理想的文本块应尽可能语义独立,即不过度依赖上下文,这样的文本是语言模型最易于理解的。因此,为文档确定最佳的块大小是确保搜索结果准确性和相关性的关键。这涉及多个决策因素,如块的大小;如果句子太短,模型可能难以理解其意义,且句子越短,包含的有效信息就越少。比较常用的有如下4种不同的方法来优化分块策略:

  1. 根据句子切分:这种方法按照自然句子边界进行切分,以保持语义完整性。
  2. 按照固定字符数来切分:这种策略根据特定的字符数量来划分文本,但可能会在不适当的位置切断句子。
  3. 按固定字符数来切分,结合重叠窗口(overlapping windows):此方法与按字符数切分相似,但通过重叠窗口技术避免切分关键内容,确保信息连贯性。
  4. 递归方法:通过递归方式动态确定切分点,这种方法可以根据文档的复杂性和内容密度来调整块的大小。

第二种方法(按照字符数切分)和第三种方法(按固定字符数切分结合重叠窗口)主要基于字符进行文本的切分,而不考虑文章的实际内容和语义。这种方式虽简单,但可能会导致主题或语义上的断裂。相对而言,递归方法更加灵活和高效,它结合了固定长度切分和语义分析。通常是首选策略,因为它能够更好地确保每个段落包含一个完整的主题。

按照句子切分

按照句子切分,其实就是通过标点符号来进行文本切分(分割),这可以直接使用Python的标准库来完成这个任务。一种简单的方法是使用re模块,它提供了正则表达式的支持,可以方便地根据标点符号来分割文本。如下示例中,展示了如何使用re.split()函数来根据中文和英文的标点符号进行文本切分。代码如下:

import redef split_text_by_punctuation(text):# 定义一个正则表达式,包括常见的中英文标点# pattern = r"[。!?。"#$%&'()*+,-/:;<=>@[\]^_`{|}~\s、]+"pattern = r"[。!?。]+"# 使用正则表达式进行分割segments = re.split(pattern, text)# 过滤掉空字符串return [segment for segment in segments if segment]

这个函数会根据中文和英文的标点符号来分割文本,并移除空字符串。定义好分割函数后,我们可以尝试进行功能测试:

# 文本
text = "春节的脚步越来越近,大街小巷都布满了节日的气氛。商店门口挂满了红灯笼和春联,家家户户都在忙着打扫卫生,准备迎接新的一年。\
小明回到家乡,感受到了浓浓的过年氛围。他在街上走着,看到小朋友们手持烟花棒,欢笑声此起彼伏。\
夜幕降临,整个城市亮起了五彩缤纷的灯光,映照着人们脸上的喜悦与期待。老人们聚在一起,回忆过去,展望未来。\
而年轻人则在夜市享受美食,放松心情。这是一个充满希望和喜悦的时刻,每个人都在以自己的方式庆祝这个特殊的节日。"# 调用函数进行分割
segments = split_text_by_punctuation(text)# 使用循环来打印每个chunk
for i, segment in enumerate(segments):print("Chunk {}: {}".format(i + 1, segment))

按照固定字符数切分

如果想按照固定字符数来切分文本,这种方法就不再依赖于标点符号,而是简单地按照给定的字符数来切分文本。我们可以编写一个函数,用来将文本分割成指定长度的片段。代码如下:

def split_text_by_fixed_length(text, length):# 使用列表推导式按固定长度切分文本return [text[i:i + length] for i in range(0, len(text), length)]

这个函数的作用是根据指定的长度(在这个例子中为100个字符)来切分文本。我们可以根据具体需要调整这个长度。

# 文本
text = "春节的脚步越来越近,大街小巷都布满了节日的气氛。商店门口挂满了红灯笼和春联,家家户户都在忙着打扫卫生,准备迎接新的一年。\
小明回到家乡,感受到了浓浓的过年氛围。他在街上走着,看到小朋友们手持烟花棒,欢笑声此起彼伏。\
夜幕降临,整个城市亮起了五彩缤纷的灯光,映照着人们脸上的喜悦与期待。老人们聚在一起,回忆过去,展望未来。\
而年轻人则在夜市享受美食,放松心情。这是一个充满希望和喜悦的时刻,每个人都在以自己的方式庆祝这个特殊的节日。"# 定义每个片段的长度
chunk_length = 100# 调用函数进行分割
result = split_text_by_fixed_length(text, chunk_length)# 打印结果
for i, segment in enumerate(result):print(f"Chunk {i+1}: {segment}")

然而,这种方法的一个明显缺点是由于仅依据长度进行切分,切分后的片段可能无法保持完整的语义。但并不意味着它不适用于文本切分任务。例如,这种方法非常适合于处理日志文件或代码块,其中文本通常以固定长度或格式出现,或者在处理来自传感器或其他实时数据源的流数据时,固定长度切分可以确保数据被均匀地处理和分析。这些应用场景中,数据的结构和形式通常是预定和规范的,因此即便是按固定长度进行切分,反而会更有利于对数据的理解和使用。

结合重叠窗口的固定字符数切分

重复窗口的意义是:块之间保持一些重叠,以确保语义上下文不会在块之间丢失。在文本处理和其他数据分析领域,"重叠"(overlap)指的是连续数据块之间共享的部分。这种方法特别常见于信号处理、语音分析、自然语言处理等领域,其中数据的连续性和上下文信息非常重要。比如下述代码所示:

def split_text_by_fixed_length_with_overlap(text, length, overlap):# 使用列表推导式按固定长度及重叠长度切分文本return [text[i:i + length] for i in range(0, len(text) - overlap, length - overlap)]# 文本
text = "春节的脚步越来越近,大街小巷都布满了节日的气氛。商店门口挂满了红灯笼和春联,家家户户都在忙着打扫卫生,准备迎接新的一年。\
小明回到家乡,感受到了浓浓的过年氛围。他在街上走着,看到小朋友们手持烟花棒,欢笑声此起彼伏。\
夜幕降临,整个城市亮起了五彩缤纷的灯光,映照着人们脸上的喜悦与期待。老人们聚在一起,回忆过去,展望未来。\
而年轻人则在夜市享受美食,放松心情。这是一个充满希望和喜悦的时刻,每个人都在以自己的方式庆祝这个特殊的节日。"# 定义每个片段的长度和重叠长度
chunk_length = 100
overlap_length = 30# 调用函数进行分割
result = split_text_by_fixed_length_with_overlap(text, chunk_length, overlap_length)# 打印结果
for i, segment in enumerate(result):print(f"Chunk {i+1}: {segment}")

如上所示,每个文本片段长度为100个字符,并且每个片段与下一个片段有30个字符的重叠。这样,每个窗口实际上是在上一个窗口向前移动30个字符的基础上开始的。这种方法特别适用于需要数据重叠以保持上下文连续性的情况,能够较好的在某一个chunk中保存某个完整的语义信息,比如在第一个Chunk中的:'他在街上走着,看到小朋友们手持烟花棒,欢笑'被截断,但是完整的语义能够在Chunk2中被存储:'他在街上走着,看到小朋友们手持烟花棒,欢笑声此起彼伏。' 那么当这条语义信息是有关于Query的上下文,就可以在chunk2中被检索出来。

递归字符文本切分

在前面讲的三种切分方法,虽然简单且更容易理解,但其存在的核心问题是:完全忽视了文档的结构,只是单纯按固定字符数量进行切分。所以难免要更进一步地去做优化,那么一个更进阶的文本分割器应该具备的是:

  • 能够将文本分成小的、具有语义意义的块(通常是句子)。
  • 可以通过某些测量方法,将这些小块组合成一个更大的块,直到达到一定的大小。
  • 一旦达到该大小,请将该块设为自己的文本片段,然后创建具有一些重叠的新文本块,以保持块之间的上下文。

根据上述需求,衍生出来的就是递归字符文本切分器,在langChain中的抽象类为:RecursiveCharacterTextSplitter,同时它也是Langchain的默认文本分割器。

from langchain.text_splitter import RecursiveCharacterTextSplitterlength_function = lensplitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n", " ", ""],chunk_size=60, chunk_overlap=12,length_function=length_function,
)
text = "春节的脚步越来越近,大街小巷都布满了节日的气氛。商店门口挂满了红灯笼和春联,家家户户都在忙着打扫卫生,准备迎接新的一年。小明回到家乡,感受到了浓浓的过年氛围。他在街上走着,看到小朋友们手持烟花棒,欢笑声此起彼伏。夜幕降临,整个城市亮起了五彩缤纷的灯光,映照着人们脸上的喜悦与期待。老人们聚在一起,回忆过去,展望未来。而年轻人则在夜市享受美食,放松心情。这是一个充满希望和喜悦的时刻,每个人都在以自己的方式庆祝这个特殊的节日。"
splits = splitter.split_text(text)

关键参数:

  • separators:指定分割文本的分隔符
  • chunk_size:被切割字符的最大长度
  • chunk_overlap:如果仅仅使用chunk_size来切割时,前后两段字符串重叠的字符数量。
  • length_function:如何计算块的长度。默认情况下,只计算字符数,也可以选择按照Token。

我们可以用LangChain提供的文本切分可视化小工具进行直观的理解:https://langchain-text-splitter.streamlit.app/

4.4 Vector stores

向量数据库,其解决的就是一个问题:更高效的实现搜索(Search)过程。传统数据库是先存储数据表,然后用查询语句(SQL)进行数据搜索,本质还是基于文本的精确匹配,这种方法对于关键字的搜索非常合适,但对于语义的搜索就非常弱。那么把传统数据库的索引思想引用到向量数据库中,同样是做搜索,在向量数据库的应用场景中就变成了:给定一个查询向量,然后在众多向量中找到最为相似的一些向量返回。

目前市面上充斥着非常多的向量数据库,从整体上可以分为开源和闭源,当然闭源意味着我们需要付费使用,而对于开源的向量数据库来说,可以下载免费使用。通过官方的数据来看,最常用的向量数据库如下:

image-20250320160146328

其中Chroma为LangChain官方主推的向量数据库,因此我们就以Chroma 为示例,尝试一下在LangChain中如何使用集成的向量数据库。Faiss与Chrom的使用方式基本保持一致,所以我们就不再重复的说明,大家可以根据官方文档,结合我们接下来对Chroma的实操自行尝试。

4.4.1 Chroma的使用方法

Chroma 是一家构建开源项目(也称为 Chroma)的公司,其官网:https://www.trychroma.com/

它支持用于搜索、过滤等的丰富功能,并能与多种平台和工具(如LangChain,, OpenAI等)集成。Chroma的核心API包括四个命令,分别用于创建集合、添加文档、更新和删除,以及执行查询。Chroma向量数据库官方原生支持Python和JavaScript,也有其他语言的社区版本支持。所以可以直接通过Python或JS操作,具体的操作文档可查阅其官方:https://docs.trychroma.com/

在使用时,因为Chroma是作为第三方集成,所以需要安装依赖包,执行如下代码:

pip install langchain-chroma

如果安装langchain-chroma报错:

error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Micro
soft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-
tools/
[end of output]

解决⽅案:需要点击【下载生成工具】进行下载,再执⾏pip install langchain-chroma
下载地址:https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/

image-20250616155936681

具体操作:

加载一个本地的.txt文档

from langchain.document_loaders import TextLoaderraw_documents = TextLoader('.sora.txt', encoding="utf-8").load()

接下来,通过文档切割器RecursiveCharacterTextSplitter,将上面完整的Docement对象切分为多个chunks。

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n", " ", ""], # 默认chunk_size=500, #块长度chunk_overlap=20, #重叠字符串长度add_start_index=True
)
documents = text_splitter.split_documents(raw_documents)

准备向量模型,这里我们依然使用BaiChuan。

from langchain_community.embeddings import BaichuanTextEmbeddings
import os# 设置API密钥
key = open('./key_files/baichuan_API-Key.md').read().strip()
embeddings_model = BaichuanTextEmbeddings(api_key=key)

创建 Chroma 数据库实例

from langchain_community.vectorstores import Chroma
#documents:文档将被转换为向量并存储在数据库中
#embeddings_model:向量的嵌入模型
#persist_directory:如果指定路径,向量存储将被持久化到此目录。如果未指定,数据将只在内存中临时存在。
db = Chroma.from_documents(documents, embeddings_model)

使用向量数据库(db)来查找与查询语句 query 相似的文档

query = "什么是Sora"
#在数据库中进行相似性搜索
#通过关键词k,可以设置返回多少个在查询过程中与Query最接近的Chunks
docs = db.similarity_search(query,k=2)
print(docs[0].page_content)query = "Sora在训练时消耗了多少算力?"
docs = db.similarity_search(query)
print(docs[0].page_content)

加入rag的生成阶段实现:

from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate#从向量数据库中检索到的结果
context_str = docs[0].page_content#创建模型客户端
llm = ChatOpenAI(model="deepseek-chat",api_key="skxxx425b3",base_url="https://api.deepseek.com"  # 根据实际调整
)
query = "Sora在训练时消耗了多少算力?"#手动构建提示词 (prompt),注入上下文 + 问题
template = f"""
你是一个专业的知识问答助手。请基于以下提供的参考信息,准确、简洁地回答用户的问题。
如果无法从提供的参考信息中找到答案,请不要编造,直接回答“我不知道”。#### 提供的参考信息:
{context_str}#### 用户提问:
{query}#### 你的回答:
"""input_vars = {"context_str": context_str,"query": query
}
prompt_template = PromptTemplate(template=template, #提示词模版input_variables=input_vars)#生成完整提示词
full_prompt = prompt_template.format(**input_vars)#调用 LLM 生成答案
response = llm.invoke(full_prompt)
print("\n最终生成的答案:")
print(response)

在上⼀个示例的基础上,如果您想要保存到磁盘,只需初始化 Chroma 客户端并传递要保存数据的目录。

# 保存到磁盘
db2 = Chroma.from_documents(documents,embeddings_model,persist_directory="./chroma_db")
docs = db2.similarity_search(query)# 从磁盘加载
db3 = Chroma(persist_directory="./chroma_db", embeddings_model)
docs = db3.similarity_search(query)
print(docs[0].page_content)

在构建实际应用程序时,除了添加和检索,非常多的情况下还需要更新和删除数据,这就需要借助到Chroma类定义的 ids 参数,它可以传入文件名或任意的标识。我们需要先根据分成Chunks构建起唯一的对应id。

import uuid
ids = [str(uuid.uuid4()) for _ in documents]
new_db = Chroma.from_documents(documents, embeddings_model,ids=ids)

接着,执行update_document方法进行更新,如下所示:

new_db.update_document(ids[0], docs[0])

与任何其他数据库一样,在向量数据库中,也可以使用.add.get.update .delete等方法,但如果想直接访问,需要执行._collection.method()。所以我们可以通过如下的代码形式,查看更新后的内容:

print(new_db._collection.get(ids=[ids[0]]))

当然,也可以直接进行删除操作,在删除之前,先看一下有多少个Chunks,代码如下所示:

print(new_db._collection.count())

删除最后一个chunk

new_db._collection.delete(ids=[ids[-1]])

再次查看存储的总Chunks数

print(new_db._collection.count())
4.4.2 Faiss的使用

Faiss 是由 Facebook 团队开源的向量检索工具,专为高维空间的海量数据提供高效、可靠的相似性检索方案。Faiss 支持 Linux、macOS 和 Windows 操作系统,在处理百万级向量的相似性检索时,Faiss 可以在牺牲一定搜索准确度的情况下,实现小于 10ms 的响应时间。

集成位于 langchain-community 包中。我们还需要安装 faiss 包本身。

pip install -U faiss-cpu tiktoken

如果您想使用启用了 GPU 的版本,也可以安装 faiss-gpu 。

from langchain_community.vectorstores import FAISS
db = FAISS.from_documents(docs, embeddings_model)query = "Pixar公司是做什么的?"
docs = db.similarity_search(query)
print(docs[0].page_content)

Faiss与Chroma使用场景的区别

  1. 数据规模和性能需求:

    • Faiss:更适合处理大规模数据,尤其是在需要利用GPU加速来提高搜索性能的场景下表现出色。例如在处理海量的图像特征向量、大规模的文本嵌入向量等场景中,Faiss能够快速地进行相似性搜索,满足对实时性和高性能的要求。
    • Chroma:适用于中小规模数据或对性能要求不是特别极致的场景。虽然Chroma也具有一定的性能优化,但在处理超大规模数据时,其性能可能受限于硬件资源,不过对于一般的小型项目或原型开发来说已经足够。
  2. 开发和集成难度:

    • Faiss:需要开发者对向量检索算法和索引结构有一定的了解,手动管理索引的创建、训练和持久化等操作,开发和集成难度相对较大。但它的灵活性也使得在一些特定场景下可以根据需求进行深度定制。
    • Chroma:提供了更简单的API和更便捷的使用方式,开箱即用,类似于一个完整的数据库,对于开发者来说更容易上手和使用,能够快速集成到各种应用中,特别适合快速原型开发和那些对数据库内部细节不太关注的应用场景。

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

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

相关文章

java 多线程deubg调试

主线程设置断点 子线程设置断点 点击子线程设置 thead, make defalut 让主线程把子线程先启动起来 子线程还没启动子线程已经启动 切换子线程运行, 进入子线程调试

day14-影刀获取抖音评论-微信自动发消息

今日内容 1 抖音视频评论写入excel 1.1 目标 # 1 母婴类--视频博主-抖音搜索 母婴 ---》排名前10的视频---》评论获取# 2 给定一个抖音视频地址--》获取抖音评论:评论人,评论时间,评论内容---》写入到excel中1.2 步…

您的能源预算,是否正被“异常气温”悄悄透支?智慧气象助力实现精准能耗管理 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年热门的国标止水钢板高评价厂家推荐榜

2025年热门的国标止水钢板高评价厂家推荐榜行业背景与市场趋势随着我国基础设施建设的持续投入和建筑防水要求的不断提高,国标止水钢板作为建筑工程中不可或缺的防水材料,市场需求呈现稳步增长态势。根据中国建筑防水…

2025年12月安检门厂家推荐:广东中安技术,手机安检门、贵金属安检门、探铜安检门、高精度安检门、半导体芯片安检门、多场景精准安检解决方案领航者

随着公共安全意识提升、各行业合规要求强化及安检技术智能化升级,安检门已从传统安防场景逐步普及至食品加工、半导体制造、保密场所、贵金属加工等多个领域,2025 年市场需求预计持续增长。但市场扩张也带来厂商技术…

2025短片产业“效率革命”,AI如何让编剧摆脱“无效内卷”?

【引言】2025 年,短剧行业迈入“拼质量、拼效率”的时代,一边是 120 万剧本的市场需求,一边是编剧“改稿 3 天、审稿 1 周、退稿无理由”的尴尬,创一 AI 是“2025 年最佳短剧 AI 工具”,用技术击破行业效率瓶颈。…

2025年知名的夜光石自发光材料/自发光材料厂家选购指南与推荐

2025年知名的夜光石自发光材料/自发光材料厂家选购指南与推荐行业背景与市场趋势随着全球城市化进程加速和绿色环保理念深入人心,自发光材料行业正迎来前所未有的发展机遇。夜光石作为一种新型环保自发光材料,凭借其…

day15-影刀对接Coze实现微信自动回复

今日内容 0 抖音评论写入excel-bug # 1 任何软件都是有bug,只是没被发现- 支付宝,微信--》他们有没有bug?-他们知道--》并没修复的bug就非常多-他们至今没发现的bug---》漏洞挖掘工程师# 2 问题:拿到抖音某个视频,…

完整教程:MySQL 全体系深度解析(存储引擎、事务、日志、MVCC、锁、索引、执行计划、复制、调优)

完整教程:MySQL 全体系深度解析(存储引擎、事务、日志、MVCC、锁、索引、执行计划、复制、调优)2025-12-09 19:30 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !…

2025年热门的流延机设备/高分子材料流延机厂家最新权威推荐排行榜

2025年热门的流延机设备/高分子材料流延机厂家权威推荐排行榜行业背景与市场趋势流延机作为高分子材料加工领域的关键设备,近年来随着新能源、电子封装、特种材料等行业的快速发展,市场需求持续增长。2024-2025年,全…

2025年热门的240KW充电桩/新能源充电桩厂家最新权威推荐排行榜

2025年热门的240KW充电桩/新能源充电桩厂家权威推荐排行榜 行业背景与市场趋势 随着全球碳中和目标的推进,新能源汽车市场迎来爆发式增长。据国际能源署(IEA)预测,2025年全球新能源汽车保有量将突破2亿辆,配套…

LMCache:基于KV缓存复用的LLM推理优化方案

LLM推理服务中,(Time-To-First-Token) 一直是个核心指标。用户发起请求到看见第一个token输出,这段时间越短体验越好,但实际部署中往往存在各种问题。 LMCache针对TTFT提出了一套KV缓存持久化与复用的方案。项目开…

2025年12月冲床静音房厂家最新推荐:常州双静环保,设备静音房、测试静音房、冲床隔音房、设备隔音房、测试隔音房、解决方案新标准

随着制造业转型升级与环保要求不断提升,工业噪声治理已成为塑料、五金、测试、机械加工等多个行业关注的焦点。2025年,静音房、隔音房等噪声控制设备市场需求持续增长,但市场上产品性能参差不齐,企业在选购时往往面…

2025年比较好的衣物护理机厂家最新TOP实力排行

2025年比较好的衣物护理机厂家TOP实力排行 开篇:行业背景与市场趋势 随着智能家居市场的快速发展,衣物护理机作为新兴家电品类,正逐渐成为现代家庭的必备产品。2025年,消费者对衣物护理的需求不再局限于简单的洗…

2025年热门的铝合金隔热条厂家推荐及采购指南

2025年热门的铝合金隔热条厂家推荐及采购指南 行业背景与市场趋势 随着全球建筑行业对节能环保要求的不断提高,铝合金隔热条作为门窗幕墙系统的关键部件,市场需求持续增长。隔热条的主要功能是阻断铝型材的热传导,…

2025年评价高的生活废水处理厂家推荐及选择参考

2025年评价高的生活废水处理厂家推荐及选择参考 行业背景与市场趋势 随着城市化进程加快和环保政策日益严格,生活废水处理行业迎来了快速发展期。2025年,我国对生活废水排放标准进一步提高,推动污水处理技术向高效…

2025年口碑好的电热水袋/防爆热水袋厂家最新用户好评榜

2025年口碑好的电热水袋/防爆热水袋厂家用户好评榜电热水袋行业背景与市场趋势随着冬季取暖需求的持续增长,电热水袋作为安全、便捷的取暖产品,近年来市场规模不断扩大。2025年,全球电热水袋市场规模预计将达到35亿…

2025年12月塑胶跑道标杆厂家最新推荐:湖北中奥特,复合型塑胶跑道、透气型塑胶跑道、自结纹塑胶跑道、老国标塑胶跑道、全塑型塑胶跑道、施工维护一体化服务新标准

随着全民健身战略深入实施、校园体育设施升级及体育强国建设步伐加快,塑胶跑道已从专业体育场馆标配逐步普及至学校、社区、公园等各类场所,2025年市场规模预计持续扩大。但市场快速发展也带来厂商技术实力、产品质量…