利用 Cursor ACP 模式搭建飞书助手机器人,无需任何大模型接口
飞书里 @ 一下就能让 Agent 查日程、读文档、跑脚本,但我不想在 Python 里再养一套 OpenAI / 智谱 / 通义的 SDK 和 API Key。
之前写过一篇 用 GolemBot 把 Codex 接到飞书机器人,思路是在 Node 生态里拼积木:GolemBot 接 IM,Codex CLI 做推理,lark-cli 读飞书资料。那套能跑,但我用下来有两个别扭的地方。一是主链路在 Node 上,和我平时写 Python 脚本、跑 conda 项目的习惯不太贴;二是 Codex 要单独配 API Key 或 OAuth,跟 Cursor 订阅是两套账。
后来我干脆把飞书助手换成了自己写的 lark-assistant-agent,核心改了一层:消息还是 lark-cli 收,智能回复交给 Cursor CLI 的 ACP 模式(agent acp)。Python 这边只做消息路由和 JSON-RPC,requirements.txt 里就 pyyaml 和 python-dotenv 两个包,一个大模型 SDK 都没有。
下面按这个项目的实际结构,把搭建过程记一遍。
先说清楚「无需大模型接口」是什么意思
这句话容易被理解成「完全不用大模型」。其实不是,准确点说是这样:
- 项目代码不调用任何第三方大模型的 HTTP API,没有
OPENAI_API_KEY,也没有智谱、通义那类 SDK。 - 智能回复走本机的
agent acp子进程,模型和工具都由 Cursor CLI 托管,认证用cursor_login(本机 Cursor 登录态),需要的话再用CURSOR_API_KEY覆盖。 - 有些消息压根不经过模型:
ping回pong、帮助回固定文案、部署类触发词直接跑 bash 脚本。
所以「无需大模型接口」说的是你不用自己去接模型 API,不是把机器人退化成纯规则引擎。开放问答该花的模型能力还是要花,只是这笔账记在 Cursor 订阅里,不落到你的 Python 服务上。
和 GolemBot + Codex 那套比,差异大概是这样:
| 维度 | GolemBot + Codex | lark-assistant-agent + Cursor ACP |
|---|---|---|
| 主语言 | Node(GolemBot) | Python |
| 模型入口 | Codex CLI / API Key | Cursor CLI agent acp |
| 项目内 LLM SDK | 无(但 Codex 侧要配) | 无 |
| Agent 规则 | AGENTS.md | AGENTS.md |
| 飞书读写 | lark-cli | lark-cli |
| 无人值守 | 看 Codex approval 配置 | auto_approve_permissions |
整体架构
链路不复杂,Python 就是薄薄一层胶水:
这张图有三个分叉:
- 本地规则:健康检查、帮助说明,毫秒级就回了。
- 确定性工作流:关键词命中后跑
scripts/deploy-edu.sh,不经过 ACP。 - Cursor ACP:剩下的自然语言问题交给本机 Agent,它能 Shell、读写文件、调 lark-cli。
前置条件
| 组件 | 用途 |
|---|---|
| conda | Python 3.11 环境 |
| npx + @larksuite/cli | 飞书事件消费、消息回复 |
| Cursor CLI | agent 命令,启动 ACP |
| AGENTS.md | Agent 工作区规则(人格、安全边界、lark-cli 约定) |
飞书应用侧需要开通 im:message、im:message:send_as_bot 及事件订阅相关 scope,机器人要加入目标群聊。
lark-cli 初始化:
npx --yes @larksuite/cli config init
Cursor CLI 安装参考官方文档:https://cursor.com/docs/cli/overview。装好后确认:
agent --help
克隆与一键初始化
git clone https://github.com/liqinsi/lark-assistant-agent.git
cd lark-assistant-agent
chmod +x scripts/setup.sh
./scripts/setup.sh
setup.sh 会创建 conda 环境 lark-assistant-agent、检查 npx / agent / AGENTS.md、从 example 复制 config/agent.yaml,并跑一遍自检。
激活环境后启动:
conda activate lark-assistant-agent
python -m src.agent
自检命令:
python -m src.agent --check
单条消息联调(30 秒窗口内往群里发消息):
python -m src.agent --once
# 发「ping」→ pong
# 发其他问题 → Cursor Agent 回复
配置说明
config/agent.yaml 是主配置,从 config/agent.yaml.example 复制而来。关键字段:
identity: bot
event_key: im.message.receive_v1
# 群白名单,留空=全部
allowed_chat_ids: []
ignore_bot_messages: true
trigger_prefix: ""
builtin_replies:
ping: pong
帮助: |
我是飞书小助手,基于 lark-cli + Cursor ACP 运行。
...
acp:
enabled: true
mode: agent # agent | ask
auto_approve_permissions: true
permission_prefer_always: true
几个字段值得单独说一下:
allowed_chat_ids:只响应指定群。生产环境建议填上,别让随便哪个群都能遥控你的开发机。trigger_prefix:设成/就只响应斜杠命令,能少一些误触发。acp.mode:agent能 Shell、能读写文件;ask只对话,适合只想问答、不想让 Agent 碰机器的情况。auto_approve_permissions:飞书这边没人去点 Cursor 弹窗,只能自动批准工具权限。反过来说,这等于放开了 Agent 在本机执行命令,所以只在可信环境开。
可选环境变量见 .env.example:
# LARK_CLI_NPX_PACKAGE=@larksuite/cli
# CURSOR_API_KEY= # 一般不填,用本机 cursor_login
LARK_IDENTITY=bot
ACP 是怎么接进去的
src/acp_client.py 里自己写了个轻量 ACP 客户端,没绑定哪个云厂商。核心就几步:
- 子进程起
agent acp,工作目录指向项目根,这样 Agent 能读到AGENTS.md。 - 走 JSON-RPC 2.0 over stdio:
initialize→authenticate(cursor_login)→session/new。 - 用户消息用
session/prompt发出去,流式收session/update,拼成最终回复。 - 按飞书
chat_id复用 ACP 会话,同一个群里的多轮对话就有上下文了。
每次 prompt 会带上飞书上下文,免得 Agent 不知道自己在哪:
你正在作为飞书小助手运行(见仓库 AGENTS.md)。
...
【飞书上下文】
- chat_id: oc_xxx
- chat_type: group
- sender_id: ou_xxx
【用户消息】
(用户原文)
无人值守的时候,Cursor 会弹权限、问问题、给 Plan,这些都由 Python 侧自动应答:
session/request_permission→ 自动选allow_always或allow_oncecursor/ask_question→ 自动选第一个选项cursor/create_plan→accepted: true
回复引擎 src/reply.py 的分发逻辑很直白,先精确匹配 builtin_replies,匹配不上再走 ACP:
for key, reply in self.builtin_replies.items():
if text == key or text.lower() == key.lower():
return reply.strip()
# ...
return self._sessions().ask(text, chat_id=..., ...)
AGENTS.md 比 Python 那点代码更值得花时间
ACP 在 session/new 时用项目根当 cwd,AGENTS.md 就是 Agent 的操作手册。项目里带了一份模板,按自己场景改就行,但至少这几件事要写清楚:
- 角色:飞书 IM 助手,中文,回复尽量简洁。
- 能做什么:用 Shell、用 lark-cli、读写项目文件。
- 不能做什么:输出密钥,对 lark-cli 高风险写操作擅自加
--yes。 - 飞书上下文各字段的含义(
chat_id/chat_type/sender_id)。 - lark-cli 的调用约定:
npx --yes @larksuite/cli ... --as bot。
这跟 GolemBot 那篇的结论是一致的:胶水代码可以很少,但边界得写清楚。否则 Agent 会在飞书空间里乱试命令,或者权限不够时干脆瞎编内容。
飞书应用怎么配
和 GolemBot 方案差不多,飞书开放平台后台按这个顺序走不太容易漏东西:
这个项目用 lark-cli 长连接消费事件,不是 HTTP 回调,所以不需要公网 IP。event_consumer.py 起的是这条命令:
npx --yes @larksuite/cli event consume im.message.receive_v1 --as bot
stderr 出现 [event] ready event_key=... 后,stdout 开始吐 NDJSON 事件行,agent.py 逐行处理。
回复走原消息线程:
npx --yes @larksuite/cli im +messages-reply --message-id <msg_id> --markdown "..."
内置工作流:不经 LLM 的部署
除了 ping / 帮助,项目还带了个确定性工作流的例子:群里 @ 小助手发「部署AI教育项目」,Python 侧先回一条确认,再跑 scripts/deploy-edu.sh(git pull → 同步 → 健康检查),最后把 Markdown 报告贴回原线程。
这条链路根本不会启动 ACP,大概 1–3 分钟跑完,适合那种「运维动作别交给模型瞎猜」的活。Skill 文档在 .agents/skills/lark-deploy-edu/SKILL.md。
消息分发在 src/agent.py 里优先级高于普通回复:
过滤 → 部署触发词?→ 跑脚本
→ 否则 → ReplyEngine(内置 / ACP)
生产环境:systemd 守护
开发机验证通过后,可以装用户级 systemd 服务:
./scripts/install-systemd.sh
systemctl --user status lark-assistant-agent
journalctl --user -u lark-assistant-agent -f
scripts/run-agent.sh 会补齐 nvm / ~/.local/bin(agent 命令)的 PATH。服务配置了 Restart=on-failure,进程挂了会自动拉起。
飞书里怎么测
从简单到复杂,一步步来:
ping
应立刻收到 pong,说明 lark-cli 收发链路是通的。
帮助
应收到固定说明,不经过 Cursor。
@小助手 用一句话介绍你能做什么
走 ACP,验证 Cursor CLI 和 AGENTS.md 是否生效。
@小助手 用 lark-cli 查一下我今天的日程标题
验证 Agent 能否调 lark-cli(需要 user 身份且已 lark-cli auth login)。
常见问题
agent 命令找不到
which agent
ls ~/.local/bin/agent
systemd 环境下 PATH 经常比交互式 shell 短,看 scripts/run-agent.sh 有没有把 ~/.local/bin 加进去。
收到消息但不回复
ignore_bot_messages是否误过滤;allowed_chat_ids白名单是否包含当前群;trigger_prefix是否要求消息以特定前缀开头;- 飞书应用权限和版本发布是否完成。
ACP 报错或超时
先在本机单独测 Cursor CLI:
cd lark-assistant-agent
agent
再跑 python -m src.agent --check。如果 --check 里 ACP 项失败,先修 CLI 登录,别在飞书群里反复试。
机器人能聊天,但调不了 lark-cli
lark-cli 读个人日历、任务这类资源要用 user 身份,而且用户本人得先登录:
npx --yes @larksuite/cli auth login --recommend
bot 身份只能收发 IM,代替不了用户去读私有资源。所以 AGENTS.md 里要交代清楚,什么情况下该带 --as user。
回复太慢
飞书 IM → Python → 起 ACP 子进程 → Agent 思考 → 可能还要 Shell / lark-cli,整条链路秒级到分钟级都正常。敏感操作可以先用 acp.mode: ask 限制工具,或者把高频问答写进 builtin_replies。
别用 kill -9 杀 lark-cli
README 里专门提过:lark-cli event consume 被强杀可能导致飞书侧长连接状态异常。用 Ctrl+C 或 systemd stop 正常退出。
安全提醒
acp.mode: agent 配上 auto_approve_permissions: true,本质上就是允许远程 IM 触发本机命令执行。这件事得当回事,至少做到:
- 用
allowed_chat_ids限制能触发它的群; AGENTS.md里把禁止事项写死(密钥、批量删除、擅自--yes);.env和config/agent.yaml不进 Git;- 用一个专门的低权限飞书应用,别拿个人超级账号长期挂着;
- 服务器本身的 SSH 和文件权限也要收紧。
它已经不是「聊天机器人」那个量级的风险了,更接近开了一个「远程 Shell 入口」。
小结
lark-assistant-agent 干的事其实很克制:飞书的收发交给 lark-cli,过滤和路由交给 Python,智能和工具交给 Cursor ACP。自己这边不用维护模型 SDK,不用管 token 计费和重试,也不用再造一个 Agent 运行时,这些都甩给 Cursor CLI 了。
跟 GolemBot 方案比,它更适合「我本机本来就在用 Cursor,顺手把飞书入口接上去」的人。代价也很实在:绑在 Cursor 生态上,ACP 协议还得自己跟着 Cursor CLI 的更新走。
我自己用下来比较舒服的分工是,确定性的事(ping、部署、定时报表)走本地规则或脚本,真正开放的问题才丢给 ACP。模型能力当然可以留着,但没必要让它去碰那些本来一行 bash 就能写死的路径。
项目地址:https://github.com/liqinsi/lark-assistant-agent
参考
- lark-assistant-agent: https://github.com/liqinsi/lark-assistant-agent
- 飞书 CLI: https://lark-cli.dev
- Cursor CLI 文档: https://cursor.com/docs/cli/overview
- 站内相关:用 GolemBot 把 Codex 接到飞书机器人
版权声明: 本文首发于 指尖魔法屋-利用 Cursor ACP 模式搭建飞书助手机器人,无需任何大模型接口(https://blog.thinkmoon.cn/post/996-bot-practice-feishu-cursor-acp/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。