譯者 | 朱先忠
審校 | 重樓
引言
近年來,檢索增強生成(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帶來了新功能,但它也為此類系統的編程帶來了復雜性。隨著我們線性增加代理數量,它們之間的交互次數可能會成倍增長。每次交互都會帶來歧義和低效率的風險,這些風險可能會傳播到后續交互中。總之,有很多機會,但也存在重大的新風險。我們能做的就是努力深入了解這些系統,為它們的挑戰和可能性做好準備。
參考資料
譯者介紹
朱先忠,51CTO社區編輯,51CTO專家博客、講師,濰坊一所高校計算機教師,自由編程界老兵一枚。
原文標題:MAS Is All You Need: Supercharge Your Retrieval-Augmented Generation (RAG) with a Multi-Agent System,作者:Shuyi Yang