11.4 多模态消息协议

WebSocket 协议扩展

在 Step 9 中,我们扩展了 WebSocket 消息协议以支持多模态内容。

消息内容类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/server/protocol.ts

/**
* 图像内容
*/
export interface ImageContent {
type: "image";
mediaType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
data: string; // base64 编码的图像数据
}

/**
* 文本内容
*/
export interface TextContent {
type: "text";
text: string;
}

/**
* 消息内容类型(支持字符串和多模态)
*/
export type MessageContent =
| string // 纯文本(向后兼容)
| TextContent // 文本对象
| ImageContent // 图像对象
| Array<TextContent | ImageContent>; // 多模态数组

客户端消息格式

1
2
3
4
5
6
7
8
/**
* 聊天消息
*/
export interface ChatSendMessage {
type: "chat.send";
sessionId?: string;
content: MessageContent; // 支持多模态内容
}

消息示例:

  1. 纯文本(向后兼容):

    1
    2
    3
    4
    {
    "type": "chat.send",
    "content": "你好"
    }
  2. 文本对象:

    1
    2
    3
    4
    5
    6
    7
    {
    "type": "chat.send",
    "content": {
    "type": "text",
    "text": "你好"
    }
    }
  3. 纯图像:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "type": "chat.send",
    "content": {
    "type": "image",
    "mediaType": "image/png",
    "data": "iVBORw0KGgo..."
    }
    }
  4. 文本+图像混合:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "type": "chat.send",
    "content": [
    {
    "type": "text",
    "text": "描述这张图片"
    },
    {
    "type": "image",
    "mediaType": "image/jpeg",
    "data": "/9j/4AAQSkZJRg..."
    }
    ]
    }

消息验证

协议层提供了完整的消息验证逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// src/server/protocol.ts

/**
* 验证消息内容
*/
function isValidContent(content: unknown): boolean {
// 1. 字符串内容(向后兼容)
if (typeof content === "string") {
return true;
}

// 2. 对象内容(文本或图像)
if (content && typeof content === "object") {
const c = content as Partial<Record<string, unknown>>;

// 文本对象
if (c.type === "text") {
return typeof c.text === "string";
}

// 图像对象
if (c.type === "image") {
return (
typeof c.data === "string" &&
typeof c.mediaType === "string"
);
}
}

// 3. 数组内容(多模态)
if (Array.isArray(content)) {
return content.every((item) => isValidContent(item));
}

return false;
}

/**
* 验证客户端消息
*/
export function validateClientMessage(
data: unknown
): data is ClientMessage {
if (!data || typeof data !== "object") {
return false;
}

const msg = data as Partial<Record<string, unknown>>;

if (!msg.type || typeof msg.type !== "string") {
return false;
}

switch (msg.type) {
case "chat.send":
// 支持字符串或多模态内容
return (
typeof msg.content === "string" ||
isValidContent(msg.content)
);
// ... 其他消息类型
default:
return false;
}
}

服务器端处理

AgentService 多模态处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// src/server/agent-service.ts

export class AgentService {
async handleChatMessage(
message: ChatSendMessage,
ws: WebSocket
): Promise<void> {
const { sessionId, content } = message;

// 1. 获取或创建会话
const session = this.getOrCreateSession(sessionId);

// 2. 转换消息格式
const userMessage = this.convertToMultimodalMessage(content);

// 3. 添加到会话历史
session.addMessage(userMessage);

// 4. 发送到 Agent
const response = await this.agent.processTurn(
userMessage,
(chunk) => this.sendChunk(ws, session.id, chunk)
);

// 5. 保存会话
await this.storage.save(session);
}

/**
* 转换为多模态消息
*/
private convertToMultimodalMessage(
content: MessageContent
): MultimodalMessage {
// 字符串内容 → 文本对象
if (typeof content === "string") {
return {
role: "user",
content: { type: "text", text: content },
};
}

// 已经是对象格式
if (content && typeof content === "object") {
// 单个内容对象 → 包装为数组
if (!Array.isArray(content)) {
return {
role: "user",
content: content as MessageContent,
};
}

// 多模态数组
return {
role: "user",
content: content as MessageContent[],
};
}

// 默认文本消息
return {
role: "user",
content: { type: "text", text: String(content) },
};
}
}

消息路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/server/server.ts

ws.on("message", async (data: Buffer) => {
try {
const message = JSON.parse(data.toString());

// 验证消息格式
if (!validateClientMessage(message)) {
ws.send(JSON.stringify(createErrorMessage("无效的消息格式")));
return;
}

// 路由到对应处理器
switch (message.type) {
case "chat.send":
await agentService.handleChatMessage(message, ws);
break;
// ... 其他消息类型
}
} catch (error) {
ws.send(JSON.stringify(createErrorMessage(String(error))));
}
});

协议兼容性

向后兼容

协议设计保持了向后兼容性:

1
2
3
4
5
// 旧的纯文本消息仍然有效
{ "type": "chat.send", "content": "你好" }

// 等价于新的文本对象格式
{ "type": "chat.send", "content": {"type": "text", "text": "你好"} }

内容规范化

服务器端将所有格式统一转换为标准格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 规范化消息内容
*/
export function normalizeContent(
content: MessageContent
): MessageContent[] {
// 字符串 → 文本对象数组
if (typeof content === "string") {
return [{ type: "text", text: content }];
}

// 单个对象 → 单元素数组
if (!Array.isArray(content)) {
return [content];
}

// 已经是数组
return content;
}

错误处理

常见错误情况

  1. 无效的媒体类型:

    1
    2
    3
    4
    {
    "type": "error",
    "error": "不支持的图像格式: image/tiff"
    }
  2. Base64 格式错误:

    1
    2
    3
    4
    {
    "type": "error",
    "error": "无效的 base64 编码"
    }
  3. 文件过大:

    1
    2
    3
    4
    {
    "type": "error",
    "error": "图像文件过大 (12.5MB),最大支持 10MB"
    }
  4. 消息格式错误:

    1
    2
    3
    4
    {
    "type": "error",
    "error": "无效的消息格式"
    }

错误处理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/server/agent-service.ts

private validateImageContent(content: ImageContent): void {
// 验证媒体类型
const validTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
if (!validTypes.includes(content.mediaType)) {
throw new Error(`不支持的图像格式: ${content.mediaType}`);
}

// 验证 base64 数据
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
if (!base64Regex.test(content.data)) {
throw new Error("无效的 base64 编码");
}

// 验证数据大小(base64 后约为原始的 4/3)
const estimatedSize = (content.data.length * 3) / 4;
if (estimatedSize > 10 * 1024 * 1024) {
throw new Error(
`图像文件过大 (${(estimatedSize / 1024 / 1024).toFixed(2)}MB)`
);
}
}

协议版本控制

为了未来扩展,协议支持版本标识:

1
2
3
4
5
6
7
/**
* 带版本的消息
*/
export interface VersionedMessage {
version: "1.0";
message: ClientMessage;
}

版本化消息示例:

1
2
3
4
5
6
7
{
"version": "1.0",
"message": {
"type": "chat.send",
"content": [...]
}
}

小结

本节介绍了多模态消息协议的设计和实现:

  • WebSocket 协议扩展
  • 消息内容类型定义
  • 消息验证逻辑
  • 服务器端处理流程
  • 向后兼容性设计
  • 错误处理机制

导航

上一篇: 11.3 图像处理与传输

下一篇: 11.5 Web UI 图像功能