DLAI-LlamaIndex-JavaScript-WeebApp-笔记-全-
DLAI LlamaIndex JavaScript WeebApp 笔记(全)
001:课程介绍 🎯
在本课程中,我们将学习如何使用LlamaIndex构建一个具备高级推理和决策能力的主动式RAG(检索增强生成)研究智能体。与处理简单查询的标准RAG不同,主动式RAG能够处理复杂的多步骤任务,例如从一系列研究论文中提取相关信息并进行综合总结。
课程概述
欢迎来到《使用LlamaIndex构建主动式RAG》课程。我是吴恩达,与我一同授课的是LlamaIndex的联合创始人兼CEO,Jerry Liu。
Jerry Liu表示,他非常高兴能参与此课程。本课程将聚焦于“智能体RAG”框架,该框架能帮助你构建具备高级推理和数据决策能力的研究智能体。
例如,假设你拥有某个主题的一系列研究论文,并希望提取与特定问题相关的信息,然后综合各论文的观点。这是一个需要多步骤处理的复杂请求。并且,早期处理步骤(如从一篇论文中识别主题)可能会改变后续所需的步骤(例如从其他论文中检索关于该主题的额外信息)。
相比之下,目前非常流行的标准RAG流程,更适用于对少量文档提出简单问题。其工作原理是检索一些上下文,将其塞入大语言模型的提示词中,然后仅调用一次大语言模型来获取响应。
本课程将把“与数据对话”的理念提升到新的高度,向你展示如何构建一个自主的研究智能体。
核心学习路径
你将学习构建完整智能体所需的渐进式推理组件。
首先,是路由。你将为请求添加决策能力,将其路由到多个不同的工具。
接下来,是工具使用。你将创建一个接口,让智能体能够选择工具,并为该工具生成正确的参数。
最后,是多步骤推理与工具使用。我们将使用大语言模型,借助一系列工具执行多步骤推理,并在此过程中保持记忆。
高级控制与监督
你将学习如何有效地与智能体交互,并利用其能力进行细致的控制和监督。
这不仅允许你在RAG流程之上创建一个更高级的研究助手,还能为你提供更有效的方法来指导其行动。
具体而言,你将学习如何确保大语言模型的可调试性。我们将探讨如何逐步查看智能体的执行过程,并利用这些信息来改进你的智能体。

另一个非常强大的工具是,允许用户在中间步骤选择性地注入指导。

例如,如果你看到智能体正在搜索错误的文档,来自人类输入的一点提示(建议它搜索不同的文档)可以带来更好的性能。这就像一位经验丰富的经理给初级员工一个提示,让其考虑新的信息。
致谢与课程启动
许多人共同努力创建了本课程。Jerry Liu特别感谢来自LlamaIndex的Logan Markowch和Anja Farjado,以及来自DeepL AI并为课程做出贡献的Dila Eadin。
在第一课中,你将构建一个基于单个文档的路由器,它可以同时处理问答和摘要任务。

这听起来很棒,让我们进入下一个视频,开始学习吧。😊

总结
本节课我们一起了解了主动式RAG的概念及其相较于标准RAG的优势,明确了本课程的学习路径——从路由、工具使用到多步骤推理。我们还预览了如何对智能体进行控制和调试,并确认了第一课的实际构建目标。接下来,我们将动手开始构建。
002:路由引擎 🧭


在本节课中,我们将学习主动式RAG的最简单形式:路由引擎。给定一个查询,路由引擎将从多个查询引擎中选择一个来执行查询。我们将基于单个文档构建一个简单的路由引擎,使其能够同时处理问答和摘要任务。

概述与设置
上一节我们介绍了主动式RAG的概念,本节中我们来看看如何构建一个基础的路由引擎。首先,我们需要完成一些准备工作。

以下是设置环境所需的步骤:
- 导入OpenAI密钥:我们首先定义一个辅助函数来导入OpenAI API密钥。
- 导入Nest Async IIio模块:由于Jupyter在后台运行事件循环,而我们的许多模块使用异步操作,导入此模块可以确保异步功能在Jupyter笔记本中正常运行。
- 加载示例文档:我们将加载一篇名为“MetaGPT”的论文PDF作为示例文档。这是一篇关于新型多智能体框架的论文,于2024年被ICLR会议接收。你也可以上传自己的文档进行尝试。
- 分割文档:我们使用LlamaIndex的句子分割器将文档分割成大小均匀的块。这里设置块大小为1024个字符。
- 配置模型:这一步是可选的,它允许我们设置默认的LLM和嵌入模型。在本课程中,默认使用
gpt-3.5-turbo和text-embedding-ada-002,但你也可以注入自己的模型。
# 示例:设置全局配置
from llama_index.core import Settings
Settings.llm = OpenAI(model="gpt-3.5-turbo")
Settings.embedding_model = OpenAIEmbedding(model="text-embedding-ada-002")
构建索引
现在,我们准备开始构建索引。索引可以被视为数据之上的一组元数据,查询索引会触发不同的检索行为。
我们将为文档节点构建两种索引:

- 向量索引:通过文本嵌入对节点进行索引。查询向量索引将根据嵌入相似度返回最相似的节点。这是构建任何RAG系统的核心抽象。
- 摘要索引:这是一种非常简单的索引。查询摘要索引将返回索引中当前存在的所有节点,其返回结果不依赖于用户的具体查询。
# 示例:构建索引
from llama_index.core import VectorStoreIndex, SummaryIndex


vector_index = VectorStoreIndex(nodes)
summary_index = SummaryIndex(nodes)
创建查询引擎与工具



接下来,我们将这些索引转化为查询引擎,进而转化为查询工具。
- 查询引擎:代表存储在索引中数据的整体查询接口,它结合了检索和LLM综合生成的能力。每个查询引擎都擅长处理特定类型的问题。
- 查询工具:是带有元数据(特别是描述该工具能回答何种问题)的查询引擎。这为路由引擎在不同查询引擎之间动态路由提供了基础。
以下是创建查询工具的过程:
- 从摘要索引和向量索引创建对应的查询引擎。
- 为每个查询引擎定义描述其功能的元数据,从而创建查询工具。
# 示例:创建查询引擎和工具
summary_query_engine = summary_index.as_query_engine()
vector_query_engine = vector_index.as_query_engine()
from llama_index.core.tools import QueryEngineTool
summary_tool = QueryEngineTool.from_defaults(
query_engine=summary_query_engine,
description="用于回答与MetaGPT相关的摘要性问题。"
)
vector_tool = QueryEngineTool.from_defaults(
query_engine=vector_query_engine,
description="用于从MetaGPT论文中检索特定上下文。"
)
定义路由引擎
有了查询引擎和工具,我们现在可以定义路由引擎。LlamaIndex提供了几种不同类型的选择器来构建路由引擎。
- LLM选择器:提示LLM输出一个JSON,然后进行解析,最后查询相应的索引。
- Pydantic选择器:利用OpenAI等模型支持的函数调用API,生成Pydantic选择对象,而不是解析原始JSON。
这些选择器可以动态地让你选择单个或多个索引进行路由。在本例中,我们尝试使用一个名为LLMSingleSelector的LLM驱动的单选择器。
# 示例:构建路由查询引擎
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector
router_query_engine = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(),
query_engine_tools=[summary_tool, vector_tool]
)
测试路由引擎
让我们通过一些查询来测试路由引擎的工作情况。启用详细输出可以让我们查看中间步骤。
测试1:摘要查询
- 查询:“What is a summary of the document?”(文档的摘要是什么?)
- 过程:路由引擎选择了
query_engine 0(即摘要工具)。这是因为问题要求整体摘要。 - 结果:返回了对整篇论文的总结,其源节点数量等于文档的总块数(34个),证实了摘要引擎被调用并综合了所有上下文。
测试2:具体信息查询
- 查询:“How do agents share information with other agents?”(智能体如何与其他智能体共享信息?)
- 过程:路由引擎选择了
query_engine 1(即向量搜索工具)。LLM给出的推理是,这个问题需要从论文的特定段落中检索具体上下文。 - 结果:成功找到了相关上下文(例如,智能体使用共享消息工具发布结构化消息),并生成了准确回答。
代码整合与总结
本节课中我们一起学习了如何构建一个基础的路由引擎。为了方便使用,可以将上述所有代码整合到一个辅助函数中。这个函数接收文件路径,并构建一个兼具向量搜索和摘要功能的路由查询引擎。
# 辅助函数示例
def get_router_query_engine(file_path):
# ... 包含上述所有步骤:加载文档、分割、构建索引、创建工具、定义路由引擎 ...
return router_query_engine
# 使用示例
query_engine = get_router_query_engine("metaGPT.pdf")
response = query_engine.query("Tell me about the ablation study results.")
通过这个整合的函数,你可以轻松加载自己的PDF文档并体验路由查询的效果。例如,询问“关于消融实验的结果”,路由引擎会识别这是需要具体上下文的问题,从而调用向量搜索工具来获取答案。

总结来说,本节课程展示了如何利用LlamaIndex构建一个能理解查询意图、并在不同查询引擎(如摘要和向量搜索)之间智能路由的简单系统。这是实现更复杂主动式RAG功能的第一步。下一节课中,我们将探索更高级的代理能力。
003:工具调用 🛠️

在本节课中,我们将学习如何让大型语言模型(LLM)不仅选择要执行的功能,还能推断出传递给该功能的参数。这超越了基础RAG中LLM仅用于内容合成的角色,是实现LLM与外部环境交互的关键一步。
概述

在基础的RAG流程中,LLM仅用于内容合成。上一节课展示了如何使用LLM通过选择不同的查询流程来做出决策,这是一种简化的工具调用形式。本节中,我们将展示如何使用LLM不仅选择要执行的函数,还能推断出传递给该函数的参数。这使得LLM能够理解如何使用向量数据库,而不仅仅是消费其输出。最终结果是,通过工具调用,用户能够提出更多问题,并获得比标准RAG技术更精确的结果。

开始编码
让我们开始编写代码。首先,我们需要设置OpenAI环境。
# 设置OpenAI环境
import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
接下来,我们将导入必要的LlamaIndex模块。
# 导入LlamaIndex模块
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.llms.openai import OpenAI
工具调用简介
工具调用是LLM与外部环境交互的必要接口。我们将展示如何从Python函数定义工具接口,LLM将使用LlamaIndex的抽象功能,自动从Python函数的签名中推断参数。
为了说明这一点,我们首先定义两个简单的计算器函数,展示工具调用的工作原理。
# 定义两个示例函数
def add(x: int, y: int) -> int:
"""将两个数字相加。"""
return x + y
def mystery(x: int, y: int) -> int:
"""计算 (x + y) * (x + y)。"""
return (x + y) * (x + y)
LlamaIndex中的核心抽象是FunctionTool。这个FunctionTool包装了你提供的任何Python函数。
# 将函数包装为工具
from llama_index.core.tools import FunctionTool
add_tool = FunctionTool.from_defaults(fn=add)
mystery_tool = FunctionTool.from_defaults(fn=mystery)
我们的函数工具与许多LLM模型(包括OpenAI)的函数调用功能原生集成。要将工具传递给LLM,你需要导入LLM模块,然后调用predict_and_call。
# 使用LLM调用工具
from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo")
response = llm.predict_and_call(
tools=[add_tool, mystery_tool],
input_str="使用mystery函数计算,其中x=2,y=9"
)
print(response)
predict_and_call接收一组工具以及一个输入提示字符串或一系列聊天消息。然后,它能够决定调用哪个工具,调用该工具本身,并返回最终响应。在这个例子中,我们看到LLM正确选择了mystery工具,并推断出参数x=2, y=9,最终返回了正确的结果121。
请注意,这个简单的例子实际上是路由器的扩展版本。LLM不仅选择工具,还决定给工具什么参数。
在向量搜索之上构建智能体层
让我们利用这个核心概念,在向量搜索之上定义一个更复杂的智能体层。LLM不仅可以选择向量搜索,我们还可以让它推断元数据过滤器。元数据过滤器是一个结构化的标签列表,有助于返回更精确的搜索结果。
我们将使用之前相同的论文“MetaGBT”。这次,让我们关注节点本身(即文本块),因为我们将查看附加在这些块上的实际元数据。
与上一课类似,我们将使用LlamaIndex的SimpleDirectoryReader来加载这个PDF文件的解析表示。
# 加载文档
documents = SimpleDirectoryReader(input_dir="./data").load_data()
接下来,与上一课类似,我们将使用句子分割器将这些文档分割成一组均匀的块,块大小为1024。
# 分割文档为节点
node_parser = SentenceSplitter(chunk_size=1024)
nodes = node_parser.get_nodes_from_documents(documents)
这里的每个节点代表一个块。让我们查看一个示例块的内容。
# 查看第一个节点的内容和元数据
example_node = nodes[0]
print(example_node.get_content(metadata_mode="all"))
当我们打印出来时,不仅得到了论文首页的解析表示,还可以看到附加在顶部的元数据。这包括page_label=1、file_name=metagbt.pdf、文件类型、文件大小以及创建和结束日期。我们将特别关注页面标签,因为如果我们尝试查看不同的节点,会得到不同的页码。实际上,我们为每个块添加了页码注释。

定义带元数据过滤的向量存储索引


接下来,我们将在这些节点上定义一个向量存储索引。与上一课类似,这基本上会在这些节点上构建一个RAG索引管道,为每个节点添加嵌入,并返回一个查询引擎。
与上次不同的是,我们可以尝试通过元数据过滤器查询这个RAG管道,以展示元数据过滤的工作原理。
# 构建向量索引
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine()

# 使用元数据过滤器进行查询
from llama_index.core.vector_stores import MetadataFilter, MetadataFilters


filters = MetadataFilters(filters=[MetadataFilter(key="page_label", value="2")])
response = query_engine.query("MetaGBT的一些高级成果是什么?", filters=filters)
print(response)
我们定义了一个查询引擎,并调用“MetaGBT的一些高级成果是什么?”。如果我们查看源节点,运行后会发现,当我们遍历源节点时,可以打印出附加在这些源节点上的元数据。我们看到这里的page_label等于2,因此它能够正确地过滤页码,将搜索限制在page_label等于2的页面集合中。
将检索工具封装为函数
本节课的最后一部分将允许我们将这个整体检索工具包装成一个函数。这个函数接收查询字符串和页码作为过滤器。然后,LLM实际上可以推断出用于用户查询的页码过滤器,而不需要用户手动指定元数据过滤器。
需要注意的是,元数据不仅限于页码。正如你所见,你可以通过LlamaIndex抽象定义任何你想要的元数据,如章节ID、标题、页脚等。使用多个元数据过滤器的能力在像GPT-4这样的更好模型中尤为突出,因此我们强烈建议你尝试一下。
在这里,我们将定义一个封装此功能的Python函数。
# 定义向量查询函数
def vector_query(query: str, page_numbers: list[int]) -> str:
"""
在索引上执行向量搜索,并指定页码作为元数据过滤器。
"""
filters = MetadataFilters(
filters=[MetadataFilter(key="page_label", value=str(p)) for p in page_numbers]
)
query_engine = index.as_query_engine()
response = query_engine.query(query, filters=filters)
return str(response)
# 将函数包装为工具
vector_query_tool = FunctionTool.from_defaults(fn=vector_query)
我们定义了一个名为vector_query的函数,它接收查询和页码。这允许你在索引上执行向量搜索,并指定页码作为元数据过滤器。最后,我们看到我们定义了vector_query_tool = FunctionTool.from_defaults(fn=vector_query),这使我们能够将其与语言模型一起使用。
使用LLM调用工具
让我们尝试用LLM(特别是GPT-3.5 Turbo)调用这个工具。我们会发现LLM能够推断出字符串以及元数据过滤器。
# 使用LLM调用向量查询工具
response = llm.predict_and_call(
tools=[vector_query_tool],
input_str="第2页描述的MetaGBT的高级成果是什么?"
)
print(response)
我们看到LLM能够制定正确的查询“MetaGBT的高级成果是什么?”,并指定页码为2。我们得到了正确的答案。与之前类似,我们可以快速验证源节点,看到返回的源节点的page_label为2。
结合多个工具
最后,我们可以从第一课的路由器示例中引入摘要工具,并将其与向量工具结合,创建这个整体的工具选择系统。
这段代码在相同的节点集上设置了一个摘要索引,并将其包装在一个类似于第一课的摘要工具中。
# 创建摘要索引和工具
from llama_index.core import SummaryIndex
summary_index = SummaryIndex(nodes)
summary_query_engine = summary_index.as_query_engine()
def summary_query(query: str) -> str:
"""生成文档的摘要。"""
response = summary_query_engine.query(query)
return str(response)
summary_tool = FunctionTool.from_defaults(fn=summary_query)
现在让我们再次尝试工具调用。LLM有一个稍微困难的任务,即除了推断函数参数外,还要选择正确的工具。
# 使用多个工具进行查询
response = llm.predict_and_call(
tools=[vector_query_tool, summary_tool],
input_str="第8页上MetaGBT与ChatDev的比较是什么?"
)
print(response)
我们看到它仍然调用了向量工具,页码等于8,并且能够返回正确的答案。我们可以通过打印源节点来验证这一点。
最后,我们可以问一个问题“这篇论文的摘要是什么?”,以展示LLM在必要时仍然可以选择摘要工具。
# 测试摘要工具
response = llm.predict_and_call(
tools=[vector_query_tool, summary_tool],
input_str="这篇论文的摘要是什么?"
)
print(response)
我们看到它返回了正确的响应。
总结

本节课中,我们一起学习了工具调用的核心概念。我们了解到,通过让LLM选择工具并推断参数,可以构建更智能、更灵活的RAG系统。我们演示了如何将Python函数封装为工具,如何使用元数据过滤器精确控制检索范围,以及如何让LLM在多个工具中做出选择。这为构建能够与文档进行复杂交互的智能体奠定了重要基础。在下一课中,我们将展示如何在文档之上构建一个完整的智能体。
004:构建智能体推理循环 🤖

在本节课中,我们将学习如何构建一个完整的智能体推理循环,而不仅仅是单次工具调用。我们将使用LlamaIndex的函数调用智能体实现,它能够进行多步推理,处理复杂的用户查询。
到目前为止,我们的查询都是在单次前向传递中完成的:给定查询,用正确的参数调用正确的工具,然后返回响应。但这仍然相当受限。如果用户提出一个包含多个步骤的复杂问题,或者一个需要澄清的模糊问题呢?本节将介绍如何定义完整的智能体推理循环,使智能体能够跨工具和多步骤进行推理。

设置环境与工具 🛠️
首先,我们需要设置环境并导入必要的模块。我们将使用与之前课程相同的MetaGPT论文作为数据源。
# 设置OpenAI API密钥
import os
os.environ['OPENAI_API_KEY'] = 'your-api-key-here'

# 导入LlamaIndex和工具
from llama_index import VectorStoreIndex, SimpleDirectoryReader
from llama_index.tools import QueryEngineTool, ToolMetadata
from llama_index.agent import FunctionCallingAgentWorker, AgentRunner
from llama_index.llms import OpenAI
# 加载文档并创建索引
documents = SimpleDirectoryReader('data').load_data()
index = VectorStoreIndex.from_documents(documents)

为了简洁起见,我们将上一课中使用的自动检索向量搜索工具和摘要工具打包成一行代码导入。
# 从工具模块导入预定义的工具
from utils import vector_tool, summary_tool
现在,我们设置函数调用智能体。与之前的笔记本一样,我们使用GPT-3.5 Turbo作为我们的LLM。
# 初始化LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
定义智能体组件 🧩
在LlamaIndex中,一个智能体主要由两个组件构成:智能体工作器(Agent Worker)和智能体运行器(Agent Runner)。智能体工作器负责执行给定智能体的下一步操作,而智能体运行器是总体的任务调度器,负责创建任务、在给定任务上协调智能体工作器的运行,并能够将最终响应返回给用户。
以下是导入和设置这些组件的代码:
# 导入智能体组件
from llama_index.agent import FunctionCallingAgentWorker, AgentRunner
# 创建函数调用智能体工作器
agent_worker = FunctionCallingAgentWorker(
tools=[vector_tool, summary_tool],
llm=llm,
verbose=True # 设置为True以查看中间输出
)

# 创建智能体运行器
agent = AgentRunner(agent_worker)
智能体工作器的主要职责是:根据现有的对话历史、内存、任何过去的状态以及当前的用户输入,使用函数调用来决定下一个要调用的工具,调用该工具,并决定是否返回最终响应。整体的智能体接口由智能体运行器封装,我们将使用它来查询智能体。

执行多步查询 🔄
现在,让我们尝试一个多步查询。我们将问:“告诉我关于MetaGPT中的智能体角色,以及它们如何相互通信。”

# 使用智能体进行查询
response = agent.query("Tell me about the agent roles in MetaGPT and then how they communicate with each other.")
print(response)
让我们跟踪智能体的输出。我们看到智能体能够将这个整体问题分解为步骤。第一部分是询问MetaGPT中的智能体角色,它调用摘要工具来回答这个问题。
需要注意的是,摘要工具不一定是最精确的。你可能会认为向量工具实际上会返回一组更简洁的上下文,更好地代表你正在寻找的相关文本片段。然而,摘要工具对于这项工作来说仍然是合理的。当然,更强大的模型(如GPT-4 Turbo、Claude 3 Sonnet或Opus)可能能够选择更精确的向量工具来帮助回答这个问题。
无论如何,我们看到我们能够获得输出:MetaGPT的智能体角色包括产品经理、架构师、项目经理、QA工程师等。然后,它使用这个输出进行思维链推理,触发下一个问题:MetaGPT中智能体角色之间的通信。
我们能够获得关于这个问题的答案:MetaGPT中智能体角色之间的通信是结构化和高效的。我们能够结合整个对话历史来生成最终响应。
当运行这样的多步查询时,你需要确保能够跟踪来源。幸运的是,与之前的课程类似,你可以查看response.source_nodes来查看这些节点的内容。这允许你检查检索到的第一个源节点的内容,这只是论文的第一页。
维护对话历史 💬
调用agent.query允许你以一次性方式查询智能体,但不保留状态。现在,让我们尝试在一段时间内维护对话历史。
智能体能够在对话内存缓冲区中维护聊天记录。内存模块可以定制,但默认情况下,它是一个扁平的项目列表,根据LLM上下文窗口的大小作为滚动缓冲区。因此,当智能体决定使用工具时,它不仅使用当前的聊天记录,还使用先前的对话历史来执行下一步或下一个操作。
因此,我们将使用agent.chat而不是agent.query。我们首先问:“告诉我使用的评估数据集。”然后,我们会问一个后续问题:“告诉我上述其中一个数据集的结果。”显然,要知道“上述数据集”是什么,你必须在对话历史中的某个地方存储这些信息。
# 使用chat方法维护对话历史
response1 = agent.chat("Tell me about the evaluation datasets used.")
print(response1)
# 提出后续问题
response2 = agent.chat("Tell me the results over one of the above datasets.")
print(response2)
智能体能够将这个查询加上对话历史转化为对向量工具的查询,询问“在Human Eval数据集上的结果”,这是使用的评估数据集之一,并能够返回最终答案。
使用低级API进行调试与控制 🐛
我们刚刚提供了一个与智能体交互的高级接口。在本节中,我们将展示允许你以更细粒度的方式逐步执行和控制智能体的功能。这不仅允许你在RAG管道上创建更高级的研究助手,还可以调试和控制它。这里的一些好处包括:对每个步骤执行的更强调试能力,以及通过允许你注入用户反馈来提高稳定性。
拥有这个低级智能体接口主要有两个原因。首先是调试能力:如果你是一个构建智能体的开发者,你可能希望更透明、更清晰地了解底层实际发生的情况,特别是如果你的智能体第一次没有正常工作。你可以实际进入,跟踪智能体的执行,查看它在何处失败,并尝试不同的输入,看看是否实际上将智能体执行修改为正确的响应。
另一个有用的原因是,实际上可以实现更丰富的用户体验,围绕这个核心智能体能力构建产品体验。例如,假设你希望在智能体执行过程中监听人类反馈,而不仅仅是在智能体执行完成后。那么,你可以想象创建某种异步队列,在整个智能体执行过程中监听来自人类的输入,如果实际有人类输入进来,你可以在智能体执行较大任务的过程中中断并修改其执行,而不必等到智能体任务完成。
我们将首先再次通过函数调用智能体工作器和智能体运行器设置来定义我们的智能体,然后开始使用低级API。我们首先从用户查询创建一个任务对象,然后开始逐步执行,甚至可以在中间注入我们自己的输入。
# 重新定义智能体(为了示例清晰)
agent_worker = FunctionCallingAgentWorker(
tools=[vector_tool, summary_tool],
llm=llm,
verbose=True
)
agent = AgentRunner(agent_worker)
# 使用低级API:创建任务
task = agent.create_task("Tell me about the agent roles in MetaGPT and then how they communicate with each other.")
print(f"Task created with ID: {task.task_id}")

# 执行任务的第一步
step_output = agent.run_step(task.task_id)
print(step_output)
当我们检查日志和智能体的输出时,我们看到第一部分实际上已经执行了。我们调用agent.get_completed_steps来查看任务已完成步骤的数量。我们看到一个步骤已经完成,这是到目前为止的当前输出。我们还可以查看智能体的任何即将到来的步骤,通过agent.get_upcoming_steps来实现。同样,我们将任务ID传递给智能体,我们能够打印出任务的即将到来步骤的数量。
这个调试接口的好处是,如果你想现在暂停执行,你可以。你可以在不完成智能体流程的情况下获取中间结果。
但让我们继续,运行接下来的两个步骤,并实际尝试注入用户输入。让我们实际问:“智能体如何共享信息?”作为用户输入。这不是原始任务查询的一部分,但通过注入这个,实际上可以修改智能体执行,以返回你想要的结果。
# 注入用户输入以修改执行
user_input = "What about how agents share information?"
agent.run_step(task.task_id, input=user_input)
# 继续执行直到完成
while not step_output.is_last:
step_output = agent.run_step(task.task_id)
print(step_output)
# 获取最终响应
final_response = agent.finalize_response(task.task_id)
print(final_response)
我们看到我们能够获得关于MetaGPT智能体如何共享信息的答案,这确实是最后一步。要将此转换为类似于你在之前一些笔记本单元格中看到的智能体响应,那么你只需要调用agent.finalize_response。
总结 📝
在本节课中,我们一起学习了如何构建一个完整的智能体推理循环。我们首先介绍了智能体的高级接口,它允许我们执行多步查询并维护对话历史。然后,我们深入探讨了低级调试接口,它提供了对智能体执行过程的更细粒度控制,允许我们逐步执行、检查状态,甚至在执行过程中注入用户输入。

通过结合高级的易用性和低级的灵活性,LlamaIndex的智能体框架为构建能够处理复杂、多步骤任务的强大RAG应用提供了坚实的基础。在下一课中,我们将展示如何构建一个跨多个文档的智能体。
005:构建多文档智能体 🧠

在本节课中,我们将学习如何扩展上一节构建的单文档智能体,使其能够处理多个文档,并应对更复杂的查询场景。我们将从处理三个文档的用例开始,然后扩展到处理十一个文档,并介绍如何使用工具检索器来高效管理大量工具。

概述
上一节我们介绍了如何构建一个能够对单个文档进行推理并回答复杂问题,同时保持记忆的智能体。
本节中,我们将看看如何将该智能体扩展到处理多个文档,并应对日益增加的复杂度。我们将通过两个具体用例来演示:首先处理三篇研究论文,然后扩展到处理十一篇论文,并引入对象索引和工具检索器的概念,以解决工具数量过多时可能出现的提示词过长、成本增加和模型混淆问题。
构建三文档智能体
我们首先从一个包含三篇文档的用例开始,然后扩展到包含十一篇文档的用例。
我们需要设置OpenAI API密钥并导入必要的库。
import os
from llama_index import VectorStoreIndex, SummaryIndex
from llama_index.tools import FunctionTool
# ... 其他导入
第一个任务是为三篇论文设置一个函数调用智能体。我们通过将每篇文档的向量搜索工具和摘要工具组合成一个列表,并将其传递给智能体来实现。这样,智能体总共拥有六个工具。

我们将从ICLR 2024下载三篇论文,包括上一节用到的MeGPT,以及LongLORA和Self-RAG。

接下来,我们将每篇论文转换成一个工具。如果你还记得第3课,我们有一个名为get_doc_tools的辅助函数,它可以自动为给定的论文构建一个向量索引工具和一个摘要索引工具。向量工具执行向量搜索,摘要工具对整个文档进行总结。

对于每篇论文,我们都会得到向量工具和摘要工具,并将它们放入一个总体的字典中,映射每篇论文的名称到其对应的工具。
然后,我们简单地将这些工具放入一个扁平列表中。我们定义GPT-3.5 Turbo作为我们选择的LLM。如果我们快速查看一下将要传递给智能体的工具数量,会发现是六个,这是因为我们有三篇论文,每篇论文有两个工具:一个向量工具和一个摘要工具。


下一步是构建我们的整体智能体工作器。这个智能体工作器包含我们传递的六个工具和LLM。

现在,我们能够针对这三篇文档或其中一篇文档提问了。让我们快速问一个关于LongLORA的问题:“告诉我LongLORA中使用的Eval数据集,然后告诉我Eval结果。”

我们得到的回答是,其中一个使用的Eval数据集是AP19测试集。然后我们能够查看LongLORA模型的Eval结果。
我们可以问的下一个问题是:“给我Self-RAG和LongLORA的摘要。”这将允许我们对两篇论文进行总结。
首先,智能体调用Self-RAG的摘要工具,输入其名称,我们能够获得描述该论文内容的输出。
我们看到智能体随后调用LongLORA的摘要工具,输入“LongLORA”,然后我们得到LongLORA的整体摘要。
最终的回答是,我们能够同时获得Self-RAG和LongLORA的摘要。
如果你想自己尝试一些查询,可以尝试这两篇或甚至三篇论文的任何组合,并要求获取摘要或论文中的具体信息,以查看智能体是否能够对每篇文档的摘要和向量工具进行推理。
扩展到十一文档智能体
现在,让我们扩展到一个更高级的用例。这里我们将实际处理十一篇ICLR论文。
我们将从ICLR 2024下载十一篇研究论文。这包括MeGPT、LongLORA、LoftQ、S-Bench、Self-RAG以及其他几篇论文。
与上一节类似,我们现在将构建一个字典,将每篇论文映射到其向量工具和摘要工具。这个部分可能需要一点时间,因为我们需要处理、索引和嵌入十一篇文档。
现在,让我们将这些工具折叠成一个扁平列表。这是我们需要一个稍微更高级的智能体和工具架构的时刻。
问题是,假设我们尝试索引所有十一篇论文,现在包含22个工具。或者假设我们尝试索引100篇或1000篇甚至更多的论文。尽管LLM的上下文窗口正在变长,但将过多的工具选择塞入LLM提示词会导致以下问题:

- 工具可能无法全部放入提示词中,特别是当文档数量很大,并且你将每个文档建模为单独的工具或一组工具时。
- 成本和延迟会激增,因为你增加了提示词中的令牌数量。
- LLM实际上可能会感到困惑,当选择数量太大时,LLM可能无法选择正确的工具。
这里的解决方案是,当用户提出查询时,我们实际上执行检索增强,但不是在文本层面,而是在工具层面。我们首先检索一小部分相关工具,然后将这些相关工具提供给智能体推理提示词,而不是所有工具。
这个检索过程类似于RAG中使用的检索过程。简单来说,它可以只是top-K向量搜索,但当然,你可以添加所有你想要的先进检索技术来过滤出相关的结果集。
我们的智能体允许你插入一个工具检索器,让你能够精确地完成这个任务。让我们展示如何实际实现这一点。
首先,我们需要索引这些工具。LlamaIndex已经对通用文本文档具有广泛的索引能力。但由于这些工具实际上是Python对象,我们需要一种方法来将这些对象转换并序列化为字符串表示,然后再转换回来。这在LlamaIndex中通过对象索引抽象来解决。
我们将为这些工具定义一个对象索引和检索器。我们看到我们导入了VectorStoreIndex,这是我们索引文本的标准接口。但我们用ObjectIndex包装了它。要构建对象索引,我们看到我们直接将Python工具作为输入插入到索引中。
你可以通过对象检索器从对象索引中检索。这将调用索引的底层检索器,并直接将输出作为对象返回。在这种情况下,输出将是工具。
现在我们已经定义了对象检索器,让我们看一个非常简单的例子。让我们提问:“告诉我MeGPT和S-Bench中使用的Eval数据集。”
现在让我们看看这个列表中的第一个工具。我们看到我们实际上直接检索到了一组工具,并且第一个工具是MeGPT的摘要工具。如果我们看第二个工具,我们看到这是一个与MeGPT和S-Bench无关的论文的摘要工具。当然,检索的质量完全取决于你的嵌入模型。然而,我们看到检索到的最后一个工具确实是S-Bench的摘要工具。
现在我们准备好设置我们的函数调用智能体了。我们注意到设置与上一课中的设置非常相似。然而,作为一个附加功能,我们展示了你实际上可以向智能体添加一个系统提示词,如果你愿意的话。这是可选的,你不需要指定它,但如果你想要额外的指导,如果你希望智能体以某种方式输出内容,或者你希望它在对这些工具进行推理时考虑某些因素,你可以这样做。这就是一个例子。
现在让我们尝试问一些比较查询。我们问:“告诉我MeGPT中使用的Eval数据集,并与S-Bench进行比较。”
我们看到它调用了MeGPT的摘要工具以及S-Bench的摘要工具。它能够获得两者的结果。然后它在这里生成了最终的回答。


现在,作为最后一个例子,让我们比较和对比两篇LoRA论文:LongLORA和LoftQ,并首先分析每篇论文的方法。

我们看到智能体正在执行这个查询,它采取的第一步是获取这个输入任务,并实际检索一组输入工具来帮助它完成这个任务。因此,通过对象检索器,希望它实际上检索到LongLORA和LoftQ的工具,以帮助它完成响应。


如果我们查看智能体的中间输出,我们看到它能够访问来自LongLORA和LoftQ的相关工具。
我们看到它首先调用LongLORA的摘要工具,参数为“LongLORA”,并且能够获得该方法的摘要。
类似地,通过调用LoftQ的摘要工具,能够获得LoftQ的方法。
最终的LLM响应能够通过比较这两个工具的响应,并将其组合起来,综合成一个满足用户查询的答案。

总结
本节课中,我们一起学习了如何构建不仅能够处理单个文档,还能处理多个文档的智能体。我们从一个三文档的简单用例开始,介绍了如何为每篇文档创建向量和摘要工具,并将它们组合成一个智能体。
接着,我们扩展到一个更复杂的十一文档用例,并遇到了工具数量过多带来的挑战。为了解决这个问题,我们引入了对象索引和工具检索器的概念。这允许智能体在回答查询时,先动态检索最相关的工具子集,而不是一次性加载所有工具,从而提高了效率并降低了LLM的困惑度。

现在,你应该掌握了正确的工具,能够构建不仅限于单文档,还能处理多文档的智能体,使你能够构建更通用、更复杂的上下文增强研究助手,以回答复杂的问题。
006:6.课程总结
在本节课中,我们将对《使用LlamaIndex构建主动式RAG》课程进行总结,回顾所学到的核心知识与技能。
感谢您参与本课程的学习,并祝贺您顺利完成。在本课程中,您学习了关于智能体驱动的RAG系统构建,从构建路由智能体开始,到工具调用,再到构建能够对多个文档进行推理的自定义智能体。
上一节我们探讨了智能体的高级功能,本节我们将对整个课程内容进行回顾与总结。
以下是本课程涵盖的核心知识点:
- 路由智能体:学习了如何构建一个能够根据查询意图,将问题路由到不同处理模块或数据源的智能体。
- 工具调用:掌握了让智能体能够调用外部工具(如搜索引擎、计算器、API)来获取信息或执行操作的方法。
- 多文档推理:理解了如何构建不仅能处理单一文档,还能综合分析多个文档内容并进行复杂推理的智能体。
如果您希望进一步深入:
- 构建自定义智能体。
- 将您的智能体实现作为社区模板提交。
- 利用高级文档解析服务。

请查阅课程中链接的相关资源。我期待看到您运用主动式RAG技术构建出的所有精彩应用。

在本节课中,我们一起回顾了构建主动式RAG系统的完整路径,从基础的路由与工具调用,到实现能够进行多文档推理的复杂智能体。希望这些知识能帮助您在开发智能、自主的问答与信息检索系统时大展身手。

浙公网安备 33010602011771号