使用 Llama-index 实现的 Agentic RAG-Router Query Engine

前言

你是否也厌倦了我在博文中经常提到的老式 RAG(Retrieval Augmented Generation | 检索增强生成) 系统?反正我是对此感到厌倦了。但我们可以做一些有趣的事情,让它更上一层楼。接下来就跟我一起将 agents 概念引入传统的 RAG 工作流,重新构建自己的 Agentic RAG 系统吧。

去年大模型领域最流行的关键词就是 RAG 了,而今年,一代新人换旧人,agents 已经取代 RAG 成为大模型领域的新宠。你大可不必为错过 RAG 的风口而沮丧,因为将 agents 引入 RAG 系统后会有更好的效果,所以现在开始学习也不晚。

在本文中,我会介绍如何使用 Llama-index 实现基本的 Agentic RAG 应用。我将在接下来的几周发布一系列(共四篇)有关 Agentic RAG 架构的文章,本文是这个系列的第一篇。

RAG 基础工作流

在开始新篇章前,让我们快速回顾一下传统的 RAG 架构的组织形式和工作原理。这部分知识在后续过程也会用到,那些对 RAG 没有任何经验的初学者务必仔细学习。

传统的 RAG 架构的组织形式和工作原理

传统的 RAG 架构的组织形式和工作原理

在上面简单的 RAG 架构图中,我们至少需要理解以下概念:

  1. 文档(Documents):用于增强 LLM 理解特定领域的上下文。可以是 PDF 或者任何文本文档,对于多模态的 LLM 来说甚至可以是图片;
  2. 块(Chunks)/ 节点(nodes):将较大的文档通过合适的算法分割成尺寸合适的块(或节点);
  3. 特征映射(Embedings):在将文档分割成块后,我们需要为每个块创建与之对应的特征映射(一般以向量的形式存储)。当系统收到来自用户的查询时,RAG 系统会通过相似性搜索找到与查询内容相关性最高的文档块。这些被检索出来的文档块会与用户查询内容一起发送给 LLM,检索出来的文档块会充当此次 LLM 调用的上下文。最终,LLM 会根据以上内容生成响应。

以上就是典型的传统 RAG 系统的组织形式和工作原理。

为什么要创建 Agentic RAG

通过上一章节我们了解了传统 RAG 的实现,这种实现方案适用于少量文档的简单 QA 任务,不适合复杂的 QA 任务和对较大文档集的总结。

而这恰好是 agents 的强势领域,将 agents 与 RAG 结合能够将传统 RAG 系统提升到一个全新的水平。通过 Agentic RAG 系统,可以轻松地执行更复杂的任务,例如文档集摘要、复杂 QA 以及其他复杂任务。Agentic RAG 还能使 RAG 系统具有工具调用的能力,并且这些工具可以是自定义函数。

在本系列文章中我将讨论以下内容:

  1. 路由式查询引擎(Router Query Engines):这是最简单的 Agentic RAG 实现方式,它提供了添加逻辑声明的能力。这种能力可以帮助 LLM 根据需要执行的任务以及提供的工具确定通过何种路径能够达到最终目的
  2. 工具调用(Tool Calling):在这篇文章中,我将介绍如何将自己定义的工具(方法)添加到 Agentic RAG 架构中。我会为 agents 实现一些接口,以便从我们提供的工具中选择合适的工具并通过 LLM 生成调用这些工具(这里我们默认工具是自定义的 Python 函数)所需要的参数;
  3. 具有多步推理能力的 Agentic RAG;
  4. 在文档集中具有多步推理能力的 Agentic RAG。

路由式查询引擎 | Router Query Engine

这是 Llama-index 中最简单的 Agentic RAG 实现方案。在这种方案中,我们只需要创建一个路由式查询引擎。它能够在 LLM 帮助下,(从提供的工具和查询引擎列表中)确定具体使用什么工具或查询引擎来解决用户查询的

下图是本文要实现的路由式查询引擎的基本结构:

本文要实现的路由式查询引擎的基本结构

本文要实现的路由式查询引擎的基本结构

项目环境初始化

创建一个名为 agentic_rag 的目录作为本系列文章的项目目录,再在 agentic_rag 内部创建一个名为 basics 的目录作为本文代码实践的工作目录。创建完成后进入 basics 目录中进行环境初始化:

代码语言:javascript

复制

# /root/to/agentic_rag/basics
poetry init

在正式开始前,你需要先准备好你的 OpenAI API 密钥,如果你还没有密钥,可以从 此处 获取。准备好密钥后,将其添加到你的 .env 文件中:

代码语言:javascript

复制

# /root/to/agentic_rag/basics/.env
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

译者注:也可以使用其他平台的 LLM 接口,你可以在 此链接 下确认 Llama-index 是否支持你期望的平台。

译者注:如果没有能力购买平台提供的 LLM 能力,也可以使用 Ollama 在本地运行指定模型,并参考 文档 在 Llama-index 中调用模型能力。

译者注:如果使用了不同平台的接口需要将后文中提到的 OpenAI 相关的接口替换成你所使用的平台的接口。

代码截图-1

代码截图-1

代码截图-2

代码截图-2

安装依赖

我们会在本项目中使用到 Llama-index 以及一些其他的依赖库,你可以通过如下命令进行安装:

代码语言:javascript

复制

# /root/to/agentic_rag/basics
poetry add python-dotenv ipykernel llama-index nest_asyncio
下载数据集

我们需要一个 PDF 文件用于后续代码实践,你可以点击 此链接 下载我所使用的 PDF。当然你也可以使用你手中的任何一个 PDF 文件(译者注:对于初学者来说最好是一个纯文本的 PDF 文件)。将它保存到 /path/to/agentic_rag/basic/datasets 目录下。

代码截图-3

代码截图-3

加载文档并将其处理成块

译者注:原文作者是使用 .ipynb 格式来编写并运行代码的,如果你不熟悉这个文件格式可以使用正常的 .py 文件。

现在我们已经将代码运行的基础环境准备好了,然我们先通过 python-dotenv 库加载环境变量:

代码语言:javascript

复制

import dotenv
%load_ext dotenv
%dotenv

随后我们需要引入 nest_asyncio 库,因为 Llama-index 在后台使用大量 asyncio 功能:

代码语言:javascript

复制

import nest_asyncio
nest_asyncio.apply()

现在,然我们加载准备好的 PDF 文档:

代码语言:javascript

复制

from llama_index.core import SimpleDirectoryReader  # load lora_paper.pdf documents  
documents = SimpleDirectoryReader(input_files=["./datasets/lora_paper.pdf"]).load_data()
创建文档块

成功加载文档后,我们需要将其分解成合适大小的块:

代码语言:javascript

复制

from llama_index.core.node_parser import SentenceSplitter  # chunk_size of 1024 is a good default value  
splitter = SentenceSplitter(chunk_size=1024)  
# Create nodes from documents  
nodes = splitter.get_nodes_from_documents(documents)

可以使用以下方法获取有关每个块的详细信息:

代码语言:javascript

复制

node_metadata = nodes[1].get_content(metadata_mode=True)  
print(node_metadata)

代码截图-4

代码截图-4

创建所需的 LLM 和 Embedding 模型对象

译者注:这里你需要将用到的模型类替换为你所使用的平台所对应的类,有些类需要添加额外的第三方依赖,详情请查看 Llama-index 文档。

在本次代码实践中,我将使用 OpenAI 的 gpt-3.5-turbo 模型作为 LLM 模型,使用 OpenAI 的 text-embedding-ada-002 模型作为 Embedding 模型。

代码语言:javascript

复制

from llama_index.core import Settings  
from llama_index.llms.openai import OpenAI  
from llama_index.embeddings.openai import OpenAIEmbedding  # LLM model  
Settings.llm = OpenAI(model="gpt-3.5-turbo")  
# embedding model  
Settings.embed_model = OpenAIEmbedding(model="text-embedding-ada-002")
创建索引

正如前文基本结构图片所示,在本次代码实践中,我们将使用两个主要的索引:

  1. 摘要索引(Summary Index):根据 Llama-index 对应文档 所示,摘要索引是一种简单的数据结构,其中节点按顺序存储。在索引构建过程中,文档文本被分块、转换为节点并存储在列表中。在查询期间,摘要索引使用一些可选的过滤器参数迭代节点,并综合所有节点的答案。
  2. 向量索引(Vector Index):一个通过 Embedding 创建的常规索引存储,可以执行相似性搜索,以获得与搜索条件最相似的 n 个的索引。

可以使用下面的代码来创建这两个索引:

代码语言:javascript

复制

from llama_index.core import SummaryIndex, VectorStoreIndex  # summary index  
summary_index = SummaryIndex(nodes)  
# vector store index  
vector_index = VectorStoreIndex(nodes)

将向量索引转换为查询引擎

在创建并存储向量索引后,我们需要继续创建查询引擎,后续会将它们转换为 agnets 使用的工具(又名查询工具)。

代码语言:javascript

复制

# summary query engine  
summary_query_engine = summary_index.as_query_engine(  response_mode="tree_summarize",  use_async=True,  
)  # vector query engine  
vector_query_engine = vector_index.as_query_engine()

在上面的例子中,我们创建了两个不同的查询引擎。后续我们会将它们都挂载到路由式查询引擎下,然后路由式查询引擎会根据用户的查询内容决定具体使用哪个查询引擎。

路由式查询引擎功能示意

路由式查询引擎功能示意

在上面的代码中,我们指定了 use_async 参数以加快查询速度,这是我们必须使用 nest_asyncio 库的原因之一。

译者注:我刚去看 nest_asyncio 的代码库,发现被 archive 了,我还以为是 Python 官方支持了,后面在阅读 相关 issue 的时候才发现原来是代码库的作者年初去世了,RIP.

创建查询工具

查询工具是一个带有元数据(例如存储当前查询工具可以用来做什么)的查询引擎。这有助于路由式查询引擎能够根据传入的用户查询来决定具体使用哪个查询引擎。

代码语言:javascript

复制

from llama_index.core.tools import QueryEngineTool  summary_tool = QueryEngineTool.from_defaults(  query_engine=summary_query_engine,  description=(  "Useful for summarization questions related to the Lora paper."  ),  
)  vector_tool = QueryEngineTool.from_defaults(  query_engine=vector_query_engine,  description=(  "Useful for retrieving specific context from the the Lora paper."  ),
)

创建路由式查询引擎

最后,我们需要创建最终用于查询的路由式查询引擎。它能够帮我们聚合上文所创建的所有查询工具,即 summary_toolvector_tool

代码语言:javascript

复制

from llama_index.core.query_engine.router_query_engine import RouterQueryEngine  
from llama_index.core.selectors import LLMSingleSelector  query_engine = RouterQueryEngine(  selector=LLMSingleSelector.from_defaults(),  query_engine_tools=[  summary_tool,  vector_tool,  ],  verbose=True  
)

LLMSingleSelector:是一个使用 LLM 从选项列表中选择出单个选项的选择器。你可以在 这个链接 中查看更多信息。

测试路由式查询引擎

译者注:在执行如下代码是记得把问题换成与你使用的 PDF 相关的问题。

可以通过如下代码测试我们创建的路由式查询引擎:

代码语言:javascript

复制

response = query_engine.query("What is the summary of the document?")  
print(str(response))

代码截图-5

代码截图-5

以上是论文的摘要,总结了我们传递给查询引擎的 Lora 论文的所有上下文。

由于我们使用的摘要索引是将所有块存储在顺序列表中,因此在生成摘要时会访问所有块并从中生成一个总摘要,然后再以此生成最终摘要。

可以通过检查响应中 source_nodes 列表的长度来确认这一点,source_nodes 属性是这次响应中用到的所有块的列表。

代码截图-6

代码截图-6

可以看到最终的结果 38 与我们前面创建的块的数量相同,这意味着所有的块都用于本次摘要生成。

再提问一个不涉及总结的问题:

代码语言:javascript

复制

response = query_engine.query("What is the long from of Lora?")  
print(str(response))

代码截图-7

代码截图-7

这次回答使用了向量索引,尽管响应内容不是很准确。

将代码合并

现在你已经大致理解了这套 Agentic RAG 工作流,让我们将其抽象成函数方便后续调用:

代码语言:javascript

复制

from llama_index.core.query_engine.router_query_engine import RouterQueryEngine  
from llama_index.core.selectors import LLMSingleSelector  
from llama_index.core.tools import QueryEngineTool  
from llama_index.core import SummaryIndex, VectorStoreIndex  
from llama_index.core import Settings  
from llama_index.llms.openai import OpenAI  
from llama_index.embeddings.openai import OpenAIEmbedding  
from llama_index.core.node_parser import SentenceSplitter  
from llama_index.core import SimpleDirectoryReaderasync def create_router_query_engine(  document_fp: str,  verbose: bool = True,  
) -> RouterQueryEngine:  # load lora_paper.pdf documents  documents = SimpleDirectoryReader(input_files=[document_fp]).load_data()  # chunk_size of 1024 is a good default value  splitter = SentenceSplitter(chunk_size=1024)  # Create nodes from documents  nodes = splitter.get_nodes_from_documents(documents)  # LLM model  Settings.llm = OpenAI(model="gpt-3.5-turbo")  # embedding model  Settings.embed_model = OpenAIEmbedding(model="text-embedding-ada-002")  # summary index  summary_index = SummaryIndex(nodes)  # vector store index  vector_index = VectorStoreIndex(nodes)  # summary query engine  summary_query_engine = summary_index.as_query_engine(  response_mode="tree_summarize",  use_async=True,  )  # vector query engine  vector_query_engine = vector_index.as_query_engine()  summary_tool = QueryEngineTool.from_defaults(  query_engine=summary_query_engine,  description=(  "Useful for summarization questions related to the Lora paper."  ),  )  vector_tool = QueryEngineTool.from_defaults(  query_engine=vector_query_engine,  description=(  "Useful for retrieving specific context from the the Lora paper."  ),  )  query_engine = RouterQueryEngine(  selector=LLMSingleSelector.from_defaults(),  query_engine_tools=[  summary_tool,  vector_tool,  ],  verbose=verbose  )  return query_engine

然后我们就可以通过如下方法便捷的调用次函数:

代码语言:javascript

复制

query_engine = await create_router_query_engine("./datasets/lora_paper.pdf")  
response = query_engine.query("What is the summary of the document?")  
print(str(response))

代码截图-7

代码截图-7

我们可以在当前目录下创建一个 utils.py 文件,用于存放刚刚抽象出来的函数:

代码截图-8

代码截图-8

之后我们就可以通过如下代码便捷的调用此函数:

代码语言:javascript

复制

from utils import create_router_query_engine  query_engine = await create_router_query_engine("./datasets/lora_paper.pdf")  
response = query_engine.query("What is the summary of the document?")  
print(str(response))

代码截图-9

代码截图-9

结语

恭喜你走到这一步。以上就是本文的全部内容了,在下一篇文章中,我将介绍如何使用工具调用(也称函数调用)来进一步加强我们的 RAG 系统。

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

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

相关文章

凌晨1点开播!Meta Connect 2024开发者大会,聚焦Llama新场景和AR眼镜

作者:十九 编辑:李宝珠 北京时间 9 月 26 日凌晨 1 点,Meta Connect 2024 开发者大会即将举行,马克扎克伯格将聚焦 AI 和元宇宙,向大家分享 Llama 模型的更多潜在应用,并介绍 Meta 最新产品 AR 眼镜和 Meta…

OceanBase云数据库战略实施两年,受零售、支付、制造行业青睐

2022年OceanBase推出云数据库产品OB Cloud,正式启动云数据库战略。两年来OB Cloud发展情况如何,9月26日,OceanBase公有云事业部总经理尹博学向记者作了介绍。 尹博学表示,OB Cloud推出两年以来,已服务超过700家客户,客…

智算中心动环监控:构建高效、安全的数字基础设施@卓振思众

在当今快速发展的数字经济时代,智算中心作为人工智能和大数据技术的核心支撑设施,正日益成为各行业实现智能化转型的重要基石。为了确保这些高性能计算环境的安全与稳定,卓振思众动环监控应运而生,成为智算中心管理的重要组成部分…

理解Java引用数据类型(数组、String)传参机制的一个例子

目录 理解Java引用数据类型(数组、String)传参机制的一个例子理解样例代码输出 参考资料 理解Java引用数据类型(数组、String)传参机制的一个例子 理解 引用数据类型传递的是地址。用引用类型A给引用类型B赋值,相当于…

Linux(含麒麟操作系统)如何实现多显示器屏幕采集录制

技术背景 在操作系统领域,很多核心技术掌握在国外企业手中。如果过度依赖国外技术,在国际形势变化、贸易摩擦等情况下,可能面临技术封锁和断供风险。开发国产操作系统可以降低这种风险,确保国家关键信息基础设施的稳定运行。在一…

【C++位图】构建灵活的空间效率工具

目录 位图位图的基本概念如何用位图表示数据位图的基本操作setresettest 封装位图的设计 总结 在计算机科学中,位图(Bitmap)是一种高效的空间管理数据结构,广泛应用于各种场景,如集合操作、图像处理和资源管理。与传统…

一文读懂 Pencils Protocol 近期不可错过的市场活动

Pencils Protocol 是 Scroll 上综合性的 DeFi 协议,自 9 月 18 日开始其陆续在 Tokensoft、Bounce、Coresky 等平台开启 DAPP 通证的销售,并分别在短期内完成售罄。吸引了来自韩国、CIS、土耳其等 70 多个国家的 5 万多名认证用户,反响热烈&a…

Jmeter关联,断言,参数化

一、关联 常用的关联有三种 1.边界提取器 2.JSON提取器 3.正则表达式提取器 接下来就详细讲述一下这三种的用法 这里提供两个接口方便练习 登录接口 接口名称:登录 接口提交方式:POST 接口的url地址:https://admin-api.macrozheng.com/a…

C#常用数据结构栈的介绍

定义 在C#中&#xff0c;Stack<T> 是一个后进先出&#xff08;LIFO&#xff0c;Last-In-First-Out&#xff09;集合类&#xff0c;位于System.Collections.Generic 命名空间中。Stack<T> 允许你将元素压入栈顶&#xff0c;并从栈顶弹出元素。 不难看出&#xff0c;…

Vue引入js脚本问题记录(附解决办法)

目录 一、需求 二、import引入问题记录 三、解决方式 一、需求 我想在我的Vue项目中引入jquery.js和bootstrap.js这种脚本文件&#xff0c;但发现不能单纯的import引入&#xff0c;问题如下。 二、import引入问题记录 我直接这么引入&#xff0c;发现控制台报错TypeError: …

华为HarmonyOS地图服务 11 - 如何在地图上增加点注释?

场景介绍 本章节将向您介绍如何在地图的指定位置添加点注释以标识位置、商家、建筑等&#xff0c;并可以通过信息窗口展示详细信息。 点注释支持功能&#xff1a; 支持设置图标、文字、碰撞规则等。支持添加点击事件。 PointAnnotation有默认风格&#xff0c;同时也支持自定…

《强化学习的数学原理》(2024春)_西湖大学赵世钰 Ch9 策略梯度方法 Box 8.1 马尔可夫决策过程的平稳分布

Box 8.1&#xff1a; 马尔可夫决策过程的平稳分布 整理自 链接 分析平稳分布的关键工具是 P π ∈ R n n P_\pi \in {\mathbb R}^{n\times n} Pπ​∈Rnn&#xff0c;它是给定策略 π π π 下的概率转移矩阵。 如果状态被索引为 s 1 , ⋯ , s n s_1,\cdots, s_n s1​,⋯…

idea2021git从dev分支合并到主分支master

1、新建分支 新建一个名称为dev的分支&#xff0c;切换到该分支下面&#xff0c;输入新内容 提交代码到dev分支的仓库 2、切换分支 切换到主分支&#xff0c;因为刚刚提交的分支在dev环境&#xff0c;所以master是没有 3、合并分支 点击push&#xff0c;将dev里面的代码合并到…

图片尺寸不合适?这3款免费好用的AI绘图神器帮你免费无缝拓展!一键扩展画面之外的内容,真的泰裤啦!

大家好&#xff0c;我是灵魂画师向阳 在处理图片素材时&#xff0c;大家有没有遇到过尺寸不合适但又不能裁切的情况&#xff1f;是不是也想过图像要是能自己“长”出一块就好了&#xff1f;这种要求在以前或许很难实现&#xff0c;但生产式 AI 技术出现后它就不再是问题了&…

基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue

基于GIKT深度知识追踪模型的习题推荐系统 目录结构 Flask-BackEnd flask后端 app 后端主体文件 alg 深度学习模块 data 数据集data_process.py 数据预处理gikt.py GIKT模型pebg.py PEBG模型params.py 一些参数train.py 仅模型训练train_test.py 模型训练和测试-五折交叉验证t…

WebGIS开发四大开源框架对比

本篇文章主要介绍GIS开发四大地图框架的差异和特点。 Cesium: Cesium是目前主流的一款三维地图框架&#xff0c;支持桌面端、web端、移动端等多平台。Mapbox&#xff1a;高清经纬度矢量瓦片&#xff0c;个性化前端表达&#xff0c;前端矢量绘制&#xff0c;支持海量地名地址。…

数据库实验2—1

10-1 查询重量在[40,65]之间的产品信息 本题目要求编写SQL语句&#xff0c; 检索出product表中所有符合40 < Weight < 65的记录。 提示&#xff1a;请使用SELECT语句作答。 表结构: CREATE TABLE product (Pid varchar(20), --商品编号PName varchar(50), --商品名称…

仓颉编程入门2,启动HTTP服务

上一篇配置了仓颉sdk编译和运行环境&#xff0c;读取一个配置文件&#xff0c;并把配置文件简单解析了一下。 前面读取配置文件&#xff0c;使用File.readFrom()&#xff0c;这个直接把文件全部读取出来&#xff0c;返回一个字节数组。然后又创建一个字节流&#xff0c;给文件…

C语言实现常见的数据结构

栈 栈是一种后进先出&#xff08;LIFO, Last In First Out&#xff09;的数据结构 #include <stdio.h> #include <stdlib.h>#define MAX 100typedef struct {int data[MAX];int top; } Stack;// 初始化栈 void init(Stack *s) {s->top -1; }// 判断栈是否为空…

VBA技术资料MF204:右键多按钮弹出菜单中使用图标

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…