Solon v4.0.5

agent - 同步与流式响应(call 与 stream)

</> markdown
2026年7月30日 下午5:58:41

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

1、交互模式对比

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

2、同步调用(call)

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 输出场景)

不同智能体的响应子类:

响应类型对应智能体说明
SimpleResponseSimpleAgent简单直接调用结果
ReActResponseReActAgent含完整推理轨迹
TeamResponseTeamAgent含各子节点执行结果

3、流式响应(stream)

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

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() 的异常不会自动传播到外层 —— 必须在 doOnErroronErrorResume 中主动捕获,否则异常将静默丢失。

全事件对照表

归属智能体事件类型触发时机最低版本
所有AgentEvent事件接口基类
SimpleAgentSimpleStartEvent运行开始v4.0.4+
ChatDeltaEvent生成增量
SimpleEndEvent运行结束
ReActAgentRunStartEvent推理循环开始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推理循环结束,含最终答案
TeamAgentTeamStartEvent团队任务开始v4.0.4+
NodeStartEvent子节点开始
SupervisorDeltaEventSupervisor 决策增量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 — 运行开始

events.doOnNext(event -> {
    if (event instanceof SimpleStartEvent e) {
        System.out.println("SimpleAgent [" + e.getAgentName() + "] 开始运行, runId=" + e.getRunId());
    }
});
字段/方法说明
getRunId()本次运行 ID
getAgentName()智能体名称

ChatDeltaEvent — 内容增量块

流式推送期间持续触发,每次携带一小段文本(token 粒度)。

StringBuilder sb = new StringBuilder();
events.doOnNext(event -> {
    if (event instanceof ChatDeltaEvent e) {
        sb.append(e.getContent()); // 累积增量片段
        System.out.print(e.getContent()); // 实时打印,模拟打字机效果
    }
});
字段/方法说明
getContent()当前增量文本片段(非累积,需自行拼接)

SimpleEndEvent — 运行结束

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+)

events.doOnNext(event -> {
    if (event instanceof RunStartEvent e) {
        System.out.println("推理开始 runId=" + e.getRunId());
    }
});
字段/方法说明
getRunId()本次运行唯一 ID(整个推理过程共享)
getAgentName()智能体名称

ReasonStartEvent — 单轮思考开始(v4.0.4+)

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

events.doOnNext(event -> {
    if (event instanceof ReasonStartEvent e) {
        System.out.println("第 " + e.getTurn() + " 轮思考开始");
    }
});
字段/方法说明
getTurn()当前是第几轮推理(从 1 开始)

ReasonDeltaEvent — 思考增量块(v4.0.4+)

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

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 中出现,贯穿整个任务生命周期。

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,再依次触发各工具的事件。

events.doOnNext(event -> {
    if (event instanceof ActionStartEvent e) {
        System.out.println("--- 开始执行工具 (第 " + e.getTurn() + " 轮) ---");
    }
});
字段/方法说明
getTurn()所在推理轮次

ToolCallStartEvent — 单个工具调用开始(v4.0.4+)

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+)

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 — 工具调用批次结束

events.doOnNext(event -> {
    if (event instanceof ActionEndEvent e) {
        System.out.println("--- 工具执行完毕 (第 " + e.getTurn() + " 轮) ---");
    }
});
字段/方法说明
getTurn()所在推理轮次

ReasonEndEvent — 单轮思考结束(v4.0.4+)

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

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。

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+)

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

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+)

events.doOnNext(event -> {
    if (event instanceof HITLDecidedEvent e) {
        if (e.isApproved()) {
            System.out.println("✅ 审批通过,继续执行");
        } else {
            System.out.println("❌ 审批拒绝,已跳过: " + e.getReason());
        }
    }
});
字段/方法说明
isApproved()是否批准执行
getReason()拒绝时的理由文本(可选)

RunEndEvent — 推理运行结束

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+)

events.doOnNext(event -> {
    if (event instanceof TeamStartEvent e) {
        System.out.println("团队任务开始: " + e.getAgentName() + " runId=" + e.getRunId());
    }
});
字段/方法说明
getRunId()本次运行 ID
getAgentName()TeamAgent 名称

NodeStartEvent — 子节点执行开始

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

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 协调器)在作出调度决策时流式输出其决策过程。

StringBuilder supervisorThought = new StringBuilder();
events.doOnNext(event -> {
    if (event instanceof SupervisorDeltaEvent e) {
        supervisorThought.append(e.getContent());
        System.out.print("[Supervisor] " + e.getContent());
    }
});
字段/方法说明
getContent()调度决策的增量文本片段

NodeEndEvent — 子节点执行结束

events.doOnNext(event -> {
    if (event instanceof NodeEndEvent e) {
        System.out.println("■ 子智能体 [" + e.getNodeName() + "] 执行完毕");
        System.out.println("  输出: " + e.getContent());
    }
});
字段/方法说明
getNodeName()已执行完毕的子智能体名称
getContent()该子节点的输出结果

TeamEndEvent — 团队任务结束(v4.0.4+)

events.doOnNext(event -> {
    if (event instanceof TeamEndEvent e) {
        System.out.println("[团队最终结果] " + e.getContent());
        System.out.println("[指标] " + e.getMetrics());
    }
});
字段/方法说明
getContent()整个团队协作的最终答案
getMessage()最终 Message 对象
getMetrics()执行指标(耗时、token 等)

9、完整流式处理示例(综合版)

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() 区分来源。