重读 OpenClaw:个人 AI 助手网关的架构学习笔记
背景:团队正在做一个面向企业的 AI 助手网关,所以再次翻开 OpenClaw 的代码,从头梳理它的架构设计。这是「重读 OpenClaw」系列的第一篇,主要讲整体架构和飞书消息的端到端数据流。
OpenClaw 是什么
一句话概括:OpenClaw 是一个跑在你本地设备上的个人 AI 助手网关。
它把 50+ 个消息渠道(飞书、Telegram、Discord、Slack、微信、QQ……)和 120+ 个 AI 模型提供商(OpenAI、Anthropic、Google、Ollama……)全部打通,让你在任何一个聊天 App 里跟同一个 AI 助手对话。
四个关键词记住它:
• 多通道:同一个 AI,出现在你所有聊天工具里 • 多模型:想用 GPT-4o 还是 Claude,你自己选 • 可插拔:渠道、模型、功能都是插件,按需安装 • 本地优先:数据归你,不经过第三方
整体架构:四层模型
读代码的过程中,我把整个项目抽象成四层:
客户端层(50+ 渠道插件) ↓ 标准化入站事件网关层(HTTP / WebSocket / 认证 / 限流) ↓ 会话 & 上下文编排层(上下文引擎 / 会话管理 / 回复管线) ↓ 模型调用AI 层(120+ 模型提供商 / Agent 配置 / 工具调用)这四层之间的边界非常清晰:每一层都有自己的接口合约,互不越界。这也是 OpenClaw 能做 50+ 渠道接入而不乱的根本原因——不管外面是什么消息平台,进来以后都是同一个内部格式。
飞书消息端到端数据流(WebSocket 模式)
理解架构最好的方式就是跟踪一条消息的完整生命周期。以下是一个飞书用户在群里发 "今天天气怎么样" 后,经过的所有阶段:
第 1 步:WebSocket 长连接监听
OpenClaw 启动后,飞书渠道被激活,默认以 WebSocket 模式连接飞书服务器。断线自动重连,指数退避 1s → 30s 封顶。
不需要公网 IP,这也是本地部署最友好的方式。
第 2 步:事件分发 → 消息接收处理器
WebSocket 收到事件后,飞书 SDK 按 event_type 分发。im.message.receive_v1 事件进入 createFeishuMessageReceiveHandler,依次执行:
1. 解析原始事件 → 标准化为 FeishuMessageEvent2. 去重检查 → 三层防护(内存 + 持久化),防止同一条消息被处理多次 3. 去抖合并 → 同一个人短时间内连发多条文字,合并成一条再给 AI
第 3 步:核心业务处理(12+ 个子步骤)
这是整个链路最复杂的环节,在 handleFeishuMessage 一个函数里完成了:
第 4 步:入站管线(Channel Turn Kernel)
标准化的上下文进入 runChannelTurn,经过分类 → 准入检查 → 防机器人循环 → 写入 SQLite 会话 → 分发。
第 5 步:AI 回复分发
dispatchReplyFromConfig 负责:加载会话历史 → 选择模型 → 构造 prompt → 调用 LLM → ReAct 循环(LLM 调工具 → 结果回来 → 继续 → 最终回复)。
第 6 步:回复发送
通过飞书 API im.message.create 把回复发回群里。支持文本、卡片、富文本等格式。
三个关键设计
1. 去重与去抖
这是生产级消息系统绕不开的问题。
去重(Dedup)——三层防护:
去重的 key 是飞书的 message_id。为什么需要去重?WebSocket 重连时飞书可能重推消息、多账号场景下同一条消息被多个 Bot 收到、Webhook 模式下的 HTTP 重试——这些场景都可能触发重复。
去抖(Debounce)——省 token:
同一个人 300-1000ms 内连续发多条文字,合并成一条再给 AI。图片和控制命令不走去抖。
2. 插件系统
OpenClaw 的插件不是"配置",而是实现了接口合约的独立 npm 包。
插件合约定义了渠道必须实现什么:生命周期、消息收发、事件标准化。飞书插件就是一个 npm 包,实现了这些接口,启动时被动态 import() 加载。
打个比方:OpenClaw 是手机操作系统,定义了 App 应该长什么样(接口标准)。飞书插件就是一个 "飞书 App"。第三方开发者也可以开发插件发布到 ClawHub 市场。
3. Agent 的真相
Agent 不是代码对象,是配置集合体。
agents: list: - id: main model: gpt-4o systemPrompt: 你是默认助手 tools: [web_search, calculator]没有常驻内存的 Agent 实例。消息来了 → 路由选出 agentId → 读取配置 → 拼 prompt → 调 LLM → LLM 按 system prompt 的指示行动 → 返回回复。每次消息处理都是独立的。
Agent 的本质 = 配置 + LLM。大模型按照某些配置输出,就叫一个 Agent。
这也意味着 OpenClaw 没有传统的"意图识别器"。意图识别这件事,完全交给了 LLM 自己——system prompt 就是 Agent 的"宪法",定义了它的身份、能力、边界和终止条件。
路由系统:8 级匹配管道
路由是决定"这条消息交给哪个 Agent 处理"的核心:
① binding.peer → 精确匹配:特定群/人 → 指定 agent② binding.peer.parent → 线程父级匹配③ binding.peer.wildcard → 通配符:所有群/所有私聊④ binding.guild+roles → Discord 服务器+角色⑤ binding.guild → Discord 服务器⑥ binding.team → 团队⑦ binding.account → 账号级别⑧ binding.channel → 渠道兜底↓ 都没命中default agent配置示例:
bindings: - match: { channel: feishu, peer: { kind: group, id: "oc-dev-group" } } agentId: tech-support - match: { channel: feishu } agentId: main路由结果会缓存,避免每次消息都遍历所有规则。
会话设计:Session Key 决定一切
Session Key 是一个字符串,决定了"这是谁跟谁的对话":
DM 模式(main,共享会话): agent:main:__main__DM 模式(per-peer,独立): agent:main:direct:alice_open_id群组模式: agent:main:feishu:group:oc-dev-group话题模式: agent:main:feishu:group_topic:oc-dev-group:thread:root_msg_iddmScope 有四种粒度:main(所有人共享一个会话)、per-peer(每人独立)、per-channel-peer(按渠道分)、per-account-channel-peer(按 Bot 分)。
会话存储在 SQLite 中,每条消息到来时读取历史 → LLM 处理 → 新消息写入。
对我们做企业 AI 网关的启发
1. 插件化是核心:渠道接入的标准化接口是支撑 50+ 渠道的基石。企业场景下渠道可能少一些,但接口合约的思路完全值得借鉴。 2. Agent = 配置:这个认知非常关键。Agent 不需要是常驻进程,配置驱动的方式更轻量、更灵活。 3. 路由是灵魂:8 级匹配管道加缓存,优雅地解决了"不同的人/群用不同的助手"这个需求。 4. 去重去抖不可省:生产环境的消息系统,这两层防护是必须的。少了一层,迟早出问题。 5. 把意图识别交给 LLM:不做传统 NLU 分类器,省掉了一个子系统的维护成本,还获得了更强的理解能力。
这是「重读 OpenClaw」系列的第一篇,主要覆盖整体架构和飞书消息的端到端数据流。下一篇会深入聊插件系统的接口设计、Agent 的配置与动态创建、以及会话存储的具体实现。欢迎关注后续更新。
本文基于 OpenClaw 源码研读整理。如果你也在做类似的 AI 网关项目,欢迎交流。
夜雨聆风