RoundTable v1.0.0-rc.1:可辩论、可拍板、可落盘的多模型会议
我让三个大模型互相当红队:一个多模型圆桌插件的架构、踩坑与一次被否掉的方案
先说结论,免得你翻到最后:
- 多模型协作 ≠ 多问几个模型。 并列回答解决的是"覆盖率",会议解决的是"收敛"——后者需要主持人、需要持久的专家会话、需要结构化归并。
- 真正难的不是让模型说话,是控制权、上下文、成本口径,以及审查者的输出格式。
- 本文有 4 个来自真实翻车的工程细节、2 个诚实故事、1 个被红队否掉的产品方向。
项目是 dsh-plugin-roundtable(DeepSeek Harness 插件,MIT,v1.0.0-rc.1)。下面所有数字和坑都是真的。

一、起点:一次真实的多模型评审
我写了一份方案草案,然后做了一件事:把三个不同厂商的模型拉进同一个会议室,只给一条规矩——只准挑毛病,不准给方案。
- claude · 架构视角
- gpt · 产品视角
- qwen · 实现成本视角
它们交回来 9 条缺陷。我逐条表态,9 条全部认定为真问题。
其中最值钱的一条,是方案自己前后矛盾:一处要求画布"可拖拽编辑",另一处写着"不另做独立编辑器"。这个矛盾我在写方案时完全没意识到,而它是纯文本层面就能当场核实的——不需要任何领域知识。
这件事让我确认了一个判断:让模型互相拆台,比让它给我答案有用得多。
二、为什么"多问几个 AI"不等于开会
同时问三个模型,你得到三份并列的回答。听起来很美,实际有四个问题:
| 问题 | 表现 |
|---|---|
| 没有收敛 | 三份答案都摆在那,谁对谁错、哪里冲突,还是你自己判断 |
| 上下文爆炸 | 三个模型各写 2000 字,你读完已经累了;再多两个模型直接放弃 |
| 没有责任主体 | 没人负责"综合",于是你成了唯一的整合器 |
| 分歧无处安放 | 模型 A 说该做、模型 B 说不该做,这个分歧本身是有价值的信息,却被并列排版抹平了 |
所以这个插件要造的不是"多路调用",而是三件东西:主持人(谁负责收敛)、持久会话(专家能被追问,不是一次性问答)、结构化归并(不让上下文被十份报告淹没)。
三、架构:主持人 + 持久子代理 + 汇聚网关
拓扑是一张环形图:
主持人(DeepSeek)
/ | \
专家A ←——→ 专家B ←——→ 专家C ← 双向通道 = 可互辩
\ | /
汇聚网关(结构化归并)
三件事值得说:
1. 每位专家是一个持久子代理(durable subagent),不是一个 API 调用。
它带着一份《全局协作总纲》入会:会议目标、自己的角色边界、协作协议、安全红线。跑完一轮它回到空闲状态,但还能被再次唤醒——所以"节点已完成"这种状态在语义上是不存在的(这一点后来成了我改界面的依据,见第八节)。
2. 两种发言通道,用错就是烧钱。
roundtable_speak:写进会议记录,不唤醒任何人。日常汇报走这条。roundtable_send_message:唤醒收件人,让它立刻开始干活。只有"必须现在动手"才用它。
这个区分不是洁癖:把日常汇报走成唤醒,专家会被无意义地拉起来烧 token。
3. 状态全落盘,且 UI 不直接改会议状态。
<工作区>/.roundtable/<会议id>/
├── meeting.json 议题、专家、连线、预算、决策
├── transcript.jsonl 逐条发言(追加写)
├── review.json 针锋相对的评审记录与表态
├── user-actions.jsonl UI 上的改动,等主持人执行
└── kb-digest.json 知识库摘要缓存
"UI 改动走队列"是刻意的设计。 你在界面上加一个专家,插件不会去偷偷改会议状态,而是往 user-actions.jsonl 追加一条记录,主持人下一轮读到它、再用 roundtable_add_node 执行。好处是:
- 只有主持人一个写者,不存在两个写者互相覆盖;
- "主持人唯一对用户负责"的语义没有被 UI 绕过去;
- 队列落盘,重启不丢。
代价是有延迟(下一轮才生效),界面上必须如实告知"已排入队列"——这是产品诚实问题,不是技术问题。

四、成本:为什么必须区分"说了多少"和"花了多少"
这是我踩过的最有教育意义的坑。
插件界面上有一个 Token 数字,超预算会自动"闭麦"。它统计的是发言文本量(中文约 0.6 token/字)。一直相安无事,直到我把它和 provider 上报的真实用量摆在一起:
| 口径 | 数值 |
|---|---|
| 界面上显示的"发言量粗估" | 5,289 |
| provider 上报的真实用量 | 127,230 |
差 24 倍。
差额来自哪里?system prompt、每位专家的 persona、历史上下文、工具调用的请求与返回——这些都不在"发言文本"里,但都是真金白银。
所以现在界面上两个数字并列显示,并且我把这段说明直接写进了 README:
它是"说了多少"的刻度,不是成本表。
工程教训:任何自己算出来的成本指标,只要它不是 provider 账单,就必须明确标注口径,否则它迟早会误导你自己。预算熔断可以基于粗估(它只需要单调、可控),但成本判断不行。
五、针锋相对:把"只挑毛病"做成协议
红队模式的核心不是"多问一遍",而是约束审查者的输出:
规则 1:只找缺陷,禁止提替代方案。
不禁止会怎样?审查者会逃到"我觉得应该这样做"——听起来像建议,实际上是把"论证"降级成了"另一种主张",你反而更难判断原方案是否成立。
规则 2:每条观点只聚焦一个缺陷,必须附证据。
证据分两级(C1):
repro:代码/bug 类,附可复现步骤(1. 2. 3.);argument:设计类,附论证链。
并且明令禁止给设计类缺陷编造伪复现步骤。这条很关键——否则模型会为了让证据看起来"硬",写出"运行这条命令即可复现"这种假东西。
规则 3:逐条三态表态,驳回必须写理由。
每个观点独立一张卡:支持 / 驳回 / 取消(三态可互切)。驳回必填理由——这不是找麻烦,而是因为"一键驳回"是逃避思考的最短路径。理由会进 user-action 记录,供主持人修订时对照。
规则 4:闭环复审最多 3 轮(首轮 + 2 次复审),超过需显式批准。
复审时只核对上一轮已认定的缺陷是否修复,禁止引入全新打分项——否则评审会变成无限循环的"每次都有新意见"。
六、四个来自真实翻车的工程细节
这一节是我最想写给掘金读者的部分:都是真事,都有具体症状。
6.1 inject 是加载门禁,不是"可选依赖"
插件曾把可选能力写进了 cordis 的 inject 列表。结果是:宿主少提供任何一个服务,整个插件不加载——工具、数据路由、GUI 页签、设置页一起消失,而且不报错。
用户看到的现象是"页签不见了",没有任何线索指向根因。
修法:必需能力才走 inject;可选能力(skill 目录、用户问答、connection 兜底传输)改成运行时 ctx.get(...) 可选读取。铁律两条:探测绝不成为新的加载门禁;降级必须留痕。
6.2 宿主会静默摘掉抛错的 UI 条目
DSH 对渲染抛错的 slot 条目会静默移除——隔离本身是正确的,但用户只看到"页签消失了"。
修法:两个界面入口都包进错误边界,把崩溃渲染成可读的报错面板 + 控制台完整堆栈,用户可以直接贴 issue。
6.3 自建 Web 路由不会自动带安全栅栏
插件有两条自己的路由(快照 + RPC)。原因是宿主的通用 connection 通道在某些组合下没挂载,只靠它会导致"无法连接会议服务"。
但自建路由有个陷阱:宿主不会自动施加 Host/Origin 与浏览器认证检查。不接的后果是真实的——本机任意网页(DNS rebinding 后同源,或者 text/plain 简单 POST 免预检)都能命中插件路由,删会议、改连线、读走全部评审内容。
修法:路由自己调宿主的 connection.requestRejection()。官方文档原话就是"Apply Connection's Host/Origin checks and browser authentication to another Web route"。另外补了 1 MiB 请求体上限——无上限的请求体等于让任何本机页面把整包塞进内存。
6.4 rc 线上"类型全红"不一定是 API 破坏
对齐宿主版本后第一次跑 tsc,60+ 处报错:
Property 'subagents' does not exist on type 'Context'
看起来像宿主改了 API。实际根因是:同一个 @deepseek-ai/cordis 存在两份物理副本(宿主一份、插件一份)。TypeScript 视为两个不同模块,于是 declare module '@deepseek-ai/cordis' { interface Context { ... } } 的声明合并失效。
把插件指向宿主那一份之后,tsc 0 错误。
教训:版本升级后类型全红,先查混装,再怀疑 API。这个坑现在被写进了插件的只读诊断脚本(doctor),它会主动比对两条路径,不一致时直接把上面这段解释打给你。
七、两个诚实故事
7.1 数字口径(上面第四节已讲,此处不重复)
7.2 评审工具自己在评审中暴露的归集缺陷
、它确实是插件的已知短板。
针锋相对评审有一个"观点自动拆分":专家一条发言里往往写了 3 条缺陷,系统要把它们拆成 3 张独立观点卡,用户才能逐条表态。
拆分优先走 LLM,失败则降级到本地零 token 的标记切分。
在一次真实评审里,出现了这样的结果:
| 观测 | 数值 |
|---|---|
| 拆分请求 | 4 次 |
| 产出的观点数 | 5 条 |
| 其中 | 最短的那条发言被成功拆成 2 条;三条长发言各被压成 1 条 |
也就是说:三位专家各写 3 条缺陷,最后只形成了 3 张大卡(每张里塞着 3 条缺陷),表态粒度被悄悄变粗了。
根因在降级路径:本地兜底识别的标记格式是
观点 N(指向 X,维度Y)
而专家实际写出的是
**缺陷 1:……**
主路径(LLM)与兜底路径(本地标记)对"什么算一条观点"的定义不一致。当 LLM 路径失败时,系统并没有"降级到同等结构",而是静默改变了输出语义。
教训(我认为这是本文最值得抄走的一条):
降级路径必须产出与主路径同构的结果。否则"兜底"不是保底,而是悄悄换了一套语义——而用户只会看到"观点变少了",永远不会知道为什么。
我最后的处理是:按发言原文手工重排成 9 条独立观点,并逐条校验"原文引用"确实是发言原文的子串(拆分器的第三道防线本来就在做这件事)。
未来的修法(还没做,如实记在这里):让本地兜底同时识别 缺陷 N: 这类常见写法,或者在降级发生时在界面上明确标注"本条未拆分"——宁可让用户看到粗糙,也不要让他看到被悄悄合并的结果。
八、一个被红队否掉的方向:把拓扑图升级成流程图
这部分我想单独讲,因为"决定不做什么"比"做了什么"更能体现设计判断。
想法:现在的拓扑图是"会议状态的投影"——先开会,图跟着变。它很好看,但读完你说不出"谁把什么交给了谁、下一步去哪"。于是我想把它升级成流程图:开会前先画蓝图,会议按图执行;节点升级成带"输入/处理/输出"的步骤;连线升级成带数据标注和条件分支的流转;蓝图可存成模板分享。
听起来很顺,对吧?
我把它交给三位专家做红队评审(架构 / 产品 / 成本三个攻击面),交回 9 条缺陷,全部认定为真。挑三条最致命的:
- 状态机与持久子代理的生命周期冲突:草案定义
pending/running/done/failed,但专家是持久子代理,做完一轮回到空闲、还能被再次唤醒——done在这个系统里根本不存在。要么把专家降级成一次性子代理(丢掉多轮追问能力),要么引入"步骤实例 / 专家会话"双层抽象(复杂度暴涨)。 - 连线的"流转"语义与平等模式互斥:"上游哪段产出 → 下游哪个输入"是确定性数据流;而平等模式下专家之间是点对点自由辩论,消息内容由发送方推理动态决定、接收方自主解读,不存在预定义输入槽位。要按蓝图校验消息,就得拦截、阻断或改写——要么破坏专家自治,要么让流程大量卡在校验失败上。
- 成本被严重低估:草案用一行话带过"节点状态扩展为 pending/running/done/failed;失败按节点配置重试或走兜底分支"。但当时没有执行引擎、没有节点级重试/超时/兜底,会议推进靠主持人手动派发。要实现那行字,需要新建:主动调度器、每节点超时与重试循环、三种兜底路由(继续/跳过/交给人)、条件分支判定模块——这是一个迷你工作流引擎,保守 2~3 周全职后端;再加上编辑器前端(端口吸附、贝塞尔拖拽、数据映射可视化配置)3~4 周。而这个草案在同一页里写着"不另做独立编辑器"——自相矛盾。
决定:不做。
但这次评审不是白做,因为它逼我把"图到底该怎么读"想清楚了。最后落地的是三件不涉及流程图的事:
- 画布图例:把"这张图在当前模式下怎么读"直接写在画布上(统筹=主持人逐次派发 / 平等=专家直达互辩 / 红队=只挑毛病),并标注虚线是系统补的骨架通道、不是真实流转。起因正是缺陷 1、2 的共同内核:同一张通道图在三种模式下含义完全不同,不写清楚,用户就会把它读成一张控制流程的路径图。
- 节点状态如实显示:直接显示持久子代理的真实状态——工作中 / 空闲(可续聊)/ 就绪(可唤醒)/ 未唤醒 / 已退出。刻意不发明"已完成 / 已失败",因为子代理跑完一轮回到空闲、仍可被再次唤醒,说它"结束了"是错的。这是缺陷 1 反过来给的启示:既然不引入执行状态机,界面就该更贴真相。
- 阵容预设:一次套用多位专家("方案评审三人组"这类固定组合)。它只回答"谁来开会"——没有节点顺序、没有连线、没有分支。缺陷 2 指出"模板与角色预设边界不清",所以这次我把边界同时写进类型注释、设置页提示文案和 README,从三个地方堵死它演化成"又一个会议模板"的可能。
方法论:一个方案被否掉的时候,别急着扔掉整份文档。把"为什么它不成立"拆出来,里面往往有几条与方案无关的、独立成立的改进。
九、装起来
插件不发布到 npm(作者拿不到 npm 账户,registry 上不存在这个包),只能从源码装:
git clone https://github.com/9931666/dsh-plugin-roundtable
cd dsh-plugin-roundtable
pnpm install && pnpm build
dsh plugin --profile web add .
装完重启 DSH → 刷新 Web UI → 设置里能看到「圆桌会议」即可。
宿主基线 DeepSeek Harness 0.1.5-rc.3;MIT 许可。
它是什么:把一个 DSH 会话变成一场可视化、可辩论、可拍板的圆桌会议。三种模式(主持人统筹 / 多模型平等 / 针锋相对)、汇聚网关、人类决策卡片、知识库中转与摘要缓存、预算熔断与闭麦、整场 Markdown 导出、匿名反馈(只记模式/模型/轮数/时间戳 + 你主动填的一句话,绝不记对话内容)。
十、已知欠账(诚实清单)
写文章最容易犯的错是只讲优点,所以我先把欠账列出来:
- 前端零测试:
RoundTableView.tsx有 1700+ 行,没有测试覆盖——因为需要先把纯逻辑从组件里抽出来。纯函数层(预算、状态、净化、拆分)有测试,界面没有。 - 只验证过一个宿主版本(0.1.5-rc.3)。兼容矩阵是单一来源、有脚本强制一致,但矩阵里目前只有一条。
- 不发 npm,升级靠
git pull && pnpm build。 - 观点拆分对长发言不稳(见 7.2),这是当前最该修的一条。

结语
用 AI 评审 AI,最容易走偏的地方是把它做成"多问几遍"。真正让结果可用的是三个约束:审查者只输出缺陷、每条缺陷必须带证据、由人逐条拍板并把理由落盘。
剩下的都是工程:让状态落盘、让写者唯一、让降级同构、让成本口径诚实。
你只负责抛出议题。

浙公网安备 33010602011771号