RoundTable v1.0.0-rc.1:可辩论、可拍板、可落盘的多模型会议

我让三个大模型互相当红队:一个多模型圆桌插件的架构、踩坑与一次被否掉的方案

先说结论,免得你翻到最后:

  1. 多模型协作 ≠ 多问几个模型。 并列回答解决的是"覆盖率",会议解决的是"收敛"——后者需要主持人、需要持久的专家会话、需要结构化归并。
  2. 真正难的不是让模型说话,是控制权、上下文、成本口径,以及审查者的输出格式。
  3. 本文有 4 个来自真实翻车的工程细节、2 个诚实故事、1 个被红队否掉的产品方向。

项目是 dsh-plugin-roundtable(DeepSeek Harness 插件,MIT,v1.0.0-rc.1)。下面所有数字和坑都是真的。


Screenshot 2026-09-25 002334

一、起点:一次真实的多模型评审

我写了一份方案草案,然后做了一件事:把三个不同厂商的模型拉进同一个会议室,只给一条规矩——只准挑毛病,不准给方案。

  • 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 绕过去;
  • 队列落盘,重启不丢。

代价是有延迟(下一轮才生效),界面上必须如实告知"已排入队列"——这是产品诚实问题,不是技术问题。


Screenshot 2026-09-25 002321

四、成本:为什么必须区分"说了多少"和"花了多少"

这是我踩过的最有教育意义的坑。

插件界面上有一个 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 条缺陷,全部认定为真。挑三条最致命的:

  1. 状态机与持久子代理的生命周期冲突:草案定义 pending/running/done/failed,但专家是持久子代理,做完一轮回到空闲、还能被再次唤醒——done 在这个系统里根本不存在。要么把专家降级成一次性子代理(丢掉多轮追问能力),要么引入"步骤实例 / 专家会话"双层抽象(复杂度暴涨)。
  2. 连线的"流转"语义与平等模式互斥:"上游哪段产出 → 下游哪个输入"是确定性数据流;而平等模式下专家之间是点对点自由辩论,消息内容由发送方推理动态决定、接收方自主解读,不存在预定义输入槽位。要按蓝图校验消息,就得拦截、阻断或改写——要么破坏专家自治,要么让流程大量卡在校验失败上。
  3. 成本被严重低估:草案用一行话带过"节点状态扩展为 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 导出、匿名反馈(只记模式/模型/轮数/时间戳 + 你主动填的一句话,绝不记对话内容)。


十、已知欠账(诚实清单)

写文章最容易犯的错是只讲优点,所以我先把欠账列出来:

  1. 前端零测试:RoundTableView.tsx 有 1700+ 行,没有测试覆盖——因为需要先把纯逻辑从组件里抽出来。纯函数层(预算、状态、净化、拆分)有测试,界面没有。
  2. 只验证过一个宿主版本(0.1.5-rc.3)。兼容矩阵是单一来源、有脚本强制一致,但矩阵里目前只有一条。
  3. 不发 npm,升级靠 git pull && pnpm build。
  4. 观点拆分对长发言不稳(见 7.2),这是当前最该修的一条。

Screenshot 2026-09-25 094633

结语

用 AI 评审 AI,最容易走偏的地方是把它做成"多问几遍"。真正让结果可用的是三个约束:审查者只输出缺陷、每条缺陷必须带证据、由人逐条拍板并把理由落盘。

剩下的都是工程:让状态落盘、让写者唯一、让降级同构、让成本口径诚实。

你只负责抛出议题。

posted @ 2026-09-25 14:19  9931666  阅读(3)  评论(0)    收藏  举报