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总共会旋转4次。

我们来分析一下这个问题。首先,我们知道圆A的半径是圆B半径的1/3。假设圆B的半径为R,则圆A的半径为r = R/3。当圆A绕着圆B外切滚动一圈时,实际上是在沿着一个以2R为半径的大圆周运动(因为两圆心之间的距离始终等于R + r = 4R/3)。

圆A沿这个大圆周移动的距离等于该大圆的周长,即\(2\pi * 2R = 4\pi R\)。而圆A自身的周长是\(2\pi r = 2\pi (R/3) = 2\pi R / 3\)。

因此,为了计算圆A在完成一圈过程中自身旋转了多少圈,我们可以用它走过的总路径长度除以其自身周长:
\[ \frac{4\pi R}{2\pi R / 3} = \frac{4\pi R * 3}{2\pi R} = 6 / 2 = 3 \]

但是这里需要注意的是,除了由于行进距离导致的旋转之外,还有一个额外的旋转来自于圆A围绕圆B中心做圆周运动时的公转效应。每当圆A绕圆B一周时,它不仅沿着外部轨迹线性移动了一圈,同时也在进行一次完整的公转。这意味着,在考虑了圆A相对于其自身轴线的自转之后,还应该加上这额外的一次旋转。

所以,最终答案是3次自转加上1次公转,总计4次旋转。
Bob: 谢谢。我是反方,我的观点是:当圆A绕着圆B滚动一圈回到起点时,圆A总共会旋转3次,而不是4次。

让我们仔细分析这个问题。根据正方的描述,我们知道圆A的半径\(r = R/3\)(其中R为圆B的半径),因此圆A绕圆B外切滚动一周所走过的路径长度确实为\(4\pi R\)。同时,圆A自身的周长为\(2\pi r = 2\pi (R/3) = 2\pi R / 3\)。

基于这些信息,我们可以计算出为了覆盖\(4\pi R\)的距离,圆A需要自转多少圈:
\[ \frac{4\pi R}{2\pi R / 3} = 6 \]

但这里的关键在于理解“额外的一次公转”是否应该被单独计数。实际上,在这种情况下,“公转”和“自转”并不是完全独立的概念。当我们说圆A完成了围绕圆B的一圈时,这已经包含了所有形式的旋转——即圆A不仅沿外部轨迹移动了一圈,同时也相对于自身轴线进行了必要的旋转以保持与圆B接触点的连续性。

换句话说,如果我们将圆A想象成一个轮子在平面上滚动而不打滑,那么无论它是直线滚动还是沿着曲线滚动,每前进自己周长的距离就完成一次完整的自转。在这个特定案例中,圆A正好通过三次这样的自转来完成整个行程,而不需要额外考虑所谓的“公转”。

因此,正确的答案应该是圆A总共旋转了3次,而非4次。
/home/runner/work/agentscope/agentscope/src/agentscope/model/_dashscope_model.py:232: DeprecationWarning: 'required' is not supported by DashScope API. It will be converted to 'auto'.
  warnings.warn(
Aggregator: {
    "type": "tool_use",
    "name": "generate_response",
    "input": {
        "finished": true,
        "correct_answer": "3次"
    },
    "id": "call_b5fb326e999041babfc06f"
}
system: {
    "type": "tool_result",
    "id": "call_b5fb326e999041babfc06f",
    "name": "generate_response",
    "output": [
        {
            "type": "text",
            "text": "Successfully generated response."
        }
    ]
}
Aggregator: 感谢两位辩论者的精彩讨论。经过分析,我们得出了最终答案:当圆A绕着圆B滚动一圈回到起点时,圆A总共会旋转3次。

正方Alice提到了一个额外的“公转”效应,并认为这应该被单独计数,从而得出4次旋转的答案。然而,反方Bob指出,在这种情况下,“公转”实际上已经包含在了自转的过程之中,因此不需要额外计算一次旋转。每前进自己周长的距离就完成一次完整的自转,而圆A正好通过三次这样的自转来完成整个行程。

因此,正确的答案是3次旋转。再次感谢两位辩论者的积极参与!

辩论结束,正确答案是: 3次

进一步阅读

Encouraging Divergent Thinking in Large Language Models through Multi-Agent Debate. EMNLP 2024.

Total running time of the script: (1 minutes 19.731 seconds)

Gallery generated by Sphinx-Gallery