第3章 消息系统(Message)
本章目标:深入理解 AgentScope 的消息系统,掌握消息的构建、内容块类型和响应处理
3.1 消息系统概述
3.1.1 消息的重要性
在 AgentScope 中,消息(Msg) 是最核心的数据结构。它承担着多重职责:
┌─────────────────────────────────────────────────────────────┐
│ 消息的作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 用户输入 │ ────────────┐ │
│ └─────────┘ │ │
│ ▼ │
│ ┌──────────┐ │
│ ┌─────────┐ │ Msg │ ┌─────────┐ │
│ │智能体通信│ ◄────► │ 消息对象 │ ◄────► │ LLM API │ │
│ └─────────┘ └──────────┘ └─────────┘ │
│ ▲ │
│ │ │
│ ┌─────────┐ │ │
│ │ 历史存储 │ ────────────┘ │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
- 用户输入:封装用户的问题和指令
- 智能体输出:封装智能体的回复和工具调用
- 智能体通信:在多智能体系统中传递信息
- LLM API 交互:转换为 LLM 可理解的格式
- 历史存储:持久化对话记录
3.1.2 消息的结构
// ========================================
// 【Msg 核心结构】
// ========================================
Msg msg = Msg.builder()
.id("msg-001") // 消息唯一标识(自动生成)
.name("Alice") // 发送者名称
.role(MsgRole.USER) // 消息角色
.content(contentBlocks) // 内容块列表
.metadata(Map.of(...)) // 可选元数据
.build();
核心字段详解:
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
id | String | 消息唯一标识 | "msg-uuid-xxx" |
name | String | 发送者名称 | "Alice", "System" |
role | MsgRole | 消息角色 | USER, ASSISTANT, SYSTEM, TOOL |
content | List | 内容块列表 | [TextBlock, ImageBlock, ...] |
metadata | Map | 扩展元数据 | {"source": "web"} |
3.2 消息角色(MsgRole)
3.2.1 角色类型
public enum MsgRole {
USER, // 用户消息
ASSISTANT, // 助手/智能体消息
SYSTEM, // 系统消息
TOOL // 工具结果消息
}
角色使用场景:
// ========================================
// 【USER】用户消息
// 来自人类用户的输入
// ========================================
Msg userMsg = Msg.builder()
.role(MsgRole.USER)
.name("user")
.textContent("今天天气怎么样?")
.build();
// ========================================
// 【ASSISTANT】助手消息
// 智能体生成的回复
// ========================================
Msg assistantMsg = Msg.builder()
.role(MsgRole.ASSISTANT)
.name("WeatherBot")
.textContent("今天北京天气晴朗,气温 20-28℃。")
.build();
// ========================================
// 【SYSTEM】系统消息
// 系统级指令,通常用于设置角色和规则
// ========================================
Msg systemMsg = Msg.builder()
.role(MsgRole.SYSTEM)
.textContent("你是一个天气助手,提供准确的天气信息。")
.build();
// ========================================
// 【TOOL】工具结果消息
// 工具执行后的结果
// ========================================
Msg toolMsg = Msg.builder()
.role(MsgRole.TOOL)
.content(List.of(
ToolResultBlock.of("tool-id-1", "get_weather",
TextBlock.builder()
.text("北京:晴,20-28℃")
.build())
))
.build();
3.2.2 角色在对话中的作用
┌─────────────────────────────────────────────────────────────┐
│ 典型对话流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ SYSTEM : 你是一个有帮助的助手... │
│ │ │
│ ▼ │
│ USER : 帮我查一下北京天气 │
│ │ │
│ ▼ │
│ ASSISTANT: 好的,我来查询北京的天气情况。 │
│ [ToolUse: get_weather(city="北京")] │
│ │ │
│ ▼ │
│ TOOL : {"city": "北京", "weather": "晴", ...} │
│ │ │
│ ▼ │
│ ASSISTANT: 北京今天天气晴朗,气温 20-28℃... │
│ │
└─────────────────────────────────────────────────────────────┘
3.3 内容块(ContentBlock)
3.3.1 ContentBlock 体系
AgentScope 使用统一的 ContentBlock 体系处理所有类型的内容:
ContentBlock (抽象基类)
│
├── TextBlock - 文本内容
│
├── ThinkingBlock - 推理过程(推理模型专用)
│
├── ImageBlock - 图像内容
├── AudioBlock - 音频内容
├── VideoBlock - 视频内容
│
├── ToolUseBlock - LLM 发起的工具调用
└── ToolResultBlock - 工具执行结果
3.3.2 TextBlock - 文本内容
// ========================================
// 【TextBlock】最常用的内容块
// ========================================
// 方式一:Builder 模式
TextBlock textBlock = TextBlock.builder()
.text("这是一段文本内容")
.build();
// 方式二:直接构造
TextBlock textBlock2 = new TextBlock("这是一段文本内容");
// 在消息中使用
Msg msg = Msg.builder()
.role(MsgRole.USER)
.content(List.of(textBlock))
.build();
// 快捷方式:textContent() 自动创建 TextBlock
Msg msgSimple = Msg.builder()
.textContent("这是一段文本内容") // 等价于上面的写法
.build();
3.3.3 ThinkingBlock - 推理过程
某些模型(如 DeepSeek、Qwen 的推理模式)会返回"思考过程":
// ========================================
// 【ThinkingBlock】推理模型的思考过程
// ========================================
// 启用思考模式的模型
DashScopeChatModel model = DashScopeChatModel.builder()
.apiKey(apiKey)
.modelName("qwen3-max")
.enableThinking(true) // 启用思考模式
.thinkingBudget(2000) // 思考 Token 预算
.build();
// 响应消息可能包含 ThinkingBlock
Msg response = agent.call(userMsg).block();
// 提取思考过程
List<ThinkingBlock> thinkingBlocks =
response.getContentBlocks(ThinkingBlock.class);
for (ThinkingBlock thinking : thinkingBlocks) {
System.out.println("思考过程: " + thinking.getThinking());
}
// 提取最终回复
String reply = response.getTextContent();
3.3.4 多模态内容块
// ========================================
// 【ImageBlock】图像内容
// ========================================
// 方式一:Base64 编码(推荐,兼容性好)
byte[] imageBytes = Files.readAllBytes(Path.of("image.png"));
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
ImageBlock imageBlock = ImageBlock.builder()
.source(Base64Source.builder()
.data(base64Image)
.mediaType("image/png") // MIME 类型
.build())
.build();
// 方式二:URL 引用
ImageBlock urlImageBlock = ImageBlock.builder()
.source(URLSource.builder()
.url("https://example.com/image.jpg")
.build())
.build();
// ========================================
// 【AudioBlock】音频内容
// ========================================
AudioBlock audioBlock = AudioBlock.builder()
.source(Base64Source.builder()
.data(base64AudioData)
.mediaType("audio/mp3")
.build())
.build();
// ========================================
// 【VideoBlock】视频内容
// ========================================
VideoBlock videoBlock = VideoBlock.builder()
.source(URLSource.builder()
.url("https://example.com/video.mp4")
.build())
.build();
支持的 MIME 类型:
| 类型 | 支持的格式 |
|---|---|
| 图像 | image/png, image/jpeg, image/gif, image/webp |
| 音频 | audio/mp3, audio/wav, audio/mpeg |
| 视频 | video/mp4, video/mpeg |
3.3.5 工具相关内容块
// ========================================
// 【ToolUseBlock】工具调用请求
// 由 LLM 生成,表示要调用某个工具
// ========================================
// 通常由 LLM 自动生成,但也可以手动构造
ToolUseBlock toolUse = ToolUseBlock.builder()
.id("tool-call-001") // 调用 ID
.name("get_weather") // 工具名称
.input(Map.of("city", "北京")) // 调用参数
.build();
// ========================================
// 【ToolResultBlock】工具执行结果
// 工具执行后返回给 LLM 的结果
// ========================================
// 文本结果
ToolResultBlock textResult = ToolResultBlock.of(
"tool-call-001", // 对应的调用 ID
"get_weather", // 工具名称
TextBlock.builder()
.text("北京:晴天,25℃")
.build()
);
// 图像结果
ToolResultBlock imageResult = ToolResultBlock.of(
"tool-call-002",
"generate_chart",
ImageBlock.builder()
.source(Base64Source.builder()
.data(base64ChartImage)
.mediaType("image/png")
.build())
.build()
);
// 错误结果
ToolResultBlock errorResult = ToolResultBlock.builder()
.id("tool-call-003")
.name("api_call")
.isError(true) // 标记为错误
.output(List.of(TextBlock.builder()
.text("API 调用失败:连接超时")
.build()))
.build();
3.4 构建消息
3.4.1 基本构建方式
// ========================================
// 【方式一】简单文本消息
// 最常用的方式
// ========================================
Msg simpleMsg = Msg.builder()
.textContent("你好,请帮我翻译这句话")
.build();
// 注意:默认 role 是 USER
// ========================================
// 【方式二】指定角色的消息
// ========================================
Msg userMsg = Msg.builder()
.name("张三")
.role(MsgRole.USER)
.textContent("今天天气怎么样?")
.build();
// ========================================
// 【方式三】多内容块消息
// 包含多种类型的内容
// ========================================
Msg multiContentMsg = Msg.builder()
.role(MsgRole.USER)
.content(List.of(
TextBlock.builder()
.text("这张图片是什么动物?")
.build(),
ImageBlock.builder()
.source(URLSource.builder()
.url("https://example.com/cat.jpg")
.build())
.build()
))
.build();
// ========================================
// 【方式四】带元数据的消息
// 附加业务相关的信息
// ========================================
Msg msgWithMetadata = Msg.builder()
.textContent("处理这个订单")
.metadata(Map.of(
"orderId", "ORD-12345",
"priority", "high",
"timestamp", System.currentTimeMillis()
))
.build();
3.4.2 消息内容提取
Msg response = agent.call(userMsg).block();
// ========================================
// 【提取文本内容】
// ========================================
// 获取所有文本内容(拼接所有 TextBlock)
String allText = response.getTextContent();
// 获取所有 TextBlock
List<TextBlock> textBlocks = response.getContentBlocks(TextBlock.class);
for (TextBlock block : textBlocks) {
System.out.println(block.getText());
}
// ========================================
// 【提取工具调用】
// ========================================
// 检查是否包含工具调用
boolean hasToolCalls = response.hasContentBlocks(ToolUseBlock.class);
// 获取所有工具调用
List<ToolUseBlock> toolCalls = response.getContentBlocks(ToolUseBlock.class);
for (ToolUseBlock tool : toolCalls) {
System.out.println("工具: " + tool.getName());
System.out.println("参数: " + tool.getInput());
}
// ========================================
// 【提取多模态内容】
// ========================================
// 获取图像
List<ImageBlock> images = response.getContentBlocks(ImageBlock.class);
// 获取思考过程
List<ThinkingBlock> thinking = response.getContentBlocks(ThinkingBlock.class);
3.5 响应元信息
3.5.1 GenerateReason - 生成原因
智能体返回的消息包含 GenerateReason,表示为什么生成这个响应:
Msg response = agent.call(userMsg).block();
GenerateReason reason = response.getGenerateReason();
switch (reason) {
case MODEL_STOP:
// 正常完成:LLM 自然结束生成
System.out.println("任务正常完成");
break;
case TOOL_SUSPENDED:
// 工具挂起:工具需要外部执行
System.out.println("工具需要外部执行");
break;
case REASONING_STOP_REQUESTED:
// 推理暂停:被 Hook 在推理阶段暂停
System.out.println("推理阶段被暂停,等待确认");
break;
case ACTING_STOP_REQUESTED:
// 行动暂停:被 Hook 在行动阶段暂停
System.out.println("行动阶段被暂停,等待确认");
break;
case INTERRUPTED:
// 被中断:用户调用了 interrupt()
System.out.println("执行被用户中断");
break;
case MAX_ITERATIONS:
// 达到上限:超过最大迭代次数
System.out.println("警告:达到最大迭代次数");
break;
}
3.5.2 ChatUsage - Token 用量
// ========================================
// 【Token 用量统计】
// 用于成本控制和性能优化
// ========================================
ChatUsage usage = response.getChatUsage();
if (usage != null) {
System.out.println("输入 Token: " + usage.getInputTokens());
System.out.println("输出 Token: " + usage.getOutputTokens());
System.out.println("总计 Token: " + usage.getTotalTokens());
// 计算成本(假设价格)
double inputCost = usage.getInputTokens() * 0.00001;
double outputCost = usage.getOutputTokens() * 0.00003;
System.out.printf("预估成本: $%.4f%n", inputCost + outputCost);
}
3.5.3 结构化数据
如果请求了结构化输出,可以提取类型化数据:
// 定义输出结构
public class WeatherInfo {
public String city;
public String weather;
public int temperature;
}
// 请求结构化输出
Msg response = agent.call(userMsg, WeatherInfo.class).block();
// 提取结构化数据
WeatherInfo data = response.getStructuredData(WeatherInfo.class);
System.out.println("城市: " + data.city);
System.out.println("天气: " + data.weather);
System.out.println("温度: " + data.temperature + "℃");
3.6 消息最佳实践
3.6.1 消息构建规范
// ========================================
// 【推荐】清晰的消息结构
// ========================================
// 好的做法:明确指定角色和名称
Msg goodMsg = Msg.builder()
.name("customer")
.role(MsgRole.USER)
.textContent("我想退换这件商品")
.metadata(Map.of("orderId", "ORD-123"))
.build();
// 不推荐:省略重要信息
Msg badMsg = Msg.builder()
.textContent("退换商品")
.build();
3.6.2 多模态消息规范
// ========================================
// 【推荐】多模态消息的最佳实践
// ========================================
// 好的做法:文字描述在前,媒体内容在后
Msg goodMultimodal = Msg.builder()
.role(MsgRole.USER)
.content(List.of(
TextBlock.builder()
.text("请分析这张图片中的产品,并给出改进建议:")
.build(),
imageBlock
))
.build();
// 好的做法:使用 Base64 确保兼容性
ImageBlock reliableImage = ImageBlock.builder()
.source(Base64Source.builder()
.data(base64Data)
.mediaType("image/png")
.build())
.build();
3.6.3 工具消息处理
// ========================================
// 【处理工具调用的完整流程】
// ========================================
Msg response = agent.call(userMsg).block();
// 检查是否有待处理的工具调用
while (response.hasContentBlocks(ToolUseBlock.class)) {
List<ToolUseBlock> toolCalls = response.getContentBlocks(ToolUseBlock.class);
// 构建工具结果
List<ToolResultBlock> results = new ArrayList<>();
for (ToolUseBlock tool : toolCalls) {
try {
// 执行工具逻辑
String result = executeToolManually(tool);
results.add(ToolResultBlock.of(
tool.getId(),
tool.getName(),
TextBlock.builder().text(result).build()
));
} catch (Exception e) {
// 处理错误
results.add(ToolResultBlock.builder()
.id(tool.getId())
.name(tool.getName())
.isError(true)
.output(List.of(TextBlock.builder()
.text("执行失败: " + e.getMessage())
.build()))
.build());
}
}
// 发送工具结果
Msg toolResultMsg = Msg.builder()
.role(MsgRole.TOOL)
.content(results.stream()
.map(r -> (ContentBlock) r)
.toList())
.build();
response = agent.call(toolResultMsg).block();
}
// 最终响应
System.out.println(response.getTextContent());
3.7 本章小结
本章我们学习了:
- 消息结构
- Msg 的核心字段:id、name、role、content、metadata - 消息在系统中的多重作用
- 消息角色
- USER:用户消息 - ASSISTANT:助手消息 - SYSTEM:系统消息 - TOOL:工具结果消息
- 内容块类型
- TextBlock:文本内容 - ThinkingBlock:推理过程 - ImageBlock/AudioBlock/VideoBlock:多模态内容 - ToolUseBlock/ToolResultBlock:工具相关
- 消息处理
- 构建各种类型的消息 - 提取消息中的内容 - 处理响应元信息
- 最佳实践
- 消息构建规范 - 多模态处理 - 工具消息处理
练习
- 构建多模态消息:创建包含文字和图片的消息,发送给视觉模型
- 提取思考过程:启用 ThinkingMode,观察并提取模型的推理过程
- Token 统计:记录一轮对话的 Token 消耗,计算预估成本
- 错误处理:模拟工具执行失败,构建错误类型的 ToolResultBlock
下一章 → 第4章 智能体