飞书个人版通过 API 建群实现多 Agent 消息分流

飞书的个人免费用户可以创建企业租户来开发自建应用,这意味着一个人也能拥有一整套开放平台的能力:机器人、多维表格 API、事件订阅。听起来很完整,直到你试图建一个群聊,界面提示你至少需要选择一个其他成员。

租户里只有你自己。没有"其他成员"可选。

于是所有 Agent 的推送(健康报告、任务早报、日常对话)全部涌入同一个私聊窗口,像一条河流被迫穿过同一座桥洞。消息量一大,找什么都要翻半天。

曾经想过用 Discord 来解决这个问题,毕竟频道机制天生适合消息分类。但仔细一算,网络可靠性、推送延迟、注意力在两个平台之间的来回切换,代价并不小。实际上飞书群聊本身就能做到同样的事,只是界面上的那道人数校验挡住了路。

好在 API 没有这道门槛。

方案

用机器人的 tenant_access_token 调用飞书 Open API 直接创建群聊。机器人作为创建者自动成为群成员,再通过 API 把自己拉进去。整个过程不需要第二个人参与。

获取 tenant_access_token

1
2
3
4
5
6
7
TENANT_TOKEN=$(curl -s -X POST \
  "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "<你的 app_id>",
    "app_secret": "<你的 app_secret>"
  }' | jq -r '.tenant_access_token')

创建群聊

1
2
3
4
5
6
7
8
9
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/chats" \
  -H "Authorization: Bearer ${TENANT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "📋 任务管理",
    "description": "任务早报与周回顾",
    "chat_mode": "group",
    "chat_type": "private"
  }'

返回结果中包含 chat_id(格式为 oc_xxx),后续投递消息需要用到这个标识。

将用户拉入群聊

1
2
3
4
5
6
7
curl -s -X POST \
  "https://open.feishu.cn/open-apis/im/v1/chats/${CHAT_ID}/members" \
  -H "Authorization: Bearer ${TENANT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "id_list": ["<用户的 open_id>"]
  }'

id_list 中传入用户的 open_id(格式为 ou_xxx)。三步下来,一个只有你和机器人的群就建好了。

实际应用:定时推送分流

我用多个 AI Agent 管理日常事务,每个 Agent 有各自的定时推送任务。将推送的投递目标从私聊改为群聊,只需要修改一个字段:

1
2
3
4
5
6
7
{
  "delivery": {
    "mode": "announce",
    "channel": "feishu",
    "to": "chat:oc_xxxxx"
  }
}

touser:ou_xxx(私聊)改为 chat:oc_xxx(群聊),消息就会投到对应的群里。

最终的分流效果:

群聊接收内容
💚 健康助手早间健康报告、下午活动检查、晚间小结、周报
📋 任务管理每日任务早报、周五回顾
私聊窗口仅保留日常对话

和 Discord 的频道分离异曲同工,但不需要额外的平台,推送也不会因为网络波动而迟到。

注意事项

  1. 群聊中机器人默认只响应被 @ 的消息。用户在群里发送普通消息,机器人只记录不回复。需要它响应时必须 @机器人名称。
  2. 权限要求:应用需要申请 im:chat(创建群聊)和 im:chat:member(管理群成员)权限。
  3. 应用状态:自建应用必须已发布,且已开启"机器人"能力,否则无法加入群聊。
  4. 群类型限制:自建应用只能加入内部群(chat_type: private),不能加入跨租户的外部群。

一点感想

这件事的启发其实不限于飞书。很多 SaaS 产品的界面会施加一些看似合理的限制("建群至少需要两人"),但它们的 API 往往没有同样的约束。界面是为大多数场景设计的,而 API 是为所有可能性开放的。当界面说"不行"的时候,值得去翻一翻 API 文档。门可能只是虚掩着。