欢迎光临
我们一直在努力

Agent Scope Java 2.x 系列【7】事件(Event)

文章目录

  • 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 把事件流推给前端,前端在客户端侧重建消息。即使连接中断,从任意检查点重放事件序列也能精确恢复消息状态。


    赞(0)
    未经允许不得转载:171主机测评 » Agent Scope Java 2.x 系列【7】事件(Event)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址