---
title: "agent - 同步与流式响应（call 与 stream）"
---

在 Solon AI 中，智能体（Agent）提供了两种主要的交互模式：同步等待的 `call()` 和异步流式的 `stream()`。这两种接口分别对应了不同的业务场景。


### 1、交互模式对比

| 模式 | 方法 | 返回类型 | 适用场景 |
|------|------|----------|----------|
| 同步 | `call()` | `AgentResponse` | 后台任务、批量处理、不需要实时展示过程 |
| 流式 | `stream()` | `Flux<AgentEvent>` | Web 对话界面、实时展示思考过程、进度反馈 |


### 2、同步调用（call）

```java
ReActAgent agent = ReActAgent.of(chatModel)
    .defaultToolAdd(new WeatherTools())
    .build();

// 同步获取最终结果（阻塞直到推理完成）
AgentResponse resp = agent.prompt("北京今天天气怎么样？")
    .call();

System.out.println(resp.getContent());
```

#### AgentResponse 主要方法

| 方法 | 描述 |
|------|------|
| `getContent()` | 获取最终答案文本 |
| `getMessage()` | 获取原始 Message 对象（含 role/content） |
| `getTrace()` | 获取推理轨迹（思考步骤、工具调用记录） |
| `getSession()` | 获取当前绑定的会话对象 |
| `getContext()` | 获取计算图上下文（任务状态快照，TeamAgent 可用） |
| `getMetrics()` | 获取执行指标（Token 消耗、耗时等） |
| `toBean(Class<T>)` | 将答案内容反序列化为结构化对象（JSON 输出场景） |

不同智能体的响应子类：

| 响应类型 | 对应智能体 | 说明 |
|----------|----------|------|
| `SimpleResponse` | SimpleAgent | 简单直接调用结果 |
| `ReActResponse` | ReActAgent | 含完整推理轨迹 |
| `TeamResponse` | TeamAgent | 含各子节点执行结果 |


### 3、流式响应（stream）

`stream()` 基于 **Project Reactor 的 `Flux<AgentEvent>`** 返回事件流，前端可据此逐帧渲染思考过程、工具调用和最终答案，实现类 ChatGPT 的打字机效果。

```java
Flux<AgentEvent> events = agent.prompt("北京今天天气怎么样？")
    .stream();

events.doOnNext(event -> {
    if (event instanceof ReasonDeltaEvent e) {
        System.out.print("[思考] " + e.getContent());           // 实时渲染"灰色思考气泡"
    } else if (event instanceof ToolCallStartEvent e) {
        System.out.println("[调用工具] " + e.getToolName() + "  参数: " + e.getToolArgs());
    } else if (event instanceof ToolCallEndEvent e) {
        System.out.println("[工具结果] " + e.getToolName() + " → " + e.getContent());
    } else if (event instanceof RunEndEvent e) {
        System.out.println("[最终答案] " + e.getContent());     // 渲染为正式回答气泡
    }
})
.doOnError(err -> System.err.println("流式错误: " + err.getMessage()))
.blockLast();
```

> **关于异常处理**：`call()` 会在异常时直接抛给调用方；而 `stream()` 的异常不会自动传播到外层 —— 必须在 `doOnError` 或 `onErrorResume` 中主动捕获，否则异常将静默丢失。


#### 全事件对照表

| 归属智能体 | 事件类型 | 触发时机 | 最低版本 |
|-----------|----------|----------|---------|
| **所有** | `AgentEvent` | 事件接口基类 | — |
| SimpleAgent | `SimpleStartEvent` | 运行开始 | v4.0.4+ |
|   | `ChatDeltaEvent` | 生成增量 | — |
|   | `SimpleEndEvent` | 运行结束 | — |
| ReActAgent | `RunStartEvent` | 推理循环开始 | v4.0.4+ |
|   | `ReasonStartEvent` | 每轮思考开始 | v4.0.4+ |
|   | `ReasonDeltaEvent` | 思考过程增量（含 isThinking 深度思考标志） | v4.0.4+ |
|   | `PlanEvent` | 计划制定/更新/修订（仅 PlanReAct 模式）| — |
|   | `ActionStartEvent` | 工具批次开始 | — |
|   | `ToolCallStartEvent` | 单工具调用开始 | v4.0.4+ |
|   | `ToolCallEndEvent` | 单工具调用结束，含执行结果 | v4.0.4+ |
|   | `ActionEndEvent` | 工具批次结束 | — |
|   | `ReasonEndEvent` | 每轮思考结束，含完整输出 | v4.0.4+ |
|   | `ContextSizeEvent` | 上下文大小检查（压缩时标记） | v4.0.0+ |
|   | `HITLPendingEvent` | 人工审批挂起 | v4.0.4+ |
|   | `HITLDecidedEvent` | 人工审批决策返回 | v4.0.4+ |
|   | `RunEndEvent` | 推理循环结束，含最终答案 | — |
| TeamAgent | `TeamStartEvent` | 团队任务开始 | v4.0.4+ |
|   | `NodeStartEvent` | 子节点开始 | — |
|   | `SupervisorDeltaEvent` | Supervisor 决策增量 | v4.0.4+ |
|   | `NodeEndEvent` | 子节点结束 | — |
|   | `TeamEndEvent` | 团队任务结束 | v4.0.4+ |

> TeamAgent 内部的子智能体（ReActAgent / SimpleAgent）也会各自发出其完整事件链。因此，订阅 TeamAgent 的流时可能收到上表中的所有事件类型，可通过 `event.getAgentName()` 区分来源。





### 4、AgentEvent 公共接口

所有事件均实现 `AgentEvent` 接口，提供以下基础方法：

| 方法 | 描述 |
|------|------|
| `getRunId()` | 本次推理运行的唯一 ID |
| `getAgentName()` | 产生该事件的智能体名称 |
| `getSession()` | 所属会话对象 |
| `getMessage()` | 关联的消息对象（部分事件可能为 null） |
| `getMeta()` | 事件元数据（Map 结构，用于扩展信息） |
| `hasMeta()` | 是否包含元数据 |
| `getContent()` | 事件内容文本（各事件含义不同，见下文） |
| `hasContent()` | 是否有内容文本 |


### 5、SimpleAgent 事件详解

SimpleAgent 流式时事件顺序固定：`SimpleStartEvent → ChatDeltaEvent(×N) → SimpleEndEvent`

#### SimpleStartEvent — 运行开始

```java
events.doOnNext(event -> {
    if (event instanceof SimpleStartEvent e) {
        System.out.println("SimpleAgent [" + e.getAgentName() + "] 开始运行, runId=" + e.getRunId());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getRunId()` | 本次运行 ID |
| `getAgentName()` | 智能体名称 |

#### ChatDeltaEvent — 内容增量块

流式推送期间持续触发，每次携带一小段文本（token 粒度）。

```java
StringBuilder sb = new StringBuilder();
events.doOnNext(event -> {
    if (event instanceof ChatDeltaEvent e) {
        sb.append(e.getContent()); // 累积增量片段
        System.out.print(e.getContent()); // 实时打印，模拟打字机效果
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 当前增量文本片段（非累积，需自行拼接） |

#### SimpleEndEvent — 运行结束

```java
events.doOnNext(event -> {
    if (event instanceof SimpleEndEvent e) {
        System.out.println("\n[完整答案] " + e.getContent());
        System.out.println("[Token 消耗] " + e.getMetrics());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 完整的聚合答案（所有增量拼接后的结果） |
| `getMessage()` | 完整 Message 对象 |
| `getMetrics()` | 执行指标（耗时、token 数等） |


### 6、ReActAgent 事件详解

ReActAgent 每轮推理的典型事件顺序：

```
RunStartEvent
  → ReasonStartEvent
    → ReasonDeltaEvent (×N)   ← 思考增量
    → [PlanEvent]             ← 仅 PlanReAct 模式
  → ReasonEndEvent
  → ActionStartEvent
    → ToolCallStartEvent (×M)
    → ToolCallEndEvent   (×M)
  → ActionEndEvent
  → [ContextSizeEvent]        ← 上下文过大时触发
  → ReasonStartEvent          ← 下一轮继续...
    ...
RunEndEvent                   ← 最终答案
```

> 若启用了 HITL，`HITLPendingEvent` 会在某轮 `ActionEndEvent` 之后插入，等待人工决策后再继续。

### RunStartEvent — 推理运行开始（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof RunStartEvent e) {
        System.out.println("推理开始 runId=" + e.getRunId());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getRunId()` | 本次运行唯一 ID（整个推理过程共享） |
| `getAgentName()` | 智能体名称 |

#### ReasonStartEvent — 单轮思考开始（v4.0.4+）

每次 LLM 开始生成（包含多轮 ReAct 循环中的每一轮）时触发。

```java
events.doOnNext(event -> {
    if (event instanceof ReasonStartEvent e) {
        System.out.println("第 " + e.getTurn() + " 轮思考开始");
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getTurn()` | 当前是第几轮推理（从 1 开始） |

#### ReasonDeltaEvent — 思考增量块（v4.0.4+）

LLM 流式生成过程中持续触发。需通过 `isThinking()` 区分普通输出和深度思考（CoT）内容。

```java
StringBuilder thought = new StringBuilder();
StringBuilder answer  = new StringBuilder();

events.doOnNext(event -> {
    if (event instanceof ReasonDeltaEvent e) {
        if (e.isThinking()) {
            // 深度思考内容（如 DeepSeek-R1、Claude 3.7 的 <thinking> 部分）
            thought.append(e.getContent());
            System.out.print("💭 " + e.getContent()); // 渲染为灰色小字
        } else {
            // 普通生成内容（Final Answer 前的推理文字 / Thought 部分）
            answer.append(e.getContent());
            System.out.print(e.getContent());
        }
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 当前增量文本片段 |
| `isThinking()` | `true` 表示该片段属于模型深度思考（reasoning token）；`false` 表示普通输出 |

#### PlanEvent — 规划事件（PlanReAct 模式专属）

仅在开启 `planningMode(true)` 的 PlanReActAgent 中出现，贯穿整个任务生命周期。

```java
events.doOnNext(event -> {
    if (event instanceof PlanEvent e) {
        switch (e.getStage()) {
            case CREATE   -> System.out.println("[计划已制定]\n" + e.getContent());
            case PROGRESS -> System.out.println("[计划更新]\n" + e.getContent());
            case REVISE   -> System.out.println("[计划已修订]\n" + e.getContent());
        }
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getStage()` | 规划阶段：`CREATE`（初次生成）/ `PROGRESS`（执行进度更新）/ `REVISE`（计划被修订）|
| `getContent()` | 计划内容文本（Markdown 格式的步骤列表） |

#### ActionStartEvent — 工具调用批次开始

一轮思考完成后，若 LLM 决定调用工具，则先触发 `ActionStartEvent`，再依次触发各工具的事件。

```java
events.doOnNext(event -> {
    if (event instanceof ActionStartEvent e) {
        System.out.println("--- 开始执行工具 (第 " + e.getTurn() + " 轮) ---");
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getTurn()` | 所在推理轮次 |

#### ToolCallStartEvent — 单个工具调用开始（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof ToolCallStartEvent e) {
        System.out.println("调用工具: " + e.getToolName());
        System.out.println("  参数: " + e.getToolArgs()); // JSON 字符串
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getToolName()` | 工具名称（即注册时的 name） |
| `getToolArgs()` | 工具入参（JSON 字符串格式） |

#### ToolCallEndEvent — 单个工具调用结束（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof ToolCallEndEvent e) {
        System.out.println("工具 [" + e.getToolName() + "] 执行完毕");
        System.out.println("  结果: " + e.getContent());
        if (e.isError()) {
            System.err.println("  ⚠ 执行异常: " + e.getErrorMessage());
        }
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getToolName()` | 工具名称 |
| `getToolArgs()` | 工具入参（同 ToolCallStartEvent） |
| `getContent()` | 工具执行返回的观察结果（Observation） |
| `isError()` | 工具是否执行失败 |
| `getErrorMessage()` | 错误信息（`isError()` 为 true 时有值） |

#### ActionEndEvent — 工具调用批次结束

```java
events.doOnNext(event -> {
    if (event instanceof ActionEndEvent e) {
        System.out.println("--- 工具执行完毕 (第 " + e.getTurn() + " 轮) ---");
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getTurn()` | 所在推理轮次 |

#### ReasonEndEvent — 单轮思考结束（v4.0.4+）

本轮 LLM 推理完成后触发，包含完整的本轮输出内容（各 Delta 的聚合结果）。

```java
events.doOnNext(event -> {
    if (event instanceof ReasonEndEvent e) {
        System.out.println("[第 " + e.getTurn() + " 轮推理完成]");
        System.out.println("  完整输出: " + e.getContent());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getTurn()` | 所在推理轮次 |
| `getContent()` | 本轮 LLM 输出的完整文本（ReasonDelta 的聚合） |
| `getMessage()` | 本轮完整 Message 对象 |

#### ContextSizeEvent — 上下文大小状态（v4.0.0+）

在每轮工具执行后触发，反映当前上下文窗口的使用情况。当触发压缩时，`isCompressed()` 为 true。

```java
events.doOnNext(event -> {
    if (event instanceof ContextSizeEvent e) {
        System.out.printf("上下文: %d / %d tokens%n", e.getCurrentSize(), e.getMaxSize());
        if (e.isCompressed()) {
            System.out.println("  ↳ 已自动压缩历史上下文");
        }
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getCurrentSize()` | 当前上下文占用的 token 数 |
| `getMaxSize()` | 配置的最大上下文 token 限制 |
| `isCompressed()` | 本轮是否触发了上下文压缩 |

#### HITLPendingEvent — 人工介入挂起（v4.0.4+）

当智能体将要调用某个需要人工审批的工具时，推理暂停并发出此事件，等待人工介入决策。

```java
events.doOnNext(event -> {
    if (event instanceof HITLPendingEvent e) {
        System.out.println("⏸ 推理已挂起，等待人工审批：");
        e.getPendingList().forEach(item ->
            System.out.println("  - 工具: " + item.getToolName() + "  参数: " + item.getToolArgs())
        );
        // 通知前端渲染审批 UI
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getPendingList()` | 待审批的工具调用列表（`List<ToolCallItem>`） |
| `getSessionPending()` | 会话挂起对象，可通过它提交审批决策 |

#### HITLDecidedEvent — 人工介入决策结果（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof HITLDecidedEvent e) {
        if (e.isApproved()) {
            System.out.println("✅ 审批通过，继续执行");
        } else {
            System.out.println("❌ 审批拒绝，已跳过: " + e.getReason());
        }
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `isApproved()` | 是否批准执行 |
| `getReason()` | 拒绝时的理由文本（可选） |

#### RunEndEvent — 推理运行结束

```java
events.doOnNext(event -> {
    if (event instanceof RunEndEvent e) {
        System.out.println("[最终答案] " + e.getContent());
        System.out.println("[轮次] " + e.getTurns());
        System.out.println("[Token] " + e.getMetrics());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 最终答案文本 |
| `getMessage()` | 最终 Message 对象 |
| `getTurns()` | 本次推理共经历的轮次数 |
| `getTrace()` | 完整推理轨迹（ReActTrace） |
| `getMetrics()` | 执行指标（耗时、token 消耗） |


### 7、TeamAgent 事件详解

TeamAgent 内部由多个子智能体协作完成任务，每个子节点的执行均会产生独立事件。

典型事件顺序：

```
TeamStartEvent
  → NodeStartEvent (子节点1)
    → [RunStartEvent ... RunEndEvent]  ← 子节点内部的 ReActAgent 事件
  → NodeEndEvent (子节点1)
  → SupervisorDeltaEvent (×N)  ← Supervisor 调度决策增量
  → NodeStartEvent (子节点2)
    ...
TeamEndEvent
```

#### TeamStartEvent — 团队任务开始（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof TeamStartEvent e) {
        System.out.println("团队任务开始: " + e.getAgentName() + " runId=" + e.getRunId());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getRunId()` | 本次运行 ID |
| `getAgentName()` | TeamAgent 名称 |

#### NodeStartEvent — 子节点执行开始

某个子智能体（成员节点）被 Supervisor 指派开始执行时触发。

```java
events.doOnNext(event -> {
    if (event instanceof NodeStartEvent e) {
        System.out.println("▶ 子智能体 [" + e.getNodeName() + "] 开始执行");
        System.out.println("  任务: " + e.getContent());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getNodeName()` | 正在执行的子智能体名称 |
| `getContent()` | Supervisor 给该子节点分配的任务描述 |

#### SupervisorDeltaEvent — 调度者决策增量（v4.0.4+）

Supervisor（ReAct 或 LLM 协调器）在作出调度决策时流式输出其决策过程。

```java
StringBuilder supervisorThought = new StringBuilder();
events.doOnNext(event -> {
    if (event instanceof SupervisorDeltaEvent e) {
        supervisorThought.append(e.getContent());
        System.out.print("[Supervisor] " + e.getContent());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 调度决策的增量文本片段 |

#### NodeEndEvent — 子节点执行结束

```java
events.doOnNext(event -> {
    if (event instanceof NodeEndEvent e) {
        System.out.println("■ 子智能体 [" + e.getNodeName() + "] 执行完毕");
        System.out.println("  输出: " + e.getContent());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getNodeName()` | 已执行完毕的子智能体名称 |
| `getContent()` | 该子节点的输出结果 |

#### TeamEndEvent — 团队任务结束（v4.0.4+）

```java
events.doOnNext(event -> {
    if (event instanceof TeamEndEvent e) {
        System.out.println("[团队最终结果] " + e.getContent());
        System.out.println("[指标] " + e.getMetrics());
    }
});
```

| 字段/方法 | 说明 |
|----------|------|
| `getContent()` | 整个团队协作的最终答案 |
| `getMessage()` | 最终 Message 对象 |
| `getMetrics()` | 执行指标（耗时、token 等） |




### 9、完整流式处理示例（综合版）

```java
agent.prompt("帮我查北京今天的天气，并翻译成英文")
    .stream()
    .doOnNext(event -> {
        if (event instanceof RunStartEvent e) {
            System.out.println("=== 推理开始 ===");

        } else if (event instanceof ReasonDeltaEvent e) {
            if (e.isThinking()) {
                System.out.print("\033[90m" + e.getContent() + "\033[0m"); // 深度思考渲染为灰色
            } else {
                System.out.print(e.getContent());
            }

        } else if (event instanceof ToolCallStartEvent e) {
            System.out.printf("%n[→ 调用 %s]  %s%n", e.getToolName(), e.getToolArgs());

        } else if (event instanceof ToolCallEndEvent e) {
            if (e.isError()) {
                System.err.printf("[✗ %s 失败] %s%n", e.getToolName(), e.getErrorMessage());
            } else {
                System.out.printf("[← %s 返回] %s%n", e.getToolName(), e.getContent());
            }

        } else if (event instanceof ContextSizeEvent e) {
            if (e.isCompressed()) {
                System.out.printf("%n[上下文已压缩: %d → 压缩后]%n", e.getCurrentSize());
            }

        } else if (event instanceof HITLPendingEvent e) {
            System.out.println("\n⏸ 等待人工审批...");
            e.getPendingList().forEach(item ->
                System.out.println("  需审批: " + item.getToolName()));

        } else if (event instanceof RunEndEvent e) {
            System.out.println("\n=== 最终答案 ===");
            System.out.println(e.getContent());
            System.out.println("共 " + e.getTurns() + " 轮，" + e.getMetrics());
        }
    })
    .doOnError(err -> System.err.println("错误: " + err.getMessage()))
    .blockLast();
```


### 10、开发建议

- **UI 渲染分层**：`ReasonDeltaEvent`（isThinking=true）→ 灰色思考气泡；`ReasonDeltaEvent`（isThinking=false）→ 推理过程展示；`RunEndEvent` / `SimpleEndEvent` / `TeamEndEvent` → 正式答案气泡。
- **错误捕获**：务必注册 `doOnError` / `onErrorResume`，否则 Reactor 流中的异常默认只打印 warn 日志，不会抛到调用方。
- **资源回收**：使用 `blockLast()` 等阻塞操作时，确保在 Web 场景下使用 SSE/WebFlux 订阅，避免阻塞 IO 线程。
- **防死循环**：为 ReActAgent 设置合理的 `maxTurns`，防止工具调用陷入死循环。
- **TeamAgent 事件过滤**：TeamAgent 的流会包含子节点内部的所有 ReAct 事件，建议通过 `event.getAgentName()` 区分来源。
