跳转到内容

IM 网关适配器架构:统一消息协议

IM 网关适配器架构:统一消息协议

1. 问题定义

AI Agent 平台需要与多种即时通讯平台(IM)集成,每个平台有独特的消息格式、媒体类型、API 风格。如果 Agent 核心逻辑直接处理各平台的原始消息格式,会导致代码极度耦合。IM 网关适配器架构的目标是:将各平台的原始消息统一转换为平台内部的标准消息格式,使 Agent 核心逻辑不感知底层 IM 差异。

2. 适配器抽象接口

核心接口方法

方法说明返回
fetchUpdates()拉取新消息(轮询或 webhook)原始消息列表
sendMessage(threadId, message)发送回复平台消息 ID
formatMessage(rawMessage)格式化原始消息标准消息对象
downloadMedia(attachment)下载媒体文件文件 Buffer

标准消息对象

适配器将平台原始消息转换为统一格式:

// 伪代码:标准消息接口
interface StandardMessage {
id: string; // 平台消息 ID
threadId: string; // 会话/群组 ID
author: { id: string; name: string; avatar?: string };
type: "text" | "image" | "voice" | "video" | "file";
content: string; // 纯文本内容
attachments: Attachment[]; // 媒体附件
timestamp: Date;
state: "received" | "sent" | "updated" | "deleted";
}

3. 格式转换器

格式转换器是适配器中的核心组件,负责将平台特定的消息结构转换为标准格式:

// 伪代码:微信格式转换器
class WechatFormatConverter {
convert(rawMessage: WechatRawMessage): StandardMessage {
return {
id: rawMessage.id,
threadId: this.extractThreadId(rawMessage),
author: {
id: rawMessage.sender_id,
name: rawMessage.sender_name,
},
type: this.detectMessageType(rawMessage),
content: this.extractText(rawMessage),
attachments: this.extractAttachments(rawMessage),
timestamp: new Date(rawMessage.create_time),
};
}
}

平台差异示例

平台图片消息语音消息文件消息
微信image_item.mediavoice_item.text (语音转文字)file_item.file_name
Discordattachments[].url不支持原生语音attachments[].url
Slackfiles[].url_private不支持原生语音files[].url_private
Telegramphoto[] (多尺寸数组)voice_note (OGG)document.file_name

4. 媒体处理架构

CDN 媒体下载

不同平台的媒体访问方式差异显著:

  • 微信:使用 CDN URL + 加密查询参数(encrypt_query_param),需要定期刷新
  • Discord:公开 URL,但有限期(CDN token 过期)
  • Slack:私有 URL,需要 Bearer token 鉴权
  • TelegramgetFile API 获取本地路径

陷阱:微信 CDN 鉴权 token 有过期时间。下载失败时应尝试刷新 token 后重试,而非直接报错。

5. 双向消息流

Webhook vs 轮询

方式优势劣势适用平台
Webhook实时性好、无轮询开销需要公网可达、需要验证签名Discord、Slack、Telegram
轮询无需公网、实现简单延迟取决于轮询间隔微信、飞书

6. 多模态消息处理

IM 平台支持丰富的消息类型,适配器需要处理多模态内容的转换:

// 伪代码:多模态消息转换
function convertMultiModal(rawMessage: RawMessage): StandardMessage[] {
const messages: StandardMessage[] = [];
for (const item of rawMessage.item_list) {
switch (item.type) {
case "text":
messages.push({ type: "text", content: item.text });
break;
case "image":
messages.push({
type: "image",
content: "[image]",
attachments: [{ url: item.mediaUrl, mimeType: item.mimeType }],
});
break;
case "voice":
// 优先使用语音转文本结果
messages.push({
type: "voice",
content: item.transcriptionText || "[voice]",
attachments: [{ url: item.mediaUrl, mimeType: "audio/ogg" }],
});
break;
case "file":
messages.push({
type: "file",
content: `[file: ${item.fileName}]`,
attachments: [{ url: item.mediaUrl, mimeType: item.mimeType, name: item.fileName }],
});
break;
}
}
return messages;
}

关键决策:语音消息优先使用转文本结果作为 content,保留原始音频作为 attachment。这样 Agent 可以直接理解语音消息的文本内容,同时在需要时可以访问原始音频。

7. 陷阱与对策

陷阱后果对策
平台 API 限流消息丢失或延迟实现指数退避重试 + 请求队列
媒体下载失败消息不完整fallback 到占位符文本(如 [图片]
消息去重重复处理同一条消息使用 message_id 去重表
Markdown 格式不兼容显示异常平台适配的 Markdown 渲染器
超长消息截断API 拒绝按平台限制拆分消息
Emoji 表情编码差异乱码统一使用 UTF-8 + 平台 Emoji 映射表

8. 参考来源

  • LobeHub ChatAdapter 抽象接口 — packages/chat-adapter-*/src/adapter.ts
  • Discord API 文档 — 消息格式和媒体处理
  • Telegram Bot API — 多模态消息类型