跳转至

Multi-Agent Debate 模式:对抗式辩论

解决什么问题

"去海南还是去东北?"——一个 Agent 容易自我强化偏见。 Multi-Agent Debate 让持不同立场的 Agent 互相攻防,由裁判评估论点强弱,最终输出更全面的决策。

核心价值:通过对抗暴露单一视角的盲点,比"一个 Agent 列优缺点"更可靠。

场景 为什么选它
旅行目的地 PK 正反方各执一词,裁判综合判断
技术选型辩论 Redis vs Memcached 各有拥趸
投资决策 多空双方对峙,避免一言堂

复杂度

⭐⭐⭐ 中等。需要维护立场一致性 + 裁判评分逻辑。

和其他模式的关系

模式 谁决定下一步 什么时候用
Multi-Agent Debate 裁判控制轮次、评分、判决 需要对抗暴露盲点,做出更全面的决策
Group Chat 主持人选发言者 目标是协作共识,不是对抗
Voting 独立回答后投票 短答案,不需要交锋过程

Debate 比 Group Chat 更尖锐(强制对立立场),比 Voting 更深入(多轮交锋而非一次投票)。

角色关系

┌────────┐       ┌────────┐
│ 正方    │ ◄───► │ 反方    │  ← 互相驳斥
│(海南派) │       │(东北派) │
└───┬────┘       └───┬────┘
    │                │
    ▼                ▼
    ┌────────────────┐
    │    裁判 Agent   │  ← 评估双方论点,做最终判决
    └────────────────┘
  • 正方/反方:交替发言,必须回应对方论点
  • 裁判:每轮打分,N 轮后出判决

完整 Python 实现

"""
Multi-Agent Debate: 海南 vs 东北 冬季旅游辩论
正方推荐海南,反方推荐东北,裁判打分后给出结论。
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field


# ── MockLLM ──────────────────────────────────────────────
class MockLLM:
    def __init__(self):
        self.call_log: list[dict] = []
        self._round = 0

    def chat(self, role: str, prompt: str) -> str:
        self.call_log.append({"role": role, "prompt": prompt[:80]})
        self._round += 1
        r = self._round

        if role == "pro_hainan":
            if r <= 3:
                return "海南冬季均温25°C,直飞便宜,三亚亚龙湾水质全国第一,适合带老人小孩。"
            return "对方说东北滑雪,但初学者受伤风险高;海南水上运动更安全,且冬季无雾霾。"

        if role == "pro_dongbei":
            if r <= 4:
                return "东北冰雪大世界独一无二,雪乡雾凇堪称奇观,哈尔滨中央大街美食丰富,人均消费比海南低40%。"
            return "海南旺季酒店翻倍,东北淡季性价比极高;且温泉+滑雪是冬季独有组合。"

        if role == "judge":
            if "评分" in prompt:
                return json.dumps({
                    "正方得分": 8.5, "正方亮点": "气候优势、安全性",
                    "反方得分": 8.0, "反方亮点": "独特体验、性价比",
                })
            if "判决" in prompt:
                return (
                    "【裁判判决】双方各有优势。带老人小孩推荐海南(安全+暖和);"
                    "年轻人追求体验推荐东北(滑雪+冰雪)。综合来看海南以微弱优势胜出(8.5 vs 8.0)。"
                )
        return ""


# ── Debater ──────────────────────────────────────────────
@dataclass
class Debater:
    name: str
    role: str
    stance: str
    llm: MockLLM = field(repr=False)

    def argue(self, history: list[dict]) -> str:
        context = "\n".join(f"[{h['speaker']}] {h['content']}" for h in history[-4:])
        prompt = f"你是{self.stance}。对话记录:\n{context}\n请发表论点或反驳对方:"
        return self.llm.chat(self.role, prompt)


# ── Judge ────────────────────────────────────────────────
@dataclass
class Judge:
    llm: MockLLM = field(repr=False)

    def score_round(self, history: list[dict]) -> dict:
        context = "\n".join(f"[{h['speaker']}] {h['content']}" for h in history)
        raw = self.llm.chat("judge", f"请评分:\n{context}")
        try:
            return json.loads(raw)
        except json.JSONDecodeError:
            return {"error": raw}

    def final_verdict(self, history: list[dict], scores: list[dict]) -> str:
        prompt = f"所有轮次得分: {json.dumps(scores, ensure_ascii=False)}\n请做最终判决:"
        return self.llm.chat("judge", prompt)


# ── Debate Arena ─────────────────────────────────────────
@dataclass
class DebateArena:
    pro: Debater
    con: Debater
    judge: Judge
    max_rounds: int = 3
    history: list[dict] = field(default_factory=list)

    def run(self, topic: str) -> str:
        print(f"[Debate] 辩题: {topic}")
        self.history.append({"speaker": "主持", "content": f"辩题: {topic}"})
        round_scores: list[dict] = []

        for i in range(self.max_rounds):
            print(f"\n--- 第 {i+1} 轮 ---")

            # 正方发言
            pro_msg = self.pro.argue(self.history)
            self.history.append({"speaker": self.pro.name, "content": pro_msg})
            print(f"  [正方] {self.pro.name}: {pro_msg[:50]}...")

            # 反方发言
            con_msg = self.con.argue(self.history)
            self.history.append({"speaker": self.con.name, "content": con_msg})
            print(f"  [反方] {self.con.name}: {con_msg[:50]}...")

            # 裁判评分
            score = self.judge.score_round(self.history)
            round_scores.append(score)
            print(f"  [裁判] {score}")

        # 最终判决
        verdict = self.judge.final_verdict(self.history, round_scores)
        print(f"\n[Debate] 最终判决: {verdict}")
        return verdict


# ── 运行入口 ─────────────────────────────────────────────
def main():
    llm = MockLLM()

    pro = Debater("海南代表", "pro_hainan", "支持冬季去海南旅游", llm)
    con = Debater("东北代表", "pro_dongbei", "支持冬季去东北旅游", llm)
    judge = Judge(llm)

    arena = DebateArena(pro=pro, con=con, judge=judge, max_rounds=3)
    result = arena.run("冬季旅游目的地: 海南 vs 东北")

    # 验证
    assert "海南" in result
    assert "东北" in result or "滑雪" in result
    assert len(llm.call_log) >= 9  # 3轮*(正方+反方+裁判) + 最终判决
    print(f"\n✓ 共 {len(llm.call_log)} 次 LLM 调用,辩论完成")


if __name__ == "__main__":
    main()

运行记录

[Debate] 辩题: 冬季旅游目的地: 海南 vs 东北

--- 第 1 轮 ---
  [正方] 海南代表: 海南冬季均温25°C,直飞便宜,三亚亚龙湾水质全国第一,适合带老...
  [反方] 东北代表: 东北冰雪大世界独一无二,雪乡雾凇堪称奇观,哈尔滨中央大街美食丰...
  [裁判] {'正方得分': 8.5, '正方亮点': '气候优势、安全性', '反方得分': 8.0, '反方亮点': '独特体验、性价比'}

--- 第 2 轮 ---
  [正方] 海南代表: 对方说东北滑雪,但初学者受伤风险高;海南水上运动更安全...
  [反方] 东北代表: 海南旺季酒店翻倍,东北淡季性价比极高;且温泉+滑雪是冬季独有组合...
  [裁判] {'正方得分': 8.5, '正方亮点': '气候优势、安全性', '反方得分': 8.0, '反方亮点': '独特体验、性价比'}

--- 第 3 轮 ---
  [正方] 海南代表: 对方说东北滑雪,但初学者受伤风险高...
  [反方] 东北代表: 海南旺季酒店翻倍...
  [裁判] {'正方得分': 8.5, '正方亮点': '气候优势、安全性', '反方得分': 8.0, '反方亮点': '独特体验、性价比'}

[Debate] 最终判决: 【裁判判决】双方各有优势。带老人小孩推荐海南(安全+暖和);年轻人追求体验推荐东北(滑雪+冰雪)。综合来看海南以微弱优势胜出(8.5 vs 8.0)。

✓ 共 10 次 LLM 调用,辩论完成

踩坑记录

现象 trace 信号 修法
立场漂移 正方说着说着开始夸对方 正方发言中出现反方关键论据且无反驳 system prompt 强调"你必须坚持己方立场"
裁判偏见 裁判每轮打分一样 连续多轮 score 值完全相同(方差为 0) 要求裁判引用具体论据来打分
人身攻击 Agent 开始攻击对方而非论点 发言中出现"你不懂""你的水平"等人身表述 加约束"只讨论事实和数据"
信息重复 第3轮还在重复第1轮的论据 不同轮次的发言文本相似度 > 0.8 prompt 加"请提出新论据"

工程备忘

  1. 立场注入:在 system_prompt 里明确立场,且每轮 prompt 重复一次。
  2. 评分维度:可以拆成"事实准确度/论证逻辑/反驳有效性"多维度打分。
  3. 轮数控制:2-4 轮最佳,超过 4 轮容易陷入循环论证。
  4. 裁判独立性:裁判不参与辩论,只读记录 + 打分,避免角色混淆。
  5. 适用场景:决策类问题 > 事实类问题。问"去哪玩"适合辩论,问"故宫几点开门"不适合。
  6. 多方辩论:支持 3+ 方辩论,但需要更复杂的轮次管理和评分机制。
  7. 与 Group Chat 的区别:Debate 强调对抗和评分,Group Chat 强调协作和共识。

读完以后

如果不需要对抗、多角色协作就够了,看 Group Chat。 如果只需要多份独立答案投票、不需要交锋过程,看 Voting。 如果辩论双方需要调用各自的工具来获取数据支撑论点,看 Agents-as-Tools

参考资料