【SAA实战】第 1 篇:ReactAgent 入门——先撸个"会调工具的助手"跑起来

风格说明:这一篇不走"先背概念"的路子。咱们直接从需求出发,代码一步步长出来,遇到知识点在代码旁边顺势讲。你只要会 Spring AI 的 ChatClient 就能跟下来。

① 先别学概念,接个活儿

假设你今天接到个需求:做个公司内部的「天气小助手」------员工问"北京今天天气咋样",它去调一个天气接口把结果带回来,还能接着聊。

你会 Spring AI,这活儿闭眼能撸:ChatClient 配个工具,完事。但今天咱们不这么干,改用 Spring AI Alibaba 的 ReactAgent 来撸。目的不是"换个 API 写一遍",而是让你亲手跑一遍之后,自己体会它和 ChatClient 到底差在哪儿。

先把依赖加上(版本 1.1.2.0,两个要对齐):

xml 复制代码
<dependency>
  <groupId>com.alibaba.cloud.ai</groupId>
  <artifactId>spring-ai-alibaba-agent-framework</artifactId>
  <version>1.1.2.0</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud.ai</groupId>
  <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
  <version>1.1.2.0</version>
</dependency>

API Key 走环境变量(别硬编码进仓库):

bash 复制代码
export AI_DASHSCOPE_API_KEY=你的key

② 最小能跑的 Agent,十来行

先不管原理,把下面这段跑起来------它定义了个天气工具,塞给 Agent,问一句,把答案打出来。

java 复制代码
// 1. 先有个模型(DashScope 的 ChatModel)
DashScopeApi dashScopeApi = DashScopeApi.builder()
    .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
    .build();
ChatModel chatModel = DashScopeChatModel.builder()
    .dashScopeApi(dashScopeApi)
    .build();

// 2. 定义工具:一个函数,输入城市名,返回天气
public static class WeatherRequest { public String location; }

public class WeatherTool implements BiFunction<WeatherRequest, ToolContext, String> {
    @Override
    public String apply(WeatherRequest req, ToolContext toolContext) {
        return req.location + " 永远是晴天!";
    }
}
ToolCallback weatherTool = FunctionToolCallback.builder("get_weather", new WeatherTool())
    .description("根据城市名查询天气")
    .inputType(WeatherRequest.class)
    .build();

// 3. 把模型 + 工具 交给 ReactAgent
ReactAgent agent = ReactAgent.builder()
    .name("weather_agent")
    .model(chatModel)
    .tools(weatherTool)
    .systemPrompt("你是一个乐于助人的天气助手")
    .saver(new MemorySaver())
    .build();

// 4. 跑
AssistantMessage response = agent.call("旧金山天气怎么样");
System.out.println(response.getText());

跑起来,控制台会打出类似 旧金山 永远是晴天! 的结果。

到这儿你八成想问:这跟 ChatClient.tools() 有啥区别?不都是"给个工具、问一句、它自己调工具回答"吗?

问得对。区别不在"能不能调工具"------Spring AI 的 ChatClient 早就能自动跑工具循环了,不用自己手写。区别得等咱们把这个 Agent 再往前推两步,才看得出来。

③ 推进一步:让它记事儿(多轮对话)

"San Francisco 天气咋样"之后,员工又问"那明天呢?"。在 ChatClient 里你要么靠 ChatMemory advisor 把历史塞回去,要么自己维护消息列表。ReactAgent 这边,你只要一个 threadId

java 复制代码
ReactAgent agent = ReactAgent.builder()
    .name("weather_agent")
    .model(chatModel)
    .tools(weatherTool)
    .saver(new MemorySaver())          // 就这行在管记忆
    .build();

// 同一个 threadId,串起一整段对话
RunnableConfig config = RunnableConfig.builder().threadId("user_123").build();

agent.call("今天旧金山天气怎么样", config);
agent.call("那明天呢", config);   // 模型知道"明天"指的是旧金山

你发现没------表面上看,ChatClient + ChatMemory 也能干一样的事 。所以"多轮记忆"压根不是 ReactAgent 的独门绝技。那它 saver 存的到底多了啥?先卖个关子,第 ⑤ 节揭晓(这是全文最关键的一点,也是很多人第一次用会迷糊的地方)。

④ 推进两步:看清它在"想"啥(stream 看过程)

再补一刀:"你们这玩意儿像个黑盒,我都不晓得它到底调没调工具、调了啥,排查问题抓瞎。"

ChatClient 给你的是最终一句回复,中间"模型思考 → 决定调工具 → 工具返回 → 再思考"这串过程你看不到。ReactAgent 用 stream() 把这串过程全吐出来:

java 复制代码
Flux<NodeOutput> stream = agent.stream("旧金山天气怎么样", config);
stream.subscribe(output -> {
    String node = output.node();      // 当前执行完的节点名,如 agent 的模型节点 / 工具节点
    boolean isStart = output.isSTART();
    boolean isEnd = output.isEND();
    // output.state() 能直接拿到完整运行时状态(消息、工具 IO 等都在里面)
});

每个 NodeOutput 代表图里"一个节点跑完"。node() 给你节点名(比如模型节点、工具节点),isSTART() / isEND() 标出首尾,state() 能直接拿到完整的运行时状态------消息、工具输入输出全在里面。这相当于给 Agent 装了"链路追踪",你一眼能看出它哪一步在调工具、哪一步跑完了,排查"它怎么乱调工具"这种问题价值巨大。

⑤ 最关键的点:MemorySaver 和 ChatMemory,到底差在哪

刚才 ③ 留的扣。很多人(包括我第一次用)以为 saver 就是 ReactAgent 版的 ChatMemory。其实不是一回事,差在存的东西不一样

  • ChatClient + ChatMemory 存的是聊天记录------谁说了啥,一张消息表。模型本身还是无状态的,历史只是作为文本重新喂回去。就像每次开会前,助理把纪要打印给你,你看了才有上下文。
  • ReactAgent + MemorySaver / Checkpointer 存的是运行时快照------除了聊天记录,还存了"工具调了啥、返回了啥、图执行到哪个节点、下一步要干啥"。

用游戏类比最准:ChatMemory 是"战绩列表"(你打过哪些怪);Checkpointer 是"存档点"(你站在地图哪个坐标、血量是几、背包有啥、任务到哪步了)。

正因为存的是"完整执行状态",你才能玩出这些花活儿(后面高级篇逐个讲):

  • 暂停 / 恢复执行:工具要扣钱了,先停一下让人确认(human-in-the-loop);
  • 时间旅行 / 回滚:上一步走错了,退回重来;
  • 取消长任务:跑了半小时的活儿,中途喊停。

这些都不是"回放聊天记录"能实现的。在简单多轮问答场景下,两者体验几乎一样;一旦你要玩"可控、可干预、可恢复"的 Agent,区别就出来了。

⑥ Spring AI vs SAA ReactAgent,诚实对照

维度 Spring AI(ChatClient) SAA ReactAgent
自动跑工具循环 ✅ 有 ✅ 有(一样,不是 SAA 独门)
多轮记忆 ✅ ChatMemory ✅ saver + threadId
记忆持久化 ✅ RedisChatMemory / JdbcChatMemory ✅ Checkpointer(Redis / Mongo 等)
过程可见性 仅模型 token 流,看不到工具/Hook 步骤 ✅ stream() 暴露每步节点事件
存的是 聊天记录 运行时快照
暂停 / 恢复 / 回滚 / 取消
护栏 Advisor(消息级切面) Hooks(Agent 级,如人工确认)
编排(多 Agent / 工作流 / A2A) ✅ 框架内建

注意看:前三行两者都有,ReactAgent 没有"取代" ChatClient 的任何已有能力。真正的增量是后面几行------可观测、可存档、可干预、可编排。这也是整个 SAA 系列要一点点展开的东西。

⑦ 小结 & 下一篇预告

今天咱们没先背概念,而是直接撸了个会调工具的 ReactAgent,又推了两步(多轮、看过程),最后点破了它和 ChatClient 真正的分水岭:不是"能不能调工具 / 记事儿",而是"存的是聊天记录还是运行时快照,以及能不能暂停 / 干预 / 编排"

下一篇《模型与消息》------Agent 已经跑起来了,咱们聊聊怎么给它换模型、怎么把 Messages 和运行时上下文(ToolContext / metadata)玩出花,顺便看几个真实工具怎么接外部系统。

相关推荐
ZGIAI1 小时前
知识库有结果,不等于召回率合格
人工智能·架构
兴通物联科技1 小时前
SMT PCB 微小 DataMatrix 码扫不动问题分析 兴通 XT8601B 600 万像素工业读码器落地实践
大数据·人工智能·单片机·嵌入式硬件·算法·计算机视觉
LorryJovens1 小时前
【LAAP的数字生命哲学】基于ARIS的人类社会观的安全架构和伦理框架
人工智能
Profile排查笔记1 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
johnsong1 小时前
AI 前沿日报:2026年08月25日
人工智能
进度猫1 小时前
5个甘特图高阶用法,不止简单排工期
大数据·人工智能·产品经理·甘特图·项目管理软件
码视野1 小时前
基于 Vue3 + Element Plus 的【基于大模型与知识图谱的 AI 智能法律咨询与案件辅助研判系统】设计与实现(附完整源码与PRD)
人工智能·知识图谱