Multi-Agent Debate

Debate workflow simulates a multi-turn discussion between different agents, mostly several solvers and an aggregator. Typically, the solvers generate and exchange their answers, while the aggregator collects and summarizes the answers.

We implement the examples in EMNLP 2024, where two debater agents will discuss a topic in a fixed order, and express their arguments based on the previous debate history. At each round a moderator agent will decide whether the correct answer can be obtained in the current iteration.

import asyncio
import os

from pydantic import Field, BaseModel

from agentscope.agent import ReActAgent
from agentscope.formatter import (
    DashScopeMultiAgentFormatter,
    DashScopeChatFormatter,
)
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub

# Prepare a topic
topic = (
    "The two circles are externally tangent and there is no relative sliding. "
    "The radius of circle A is 1/3 the radius of circle B. Circle A rolls "
    "around circle B one trip back to its starting point. How many times will "
    "circle A revolve in total?"
)


# Create two debater agents, Alice and Bob, who will discuss the topic.
def create_solver_agent(name: str) -> ReActAgent:
    """Get a solver agent."""
    return ReActAgent(
        name=name,
        sys_prompt=f"You're a debater named {name}. Hello and welcome to the "
        "debate competition. It's unnecessary to fully agree with "
        "each other's perspectives, as our objective is to find "
        "the correct answer. The debate topic is stated as "
        f"follows: {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"]]

# Create a moderator agent
moderator = ReActAgent(
    name="Aggregator",
    sys_prompt=f"""You're a moderator. There will be two debaters involved in a debate competition. They will present their answer and discuss their perspectives on the topic:
``````
{topic}
``````
At the end of each round, you will evaluate both sides' answers and decide which one is correct.""",
    model=DashScopeChatModel(
        model_name="qwen-max",
        api_key=os.environ["DASHSCOPE_API_KEY"],
        stream=False,
    ),
    # Use multiagent formatter because the moderator will receive messages from more than a user and an assistant
    formatter=DashScopeMultiAgentFormatter(),
)


# A structured output model for the moderator
class JudgeModel(BaseModel):
    """The structured output model for the moderator."""

    finished: bool = Field(
        description="Whether the debate is finished.",
    )
    correct_answer: str | None = Field(
        description="The correct answer to the debate topic, only if the debate is finished. Otherwise, leave it as None.",
        default=None,
    )


async def run_multiagent_debate() -> None:
    """Run the multi-agent debate workflow."""
    while True:
        # The reply messages in MsgHub from the participants will be broadcasted to all participants.
        async with MsgHub(participants=[alice, bob, moderator]):
            await alice(
                Msg(
                    "user",
                    "You are affirmative side, Please express your viewpoints.",
                    "user",
                ),
            )
            await bob(
                Msg(
                    "user",
                    "You are negative side. You disagree with the affirmative side. Provide your reason and answer.",
                    "user",
                ),
            )

        # Alice and Bob doesn't need to know the moderator's message, so moderator is called outside the MsgHub.
        msg_judge = await moderator(
            Msg(
                "user",
                "Now you have heard the answers from the others, have the debate finished, and can you get the correct answer?",
                "user",
            ),
            structured_model=JudgeModel,
        )

        if msg_judge.metadata.get("finished"):
            print(
                "\nThe debate is finished, and the correct answer is: ",
                msg_judge.metadata.get("correct_answer"),
            )
            break


asyncio.run(run_multiagent_debate())
Alice: Thank you. As the affirmative side, I will argue that when circle A, with a radius 1/3 that of circle B, rolls around the circumference of circle B without sliding, it will revolve 4 times in total by the time it returns to its starting point.

To understand this, let's break down the problem:

- Let the radius of circle B be \( R \).
- The radius of circle A is then \( \frac{R}{3} \).

When circle A rolls around circle B, the path that the center of circle A follows is a circle with a radius equal to the sum of the radii of circles A and B, which is \( R + \frac{R}{3} = \frac{4R}{3} \).

The circumference of the path that the center of circle A follows (the larger circle) is:
\[ 2\pi \times \frac{4R}{3} = \frac{8\pi R}{3} \]

The circumference of circle A itself is:
\[ 2\pi \times \frac{R}{3} = \frac{2\pi R}{3} \]

Now, to find out how many times circle A revolves, we need to divide the circumference of the path by the circumference of circle A:
\[ \frac{\frac{8\pi R}{3}}{\frac{2\pi R}{3}} = \frac{8\pi R}{3} \times \frac{3}{2\pi R} = 4 \]

Therefore, as circle A completes one trip around circle B, it will have revolved 4 times. This is due to the fact that the distance it travels along the path is exactly 4 times its own circumference, leading to 4 complete revolutions.
Bob: Thank you. As the negative side, I will argue that circle A will actually revolve 3 times, not 4, as it rolls around circle B and returns to its starting point.

To understand this, let's revisit the problem with a focus on the relative motion of the two circles:

- Let the radius of circle B be \( R \).
- The radius of circle A is then \( \frac{R}{3} \).

When circle A rolls around circle B, the path that the center of circle A follows is indeed a circle with a radius equal to the sum of the radii of circles A and B, which is \( R + \frac{R}{3} = \frac{4R}{3} \).

The circumference of the path that the center of circle A follows (the larger circle) is:
\[ 2\pi \times \frac{4R}{3} = \frac{8\pi R}{3} \]

The circumference of circle A itself is:
\[ 2\pi \times \frac{R}{3} = \frac{2\pi R}{3} \]

However, when we consider the number of revolutions, we need to account for the fact that as circle A rolls around circle B, it is also rotating around its own center. This means that one complete revolution around circle B does not equate to the full number of rotations of circle A due to the additional rotation caused by the curvature of the path.

The key insight here is that for every full trip around the circumference of circle B, circle A will have made one less revolution than the number of circumferences it has traveled. This is because the final rotation that aligns it back to the starting orientation is already counted in the path length.

Thus, the total number of revolutions of circle A is:
\[ \frac{\frac{8\pi R}{3}}{\frac{2\pi R}{3}} - 1 = 4 - 1 = 3 \]

Therefore, as circle A completes one trip around circle B, it will have revolved 3 times. This accounts for the relative motion and the fact that the last rotation to return to the starting orientation is part of the path length, not an additional revolution.
/home/runner/work/agentscope/agentscope/src/agentscope/model/_dashscope_model.py:182: 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": "4"
    },
    "id": "call_5006c30685674b168a0fc6"
}
system: {
    "type": "tool_result",
    "id": "call_5006c30685674b168a0fc6",
    "name": "generate_response",
    "output": [
        {
            "type": "text",
            "text": "Successfully generated response."
        }
    ]
}
Aggregator: Thank you both, Alice and Bob, for your detailed explanations. After carefully considering the arguments presented, the correct number of times circle A will revolve as it rolls around circle B and returns to its starting point is 4.

Alice's calculation correctly identifies that the total number of revolutions is determined by dividing the path length (the circumference of the larger circle) by the circumference of circle A. This results in 4 complete revolutions, as the distance traveled by the center of circle A is exactly 4 times the circumference of circle A.

Bob's argument introduces an interesting point about relative motion, but the key insight here is that the final rotation to align circle A back to its starting orientation is already included in the path length. Therefore, there is no need to subtract one revolution from the total count.

Thus, the correct answer is 4 revolutions. Thank you both for your participation and for making this a thoughtful and engaging debate.

The debate is finished, and the correct answer is:  4

Further Reading

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

Total running time of the script: (0 minutes 28.676 seconds)

Gallery generated by Sphinx-Gallery