【AI Agent系列】【MetaGPT多智能體學習】3. 開發一個簡單的多智能體系統,兼看MetaGPT多智能體運行機制

本系列文章跟隨《MetaGPT多智能體課程》(https://github.com/datawhalechina/hugging-multi-agent),深入理解并實踐多智能體系統的開發。

本文為該課程的第四章(多智能體開發)的第一篇筆記。主要記錄下多智能體的運行機制及跟著教程,實現一個簡單的多智能體系統。

系列筆記

  • 【AI Agent系列】【MetaGPT多智能體學習】0. 環境準備 - 升級MetaGPT 0.7.2版本及遇到的坑
  • 【AI Agent系列】【MetaGPT多智能體學習】1. 再理解 AI Agent - 經典案例和熱門框架綜述
  • 【AI Agent系列】【MetaGPT多智能體學習】2. 重溫單智能體開發 - 深入源碼,理解單智能體運行框架

文章目錄

  • 系列筆記
  • 0. 多智能體間互通的方式 - Enviroment組件
    • 0.1 多智能體間協作方式簡介
    • 0.2 Environment組件 - 深入源碼
      • 0.2.1 參數介紹
      • 0.2.2 add_roles函數 - 承載角色
      • 0.2.3 publish_message函數 - 接收角色發布的消息 / 環境中的消息被角色得到
      • 0.2.4 run函數 - 運行入口
  • 1. 開發一個簡單的多智能體系統
    • 1.1 多智能體需求描述
    • 1.2 代碼實現
      • 1.2.1 學生智能體
      • 1.2.2 老師智能體
      • 1.2.3 創建多智能體交流的環境
      • 1.2.4 運行
      • 1.2.5 完整代碼
  • 2. 總結

0. 多智能體間互通的方式 - Enviroment組件

0.1 多智能體間協作方式簡介

在上次課中,我也曾寫過 多智能體運行機制 的筆記(這篇文章:【AI Agent系列】【MetaGPT】【深入源碼】智能體的運行周期以及多智能體間如何協作)。其中我自己通過看源碼,總結出了MetaGPT多智能體間協作的方式:

(1)每一個Role都在不斷觀察環境中的信息(_observe函數)
(2)當觀察到自己想要的信息后,就會觸發后續相應的動作
(3)如果沒有觀察到想要的信息,則會一直循環觀察
(4)執行完動作后,會將產生的msg放到環境中(publish_message),供其它Role智能體來使用。

現在再結合《MetaGPT多智能體》課程的教案,發現還是理解地有些淺了,我只關注了智能體在關注自己想要的信息,觀察到了之后就開始行動,而忽略了其背后的基礎組件 - Enviroment。

0.2 Environment組件 - 深入源碼

MetaGPT中對Environment的定義:環境,承載一批角色,角色可以向環境發布消息,可以被其他角色觀察到。短短一句話,總結三個功能:

  • 承載角色
  • 接收角色發布的消息
  • 環境中的消息被角色得到

下面我們分開來細說。

0.2.1 參數介紹

首先來看下 Environment 的參數:

class Environment(ExtEnv):"""環境,承載一批角色,角色可以向環境發布消息,可以被其他角色觀察到Environment, hosting a batch of roles, roles can publish messages to the environment, and can be observed by other roles"""model_config = ConfigDict(arbitrary_types_allowed=True)desc: str = Field(default="")  # 環境描述roles: dict[str, SerializeAsAny["Role"]] = Field(default_factory=dict, validate_default=True)member_addrs: Dict["Role", Set] = Field(default_factory=dict, exclude=True)history: str = ""  # For debugcontext: Context = Field(default_factory=Context, exclude=True)
  • desc:環境的描述
  • roles:字典類型,指定當前環境中的角色
  • member_addrs:字典類型,表示當前環境中的角色以及他們對應的狀態
  • history:記錄環境中發生的消息記錄
  • context:當前環境的一些上下文信息

0.2.2 add_roles函數 - 承載角色

def add_roles(self, roles: Iterable["Role"]):"""增加一批在當前環境的角色Add a batch of characters in the current environment"""for role in roles:self.roles[role.profile] = rolefor role in roles:  # setup system message with rolesrole.set_env(self)role.context = self.context

從源碼中看,這個函數的功能有兩個:

(1)給 Environment 的 self.roles 參數賦值,上面我們已經知道它是一個字典類型,現在看來,它的 key 為 role 的 profile,值為 role 本身。

疑問: role 的 profile 默認是空(profile: str = ""),可以沒有值,那如果使用者懶得寫profile,這里會不會最終只有一個 Role,導致無法實現多智能體?歡迎討論交流。

(2)給添加進來的 role 設置環境信息

Role的 set_env 函數源碼如下,它又給env設置了member_addrs。有點繞?

def set_env(self, env: "Environment"):"""Set the environment in which the role works. The role can talk to the environment and can also receivemessages by observing."""self.rc.env = envif env:env.set_addresses(self, self.addresses)self.llm.system_prompt = self._get_prefix()self.set_actions(self.actions)  # reset actions to update llm and prefix

通過 add_roles 函數,將 role 和 Environment 關聯起來。

0.2.3 publish_message函數 - 接收角色發布的消息 / 環境中的消息被角色得到

Environment 中的 publish_message 函數是 Role 在執行完動作之后,將自身產生的消息發布到環境中調用的接口。Role的調用方式如下:

def publish_message(self, msg):"""If the role belongs to env, then the role's messages will be broadcast to env"""if not msg:returnif not self.rc.env:# If env does not exist, do not publish the messagereturnself.rc.env.publish_message(msg)

通過上面的代碼來看,一個Role只能存在于一個環境中?

然后看下 Environment 中源碼:

def publish_message(self, message: Message, peekable: bool = True) -> bool:"""Distribute the message to the recipients.In accordance with the Message routing structure design in Chapter 2.2.1 of RFC 116, as already plannedin RFC 113 for the entire system, the routing information in the Message is only responsible forspecifying the message recipient, without concern for where the message recipient is located. How toroute the message to the message recipient is a problem addressed by the transport framework designedin RFC 113."""logger.debug(f"publish_message: {message.dump()}")found = False# According to the routing feature plan in Chapter 2.2.3.2 of RFC 113for role, addrs in self.member_addrs.items():if is_send_to(message, addrs):role.put_message(message)found = Trueif not found:logger.warning(f"Message no recipients: {message.dump()}")self.history += f"\n{message}"  # For debugreturn True

其中重點是這三行代碼,檢查環境中的所有Role是否訂閱了該消息(或該消息是否應該發送給環境中的某個Role),如果訂閱了,則調用Role的put_message函數,Role的 put_message函數的作用是將message放到本身的msg_buffer中:

for role, addrs in self.member_addrs.items():if is_send_to(message, addrs):role.put_message(message)

這樣就實現了將環境中的某個Role的Message放到環境中,并通知給環境中其它的Role的機制。

0.2.4 run函數 - 運行入口

run函數的源碼如下:

async def run(self, k=1):"""處理一次所有信息的運行Process all Role runs at once"""for _ in range(k):futures = []for role in self.roles.values():future = role.run()futures.append(future)await asyncio.gather(*futures)logger.debug(f"is idle: {self.is_idle}")

k = 1, 表示處理一次消息?為什么要有這個值,難道還能處理多次?意義是什么?

該函數其實就是遍歷一遍當前環境中的所有Role,然后運行Role的run函數(運行單智能體)。

Role的run里面,就是用該環境觀察信息,行動,發布信息到環境。這樣多個Role的運行就通過 Environment 串起來了,這些之前已經寫過了,不再贅述,見:【AI Agent系列】【MetaGPT】【深入源碼】智能體的運行周期以及多智能體間如何協作。

1. 開發一個簡單的多智能體系統

復現教程中的demo。

1.1 多智能體需求描述

  • 兩個智能體:學生 和 老師
  • 任務及預期流程:學生寫詩 —> 老師給改進意見 —> 學生根據意見改進 —> 老師給改進意見 —> … n輪 … —> 結束

1.2 代碼實現

1.2.1 學生智能體

學生智能體的主要內容就是根據指定的內容寫詩。

因此,首先定義一個寫詩的Action:WritePoem

然后,定義學生智能體,指定它的動作就是寫詩(self.set_actions([WritePoem])

那么它什么時候開始動作呢?通過 self._watch([UserRequirement, ReviewPoem]) 設置其觀察的信息。當它觀察到環境中有了 UserRequirement 或者 ReviewPoem 產生的信息之后,開始動作。UserRequirement為用戶的輸入信息類型。

class WritePoem(Action):name: str = "WritePoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Write a poem about the subject provided by human, Return only the content of the generated poem with NO other texts.If the teacher provides suggestions about the poem, revise the student's poem based on the suggestions and return.your poem:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Student(Role):name: str = "xiaoming"profile: str = "Student"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([WritePoem])self._watch([UserRequirement, ReviewPoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 獲取所有記憶# logger.info(msg)poem_text = await WritePoem().run(msg)logger.info(f'student : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msg

1.2.2 老師智能體

老師的智能體的任務是根據學生寫的詩,給出修改意見。

因此創建一個Action為ReviewPoem

然后,創建老師的智能體,指定它的動作為Review(self.set_actions([ReviewPoem])

它的觸發實際應該是學生寫完詩以后,因此,加入self._watch([WritePoem])

class ReviewPoem(Action):name: str = "ReviewPoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Check student-created poems about the subject provided by human and give your suggestions for revisions. You prefer poems with elegant sentences and retro style.Return only your comments with NO other texts.your comments:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Teacher(Role):name: str = "laowang"profile: str = "Teacher"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([ReviewPoem])self._watch([WritePoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 獲取所有記憶poem_text = await ReviewPoem().run(msg)logger.info(f'teacher : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msg

1.2.3 創建多智能體交流的環境

前面我們已經知道了多智能體之間的交互需要一個非常重要的組件 - Environment。

因此,創建一個供多智能體交流的環境,通過 add_roles 加入智能體。

然后,當用戶輸入內容時,將該內容添加到環境中publish_message,從而觸發相應智能體開始運行。這里cause_by=UserRequirement代表消息是由用戶產生的,學生智能體關心這類消息,觀察到之后就開始行動了。

classroom = Environment()classroom.add_roles([Student(), Teacher()])classroom.publish_message(Message(role="Human", content=topic, cause_by=UserRequirement,send_to='' or MESSAGE_ROUTE_TO_ALL),peekable=False,
)

1.2.4 運行

加入相應頭文件,指定交互次數,就可以開始跑了。注意交互次數,直接決定了你的程序的效果和你的錢包!

import asynciofrom metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.environment import Environmentfrom metagpt.const import MESSAGE_ROUTE_TO_ALLasync def main(topic: str, n_round=3):while n_round > 0:# self._save()n_round -= 1logger.debug(f"max {n_round=} left.")await classroom.run()return classroom.historyasyncio.run(main(topic='wirte a poem about moon'))

1.2.5 完整代碼

import asynciofrom metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.environment import Environmentfrom metagpt.const import MESSAGE_ROUTE_TO_ALLclassroom = Environment()class WritePoem(Action):name: str = "WritePoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Write a poem about the subject provided by human, Return only the content of the generated poem with NO other texts.If the teacher provides suggestions about the poem, revise the student's poem based on the suggestions and return.your poem:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass ReviewPoem(Action):name: str = "ReviewPoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Check student-created poems about the subject provided by human and give your suggestions for revisions. You prefer poems with elegant sentences and retro style.Return only your comments with NO other texts.your comments:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Student(Role):name: str = "xiaoming"profile: str = "Student"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([WritePoem])self._watch([UserRequirement, ReviewPoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 獲取所有記憶# logger.info(msg)poem_text = await WritePoem().run(msg)logger.info(f'student : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msgclass Teacher(Role):name: str = "laowang"profile: str = "Teacher"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([ReviewPoem])self._watch([WritePoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 獲取所有記憶poem_text = await ReviewPoem().run(msg)logger.info(f'teacher : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msgasync def main(topic: str, n_round=3):classroom.add_roles([Student(), Teacher()])classroom.publish_message(Message(role="Human", content=topic, cause_by=UserRequirement,send_to='' or MESSAGE_ROUTE_TO_ALL),peekable=False,)while n_round > 0:# self._save()n_round -= 1logger.debug(f"max {n_round=} left.")await classroom.run()return classroom.historyasyncio.run(main(topic='wirte a poem about moon'))
  • 運行結果

在這里插入圖片描述

2. 總結

總結一下整個流程吧,畫了個圖。

在這里插入圖片描述
從最外圍環境 classroom開始,用戶輸入的信息通過 publish_message 發送到所有Role中,通過Role的 put_message 放到自身的 msg_buffer中。

當Role運行run時,_observemsg_buffer中提取信息,然后只過濾自己關心的消息,例如Teacher只過濾出來自WritePoem的,其它消息雖然在msg_buffer中,但不處理。同時,msg_buffer中的消息會存入memory中。

run完即執行完相應動作后,通過publish_message將結果消息發布到環境中,環境的publish_message又將這個消息發送個全部的Role,這時候所有的Role的msg_buffer中都有了這個消息。完成了智能體間信息的一個交互閉環。


站內文章一覽

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714201.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714201.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714201.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

rsyslog配置中支持的模塊

rsyslog 配置語法 官方文檔 支持的模塊 rsyslog包含了很多模塊,主要分為輸入模塊、輸出模塊等等。 下面是關于 rsyslog 支持的大的模塊分類及其作用的說明: 模塊分類作用代表模塊鏈接Output Modules用于將日志消息輸出到不同的目的地omfile - 將日志消息輸出到文件omstdou…

CF 1867B

代碼 #include<bits/stdc.h>using namespace std;const int N1e510;int ans[N];void solve() {//輸入字符串長度和字符串int n;string s;cin>>n>>s;//下面說的修改操作是進行異或操作//k表示前后對稱位置不相等的字符的對數//m表示前后對稱位置相等的字符的…

C/C++基礎語法

C/C基礎語法 文章目錄 C/C基礎語法頭文件經典問題鏈表鏈表基礎操作 秒數轉換閏年斐波那契數列打印n階菱形曼哈頓距離菱形圖案的定義大數計算 輸入輸出格式化輸入輸出getline()函數解決cin只讀入一個單詞的問題fgets讀入整行輸出字符數組&#xff08;兩種方式puts和printf&#…

Linux或Windows下判斷socket連接狀態

前言 場景&#xff1a;客戶端程序需要實時知道和服務器的連接狀態。比較通用的做法應用層是采用心跳機制&#xff0c;每隔一端時間發送心跳能回復說明服務器正常。 實際應用場景中&#xff0c;服務端和客戶端并不是一家廠商的&#xff0c;比如說筆者這種情況&#xff0c;服務端…

推特API(Twitter API)V2 查詢用戶信息

前面章節已經介紹使用code換取Token的整個流程了&#xff0c;這里不再重復闡述了&#xff0c;下面我們介紹如何使用Token查詢用戶信息等操作。 1.引入相關依賴Maven <dependency> <groupId>oauth.signpost</groupId> <artifactId>signpost-co…

二刷代碼隨想錄——貪心day34

文章目錄 前言貪心知識點貪心的套路 貪心一般解題步驟一、860. 檸檬水找零二、406. 根據身高重建隊列三、452. 用最少數量的箭引爆氣球總結 前言 一個本碩雙非的小菜雞&#xff0c;備戰24年秋招&#xff0c;計劃二刷完卡子哥的刷題計劃&#xff0c;加油&#xff01; 二刷決定精…

day10_oop

今日內容 零、 復習昨日 一、作業 二、繼承 三、重寫 四、this和super 五、訪問修飾符 零、 復習昨日 數組創建的兩種方式 new int[3];new int[]{值,值2,…}存值: 數組名[下標] 值 構造方法什么作用?有參無參構造什么區別? 創建對象無參創建出的對象屬性是默認值有參創建出的…

【力扣白嫖日記】602.好友申請II:誰有最多的好友

前言 練習sql語句&#xff0c;所有題目來自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免費數據庫練習題。 今日題目&#xff1a; 602.好友申請II&#xff1a;誰有最多的好友 表&#xff1a;RequestAccepted 列名類型requester_idintaccept…

外賣店優先級

題目描述 ”飽了么”外賣系統中維護著N 家外賣店&#xff0c;編號1~N。每家外賣店都有一個優先級&#xff0c;初始時(0時刻)優先級都為0。 每經過1個時間單位&#xff0c;如果外賣店沒有訂單&#xff0c;則優先級會減少1&#xff0c;最低減到0;而如果外賣店有訂單&#xff0c;則…

【AIGC】微笑的秘密花園:紅玫瑰與少女的美好相遇

在這個迷人的畫面中&#xff0c;我們目睹了一個迷人的時刻&#xff0c;女子則擁有一頭柔順亮麗的秀發&#xff0c;明亮的眼睛如同星河般璀璨&#xff0c;優雅而靈動&#xff0c;她的微笑如春日暖陽&#xff0c;溫暖而又迷人。站在紅玫瑰花瓣的驚人洪水中。 在一片湛藍無云的晴…

Liberod的License申請

Liberod的License申請 找到license申請的路徑 查找C盤的磁盤序列號 鍵盤的win+R,輸入cmd 輸入vol,然后回車 圖中的DiskID就是填寫你C盤序列號的位置,填寫完成后點擊Register,幾秒鐘后會提示你,預計45分鐘后會發送到你的郵箱

docker-mysql:5.7安裝

1、下載mysql:5.7鏡像 [rootlocalhost ~]# docker search mysql (某個XXX鏡像名字) [rootlocalhost ~]# docker pull mysql:5.7 按裝之前查看一下是否按裝過mysql。如果安裝過會占用3306端口。 [rootlocalhost ~]# ps -ef | grep mysql 2、安裝 # -d&#xff1a;后臺運行 #…

C語言基礎(五)——結構體與C++引用

七、結構體與C引用 7.1 結構體的定義、初始化、結構體數組 C 語言提供結構體來管理不同類型的數據組合。通過將不同類型的數據組合成一個整體&#xff0c;方便引用 例如&#xff0c;一名學生有學號、姓 名、性別、年齡、地址等屬性&#xff0c;如果針對學生的學號、姓名、年齡…

MJ V7 在 V6 Beta 發布后即將推出,即將到來的人工智能 API 訪問!

讓我們深入了解 MidJourney 的新功能 在發布官方 Beta 之前總結 V6 Alpha 隨著 MidJourney V6 Alpha 上周成為默認版本&#xff0c;該團隊現在正在努力在過渡到官方 Beta 版本之前進行進一步的改進&#xff1a; 一組 3 個視覺一致性功能 1 — 升級的“風格參考”功能 這將是…

團體程序設計天梯賽 L2-003 月餅(多重背包模板)

L2-003 月餅 分數 25 月餅是中國人在中秋佳節時吃的一種傳統食品&#xff0c;不同地區有許多不同風味的月餅。現給定所有種類月餅的庫存量、總售價、以及市場的最大需求量&#xff0c;請你計算可以獲得的最大收益是多少。 注意&#xff1a;銷售時允許取出一部分庫存。樣例給…

pytorch基礎1-pytorch介紹與張量操作

專題鏈接&#xff1a;https://blog.csdn.net/qq_33345365/category_12591348.html 本教程翻譯自微軟教程&#xff1a;https://learn.microsoft.com/en-us/training/paths/pytorch-fundamentals/ 初次編輯&#xff1a;2024/3/1&#xff1b;最后編輯&#xff1a;2024/3/1 這是本…

高中數學:分式函數值域的求法

一、求值域的兩種基本思路 1、根據函數圖像和定義域求出值域。 難點&#xff1a;畫出函數圖像 2、研究函數單調性和定義域求出值域。 二、函數圖像畫法 高中所學的分式函數&#xff0c;基本由反比例函數平移得到。 復雜分式函數圖像畫法的兩個要點&#xff1a; a、找垂直、…

mysql 常用命令練習

管理表格從表中查詢數據從多個表查詢修改數據sql變量類型 管理表格 創建一個包含三列的新表 CREATE TABLE products (id INT,name VARCHAR(255) NOT NULL,price INT DEFAULT 0,PRIMARY KEY(id) // 自增 ); 從數據庫中刪除表 DROP TABLE product; 向表中添加新列 ALTER TAB…

如何優化阿里云幻獸帕魯/Palworld的多人聯機性能,并避免內存溢出導致的異常退出游戲?

優化阿里云幻獸帕魯/Palworld的多人聯機性能并避免內存溢出導致的異常退出游戲&#xff0c;可以采取以下幾種方法&#xff1a; 選擇合適的內存配置&#xff1a;由于幻獸帕魯是一個對內存需求較高的游戲&#xff0c;建議選擇至少16GB的內存。對于不同的玩家數量&#xff0c;可以…

【ArcGIS】漁網分割提取柵格圖+網格化分析圖繪制

ArcGIS按漁網分割提取柵格圖并繪制網格化分析圖 準備數據操作步驟步驟1&#xff1a;創建漁網&#xff08;Create Fishnet&#xff09;步驟2&#xff1a;柵格數據處理步驟3&#xff1a;柵格插值步驟4&#xff1a;數據關聯 參考 網格化的目的是讓各個數據更加標準化的進行統計。因…