消息全链路跟踪
跟踪一条消息从用户在 IM 中发送,到收到 AI 回复的完整旅程。
每个环节标注关键代码入口,方便你对照源码阅读。
这里以 WhatsApp 扩展渠道 作为代表场景进行阐述。
全景概览
用户在 WhatsApp 发送 "明天天气怎么样?"
│
▼
① 渠道接收 WhatsApp Web 服务端接收 webhook/事件 → 解析产生初步统一格式 MsgContext (或 InboundEnvelope)
│
▼
② 路由决策 resolveAgentRoute() → 匹配 binding 规则 → 确定 agentId + sessionKey
│
▼
③ 上下文与防抖 类型收窄至 FinalizedMsgContext,加载历史会话并补充 ThreadHistoryBody 等
│
▼
④ Agent 执行 通过 Agent 引擎交互模型 → 产生思考日志 + 触发工具调用 (如 WhatsApp Web 操作工具)
│
▼
⑤ 发送与分发 统一 Dispatcher 缓冲包装,调用底层 WhatsApp Outbound 适配器转化为特定结构体发至客户端
│
▼
⑥ 状态保存 会话追加写入 JSONL + 记忆索引更新
第 ① 步:渠道接收消息
做了什么 :把各个平台如 WhatsApp 的各异构消息格式抹平,转换成系统能统一处理的基础结构模型(如 MsgContext)。
关键代码:
extensions/whatsapp/src/active-listener.ts或渠道的messagingAdapter.onInbound()src/plugin-sdk/inbound-envelope.ts
数据在节点上的演化:
typescript
// 1. 输入侧原生数据:WhatsApp Web 或者 API 传来的异构 Payload
{
type: 'chat',
body: '明天天气怎么样?',
from: '...'
// 包含图片甚至音频的二进制流附带
}
// 2. 管道输出结构:初步提取处理成统一上下文 MsgContext
{
Body: '明天天气怎么样?',
From: '...',
Provider: 'whatsapp',
AccountId: 'bot1'
// 媒体会被提取出来保存至文件系统,附加上 MediaPath 以供接下来处理
}
具体过程:
- WhatsApp 平台监听到新消息,传入原始事件对象。
- 渠道适配器提取核心文字、图片引用等(屏蔽不同 SDK 差异)。
- 进行初始的白名单与访问黑白名单基础校验。
第 ② 步:路由决策
做了什么 :通过配置的路由规则决定将这条消息交给哪个(些) Agent 处理,同时为这个会话赋予全系统唯一的 Session Key 标识。
关键代码:
src/routing/resolve-route.ts---resolveAgentRoute()src/routing/session-key.ts
数据在节点上的演化 :
该节点主要通过核心信息查找并建立起一个稳定结构 ResolvedAgentRoute。
typescript
// 核心输出:ResolvedAgentRoute
{
agentId: "main",
channel: "whatsapp",
accountId: "bot1",
sessionKey: "agent:main:whatsapp:bot1:direct:user123", // 用于全系统持久化、高并发资源锁定的凭证
matchedBy: "default" // 这有助于开发者在打印调试时看出匹配的理由
}
具体过程 :
路由函数根据 MsgContext 包含的来源 From、频道等逐级匹配 binding 规则(群组、特权用户、默认映射)。
第 ③ 步:上下文组装与拦截
做了什么 :这阶段不仅是将记忆等内容补充进来,重要的是收窄类型 (Type Narrowing) 和保证单线程的消息队列保护 (Try-finally wrap),防止高频触发。
关键代码:
src/auto-reply/dispatch.ts---finalizeInboundContext()src/agents/context.ts
数据在节点上的演化 :
利用 TypeScript 系统对松散组合字段做规范,强化安全性。
typescript
// 核心输出结构:FinalizedMsgContext
{
// 继承自 MsgContext,且加入了更确定性的权限开关等
...msgContext,
CommandAuthorized: true, // 把原为可选的属性转换成必选,保障后续流程不出界
ThreadHistoryBody: "...", // 从 `~/.openclaw/sessions/` 加载的关联会话组装拼接
}
具体过程 :
系统构建系统的 AI 系统提示词(System Prompts),并调用 ReplyDispatcher 来独占会话处理锁,保障处理这笔消息时如果有其余消息进入则加入等待队列。
第 ④ 步:Agent 执行引擎
这是最核心的模型处理循环环节,由于前置数据已经过标准化及收窄验证,引擎核心不再需要知晓"WhatsApp 是什么"。
执行栈:
dispatchInboundMessage() ← 入口:分发上下文
└→ runReplyAgent() ← 调度具体的 Agent 栈
└→ runAgentTurnWithFallback() ← 容错隔离沙箱
└→ runEmbeddedPiAgent() ← 大模型驱动的调度中心
模型执行循环 (runEmbeddedAttempt):
- 输入模型大纲 :带着
FinalizedMsgContext去调用 Claude / OpenAI。 - 工具响应处理 :遇到模型请求了类似 "whatsapp-web-login" 等
WhatsApp actions工具调用时,打断生成 → 执行工具 → 将结果拼接到会话栈回喂模型。 - 输出收集阶段:生成纯文字或者指令。
关键代码:
src/auto-reply/reply/agent-runner.tssrc/agents/pi-embedded-runner/run.ts
第 ⑤ 步:发送与分发缓冲
做了什么:接收到 AI 输出流后,缓冲合并文本并最终利用下层渠道能力推入真正的聊天框里。
关键代码:
src/auto-reply/reply/provider-dispatcher.ts
数据在节点上的演化:
typescript
// Agent 输出文字 Buffer: ["明", "天", "天", "气", ...]
// ↓
// 通过 ReplyDispatcher 包装缓冲切片(处理各平台的单句最大字数等配置)
// ↓
// 输出回各个 Channels 接口的特定对象,例如:发送至 WhatsApp
await outboundAdapter.send({
channel: 'whatsapp',
to: '...',
body: '明天天气怎么样?的回复内容...'
});
具体过程:
- 过滤掉工具输出细节让回显干练友好。
- 依据 WhatsApp 或其环境的长文本规范自动切分长消息进行发送防吞没。
- 使用
outboundAdapter实际触达平台 SDK。
第 ⑥ 步:状态保存
做了什么 :消息流程尾声,回收所用的缓冲资源、取消并释放资源锁(Dispatcher 释放),刷新对应用户 Session Key 对应的盘总落盘文件(~/.openclaw/sessions/)及执行向量存储更新。
各阶段耗时参考
| 阶段 | 典型耗时 | 说明 |
|---|---|---|
| ① 渠道接收 | < 10ms | 解析 WhatsApp Webhook 并得到 MsgContext |
| ② 路由决策 | < 10ms | binding 匹配出 ResolvedAgentRoute |
| ③ 上下文组装 | 50-200ms | 磁盘读取、锁排队及记忆提取 |
| ④ Agent 首字 | 200-500ms | 模型引擎的预测流启动 |
| ⑤ 回复发送 | < 100ms | 数据结构还原、格式化与分发调用 |
端到端延迟:一般依赖第三方模型的网络速度,平均在 1-5 秒(如果有大量的工具调用则会延长等待)。