基于AG2+ChromaDB开发多代理RAG系统 原创
本文中,我们将介绍如何使用开源的AI多代理框架AG2和开源的向量数据库ChromaDB构建一个多代理RAG系统。
引言
近年来,检索增强生成(RAG)系统得到了迅速改进。理想情况下,我们可以将其演变划分为三个阶段。第一阶段,即在LLM出现之前,信息检索系统主要依赖于传统的搜索算法和索引技术。这些系统在理解上下文和生成类似人类的响应方面的能力是非常有限的。第二阶段,出现了LLM(大型语言模型)阶段,导致了剧烈的范式转变。现在,即第三阶段,出现了代理,另一种范式转变正在发生。
首先让我们退一步来看看什么是RAG?
RAG的工作原理
要了解RAG系统的工作原理,将其流程与图书馆的流程进行比较会有所帮助。
RAG的基本组件
- 摄取。此阶段类似于储备图书馆。就像图书管理员组织书籍并创建索引一样,RAG系统通过将数据转换为称为嵌入的数字表示来准备数据。这些嵌入存储在向量数据库中,便于以后查找相关信息。
- 检索。当用户提出问题时,这就像向图书管理员询问信息。RAG系统使用查询来搜索索引数据并从数据库中检索最相关的文档或信息。此过程可确保系统提取准确且最新的内容。
- 生成。利用检索到的信息,系统将这些信息与其内部知识相结合来生成响应。这类似于图书管理员综合来自多个来源的信息以提供问题的答案一样。
本照片由Radu Marcusu在Unsplash上拍摄
需要澄清的是,虽然摄取阶段严格来说不是RAG(代表“检索增强生成”)的组成部分,但我总是更喜欢将摄取作为该过程的关键部分。显然,如果没有适当的知识组织,后续阶段不太可能有效运作。
RAG系统传统上通过顺序工作流运行,其中不同的管道分别用来处理数据的提取、基于用户查询的相关信息检索以及使用检索到的数据生成响应。虽然这种架构对于许多应用程序来说都是简单而有效的,但它在需要复杂和非线性交互的场景中存在很大的局限性。
MAS=?
在人工智能的背景下,代理被定义为感知其环境、做出决策并自主采取行动以实现特定目标的系统或程序。例如,图书管理员可以被视为代理;他组织书籍、研究信息并制定对查询的回复。与AI代理非常相似,图书管理员可以浏览大量信息,整理并提供知识访问渠道,同时适应用户的需求。我们将开发的代理主要将决策组件委托给大型语言模型(LLM),利用其先进的功能来处理和生成类似人类的文本。
基于LLM的多代理系统(MAS)由一组此类代理组成,它们协作实现共同目标或解决复杂问题。在MAS中,每个代理独立运行,但可以与其他代理进行通信、辩论和协调,以共享信息、委派任务并提高整体系统性能。
别担心,我们不会用Python从头开始编写多代理系统(MAS)。目前,已经存在几种可用的框架可以简化开发过程。需要强调的是,本文的目标不是构建终极的多代理检索增强生成系统,而仅是展示如何使用可用的工具轻松构建相对复杂的系统。
注意,本文中显示的每一段代码也都展示在本文的GitHub存储库中。
准备好了吗?我们开始吧!
环境设置
我们在本文中将使用Anaconda进行开发。如果你的机器上没有它,请从官方网站下载并安装它(只需按照安装脚本说明操作即可)。
然后,打开终端,使用一些我们在此过程中使用的包来创建环境:
conda create -n "mas" python=3.12.8
conda activate mas
git clone https://github.com/ngshya/mas-is-all-you-need.git
cd mas-is-all-you-need
pip install -r requirements.txt
接下来,我们需要在项目文件夹中创建一个.env文件,我们将OpenAI API密钥和ChromaDB配置放在其中。例如,我的这些数据看起来像下面的样子:
OPENAI_API_KEY="sk-proj-abcdefg..."
CHROMA_DB_HOST="localhost"
CHROMA_DB_PORT=8001
数据摄取
在本文的代码存储库中,我们已经准备了一些位于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数据库了:
chmod +x start_chroma
./start_chroma
然后,在同一个项目文件夹中的单独终端中运行摄取管道(请记住使用你之前创建的相同Python环境):
chmod +x ingest
./ingest
如果我们查看一下文件ingest的内容,我们会注意到关于Turin城市的最后一行被注释了。这是有原因的,我们很快就会发现这个问题。
检索
在开始构建我们的MAS应用之前,我们需要定义一些函数来从ChromaDB数据库中检索信息。你可以在tools_retrieve.py文件中找到这些函数的实现。基本逻辑是,函数tries()连接到数据库,计算输入查询的嵌入,查看类似的块并返回结果。我们可以通过搜索“Universities in Amsterdam”来测试此脚本:
python tools_retrieve.py --query "Universities in Amsterdam" --n_results 2
输出结果应该类似于如下内容:
[
{
"uuid": "9a921695-9310-53b4-9f52-c42d7c6432ef",
"distance": 0.5576044321060181,
"source": "kb/cities/europe/amsterdam.txt",
"last_update": "12 January 2025 07:11:18 UTC +0000",
"chunk": "\n<context>Educational Institutions</context>\n<content>The University of Amsterdam (abbreviated as UvA, Dutch: Universiteit van Amsterdam) is a public research university located in Amsterdam, Netherlands. Established in 1632 by municipal authorities, it is the fourth-oldest academic institution in the Netherlands still in operation. The UvA is one of two large, publicly funded research universities in the city, the other being the Vrije Universiteit Amsterdam (VU). It is also part of the largest research universities in Europe with 31,186 students, 4,794 staff, 1,340 PhD students and an annual budget of \u20ac600 million. It is the largest university in the Netherlands by enrollment.</content>\n"
},
{
"uuid": "5ce692ab-b762-53f7-84bc-f95fc6585015",
"distance": 0.561765730381012,
"source": "kb/cities/europe/amsterdam.txt",
"last_update": "12 January 2025 07:11:18 UTC +0000",
"chunk": "\n<context>University Structure and Achievements</context>\n<content>The main campus is located in central Amsterdam, with a few faculties located in adjacent boroughs. The university is organised into seven faculties: Humanities, Social and Behavioural Sciences, Economics and Business, Science, Law, Medicine, Dentistry. Close ties are harbored with other institutions internationally through its membership in the League of European Research Universities (LERU), the Institutional Network of the Universities from the Capitals of Europe (UNICA), European University Association (EUA) and Universitas 21. The University of Amsterdam has produced six Nobel Laureates and five prime ministers of the Netherlands.</content>\n"
}
]
使用AG2框架构建MAS系统
AG2(以前称为AutoGen)是一个创新型的开源编程框架,旨在促进AI代理的开发并增强多个代理之间的协作以解决复杂任务;其主要目标是简化代理AI的创建和研究。虽然AG2官方网站声称该框架已准备好“在几分钟内构建可用于生产场景的多代理系统”,但我个人认为在完全可用于生产场景之前仍需要做一些工作。不可否认的是,AG2为创建旨在研究的实验提供了一个非常用户友好的环境。需要强调的是,还有许多其他框架可用于创建多代理系统。例如:Letta、LangGraph、CrewAI等。
在本文中,我们将实现一个MAS,其中包含:
- 人类→人类输入的代理。
- 代理摄取→负责从文本文件或直接从文本输入中摄取信息。
- 代理检索→负责从内部数据库中提取相关信息,以协助其他代理回答用户问题。
- 代理应答→负责使用代理摄取检索到的信息为用户查询提供答案。
- 代理路由器→负责促进人类用户与其他代理之间的通信。
人类将只与代理路由器交互,后者将负责内部聊天组,包括代理检索、代理应答和代理摄取。聊天组内的代理利用它们的知识和工具协作,提供尽可能最好的答案。
#代理拓扑结构
Human <-> Agent Router <-> [Agent Ingestion, Agent Retrieve, Agent Answer]
MA-RAG(多代理检索增强生成)系统的完整代码可以在mas.py文件中找到。在本部分中,我们将讨论代码中一些特别值得注意的关键组件和功能。
代理定义
要在AG2中定义代理,我们使用ConversableAgent()类。例如,要定义代理摄取:
agent_ingestion = ConversableAgent(
name = "agent_ingestion",
system_message = SYSTEM_PROMPT_AGENT_INGESTION,
description = DESCRIPTION_AGENT_INGESTION,
llm_config = llm_config,
human_input_mode = "NEVER",
silent=False
)
上面各个参数指定了:
- 名称:agent_ingestion;
- 定义代理的系统提示:SYSTEM_PROMPT_AGENT_INGESTION是prompts.py中定义的变量。
SYSTEM_PROMPT_AGENT_INGESTION = '''
You are the **Ingestion Agent** tasked with acquiring new knowledge from various sources. Your primary responsibility is to ingest information from text files or directly from text inputs.
### Key Guidelines:
- **No New Information**: You do not contribute new information to conversations; your role is strictly to ingest and store knowledge.
- **Evaluation of Information**: Before ingesting any new knowledge, carefully assess whether the information provided is genuinely novel and relevant.
- **Step-by-Step Approach**: Take a moment to reflect and approach each task methodically. Breathe deeply and focus on the process.
### Tools Available:
1. **`path_to_db()`**: Use this tool to ingest knowledge from a specified text file.
2. **`text_to_db()`**: Utilize this tool to ingest knowledge directly from provided text.
Your mission is to enhance the database with accurate and relevant information while ensuring that you adhere to the guidelines above.
'''
- 在消息路由过程中有帮助的描述(DESCRIPTION_AGENT_INGESTION是在prompts.py中定义的变量);
DESCRIPTION_AGENT_INGESTION = '''
I am the **Ingestion Agent** responsible for acquiring new knowledge from text files or directly from user-provided text.
'''
- LLM的配置;
llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.environ["OPENAI_API_KEY"],
"temperature": 0.7,
}
]
}
- 是否每次收到消息时都要求人工输入(通过设置human_input_mode=“NEVER”,代理将永远不会提示人工输入);
- 是否不打印发送的消息。
同样,我们可以定义所有其他的代理(human、agent_retrieve、agent_answer、agent_router)。
添加工具
到目前为止,我们已经定义了各种代理;但是,根据目前的配置,这些代理只能接收文本输入并以文本输出进行响应,而无法执行需要特定工具的更复杂任务。例如,当前状态下的代理无法访问我们在本文第一部分中创建的数据库来进行搜索。
为了实现此功能,我们需要“告诉”代理,它可以访问能够执行某些任务的工具。我们选择确定性地实现一个工具,而不是让代理自己弄清楚,这是基于效率和可靠性而考虑的。这种确定性的方法可以降低出错的可能性,因为可以明确定义和编码该过程。尽管如此,我们仍将赋予代理责任和自主权,以选择使用哪种工具,确定其使用参数,并决定如何组合多种工具来处理复杂请求。指导和自主之间的这种平衡将增强代理的能力,同时保持结构化方法。
我希望现在你已经很清楚,与许多非专家声称代理“非常聪明”以至于可以毫不费力地处理复杂任务的说法相反,实际上幕后有大量工作正在研发中。代理所依赖的基础工具需要仔细研究、实施和测试。即使在生成式AI领域,也没有任何事情是“自动发生的”。理解这一区别对于理解开发有效AI系统所涉及的复杂性和工作量至关重要。虽然这些代理可以执行令人印象深刻的任务,但它们的能力是精心设计和深思熟虑的结果,而不是天生的智慧。
还记得我们之前为摄取创建的函数text_to_db()和path_to_db()吗?我们可以通过这种方式将它们“注册”到代理摄取中:
register_function(
path_to_db,
caller=agent_ingestion,
executor=agent_ingestion,
name="path_to_db",
description="Ingest new knowledge from a text file given its path.",
)
register_function(
text_to_db,
caller=agent_ingestion,
executor=agent_ingestion,
name="text_to_db",
description="Ingest new knowledge from a piece of conversation.",
)
类似地,我们可以将检索工具添加到代理检索中:
register_function(
retrieve_str,
caller=agent_retrieve,
executor=agent_retrieve,
name="retrieve_str",
description="Retrieve useful information from internal DB.",
)
MAS拓扑结构
到目前为止,我们已经定义了每个代理、它们的角色以及它们可以使用的工具。剩下的就是这些代理如何组织以及它们如何相互通信。我们的目标是创建一个拓扑。在这个拓扑中,人类与代理路由器交互,然后代理路由器与其他代理一起参与嵌套聊天组。该组协作解决人类查询,自主确定操作顺序,选择适当的工具并制定响应。在此设置中,代理路由器充当中央协调器,指导代理之间的信息流(代理摄取、代理检索和代理应答)。每个代理都有一个特定的功能:代理摄取处理传入数据,代理检索从数据库访问相关信息,代理应答根据收集到的见解提出最终响应。
要创建群聊,我们可以使用GroupChat()类。
group_chat = GroupChat(
agents = [
agent_router,
agent_ingestion,
agent_retrieve,
agent_answer
],
messages=[],
send_introductions=False,
max_round=10,
speaker_selection_method="auto",
speaker_transitions_type="allowed",
allowed_or_disallowed_speaker_transitions={
agent_router: [agent_ingestion, agent_retrieve, agent_answer],
agent_ingestion: [agent_router],
agent_retrieve: [agent_answer],
agent_answer: [agent_router],
},
)
在上面这段代码中,我们列出了将成为群组一部分的代理(agents),决定它们不需要在聊天开始时自我介绍(send_introductions),将对话的最大轮次设置为10(max_round),将每轮的发言人选择委托给聊天管理器(speaker_selection_method),并将对话转换限制为特定方案(allowed_or_disallowed_speaker_transitions)。
创建群组后,我们需要一个群组管理器来管理对话顺序:
group_chat_manager = GroupChatManager(
groupchat=group_chat,
llm_config=llm_config,
silent=False,
is_termination_msg=lambda msg: "(to human)" in msg["content"].lower()
)
这里需要注意的是用于is_termination_msg参数的lambda函数。此函数通过检查最后一条消息是否包含子字符串“(to human)”来确定聊天何时应该终止。此机制至关重要,因为在代理路由器的系统提示中,它指定:“清楚地表明你的消息的预期收件人。例如,在向用户致辞时使用(to human)。”这种方法为何时退出嵌套聊天并向人类用户返回响应提供了明确的信号。
现在,我们需要进行群聊,我们刚刚创建了一个从代理路由器进行统计的嵌套聊天。
nested_chats = [
{
"recipient": group_chat_manager,
"summary_method": "last_msg",
}
]
agent_router.register_nested_chats(
nested_chats,
trigger=lambda sender: sender in [human],
)
通过利用结构化的通信框架和代理之间的预定义转换,我们可以确保代理之间的高效协作,同时允许决策的灵活性。
开始聊天
现在,我们准备好了,要开始与代理路由器进行聊天了:
chat_results = human.initiate_chat(
agent_router,
message=input("Ciao! How can I assist you today? �� : "),
max_turns = 100
)
或者,我们可以直接运行Python脚本:
python mas.py
你应该看到类似如下操作与输出:
$ python mas.py [conda:mas]
Ciao! How can I assist you today? �� :
只需输入你的消息,你就会收到代理路由器的答复。
你可以在此处找到这个对话的完整示例。首先,聊天以问题开始:
[Human]
Is there a university in Philadelphia, and was it established before the University of California, Berkeley?
中文含义vk如下:
费城有大学吗?它是在加州大学伯克利分校之前建立的吗?
而代理路由器给出的答案是:
[Agent Router]
Yes, there is a university in Philadelphia known as the **University of Pennsylvania** (commonly referred to as Penn or UPenn). It is a private Ivy League research university and was chartered prior to the U.S. Declaration of Independence, making it one of the oldest institutions of higher education in the United States. The university first convened its board of trustees in 1749, which positions it as the fourth-oldest institution, though some argue it may be the fifth-oldest due to the establishment dates of other institutions. ��
On the other hand, the **University of California, Berkeley** was founded in **1868**.
Given these dates, the University of Pennsylvania was established significantly earlier than the University of California, Berkeley. ��
**Sources**:
1. University of Pennsylvania Overview (last updated: 12 January 2025)
2. University of California, Berkeley Overview (last updated: 12 January 2025)
中文含义如下:
是的,费城有一所大学,名为**宾夕法尼亚大学**(通常称为Penn或UPenn)。它是一所私立常春藤盟校研究型大学,在美国《独立宣言》之前就已成立,是美国最古老的高等教育机构之一。该大学于1749年首次召集董事会,这使它成为第四古老的机构,尽管有些人认为,由于其他机构的成立日期,它可能是第五古老的机构。
另一方面,**加州大学伯克利分校**成立于**1868年**。
鉴于这些日期,宾夕法尼亚大学的成立时间明显早于加州大学伯克利分校。
**来源**:
1. 宾夕法尼亚大学概况(最后更新时间:2025年1月12日)
2. 加州大学伯克利分校概况(最后更新时间:2025年1月12日)
请注意,我们一开始提取的文本文件中并没有提供对初始问题的直接回答。事实上,如果我们查看第74行的嵌套聊天日志,我们可以看到,在嵌套聊天中,代理检索器会使用检索工具自动进行两次不同的查询,查询有关费城大学和加州大学伯克利分校的信息,然后代理应答会使用检索到的块提出解决方案。
注意,当我们添加新数据源时也会涉及类似的机制(还记得我们之前遗漏的关于都灵城市的文件吗?):
[Human]
What do you know about the University of Turin?
中文含义:
你对都灵大学了解多少?
[Agent Router]
�� It seems I couldn’t find specific information about the University of Turin. The details I retrieved were mainly about other universities like the University of Barcelona and UC Berkeley. If there’s a particular aspect of the University of Turin you’re curious about, or if you’d like information on something else, just let me know! ��
中文含义如下:
似乎我找不到关于都灵大学的具体信息。我检索到的详细信息主要是关于其他大学,如巴塞罗那大学和加州大学伯克利分校。如果你对都灵大学的某个方面感兴趣,或者你想了解其他信息,请告诉我!
[Human]
Information about Turin can be found in kb/cities/europe/turin.txt
中文含义如下:
有关都灵的信息可以在kb/cities/europe/turin.txt中找到。
[Agent Router]
Here is some information about Turin:
### Overview
**Location**: Turin (Italian: Torino) is a city in Northern Italy, …
[…]
中文含义如下:
以下是关于都灵的一些信息:
###概述
**位置**:都灵(意大利语:Torino)是意大利北部的一个城市,……
在后台发生的事情是,在嵌套聊天中的第一次交互中,代理应答意识到检索到的信息都与都灵无关(第163行)。然而,第二次,当代理检测到新的信息源时,代理摄取被调用(第179行)来处理新数据并将其保存在数据库中。这种动态说明了系统内代理的协作性质。最初,代理应答无法找到相关信息,这凸显了彻底的数据检索过程的重要性。一旦确定了新的来源,代理摄取就会迅速介入,以确保将有价值的信息纳入数据库,从而增强代理在未来交互中有效响应的能力。
你可以在日志文件中获得更多示例,我们在其中测试了系统如何适应外部矛盾陈述(第34行)以及代理摄取如何保存直接来自对话的新信息(第54、352行)。
超越玩具级的MA-RAG
本文中,我们已经探索了如何基于多代理范式来构建RAG系统。我们提出的当然是这种系统在生产环境中如何运行的简化形式。其中,我们有意忽略了许多重要方面(例如护栏、令牌使用、聊天界面设计、身份验证等),并且有许多领域需要进行重大改进。例如,完整的数据提取和知识库更新管道是必不可少的,同时增强信息检索方法,可以利用基于图形的方法,而不是仅仅依赖于嵌入相似性。此外,代理的拓扑结构可以根据需要变得尽可能复杂。例如,可以创建多个聊天组,每个聊天组都专注于整个管道的特定方面。此外,我们可以引入监督/判断角色来批判性地评估提议的计划和解决方案。可能性几乎是无限的,为特定应用场景找到正确的解决方案通常本身就是一种艺术形式。
结论
MAS的迅速普及当然存在泡沫的因素,但它也受到此类系统解决以前无法想象的复杂任务的潜力的推动。目前,我们仍处于这项技术的初步阶段,尽管众多的平台正在争相涌现以促进MAS的创建。回顾一下本文内容,很明显,除了LLM的功能外,知识库的管理对于RAG系统来说也是至关重要的,即使通过MAS进行了增强。
此外,虽然MAS带来了新功能,但它也为此类系统的编程带来了复杂性。随着我们线性增加代理数量,它们之间的交互次数可能会成倍增长。每次交互都会带来歧义和低效率的风险,这些风险可能会传播到后续交互中。总之,有很多机会,但也存在重大的新风险。我们能做的就是努力深入了解这些系统,为它们的挑战和可能性做好准备。
参考资料
- https://github.com/ngshya/mas-is-all-you-need/tree/main
- https://ag2.ai/
- https://www.trychroma.com/
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。
原文标题:MAS Is All You Need: Supercharge Your Retrieval-Augmented Generation (RAG) with a Multi-Agent System,作者:Shuyi Yang