译者 | 朱先忠
审校 | 重楼
引言
近年来,检索增强生成(RAG)系统得到了迅速改进。理想情况下,我们可以将其演变划分为三个阶段。第一阶段,即在LLM出现之前,信息检索系统主要依赖于传统的搜索算法和索引技术。这些系统在理解上下文和生成类似人类的响应方面的能力是非常有限的。第二阶段,出现了LLM(大型语言模型)阶段,导致了剧烈的范式转变。现在,即第三阶段,出现了代理,另一种范式转变正在发生。
首先让我们退一步来看看什么是RAG?
RAG的工作原理
要了解RAG系统的工作原理,将其流程与图书馆的流程进行比较会有所帮助。
RAG的基本组件
- 摄取。此阶段类似于储备图书馆。就像图书管理员组织书籍并创建索引一样,RAG系统通过将数据转换为称为嵌入的数字表示来准备数据。这些嵌入存储在向量数据库中,便于以后查找相关信息。
- 检索。当用户提出问题时,这就像向图书管理员询问信息。RAG系统使用查询来搜索索引数据并从数据库中检索最相关的文档或信息。此过程可确保系统提取准确且最新的内容。
- 生成。利用检索到的信息,系统将这些信息与其内部知识相结合来生成响应。这类似于图书管理员综合来自多个来源的信息以提供问题的答案一样。
本照片由Radu Marcusu在Unsplash上拍摄
需要澄清的是,虽然摄取阶段严格来说不是RAG(代表“检索增强生成”)的组成部分,但我总是更喜欢将摄取作为该过程的关键部分。显然,如果没有适当的知识组织,后续阶段不太可能有效运作。
RAG系统传统上通过顺序工作流运行,其中不同的管道分别用来处理数据的提取、基于用户查询的相关信息检索以及使用检索到的数据生成响应。虽然这种架构对于许多应用程序来说都是简单而有效的,但它在需要复杂和非线性交互的场景中存在很大的局限性。
MAS=?
在人工智能的背景下,代理被定义为感知其环境、做出决策并自主采取行动以实现特定目标的系统或程序。例如,图书管理员可以被视为代理;他组织书籍、研究信息并制定对查询的回复。与AI代理非常相似,图书管理员可以浏览大量信息,整理并提供知识访问渠道,同时适应用户的需求。我们将开发的代理主要将决策组件委托给大型语言模型(LLM),利用其先进的功能来处理和生成类似人类的文本。
基于LLM的多代理系统(MAS)由一组此类代理组成,它们协作实现共同目标或解决复杂问题。在MAS中,每个代理独立运行,但可以与其他代理进行通信、辩论和协调,以共享信息、委派任务并提高整体系统性能。
别担心,我们不会用Python从头开始编写多代理系统(MAS)。目前,已经存在几种可用的框架可以简化开发过程。需要强调的是,本文的目标不是构建终极的多代理检索增强生成系统,而仅是展示如何使用可用的工具轻松构建相对复杂的系统。
注意,本文中显示的每一段代码也都展示在本文的GitHub存储库中。
准备好了吗?我们开始吧!
环境设置
我们在本文中将使用Anaconda进行开发。如果你的机器上没有它,请从官方网站下载并安装它(只需按照安装脚本说明操作即可)。
然后,打开终端,使用一些我们在此过程中使用的包来创建环境:
接下来,我们需要在项目文件夹中创建一个.env文件,我们将OpenAI API密钥和ChromaDB配置放在其中。例如,我的这些数据看起来像下面的样子:
数据摄取
在本文的代码存储库中,我们已经准备了一些位于kb文件夹中的示例数据。这些文本文件的内容来自维基百科。为了便于提取此文件夹中的文本文件,我们在tools_ingestion.py文件中实现了一些函数:
- get_txt_file_content():读取文本文件的内容。
- process_text():通过LLM调用将长文本转换为块。
- text_to_list():将前一个函数的输出简化为有效的Python列表。
- save_chunks_to_db():将text_to_list()的输出保存到持久数据库(在我们的例子中是ChromaDB)。
- path_to_db():按顺序调用get_txt_file_content()→process_text()→text_to_list()→save_chunks_to_db()。
- text_to_db():按顺序调用process_text()→text_to_list()→save_chunks_to_db()。
在此,我不会对这些函数进行解释,因为它们已经在脚本中进行了相应的注释。现在,我们可以启动ChromaDB数据库了:
然后,在同一个项目文件夹中的单独终端中运行摄取管道(请记住使用你之前创建的相同Python环境):
如果我们查看一下文件ingest的内容,我们会注意到关于Turin城市的最后一行被注释了。这是有原因的,我们很快就会发现这个问题。
检索
在开始构建我们的MAS应用之前,我们需要定义一些函数来从ChromaDB数据库中检索信息。你可以在tools_retrieve.py文件中找到这些函数的实现。基本逻辑是,函数tries()连接到数据库,计算输入查询的嵌入,查看类似的块并返回结果。我们可以通过搜索“Universities in Amsterdam”来测试此脚本:
输出结果应该类似于如下内容:
使用AG2框架构建MAS系统
AG2(以前称为AutoGen)是一个创新型的开源编程框架,旨在促进AI代理的开发并增强多个代理之间的协作以解决复杂任务;其主要目标是简化代理AI的创建和研究。虽然AG2官方网站声称该框架已准备好“在几分钟内构建可用于生产场景的多代理系统”,但我个人认为在完全可用于生产场景之前仍需要做一些工作。不可否认的是,AG2为创建旨在研究的实验提供了一个非常用户友好的环境。需要强调的是,还有许多其他框架可用于创建多代理系统。例如:Letta、LangGraph、CrewAI等。
在本文中,我们将实现一个MAS,其中包含:
- 人类→人类输入的代理。
- 代理摄取→负责从文本文件或直接从文本输入中摄取信息。
- 代理检索→负责从内部数据库中提取相关信息,以协助其他代理回答用户问题。
- 代理应答→负责使用代理摄取检索到的信息为用户查询提供答案。
- 代理路由器→负责促进人类用户与其他代理之间的通信。
人类将只与代理路由器交互,后者将负责内部聊天组,包括代理检索、代理应答和代理摄取。聊天组内的代理利用它们的知识和工具协作,提供尽可能最好的答案。
MA-RAG(多代理检索增强生成)系统的完整代码可以在mas.py文件中找到。在本部分中,我们将讨论代码中一些特别值得注意的关键组件和功能。
代理定义
要在AG2中定义代理,我们使用ConversableAgent()类。例如,要定义代理摄取:
上面各个参数指定了:
- 名称:agent_ingestion;
- 定义代理的系统提示:SYSTEM_PROMPT_AGENT_INGESTION是prompts.py中定义的变量。
- 在消息路由过程中有帮助的描述(DESCRIPTION_AGENT_INGESTION是在prompts.py中定义的变量);
- LLM的配置;
- 是否每次收到消息时都要求人工输入(通过设置human_input_mode=“NEVER”,代理将永远不会提示人工输入)。
- 是否不打印发送的消息。
同样,我们可以定义所有其他的代理(human、agent_retrieve、agent_answer、agent_router)。
添加工具
到目前为止,我们已经定义了各种代理;但是,根据目前的配置,这些代理只能接收文本输入并以文本输出进行响应,而无法执行需要特定工具的更复杂任务。例如,当前状态下的代理无法访问我们在本文第一部分中创建的数据库来进行搜索。
为了实现此功能,我们需要“告诉”代理,它可以访问能够执行某些任务的工具。我们选择确定性地实现一个工具,而不是让代理自己弄清楚,这是基于效率和可靠性而考虑的。这种确定性的方法可以降低出错的可能性,因为可以明确定义和编码该过程。尽管如此,我们仍将赋予代理责任和自主权,以选择使用哪种工具,确定其使用参数,并决定如何组合多种工具来处理复杂请求。指导和自主之间的这种平衡将增强代理的能力,同时保持结构化方法。
我希望现在你已经很清楚,与许多非专家声称代理“非常聪明”以至于可以毫不费力地处理复杂任务的说法相反,实际上幕后有大量工作正在研发中。代理所依赖的基础工具需要仔细研究、实施和测试。即使在生成式AI领域,也没有任何事情是“自动发生的”。理解这一区别对于理解开发有效AI系统所涉及的复杂性和工作量至关重要。虽然这些代理可以执行令人印象深刻的任务,但它们的能力是精心设计和深思熟虑的结果,而不是天生的智慧。
还记得我们之前为摄取创建的函数text_to_db()和path_to_db()吗?我们可以通过这种方式将它们“注册”到代理摄取中:
类似地,我们可以将检索工具添加到代理检索中:
MAS拓扑结构
到目前为止,我们已经定义了每个代理、它们的角色以及它们可以使用的工具。剩下的就是这些代理如何组织以及它们如何相互通信。我们的目标是创建一个拓扑。在这个拓扑中,人类与代理路由器交互,然后代理路由器与其他代理一起参与嵌套聊天组。该组协作解决人类查询,自主确定操作顺序,选择适当的工具并制定响应。在此设置中,代理路由器充当中央协调器,指导代理之间的信息流(代理摄取、代理检索和代理应答)。每个代理都有一个特定的功能:代理摄取处理传入数据,代理检索从数据库访问相关信息,代理应答根据收集到的见解提出最终响应。
要创建群聊,我们可以使用GroupChat()类。
在上面这段代码中,我们列出了将成为群组一部分的代理(agents),决定它们不需要在聊天开始时自我介绍(send_introductions),将对话的最大轮次设置为10(max_round),将每轮的发言人选择委托给聊天管理器(speaker_selection_method),并将对话转换限制为特定方案(allowed_or_disallowed_speaker_transitions)。
创建群组后,我们需要一个群组管理器来管理对话顺序:
这里需要注意的是用于is_termination_msg参数的lambda函数。此函数通过检查最后一条消息是否包含子字符串“(to human)”来确定聊天何时应该终止。此机制至关重要,因为在代理路由器的系统提示中,它指定:“清楚地表明你的消息的预期收件人。例如,在向用户致辞时使用(to human)。”这种方法为何时退出嵌套聊天并向人类用户返回响应提供了明确的信号。
现在,我们需要进行群聊,我们刚刚创建了一个从代理路由器进行统计的嵌套聊天。
通过利用结构化的通信框架和代理之间的预定义转换,我们可以确保代理之间的高效协作,同时允许决策的灵活性。
开始聊天
现在,我们准备好了,要开始与代理路由器进行聊天了:
或者,我们可以直接运行Python脚本:
你应该看到类似如下操作与输出:
只需输入你的消息,你就会收到代理路由器的答复。
你可以在此处找到这个对话的完整示例。首先,聊天以问题开始:
中文含义vk如下:
而代理路由器给出的答案是:
中文含义如下:
请注意,我们一开始提取的文本文件中并没有提供对初始问题的直接回答。事实上,如果我们查看第74行的嵌套聊天日志,我们可以看到,在嵌套聊天中,代理检索器会使用检索工具自动进行两次不同的查询,查询有关费城大学和加州大学伯克利分校的信息,然后代理应答会使用检索到的块提出解决方案。
注意,当我们添加新数据源时也会涉及类似的机制(还记得我们之前遗漏的关于都灵城市的文件吗?):
中文含义:
中文含义如下:
中文含义如下:
中文含义如下:
在后台发生的事情是,在嵌套聊天中的第一次交互中,代理应答意识到检索到的信息都与都灵无关(第163行)。然而,第二次,当代理检测到新的信息源时,代理摄取被调用(第179行)来处理新数据并将其保存在数据库中。这种动态说明了系统内代理的协作性质。最初,代理应答无法找到相关信息,这凸显了彻底的数据检索过程的重要性。一旦确定了新的来源,代理摄取就会迅速介入,以确保将有价值的信息纳入数据库,从而增强代理在未来交互中有效响应的能力。
你可以在日志文件中获得更多示例,我们在其中测试了系统如何适应外部矛盾陈述(第34行)以及代理摄取如何保存直接来自对话的新信息(第54、352行)。
超越玩具级的MA-RAG
本文中,我们已经探索了如何基于多代理范式来构建RAG系统。我们提出的当然是这种系统在生产环境中如何运行的简化形式。其中,我们有意忽略了许多重要方面(例如护栏、令牌使用、聊天界面设计、身份验证等),并且有许多领域需要进行重大改进。例如,完整的数据提取和知识库更新管道是必不可少的,同时增强信息检索方法,可以利用基于图形的方法,而不是仅仅依赖于嵌入相似性。此外,代理的拓扑结构可以根据需要变得尽可能复杂。例如,可以创建多个聊天组,每个聊天组都专注于整个管道的特定方面。此外,我们可以引入监督/判断角色来批判性地评估提议的计划和解决方案。可能性几乎是无限的,为特定应用场景找到正确的解决方案通常本身就是一种艺术形式。
结论
MAS的迅速普及当然存在泡沫的因素,但它也受到此类系统解决以前无法想象的复杂任务的潜力的推动。目前,我们仍处于这项技术的初步阶段,尽管众多的平台正在争相涌现以促进MAS的创建。回顾一下本文内容,很明显,除了LLM的功能外,知识库的管理对于RAG系统来说也是至关重要的,即使通过MAS进行了增强。
此外,虽然MAS带来了新功能,但它也为此类系统的编程带来了复杂性。随着我们线性增加代理数量,它们之间的交互次数可能会成倍增长。每次交互都会带来歧义和低效率的风险,这些风险可能会传播到后续交互中。总之,有很多机会,但也存在重大的新风险。我们能做的就是努力深入了解这些系统,为它们的挑战和可能性做好准备。
参考资料
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。
原文标题:MAS Is All You Need: Supercharge Your Retrieval-Augmented Generation (RAG) with a Multi-Agent System,作者:Shuyi Yang