备注
Go to the end to download the full example code.
Multi-Agent Debate¶
Multi-Agent debate 模拟不同智能体之间的多轮讨论场景,通常包括几个 solver 和一个 aggregator。 典型情况下,solver 生成并交换他们的答案,而 aggregator 收集并总结答案。
我们实现了 EMNLP 2024 中的示例,其中两个 solver 智能体将按固定顺序讨论一个话题,根据先前的辩论历史表达他们的论点。 在每一轮中,主持人智能体将决定是否可以在当前轮获得最终的正确答案。
import asyncio
import os
from pydantic import Field, BaseModel
from agentscope.agent import ReActAgent
from agentscope.formatter import (
DashScopeMultiAgentFormatter,
)
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub
# 准备一个话题
topic = "两个圆外切且没有相对滑动。圆A的半径是圆B半径的1/3。圆A绕圆B滚动一圈回到起点。圆A总共会旋转多少次?"
# 创建两个辩论者智能体,Alice 和 Bob,他们将讨论这个话题。
def create_solver_agent(name: str) -> ReActAgent:
"""获取一个解决者智能体。"""
return ReActAgent(
name=name,
sys_prompt=f"你是一个名为 {name} 的辩论者。你好,欢迎来到"
"辩论比赛。我们的目标是找到正确答案,因此你没有必要完全同意对方"
f"的观点。辩论话题如下所述:{topic}",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
formatter=DashScopeMultiAgentFormatter(),
)
alice, bob = [create_solver_agent(name) for name in ["Alice", "Bob"]]
# 创建主持人智能体
moderator = ReActAgent(
name="Aggregator",
sys_prompt=f"""你是一个主持人。将有两个辩论者参与辩论比赛。他们将就以下话题提出观点并进行讨论:
``````
{topic}
``````
在每轮讨论结束时,你将评估辩论是否结束,以及话题正确的答案。""",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
stream=False,
),
# 使用多智能体格式化器,因为主持人将接收来自多于用户和助手的消息
formatter=DashScopeMultiAgentFormatter(),
)
# 主持人的结构化输出模型
class JudgeModel(BaseModel):
"""主持人的结构化输出模型。"""
finished: bool = Field(description="辩论是否结束。")
correct_answer: str | None = Field(
description="辩论话题的正确答案,仅当辩论结束时提供该字段。否则保留为 None。",
default=None,
)
async def run_multiagent_debate() -> None:
"""运行多智能体辩论工作流。"""
while True:
# MsgHub 中参与者的回复消息将广播给所有参与者。
async with MsgHub(participants=[alice, bob, moderator]):
await alice(
Msg(
"user",
"你是正方,请表达你的观点。",
"user",
),
)
await bob(
Msg(
"user",
"你是反方。你不同意正方的观点。请表达你的观点和理由。",
"user",
),
)
# Alice 和 Bob 不需要知道主持人的消息,所以主持人在 MsgHub 外部调用。
msg_judge = await moderator(
Msg(
"user",
"现在你已经听到了他们的辩论,现在判断辩论是否结束,以及你能得到正确答案吗?",
"user",
),
structured_model=JudgeModel,
)
if msg_judge.metadata.get("finished"):
print(
"\n辩论结束,正确答案是:",
msg_judge.metadata.get("correct_answer"),
)
break
asyncio.run(run_multiagent_debate())
Alice: 正方观点是:当圆A绕着圆B外切滚动一圈回到起点时,圆A总共会旋转3次。
理由如下:
1. 圆A的半径是圆B半径的1/3,这意味着如果圆B的半径为R,则圆A的半径为r=R/3。
2. 当圆A沿着圆B的边缘滚动一圈时,它所走过的路径长度等于圆B的周长,即2πR。
3. 由于圆A和圆B之间没有相对滑动,因此圆A在其自身中心轴上的旋转次数可以通过将其行走的距离(即圆B的周长)除以其自身的周长来计算。圆A的周长为2πr=2π(R/3)。
4. 因此,圆A旋转的次数为(2πR)/(2π(R/3)) = 3。
综上所述,当圆A绕着圆B滚动一圈回到起始位置时,圆A将会完成3次完整的自转。
Bob: 谢谢你的陈述,Alice。我理解你的计算方法,但我认为圆A在绕着圆B滚动一圈回到起点时,实际上会旋转4次而不是3次。我的理由如下:
1. **考虑相对运动**:当我们说圆A绕着圆B滚动一圈时,我们实际上是在描述一个复合运动过程。这个过程中不仅包括了圆A沿着圆B边缘的公转(即绕圆B一周),还包括了圆A自身的自转。
2. **自转与公转的关系**:当圆A完成一次完整的公转(即绕圆B一圈)时,它确实走过了等于圆B周长的距离,即2πR。但这里的关键在于,要使圆A能够恰好回到其初始位置并且没有发生任何滑动,它必须多旋转一次以补偿开始时的位置偏移。
3. **直观理解**:想象一下,如果将圆A放置于一条直线上,并让它滚动直到它的某个特定点再次接触地面。此时,圆A已经完成了整整一圈的自转。接着,如果我们再让这个圆继续滚动,直到它重新回到原来的位置,那么它还需要再额外旋转一圈才能确保那个特定点再次触地。因此,在这种情况下,总共需要两次完整的自转。
4. **数学证明**:根据上述分析,我们可以得出结论,对于圆A来说,除了因沿圆B路径行进所需的基本3圈外,还需加上为了调整到初始状态而额外增加的一圈自转。因此,总共有4圈自转。
综上所述,我认为当圆A绕着圆B滚动一圈并回到起点时,它实际上会经历4次完整自转而非3次。这其中包括了3次由于跟随圆B外围轨迹产生的自然转动,以及1次用于调整回初始相位的附加转动。
Aggregator: {
"type": "tool_use",
"name": "generate_response",
"input": {
"finished": false,
"correct_answer": null
},
"id": "call_37336f2bb7a94216a2c7b7"
}
system: {
"type": "tool_result",
"id": "call_37336f2bb7a94216a2c7b7",
"name": "generate_response",
"output": [
{
"type": "text",
"text": "Successfully generated response."
}
]
}
Aggregator: 感谢两位辩论者的精彩发言。目前看来,我们已经听到了双方对于圆A绕圆B滚动一圈回到起点时自转次数的不同观点:Alice认为是3次,而Bob则提出了4次的论点,并且给出了一些直观的理解和数学上的解释。
为了进一步澄清这个问题,我们可以考虑一下圆A与圆B接触点的变化情况。当圆A开始滚动时,它的一个特定点会与圆B接触;随着圆A继续沿圆B边缘移动直至回到原位,这个初始接触点实际上需要经历额外的一圈才能重新对准起始位置。这意味着除了由于沿着圆B路径行进而产生的3次自转外,确实还需要加上一次额外的自转来调整回初始相位。
因此,根据更深入的分析,正确答案应该是**圆A总共会旋转4次**。现在,既然我们已经得出了明确的答案,这场辩论可以结束了。再次感谢Alice和Bob带来的精彩讨论!
Alice: 感谢Bob的详细解释。我理解了你的观点,但我想进一步澄清并坚持正方的观点:当圆A绕着圆B外切滚动一圈回到起点时,圆A总共会旋转3次。
让我们再仔细分析一下这个过程:
1. **路径长度与自转关系**:首先,圆A沿着圆B的边缘滚动一圈,它所走过的路径长度确实是圆B的周长2πR。由于没有相对滑动,这意味着圆A在其自身中心轴上的旋转次数可以通过将其行走的距离(即2πR)除以其自身的周长来计算。圆A的周长为2πr=2π(R/3)。
因此,圆A旋转的次数为(2πR)/(2π(R/3)) = 3。
2. **关于额外一次自转的问题**:你提到的额外一次自转是为了调整到初始状态而产生的。但实际上,在圆A绕圆B滚动一圈的过程中,它已经在不断调整自己的位置以保持与圆B的接触点不变。因此,不需要再额外增加一次自转来调整回初始相位。圆A在完成绕圆B一周的同时,已经自然地完成了3次完整的自转,并且回到了起始位置。
3. **直观理解**:我们可以将这个问题简化为一个更简单的例子来帮助理解。假设有一个小齿轮(代表圆A)和一个大齿轮(代表圆B),它们之间啮合良好,没有滑动。当小齿轮绕着大齿轮滚动一圈回到起点时,它实际上只会在大齿轮上滚动三次(因为其半径是大齿轮的1/3)。这与我们的计算结果是一致的。
综上所述,我认为圆A在绕着圆B滚动一圈回到起点时,总共会旋转3次。这包括了所有必要的自转,而不需要额外的一次自转来调整回初始相位。
Bob: 谢谢Alice的进一步解释。我理解你的观点,但我仍然认为圆A在绕着圆B滚动一圈回到起点时,实际上会旋转4次而不是3次。让我再详细阐述一下我的理由:
1. **复合运动的本质**:当圆A绕着圆B滚动一圈时,它同时进行两种运动:一种是沿着圆B边缘的公转(即绕圆B一周),另一种是圆A自身的自转。这两种运动是相互独立但又相互关联的。
2. **自转与公转的关系**:圆A在绕圆B滚动一圈的过程中,确实走过了等于圆B周长的距离,即2πR。然而,为了确保圆A能够恰好回到其初始位置并且没有发生任何滑动,它必须多旋转一次以补偿开始时的位置偏移。这是因为圆A在滚动过程中不仅需要覆盖圆B的周长,还需要调整自己的相位以回到初始位置。
3. **直观理解**:想象一个简单的例子来帮助理解。假设你有一个小齿轮(代表圆A)和一个大齿轮(代表圆B),它们之间啮合良好,没有滑动。当你让小齿轮绕着大齿轮滚动一圈回到起点时,你会发现小齿轮不仅在大齿轮上滚动了三次(因为其半径是大齿轮的1/3),而且它还额外旋转了一次以确保回到初始位置。这个额外的一次自转是为了调整小齿轮的相位,使其与初始位置对齐。
4. **数学证明**:我们可以通过以下步骤来进一步证明这一点:
- 圆A绕圆B滚动一圈所走过的路径长度为2πR。
- 圆A的周长为2πr = 2π(R/3)。
- 因此,圆A旋转的次数为(2πR)/(2π(R/3)) = 3。
- 但是,为了使圆A回到初始位置,它需要额外旋转一次。因此,总共有4次自转。
5. **物理实验**:如果你有机会进行实际的物理实验,你可以用两个不同大小的硬币或圆盘来模拟这个过程。你会发现,当小圆盘绕着大圆盘滚动一圈回到起点时,它实际上会旋转4次。
综上所述,我认为当圆A绕着圆B滚动一圈并回到起点时,它实际上会经历4次完整自转而非3次。这其中包括了3次由于跟随圆B外围轨迹产生的自然转动,以及1次用于调整回初始相位的附加转动。
Aggregator: {
"type": "tool_use",
"name": "generate_response",
"input": {
"finished": true,
"correct_answer": "当圆A绕着圆B滚动一圈并回到起点时,它实际上会经历4次完整自转。这其中包括了3次由于跟随圆B外围轨迹产生的自然转动,以及1次用于调整回初始相位的附加转动。"
},
"id": "call_00c552f92ac749b590d5d6"
}
system: {
"type": "tool_result",
"id": "call_00c552f92ac749b590d5d6",
"name": "generate_response",
"output": [
{
"type": "text",
"text": "Successfully generated response."
}
]
}
Aggregator: 感谢Alice和Bob的深入讨论。通过双方的观点和论证,我们可以得出结论:
当圆A绕着圆B滚动一圈并回到起点时,它实际上会经历4次完整自转。这其中包括了3次由于跟随圆B外围轨迹产生的自然转动,以及1次用于调整回初始相位的附加转动。
因此,正确答案是:**圆A总共会旋转4次**。
再次感谢两位辩论者的精彩发言!这场辩论已经结束。
辩论结束,正确答案是: 当圆A绕着圆B滚动一圈并回到起点时,它实际上会经历4次完整自转。这其中包括了3次由于跟随圆B外围轨迹产生的自然转动,以及1次用于调整回初始相位的附加转动。
进一步阅读¶
Encouraging Divergent Thinking in Large Language Models through Multi-Agent Debate. EMNLP 2024.
Total running time of the script: (1 minutes 11.706 seconds)