AGI|基于LangChain实现的三种高级RAG检索方法

一、前言

RAG(Retrieval-Augmented Generation)检索增强生成,是现如今基于企业私域知识的问答应用所使用的主流技术之一。相较于重新训练基于私域知识的大模型来说,RAG没有额外的预训练成本,且回答效果与之相当。

但在实际应用场景中,RAG所面临最大的问题是LLM的上下文长度限制。企业私域知识文本的数量十分庞大,不可能将其全部放在模型的prompt中,即使现在各类模型已经将上下文token从年初的2k、4k扩充到了128k、192k,但是这可能也就是一份合同、一份标书的长度。因此,如何减少传递给模型的内容数量且同时提高内容质量,是提升基于RAG的AI应用回答准确度的一个重要方法。

本篇文章将基于LangChain实现三种高级检索方法,句子窗口检索和自动合并检索旨在改善RAG流程的召回过程中存在的信息残缺的问题,而多路召回检索则保证了在多个文档中检索召回的准确性。

二、先验知识

●RAG简要流程

加载文档——切分划片——嵌入为向量表示——存入数据库

向量化问题——向量召回文档——合并放入Prompt——LLM生成答案

三、句子窗口检索

(一)概念

在文档进行切片工作后,文档被分为若干个Langchain自定义的Document对象,该对象有两个属性,一是page_content即该切片的文本内容,二是meta_data即有关该切片的一些信息和可自定义封装进入的信息。

句子窗口检索方法,将每个切片的相邻切片的内容封装在切片的meta_data中。在检索和召回过程时,根据命中文档的meta_data可获得此段落的上下文信息,并将其封装进入命中文档的page_content中。组合完成的文档列表即可作为prompt交付给大模型生成。

在实际问答任务中,我们建议使用切片器将文档切分为较短的分片,或使用依据标点符号进行切分的切片器。保证整片文档拥有较细的颗粒度。同时在封装和召回阶段,适当扩大窗口大小,保证召回段落的完整性。

(二)BERT

(1)元数据封装

def metadata_format(self, ordered_text, **kwargs):
count = kwargs.get("split_count", 1)
fori, documentinenumerate(ordered_text):
ifi > 0:
document.metadata['previous_page'] = ordered_text[i-count].page_content
else:
document.metadata['previous_page'] = ''

ifi < len(ordered_text) - 1:
document.metadata['next_page'] = ordered_text[i+count].page_content
else:
document.metadata['next_page'] = ''
returnordered_text

(2)数据重构

def search_and_format(self, databases, query, **kwargs):
top_documents = []
fordb in databases:
top_documents.append(db.similarity_search_with_score(query))
docs = []
fordoc, _ in top_documents:
doc.page_content = doc.metadata.get("previous_page") + doc.page_content + doc.metadata.get("next_page")
docs.append(doc)
returndocs

(3)调用示例伪代码

#load document
......

#split
......
#use smartvision sdk to format
sentence_window_retrival = SentenceWindow()
formatted_documents = sentence_window_retrival.metadata_format(documents, split_count=2)

#embedding 
......

#load inlocalvector db
......

#use smartvision sdk to dosearch and multiple recall
databases = [db]
query = "烟草专卖品的运输"
top_documents = sentence_window_retrival.search_and_format(databases, query)
print(top_documents)

四、自动合并检索

(一)概念

自动合并检索方法,实现方法源自Llamaindex所封装的自动合并检索,但RAG全流程需要制定一套准确的规范,因此在用户文档完成读取和切片工作后,所得到的Langchain格式的Document对象需转化为Llamaindex定义的Document对象,便可通过Llamaindex的自定义算法自动划分整个切片列表的子节点和父节点,最后鉴于规范再重新转化为Langchain格式的Document对象,并将父节点信息、深度信息等封装进每个节点。

在检索阶段,召回最相关的若干个节点,遍历这些节点和附加信息,如若超过K个节点同时属于同一个节点(这里的K为用户自定义阈值,通常为一个节点所有子节点的半数)则执行合并该父节点下属所有子节点,即返回整个父节点内容。这使我们能够将可能不同的较小上下文合并到一个可能有助于综合的更大上下文中。

(二)代码实现和调用

(1)元数据封装

defauto_merge_format(documents, **kwargs):
ifdocuments isNone:
raiseValueError('documents is required')
formatted_documents = []
doc_text = "\n\n".join([d.page_content ford indocuments])
docs = [Document(text=doc_text)]
node_parser = HierarchicalNodeParser.from_defaults(chunk_sizes=kwargs.get("pc_chunk_size", [2048, 512, 128]),chunk_overlap=kwargs.get("pc_chunk_overlap", 10))
nodes = node_parser.get_nodes_from_documents(docs)
leaf_nodes = get_leaf_nodes(nodes)
root_nodes = get_root_nodes(nodes)
middle_nodes = get_middle_node(nodes, leaf_nodes, root_nodes)
root_context_dict = {}
forroot_node innodes:
root_context_dict[root_node.node_id] = root_node.get_content()

fornode innodes:
ifnode.parent_node:
node_id = node.node_id
root_node_id = node.parent_node.node_id
root_node_content = root_context_dict.get(node.parent_node.node_id)
root_node_child_count = 0
forparent_node inroot_nodes + middle_nodes:
ifparent_node.node_id == node.parent_node.node_id:
root_node_child_count = len(parent_node.child_nodes)
break
depth = 2ifnode inmiddle_nodes else3
child_count = len(node.child_nodes) ifnode.child_nodes isnotNoneelse0
document = langchain.schema.Document(page_content=node.get_content(),metadata={"node_id": node_id, "root_node_id": root_node_id, "root_node_content": root_node_content, "root_node_child_count": root_node_child_count, "depth": depth, "child_count": child_count})
formatted_documents.append(document)
returnformatted_documents

(2)数据重构

defsearch_and_format(self, databases, query, **kwargs):
top_documents = []
fordb indatabases:
top_document = db.similarity_search_with_score(query)
top_documents.append(top_document)
leaf_nodes = [doc fordoc, _ intop_documents]
returndo_merge(leaf_nodes, **kwargs)


defgroup_nodes_by_depth(nodes, depth):
return[node fornode innodes ifnode.metadata.get("depth") == depth]

defprocess_group(nodes, threshold):
grouped_by_root_id = {}
fornode innodes:
root_id = node.metadata.get("root_node_id")
grouped_by_root_id.setdefault(root_id, []).append(node)

merge_context = []
forgroup ingrouped_by_root_id.values():
node_count = len(group)
child_count = group[0].metadata.get("root_node_child_count")
ifnode_count / child_count >= threshold:
merge_context.append(langchain.schema.Document(
page_content=group[0].metadata.get("root_node_content")
))
else:
fordocument ingroup:
merge_context.append(document)
returnmerge_context

defdo_merge(nodes, **kwargs)-> List[langchain.schema.Document]:
threshold = kwargs.get("threshold", 0.5)
leaf_nodes = group_nodes_by_depth(nodes, 3)
middle_nodes = group_nodes_by_depth(nodes, 2)
leaf_merge_context = process_group(leaf_nodes, threshold)
middle_merge_context = process_group(middle_nodes, threshold)
merge_content = leaf_merge_context + middle_merge_context
returnmerge_content

defget_middle_node(nodes, leaf_nodes, root_nodes):
middle_node = []
fornode innodes:
ifnode notinleaf_nodes andnode notinroot_nodes:
middle_node.append(node)
returnmiddle_node

(3)调用示例伪代码

#load document
......

#split
......

#use smartvision sdk to format
auto_merge_retrival = AutoMergeRetrieval()
formatted_documents = auto_merge_retrival.metadata_format(documents,
pc_chunk_size=[1024, 128, 32],
pc_chunk_overlap=4)
#embedding 
......

#load inlocalvector db
......

#use smartvision sdk to dosearch and multiple recall
top_documents = auto_merge_retrival.search_and_format(databases, query, threshold=0.5)
print(top_documents)

五、多路召回检索

(一)概念

多路召回检索方法,在元数据封装环节并未做任何操作,而在检索阶段他允许用户上传多个数据集或不同类型的向量数据库作为检索对象,以适应用户私域知识库文档类型不同,文档数量庞大的问题。从多个数据源检索得到文档列表,而后通过rerank模型对文档与问题的相关性进行评分,筛选出大于一定分值的文档,组合成为prompt。

由此可见,多路召回检索在数据源广而杂的情况下,富有更好的效果。此外,rerank模型虽能进行再次的重排以提高准确性,但是在牺牲速度和效率的前提下进行的,因此需充分考虑这个问题。

(二)代码实现

(1)元数据封装

defmetadata_format(self, ordered_text, **kwargs):
"""
默认rag,不做任何处理
"""
returnordered_text

(2)数据重构

defsearch_and_format(self, databases, query, **kwargs):
top_documents = []
result_data = []
fordb indatabases:
top_document = db.similarity_search_with_score(query)
top_documents.append(top_document)
pairs = [[query, item.page_content] foritem intop_documents]
withtorch.no_grad():
rerank_tokenizer = AutoTokenizer.from_pretrained(RERANK_FILE_PATH)
inputs = rerank_tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
rerank_model = AutoModelForSequenceClassification.from_pretrained(RERANK_FILE_PATH)
scores = rerank_model(**inputs, return_dict=True).logits.view(-1, ).float()
fori, score inenumerate(scores):
data = {
"text": top_documents[i].page_content,
"score": float(score)
}
result_data.append(data)
returnresult_data

六、结语

本文提供的三种高级RAG检索方法,但仅改善了流程中检索召回环节的信息残缺问题,实质上RAG全流程均存在各种优化方法,但最有效的方法仍是改进或提供新的召回方式。

总结以上三种方法,均需要重点注意切片器的选用并控制切片大小,过大导致上下文长度过长,且有研究表明过长的prompt易使大模型忽略的中间部分的信息。过短则导致关键信息残缺,无法为大模型提供有效的上下文。因此开发者需根据文档类型和结构,谨慎选择并适当调节优化。

神州数码集团的神州问学平台不仅提供了本文所述的三种高级检索方法的SDK,而且我们的开发团队正不断探索和研发新的、更高效的检索技术。我们致力于满足客户对于多样化私域知识库结构的需求,以实现更精准、更全面的搜索体验。同时,我们也欢迎您体验平台并提供宝贵意见。

作者:孙泽文| 神州数码云基地

更多AI小知识欢迎关注“神州数码云基地”公众号,回复“AI与数字化转型”进入社群交流

版权声明:文章由神州数码武汉云基地团队实践整理输出,转载请注明出处。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

容器Docker:轻量级虚拟化技术解析

引言 随着云计算和虚拟化技术的飞速发展&#xff0c;容器技术以其轻量级、高效、可移植的特性&#xff0c;逐渐成为了软件开发和部署的新宠。在众多容器技术中&#xff0c;Docker以其简单易用、功能强大的特点&#xff0c;赢得了广泛的关注和应用。本文将全面介绍Docker的基本概…

数据挖掘算法原理与实践:决策树

第2关&#xff1a;决策树算法原理 任务描述 本关任务&#xff1a;根据本关所学知识&#xff0c;完成 calcInfoGain 函数。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 信息熵&#xff1b;条件熵&#xff1b;信息增益。 信息熵 信息是个很抽象的概念。…

04-xss获取cookie实验

二、开发XSS服务器端 1、确认实验环境 攻击者服务器&#xff1a;192.168.74.134&#xff0c;将获取到cookie数据保存到该服务器的数据库中&#xff0c;运行PHP代码暴露一个接收Cookie的URL地址。 正常Web服务器&#xff1a;192.168.74.133&#xff0c;用于正常的用户访问的目…

Linux磁盘IO、网络IO、零拷贝详解

一、什么是I/O&#xff1f; 在计算机操作系统中&#xff0c;所谓的I/O就是输入&#xff08;input&#xff09;和输出&#xff08;output&#xff09;,也可以理解为读&#xff08;read&#xff09;和写&#xff08;write&#xff09;,针对不同的对象&#xff0c;I/O模式可以划分…

46. UE5 RPG 实现角色死亡效果

在上一篇文章中&#xff0c;我们实现了敌人受到攻击后会播放受击动画&#xff0c;并且还给角色设置了受击标签。并在角色受击时&#xff0c;在角色身上挂上受击标签&#xff0c;在c里&#xff0c;如果挂载了此标签&#xff0c;速度将降为0 。 受击有了&#xff0c;接下来我们将…

Compose 状态管理

文章目录 Compose 状态管理概述使用MutableStaterememberStatelessComposable & StatefulComposable状态提升rememberSaveable支持parceable不支持parceable 使用ViewModelViewModelProvider.Factory 使用Flow Compose 状态管理 概述 当应用程序的状态发生变化时&#xf…

如何与精益生产咨询公司合作,确保项目的成功?

随着竞争的白热化&#xff0c;企业为了提升生产效率和降低成本&#xff0c;纷纷寻求精益生产咨询公司的帮助。然而&#xff0c;与咨询公司合作并不是一蹴而就的事情&#xff0c;需要双方共同努力&#xff0c;才能确保项目的成功。那么&#xff0c;如何与精益生产咨询公司合作&a…

Unity射击游戏开发教程:(10)创建主界面

主界面开发 玩游戏时,主菜单是事后才想到要做的。实际上几乎每个游戏都有一个主界面。如果你点击打开游戏并立即开始游戏,你会感到非常惊讶。本文将讨论如何创建带有启动新游戏的交互式按钮的主界面/主菜单。 主菜单将是一个全新的场景。我们将添加一个 UI 图像元素,并在图像…

cookie,session,token

目的&#xff1a;解决用户登录状态 从一个简单的登录开始说起&#xff0c; 在我们访问bilibili的时候&#xff0c;第一次需要登录&#xff0c;但后续就不需要登录了&#xff0c;可以直接访问bilibili。 而且每次在页面请求服务器的资源都需要维持登录状态&#xff0c;如果没…

运维实施工程师之Linux服务器全套教程

一、Linux目录结构 1.1 基本介绍 Linux 的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的目录。 在 Linux 世界里&#xff0c;一切皆文件&#xff08;即使是一个硬件设备&#xff0c;也是使用文本来标…

暗区突围进不去/游戏无法启动/掉帧卡顿/报错的解决方法

暗区突围是一款高拟真硬核射击手游&#xff0c;打造了全新的沉浸式暗区战局体验&#xff0c;发行商是腾讯公司。这个游戏名词虽然看起来有些陌生&#xff0c;但其本身的玩法内核毫无疑问的是&#xff0c;这款游戏在画面质量和枪械操作方面&#xff0c;都是手游市场上同类游戏中…

文字转语音粤语怎么转换?6个软件教你快速进行文字转换语音

文字转语音粤语怎么转换&#xff1f;6个软件教你快速进行文字转换语音 当需要将文字转换为粤语语音时&#xff0c;可以使用多种工具和服务&#xff0c;这些工具可以帮助您快速而准确地实现这一目标。以下是六个非国内的语音转换软件&#xff0c;它们可以帮助您将文字转换为粤语…

【微磁学】对于现阶段微磁学仿真发展的思考1-理论篇

系列文章目录 对于现阶段微磁学仿真发展的思考1-理论篇 对于现阶段微磁学仿真发展的思考2-工具篇 文章目录 系列文章目录前言一、微磁学的数学区二、微磁学的物理区三、微磁学仿真现存的一些问题四、微磁学代码区&#xff1a;上手操作&#xff0c;理解更深入栗子1: 能量最小化…

人脸美妆SDK解决方案,自研人脸美妆方案

美妆已经成为视频内容中不可或缺的一部分。从拍摄到编辑&#xff0c;再到直播&#xff0c;美妆效果都能为视频内容增添魅力&#xff0c;吸引更多观众的眼球。为了满足企业对于高质量美妆效果的需求&#xff0c;美摄科技凭借多年的技术积累和创新精神&#xff0c;推出了全新的人…

Jmeter 中 CSV 如何参数化测试数据并实现自动断言

当我们使用Jmeter工具进行接口测试&#xff0c;可利用CSV Data Set Config配置元件&#xff0c;对测试数据进行参数化&#xff0c;循环读取csv文档中每一行测试用例数据&#xff0c;来实现接口自动化。此种情况下&#xff0c;很多测试工程师只会人工地查看响应结果来判断用例是…

局域网监控软件能干什么|有哪些好用的局域网监控软件

企业局域网已成为日常工作中不可或缺的一部分。 然而&#xff0c;网络环境的复杂性和员工上网行为的多样性&#xff0c;使得企业面临着诸多安全风险和管理挑战。 因此&#xff0c;高效局域网监控上网记录监测成为了企业保障信息安全和提升工作效率的重要手段。 高效局域网监控…

linux - 主次设备号自动申请

alloc_chrdev_region 原型如下&#xff0c;该函数向内核申请一个空闲的主设备号。 alloc_chrdev_region(&g_aputriger_dev, 0, APUTRIGER_MAX_NUM, "aputriger0"); 第四个参数是我们使用cat /proc/devices 看到的名称 /*** alloc_chrdev_region() - register a…

智慧交通系统:未来出行,从这里开始

随着城市化进程的加快&#xff0c;交通拥堵、事故频发、停车难等问题日益凸显&#xff0c;传统交通管理模式已难以满足现代社会的需求。智慧交通系统作为解决这些问题的关键&#xff0c;通过集成创新技术&#xff0c;实现交通管理的智能化、信息化&#xff0c;提高交通系统的运…

TC6291C 是一款电流模式升压型DC-DC转换器芯片

一般概述 TC6291C是一款电流模式升压型DC-DC转换器。其脉宽调制电路&#xff0c;内置0.2Q功率场效应管使这个调节器具有高功率效率。内部补偿网络也减少了多达6个的外部元件。误差信号放大器的同相输入端连接到0.6V精密基准电压&#xff0c;内部软启动功能可以减小瞬间突…

一文带你了解 Oracle 23ai 新特性 Vector 的基础用法

Oracle Database 23ai 来了&#xff0c;虽然目前只是云上可商用&#xff0c;但是 OP 有 FREE 版本可以进行开发。 本文将介绍 Oracle 23ai 的新特性之一&#xff1a; AI 向量搜索&#xff0c;的部分内容。 向量数据类型 23ai 新增向量数据类型&#xff0c;可以用于表示一系列的…