一、引言:为什么消息与事件是智能体框架的"神经系统"
在 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
消息定义了智能体"说什么",事件定义了智能体"怎么做"。两者合一,构成了一个可观测、可干预、可信赖的智能体执行流。