番外篇二:《不到十行代码,我用ReactAgen搭了个会自己调工具的 Agent》

番外一篇我们理清了"Spring AI 是接口层、Alibaba 是编排层"。这篇动手------用 ReactAgent 十分钟搭一个会自己思考、自己调工具的 Agent。你会发现, Spring AI 第 5 篇手搓的那套 @Tool 调用,这儿几乎零改动就能直接复用。

一、先回忆第5篇怎么查天气的

翻回专栏第5篇《AI 学会调接口了》,我们用 @Tool + ChatClient 让 AI 查了天气。那一刻其实已经白嫖了"AI 自动调工具"的能力------模型自己决定要不要查、Spring AI 在底层( ToolCallingAdvisor )把工具执行了、结果回灌、再问一次模型,整条链路都是框架内部逻辑,一行循环都没写。

既然 ChatClient 已经能自动调工具、还能自动跑工具循环,那 ReactAgent 到底多了啥?

答案是:第5篇跑的那条工具循环,是个黑盒 ------我们只拿到最终答案,看不见模型中间"想了啥、调了哪个工具、看了什么返回" ,也没法给它套个迭代上限防止跑飞。而 ReactAgent 干的事,就是把这条黑盒循环变成白盒:同样自动调工具,但它把每一步的 Thought / Action / Observation 摊开给我们看,还能控迭代、控停止。

(别急,好多同学八成会问"ChatClient 不是也能调工具吗、那要 ReactAgent 干啥",后面专门掰扯它俩的区别。)

二、十行代码,Agent 自己管循环

直接上我工程 ReactAgentConfig 里的配置:

scss 复制代码
@Bean
public ReactAgent reactAgent(ChatModel chatModel, WeatherTools weatherTools) {
    return ReactAgent.builder()
            .name("demo-react-agent")
            .description("一个简单的 ReAct Agent 演示,支持天气查询和四则运算")
            .model(chatModel)            // ← 你 Spring AI 的 ChatModel,原样喂
            .instruction("你是一个智能助手,能通过工具查询天气和进行数学计算......")
            .methodTools(weatherTools)   // ← 直接挂你的 @Tool 对象
            .enableLogging(true)         // ← 把思考链打出来,调试神器
            .build();
}

数一下,核心就六行。.build() 出来的 ReactAgent,已经是一个"给目标、自己干"的智能体了。

逐行说人话:

  • name / description:给 Agent 起名和写简介,多智能体协作时靠它互相识别。
  • model(chatModel) :番外一篇说过,这就是 Spring AI 第 1 篇认识的 ChatModel,通义千问产出的标准实现,一字没变
  • instruction:系统提示词,定人设和规矩------和第 2 篇《Prompt 不是玄学》里写的 system prompt 是一回事。
  • methodTools(weatherTools) :把你的工具对象丢进去,框架自动把 @Tool 方法抽成工具 schema 挂上。
  • enableLogging(true) :强烈建议开着,后面你能直接看到它"想一步、做一步"的全过程。

三、工具?就是第 5 篇那套 @Tool

重点来了------工具代码根本不用重写 。工程里的 WeatherTools 就是标准 Spring AI 写法:

less 复制代码
@Component
public class WeatherTools {

    @Tool(description = "查询指定城市的实时天气信息,包括天气状况、温度和湿度")
    public String getCurrentWeather(
            @ToolParam(description = "要查询天气的城市名称,例如:北京、上海") String city) {
        // 演示用:返回模拟数据(真实场景把这里换成调真实天气 API 即可)
        int temperature = new Random().nextInt(35) + 5;
        return String.format("{"city":"%s","temperature":%d}", city, temperature);
    }

    @Tool(description = "对两个整数进行四则运算")
    public String calculate(@ToolParam(description = "运算符") String operator,
                            @ToolParam(description = "第一个操作数") int a,
                            @ToolParam(description = "第二个操作数") int b) {
        // add / subtract / multiply / divide ...
    }
}

看见没?@Tool@ToolParam、返回 JSON------全是第 5 篇熟的套路

四、"等等,ChatClient 也能调工具啊?"------它俩到底差在哪

写到这里,心里冒出一个疑问------老实说,我写稿时也卡了一下:

专栏第 5 篇用 ChatClient + @Tool,模型不也是自己决定要不要调工具 吗?而且 Spring AI 默认还会帮我们把工具执行了、结果回灌、再问一次模型......那 ReactAgent 这不就多此一举?

先把话说透:这个直觉完全成立,前半句一点没错。

翻 Spring AI 官方文档的原话------当 ChatClient.tools(...) 时,框架会在 advisor 链里自动挂一个 ToolCallingAdvisor,它干的事是:

"The ToolCallingAdvisor implements the tool calling loop... looping until no more tool calls are needed."

翻译过来:ChatClient 不只是"模型决定调工具",它还会替我们跑一个工具调用循环 ------模型发 tool_calls → 框架执行 → 结果回灌 → 模型再决定还要不要调 → 直到模型不再发 tool_calls 为止。要不要继续循环,靠的是 ToolExecutionEligibilityPredicate(默认逻辑就是 chatResponse.hasToolCalls())。

所以,区别根本不是"谁决定调工具"------这俩都是模型决定的 。真正的差别在三个字:循环的性质

维度 ChatClient.tools() ReactAgent
谁决定调哪个工具 模型 模型
谁在跑循环 ToolCallingAdvisor 自动接管(黑盒) 框架的 ReAct 状态机
循环的停止条件 "模型还在发 tool_calls 就继续" "模型给出最终答案 / 达到迭代上限就停"
中间过程可不可见 默认不暴露 enableLogging 全程可见 Thought / Action / Observation
迭代上限 默认没有显式上限,靠模型自己收手 可配置停止条件与迭代上限,防跑飞
推理是否结构化 模型原生 tool-calling,无强制结构 显式"想 → 做 → 看"三段式

一句话戳破:

ChatClient 的工具循环 = 模型原生 tool-calling 的"黑盒循环";ReactAgent = 被框架框成 Thought→Action→Observation 的"白盒状态机循环"。 前者更轻,后者更可控、可观测。

那实战怎么选?给你个口诀:

  • 问一句、调一两个工具、直接给答案 (比如"现在几点""北京天气如何")→ ChatClient.tools() 就够了。别动不动就上 Agent,杀鸡不用牛刀。
  • 要"先查 A 拿 ID、再拿 ID 查 B、最后综合判断"这种多步依赖推理 ,或者你想看清它每一步在想啥、怕它跑飞想加迭代上限ReactAgent

顺手点破一个最常见的误解:很多人以为"Agent = 会自动调工具",其实"自动调工具"是 ChatClient 就有的能力 。Agent 真正的增量,是可控、可观测、可编排的多步推理

五、跑起来,看它"想一步做一步"

Controller 极简,把 query 丢给 Agent:

less 复制代码
@GetMapping("/react")
public Map<String, String> chat(@RequestParam String query) {
    AssistantMessage message = reactAgent.call(query);
    return Map.of("query", query, "answer", message.getText());
}

curl 一发:

rust 复制代码
curl 'http://localhost:9999/react?query=北京和上海今天哪个温度更高?'

因为开了 enableLogging,控制台会吐出这样的 ReAct 轨迹(数据为模拟):

css 复制代码
Thought: 要比较两地温度,需要先分别查询北京和上海的天气。
Action: getCurrentWeather("北京")  → Observation: {"city":"北京","temperature":28}
Action: getCurrentWeather("上海")  → Observation: {"city":"上海","temperature":31}
Thought: 上海 31 度高于北京 28 度。
Final Answer: 上海今天比北京热,上海 31℃,北京 28℃。

这就是 ReAct 循环被框架跑起来的样子:只问了一句,它自己决定"先查北京、再查上海、然后比大小"。

六、它到底替你干了啥

把话说透,ReactAgent 内置了三样东西:

  • ReAct 循环(白盒版) :思考 → 调工具 → 观察 → 再思考,直到能回答。ChatClient 其实也会自动跑工具循环,但过程看不见;ReactAgent 把它摊开成看得见的 Thought / Action / Observation,还能控迭代上限。
  • 工具调度 :多个 @Tool 自动注册,模型自己选、自己传参。对应你第 6 篇"智能调度中心"。

等于说,第5篇用 ChatClient 已经拿到了"自动调工具"的能力,ReactAgent 是在这之上把循环白盒化、可观测、可编排------从"只拿到最终答案"变成了"既拿到答案、也看得清每一步、还能控住它"。

七、下篇预告

别被"十行代码"骗了------ReactAgent 这么丝滑,是因为它底下踩着一套 Graph 运行时。所谓"想一步做一步",本质是 Graph 里的一个个节点在流转。

相关推荐
kyriewen4 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
东小西6 小时前
番外篇一:《Spring AI Alibaba 到底是啥?一张图理清两者关系》
openai·ai编程
逻辑帧6 小时前
洞见AI本质系列-参数的演化
ai编程
9i编程6 小时前
工具是编程的铠甲(下篇):从文件对比、全文搜索到数据库设计
后端·openai·ai编程
用户45989204565166 小时前
我用 AI Agent 自动生成了整个 App 的业务地图(开源)
ai编程·客户端
ServBay7 小时前
MCP Server 是什么?为什么是2026年开发团队的必备?
aigc·ai编程·mcp
Cerrda7 小时前
把团队 Mock 工作流做成可安装 Skill:faker-mock-setup 上架 skills.sh 实践
ai编程·cursor
八号当铺8 小时前
使用 Figma Agent Kit:插件 + MCP + 还原 Skill,打通本地设计协作
前端·人工智能·ai编程
无责任此方_修行中8 小时前
搓了一个国产大模型与 AI Agent 比价工具
前端·后端·ai编程