Datawhale二月组队学习:Hugging Multi Agent笔记Task05

1 Agent

1.0 前言

1.1 Agent体系

Agent(智能体) = 一个设置了一些目标或任务,可以迭代运行的大型语言模型。这与大型语言模型(LLM)在像ChatGPT这样的工具中“通常”的使用方式不同。在ChatGPT中,你提出一个问题并获得一个答案作为回应。而Agent拥有复杂的工作流程,模型本质上可以自我对话,而无需人类驱动每一部分的交互。 -Logan Kilpatrick,OpenAI 开发者关系负责人

在这次人工智能的浪潮中AI Agent的火花诞生于 GPT插件商城以及AutoGPT。这分别提到Agent的工具调用能力和规划能力,在 LLM 支持的自主Agent系统中,LLM 充当Agents的大脑,并辅以几个关键组成部分:

  • 规划

    • 子目标和分解:Agents将大型任务分解为更小的、可管理的子目标,从而能够有效处理复杂的任务。

    • 反思和完善:Agents可以对过去的行为进行自我批评和自我反思,从错误中吸取教训,并针对未来的步骤进行完善,从而提高最终结果的质量。

  • 记忆

    • 短期记忆:我认为所有的上下文学习(参见提示工程)都是利用模型的短期记忆来学习。

    • 长期记忆:这为Agents提供了长时间保留和回忆(无限)信息的能力,通常是通过利用外部向量存储和快速检索来实现。

  • 工具使用

    • Agents学习调用外部 API 来获取模型权重中缺失的额外信息(通常在预训练后很难更改),包括当前信息、代码执行能力、对专有信息源的访问等。

1.2 MetaGPT单智能体

在MetaGPT看来,我们把Agent想象成环境中的数字人,其中

Agent = 大语言模型(LLM) + 观察 + 思考 + 行动 + 记忆

这个公式概括了智能体的功能本质。为了理解每个组成部分,让我们将其与人类进行类比:

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

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

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

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

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

  • 一个agent在启动后他会观察自己能获取到的信息,加入自己的记忆中

  • 下一步进行思考,决定下一步的行动,也就是从Action1,Action2,Action3中选择执行的Action

  • 决定行动后,紧接着就执行对应行动,得到这个环节的结果

在MetaGPT内 Role 类是智能体的逻辑抽象。一个 Role 能执行特定的 Action,拥有记忆、思考并采用各种策略行动。基本上,它充当一个将所有这些组件联系在一起的凝聚实体。目前,让我们只关注一个执行动作的智能体,并看看如何实现一个最简单的 Agent

1.3 MetaGPT多智能体

MetaGPT是一个多智能体协作框架,将标准化操作(SOP)程序编码为提示确保解决问题时采用结构化方法。要求智能体以专家形式参与协作,并按要求生成结构化的输出,例如高质量的需求文档、架构设计图和流程图等。结构化的输出对于单个智能体即是更高层次的思维链(Chain-of-Thought),对于下游角色则是语义清晰、目标明确的上下文(Context)。通过明确定义的角色分工,复杂的工作得以分解为更小、更具体的任务。从而提升了LLMs的输出质量。

主要特点:

  • 稳定的解决方案:借助SOP,与其他 Agents 相比,MetaGPT 已被证明可以生成更一致和正确的解决方案。

  • 多样化的角色分配:为LLM分配不同角色的能力确保了解决问题的全面性。

在MetaGPT中多智能体 = 智能体+环境+SOP+评审+路由+订阅+经济

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

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

  • 标准流程(SOP):这些是管理智能体行动和交互的既定程序,确保系统内部的有序和高效运作。

  • 评审:评审是为了解决幻觉问题。人类的幻觉实际高于大语言模型,但人类已经习惯了幻觉与错误,日常中会通过大量评审来保障复杂工作每一步的可靠性。严谨有效的评审过程能将复杂工作整体的错误率降低90%

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

  • 订阅:需求说了一个制度改革或市场变化,所有人都应该关注/判断影响/修改计划

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

2 多智能体开发案例

2.1 基于Environment开发简单多智能体系统-老师指导学生写诗

根据给定的主题提供一篇优美的诗,这个多智能体系统首先接收用户的需求(写关于XX主题的诗),在系统中,当学生(agent2)关注到布置的题目后就会开始创作,当老师(agent2)发现学生写作完成后就会给学生提出意见,根据老师给出的意见,学生将修改自己的作品,直到设定循环结束。

完整代码如下:

import asyncio
from metagpt.environment import Environment
from metagpt.actions import Action, UserRequirement
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.const import MESSAGE_ROUTE_TO_ALL
from metagpt.logs import logger

# 定义classroom作为environment
classroom = Environment()

# 定义Student的Action
# 根据用户提供的主题来编写诗句,并且根据teacher的建议修改诗句
class WritePoem(Action):
    name: str = "WritePoem"
    language: str = "Chinese"
    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, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg, language=self.language)
        rsp = await self._aask(prompt=prompt)
        return rsp

# 定义Teacher的Action
# 读取student的写作内容,并且给出修改意见(可以增加老师的诗歌偏好来放大修改效果)
class ReviewPoem(Action):
    name: str = "ReviewPoem"
    language: str = "Chinese"
    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, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg, language=self.language)
        rsp = await self._aask(prompt=prompt)
        return rsp

# 定义Student角色
class Student(Role):
    name: str = "xiaoming"
    profile: str = "Student"
    language: str = "Chinese"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([WritePoem(language=self.language)])
        # 关注用户的需求和老师的指导
        self._watch([UserRequirement, ReviewPoem])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo
        msg = self.get_memories()  # 获取所有记忆
        poem_text = await todo.run(msg)
        logger.info(f'student : {poem_text}')
        msg = Message(content=poem_text, role=self.profile, cause_by=type(todo))
        return msg

# 定义Teacher角色
class Teacher(Role):
    name: str = "laowang"
    profile: str = "Teacher"
    language: str = "Chinese"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([ReviewPoem(language=self.language)])
        self._watch([WritePoem])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo
        msg = self.get_memories()  # 获取所有记忆
        poem_text = await todo.run(msg)
        logger.info(f'teacher : {poem_text}')
        msg = Message(content=poem_text, role=self.profile, cause_by=type(todo))
        return msg

async def main(topic: str, n_round=5):
    # 将Roles添加到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,)

    while n_round > 0:
        # self._save()
        n_round -= 1
        logger.debug(f"max {n_round=} left.")
        await classroom.run()
    return classroom.history

if __name__ == "__main__":
    asyncio.run(main(topic='写一首关于春天的诗', n_round=4))

运行结果(部分)如下:

2.2 基于Team开发智能体团队-代码编写、测试、评估

我们需要三个步骤来建立团队并使其运作:

  1. 定义每个角色能够执行的预期动作

  2. 基于标准作业程序(SOP)确保每个角色遵守它。通过使每个角色观察上游的相应输出结果,并为下游发布自己的输出结果,可以实现这一点。

  3. 初始化所有角色,创建一个带有环境的智能体团队,并使它们之间能够进行交互。

我们需要定义三个具有各自动作的Role

  • SimpleCoder 具有 SimpleWriteCode 动作,接收用户的指令并编写主要代码

  • SimpleTester 具有 SimpleWriteTest 动作,从 SimpleWriteCode 的输出中获取主代码并为其提供测试套件

  • SimpleReviewer 具有 SimpleWriteReview 动作,审查来自 SimpleWriteTest 输出的测试用例,并检查其覆盖范围和质量

完整代码如下:

import asyncio
import re
from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team


def parse_code(rsp):
    pattern = r"```python(.*)```"
    match = re.search(pattern, rsp, re.DOTALL)
    code_text = match.group(1) if match else rsp
    return code_text

# SimpleWriteCode 将会根据用户的需求来生成代码
class SimpleWriteCode(Action):
    name: str = "SimpleWriteCode"
    PROMPT_TEMPLATE: str = """
    Write a python function that can {instruction}.
    Return ```python your_code_here ``` with NO other texts,
    your code:
    """
    async def run(self, instruction: str, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)
        rsp = await self._aask(prompt)
        code_text = parse_code(rsp)
        return code_text

class SimpleCoder(Role):
    name: str = "Alice"
    profile: str = "SimpleCoder"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([SimpleWriteCode])
        self._watch([UserRequirement])

# SimpleWriteTest 将会对生成的代码进行测试
class SimpleWriteTest(Action):
    name: str = "SimpleWriteTest"
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Write {k} unit tests using pytest for the given function, assuming you have imported it.
    Return ```python your_code_here ``` with NO other texts,
    your code:
    """
    async def run(self, context: str, k: int = 3, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(context=context, k=k)
        rsp = await self._aask(prompt)
        code_text = parse_code(rsp)
        return code_text

class SimpleTester(Role):
    name: str = "Bob"
    profile: str = "SimpleTester"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([SimpleWriteTest])
        self._watch([SimpleWriteCode, SimpleWriteReview])  # feel free to try this too

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        todo = self.rc.todo
        # context = self.get_memories(k=1)[0].content # use the most recent memory as context
        context = self.get_memories()  # use all memories as context
        code_text = await todo.run(context, k=5)  # specify arguments
        msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
        return msg

# SimpleWriteReview 将会对测试的结果做出评价
class SimpleWriteReview(Action):
    name: str = "SimpleWriteReview"
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Review the test cases and provide one critical comments:
    """
    async def run(self, context: str, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(context=context)
        rsp = await self._aask(prompt)
        return rsp

class SimpleReviewer(Role):
    name: str = "Charlie"
    profile: str = "SimpleReviewer"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([SimpleWriteReview])
        self._watch([SimpleWriteTest])

async def main(idea: str = "write a function that calculates the product of a list",
               investment: float = 3.0, n_round: int = 5, add_human: bool = False,):
    logger.info(idea)
    # 我们初始化所有角色,设置一个Team,Hire用于在团队中添加员工
    team = Team()
    team.hire([SimpleCoder(), SimpleTester(), SimpleReviewer(is_human=add_human),])
    # invest方法负责控制预算
    team.invest(investment=investment)
    # 调用run_project 方法给智能体们一个需求,接着在n_round的循环中,重复检查预算与运行env,最后返回环境中角色的历史对话
    team.run_project(idea)
    await team.run(n_round=n_round)

if __name__ == "__main__":
    asyncio.run(main(idea='write a function that calculates the product of a list'))

运行结果(部分)如下:

2.3 开发多智能体间通信的机制-辩论

我们需要3个步骤来设定它们的辩论:

  1. 定义一个具有发言行为的辩手角色

  2. 处理辩手之间的通信,也就是让拜登听特朗普说话,反之亦然

  3. 初始化两个辩手实例,拜登和特朗普,创建一个带有环境的团队,并使它们能够相互交互

完整代码如下:

import asyncio
import platform
from typing import Any
from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team

class SpeakAloud(Action):
    name: str = "SpeakAloud"
    PROMPT_TEMPLATE: str = """
    ## BACKGROUND
    Suppose you are {name}, you are in a debate with {opponent_name}.
    ## DEBATE HISTORY
    Previous rounds:
    {context}
    ## YOUR TURN
    Now it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,
    craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:
    """
    async def run(self, context: str, name: str, opponent_name: str, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)
        rsp = await self._aask(prompt)
        return rsp

class Debator(Role):
    name: str = ""
    profile: str = ""
    opponent_name: str = ""
    def __init__(self, **data: Any):
        super().__init__(**data)
        self._init_actions([SpeakAloud])
        self._watch([UserRequirement, SpeakAloud])
    # 接下来,我们使每个辩手听取对手的论点。这通过重写 _observe 函数完成。
    # 这是一个重要的点,因为在环境中将会有来自特朗普和拜登的 "SpeakAloud 消息"(由 SpeakAloud 触发的 Message)。
    # 我们不希望特朗普处理自己上一轮的 "SpeakAloud 消息",而是处理来自拜登的消息,反之亦然。
    # (在即将到来的更新中,我们将使用一般的消息路由机制来处理这个过程。在更新后,你将不再需要执行此步骤)
    async def _observe(self) -> int:
        await super()._observe()
        # 这个过程的结果是,self.rc.news 现在只包含了发送给当前实例的消息,移除了所有其他消息,
        # 包括可能由当前实例在上一轮发送的消息(如果有的话)。这样的筛选机制对于确保实例只响应并处理针对自己的消息非常重要
        self.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}]
        return len(self.rc.news)
    # 最后,我们使每个辩手能够向对手发送反驳的论点。
    # 在这里,我们从消息历史中构建一个上下文,使 Debator 运行他拥有的 SpeakAloud 动作,
    # 并使用反驳论点内容创建一个新的 Message。请注意,我们定义每个 Debator 将把 Message 发送给他的对手。
    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        todo = self.rc.todo
        memories = self.get_memories()
        context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)
        # print(context)
        rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)
        # 这三个参数分别是形容Message的内容属性,来自于哪个action以及角色,并要发送给哪个角色。通过这样的机制可以实现相较于watch更灵活的订阅机制。
        msg = Message(content=rsp, role=self.profile, cause_by=type(todo), sent_from=self.name, send_to=self.opponent_name,)
        self.rc.memory.add(msg)
        return msg

# 实例化
async def debate(idea: str, investment: float = 3.0, n_round: int = 5):
    # 我们用Debator类实例化出Biden和Trump两个角色
    Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")
    Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")
    team = Team()
    team.hire([Biden, Trump])
    team.invest(investment)
    # 用send_to指定谁先发言
    team.run_project(idea, send_to="Biden")  # send debate topic to Biden and let him speak first
    await team.run(n_round=n_round)

def main(idea: str = "The U.S. should commit more in climate change fighting",
         investment: float = 3.0, n_round: int = 10):
    logger.info(idea)
    if platform.system() == "Windows":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    asyncio.run(debate(idea, investment, n_round))

if __name__ == "__main__":
    main(idea='The U.S. should commit more in climate change fighting', n_round=5)

运行结果(部分)如下:

2.4 开发多智能体基础作业-文字版你画我猜

要求其中含有两个agent,其中一个agent负责接收来自用户提供的物体描述并转告另一个agent,另一个agent将猜测用户给出的物体名称,两个agent将不断交互直到另一个给出正确的答案。

完整代码如下:

import asyncio
from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.const import MESSAGE_ROUTE_TO_ALL
from metagpt.environment import Environment

game = Environment()

class Relay(Action):
    name: str = "Relay"
    language: str = "Chinese"
    PROMPT_TEMPLATE: str = """
    Here is the historical conversation record : {msg} .
    Get the description provided by human to embellish slightly it and then relay it to the guesser.
    Returns only your description with NO other texts.
    your relay:
    """
    async def run(self, msg: str, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg, language=self.language)
        rsp = await self._aask(prompt)
        return rsp

class Communicator(Role):
    name: str = "xiaoming"
    profile: str = "Communicator"
    language: str = "Chinese"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([Relay(language=self.language)])
        self._watch([UserRequirement, Guess])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo
        msg = self.get_memories()  # 获取所有记忆
        game_text = await todo.run(msg)
        logger.info(f'Relay : {game_text}')
        msg = Message(content=game_text, role=self.profile, cause_by=type(todo))
        return msg

class Guess(Action):
    name: str = "Guess"
    language: str = "Chinese"
    PROMPT_TEMPLATE: str = """
    Here is the historical conversation record : {msg} .
    Get the description provided by communicator and guess the name of the description object.
    Returns only the object name of you guessed with NO other texts.
    your guess:
    """
    async def run(self, msg: str, *args, **kwargs):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg, language=self.language)
        rsp = await self._aask(prompt)
        return rsp

class Guesser(Role):
    name: str = "xiaohong"
    profile: str = "Guesser"
    language: str = "Chinese"
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([Guess(language=self.language)])
        self._watch([Relay])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo
        msg = self.get_memories()  # 获取所有记忆
        game_text = await todo.run(msg)
        logger.info(f'Guesser : {game_text}')
        msg = Message(content=game_text, role=self.profile, cause_by=type(todo))
        return msg

async def main(topic: str, n_round=3):
    game.add_roles([Communicator(), Guesser()])
    game.publish_message(Message(role="Human",
                                 content=topic,
                                 cause_by=UserRequirement,
                                 send_to='xiaoming' or MESSAGE_ROUTE_TO_ALL), peekable=False,)
    while n_round > 0:
        # self._save()
        n_round -= 1
        logger.debug(f"max {n_round=} left.")
        await game.run()
    return game.history

if __name__ == "__main__":
    asyncio.run(main(topic='两个字,每天早上升起,傍晚落下, 当它出现时会给人类带来能量', n_round=10))

运行结果(部分)如下:

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值