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.media | voice_item.text (语音转文字) | file_item.file_name |
| Discord | attachments[].url | 不支持原生语音 | attachments[].url |
| Slack | files[].url_private | 不支持原生语音 | files[].url_private |
| Telegram | photo[] (多尺寸数组) | voice_note (OGG) | document.file_name |
4. 媒体处理架构
CDN 媒体下载
不同平台的媒体访问方式差异显著:
- 微信:使用 CDN URL + 加密查询参数(
encrypt_query_param),需要定期刷新 - Discord:公开 URL,但有限期(CDN token 过期)
- Slack:私有 URL,需要 Bearer token 鉴权
- Telegram:
getFileAPI 获取本地路径
陷阱:微信 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 — 多模态消息类型