我在飞书里养了个“分身”——私聊喊它办事,群里 @ 它干活,还能替我传话

发布时间:2026/6/25 19:21:31
我在飞书里养了个“分身”——私聊喊它办事,群里 @ 它干活,还能替我传话 先给你看一个我每天都在用的画面。我在飞书里私聊一个人帮我看看今天的数据。过几秒它把结果回给我了。我在一个工作群里 它一句把刚才那个结论整理一下发出来。它在群里 着我、引用着我那条消息把整理好的东西回了出来。我又跟它说这事告诉一下隔壁那个群。它真就跑到隔壁群里 上对应的人开口第一句还带着某某找你——办完回来跟我说一声已经同步过去了。你是不是以为我在说某个同事不是。这是我自己接进飞书的一个 AI——我私下管它叫分身。它没有工位、不用发工资但你私聊它、群里 它、让它替你传话跑腿它都接得住。这一篇我换个讲法不光说它像不像个人而是把我到底怎么把它做出来的一段一段、连关键代码都摊给你看。但你别紧张——这篇不是让你去写代码恰恰相反我会告诉你每一块功能该怎么说给 AI 听让它替你写出来。你负责想清楚要什么代码交给它。说明下面的真实代码来自公开仓库ArchAIHarness/feishu-botMIT 许可配置已脱敏基于 2026-04 的 OpenCode 插件机制和飞书开放平台 SDK。API 细节以官方文档为准但怎么跟 AI 把需求说清楚这套方法跟版本无关。一、连接其实是最薄的一层先破一个最常见的误会。一说把 AI 接进飞书大部分人第一反应是这肯定是个大工程吧要对接平台、要处理协议、要搭服务器……我也是这么以为的直到真动手才发现——接进去是整件事里最简单的一步。飞书给开发者留了官方口子你注册一个机器人应用拿到一对钥匙一个app_id、一个app_secret然后让程序跟飞书建一条长连接飞书那边一有新消息就顺着这条线推给你。这条线叫 WebSocket。你不用记这个词只要知道它的作用它给你的 AI 装了一对耳朵飞书里谁说了话它当场就听见。核心就这么几行——建一个客户端注册一个收到消息的回调constwsClientnewWSClient({appId,appSecret});constdispatchernewEventDispatcher({}).register({im.message.receive_v1:async(data){constmsg(data.event||data).message;// 收到一条消息了下面开始处理……},});wsClient.start({eventDispatcher:dispatcher});填上那对钥匙、把连接打开、注册一个回调齐活。耳朵就接上了。这段你完全不用自己敲。你打开你的 AI 搭子把需求讲清楚就行比如这么说“我要做一个飞书机器人用飞书官方的larksuiteoapi/node-sdk通过 WebSocket 长连接收消息。帮我写一段读app_id、app_secret建好 WSClient注册im.message.receive_v1事件收到消息时把发送人、群 ID、正文先解析出来。密钥从一个feishu.yaml里读别写死在代码里。”你看这句话里其实就藏着四件事用哪个库、走什么连接、监听哪个事件、密钥放哪。把这四件事说清楚AI 就能给你一段能跑的连接代码。你不需要知道 WSClient 内部怎么握手——那是它该操心的不是你。真正费劲、真正决定这个分身像不像个人的根本不在这一层。那在哪儿在它听见消息之后怎么做事、怎么做人。二、凭什么它是我的分身而不是个陌生人你有没有用过那种一问一答的客服机器人你问一句它答一句下一句它就不记得你刚说过啥了每次都像第一次见面。那种东西谈不上分身顶多是个复读机。我这个分身不一样——你能跟它连着聊。上一句说看看今天的数据下一句直接说那昨天的呢它知道昨天的接的是数据不用你从头交代。怎么做到的说穿了就一句话给每一个聊天单独开一个脑子。落到代码里是这样每来一条消息先按谁在哪儿跟我说话拼出一个标题私聊用对方名字、群聊用群名/发言人然后拿这个标题去找有没有现成的会话有就接着用没有才新建constsessionTitlechatTypep2p?飞书私聊:${senderName}:飞书群聊:${chatName}/${senderName};// 用标题找现成会话找到就复用找不到才新建letsessionexistingSessions.find((s)s.titlesessionTitle);if(!session)sessionawaitclient.session.create({body:{title:sessionTitle}});就这一招分身立刻有了记性跟我私聊是一条线、在 A 群被 是另一条、在 B 群又是一条各记各的互不串味。它在私聊里记得我们聊到哪了到了群里又不会把私聊的事抖出来——这正是一个靠谱的人该有的分寸。你要让 AI 写这一块关键是把会话怎么隔离讲明白“每条飞书消息进来按场景拼一个会话标题私聊用对方名字群聊用’群名/发言人’。先拿这个标题去已有会话里找找到就复用、把新消息追加进去找不到再新建。目的是让私聊、不同群各自独立记忆互不干扰。”注意我没让它做个记忆系统——那种说法太大AI 容易给你整一套数据库。我只说了按什么维度区分、找不到才新建它就知道该用会话标题做 key 去复用。把隔离的维度说准比堆术语管用得多。三、它的耳朵很挑——这是我特意调的你可能担心把 AI 塞进群里它会不会逮谁说话都插嘴把群搅得鸡飞狗跳不会。因为它的耳朵是我特意调挑剔的。在收到消息那个回调里我加了三道过滤缺一道它都可能变成群里的灾难。第一太旧的消息直接扔。这条我放在最前面是血泪教训constmsgTimeparseInt(msg.create_time,10);if(msgTimeDate.now()-msgTime60000)return;// 超过 1 分钟的旧消息丢弃设想一下程序半夜重启了一次飞书会把它失联那段时间积压的消息呼啦啦全补推过来。要是它照单全收、一条条都去回那就是凌晨三点把整个群挨个 一遍的社死现场。所以我给它定死太旧的不是任务是历史看一眼就扔。第二群里没 它就装没听见。if(chatTypegroup){constbotMentionedmentions.some((m)/* 被 的是不是机器人 */);if(!botMentioned)return;// 群里没点我名不理}群是用来聊天的它要是句句都接那不叫助手叫话痨。私聊不用这条因为私聊里只有你俩每句都是冲它说的。第三发太快就自己踩刹车。它对外发消息有节制——每秒最多几条、每分钟最多上百条到顶就自己等一下。这不是抠门是怕一股脑发太猛被平台当异常限制了。你看这三条没一条是高科技全是怎么做个有眼力见儿的人。难的从来不是技术是分寸。怎么让 AI 把这层耳朵装上我的经验是别笼统说做好健壮性要把每条规矩连同为什么一起讲——讲清后果AI 才知道轻重“在收消息的回调里加三道过滤① 消息创建时间超过 60 秒的直接丢弃防止程序重启后积压消息被补推、半夜炸群② 群聊消息只有在机器人被 时才处理私聊不限制③ 给发送消息加限流每秒最多 5 条、每分钟最多 100 条超了就排队等避免被飞书限流。”把会出什么事说给它听它写出来的过滤就带着判断而不是一堆没灵魂的 if。四、给它立规矩私聊、群聊、传话各有各的章法耳朵调好了接下来是最花心思的部分——教它在不同场合怎么待人接物。这部分我没写一行复杂代码就是拿大白话把规矩讲给它听写进一份叫AGENTS.md的行为说明里。我把它要会的场面归成三种场景一私聊喊它办事。你私信发指令它办完私信回结果。一对一干脆利落。场景二群里 它干活。群里 它发指令它办完要在群里 回你、并且引用你那条原始消息——这样别人一看就知道它在回谁、回的哪件事。场景三让它替你传话。你让它把结果转发到另一个群或某个人它转过去时开口带一句谁找你——比如老王找你今天的数据是这样……“传完再回来跟你说已经同步给老王了”。这套规矩在AGENTS.md里长这样几乎就是大白话### 场景3群聊指令 群聊中 机器人发指令在群里 发送方回复结果并引用原消息。 指令要求转发到其他群/人时转发时告知接收人是谁找的 - 用户在群里 机器人把结果发 yyy 群里 → 在 yyy 群 接收人发xxx 找你结果…… → 群里 发送方回复消息已同步 yyy 群你发现没有这三套规矩跟你培训一个新来的助理讲的是一模一样的东西什么场合公开说、什么场合私下说、替人带话要报上是谁托的。AI 不会天生懂这些得有人给它立规矩。立规矩的是人照着做的是它。这一块恰恰最不用写代码——你说出来的规矩本身就是程序的一部分。你要做的是把什么场合怎么回想清楚一条条写成大白话喂给它。想让 AI 帮你拟这份说明可以这么开口“帮我写一份飞书助手的行为说明AGENTS.md分三个场景① 私聊发指令办完私信回② 群里 我发指令我办完在群里 回发送方并引用原消息③ 让我转发给别的群或人时转发时先报’谁找你’转完再回来告诉发送方已同步。每个场景给一个对话示例。”写完你自己读一遍像不像在交代一个新人像就对了。五、给它几只手——但别让它自己瞎抓光有规矩还不够。规矩是该怎么做人可它总得有几只手去真的把事做了。我给它配了四只工具每只管一摊名字一看就懂工具干什么feishu_send_message发消息私聊或群里群里能自动 人feishu_search_user按名字找人拿到对方的真实 IDfeishu_list_chats列出它在哪些群里feishu_list_chat_members看某个群里都有谁在 OpenCode 里一只手就是一段带说明的小函数。拿发消息这只手举例骨架是这样feishu_send_message:tool({description:发送飞书消息。群聊 人传 at_names人名逗号分隔工具会自动查 open_id。,args:{receive_id:tool.schema.string(),content:tool.schema.string(),at_names:tool.schema.string(),// 要 的人名reply_to:tool.schema.string(),// 引用哪条原消息},asyncexecute(args){// 真正调飞书接口发消息……},}),注意那个description——它不是写给人看的注释是写给 AI 看的说明书。AI 全靠这句话判断什么时候该用这只手、每个参数填什么。这正是上一篇聊插件是聊出来的那回讲过的道理你把这只手的用途和参数讲清楚AI 写出来的就是它自己看得懂、用得对的工具。这里有个我踩过、也最值得你记住的坑发消息之前必须先把发给谁弄准。AI 容易在这儿想当然——群里要 一个人它可能凭感觉去拼一个 标记八成 错人或者 了个不存在的。我的解法是把 谁这件事从 AI 手里收走交给工具自己办。AI 只管给一个人名工具拿着名字先去群成员里找、找不到再翻通讯录查到真实 ID 才拼 // AI 只给人名工具自己去查真实 ID绝不让 AI 手搓 标记const{found,missing}awaitresolveAtNames(args.at_names,chatId);if(missing.length0){return没找到这些人${missing.join(,)}请先确认姓名;}所以我给它的铁律是先查清楚再动手。这跟一个靠谱的人发重要通知前会先核对收件人名单一个道理——宁可多查一步不可错发一条。你要让 AI 把这套手做出来重点是讲清每只手干一件事和动手前先查 ID这条纪律“给这个飞书助手配四个工具各管一摊发消息、按名字搜用户拿 ID、列出所在群、列某群成员。发消息工具要支持群里 人——但不要让模型自己拼 标记而是接收人名工具内部先查群成员、再查通讯录拿到 open_id查不到就报错让它确认姓名。每个工具的 description 写清楚什么时候用、参数怎么填。”你给的不是代码是职责怎么切、纪律怎么定。这些想清楚了AI 落地成函数是顺手的事。六、全篇的题眼凭什么它说完就一定真发出去了现在说到我最得意、也最想掰开揉碎讲给你的一处设计。你想过没有AI 这东西是出了名的会自言自语。你让它去群里回个话它很可能在自己那边洋洋洒洒写了一大段好的我已经回复了……——结果一个字都没真发出去。它以为自己说了群里其实一片寂静。这要是发生在真实办公里就是事故。那怎么治它这个毛病我的答案是——不靠它自觉靠规矩和兜底给它焊死。我设了三道闸一道比一道硬。第一道把话挑明事前。我在它的行为说明里用最重的语气写死一条命令- **绝对禁止只在对话中输出文字**任何回复必须调用 feishu_send_message 通过飞书发送否则对方完全看不到 - 无论什么情况最后一步必须是调用 feishu_send_message这是事先把丑话说在前头。但你也看出来了这只是嘴上约定AI 心情不好照样可能违约。第二道盯着它到底做没做事中。光靠嘴说不够。我在每只手用完之后挂了个埋点专门盯着它有没有真动用发消息那只手tool.execute.after:async(input){if(input.toolfeishu_send_message){feishuToolCalledForSession.add(input.sessionID);// 真发过了打个勾}},动了就打个勾没动这个勾就一直是空的。这一步不打扰 AI只是默默记账。第三道也是最关键的——它要是没做我替它补上事后。当它这一轮活儿干完、安静下来OpenCode 管这叫session.idle程序回头查那个勾要是发现勾是空的——说了一通却没真发——兜底机制立刻启动把它刚才写的最后那段话抓出来替它真正发到该去的地方event:async({event}){if(event.typesession.idle){// 这一轮它调过发消息工具吗调过就放过if(feishuToolCalledForSession.has(sessionId))return;// 没调过把它最后写的那段话抓出来替它发出去consttextContent/* 取最后一条助手回复的文字 */;awaitfeishu.sendMessage(/* 发到该去的人或群 */);}},你品品这三道闸的层次先用规矩劝它事前再盯着它做没做事中最后发现没做就强制补上事后。它干不干得对不全押在它自觉上漏了、错了有一套机制在后面接着。这套思路其实就是这一路我反复念叨的那句话落到了实处人定规矩AI 执行系统兜底审计。一个真正能托付事情的 AI靠的从来不是它有多聪明、多听话而是哪怕它出岔子你也兜得住。这三道闸怎么讲给 AI 让它实现关键是把软约束 硬兜底两层都点出来“我担心模型只在对话里说’已回复’却没真调发送工具。帮我加双保险① 在行为说明里强制写明最后一步必须调 feishu_send_message② 在代码里记录每轮有没有真的调过这个发送工具等这一轮空闲下来session.idle如果发现没调过就自动把它最后那段回复抓出来替它发到对应的人或群。”光说第①条AI 给你的是一句口号把第②条的埋点 空闲兜底也说出来它才会给你一道真正焊死的闸。七、最妙的一步把它装进一个盒子变成能交付的产品到这儿分身已经能在我电脑上跑了。但你想过没有——它只活在我这台开着 OpenCode 的电脑上。我电脑一关它就死了我想给同事用、想丢到服务器上长期跑难道让每个人都装一遍环境、配一遍依赖这就是从我自己能用的玩具到能交给别人的产品之间那道最容易被忽略、其实最关键的坎。我跨过这道坎靠的是一个特别朴素的东西——一份十几行的Dockerfile。它干的事说白了就是把这个分身和它需要的一切打包进一个标准盒子谁拿到盒子谁就能原样跑起来。FROM node:22-slim ENV TZAsia/Shanghai RUN npm install -g opencode-ai # 把 AI 运行时装进盒子 WORKDIR /app COPY .opencode/ .opencode/ # 把工具、规矩、依赖都搬进来 COPY AGENTS.md . # 把怎么做人的说明也带上 EXPOSE 4096 CMD [opencode, web, --port, 4096, --hostname, 0.0.0.0]你仔细读这十几行会发现它把前面所有东西——AI 运行时、四只手、那份待人接物的规矩——全都收进了一个盒子里。最后一行更妙它不是把分身跑成一个黑乎乎的后台进程而是opencode web——直接起成一个带网页界面的服务。这意味着盒子一跑起来你打开浏览器就能看见它、跟它对话、看它在飞书里怎么应对。这一下性质就变了我电脑关不关机不影响它——盒子跑在服务器上它就 7×24 在线同事想用不用配环境——把盒子拉下来一跑就行哪天想搬到别的机器、别的云上整个盒子端走落地即跑。这才是这份 Dockerfile 真正有意思的地方它不是部署的收尾杂活它是那一下点石成金——把一个躺在我硬盘里的脚本变成了一个能复制、能分发、能长期在线服务的轻量产品。一个分身和一支可交付的服务之间差的就是这个盒子。而盒子里还藏着一条安全线特别值得说真正的密钥绝不打进盒子。打包时进去的是一份占位的示例配置真钥匙等盒子跑起来的那一刻才从外部挂进去dockerrun-p4096:4096\-v$PWD/.opencode/feishu.yaml:/app/.opencode/feishu.yaml:ro\archaiharness/feishu-bot这样这个盒子你可以放心大胆地分发、公开——因为里头根本没有秘密秘密永远在运行它的人自己手上。怎么让 AI 帮你打这个盒子把打包成可分发的服务和密钥别进盒子这两件事说清楚“给这个项目写一个 Dockerfile基于 node:22全局装 opencode-ai把 .opencode 目录和 AGENTS.md 拷进去启动命令用 opencode web 起成带网页的服务、暴露 4096 端口。注意真实的 feishu.yaml 密钥配置绝对不要打进镜像镜像里只放示例配置真配置在 docker run 时用 -v 挂载进去。”你交代的还是那两件你真正在乎的事——做成能跑的服务、别把钥匙焊死在盒子里。怎么写FROM、怎么COPYAI 比你熟。八、这套分身换个壳照样能搭讲到这儿你可能会问那我不用飞书用企业微信、钉钉、Slack是不是就白搭了恰恰相反。你回头看看我们一路拆下来的东西会发现真正值钱的部分跟飞书几乎没关系给每个聊天单开一个会话、让它有记性——这跟用哪个软件无关那对挑剔的耳朵——没点名就不理、旧消息就扔、发太快就刹车——换哪个平台都得这么做人三套待人接物的规矩——私聊私信回、群里 着回、传话带上谁找你——这是做人的章法不是飞书的功能三道闸保证说了就一定发出去——这更是跟平台八竿子打不着的治理思路还有那个把一切打包成服务的盒子——换哪个 IM盒子都照打。真正跟飞书绑定的其实只有最表层那一点点连它的 SDK、调它的接口发消息。换成别的 IM把这层壳一换就行里头的做人章法原样照搬。而且这层壳怎么换同样是一句话讲给 AI 的事——把换成钉钉/Slack 的消息事件和发送接口、其余逻辑不动说清楚它就能帮你改。所以你别把它理解成我做了个飞书机器人。你做的是一个能做事、会做人、还兜得住、还能打包交付的 AI 分身——飞书只是它今天住的那间屋子。屋子可以换人是同一个人。九、写在最后回到开头那个画面私聊喊它办事、群里 它干活、让它替我传话。看着挺像科幻拆开了你会发现没有一处是魔法——每一块都是一段能讲清楚的逻辑而每一段逻辑你都能用一句大白话讲给 AI让它替你写出来。它能住在飞书里是因为有人给它装了耳朵、开了记性它能在群里不闯祸是因为有人替它定了分寸它能让你放心托付是因为哪怕它偷懒漏发背后还有一道闸替它补上它能从我的玩具变成谁都能跑的服务是因为有人把它装进了一个盒子。这个分身像不像个靠谱的人、能不能交到别人手上从来不取决于 AI 本身有多强而取决于你把规矩立得有多清楚、把兜底铺得有多扎实、把交付想得有多周全。而这些恰恰都不需要你会写代码——只需要你想清楚然后讲清楚。未来真正会用 AI 的人不一定是会敲多少代码的人而是那种能把这件事该怎么做、做错了怎么兜、怎么交到别人手上想明白、再讲给 AI 去执行的人。AI 负责把话变成代码你负责立规矩、定边界——这俩搭一块儿才有那个让你眼前一亮的分身。下一篇我想带你看一个更野的场景把这套AI 分身的思路从一个办事员扩成一支能从头到尾干活的队伍——一个制片总监带着选题、对标、写稿、剪片、发布几个专员把一条短视频从一个念头做成成片。一个人能使唤的分身已经够爽了那一支能协作的呢咱们下篇见。关于 ArchAIHarness这篇文章是「看懂 AI 与智能体」专栏的一部分由ArchAIHarness持续输出。ArchAIHarness 是一套面向 AI 时代软件工程的人机协同架构哲学与公开工程资产主张架构师定义秩序AI 在秩序中生长。人立法AI 执行体系审计。如果你也希望 AI 在明确的架构边界内协作而不是在混沌中碰运气欢迎到 GitHub 上看看我们在做什么组织主页github.com/ArchAIHarness — 了解完整理念与资产全景本专栏zhuanlan-ai-and-agents— 所有文章的源码与发布记录实践指南docs— 架构哲学、工程方法和落地指南开源工具agent-workflows— 可复用的 AI 协作 Agents、Skills 与 Tools本文样本feishu-bot— 本文拆解的飞书分身真实仓库clone 下来填上你自己的飞书密钥即可跑Engineered by Architects · Empowered by AI · Audited by Discipline