MetaGPT day06 Environment组件源码 多智能体辩论

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.  
'''

参考

posted @ 2024-03-08 17:25  passion2021  阅读(144)  评论(0编辑  收藏  举报