11.2 入站消息规范化

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~560k tokens,输出 ~50k tokens(本章合计)


不同消息平台的消息格式千差万别:Telegram 用 chat.idfrom.username,WhatsApp 用 JID(Jabber ID),Discord 用 snowflake ID,Signal 用 E.164 电话号码。OpenClaw 的入站消息管线必须将这些异构格式统一为内部的 MsgContext 结构,才能让下游的路由、会话管理和 AI 代理处理逻辑不需要关心消息来源。


11.2.1 消息格式统一:文本、图片、音频、视频、文件

统一消息上下文:MsgContext

所有通道的入站消息最终都会被转换为 MsgContext 结构(定义在 src/auto-reply/templating.ts),这是 OpenClaw 内部消息的"通用语言"。MsgContext 的核心字段包括:

字段类别
关键字段
说明

通道标识

Channel, AccountId

来源通道和账号

对话类型

ChatType

"direct" / "group" / "channel"

发送者

From, SenderId, SenderName, SenderUsername, SenderE164

多维度发送者身份

接收者

To

目标标识(通常是 bot 自身)

消息内容

Body, BodyForAgent

原始文本 / 清洗后文本

媒体

MediaPath, MediaUrl, MediaType, MediaPaths, MediaUrls, MediaTypes

附件信息

回复上下文

ReplyToId, ReplyToBody, ReplyToIsQuote

引用的消息

群组信息

GroupSubject, GroupSpace, GroupChannel

群名、空间、频道

线程

ThreadLabel, MessageThreadId

线程标签和 ID

会话标签

ConversationLabel

用于会话路由的人类可读标签

对话类型归一化

入站消息的第一步是对话类型归一化。不同平台对"私聊"和"群聊"的叫法各不相同:

平台
原始类型
归一化后

Telegram

private

direct

Telegram

group / supergroup

group

Telegram

channel

channel

Discord

DM

direct

Discord

GUILD_TEXT

channel

WhatsApp

私聊

direct

WhatsApp

@g.us JID

group

Slack

DM

direct

Slack

channel / group

channel

发送者身份的多维度表示

发送者身份是跨平台差异最大的部分。OpenClaw 用多个互补字段来表示同一个发送者:

衍生解释:E.164 格式

E.164 是国际电信联盟(ITU)定义的电话号码格式标准,以 + 开头,后跟国家代码和号码,最长 15 位数字。例如 +8613812345678(中国手机号)。Signal 和 WhatsApp 都以 E.164 格式标识用户。

不同通道提供不同的字段组合:

通道
SenderId
SenderName
SenderUsername
SenderE164

Telegram

12345678

"张三"

"zhangsan"

WhatsApp

"张三"

+8613812345678

Discord

1234567890123456

"张三"

"zhangsan#1234"

Signal

"张三"

+8613812345678

Slack

U012AB3CD

"张三"

"zhangsan"

发送者标签的生成逻辑(用于系统提示词中的对话标识):

通道特有的目标归一化

每个通道都有自己的目标 ID 格式,需要归一化为统一的内部格式。以 Telegram 和 WhatsApp 为例:

归一化后的目标 ID 统一使用 通道:标识 格式(如 telegram:@usernamewhatsapp:[email protected]),使路由和会话管理可以统一处理。


11.2.2 媒体附件处理

附件解析管线

当入站消息包含图片、文件等附件时,OpenClaw 通过 parseMessageWithAttachments(位于 src/gateway/chat-attachments.ts)进行解析:

这个管线有几个值得注意的设计决策:

  1. 大小限制:默认 5 MB,防止超大文件导致内存溢出。

  2. MIME 类型嗅探:不信任客户端声称的 MIME 类型,而是从实际 base64 数据的头部字节中检测。这防止了类型伪造攻击。

  3. MIME 不匹配处理:当嗅探结果与声称的类型不同时,优先使用嗅探结果并记录警告。

  4. 仅图片:当前只支持图片类型的附件直接传递给 AI 模型(因为模型的多模态能力主要支持图片)。非图片附件被丢弃并记录日志。

衍生解释:MIME 类型嗅探(MIME Sniffing)

MIME(Multipurpose Internet Mail Extensions)类型标识文件的格式,如 image/jpegapplication/pdf。客户端发送的 MIME 类型可能不准确——要么是错误标注,要么是恶意伪造。MIME 嗅探通过读取文件的头部字节(魔数)来检测实际类型。例如 JPEG 文件以 FF D8 FF 开头,PNG 文件以 89 50 4E 47 开头。OpenClaw 使用 detectMime 函数从 base64 数据的前 256 字节中检测实际 MIME 类型。

Data URL 处理

base64 数据可能以 Data URL 格式传入(如 data:image/jpeg;base64,/9j/4AAQ...),需要剥离前缀:

base64 校验

解析后的 base64 数据要通过格式和大小校验:

衍生解释:base64 编码

base64 是一种将二进制数据编码为 ASCII 字符串的方法。它将每 3 个字节(24 位)分成 4 组 6 位,每组映射到一个可打印字符(A-Z、a-z、0-9、+、/),末尾不足时用 = 填充。因此 base64 字符串的长度必须是 4 的倍数,编码后的大小约为原始数据的 4/3 倍。


11.2.3 消息清洗

信封剥离

OpenClaw 在多代理、多通道场景下,消息在转发时会被包裹在"信封"中——前面添加通道和时间戳标记。当这些消息最终到达 AI 模型时,信封需要被剥离,避免干扰模型理解:

信封检测有两个判定条件:

  1. 包含时间戳:如 [Telegram 2025-02-18T08:30Z] 你好 → 剥离后变为 你好

  2. 以通道名开头:如 [WhatsApp zhangsan] 消息内容 → 剥离后变为 消息内容

消息 ID 提示清理

某些消息中可能包含用于回复引用的 [message_id: ...] 标注。这些标注对人类读者和 AI 都是噪音,需要清除:

批量消息清洗

清洗操作可以应用于整个消息历史数组,且只处理用户角色(role === "user")的消息——助手消息和系统消息不需要清洗:

注意这里对 content 字段的处理分为两种情况:

  • 字符串格式:直接对整个字符串执行清洗

  • 数组格式:遍历每个 content block,只对 type === "text" 的块执行清洗

这是因为 Claude API 支持两种消息格式——简单字符串和结构化 content block 数组。OpenClaw 需要兼容两者。

清洗管线的完整流程


本节小结

  1. MsgContext 统一消息结构是所有通道的"通用语言",包含通道标识、发送者身份、消息内容、媒体附件、回复上下文等完整信息。

  2. 发送者身份使用多维度表示(SenderId、SenderName、SenderUsername、SenderE164),适应不同平台的标识体系。E.164 格式用于电话号码标准化。

  3. 附件处理管线执行 base64 校验、MIME 嗅探和大小限制,优先使用嗅探的 MIME 类型而非客户端声称的类型,当前仅支持图片附件直接传递给 AI 模型。

  4. 消息清洗剥离信封前缀和 message_id 标注,且只作用于用户角色的消息,避免干扰 AI 模型的理解。

  5. 通道特有的目标归一化(如 telegram:@usernamewhatsapp:[email protected])将异构 ID 格式统一为 通道:标识 格式,为下游路由提供一致的寻址基础。

Last updated