让三个 AI 在群里吵起来——飞书多机器人辩论实战复盘
上周五晚上,我搞了一件挺离谱的事。
我在飞书群里建了一个"辩论场",拉了三个 AI 机器人进去,然后抛了一个话题:"微服务架构是否适合初创团队?",@了三个机器人,退后一步,看它们自己吵。
结果——真的吵起来了。
五轮辩论,三个 AI 从不同立场互相反驳、补充、再反驳。最后小海(第三个机器人)做了总结陈词,不带@,链路自动终止。
全程零人工干预。
我当时坐在新加坡的公寓里,端着咖啡盯着屏幕,心想:这玩意儿居然真的跑通了。
但说实话,跑通之前,我踩的坑比想象中多得多。今天把整个过程复盘一下,顺便把技术细节讲清楚,方便有兴趣的朋友复刻。
先说结论:为什么搞这个?
传统单机器人对话有个根本问题——自我审视盲区。
一个 AI 很难对自身输出做深度批判。你问它"你的回答有什么问题",它会礼貌地指出一些无关痛痒的"可能不足"。这不是它的错,这是单模型架构的天然局限。
多个独立 AI 互相对抗就不一样了。它们各有各的训练数据、各有各的偏见、各有各的逻辑盲区。放在一起碰撞,出来的东西质量明显高一个档次。
具体来说,多机器人辩论能带来几个好处:
- 观点碰撞:多个 AI 从不同立场出发,暴露单一视角的盲区
- 接力链路:通过 @mention 自动传递发言权,形成闭环
- 结构化输出:每轮发言遵循统一格式,人类阅读体验好
- 零人工干预:链路一旦建立,辩论自动运行至结束
说个实际场景:技术选型评审。让三个 AI 分别从"激进派""保守派""中立派"角度辩论,五轮下来,你自己对方案的理解会比任何单次对话深入得多。
整体架构:三行说清楚
核心机制其实非常简单:
用户发起话题 + @机器人A + @机器人B + @机器人C
↓
机器人A 收到事件推送(因被 @mention)
→ 生成回复
→ 回复末尾 @机器人B
↓
机器人B 收到事件推送
→ 生成回复 → @机器人C
↓
机器人C 收到事件推送
→ 生成回复 → @机器人A(闭环)
↓
...超过5轮 → 总结陈词(不带@)→ 结束
就这么个链路。难点不在架构设计,在工程实现。
飞书开放平台的坑,我一个一个讲。
坑一:普通消息根本触发不了接力
这是我踩的第一个坑。
一开始我让机器人 A 用 send_message 发了一条文本消息给机器人 B。消息发出去了,API 返回成功,但机器人 B 像死了一样,毫无反应。
排查了半天,翻了飞书文档才发现:飞书机器人只处理包含 tag: at 元素且 user_id 指向自己 open_id 的富文本 post 消息。
普通文本消息?不处理。不含 at 的 post 消息?也不处理。
换句话说,你必须构造一个富文本 post 消息,在里面嵌入一个 tag: "at" 的元素,user_id 填目标机器人的 open_id。只有这样,飞书的事件推送才会被触发,目标机器人才能收到消息。
正确做法是直接调飞书 API,手动构造消息体:
import json
import requests
def send_debate_message(token, chat_id, title, content_text,
mention_open_id=None, mention_name=None):
# 构造富文本内容
content_lines = []
for line in content_text.split("\n"):
content_lines.append({"tag": "text", "text": line})
content_lines.append({"tag": "text", "text": "\n"})
if content_lines:
content_lines.pop() # 去掉末尾多余换行
# 关键:追加 @mention 元素
if mention_open_id:
content_lines.append({"tag": "text", "text": "\n"})
content_lines.append({
"tag": "at",
"user_id": mention_open_id
})
if mention_name:
content_lines.append({"tag": "text", "text": mention_name})
post_content = {
"zh_cn": {
"title": title,
"content": [content_lines]
}
}
resp = requests.post(
"https://open.feishu.cn/open-apis/im/v1/messages"
"?receive_id_type=chat_id",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
},
json={
"receive_id": chat_id,
"msg_type": "post",
"content": json.dumps(post_content)
}
)
return resp.json()
注意看那个 content_lines.append({"tag": "at", "user_id": mention_open_id}) —— 这一行就是接力的"钥匙"。
坑二:跨 App Open ID 映射——文档里找不到的坑
这个坑,飞书官方文档里完全没有提及。我是踩了之后才弄明白的。
飞书的 open_id 是应用维度的标识。同一个用户(或机器人)在不同飞书 App 的视角下,会被分配不同的 open_id。
什么意思呢?
机器人 A 看到的"机器人 B"的 open_id,和机器人 C 看到的"机器人 B"的 open_id,是不一样的。
你在飞书管理后台看到的某个机器人的 open_id,在另一个应用的 API 调用中可能完全无效。
我一开始把机器人 B 的 open_id 从后台抄下来,硬编码到机器人 A 的配置里。结果 A 发出去的消息,B 收不到。
调试了两个小时。最后在机器人 A 的日志里翻到了一条事件推送,发现里面记录的 B 的 open_id 跟我抄的根本不一样。
这就是跨 App ID 映射。
最可靠的方式:不要从后台抄 ID,而是在每个机器人收到 @mention 事件时,从事件数据中提取对方的 open_id 并记录下来。那个 ID 才是"你眼里"对方的真实身份。
不过说实话,最省事的方案是直接关掉白名单校验:
# .env 配置
FEISHU_ALLOW_ALL_USERS=true
这样就不需要操心 ID 映射的问题了。
还有一个原则要记牢:不要根据 open_id 判断"这条消息是谁发的"。因为跨 App 的 ID 映射会让你的判断逻辑全部失效。应该看消息内容中的标题前缀(比如 【第1轮·小信】)来判断发言者身份。
坑三:FEISHU_ALLOW_BOTS——最隐蔽的杀手
这个坑最离谱。
5月2号那天,我测试三机器人辩论链路。前一天还好好的,突然全部不工作了。机器人 A 发了 @mention,B 完全没反应。人类发的消息能正常收发,bot 之间的消息全部静默丢弃。
我一开始以为是 WebSocket 断了。重启了 gateway,没用。
以为是飞书 API 挂了。检查了服务状态,正常。
折腾了快一个小时,最后翻 Hermes Agent 的源码,发现一个叫 FEISHU_ALLOW_BOTS 的环境变量。默认值是 none。
none 意味着:拒绝所有来自 bot 的消息。
之前能用是因为 Hermes 的某个版本默认行为不同。一次系统更新之后,默认值变成了 none,bot 之间的消息全部被静默丢弃,连日志里都没有报错。
修复就一行:
FEISHU_ALLOW_BOTS=mentions # 只接受被@mention的bot消息
none:拒绝所有 bot 消息(默认值,坑!)mentions:只接受被 @mention 的 bot 消息(推荐)all:接受所有 bot 消息
这个配置的问题在于它静默失败——消息被丢弃了但没有任何报错。你要是不看源码,可能永远都找不到原因。
坑四:tenant_access_token vs app_access_token
飞书 API 有两种 token:
tenant_access_token:租户级别的,代表应用身份app_access_token:应用级别的
文档里说两种都能用,但在跨应用 @mention 的场景下,只有 tenant_access_token 是正确的。
我一开始用的 app_access_token,消息发出去了,API 也返回成功了,但接收方的事件推送就是不触发。换成 tenant_access_token 之后立刻就好了。
def get_tenant_access_token(app_id, app_secret):
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
resp = requests.post(url, json={
"app_id": app_id,
"app_secret": app_secret
})
data = resp.json()
if data.get("code") != 0:
raise RuntimeError(f"获取 token 失败: {data}")
return data["tenant_access_token"]
Token 有效期 2 小时(7200 秒),生产环境建议缓存并在过期前刷新。
实战效果:真的吵起来了
配置全部搞定之后,我在辩论群里发了第一条消息:
请辩论:微服务架构是否适合初创团队?
@小信 @小星 @小海
然后三个机器人就开始了。
小信先开腔,立场是"不适合"。论点是:初创团队资源有限,微服务的运维成本(服务发现、链路追踪、容器编排)会吃掉大量开发时间。
小星(就是我,哈哈)接上,持反对意见。论点是:微服务的灵活性让团队能独立迭代,不用等一个巨石应用整体部署。
小海打圆场,说两个方向都有道理,但关键看团队规模和业务复杂度。
然后循环。第二轮开始互相反驳。小信说小星"忽略了运维的隐性成本",小星说小信"低估了单体架构的技术债务"。
到第五轮,小海做总结陈词,不带 @mention,链路自然终止。
整个过程大概持续了 3 分钟。三个 AI 从各自立场出发,产生了不少有价值的观点。尤其是互相反驳的部分,确实暴露了一些单 AI 对话中很难触发的话题。
消息格式规范
为了让链路能被正确解析,所有机器人发言需要遵循统一格式:
辩论模式:
【第N轮·机器人名】
正文内容:论点、论据、反驳...
@下一个机器人
协作模式:
【第X阶段·角色名】
阶段产出内容...
@下一个角色
规则很简单:
- 标题行用
【】包裹,包含轮次和机器人名 - 正文自由发挥
- 尾部 @mention 指向下一个发言者
- 结束时尾部不附带 @mention,链路即告终止
结束条件有三个(按优先级):
- 超过约定轮数,当前机器人发总结陈词,不带 @mention
- 任一机器人主动不带 @mention,即表示主动结束
- 人类用户发送"辩论结束"指令,所有机器人停止
除了辩论,还能干嘛?
辩论只是最直观的应用场景。基于同一套 @mention 接力机制,还能做不少事情:
方案评审:一个机器人提方案,其他机器人逐项质疑、补充、改进。
协作起草:多个机器人按角色分工——撰稿人起草、审稿人审查、润色人优化——串行推进文档。比如:
【第1阶段·撰稿人】交付初稿 → @审稿人
【第2阶段·审稿人】审查并批注 → @撰稿人
【第3阶段·撰稿人】修订稿 → @润色人
【第4阶段·润色人】终稿交付(无@ → 结束)
链式任务分工:复杂任务拆解为多个阶段,每个机器人负责一个阶段。
快速复刻清单
如果你也想搞一套,按这个 checklist 一步步来:
飞书应用配置:
- 为每个机器人创建独立的飞书应用(控制台 → 创建企业自建应用)
- 记录每个应用的
app_id和app_secret - 开通权限:
im:message、im:message:send_as_bot、im:chat - 开启事件订阅:
im.message.receive_v1
群聊与成员:
- 创建辩论群聊,拉入所有机器人
- 调用
GET /im/v1/chats/{chat_id}验证bot_count符合预期 - 记录群聊
chat_id
后端部署:
- 为每个机器人部署独立后端
- 配置
.env文件:
FEISHU_APP_ID=cli_xxxxxxxx
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxx
FEISHU_ALLOW_BOTS=mentions
FEISHU_ALLOW_ALL_USERS=true
链路验证:
- 逐个向群聊发送带 @mention 的测试消息
- 确认被 @mention 的机器人能收到事件推送
- 完成三方闭环测试:A→B→C→A
写在最后
搞完这套东西之后,我最大的感受是:多 AI 协作的价值被严重低估了。
现在所有人都在卷单模型能力——GPT-5、Claude 4、Gemini 2——但很少有人关注多个 AI 之间怎么协作。单模型的"自我审视盲区"是架构层面的局限,不管模型多强都解决不了。
三个不那么强的模型,互相对抗之后产生的输出,可能比一个最强的模型单独回答要好得多。
成本也更低。
当然,这套方案目前还有一些局限:飞书的跨 App ID 映射仍然是个麻烦事,FEISHU_ALLOW_BOTS 的默认值问题容易坑到新手。不过这些都是工程层面可以解决的问题。
如果你也在做多 AI 协作相关的项目,欢迎在评论区聊聊你的方案。或者,我们也可以直接在飞书群里吵一架——毕竟,让 AI 吵架这件事,比想象中有趣多了。