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 present the argument that when circle A, with a radius 1/3 that of circle B, rolls around the outside of circle B without sliding, it will revolve 4 times in total to return 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 \( \frac{R}{3} \).

When circle A rolls around circle B, it travels along the circumference of a larger circle whose radius is the sum of the radii of both circles, which is \( R + \frac{R}{3} = \frac{4R}{3} \).

The circumference of this path is:
\[ C_{\text{path}} = 2\pi \left(\frac{4R}{3}\right) = \frac{8\pi R}{3} \]

The distance that circle A needs to travel to complete one trip around circle B is equal to the circumference of the path, \( \frac{8\pi R}{3} \).

Now, for each full revolution of circle A, it travels a distance equal to its own circumference, which is:
\[ C_{\text{A}} = 2\pi \left(\frac{R}{3}\right) = \frac{2\pi R}{3} \]

To find out how many revolutions circle A makes while traveling the entire path, we divide the total path distance by the circumference of circle A:
\[ \text{Number of Revolutions} = \frac{\frac{8\pi R}{3}}{\frac{2\pi R}{3}} = \frac{8\pi R}{3} \times \frac{3}{2\pi R} = 4 \]

Therefore, circle A will make 4 complete revolutions as it rolls around the outside of circle B and returns to its starting position.
Bob: Thank you. As the negative side, I will present the argument that when circle A, with a radius 1/3 that of circle B, rolls around the outside of circle B without sliding, it will revolve 3 times in total to return to its starting point.

To clarify, let's review and slightly adjust the understanding of the problem:

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

When circle A rolls around circle B, it travels along the circumference of a larger circle whose radius is the sum of the radii of both circles, which is \( R + \frac{R}{3} = \frac{4R}{3} \).

The circumference of this path is:
\[ C_{\text{path}} = 2\pi \left(\frac{4R}{3}\right) = \frac{8\pi R}{3} \]

Now, for each full revolution of circle A, it travels a distance equal to its own circumference, which is:
\[ C_{\text{A}} = 2\pi \left(\frac{R}{3}\right) = \frac{2\pi R}{3} \]

However, the key point here is that as circle A rolls around circle B, it not only travels the path but also rotates around its own center. This rotation due to the path (which we can call the "external" or "path-induced" rotation) must be distinguished from the "internal" or "self-rotation" of circle A as it makes contact with circle B.

To find out how many revolutions circle A makes while traveling the entire path, we divide the total path distance by the circumference of circle A:
\[ \text{Number of Path-Induced Revolutions} = \frac{\frac{8\pi R}{3}}{\frac{2\pi R}{3}} = \frac{8\pi R}{3} \times \frac{3}{2\pi R} = 4 \]

But, since circle A is rolling without sliding, it also has to rotate once for every full circumference of circle B it touches. Circle B has a circumference of:
\[ C_{\text{B}} = 2\pi R \]

Since circle A's radius is 1/3 of circle B, it will touch the circumference of circle B 3 times (as \( 3 \times \frac{R}{3} = R \)) during one complete trip around circle B. Therefore, circle A will make an additional 1 internal revolution for each time it touches the full circumference of circle B, adding up to 3 internal revolutions.

Thus, the total number of revolutions made by circle A is:
\[ \text{Total Revolutions} = \text{Path-Induced Revolutions} - 1 = 4 - 1 = 3 \]

Therefore, circle A will make 3 complete revolutions as it rolls around the outside of circle B and returns to its starting position.
/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": {
        "correct_answer": "4",
        "finished": true
    },
    "id": "call_9fdf013791bd47a2b53c1d"
}
system: {
    "type": "tool_result",
    "id": "call_9fdf013791bd47a2b53c1d",
    "name": "generate_response",
    "output": [
        {
            "type": "text",
            "text": "Successfully generated response."
        }
    ]
}
Aggregator: The debate has concluded, and after carefully considering the arguments presented by both sides, the correct answer is that circle A will revolve 4 times in total as it rolls around the outside of circle B and returns to its starting point.

Alice's argument correctly calculates the number of revolutions based on the path distance and the circumference of circle A. The key point is that the total number of revolutions is determined by the ratio of the path length to the circumference of the rolling circle, which indeed results in 4 revolutions.

Bob's argument introduces an additional concept of "internal" or "self-rotation," but this does not apply in the context of the problem. The 4 revolutions already account for the complete rotations of circle A as it rolls around the larger circle without sliding.

Therefore, Alice's answer is the correct one.

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 42.483 seconds)

Gallery generated by Sphinx-Gallery