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 加"请提出新论据" |
工程备忘
- 立场注入:在 system_prompt 里明确立场,且每轮 prompt 重复一次。
- 评分维度:可以拆成"事实准确度/论证逻辑/反驳有效性"多维度打分。
- 轮数控制:2-4 轮最佳,超过 4 轮容易陷入循环论证。
- 裁判独立性:裁判不参与辩论,只读记录 + 打分,避免角色混淆。
- 适用场景:决策类问题 > 事实类问题。问"去哪玩"适合辩论,问"故宫几点开门"不适合。
- 多方辩论:支持 3+ 方辩论,但需要更复杂的轮次管理和评分机制。
- 与 Group Chat 的区别:Debate 强调对抗和评分,Group Chat 强调协作和共识。
读完以后
如果不需要对抗、多角色协作就够了,看 Group Chat。 如果只需要多份独立答案投票、不需要交锋过程,看 Voting。 如果辩论双方需要调用各自的工具来获取数据支撑论点,看 Agents-as-Tools。