文章目录
- 1. 概述
- 2. 继承关系
- 3. 事件生命周期
-
- 3.1 顶层标识
- 3.2 二级标识
- 3.3 标准三段式模式
- 3.4 执行流程
- 3. 事件分类详解
-
- 3.1 智能体调用事件
- 3.2 模型调用事件
- 3.3 文本块事件
- 3.4 思考块事件
- 3.5 数据块事件
- 3.6 工具调用事件
- 3.7 工具结果事件
- 3.8 异常与中断事件
- 3.9 HITL 事件
- 3.10 子 Agent 事件
- 4. 从事件流重建消息
1. 概述
Agent 执行一个复杂任务不是瞬间完成的,它会经历多个连续的步骤,每个步骤都会产生增量变化,Event 的作用就是把这些原本不可见的内部执行过程,变成一个个可观测、可处理的标准化对象。
具体来说,增量进度包括这三类最常见的场景:
- 文本 token 增量到达:大模型生成回复是一个字一个字出来的,每生成几个 token 就会触发一个 TextBlockDeltaEvent,前端可以实时渲染,用户不会看到空白的加载页面。
- 工具调用逐步构建:Agent 决定调用工具时,工具的参数也是逐步生成的,最后触发 ToolCallEndEvent 表示参数构建完成。
- 工具结果流式返回:具执行后的结果也可能是流式的,这时候会产生 ToolResultTextDeltaEvent 或 ToolResultDataDeltaEvent,实时推送工具的输出内容。
v2 将 v1 的 6 种粗粒度 Event 拆分为 27 种细粒度 AgentEvent,把 Agent 执行的每一个微小步骤都暴露了出来:
- 前端不需要做任何 diff,只需要监听对应的事件类型,直接把 delta 内容追加到页面上即可
- 可以实现更丰富的交互效果:比如工具调用时显示加载动画、思考过程用灰色字体区分、多媒体内容边下载边显示
- 网络中断后可以从断点恢复:只需要重放最后一个收到的事件之后的序列,就能精确还原对话状态,不需要重新执行整个任务
2. 继承关系
所有事件继承自 AgentEvent 基类:
AgentEvent (abstract)
│ id, createdAt, source, getType()
│
├── AgentStartEvent / AgentEndEvent
├── ModelCallStartEvent / ModelCallEndEvent
├── TextBlockStartEvent / TextBlockDeltaEvent / TextBlockEndEvent
├── ThinkingBlockStartEvent / ThinkingBlockDeltaEvent / ThinkingBlockEndEvent
├── DataBlockStartEvent / DataBlockDeltaEvent / DataBlockEndEvent
├── ToolCallStartEvent / ToolCallDeltaEvent / ToolCallEndEvent
├── ToolResultStartEvent / ToolResultTextDeltaEvent
│ / ToolResultDataDeltaEvent / ToolResultEndEvent
├── ExceedMaxItersEvent / RequestStopEvent
├── RequireUserConfirmEvent / UserConfirmResultEvent
├── RequireExternalExecutionEvent / ExternalExecutionResultEvent
└── SubagentExposedEvent
AgentEvent 提供公共方法:
| getId() | String | 唯一事件标识符 |
| getCreatedAt() | String | ISO 8601 时间戳 |
| getType() | AgentEventType | 事件类型枚举 |
| getSource() | String | 来源路径。顶层 Agent 为 null;子 Agent 为斜杠分隔路径(如 "main/researcher") |
3. 事件生命周期
3.1 顶层标识
replyId 是最高层级的关联 ID,对应 Agent 对一条用户消息的完整一次回复:
- 同一次 streamEvents() 调用产生的所有事件,共享同一个 replyId
- 当页面同时存在多轮对话、多个并发 Agent 请求时,通过 replyId 可以立刻判断当前事件属于哪一条消息,避免把 A 回复的内容拼到 B 消息上
同一次回复中所有事件共享相同的 replyId。用 blockId 关联文本/思考/数据块事件,用 toolCallId 关联工具调用和工具结果事件。
3.2 二级标识
一整轮回复里往往不只有一段文字,可能同时包含:文本回答、思考过程、图片、多次工具调用。
这时候就需要二级 ID 做内部分组:
- blockId:用于文本/思考/数据块事件,每一段独立内容(比如一段正文、一张图片、一段思考链)拥有唯一的 blockId
- toolCallId:用于工具调用+工具结果事件,每一次工具调用拥有唯一的 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 执行流程
流程分段说明:
推理阶段
- 启动:AgentStartEvent → 发起模型调用 ModelCallStartEvent
- 文本块流式:TextBlockStart → 多段Delta → TextBlockEnd
- 数据块流式:DataBlockStart → 多段Delta → DataBlockEnd
- 工具调用流式:ToolCallStart → 多段Delta → ToolCallEnd
- 模型推理结束:ModelCallEndEvent
执行阶段
- 工具结果回传:ToolResultStart → 文本分片Delta、数据分片Delta → ToolResultEnd
- 全流程收尾:AgentEndEvent
Agent
Client
Agent
Client
#mermaid-svg-snMEUpMaFlP9zC9h{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-snMEUpMaFlP9zC9h .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-snMEUpMaFlP9zC9h .error-icon{fill:#552222;}#mermaid-svg-snMEUpMaFlP9zC9h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-snMEUpMaFlP9zC9h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-snMEUpMaFlP9zC9h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-snMEUpMaFlP9zC9h .marker.cross{stroke:#333333;}#mermaid-svg-snMEUpMaFlP9zC9h svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-snMEUpMaFlP9zC9h p{margin:0;}#mermaid-svg-snMEUpMaFlP9zC9h .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-snMEUpMaFlP9zC9h text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-snMEUpMaFlP9zC9h .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-snMEUpMaFlP9zC9h .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-snMEUpMaFlP9zC9h .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-snMEUpMaFlP9zC9h .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-snMEUpMaFlP9zC9h #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-snMEUpMaFlP9zC9h .sequenceNumber{fill:white;}#mermaid-svg-snMEUpMaFlP9zC9h #sequencenumber{fill:#333;}#mermaid-svg-snMEUpMaFlP9zC9h #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-snMEUpMaFlP9zC9h .messageText{fill:#333;stroke:none;}#mermaid-svg-snMEUpMaFlP9zC9h .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-snMEUpMaFlP9zC9h .labelText,#mermaid-svg-snMEUpMaFlP9zC9h .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-snMEUpMaFlP9zC9h .loopText,#mermaid-svg-snMEUpMaFlP9zC9h .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-snMEUpMaFlP9zC9h .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-snMEUpMaFlP9zC9h .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-snMEUpMaFlP9zC9h .noteText,#mermaid-svg-snMEUpMaFlP9zC9h .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-snMEUpMaFlP9zC9h .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-snMEUpMaFlP9zC9h .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-snMEUpMaFlP9zC9h .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-snMEUpMaFlP9zC9h .actorPopupMenu{position:absolute;}#mermaid-svg-snMEUpMaFlP9zC9h .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-snMEUpMaFlP9zC9h .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-snMEUpMaFlP9zC9h .actor-man circle,#mermaid-svg-snMEUpMaFlP9zC9h line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-snMEUpMaFlP9zC9h :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
推理阶段
TextBlock (blockId)
DataBlock (blockId)
ToolUseBlock (toolCallId)
执行阶段
ToolResultBlock (toolCallId)
AgentStartEvent
ModelCallStartEvent
TextBlockStartEvent
TextBlockDeltaEvent (×N)
TextBlockEndEvent
DataBlockStartEvent
DataBlockDeltaEvent (×N)
DataBlockEndEvent
ToolCallStartEvent
ToolCallDeltaEvent (×N)
ToolCallEndEvent
ModelCallEndEvent
ToolResultStartEvent
ToolResultTextDeltaEvent (×N)
ToolResultDataDeltaEvent (×N)
ToolResultEndEvent
AgentEndEvent
3. 事件分类详解
3.1 智能体调用事件
AgentStartEvent:当智能体开始处理一次调用请求时触发回复:
| getReplyId() | String | 回复消息 ID |
| getSessionId() | String | 会话 ID |
| getName() | String | Agent 名称 |
| getRole() | String | 角色(默认 "assistant") |
AgentEndEvent :在智能体完成一次调用任务的处理后触发回复:
| getReplyId() | String | 回复消息 ID |
3.2 模型调用事件
ModelCallStartEvent:当模型开始调用时触发回复。
ModelCallEndEvent:当模型调用结束时触发回复。
| ModelCallStartEvent | modelName |
| ModelCallEndEvent | inputTokens, outputTokens |
3.3 文本块事件
TextBlockStartEvent:新的文本块开始。
| getReplyId() | String | 回复消息 ID |
| getBlockId() | String | 文本块唯一标识符 |
TextBlockDeltaEvent:增量文本到达。
| getReplyId() | String | 回复消息 ID |
| getBlockId() | String | 文本块唯一标识符 |
| getDelta() | String | 增量文本内容 |
TextBlockEndEvent:文本块完成。
| getReplyId() | String | 回复消息 ID |
| getBlockId() | String | 文本块唯一标识符 |
3.4 思考块事件
与文本事件结构一致,承载模型思维链内容:
- ThinkingBlockStartEvent:一段独立思维链内容开始输出。携带 replyId、唯一 blockId,告知消费端准备接收思考内容分片。
- ThinkingBlockDeltaEvent 承载思维链增量文字片段,模型每生成一小段推理思考内容就推送一条;getDelta() 获取增量文本,比如模型内心分步推理、自我校验、思路推演内容。
- ThinkingBlockEndEvent:当前这一段思维链全部输出完毕,标记该 blockId 对应的思考块闭合,不会再有新的增量数据。
3.5 数据块事件
承载图片/音频/视频等二进制数据:
- DataBlockStartEvent:getMediaType() 返回 MIME 类型(如 "image/png")
- DataBlockDeltaEvent:getData() 返回增量 base64 编码数据
- DataBlockEndEvent :结束事件
3.6 工具调用事件
ToolCallStartEvent:Agent 开始工具调用。
| getReplyId() | String | 回复消息 ID |
| getToolCallId() | String | 工具调用唯一标识符 |
| getToolCallName() | String | 被调用的工具名称 |
ToolCallDeltaEvent:增量工具参数到达,getDelta() 返回 JSON 参数片段。
ToolCallEndEvent:工具调用参数完成。
3.7 工具结果事件
ToolResultStartEvent:工具开始执行,携带 toolCallId、toolCallName。
ToolResultTextDeltaEvent:工具的增量文本输出,getDelta() 返回文本片段。
ToolResultDataDeltaEvent :工具的二进制数据输出,包含 mediaType / data / url 字段。
ToolResultEndEvent :工具执行完成。
3.8 异常与中断事件
ExceedMaxItersEvent:达到最大推理执行迭代次数,含 replyId 。
RequestStopEvent:中间件或工具发起的提前停止请求。
3.9 HITL 事件
| RequireUserConfirmEvent | Agent → 客户端 | replyId, toolCalls(List<ToolUseBlock>) |
| UserConfirmResultEvent | 客户端 → Agent(输入) | List<ConfirmResult> |
| RequireExternalExecutionEvent | Agent → 客户端 | 需外部系统执行 |
| ExternalExecutionResultEvent | 客户端 → Agent(输入) | List<ToolResultBlock> |
3.10 子 Agent 事件
SubagentExposedEvent``:子 Agent` 被暴露为用户入口点。
| getSubagentId() | String | 子 Agent 唯一标识 |
| getAgentId() | String | 子 Agent 类型 ID |
| getSessionId() | String | 子 Agent 会话 ID |
| getLabel() | String | 用户可见标签名(可选) |
SSE / 流式消费端可据此在 UI 上渲染新的会话入口。
4. 从事件流重建消息
事件与消息并非相互独立,而是同一数据的两种视图。streamEvents 产出的事件流可以按 replyId / blockId / toolCallId 聚合还原成完整的 AssistantMessage。这保证了最终消息状态可以仅凭事件流完整还原。
StringBuilder accumulated = new StringBuilder();
agent.streamEvents(userMsg)
.doOnNext(event -> {
if (event instanceof AgentStartEvent start) {
System.out.println("[start replyId=" + start.getReplyId() + "]");
} else if (event instanceof TextBlockDeltaEvent delta) {
accumulated.append(delta.getDelta());
} else if (event instanceof ToolCallStartEvent tc) {
System.out.println("[tool] " + tc.getToolCallName());
} else if (event instanceof ToolResultEndEvent end) {
System.out.println("[tool result state=" + end.getState() + "]");
} else if (event instanceof AgentEndEvent end) {
System.out.println("\\n[end] full text:\\n" + accumulated);
}
})
.blockLast();
这种设计让部署更加灵活:后端通过 SSE 把事件流推给前端,前端在客户端侧重建消息。即使连接中断,从任意检查点重放事件序列也能精确恢复消息状态。




