第3章 消息系统(Message)

第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();

核心字段详解:

字段类型说明示例
idString消息唯一标识"msg-uuid-xxx"
nameString发送者名称"Alice", "System"
roleMsgRole消息角色USER, ASSISTANT, SYSTEM, TOOL
contentList内容块列表[TextBlock, ImageBlock, ...]
metadataMap扩展元数据{"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 本章小结

本章我们学习了:

  1. 消息结构

- Msg 的核心字段:id、name、role、content、metadata - 消息在系统中的多重作用

  1. 消息角色

- USER:用户消息 - ASSISTANT:助手消息 - SYSTEM:系统消息 - TOOL:工具结果消息

  1. 内容块类型

- TextBlock:文本内容 - ThinkingBlock:推理过程 - ImageBlock/AudioBlock/VideoBlock:多模态内容 - ToolUseBlock/ToolResultBlock:工具相关

  1. 消息处理

- 构建各种类型的消息 - 提取消息中的内容 - 处理响应元信息

  1. 最佳实践

- 消息构建规范 - 多模态处理 - 工具消息处理


练习

  1. 构建多模态消息:创建包含文字和图片的消息,发送给视觉模型
  2. 提取思考过程:启用 ThinkingMode,观察并提取模型的推理过程
  3. Token 统计:记录一轮对话的 Token 消耗,计算预估成本
  4. 错误处理:模拟工具执行失败,构建错误类型的 ToolResultBlock

下一章 → 第4章 智能体

← 返回目录