一篇关于「AI 怎么在聊天软件里成为一个正式角色」的实践拆解。
我每天都在飞书里跟我的 AI Agent 协作:发消息派活、在文档里 @ 它改稿、点卡片批准命令。这套体验不是「接了个机器人」,而是一整个平台接入层的设计。这篇文章拆一拆 Hermes Agent(开源)的飞书接入层是怎么做的,以及我用下来踩过的坑和还欠着的优化。
一、为什么要把飞书当 AI 的操作台
做 AI 工具的人有个共识:AI 最好长在用户已经在的地方,而不是让用户多开一个工具。
对我来说,那个「已经在的地方」是飞书——上班开着它,手机上也有它,同事、文档、日程全在里面。于是我把自己的 AI Agent 接进了飞书,日常用法就三种:
- DM 里直接发消息:派活、问问题、传文件,像跟同事聊天一样;
- 文档里 @ 它:在飞书写完的东西,选中文字评论 @ 一下,AI 读完上下文直接在评论线程里回;
- 点卡片做决策:AI 要跑危险命令时,发一张「允许一次 / 本次会话 / 总是允许」的卡片,我点一下按钮就完成授权。
体验好的背后是大量看不见的设计。这篇拆的是 Hermes Agent(NousResearch 的开源项目)里飞书平台接入层的实现——一个 5700 多行的适配器,加上配套的文档评论智能体,再加上我实际使用中的配置和踩坑。
二、先对齐概念:平台接入层,不是 MCP,也不是 Harness
之前写过一篇《从 MCP 到 Harness》,把工具生态分成了两层:MCP 解决「AI 怎么连上能力」,harness 解决「不绑项目、产出结构化」。这次要补上第三格:平台接入层。
| 层 | 回答的问题 | 例子 |
|---|---|---|
| 能力提供方 | 产品本身提供什么 | 飞书、蓝湖、Lookin |
| MCP(协议层) | AI 怎么以统一方式调能力 | lanhu-mcp、Lookin MCP |
| 项目级集成 | 某个项目怎么接 | 项目根 .mcp.json |
| 项目无关 harness | 不绑项目、CLI/模板/产出目录 | lanhu-harness、lookin-harness |
| 平台接入层 | AI 怎么成为聊天软件里的一个角色 | Hermes 飞书适配器 |
判定口诀:MCP 管「工具调用」,接入层管「消息往来」——收消息、发消息、处理按钮、维护会话。两者不冲突,甚至可以组合:我这套的文档评论智能体,本质就是「接入层接到评论事件 + 工具集读取文档 + LLM 回复」。
三、架构拆解:四层设计
1. 连接层:长连接优先,webhook 兜底
两种模式二选一:
- WebSocket 长连接(推荐):Hermes 主动向外连飞书,不需要公网地址、不需要配回调 URL,SDK 自带心跳和自动重连。个人电脑跑 Agent 首选。
- Webhook:需要公网可达的 HTTP 端点(
/feishu/webhook),适合已有服务器的场景。
webhook 模式的安全设计值得抄:支持 encrypt_key 签名校验 + verification_token 双重验证,飞书推送的每个请求都要验签,验不过直接 401。URL 验证的 challenge 自动应答,但应答被 token 门控——防止有人拿伪造请求证明自己控制了这个端点。
2. 消息层:入站统一模型,出站 Markdown 渲染
入站:飞书的消息类型非常多——纯文本、富文本(post)、图片、文件、音频、合并转发、群名片、交互卡片。接入层做的第一件事是把它们全部规范化成一个统一的消息模型:文本内容 + 媒体引用 + @ 提及 + 类型标记。富文本 post 会被解析成可读文本,图片/文件提取出资源引用,@_all 这种 SDK 偶发漏报的提及也有兜底。
出站:反过来,AI 回复的 Markdown 会被渲染成飞书的富文本卡片——标题、列表、代码块、链接都有对应的 post 元素;长消息按 ~4000 字符自动分块,避免一条消息被截断;图片、文件走上传后发送。
3. 会话与门控:谁、在哪儿、什么时候能触发
这是接入层最容易做砸的部分,它的设计:
- **DM 全响应,群聊必须 @**:私聊直接回;群聊只有被 @(或 @all)才处理,避免在群里乱插话。群策略三档:
open(任何人 @ 都回)/allowlist(只回白名单里的人)/disabled(完全不理群消息)。 - 共享群按人隔离会话:一个群里多个人用同一个 bot,每个人的上下文互不串——这是「群里开会」能用的关键。
- 消息 ID 持久化去重:message_id 落盘,WebSocket 和 webhook 双通道都不会重复处理同一条消息。
- bot 身份自动识别:启动时自动探测自己的 open_id 和名字,@mention 门控靠它判断「是不是在叫我」。
4. 交互层:把「点按钮」变成「下指令」
聊天软件最强的能力不是发消息,是交互。这层做了三件事:
- 审批卡片:Agent 要跑危险命令时,发一张带「允许一次 / 本次会话 / 总是允许 / 拒绝」按钮的卡片。用户点一下,卡片回调变成一条命令事件走正常命令管线——授权过程对 AI 完全透明。按钮有 15 分钟去重窗口,防止双击重复处理。
- update 确认卡片:
hermes update这类需要确认的操作,用飞书原生 Yes/No 卡片,点完卡片内联更新成已决状态。 - 表情回应当「正在输入」:飞书 API 没有 typing indicator,实现就绕了一下——AI 工作时给自己发的消息加一个「处理中」表情回应,完成后撤掉。用户看到的反馈和别的平台的「正在输入」一样自然。
四、亮点:文档评论智能体——AI 长到文档里
聊天接入只是第一步。这套接入层最有意思的是文档评论智能体:飞书文档里,任何人选中文字评论并 @ 机器人,Hermes 会:
- 收到
drive.notice.comment_add_v1事件; - 并行拉取文档全文 + 评论时间线(整篇评论 20 条、选区评论 12 条);
- LLM 基于「文档内容 + 当前评论线程」生成回复;
- 分块(4000 字符)以线程回复的形式贴回评论里;
- 按文档缓存会话上下文(1 小时 / 50 条上限),连续追问不丢前文。
权限是三层访问控制:精确文档 → 通配符规则 → 顶层默认,每层两种策略:allowlist(静态名单)或 pairing(静态名单 ∪ 运行时授权)。规则存在 JSON 里、mtime 热加载——改配置不用重启网关,还有 CLI 可以直接查规则、模拟权限检查、管理运行时授权。
意义在于:AI 从「聊天窗口里」扩展到了「工作文档里」。人在哪,AI 就在哪,不用把内容搬来搬去。
五、实测踩坑(真机实证)
接入层的坑不在「连不上」,在「看似能用但悄悄丢东西」:
- 富文本消息里内嵌文件收不到。 有一次我在飞书里发一条带文件的富文本消息(客户端会把文件包进 post 里),Agent 这边只收到一条占位文本
[Rich text message],文件根本没解析出来。排查后确认:post 解析器对普通 file 块有兜底提取,但这种「富文本内嵌文件」的结构没覆盖到,链路在解析或下载环节就丢了。这是目前最值得修的缺口——用户以为发了文件,AI 只看到一行字。 - 卡片按钮 200340:配置了事件 ≠ 配置了回调。 飞书里「事件订阅」和「回调配置」是两个独立的 tab。只订阅了消息事件、没配
card.action.trigger回调、或没发布新版本——卡片能正常发出去(发卡片只要发消息权限),但用户一点按钮就报 200340,点击根本到不了 Agent,日志里什么都没有。坑就坑在「卡片看起来是好的」。 - 开发期宽松配置的风险。 自己机器上跑,
allow_all_users+ 群聊open策略很爽。但一旦把 bot 挂到公网或拉进更多群,这就是裸奔——任何人 @ 都能触发。上线前必须收紧到 allowlist + 群策略allowlist。
六、优化方向(如实评估)
用了一段时间,欠着的优化分两层:
接入层本身(开源项目层面):
adapter.py5700 行单体,post 解析、Markdown 渲染、事件分发全在一个文件,是重构候选;- 富文本内嵌文件链路需要补全(上面第 5 节的实证缺口);
- 飞书支持消息更新,理论上可以做流式输出,目前未启用(用表情回应模拟「正在输入」已经是替代方案)。
使用层(我自己):
- 凭据从明文配置挪到
.env/ 钥匙串; - 生产前把
allow_all_users收紧成 allowlist; - 复杂富文本(表格、高亮)入站会退化成纯文本,这类内容要靠文档工具而不是聊天消息传。
七、写在最后
这套接入层是开源项目的一部分,不是我闭门造的车——但「把它用成自己的操作台」这件事,配置、流程、坑,都是自己踩出来的。目前还在日常打磨中,暂时不封装成独立项目发布。
不过方向是明确的:接入层会成为每个 AI 工作流的地基。MCP 解决「AI 有没有手」,接入层解决「AI 有没有工位」——而工位,应该长在用户每天打开的那个软件里。等坑填得差不多了,大概率会把配置模板和使用经验整理共享出来,欢迎到时候来拍砖。
(文中涉及的 Hermes Agent 为开源项目,本文基于其公开代码与个人使用实践,未包含任何私密配置。飞书为字节跳动旗下产品。)