目录
- AgentScope 事件系统设计文档
- 1. 概述
- 2. 继承关系
- 3. 事件生命周期
- 3.1 顶层标识 —— replyId
- 3.2 二级标识 —— blockId / toolCallId
- 3.3 标准三段式模式
- 3.4 执行流程
- 4. 事件分类详解
- 4.1 智能体调用事件
- 4.2 模型调用事件
- 4.3 文本块事件
- 4.4 思考块事件
- 4.5 数据块事件
- 4.6 工具调用事件
- 4.7 工具结果事件
- 4.8 异常与中断事件
- 4.9 HITL 事件
- 4.10 子 Agent 事件
- 5. 从事件流重建消息
AgentScope 事件系统设计文档
本文档系统介绍
AgentScope 2.0的事件系统:v2将v1的 6 种粗粒度Event拆分为27 种细粒度AgentEvent,把Agent执行的每一个微小步骤都暴露为可观测、可处理的标准化对象。
1. 概述
Agent执行一个复杂任务不是瞬间完成的,它会经历多个连续的步骤,每个步骤都会产生增量变化,Event的作用就是把这些原本不可见的内部执行过程,变成一个个可观测、可处理的标准化对象。
三类最常见的增量进度场景:
- 文本 token 增量到达:大模型生成回复是一个字一个字出来的,每生成几个 token 就触发一个
TextBlockDeltaEvent,前端可以实时渲染,用户不会看到空白的加载页面。 - 工具调用逐步构建:
Agent决定调用工具时,工具的参数也是逐步生成的,最后触发ToolCallEndEvent表示参数构建完成。 - 工具结果流式返回:工具执行后的结果也可能是流式的,产生
ToolResultTextDeltaEvent或ToolResultDataDeltaEvent,实时推送工具的输出内容。
细粒度拆分带来的收益:
- 前端不需要做任何 diff:只需监听对应事件类型,直接把
delta内容追加到页面即可 - 实现更丰富的交互效果:工具调用时显示加载动画、思考过程用灰色字体区分、多媒体内容边下载边显示
- 网络中断后可从断点恢复:只需重放最后一个收到的事件之后的序列,即可精确还原对话状态,无需重新执行整个任务
2. 继承关系
所有事件继承自AgentEvent基类:
AgentEvent (abstract) │ id, createdAt, source, getType() │ ├─ ① 智能体生命周期 │ ├── AgentStartEvent │ └── AgentEndEvent │ ├─ ② 模型调用 │ ├── ModelCallStartEvent │ └── ModelCallEndEvent │ ├─ ③ 文本块 TextBlockStart → TextBlockDelta(×N) → TextBlockEnd ├─ ④ 思考块 ThinkingBlockStart → ThinkingBlockDelta(×N) → ThinkingBlockEnd ├─ ⑤ 数据块 DataBlockStart → DataBlockDelta(×N) → DataBlockEnd ├─ ⑥ 工具调用 ToolCallStart → ToolCallDelta(×N) → ToolCallEnd ├─ ⑦ 工具结果 ToolResultStart → TextDelta/DataDelta(×N) → ToolResultEnd │ ├─ ⑧ 异常与中断 ExceedMaxItersEvent / RequestStopEvent │ ├─ ⑨ HITL RequireUserConfirm / UserConfirmResult │ RequireExternalExecution / ExternalExecutionResult │ └─ ⑩ 子 Agent SubagentExposedEventAgentEvent公共方法:
| 方法 | 类型 | 说明 |
|---|---|---|
getId() | String | 唯一事件标识符 |
getCreatedAt() | String | ISO 8601 时间戳 |
getType() | AgentEventType | 事件类型枚举 |
getSource() | String | 来源路径。顶层 Agent 为 null;子 Agent 为斜杠分隔路径(如"main/researcher") |
3. 事件生命周期
3.1 顶层标识 —— replyId
replyId是最高层级的关联 ID,对应Agent对一条用户消息的完整一次回复:
- 同一次
streamEvents()调用产生的所有事件,共享同一个replyId - 当页面同时存在多轮对话、多个并发
Agent请求时,通过replyId可以立刻判断当前事件属于哪一条消息,避免把 A 回复的内容拼到 B 消息上
同一次回复中所有事件共享相同的
replyId。用blockId关联文本/思考/数据块事件,用toolCallId关联工具调用和工具结果事件。
3.2 二级标识 —— blockId / toolCallId
一整轮回复里往往不只有一段文字,可能同时包含:文本回答、思考过程、图片、多次工具调用。此时需要二级 ID 做内部分组:
blockId:用于文本/思考/数据块事件,每一段独立内容(一段正文、一张图片、一段思考链)拥有唯一的blockIdtoolCallId:用于工具调用 + 工具结果事件,每一次工具调用拥有唯一的toolCallId,调用事件和结果事件通过它一一对应
3.3 标准三段式模式
start → delta(×N) → end是所有内容类事件统一遵循的流式协议,无论是文本、思考、数据还是工具调用,都遵守完全一样的三段式结构,用最低的复杂度实现流式传输。
| 阶段 | 作用 | 触发时机 |
|---|---|---|
Start事件 | 预告「某类内容即将开始传输」 | 内容块创建的第一时间推送,携带唯一 ID、元信息(如文本块的 blockId、工具的名称) |
Delta事件 | 传输增量内容,可出现 N 次 | 每生成/接收到一段数据就推送一次,携带增量片段(文本 token、参数片段、二进制分片) |
End事件 | 标记「该内容块传输完成」 | 全部内容发送完毕时推送,代表这个块已闭合,不会再有新的 delta |
两个直观例子:
- 文本块:先推
TextBlockStartEvent(告诉前端「准备好,要开始打字了」),然后每生成几个 token 推一次TextBlockDeltaEvent,全部生成完推TextBlockEndEvent - 工具调用:先推
ToolCallStartEvent(告诉前端「Agent 要调用 XX 工具了」),然后参数 JSON 分片推送ToolCallDeltaEvent,参数拼完推ToolCallEndEvent
3.4 执行流程
推理阶段(AgentStart→ 模型调用 → 各内容块流式 →ModelCallEnd):
- 启动:
AgentStartEvent→ 发起模型调用ModelCallStartEvent - 文本块流式:
TextBlockStart→ 多段Delta→TextBlockEnd - 数据块流式:
DataBlockStart→ 多段Delta→DataBlockEnd - 工具调用流式:
ToolCallStart→ 多段Delta→ToolCallEnd - 模型推理结束:
ModelCallEndEvent
执行阶段(工具结果回传 → 收尾):
- 工具结果回传:
ToolResultStart→ 文本分片Delta、数据分片Delta→ToolResultEnd - 全流程收尾:
AgentEndEvent
4. 事件分类详解
4.1 智能体调用事件
AgentStartEvent:当智能体开始处理一次调用请求时触发。
| 方法 | 类型 | 说明 |
|---|---|---|
getReplyId() | String | 回复消息 ID |
getSessionId() | String | 会话 ID |
getName() | String | Agent 名称 |
getRole() | String | 角色(默认"assistant") |
AgentEndEvent:在智能体完成一次调用任务的处理后触发。
| 方法 | 类型 | 说明 |
|---|---|---|
getReplyId() | String | 回复消息 ID |
4.2 模型调用事件
| 事件 | 特殊字段 |
|---|---|
ModelCallStartEvent | modelName |
ModelCallEndEvent | inputTokens,outputTokens |
4.3 文本块事件
TextBlockStartEvent:新的文本块开始。
| 方法 | 类型 | 说明 |
|---|---|---|
getReplyId() | String | 回复消息 ID |
getBlockId() | String | 文本块唯一标识符 |
TextBlockDeltaEvent:增量文本到达。
| 方法 | 类型 | 说明 |
|---|---|---|
getReplyId() | String | 回复消息 ID |
getBlockId() | String | 文本块唯一标识符 |
getDelta() | String | 增量文本内容 |
TextBlockEndEvent:文本块完成(字段同TextBlockStartEvent)。
4.4 思考块事件
与文本事件结构一致,承载模型思维链内容:
ThinkingBlockStartEvent:一段独立思维链内容开始输出。携带replyId、唯一blockId,告知消费端准备接收思考内容分片。ThinkingBlockDeltaEvent:承载思维链增量文字片段,模型每生成一小段推理思考内容就推送一条;getDelta()获取增量文本,如模型内心分步推理、自我校验、思路推演内容。ThinkingBlockEndEvent:当前这一段思维链全部输出完毕,标记该blockId对应的思考块闭合。
4.5 数据块事件
承载图片/音频/视频等二进制数据:
DataBlockStartEvent:getMediaType()返回 MIME 类型(如"image/png")DataBlockDeltaEvent:getData()返回增量 base64 编码数据DataBlockEndEvent:结束事件
4.6 工具调用事件
ToolCallStartEvent:Agent开始工具调用。
| 方法 | 类型 | 说明 |
|---|---|---|
getReplyId() | String | 回复消息 ID |
getToolCallId() | String | 工具调用唯一标识符 |
getToolCallName() | String | 被调用的工具名称 |
ToolCallDeltaEvent:增量工具参数到达,getDelta()返回 JSON 参数片段。ToolCallEndEvent:工具调用参数完成。
4.7 工具结果事件
ToolResultStartEvent:工具开始执行,携带toolCallId、toolCallName。ToolResultTextDeltaEvent:工具的增量文本输出,getDelta()返回文本片段。ToolResultDataDeltaEvent:工具的二进制数据输出,包含mediaType/data/url字段。ToolResultEndEvent:工具执行完成。
4.8 异常与中断事件
ExceedMaxItersEvent:达到最大推理执行迭代次数,含replyId。RequestStopEvent:中间件或工具发起的提前停止请求。
4.9 HITL 事件
| 事件 | 方向 | 关键字段 |
|---|---|---|
RequireUserConfirmEvent | Agent → 客户端 | replyId,toolCalls(List<ToolUseBlock>) |
UserConfirmResultEvent | 客户端 → Agent(输入) | List<ConfirmResult> |
RequireExternalExecutionEvent | Agent → 客户端 | 需外部系统执行 |
ExternalExecutionResultEvent | 客户端 → Agent(输入) | List<ToolResultBlock> |
4.10 子 Agent 事件
SubagentExposedEvent:子 Agent 被暴露为用户入口点。
| 方法 | 类型 | 说明 |
|---|---|---|
getSubagentId() | String | 子 Agent 唯一标识 |
getAgentId() | String | 子 Agent 类型 ID |
getSessionId() | String | 子 Agent 会话 ID |
getLabel() | String | 用户可见标签名(可选) |
SSE / 流式消费端可据此在 UI 上渲染新的会话入口。
5. 从事件流重建消息
事件与消息并非相互独立,而是同一数据的两种视图。streamEvents产出的事件流可以按replyId/blockId/toolCallId聚合还原成完整的AssistantMessage,保证最终消息状态可以仅凭事件流完整还原。
代码示例:流式消费并重建文本
StringBuilderaccumulated=newStringBuilder();agent.streamEvents(userMsg).doOnNext(event->{if(eventinstanceofAgentStartEventstart){System.out.println("[start replyId="+start.getReplyId()+"]");}elseif(eventinstanceofTextBlockDeltaEventdelta){accumulated.append(delta.getDelta());}elseif(eventinstanceofToolCallStartEventtc){System.out.println("[tool] "+tc.getToolCallName());}elseif(eventinstanceofToolResultEndEventend){System.out.println("[tool result state="+end.getState()+"]");}elseif(eventinstanceofAgentEndEventend){System.out.println("\n[end] full text:\n"+accumulated);}}).blockLast();这种设计让部署更加灵活:后端通过 SSE 把事件流推给前端,前端在客户端侧重建消息。即使连接中断,从任意检查点重放事件序列也能精确恢复消息状态。