AgentScope 2.0:4. Message & Event —— 消息模型与事件流深度解析

一、引言:为什么消息与事件是智能体框架的"神经系统"

在 AgentScope Java 2.0 的构建块体系中,Message(消息)Event(事件) 共同构成了智能体的"神经系统":

  • Message 是智能体之间、智能体与模型之间传递信息的静态载体------它定义了"说什么"
  • Event 是推理-行动循环中每一步的动态信号------它定义了"正在发生什么"

消息与事件模块,打造可观测、可交互的执行流。

本文深入解析这两大核心抽象的设计哲学、类型体系与工程实践。

二、消息模型(Message):统一 ContentBlock 架构

2.1 设计哲学

AgentScope 2.0 对消息层进行了彻底重构,核心设计原则:

原则 说明
统一抽象 文本、图片、音频、视频、工具调用、工具结果统一收敛到 ContentBlock
强类型校验 使用 Java 17 sealed class + record,构造期按 role 校验,非法组合直接报错
可持久化 Msg 是可序列化的最小对话单元,直接写入 AgentState
多模态原生 不是"附加"多模态,而是从类型系统层面原生支持

2.2 Msg 消息结构

文本 复制代码
Msg
├── role: MsgRole (USER / ASSISTANT / SYSTEM / TOOL)
├── content: List<ContentBlock>
├── generateReason: GenerateReason (可选)
└── name: String (可选,用于多 Agent 场景标识)

核心定义: Msg 是消息主体,由 role + List 组成,是可持久化的最小对话单元。

java 复制代码
// 源码位置: io.agentscope.core.message.Msg
public final class Msg {
    private final MsgRole role;
    private final List<ContentBlock> content;
    private final GenerateReason generateReason;
    private final String name;
    // ...
}

2.3 MsgRole ------ 消息角色

角色 说明 允许的 ContentBlock
USER 用户输入 TextBlock, ImageBlock, DataBlock
ASSISTANT 模型输出 TextBlock, ThinkingBlock, ToolUseBlock
SYSTEM 系统指令 TextBlock
TOOL 工具返回 ToolResultBlock

强校验机制:构造期按 role 校验 ContentBlock 类型,非法组合在构造时即抛出异常,而非运行时才暴露。这是 2.0 相比 1.x 的重大改进------将错误前移到编译/构造阶段。

2.4 ContentBlock 类型体系

ContentBlock 是消息内容的原子片段,采用 sealed class 设计,确保类型安全与穷举性:

java 复制代码
public sealed interface ContentBlock
    permits TextBlock, ImageBlock, DataBlock,
            ThinkingBlock, ToolUseBlock, ToolResultBlock {
}

2.4.1 TextBlock ------ 纯文本

java 复制代码
record TextBlock(String text) implements ContentBlock {}

最基础的内容类型,承载对话文本。

2.4.2 ImageBlock ------ 图片

java 复制代码
record ImageBlock(Source source, String mediaType) implements ContentBlock {}

支持多模态图片输入,Source 定义数据来源(Base64 / URL / 文件路径)。

2.4.3 DataBlock ------ 文件/数据

java 复制代码
record DataBlock(Source source, String fileName, String mediaType) implements ContentBlock {}

承载文件、音频、视频等二进制数据。

2.4.4 Source ------ 数据源抽象

ImageBlock 和 DataBlock 共享 Source 抽象:

Source 类型 说明
Base64Source 内联 Base64 编码
URLSource 远程 URL 引用
FileSource 本地文件路径

2.4.5 ThinkingBlock ------ 模型思考

java 复制代码
record ThinkingBlock(String thinking) implements ContentBlock {}

承载模型的推理过程(Chain-of-Thought),仅在 ASSISTANT 角色消息中出现。这一设计使得"思考过程"成为一等公民,可被中间件拦截、记录或展示。

2.4.6 ToolUseBlock ------ 工具调用请求

java 复制代码
record ToolUseBlock(
    String id,          // 调用唯一标识
    String name,        // 工具名称
    Map<String, Object> input  // 调用参数
) implements ContentBlock {}

出现在 ASSISTANT 角色消息中,表示模型决定调用某个工具。

2.4.7 ToolResultBlock ------ 工具调用结果

java 复制代码
record ToolResultBlock(
    String toolUseId,   // 对应的 ToolUseBlock id
    String content,     // 执行结果
    boolean isError     // 是否执行出错
) implements ContentBlock {}

出现在 TOOL 角色消息中,表示工具执行完毕后的返回。

2.5 ContentBlock 类型全景图

文本 复制代码
ContentBlock (sealed interface)
├── TextBlock          ← 纯文本(USER/ASSISTANT/SYSTEM)
├── ImageBlock         ← 图片(USER)
├── DataBlock          ← 文件/音频/视频(USER)
├── ThinkingBlock      ← 模型思考过程(ASSISTANT)
├── ToolUseBlock       ← 工具调用请求(ASSISTANT)
└── ToolResultBlock    ← 工具执行结果(TOOL)

2.6 GenerateReason ------ 生成原因

标识本轮 Assistant 消息的终止原因:

枚举值 说明
END_TURN 模型自然结束回复
TOOL_USE 模型请求调用工具(循环继续)
MAX_ITERATIONS 达到最大迭代次数,强制终止

这一设计让上层逻辑可以精确判断"为什么停止了",而非猜测。

2.7 快捷消息工厂

框架提供静态工厂方法简化消息创建:

java 复制代码
// 用户消息
Msg userMsg = new UserMessage("帮我查一下北京天气");

// 系统消息
Msg sysMsg = new SystemMessage("你是一个天气助手");

// 带图片的多模态消息
Msg multiModal = Msg.builder()
    .role(MsgRole.USER)
    .content(TextBlock.of("这张图里有什么?"))
    .content(ImageBlock.of(Source.base64(imageBytes), "image/png"))
    .build();

2.8 常用模式

模式一:提取纯文本

java 复制代码
String text = msg.getTextContent();  // 拼接所有 TextBlock

模式二:读取结构化输出

java 复制代码
WeatherResult result = msg.getStructuredData(WeatherResult.class);

模式三:消息在 ReAct 循环中的传递

文本 复制代码
UserMessage → [推理] → AssistantMessage(ToolUseBlock)
    → [工具执行] → ToolMessage(ToolResultBlock)
    → [推理] → AssistantMessage(TextBlock, GenerateReason.END_TURN)

三、事件系统(Event):可观测的执行流

3.1 设计理念

每一步------模型调用、文本增量、工具执行、工具结果------都以类型化事件流出。订阅一次,前端 UI 实时跟上。

AgentScope 2.0 的事件系统提供约 35 个事件类,覆盖智能体执行的完整生命周期。事件通过 streamEvents() 以 Flux 形式流出,天然适配响应式编程与 SSE(Server-Sent Events)。

3.2 AgentEvent 基类

每个事件都继承自 AgentEvent,提供统一的元数据:

java 复制代码
public abstract class AgentEvent {
    public String getId();            // 唯一事件标识符
    public String getCreatedAt();     // ISO 8601 时间戳
    public AgentEventType getType();  // 事件类型枚举
    public String getSource();        // 来源路径
}

source 字段的精妙设计:

  • 顶层 Agent:source = null
  • 子 Agent:source = "main/sub-agent-1"(斜杠分隔路径)

这使得在多层嵌套的子 Agent 架构中,每个事件都能精确追溯到产生它的 Agent 层级。

3.3 事件生命周期:标准三段式模式

AgentScope 2.0 的事件遵循标准三段式(Start → Delta → End):

文本 复制代码
┌─────────────────────────────────────────────────────┐
│  XxxStartEvent    →  标记某个阶段开始                  │
│  XxxDeltaEvent    →  流式增量数据(可多次触发)          │
│  XxxEndEvent      →  标记某个阶段结束                  │
└─────────────────────────────────────────────────────┘

这种设计使得:

  • 前端渲染可以精确知道何时开始显示、何时追加内容、何时关闭
  • 性能监控可以精确计算每个阶段的耗时
  • 异常处理可以精确定位中断点

3.4 事件分类详解

3.4.1 智能体调用事件

事件 说明
AgentStartEvent Agent 开始处理请求
AgentEndEvent Agent 完成本轮回复

3.4.2 模型调用事件

事件 说明
ModelCallStartEvent 开始调用 LLM
ModelCallEndEvent LLM 返回完成

3.4.3 文本块事件

事件 说明
TextBlockStartEvent 文本生成开始
TextBlockDeltaEvent 文本增量(流式输出核心)
TextBlockEndEvent 文本生成结束

3.4.4 思考块事件

事件 说明
ThinkingBlockStartEvent 模型开始思考
ThinkingBlockDeltaEvent 思考内容增量
ThinkingBlockEndEvent 思考结束

3.4.5 数据块事件

事件 说明
DataBlockStartEvent 数据块开始
DataBlockDeltaEvent 数据增量
DataBlockEndEvent 数据块结束

3.4.6 工具调用事件

事件 说明
ToolCallStartEvent 工具调用开始(含工具名、参数)
ToolCallEndEvent 工具调用完成

3.4.7 工具结果事件

事件 说明
ToolResultEvent 工具执行结果返回

3.4.8 异常与中断事件

事件 说明
ErrorEvent 执行异常
InterruptEvent 执行被中断

3.4.9 HITL(Human-in-the-Loop)事件

事件 说明
PermissionRequestEvent 请求人工审批
PermissionResponseEvent 人工审批结果

3.4.10 子 Agent 事件

事件 说明
SubAgentStartEvent 子 Agent 启动
SubAgentEndEvent 子 Agent 完成

3.5 事件类型枚举(AgentEventType)

所有事件类型通过 AgentEventType 枚举统一管理,便于 switch 分发:

java 复制代码
public enum AgentEventType {
    AGENT_START, AGENT_END,
    MODEL_CALL_START, MODEL_CALL_END,
    TEXT_BLOCK_START, TEXT_BLOCK_DELTA, TEXT_BLOCK_END,
    THINKING_BLOCK_START, THINKING_BLOCK_DELTA, THINKING_BLOCK_END,
    DATA_BLOCK_START, DATA_BLOCK_DELTA, DATA_BLOCK_END,
    TOOL_CALL_START, TOOL_CALL_END,
    TOOL_RESULT,
    ERROR, INTERRUPT,
    PERMISSION_REQUEST, PERMISSION_RESPONSE,
    SUB_AGENT_START, SUB_AGENT_END,
    // ... 更多类型
}

3.6 执行流程中的事件序列

一次典型的 ReAct 循环产生的事件序列:

文本 复制代码
AgentStartEvent
  ├── ModelCallStartEvent
  │     ├── ThinkingBlockStartEvent
  │     ├── ThinkingBlockDeltaEvent × N
  │     ├── ThinkingBlockEndEvent
  │     ├── TextBlockStartEvent
  │     ├── TextBlockDeltaEvent × N
  │     ├── TextBlockEndEvent
  │     └── ToolCallStartEvent (模型决定调用工具)
  ├── ModelCallEndEvent
  ├── ToolCallStartEvent (工具实际执行)
  ├── ToolResultEvent
  ├── ModelCallStartEvent (第二轮推理)
  │     ├── TextBlockStartEvent
  │     ├── TextBlockDeltaEvent × N
  │     └── TextBlockEndEvent
  ├── ModelCallEndEvent
  └── AgentEndEvent

3.7 从事件流重建消息

事件流不仅是"观察窗口",还可以反向重建完整的 Msg:

文本 复制代码
TextBlockDelta × N  →  拼接  →  TextBlock
ToolCallStart + ToolResult  →  ToolUseBlock + ToolResultBlock
这使得即使只订阅了事件流,也能完整还原对话历史。
## 四、事件订阅与流式输出实战
### 4.1 基础订阅
```java
agent.streamEvents(new UserMessage("介绍 AgentScope 2.0"))
    .doOnNext(event -> {
        switch (event.getType()) {
            case TEXT_BLOCK_DELTA ->
                System.out.print(((TextBlockDeltaEvent) event).getDelta());
            case TOOL_CALL_START ->
                System.out.println("\n🔧 调用工具: " +
                    ((ToolCallStartEvent) event).getToolCallName());
            case THINKING_BLOCK_DELTA ->
                System.out.print("💭 " +
                    ((ThinkingBlockDeltaEvent) event).getDelta());
            case AGENT_END ->
                System.out.println("\n✅ 回复完成");
        }
    })
    .blockLast();

4.2 Spring WebFlux SSE 端点

java 复制代码
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(
        @RequestParam String message,
        @RequestParam String sessionId,
        @RequestParam String userId) {

    RuntimeContext ctx = RuntimeContext.builder()
        .sessionId(sessionId)
        .userId(userId)
        .build();

    return agent.streamEvents(new UserMessage(message), ctx)
        .filter(e -> e.getType() == AgentEventType.TEXT_BLOCK_DELTA)
        .map(e -> ServerSentEvent.<String>builder()
            .data(((TextBlockDeltaEvent) e).getDelta())
            .build());
}

4.3 多 Agent 事件追踪

java 复制代码
agent.streamEvents(new UserMessage("帮我完成数据分析"))
    .doOnNext(event -> {
        String source = event.getSource();
        if (source != null) {
            // 来自子 Agent 的事件
            System.out.println("[" + source + "] " + event.getType());
        } else {
            // 来自主 Agent 的事件
            System.out.println("[main] " + event.getType());
        }
    })
    .blockLast();

五、消息与事件的协作关系

5.1 静态 vs 动态

维度 Message (Msg) Event (AgentEvent)
本质 静态数据载体 动态执行信号
生命周期 持久化存储 瞬时流过
用途 上下文传递、状态恢复 实时渲染、监控、干预
粒度 完整消息 增量片段
产生时机 推理完成后 推理过程中

5.2 转换关系

文本 复制代码
事件流(实时)                    消息(持久化)
─────────────                    ─────────────
TextBlockDelta × N    ──聚合──→  Msg(ASSISTANT, [TextBlock])
ToolCallStart         ──记录──→  Msg(ASSISTANT, [ToolUseBlock])
ToolResult            ──记录──→  Msg(TOOL, [ToolResultBlock])

5.3 事件驱动的消息更新

在 2.0 架构中,消息的构建是事件驱动的:

java 复制代码
// 内部实现逻辑(简化)
MsgBuilder builder = Msg.builder().role(MsgRole.ASSISTANT);

eventStream.subscribe(event -> {
    if (event instanceof TextBlockEndEvent e) {
        builder.content(new TextBlock(e.getFullText()));
    }
    if (event instanceof ToolCallStartEvent e) {
        builder.content(new ToolUseBlock(e.getId(), e.getName(), e.getInput()));
    }
});

// 流结束后 → 完整 Msg 写入 AgentState

六、与 1.x 的对比:消息模型演进

维度 1.x 2.0
消息类型 多种 Msg 子类(TextMsg, ImageMsg...) 统一 Msg + ContentBlock
类型安全 运行时检查 构造期 sealed class 强校验
多模态 附加支持 原生一等公民
工具调用 特殊字段 ToolUseBlock / ToolResultBlock
思考过程 无独立表示 ThinkingBlock 独立承载
事件系统 Hook 回调(扁平) 35+ 类型化事件(结构化)
流式输出 有限支持 完整三段式事件流
子 Agent 追踪 source 路径精确标识

七、工程化最佳实践

7.1 事件日志与审计

java 复制代码
agent.streamEvents(userMsg, ctx)
    .doOnNext(event -> {
        auditLog.record(AuditEntry.builder()
            .eventId(event.getId())
            .timestamp(event.getCreatedAt())
            .type(event.getType())
            .source(event.getSource())
            .userId(ctx.getUserId())
            .sessionId(ctx.getSessionId())
            .build());
    })
    .subscribe();

7.2 Token 消耗监控

java 复制代码
.doOnNext(event -> {
    if (event instanceof ModelCallEndEvent e) {
        metrics.recordTokenUsage(
            e.getModelName(),
            e.getPromptTokens(),
            e.getCompletionTokens()
        );
    }
})

7.3 异常告警

java 复制代码
.doOnNext(event -> {
    if (event instanceof ErrorEvent e) {
        alertService.fire(Alert.builder()
            .level(AlertLevel.CRITICAL)
            .message("Agent 执行异常: " + e.getErrorMessage())
            .source(event.getSource())
            .build());
    }
})

7.4 HITL 审批流集成

java 复制代码
.doOnNext(event -> {
    if (event instanceof PermissionRequestEvent e) {
        // 推送到审批系统
        approvalService.submit(ApprovalRequest.builder()
            .toolName(e.getToolName())
            .parameters(e.getParameters())
            .agentSource(event.getSource())
            .build());
    }
})

八、设计哲学总结

AgentScope Java 2.0 的消息与事件系统体现了三个核心设计原则:

8.1 类型即文档

使用 sealed class + record,让编译器成为第一道防线。开发者无需查阅文档即可通过 IDE 自动补全了解所有可能的 ContentBlock 和 Event 类型。

8.2 事件即接口

事件流是框架与外部世界的唯一实时接口。无论是 Web 前端、TUI 终端、监控系统还是审批流程,都通过同一套事件流接入。

8.3 消息即状态

Msg 不只是"传话",它是 AgentState 的持久化单元。会话恢复、上下文压缩、记忆提炼,全部基于 Msg 进行操作。

九、结语

AgentScope Java 2.0 的消息与事件系统,用类型安全解决了"消息混乱"问题,用结构化事件解决了"执行黑箱"问题,用三段式模式解决了"流式渲染"问题。 对于 Java 开发者而言,这套设计完美契合了 JVM 生态的强类型传统:

  • sealed class 保证穷举性
  • record 保证不可变性
  • Flux 保证响应式
  • 构造期校验保证 fail-fast

消息定义了智能体"说什么",事件定义了智能体"怎么做"。两者合一,构成了一个可观测、可干预、可信赖的智能体执行流。

相关推荐
元界metalite1 小时前
MyBatis 字段改名为何查询不报错?MetaLite ORM 如何做到类型安全?
后端
Csvn1 小时前
🐍 Day 2 :Python 变量与数据类型 — 一切皆对象
后端
Csvn2 小时前
📊 SQL 入门 Day 17:数据更新与删除
后端·sql
秋天的一阵风2 小时前
🔥 Network 里那坨 "data:" 我真看吐了,自制开源 Chrome 插件,AI 流式调试直接开挂
前端·人工智能·后端
IT_陈寒2 小时前
Python的GIL让我深夜加班,这破锁到底怎么折腾的
前端·人工智能·后端
覆东流2 小时前
2.Java程序基础
java·开发语言·后端
ttwuai2 小时前
Go 后台定时任务启停不生效怎么办?先查调度同步链路
开发语言·后端·golang
AINative软件工程3 小时前
LLM 应用的 Feature Flag 工程实践:Prompt、模型与 AI 行为的生产安全灰度
后端·llm·ai编程
weixin_431600443 小时前
前端对接 SSE 的两种常见方式
前端·后端·学习·ai·sse·nest.js