MetaGPT-Agent相关代码分析

Agent

参考资料:智能体入门 | MetaGPT

组成

Agent = LLM + Observation + Thought + Action + Memory

-大语言模型(LLM):LLM作为智能体的“大脑”部分,使其能够处理信息,从交互中学习,做出决策并执行行动。

-观察:这是智能体的感知机制,使其能够感知其环境。智能体可能会接收来自另一个智能体的文本消息、来自监视摄像头的视觉数据或来自客户服务录音的音频等一系列信号。这些观察构成了所有后续行动的基础。

-思考:思考过程涉及分析观察结果和记忆内容并考虑可能的行动。这是智能体内部的决策过程,其可能由LLM进行驱动。

-行动:这些是智能体对其思考和观察的显式响应。行动可以是利用 LLM 生成代码,或是手动预定义的操作,如阅读本地文件。此外,智能体还可以执行使用工具的操作,智能体还可以执行使用工具的操作,包括在互联网上搜索天气,使用计算器进行数学计算等。

-记忆:智能体的记忆存储过去的经验。这对学习至关重要,因为它允许智能体参考先前的结果并据此调整未来的行动。

多Agent组成

多智能体 = Agents + Environment + SOP + Communication + Economy

这些组件各自发挥着重要的作用:

Agents:在上面单独定义的基础上,在多智能体系统中的智能体协同工作,每个智能体都具备独特有的LLM、观察、思考、行动和记忆。

Environment:环境是智能体生存和互动的公共场所。智能体从环境中观察到重要信息,并发布行动的输出结果以供其他智能体使用。

SOP:这些是管理智能体行动和交互的既定程序,确保系统内部的有序和高效运作。例如,在汽车制造的SOP中,一个智能体焊接汽车零件,而另一个安装电缆,保持装配线的有序运作。

Communication:通信是智能体之间信息交流的过程。它对于系统内的协作、谈判和竞争至关重要。

Economy:这指的是多智能体环境中的价值交换系统,决定资源分配和任务优先级。

案例:

-在环境中,存在三个智能体Alice、Bob和Charlie,它们相互作用。

-他们可以将消息或行动的输出结果发布到环境中,同时也会被其他智能体观察到。

-下面将揭示智能体Charlie的内部过程,该过程同样适用于Alice和Bob。

-在内部,智能体Charlie具备我们上述所介绍的部分组件,如LLM、观察、思考、行动。

-Charlie思考和行动的过程可以由LLM驱动,并且还能在行动的过程中使用工具。

-Charlie观察来自Alice的相关文件和来自Bob的需求,获取有帮助的记忆,思考如何编写代码,执行写代码的行动,最终发布结果。

-Charlie通过将结果发布到环境中以通知Bob。Bob在接收后回复了一句赞美的话。

Agent案例

1使用现成agent

预期

让agent写一个产品需求文档

代码

代码分析

关键类
Role

Role 类是智能体的逻辑抽象。

一个 Role 能执行特定的 Action,拥有记忆、思考并采用各种策略行动。基本上,它充当一个将所有这些组件联系在一起的凝聚实体。

有以下几个属性(具体作用后面会分析):

RoleContext

Role的运行时上下文

拥有environment,memory,state,todo等等

有以下几个属性(具体作用后面会分析):

Action

Action 是动作的逻辑抽象,里面有一个run方法,指具体执行的Action的动作(由于Action是抽象类,所以没有定义具体的Action)

有以下几个属性(具体作用后面会分析):

Environment

环境,承载一批角色,角色可以向环境发布消息,可以被其他角色观察到

有以下几个属性(具体作用后面会分析):

关键代码

1)role = ProductManager()

ProductManager初始化:

①调用父类Role初始化函数,创建RoleSetting,创建LLM,创建RoleContext

②初始化actions和states(图里是WritePRD构成的单链表)

a. 将actions放到自己的属性里

b. 以key-value对的形式,将actions里的index与action放到states里面

③添加watch Actions,监听对应的Action

2)result = await role.run(msg)

①将msg存到memory中

②执行react,获取response

关键变量:state当前的状态,todo需要完成的任务

a.开启循环,当react循环次数达到上限,或者todo为空时,退出循环

b._think:根据角色对应的prompt前缀,以及memory,让大模型给出下一步的Action,并且更新state和todo

构建prompt并发给LLM获取答案:

将RoleContext中的history,在init_actions时构建的states(即Index-Action对)和但当前的状态state等信息发给LLM,让LLM给出下一个state(也即Actions所对应的index)

根据LLM的答案,更新state和todo

c._act:根据todo和memory,执行action,得到结果后,更新msg,更新todo,更新memory

具体action:

其中,prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING, format=format)这句,会调用llm_api去询问大模型,得到结果

将结果添加到RoleContext的memory中

d.更新token,循环次数等等

e.进入下一次循环

f.当循环退出时,返回rsp

③将rsp发布到RoleContext的environment中

将msg广播到environment

放到消息队列里面,并且添加到environment的memory中

总结完整调用逻辑

1初始化Role

1.1创建RoleContext等

1.2初始化actions,初始化states

1.3添加Action监听

2 role.run()

2.1将msg存到memory中

2.2执行react

2.2.1开启循环,当react循环次数达到上限,或者todo为空时,推出循环

2.2.2_think:根据角色对应的prompt前缀,以及memory,让大模型给出下一步的Action,并且更新state和todo

将RoleContext中的history,在init_actions时构建的states(即Index-Action对)和但当前的状态state等信息发给LLM,让LLM给出下一个state(也即Actions所对应的index)

根据LLM的答案,更新state和todo

2.2.3_act:根据todo和memory,执行action,得到结果后,更新msg,更新todo,更新memory

将结果添加到RoleContext的memory中

2.2.4更新token,循环次数等等

2.2.5进入下一次循环

2.2.6当循环退出时,返回rsp

2.3将rsp发布到RoleContext的environment中

2自定义单一动作的Agent

预期

用自然语言编写代码,并想让一个agent(假设为SimpleCoder)为我们做这件事。

代码

1)定义一个编写代码的动作

2)为智能体配备这个动作

定义Action实现类SimpleWriteCode

定义Role的实现类SimpleCoder

在这个示例中,我们创建了一个 SimpleCoder,它能够根据人类的自然语言描述编写代码。

步骤如下:

1)我们为其指定一个名称和配置文件。

2)我们使用 self._init_action 函数为其配备期望的动作 SimpleWriteCode。

3)我们覆盖 _act 函数,其中包含智能体具体行动逻辑。我们写入,我们的智能体将从最新的记忆中获取人类指令,运行配备的动作,MetaGPT将其作为待办事项 (self._rc.todo) 在幕后处理,最后返回一个完整的消息。

运行Role

代码分析

关键类

和上面1使用现成agent的流程一样

关键代码
1)role = SimpleCoder()(原理和上面1使用现成agent的流程一样)

①初始化基本信息,创建RoleContext等

②初始化Actions和states

2)result = await role.run(msg)

逻辑基本和上面的run一样,但是在_react中,调用_act方法时,会调用我们覆盖后的方法,如下图:

通过SimpleWriteCode().run(),可以调用到SimpleWriteCode这个Action的run方法,它会向LLM提出要求,写python代码,并用正则匹配提取出来

拿到code_text后,构建msg,并返回给外层

总结完整调用逻辑

和上面1使用现成agent的流程序列基本一样,区别仅仅在于Role的_act()和Action的run()

3自定义多动作的Agent

预期

假设现在我们不仅希望用自然语言编写代码,而且还希望生成的代码立即执行。

一个拥有多个动作的智能体可以满足我们的需求。让我们称之为RunnableCoder,一个既写代码又立即运行的Role。

我们需要两个Action:SimpleWriteCode 和 SimpleRunCode

代码

定义Action实现类SimpleWriteCode

直接调用命令行去执行python代码

定义Role实现类RunnableCoder

和上面2定义单一动作的智能体差不多

①构造函数

用 self._init_actions 初始化所有 Action

指定每次 Role 会选择哪个 Action。我们将 react_mode 设置为 "by_order",这意味着 Role 将按照 self._init_actions 中指定的顺序执行其能够执行的 Action。在这种情况下,当 Role 执行 _act 时,self._rc.todo 将首先是 SimpleWriteCode,然后是 SimpleRunCode。

②覆盖 _act 函数

Role 从上一轮的人类输入或动作输出中检索消息,用适当的 Message 内容提供当前的 Action (self._rc.todo),最后返回由当前 Action 输出组成的 Message。

运行Role

代码分析

关键类

和上面1使用现成agent的流程一样

关键代码
1)role = RunnableCoder()

RunnableCoder初始化:

①调用父类Role初始化函数,创建RoleSetting,创建LLM,创建RoleContext

②初始化actions和states

③设置react_mode

React_mode有3中模式,具体的描述如下

这里设置成by_order,也就是让RunnableCoder按照init_actions的顺序执行(也就是先执行SimpleWriteCode,后执行SimpleRunCode)

2)result = await role.run(msg)

框架和上面使用现成agent一样

不同之处在于:

在role执行react的时候,由于react_mode是by_order,这里会执行_act_by_order(),如下图

其中order的执行逻辑如下图:

也即按照init_actions的顺序进行_act,注意这里的_set_state,会把rc.todo设置为当前遍历的action

而_act的逻辑如下:

在其中会执行todo.run,也就是action的run方法

也就是会依次执行SimpleWriteCode, SimpleRunCode

执行完成后,返回rsp

总结完整调用逻辑

和上面1使用现成agent基本一样,区别仅仅在于react_mode不同,进而act的方式不同

4多agent

预期

创建一名编码人员,一名测试人员,一名审阅人员,来组成一个团队,完成从编码到测试到审阅的过程。

具体的角色与其对应的职责如下:

  • SimpleCoder:具有 SimpleWriteCode 动作,接收用户的指令并编写主要代码
  • SimpleTester 具有 SimpleWriteTest 动作,从 SimpleWriteCode 的输出中获取主代码并为其提供测试套件
  • SimpleReviewer 具有 SimpleWriteReview 动作,审查来自 SimpleWriteTest 输出的测试用例,并检查其覆盖范围和质量

代码

定义Action和Role
定义Action

①Action1:写代码

②Action2:根据代码写k条测试用例

③根据代码和测试用例进行整体评估

定义Role

①SimpleCoder:和自定义单一动作的Agent中的Role是一样的

②SimpleTester:代码测试人员

③SimpleReviewer:代码审阅人员

构建并运行Team

现在我们已经定义了三个 Role,接着我们初始化所有角色,设置一个 Team,并hire 它们。构建完成之后,运行team。

代码分析

关键类
Team

拥有一个或多个Role(也就是agent),SOP和消息平台的类。专门用于执行多agent的活动。

有以下几个属性(具体作用后面会分析):

关键代码
1)team = Team()

创建一个team对象

注意,这里会创建一个默认的Environment对象,并赋值给environment字段

2)team.hire([SimpleCoder(),SimpleTester(),SimpleReviewer()])

Team雇佣员工来合作:

具体完成的工作有两步(具体逻辑如下):

①将role的environment设置成team中创建的environment

②将role以role.profile-role的key-value对的形式,放到team所对应的environment的roles中

3)team.invest(investment=investment)

投资investment的预算max_budget。

其作用为:当我们跑LLM时的cost超过max_budget时,抛出NoMoneyException

这里的investment为我们在main函数中设置的参数

具体的工作:

①把team的investment设置成investment

②把config的最大预算也设置成investment

4)team.start_project(idea)

①设置ieda,这里的idea和investment一样,来自于main函数中的参数

②将idea作为content,把cause_by的Action设置成BossRequirement,构建Message,并发布到environment中

注意这里message会被存在两个集合中,一个是list格式的storage,另一个是dict格式的index(用来根据cause_by来检索message)

5)await team.run(n_round=n_round)

①进入循环

②self._check_balance()检查余额,判断当前总的花费是否超过我们的最大预算,如果超过,就抛出异常

③执行self.environment.run()

遍历所有的role,并发执行其中的run方法,并且用futures收集这部分异步任务的结果,等最后所有的role都执行完成之后,返回

由于environment.run()的run()方法

④当environment.run()执行完成后,进入下一次循环退出循环

④返回environment中的history

role.run()和多agent协作的解析

我们有三个角色:

SimpleCoder会执行SimpleWriteCode的Action,且监视着BossRequirement

SimpleTester会执行SimpleWriteTest的Action,且监视着SimpleWriteCode

SimpleReviewer会执行SimpleWriteReview的Action,且监视着SimpleWriteTest

也就是这个调用顺序会是SimpleCoder->SimpleTester->SimpleReviewer

接下来我们先看role的run()方法,接着,从上面第五步await team.run(n_round=n_round)中的遍历所有的role,并发执行其中的run方法这个部分,来看metaGPT是如何一步一步执行的

role.run()

由于上面第五步await team.run(n_round=n_round)中的遍历所有的role,并发执行其中的run方法这个部分,其中的run是不带任何参数的,而我们看role的run方法(如下图),如果message为None,是会走到elif not wait self._observe()这里的,这里会执行self._observe(),然后根据其结果来判断是否要执行这一个elif里面的代码块

_observe()

作用:从environment中观察,并获取重要的信息,将其添加到memory中

①env_msgs = self._rc.env.memory.get()

从environment的memory中获取所有的Message,具体参考下面代码:

②observed = self._rc.env.memory.get_by_actions(self._rc.watch)

在environment的memory的index: dict[Type[Action], list[Message]]中,获取由watch的Action所触发的Message,获取完之后,拼接在一起,形成一个list返回

③self._rc.news = self._rc.memory.find_news(observed)

找到在observered中存在,且在rc自己的memory的storage中(注意这里是rc的memory,和上面②中的不一样,不是rc的environment的memory)不存在的Message,并形成list返回,也就是找到之前没有监测到的message,也就是找到新产生的message

并且设置到当前role的context中的news变量中

④for i in env_msgs: self.recv(i)

将environment中所有的message都存到该role的memory中,也就是把environment中的memory的Message信息,添加到rc的memory中

这一步相当于是告诉role,这些message我都已经看过了,下次有类似的message直接跳过,避免产生重复的news

⑤将news的简略信息拼起来,并打印到控制台

⑥返回news的长度

elif not判断

如果没有新的news(也就是news的长度为0),那么就直接返回

如果有新的news就执行react(),并publish到environment中

rsp = await self.react()

和1使用现成agent解释的差不多

self._publish_message(rsp)

和1使用现成agent解释的差不多

多agent协作

我们按照上面分析的role.run()的代码来每个agent执行的动作

SimpleCoder

①_observe()

注意observed = self._rc.env.memory.get_by_actions(self._rc.watch)这一句

因为SimpleCoder添加watch的Action是BossRequirement,所以这一步会过滤出BossRequirement相关的message,如下图:

接着,会把这条消息Human: write a function that calculates the product of a list设置到rc的news中

然后,当运行到最后时,各个变量的状态如下(参考下面红框中的部分):

②elif not判断

由于news有1条message,所以len(self._rc.news)为1,不会进到对应的代码块里面,相应的会执行后面的react()和publish_message()

③self.react()

a._think()

由于当前role里面只配备了一个Action,所以这里直接把当前的todo设置成此Action,也就是SimpleWriteCode,设置完成之后,直接返回

b._act()

会走到我们自定义的_act方法里面去

正常情况下应该是todo.run(),但是官网代码写的是code_text = await SimpleWriteCode().run(msg.content),但是也不影响,因为在这个role里,todo只能是SimpleWriteCode或者None

c. SimpleWriteCode().run()

正常情况下,应该是执行上面这几步,去询问LLM,但我这里连不上open ai的接口,就直接写死了一段代码来测试,如下图

d.回到_act()方法中,构建message

注意content是code_text,cause_by是type(todo)

④self.publish_message(rsp)

向environment中添加消息

SimpleTester

①_observe()

因为SimpleTester添加watch的Action是SimpleWriteCode,所以这里会过滤出SimpleWriteCode相关的且以前没有处理过的新的message,并设置到rc的news中

然后,当运行到最后时,各个变量的状态如下(参考下面红框中的部分):

②elif not判断

由于news有1条message,所以len(self._rc.news)为1,不会进到对应的代码块里面,相应的会执行后面的react()和publish_message()

③self.react()

a._think()

由于当前role里面只配备了一个Action,所以这里直接把当前的todo设置成此Action,也就是SimpleWriteTest,设置完成之后,直接返回

b._act()

会走到我们自定义的_act方法里面去

c. SimpleWriteTest ().run()

正常情况下,应该是执行上面这几步,去询问LLM,但我这里连不上open ai的接口,就直接写死了一段代码来测试,如下图

d.回到_act()方法中,构建message

④self.publish_message(rsp)

向environment中添加消息

SimpleReviewer

①_observe()

因为SimpleReviewer添加watch的Action是SimpleWriteTest,所以这里会过滤出SimpleWriteTest相关的且以前没有处理过的新的message,并设置到rc的news中

然后,当运行到最后时,各个变量的状态如下(参考下面红框中的部分):

②elif not判断

由于news有1条message,所以len(self._rc.news)为1,不会进到对应的代码块里面,相应的会执行后面的react()和publish_message()

③self.react()

a._think()

由于当前role里面只配备了一个Action,所以这里直接把当前的todo设置成此Action,也就是SimpleWriteReview,设置完成之后,直接返回

b._act()

由于我们没有在SimpleReviewer中自定义_act(),因此这里会调用其父类Role的_act方法()

先获取important_memory

然后执行todo.run(),也就是其所持有的SimpleWriteReview的Action对象的run方法

c. SimpleWriteReview.run()

调用open ai的接口去询问LLM

但由于我这里连不上open ai的接口,这里出现了异常……

5人类介入

预期

在上面4多agent的代码示例中,我们想要更好的控制review的过程,我们希望自己来担任SimpleReviewer的角色

代码

在初始化SimpleReviewer时,在构造函数中加一个参数is_human=True就可以了

结果

在SimpleTester执行完之后,轮到SimpleReviewer执行动作时,系统给出了对应的context,并问我如何评价?

我输入good,程序完成,并退出。

代码分析

关键类
HumanProvider

人类将自己作为一个 "模型",而这个 "模型 "实际上将人类的输入作为自己的反应。

这样就可以在框架的任何地方用人来替代 LLM,从而引入人与人之间的互动

主要看一下aask和ask方法,如果role的is_human为True,那么其绑定的action在run时,会调用到HumanProvider的aask方法。

我们看aask方法,会直接调用到ask方法,在ask方法里,会打印传入的msg,并且会让人类在控制台输入结果返回

关键代码
1)SimpleReviewer(is_human=True)

①会先调用父类的构造函数,并把is_human传进去

②在初始化时,如果is_human为True,会创建一个HumanProvider,并赋值给self的_llm变量

③接着,我们回到SimpleReviewer的构造函数里,它会执行self._init_actions([SimpleWriteReview])

在self._init_actions([SimpleWriteReview])里,会执行到i = action("", llm=self._llm),也就是把role里的llm赋给action的llm变量(这点参考下面Action的构造函数),赋值完成之后,会把action添加到role自己的actions集合中,后面执行action的时候,会选择里面的实例

2)Action.run()

通过上面1使用现成agent、2自定义单一动作agent、3自定义多动作agent和4多agent的分析,我们知道最终调用会走到对应action的run方法里面去

也就是,对于SimpleReviewer这个角色,会走到SimpleWriteReview的run方法里面去,而在run方法里,会执行self._aask(promot)

我们继续看_aask方法,在最后,会调用self.llm.aask(prompt, system_msgs),也就是会找到当前action所持有的llm,执行aask方法

也就是会找到我们定义的HumanProvider来执行aask操作,以此来实现人工介入

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当出现 SSH Agent 失败错误代码-1 时,这表示 SSH agent 进程在启动或执行期间遇到了问题。SSH Agent 是用于管理 SSH 密钥的工具,它允许用户在登录到远程服务器时,不需要每次都输入密码。 出现这个错误可能有多种原因,下面列举一些可能的解决方案: 1. 检查 SSH agent 进程是否正在运行。可以使用以下命令检查: ``` ps -ef | grep ssh-agent ``` 如果没有找到 ssh-agent 进程,可能需要手动启动它: ``` eval $(ssh-agent) ``` 2. 检查 SSH agent 的环境变量是否正确设置。可以使用以下命令检查: ``` echo $SSH_AUTH_SOCK ``` 如果未设置或设置错误,可以手动设置它: ``` export SSH_AUTH_SOCK=/tmp/ssh-XXXXXXXXXX/agent.XXXXXXXX ``` 其中 XXXXXXXXXX 是随机生成的字符串。 3. 验证密钥是否正确添加到 SSH agent 中。可以使用以下命令列出已经添加的密钥: ``` ssh-add -l ``` 如果没有任何输出,表示没有密钥被添加到 SSH agent 中。可以使用以下命令添加密钥: ``` ssh-add ~/.ssh/id_rsa ``` 其中 ~/.ssh/id_rsa 是私钥的路径,请根据自己的情况进行替换。 4. 检查 SSH agent 的日志文件以获取更多详细信息。可以使用以下命令查看日志文件: ``` tail -f ~/.ssh/ssh-agent.log ``` 查看日志文件可以帮助确定错误的具体原因。 如果上述解决方案都没有解决问题,建议在搜索引擎或相关的技术论坛上搜索该错误代码,以获取更多的帮助和指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值