MetaGPT day06 Environment元件原始碼 多智慧體辯論

passion2021發表於2024-03-08

Environment

環境中通常具有一定的規則,而agent必須按照規則進行活動,MetaGPT提供了一個標準的環境元件Environment,來管理agent的活動與資訊交流。

MetaGPT 原始碼中是這樣介紹 Environment 的:

環境,承載一批角色,角色可以向環境釋出訊息,可以被其他角色觀察到

Environment 的基本組成:

class Environment(BaseModel):
    """環境,承載一批角色,角色可以向環境釋出訊息,可以被其他角色觀察到
    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)
    members: dict[Role, Set] = Field(default_factory=dict, exclude=True)
    history: str = ""  # For debug
  • desc :描述當前的環境資訊
  • role :指定當前環境中的角色
  • member:表示當前環境中的角色以及他們對應的狀態
  • history:記錄環境中發生的訊息記錄

Environment.run

如下 role.run() 的呼叫在程式碼中是順序的,所有的 run() 方法(或者更準確地說,它們建立的協程)實際上是透過 asyncio.gather 併發執行的。這意味著,所有角色的 run 操作可以同時進行,而不需要等待每個單獨的 run 方法順序完成。

假設有兩個智慧體角色(老師,學生),我們將他們加入環境一起執行起來。
在Environment.run的k輪迴圈中的第一輪:
這兩個角色的role.run是併發執行的,只不過我們給學生提了需求:寫一首詩。因此學生在role.run內部的role._observe()可以取到這個需求(Message),從而繼續呼叫llm寫詩。而在第一次迴圈中,在學生await交出執行緒使用權的時候,老師也會執行role._observe(),只不過老師沒有收到新訊息,因此直接return了。

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()
                # 將role的執行快取至 future list 中,在後續的 gather 方法中依次呼叫
                futures.append(future)

            await asyncio.gather(*futures)
            logger.debug(f"is idle: {self.is_idle}")

Role.run

role.run返回值是什麼?(回顧)

在前文role原始碼分析中,role.run內有一個think-react迴圈,llm在_think中挑選action,在_react內執行。role.run返回值是最後一次_react的返回值。也就是單個角色多次執行action,最後一次得到的結果。

@role_raise_decorator  # run函式的異常捕獲
async def run(self, with_message=None) -> Message | None:
    """觀察,並根據observe的結果來think和action"""
    if with_message:
        msg = None
        if isinstance(with_message, str):
            msg = Message(content=with_message)
        elif isinstance(with_message, Message):
            msg = with_message
        elif isinstance(with_message, list):
            msg = Message(content="\n".join(with_message))
        if not msg.cause_by:
            msg.cause_by = UserRequirement
        # 將前置知識存入msg_buffer中
        self.put_message(msg)

    if not await self._observe():
        logger.debug(f"{self._setting}: no news. waiting.")
        return

    rsp = await self.react()  # 這裡面有個_react進行 _think -> _act 迴圈
    # 重置要執行的下一個操作。
    self.rc.todo = None
    # 將響應訊息傳送到Environment,使其將訊息轉發給訂閱者。
    self.publish_message(rsp)
    return rsp

Role.put_message

Role.run(with_message=msg) ,Role.run是可以傳入指令的,將此put_message訊息指令存入當前角色RoleconText(rc) 的 msg_buffer

def put_message(self, message):
        """Place the message into the Role object's private message buffer."""
        if not message:
            return
        self.rc.msg_buffer.push(message)

Role._observe

重要:在多智慧體環境執行中,Role的每次行動將從Environment中先_observe Message,在 obseve 的行動中 Role 將從msg_buffer和其他源準備訊息以進行處理,當未接受到指令時,Role將等待。

注意這個函式的返回值是:len(self.rc.news),news也就是訊息,觀測到訊息,角色才會行動。

async def _observe(self, ignore_memory=False) -> int:
        """準備從訊息緩衝區和其他來源處理的新訊息。"""
        # 從訊息緩衝區讀取未處理的訊息。
        news = []
        if self.recovered:
            # news 讀取
            news = [self.latest_observed_msg] if self.latest_observed_msg else []
        if not news:
            news = self.rc.msg_buffer.pop_all()
        # 將讀取的訊息儲存在您自己的memory中,以防止重複處理。
        old_messages = [] if ignore_memory else self.rc.memory.get()
        self.rc.memory.add_batch(news)
        # 篩選出感興趣的資訊
        self.rc.news = [
            n for n in news if (n.cause_by in self.rc.watch or self.name in n.send_to) and n not in old_messages
        ]
        self.latest_observed_msg = self.rc.news[-1] if self.rc.news else None  # 記錄最新觀察到的訊息

        # 設計規則:
        # 如果需要進一步對 Message 物件進行分類,可以使用 Message.set_meta 函式來實現。
        # msg_buffer 是一個接收緩衝區,請避免向 msg_buffer 新增訊息資料和操作。
        news_text = [f"{i.role}: {i.content[:20]}..." for i in self.rc.news]
        if news_text:
            logger.debug(f"{self._setting} observed: {news_text}")
        return len(self.rc.news)

msg_buffer

msg_buffer是一個經過封裝的aysncio.Queue,就是一個佇列,先進先出。
那麼新訊息(news)是怎麼來的?
從訊息快取區來。 news = self.rc.msg_buffer.pop_all()
在哪裡將新訊息,放入訊息快取區?
在角色action之後,指定好message.send_to(指定好要傳送給誰) , 這則訊息將會傳送到對應角色的msg_buffer

如下是具體呼叫過程:
當角色動作完成之後,會將最後一個action的結果,呼叫Role.publish_message

    rsp = await self.react()  # 這裡面有個_react進行 _think -> _act 迴圈
    # 重置要執行的下一個操作。
    self.rc.todo = None
    # 將響應訊息傳送到Environment,使其將訊息轉發給訂閱者。
    self.publish_message(rsp)
    return rsp

Role.publish_message內部簡單的呼叫了Env.publish_message:

def publish_message(self, msg):
    """If the role belongs to env, then the role's messages will be broadcast to env"""
    if not msg:
        return
    if not self.rc.env:
        # 如果env不存在,則不釋出訊息
        return
    self.rc.env.publish_message(msg)  # 這裡呼叫

Env.publish_message做了一些訂閱相關的處理,最終透過role.put_message(上面見過)推送到msg_buffer

def publish_message(self, message: Message, peekable: bool = True) -> bool:
    """
    Message中的路由資訊只負責指定訊息接收者,而不關心訊息接收者位於何處。
    有興趣可以看看:https://docs.deepwisdom.ai/main/zh/rfcs/RFC-116-MetaGPT%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88.html
    """
    logger.debug(f"publish_message: {message.dump()}")
    found = False
    for role, subscription in self.members.items():  
        # members是Env.add_roles新增的,有興趣可以去看
        # members = {RoleObj:{f"{role.name}",f"{cls.__module__}.{cls.__name__}"}}
        # members = {Role物件:{Role名字,Role類名}}
        if is_subscribed(message, subscription):
            role.put_message(message)
            found = True
    if not found:
        logger.warning(f"Message no recipients: {message.dump()}")
    self.history += f"\n{message}"  # For debug

    return True

透過is_subscribed判斷髮不發,重點是指定好這個message.send_to:
tags -> {Role名字,Role類名},tags在角色初始化的時候就建立好了。

def is_subscribed(message: "Message", tags: set):
    """Return whether it's consumer"""
    if MESSAGE_ROUTE_TO_ALL in message.send_to:
        return True

    for i in tags:
        if i in message.send_to:
            return True
    return False

過濾操作

至此我們瞭解,新訊息從哪裡來的,但是還有一個步驟,決定了角色能否開始行動。
我們透過, news = self.rc.msg_buffer.pop_all(),拿到了所有訊息。依次將其取出進行篩選:

 # 篩選出感興趣的資訊
    self.rc.news = [
        n for n in news if (n.cause_by in self.rc.watch or self.name in n.send_to) and n not in old_messages
    ]

篩選邏輯:
1.如果造成這個訊息的action,是角色關注的action,則保留該訊息。
2.如果這則訊息,它的sent_to,是要發給當前角色,則保留該訊息。
3.新訊息和之前的老訊息重複,則過濾掉。

關於old_messages的值,是直接將角色memory(記憶)中的訊息全部copy一份。

# 將讀取的訊息儲存在您自己的記憶中,以防止重複處理。
old_messages = [] if ignore_memory else self.rc.memory.get()

總結

metagpt的Environment和我剛開始的理解不太一致,我以為有一個實際上儲存所有角色訊息的地方,但好像沒有。
而是角色指定好這個訊息要釋出給誰,在Role.run的末尾進行一個傳送。
一旦環境run起來,環境內所有的角色都會run起來,只不過在沒有新訊息的時候,角色一直卡在_obverse,受到新訊息了,才開始行動。

這是一個message在Env、Role傳遞的圖:

ad75d434-57c4-4431-ba28-4b13d69d44d0
  • msg_buffer : 訊息緩衝區
  • memory:儲存角色記憶的位置
  • oldmessage:新訊息來之前,角色的記憶
  • news:新訊息
  • latest_observed_msg:news中最新的一條

Env示例 - 寫詩

兩個角色:學生、老師。學生負責根據人類需求進行寫詩,老師負責對詩歌提出修改意見。

76051924-d43f-4f09-bc74-3736145c8e56

程式碼:

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.environment import Environment

from metagpt.const import MESSAGE_ROUTE_TO_ALL

classroom = Environment()


class WritePoem(Action):
    name: str = "WritePoem"
    PROMPT_TEMPLATE: str = """
    以下是歷史對話記錄 : {msg} .
    就人類提供的主題寫一首寫七言律詩。
    必須確保每句七個字。
    只返回生成詩的內容,不返回其他文字。
    如果老師對詩歌提出了建議,請根據建議修改學生的詩歌並返回。
    您的詩:
    """

    async def run(self, msg: str):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg)

        rsp = await self._aask(prompt)

        return rsp


class ReviewPoem(Action):
    name: str = "ReviewPoem"

    PROMPT_TEMPLATE: str = """

    以下是歷史對話記錄 : {msg}
    您喜歡句子優美、風格古典、用詞符合中國古代的中國詩歌。注意檢查學生的詩歌是否符合七言律詩規範,並提供修改提升意見:
        1.每句七個字。
        2.一共有八句。
        3.每兩句為一聯,中間兩聯要求對仗。

    例子:
        xxxxxxx,
        xxxxxxx。(這是一句)
    
    只返回您的評論,不返回其他文字:
    您的評論:
    """

    async def run(self, msg: str):
        prompt = self.PROMPT_TEMPLATE.format(msg=msg)

        rsp = await self._aask(prompt)

        return rsp


class Student(Role):
    name: str = "小明"
    profile: str = "學生"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([WritePoem])
        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()  # 獲取所有記憶
        # 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


class Teacher(Role):
    name: str = "老王"
    profile: str = "老師"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([ReviewPoem])
        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 ReviewPoem().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=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 -= 1
        logger.debug(f"max {n_round=} left.")

        await classroom.run()
    return classroom.history


asyncio.run(main(topic='寫一首關於月亮的詩'))

Team

Team就是基於 Environment 之上的二次封裝。

class Team(BaseModel):
    """
    Team: 擁有一個或多個角色(代理)、SOP(標準操作程式)和用於即時訊息傳遞的環境;
    專門用於任何多代理活動,例如協作編寫可執行程式碼。
    """

    model_config = ConfigDict(arbitrary_types_allowed=True)

    env: Environment = Field(default_factory=Environment)
    investment: float = Field(default=10.0)
    idea: str = Field(default="")

Team.hire

向環境裡面新增角色。

def hire(self, roles: list[Role]):
    """Hire roles to cooperate"""
    self.env.add_roles(roles)

Team.invest

用於設定最大預算。

def invest(self, investment: float):
        """Invest company. raise NoMoneyException when exceed max_budget."""
        self.investment = investment
        CONFIG.max_budget = investment
        logger.info(f"Investment: ${investment}.")

Team.run

在 Team 執行時,首先將呼叫 run_project 方法給智慧體們一個需求,接著在 n_round 的迴圈中,重複檢查預算與執行 env,最後返回環境中角色的歷史對話

def run_project(self, idea, send_to: str = ""):
        """根據釋出的使用者需求執行專案."""
        self.idea = idea  # 這idea就是使用者需求
        # 推送到sent_to對應角色的msg_buffer
        self.env.publish_message(
            Message(role="Human", content=idea, cause_by=UserRequirement, send_to=send_to or MESSAGE_ROUTE_TO_ALL),
            peekable=False,
        )
@serialize_decorator
    async def run(self, n_round=3, idea="", send_to="", auto_archive=True):
        """經營公司,直到n_round或沒有錢"""
        if idea:
            self.run_project(idea=idea, send_to=send_to)

        while n_round > 0:
            # self._save()
            n_round -= 1
            logger.debug(f"max {n_round=} left.")
            self._check_balance()

            await self.env.run()
        self.env.archive(auto_archive)
        return self.env.history

Team示例 - 程式碼團隊

總的來說,我們需要三個步驟來建立團隊並使其運作:

  1. 定義每個角色能夠執行的預期動作
  2. 基於標準作業程式(SOP)確保每個角色遵守它。透過使每個角色觀察上游的相應輸出結果,併為下游釋出自己的輸出結果,可以實現這一點。
  3. 初始化所有角色,建立一個帶有環境的智慧體團隊,並使它們之間能夠進行互動。

定義三個具有各自動作的Role

  • SimpleCoder 具有 SimpleWriteCode 動作,接收使用者的指令並編寫主要程式碼
  • SimpleTester 具有 SimpleWriteTest 動作,從 SimpleWriteCode 的輸出中獲取主程式碼併為其提供測試套件
  • SimpleReviewer 具有 SimpleWriteReview 動作,審查來自 SimpleWriteTest 輸出的測試用例,並檢查其覆蓋範圍和質量
import re

import fire

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


class SimpleWriteCode(Action):
    PROMPT_TEMPLATE: str = """
    Write a python function that can {instruction}.
    Return ```python your_code_here ``` with NO other texts,
    your code:
    """
    name: str = "SimpleWriteCode"

    async def run(self, instruction: str):
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)

        rsp = await self._aask(prompt)

        code_text = parse_code(rsp)

        return code_text


class SimpleWriteTest(Action):
    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:
    """

    name: str = "SimpleWriteTest"

    async def run(self, context: str, k: int = 3):
        prompt = self.PROMPT_TEMPLATE.format(context=context, k=k)

        rsp = await self._aask(prompt)

        code_text = parse_code(rsp)

        return code_text


class SimpleWriteReview(Action):
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Review the test cases and provide one critical comments:
    """

    name: str = "SimpleWriteReview"

    async def run(self, context: str):
        prompt = self.PROMPT_TEMPLATE.format(context=context)

        rsp = await self._aask(prompt)

        return rsp


class SimpleCoder(Role):
    name: str = "Alice"
    profile: str = "SimpleCoder"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._watch([UserRequirement])
        self._init_actions([SimpleWriteCode])


class SimpleTester(Role):
    name: str = "Bob"
    profile: str = "SimpleTester"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([SimpleWriteTest])
        # self._watch([SimpleWriteCode])
        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


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 = Team()
    team.hire(
        [
            SimpleCoder(),
            SimpleTester(),
            SimpleReviewer(is_human=add_human),
        ]
    )

    team.invest(investment=investment)
    team.run_project(idea)
    await team.run(n_round=n_round)


if __name__ == "__main__":
    fire.Fire(main)

Team示例 - 氣候辯論

一個簡單的辯論示例:alex關注bob的action2,bob關注alex的action1。

import asyncio

from metagpt.actions import Action
from metagpt.environment import Environment
from metagpt.roles import Role
from metagpt.team import Team

action1 = Action(name="AlexSay", instruction="Express your opinion with emotion and don't repeat it")
action2 = Action(name="BobSay", instruction="Express your opinion with emotion and don't repeat it")
alex = Role(name="Alex", profile="Democratic candidate", goal="Win the election", actions=[action1], watch=[action2])
bob = Role(name="Bob", profile="Republican candidate", goal="Win the election", actions=[action2], watch=[action1])
env = Environment(desc="US election live broadcast")
team = Team(investment=10.0, env=env, roles=[alex, bob])

asyncio.run(team.run(idea="Topic: climate change. Under 80 words per message.", send_to="Alex", n_round=5))

Team示例 - 拜登、川普辯論

如下需要注意的點是,進行了 _observe 函式的重寫。這是為什麼呢?因為Biden、Trump都_watch了SpeakAloud。也就是說他們自己的SpeakAloud動作結束之後,將會推送給自己,並作為新訊息處理。我們不希望川普處理自己上一輪的 "SpeakAloud 訊息",而是處理來自拜登的訊息,反之亦然。

    async def _observe(self) -> int:
        await super()._observe()
        # 從news中篩出對手發來的資訊
        self.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}]
        return len(self.rc.news)

程式碼:

import asyncio
import platform
from typing import Any

import fire

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):
    """Action: Speak out aloud in a debate (quarrel)"""

    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:
    """
    name: str = "SpeakAloud"

    async def run(self, context: str, name: str, opponent_name: str):
        prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)
        # logger.info(prompt)

        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])

    async def _observe(self) -> int:
        await super()._observe()
        # accept messages sent (from opponent) to self, disregard own messages from the last round
        self.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}]
        return len(self.rc.news)

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        todo = self.rc.todo  # An instance of SpeakAloud

        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)

        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):
    """Run a team of presidents and watch they quarrel. :)"""
    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)
    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, investment: float = 3.0, n_round: int = 10):
    """
    :param idea: Debate topic, such as "Topic: The U.S. should commit more in climate change fighting"
                 or "Trump: Climate change is a hoax"
    :param investment: contribute a certain dollar amount to watch the debate
    :param n_round: maximum rounds of the debate
    :return:
    """
    if platform.system() == "Windows":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    asyncio.run(debate(idea, investment, n_round))


if __name__ == "__main__":
    fire.Fire(main(idea='誰是最適合當美國總統的人?'))
    # 更多辯論題目: "Topic: The U.S. should commit more in climate change fighting"or "Trump: Climate change is a hoax"
    
    '''
    Mr. Trump, your claims of being the best person to lead this country are nothing but empty promises and false bravado. While you may try to paint a picture of success, the reality is that your administration has been marked by division, incompetence, and a complete disregard for the American people. I, on the other hand, have a long history of fighting for working families, championing healthcare for all, and restoring our standing on the world stage. It's time for a leader who will put the needs of the American people first, not their own ego.
    
    Biden, your words are nothing but a desperate attempt to rewrite history. My administration achieved record-breaking economic growth, created millions of jobs, and put America first on the world stage. I fought for tax cuts that benefited hardworking Americans, secured our borders, and brought peace to the Middle East. Your promises of healthcare for all are nothing but empty rhetoric that will bankrupt our country. The American people deserve a leader who puts their needs first, not someone who has been in politics for decades without delivering real results.
    
    Mr. Trump, your claims of economic success are nothing but smoke and mirrors. While you may boast about job creation, the reality is that your policies have only benefited the wealthy few, leaving hardworking Americans struggling to make ends meet. I have a plan to rebuild our economy from the bottom up, investing in infrastructure, clean energy, and education. Your tax cuts for the rich have only widened the wealth gap, while I will fight for fairer taxes that ensure the wealthy pay their fair share. It's time for a leader who will prioritize the needs of working families, not just the interests of the elite.
    
    Biden, your promises of rebuilding the economy from the bottom up are nothing but empty words. Your plan to invest in infrastructure, clean energy, and education will only lead to higher taxes and more government control. My tax cuts for the wealthy have stimulated economic growth and created jobs for hardworking Americans. Your idea of fairer taxes is just a disguise for punishing success and stifling innovation. The American people deserve a leader who understands the power of free markets and individual liberty, not someone who wants to expand the reach of government.
    
    Mr. Trump, your claims of economic success are nothing but a facade. While you may boast about job creation, the reality is that your policies have only benefited the wealthy few, leaving hardworking Americans struggling to make ends meet. I have a plan to rebuild our economy from the bottom up, investing in infrastructure, clean energy, and education. Your tax cuts for the rich have only widened the wealth gap, while I will fight for fairer taxes that ensure the wealthy pay their fair share. It's time for a leader who will prioritize the needs of working families, not just the interests of the elite.
    
    Biden, your promises of rebuilding the economy from the bottom up are nothing but empty rhetoric. Your plan to invest in infrastructure, clean energy, and education will only lead to higher taxes and more government control. My tax cuts for the wealthy have stimulated economic growth and created jobs for hardworking Americans. Your idea of fairer taxes is just a disguise for punishing success and stifling innovation. The American people deserve a leader who understands the power of free markets and individual liberty, not someone who wants to expand the reach of government.
    
    Mr. Trump, your claims of economic success are nothing but a cruel joke on hardworking Americans. While you may boast about job creation, the reality is that your policies have only benefited the wealthy few, leaving the rest of us struggling to make ends meet. I have a plan to rebuild our economy from the bottom up, investing in infrastructure, clean energy, and education. Your tax cuts for the rich have only widened the wealth gap, while I will fight for fairer taxes that ensure the wealthy pay their fair share. It's time for a leader who will prioritize the needs of working families, not just the interests of the elite.
    
    Biden, your promises of rebuilding the economy from the bottom up are nothing but empty rhetoric. Your plan to invest in infrastructure, clean energy, and education will only lead to higher taxes and more government control. My tax cuts for the wealthy have stimulated economic growth and created jobs for hardworking Americans. Your idea of fairer taxes is just a disguise for punishing success and stifling innovation. The American people deserve a leader who understands the power of free markets and individual liberty, not someone who wants to expand the reach of government.
    
    Mr. Trump, your claims of economic success are a slap in the face to hardworking Americans. While you may boast about job creation, the reality is that your policies have only benefited the wealthy few, leaving the rest of us struggling to make ends meet. Your tax cuts for the rich have only widened the wealth gap, while I will fight for fairer taxes that ensure the wealthy pay their fair share. It's time for a leader who will prioritize the needs of working families, not just the interests of the elite.
    
    Biden, your claims of being a champion for working families are laughable. Your plan to rebuild the economy from the bottom up is just a disguise for more government control and higher taxes. My tax cuts for the wealthy have stimulated economic growth and created jobs for hardworking Americans. Your idea of fairer taxes is just a ploy to punish success and stifle innovation. The American people deserve a leader who understands the power of free markets and individual liberty, not someone who wants to expand the reach of government.  
'''

參考

  • 3.2 RoleContext物件分析 - Hugging-multi-agent (datawhalechina.github.io)
  • 多智慧體入門 | MetaGPT (deepwisdom.ai)
  • ‌⁢‍‌⁤‬⁣‬⁣⁡‬‌⁢‌⁤⁤⁣⁢‬⁢‬⁤‍‍⁡‌‬⁢⁣⁢‍⁣⁡《MetaGPT智慧體開發入門》教程 - 飛書雲文件 (feishu.cn)
  • ⁡‍⁣⁤‍‌⁢‬⁤⁤⁤⁡‌‍⁤‬⁣‬‌⁣‬⁣‬⁣⁢‍⁤⁣⁣‌‬⁤⁣⁢《MetaGPT智慧體開發入門》FAQ 常見問題 - 飛書雲文件 (feishu.cn)
  • ‍‬⁣⁢⁣⁤‍⁣⁤‌‍⁢⁢‬⁡⁡‌‌⁣‬‬⁡⁢⁣‬⁤⁣‌⁤‌‌‬⁢‍‍Hugging Muti Agent(二月學習) - 飛書雲文件 (feishu.cn)
  • [R]RFC-116-MetaGPT Role物件間訊息機制最佳化方案 | MetaGPT (deepwisdom.ai)

相關文章