
TL;DR(30 秒速览)
- 单 Agent 分析容易"一边倒"——要么过度看多,要么过度看空。解决方案:让 AI 自己辩论
- 三角色模型:**Judge(裁判)**负责流程和个性化 Prompt、**Participants(参与者)**对抗辩论、Judge 自主判断收敛
- 不是固定轮次——Judge 通过结构化 JSON {"converged": true/false} 自主决定何时达成共识
- 裁判使用中立编排 Prompt(剥离人设),防止不自觉地偏向某个参与者
- 开源地址:github.com/haibingzhao/easyai
前情提要:在上一篇文章中,我们聊了 DAG Swarm 编排——用 Kahn 算法实现多 Agent 的分层并行执行。但同一层里的多个 Agent 各说各话,谁来综合判断?答案是 Deliberation——对抗辩论 + 裁判裁决。
一个 Agent 的盲区
让一个 AI 分析苹果公司的投资价值,它可能会说:
“苹果基本面强劲,iPhone 销量稳健,服务业务增长迅猛,建议买入。”
听起来很有道理。但你问它风险因素,它也能列出一堆:
“估值偏高,中美关系存在不确定性,创新放缓。”
然后呢?它又告诉你"综合来看仍然值得买入"。
这就是单 Agent 的盲区——它不是没有看到反面论据,而是看到了也会合理化掉。LLM 天然倾向于给出一个"自洽"的结论,而不是真正对抗性地审视自己的观点。
核心矛盾: 单 Agent 能看到正反两面的信息,但缺乏结构性对抗——没有人逼它认真对待反面论据。
灵感来自法庭
法庭是怎么接近真相的?不是让一个法官自己去调查,而是:
EasyAI 的 Deliberation 机制就是这个思路: 
Judge(裁判)
├── 生成开场 Prompt → 所有参与者响应
├── 审阅完整历史 → 为每位参与者生成个性化 Prompt
├── 自主判断是否收敛 → 未收敛则继续下一轮
└── 收敛后 → 输出最终裁决
Participant 1(多头)
└── 接收个性化 Prompt → 输出论点
Participant 2(空头)
└── 接收个性化 Prompt → 输出论点
四个关键设计决策

一句话概括: Deliberation 的核心不是"让多个 AI 说话",而是谁来决定说什么、什么时候停、怎么避免偏见。
决策 1:裁判的中立性——剥离人设
这是最关键的设计。如果 Judge 本身是一个"投资分析师",它在主持辩论时会不自觉地偏向某种分析框架。
解决方案:在编排阶段,替换 Judge 的 System Prompt 为中立编排 Prompt。
// DeliberationTaskExecutor.kt
val ORCHESTRATOR_SYSTEM_PROMPT = """
|You are the orchestrator of a multi-agent deliberation session.
|
|Your responsibilities:
|1. Generate clear, balanced prompts that guide participants
| through structured discussion
|2. Objectively assess whether consensus has been reached
|3. When generating personalized prompts, reference specific
| prior arguments and direct participants to address
| unresolved points
|
|Be neutral, fair, and focused on productive deliberation.
|Do not inject your own opinions or expertise — your role
|is to facilitate the discussion, not participate in it.
""".trimMargin()
注意最后一句:“Do not inject your own opinions — your role is to facilitate, not participate.” Judge 在编排阶段是纯粹的流程管理者,不带任何观点倾向。
但在最终裁决(Verdict)阶段,Judge 恢复自己的人设——因为裁决本身就是需要专业判断的。
决策 2:个性化 Prompt——每轮量身定制
第 1 轮:所有参与者收到 Judge 生成的同一个开场 Prompt。
第 2 轮起:Judge 审阅完整辩论历史后,为每位参与者单独生成 Prompt:
{
"converged": false,
"reason": "多头尚未回应空头关于估值泡沫的质疑",
"prompts": [
{
"participantId": "BullResearcher",
"prompt": "空头指出 PE 35x 远超历史均值。请从增长预期角度回应这一估值质疑。你的前两轮论证(agent=BullResearcher)强调了服务业务增长,请具体量化。"
},
{
"participantId": "BearResearcher",
"prompt": "多头认为服务业务可以支撑高估值。请从历史估值回归的角度,具体分析服务业务的天花板。"
}
]
}
每个 Prompt 都:
- 引用了具体的历史论点(“空头指出 PE 35x”)
- 指向未解决的问题(“估值质疑”)
- 提醒参与者自己的身份(“你的前两轮论证”)
决策 3:自主收敛——不是固定轮次
传统做法是固定辩论 3 轮就结束。但有些议题 2 轮就够了,有些可能需要 5 轮。
EasyAI 的方案:Judge 通过结构化 JSON 自主判断收敛。
// JSON Schema 约束 Judge 的输出
val ROUND_PROMPTS_SCHEMA = """
{
"type": "object",
"properties": {
"converged": { "type": "boolean" },
"reason": { "type": "string" },
"prompts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"participantId": { "type": "string" },
"prompt": { "type": "string" }
},
"required": ["participantId", "prompt"]
}
}
},
"required": ["converged", "reason"]
}
"""
当 converged = true 时,辩论结束,进入裁决。当 converged = false 时,Judge 必须为每位参与者生成下一轮的个性化 Prompt。
安全网: maxRounds(默认 3)是硬性上限,防止无限辩论。
for (round in startRound..deliberation.maxRounds) {
// Judge 生成个性化 Prompt 或判断收敛
val generatedPrompts = generateRoundPrompts(...)
if (generatedPrompts == null) {
// converged = true → 进入裁决
break
}
// 每位参与者响应
for (participantId in speakers) {
// …
}
}
// 裁决阶段
val verdict = workerExecutor.executeWorker(judgeSpec, verdictPrompt, ...)
决策 4:参与者 Prompt 互相隔离
参与者 P1 看不到 P2 的 System Prompt(人设),只能看到 P2 的发言内容。
辩论历史用 XML 标签格式化,清晰区分每个参与者的发言:
<deliberation_history>
<entry agent="BullResearcher" round="1">
苹果服务业务年增长 20%,护城河深厚…
</entry>
<entry agent="BearResearcher" round="1">
PE 35x 远超历史均值 22x,估值泡沫明显…
</entry>
</deliberation_history>
这确保了信息对称——每个参与者知道对方说了什么,但不知道对方的"内心想法"(System Prompt)。就像法庭上,控辩双方可以互相质证,但不能读对方的内心。
完整的执行流程
把四个决策串起来,一次完整的 Deliberation 执行流程是:
┌─ 开场阶段 ──────────────────────────────────────────┐
│ Judge(中立编排 Prompt)→ 生成开场 Prompt │
│ 所有参与者收到同一个开场 Prompt → 各自响应 │
└──────────────────────────────────────────────────────┘
↓
┌─ 辩论循环(Round 2..maxRounds)─────────────────────┐
│ Judge 审阅完整历史 → 输出 JSON: │
│ converged=true → 进入裁决 │
│ converged=false → 为每位参与者生成个性化 Prompt │
│ 每位参与者收到自己的 Prompt → 各自响应 │
│ 每轮结束后持久化 DeliberationEntry │
└──────────────────────────────────────────────────────┘
↓
┌─ 裁决阶段 ──────────────────────────────────────────┐
│ Judge(恢复自身人设)→ 收到完整辩论历史 │
│ → 输出最终裁决(结论 + 理由 + 置信度) │
└──────────────────────────────────────────────────────┘
断点恢复:辩论到一半服务重启了怎么办
辩论可能持续 10-20 分钟。如果第 3 轮结束时服务重启了,前 2 轮的 token 和讨论内容不能丢。
方案:每个发言者完成后立即持久化。
// 每个参与者响应后立即写入数据库
history.add(DeliberationEntry(
agentId = participantId,
round = round,
response = result.summary,
inputTokens = result.inputTokens,
outputTokens = result.outputTokens,
// …
))
// 增量持久化
store?.saveDeliberationHistory(run.id, task.id, history.toList())
恢复时,从数据库加载历史记录,重建 token 计数器,找到最后一个完整轮次,从下一轮继续:
// 从持久化历史重建 token 计数器
for (entry in history) {
total.input += entry.inputTokens
total.output += entry.outputTokens
}
// 找到最后完成的轮次
val lastCompletedRound = /* 找到所有参与者都完成的最后轮次 */
val startRound = lastCompletedRound + 1
关键细节: token 计数器必须从持久化数据重建,不能从内存恢复——因为进程重启后内存已经清空了。
消除偏见的四道防线
| Judge 自身人设泄漏 | 编排阶段替换为中立 Prompt | ORCHESTRATOR_SYSTEM_PROMPT |
| 参与者互相影响人设 | Prompt 互相隔离,只共享发言内容 | formatDeliberationHistoryText() |
| LLM 跑题或"人身攻击" | JSON Schema 约束输出格式 | ROUND_PROMPTS_SCHEMA |
| 无限辩论 | maxRounds 硬性上限 | for (round in 1..maxRounds) |
前端可视化
辩论过程在前端以轮次卡片的形式展示:
- 轮次选择器:顶部横栏,点击切换不同轮次
- 参与者卡片:每轮中每个参与者的发言独立展示,可折叠
- 开场 Prompt / 轮次 Prompt:可查看 Judge 为每轮生成的 Prompt(理解"为什么这么问")
- 收敛状态指示器:实时显示辩论是否已收敛
- 最终裁决区:独立展示 Judge 的最终裁决,支持 Markdown 渲染
运行中的辩论通过轮询(3 秒间隔)自动刷新——每当有新的 DeliberationEntry 写入数据库,前端就能看到。
实战案例
案例 1:多空辩论(投资分析)
participants: [BullResearcher, BearResearcher]
judge: ResearchManager
maxRounds: 3
多头看到利好数据,空头看到风险因素,裁判综合裁决。经过 2-3 轮对抗,通常能收敛到一个带有明确评级(买入/持有/卖出)的投资方案。
案例 2:三方风险辩论
participants: [AggressiveAnalyst, ConservativeAnalyst, NeutralAnalyst]
judge: PortfolioManager
maxRounds: 3
在多空辩论之后,再加一轮三方辩论——激进派主张重仓、保守派主张轻仓、中立派平衡——最终由 Portfolio Manager 给出交易决策。
案例 3:代码方案评审
participants: [MicroserviceAdvocate, MonolithAdvocate]
judge: TechLead
maxRounds: 2
一个 Agent 主张微服务架构,另一个主张单体——裁判给出技术推荐。通常 2 轮就够了。
踩坑记录
三个坑,每个都和"LLM 不听话"有关。
坑 1:Judge 的人设泄漏。 最初没有替换 System Prompt,Judge 在生成个性化 Prompt 时会不自觉地偏向某个参与者——比如给多头生成"请继续论证增长逻辑",给空头生成"请简要说明风险"。修复:引入 ORCHESTRATOR_SYSTEM_PROMPT,在编排阶段完全剥离人设。
坑 2:LLM 偷懒提前收敛。 有时 Judge 在第 1 轮就返回 converged: true,理由是"双方观点已经很清晰"。实际上参与者还没来得及互相质疑。修复:增加 reason 字段的检查——如果 reasoning 太短或没有引用具体论点,使用 fallback Prompt 强制继续。
坑 3:持久化遗漏 token 用量。 最初 DeliberationEntry 不包含 token 字段,断点恢复后无法统计成本——因为内存中的计数器丢了。修复:每个 Entry 记录 inputTokens、outputTokens、cacheReadTokens、cacheWriteTokens、durationMs,恢复时逐项累加重建。
写在最后
单 Agent 分析像一个人自己下棋——他能看到好几种走法,但最终会选自己最喜欢的那一种。多 Agent 辩论像两个高手对弈——每一步都会被对手挑战,最终的结果更接近真相。
| 偏见控制 | 依赖 Prompt 工程 | 结构性对抗 |
| 反面论据 | 看到但可能合理化 | 被对手强制提出 |
| 收敛判断 | Agent 自己说了算 | Judge 独立判断 |
| 输出质量 | 自洽但可能偏颇 | 经过对抗检验 |
| 成本 | 1 次 LLM 调用 | N 轮 × M 参与者 + 裁决 |
EasyAI 的 DeliberationTaskExecutor 用 604 行 Kotlin 代码实现了完整的对抗辩论系统——中立裁判、个性化 Prompt、自主收敛、断点恢复、增量持久化。核心思想只有一个:让 AI 像法庭一样接近真相。
下一篇,我们聊聊 EasyAI 的 TEAM Agent——Leader 如何自动分解任务并协调 6 个专业成员并行工作。
开源地址:https://github.com/haibingzhao/easyai
欢迎 Star、Issue 和 PR。
网硕互联帮助中心






评论前必须登录!
注册