ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring AI Alibaba ReactAgent实战:从ReAct原理到工程落地

Spring AI Alibaba ReactAgent实战:从ReAct原理到工程落地 降龙十八掌打到第九掌动作开始讲究“跃”了。或跃在渊字面意思是龙或跃起、或安于渊中进退之间都在蓄势。用这个意象来比喻 LLM Agent 正好模型每发起一次工具调用就像跃出水面去够目标每拿到一次工具返回结果又像落回渊中重新观察全局然后决定下一次跃出的方向。这次要拆的是 spring-ai-alibaba 生态里的 ReactAgent——一个把 ReActReasoning Acting模式落到实处的应用实践。我最初入场的想法很简单“不就是循环里反复调工具嘛”结果在真实业务里却踩了不少坑工具乱选、参数瞎传、循环烧钱、上下文爆炸。这篇文章就从原理到实操把这件事完整梳理一遍重点放在能直接落地的部分。适合谁来读想基于 Spring AI / Spring AI Alibaba 搭 Agent 应用的 Java 后端开发或者对 ReAct 模式感兴趣、想找一套开箱即用方案的朋友。你不必有 Agent 开发经验但最好熟悉 Spring Boot 基本用法并且对“大模型能做什么、不能做什么”有基本概念。1. ReactAgent 到底在解决什么问题1.1 大模型的“脑”和“手”是分开的大模型最擅长的三件事语义理解、文本生成、逻辑推理。但注意这三件事都在“文本空间”里发生。让它实时查天气做不到让它查询数据库里某笔订单的状态做不到让它直接执行一段代码并读取结果同样做不到。这里有两种典型的翻车场景我估计你多少都遇到过。第一种是幻觉问题。你问模型“这份合同里违约条款第几条提到了赔偿比例”它可能给你一个“看起来很像”的答案但对应行号和条文内容是编的。原因很简单模型不是真的“读”了你的合同它只是在预测最像的回答。第二种是能力边界问题。你问“北京现在空气质量怎么样”模型训练数据里根本没有“现在”这个概念它只能根据历史语料拼一个“北京空气质量以优良为主”的模糊回答。这种回答放在聊天里没问题放在业务系统里就是事故。Function Calling / Tool Calling 解决了一部分问题让模型在需要时调用一个预先定义好的函数。但单轮工具调用只覆盖“一次调用就完事”的场景。真实业务里的问题往往是一连串连续动作——查用户、查订单、查库存、算价格、生成回复。每一步的输入都依赖上一步的输出。这就是 Agent 式循环控制机制存在的意义。ReactAgent 的核心思路来自一篇挺有名的论文《ReAct: Synergizing Reasoning and Acting in Language Models》让模型循环执行三个动作思考当前该做什么Thought、调用工具去执行Action、观察工具返回的真实结果Observation直到问题彻底解决。它本质上把“思考”和“行动”绑定成了一个互锁的闭环。1.2 什么时候该用单轮调用什么时候该上 ReactAgent很多朋友的问题不是“不会用 ReactAgent”而是“不知道什么时候该上”。我把两类场景对比一下你自己对照。单轮 Tool Calling 适合的场景特征很明确决策路径短一次调用能拿到全部信息工具之间没有强依赖。比如“把这个文本翻译成英文”“帮我总结这篇新闻的要点”或者“查询订单号为 XXX 的物流状态”。这类任务模型只需要做一次工具选择执行完答案就齐了。ReactAgent 适合的场景则是多步骤、信息依赖链长的任务。举个例子用户问“北京明天适合跑步吗如果适合按我 70kg 体重跑 5 公里大概消耗多少卡路里”这个问题至少需要三步查北京明天天气 → 根据天气判断是否适合跑步 → 如果适合查或计算卡路里消耗。而且第二步的判断结果会决定第三步执行与否。单轮调用里的模型经常犯一个毛病只调了天气工具卡路里就张口瞎编。再举个更业务的例子用户投诉“我上周买的商品一直没到货”一个合格的客服 Agent 需要依次查订单、查物流、查售后政策才可能给出靠谱回答。如果只做单轮调用模型大概率会在某个环节上“猜”。所以我的选择标准很简单如果答案需要两个及以上工具的输出做组合判断或者工具的调用顺序不固定、依赖中间结果就直接上 ReactAgent别在单轮调用里硬凑。1.3 跟“复杂 Prompt”和“Chain”的区别有人会说那我用 Prompt 把工具全塞进去让它自己选不就行了可以但实践里有三个坎。第一工具一多模型注意力会被稀释选错工具的概率明显上升。第二复杂依赖链上模型很难在单轮里面把所有步骤的输入补齐总要有一两步靠猜。第三模型上下文长度有限把十几个工具描述全量塞进每一轮 Prompttoken 成本高还容易超限。还有一种方案是 Chain链式调用也就是把流程在代码里固定死先查 A再调 B最后生成结果。这种方式的优点是确定性强缺点也很明显——流程不灵活。用户的问题但凡在链路中间拐个弯你的代码就适配不了。比如上面的天气例子如果用户随口说“顺便帮我查一下明天的紫外线”链路是不是又要改一版代码ReactAgent 的聪明之处是把“下一步做什么”的决策权交给了模型让它按实际观测结果动态调整。这就像开车导航你不需要提前背下整个路线每个路口看一眼指示牌就行走错了它会立刻重新规划。代价是确定性下降、调试难度上升但换来的是对复杂开放问题的处理能力。2. Spring AI Alibaba 的 ReactAgent 长什么样2.1 三个核心组件Spring AI Alibaba 是阿里在 Spring AI 通用抽象之上做的适配层面向阿里云百炼 DashScope通义千问模型。一个 ReactAgent 应用拆开看是三块拼图。第一块是聊天模型ChatModel负责“思考与决策”也就是 ReAct 里的 Thought。在 spring-ai-alibaba 里通常配置为 DashScope 系列的千问模型开发阶段我用 qwen-plus 比较多复杂任务再上 qwen-max。模型选择直接决定了 Agent 上限因为工具选择、参数提取、结果归纳全依赖它的推理能力。第二块是工具Tool负责“行动”也就是 ReAct 里的 Action。Spring AI 里一个工具就是一个加了 Tool 注解的方法方法名和描述文字就是模型理解这个工具的唯一“说明书”。这个描述我愿称之为 Agent 的灵魂写得好不好直接决定模型会不会正确选它。第三块是控制循环Agent Loop负责把 Thought → Action → Observation 串起来。这一层你可以自己写也可以使用框架预置的 Agent 抽象。我刚开始是自己写循环的也就几十行代码但真正跑起来才发现坑全在细节循环终止条件、工具调用异常处理、上下文长度控制、连续两次调用同一工具的检测。能交给框架收敛的别自己去造。从依赖关系上看ChatModel 是底座Tool 是外挂能力Agent Loop 是胶水层。三者缺一不可但实际开发里你的大多数精力应该花在 Tool 的打磨上而不是纠结 ChatModel 换哪个或者自己重写循环。2.2 ReAct 循环到底怎么转理解 ReAct 循环最好的方式是把每一轮拆成四步。第一步把当前对话消息和可用工具的 JSON Schema 列表一起发给模型。第二步模型输出结果可能是两种一种是不带任何工具调用的普通文本说明它觉得问题已经解决另一种是包含工具名和参数的调用指令。第三步如果是工具调用应用去执行对应工具拿到真实返回值。第四步把这个返回值作为新的消息追加进对话再次发给模型回到第一步。关键点在于每轮工具结果都会补进上下文所以模型能看到自己刚才那番操作产生了什么真实效果。有纠正机会这是 ReactAgent 比单轮 Tool Calling 稳健的根本原因。举个例子你就明白了。第一轮模型决定调用 getWeather(北京)工具返回“晴25度”这个结果进入第二轮对话第二轮模型看到“25度”之后才决定“适合跑步”于是继续调用 estimateCalories(70, 5)。如果第一轮工具返回的数据不是它预期的格式或者数据本身没有意义模型在第二轮会尝试换个工具或者换个参数而不是硬着头皮胡编。Spring AI Alibaba 里这套循环有些版本已经帮你收敛成高层的 Agent 组件如果版本还不支持就需要自己在业务代码里维护消息列表。这个我留到第三部分详细说。2.3 关键参数与选型建议实际配置一个 ReactAgent下面这几个参数最影响成败我拿一张表总结一下。参数作用我的经验model模型名称qwen-plus 起步复杂链路用 qwen-maxtemperature采样随机性工具调用场景设 0~0.3别超过 0.5maxIterations最大循环次数5~10 够用防止死循环和费用失控tool 描述模型选工具的依据多花时间写比换模型见效快上下文裁剪控制 token 消耗迭代多时必须做摘要或裁剪这里重点说下 temperature。很多人从纯对话场景带过来的习惯喜欢把温度调到 0.7 以上追求“创意”但在 Agent 场景这是灾难。Agent 需要的是“听话”工具名必须选对、参数必须精准、格式必须合理。温度一高模型可能会在工具选择和参数编造上给你制造各种惊喜。我实测下来Agent 场景 temperature 直接设 0 也没问题反而收敛更快。maxIterations 这个参数核心作用是防呆。模型在工具调用上偶尔会陷入“手滑循环”比如查了一个不存在的用户 ID工具返回错误模型没读懂错误又用同样的 ID 去查这样来回 20 次也不是没可能。设一个上限至少保证系统不会无限烧钱。2.4 工具数量的控制与描述工具不是越多越好这个观念我得反复强调。模型每轮都要把全部工具的描述塞进上下文里做选择工具越多注意力越分散选错的概率越高。我见过把二三十个工具全塞给一个 Agent 的项目结果模型经常在相似工具之间“左右横跳”。我的经验是给每个 Agent 配 3~8 个工具超过这个数量就要考虑拆分。拆分有两种思路。一种是按领域拆比如天气 Agent 只负责天气相关工具订单 Agent 只负责订单链路各管各的。另一种是按步骤拆把“查信息”和“做计算”拆成两个 Agent前一个输出结构化结果后一个基于结果计算用代码或消息队列衔接。另外描述怎么写很讲究。我给一个格式模版用途 关键参数说明 示例。比如“查询指定城市当前天气返回温度和天气状况参数 city 为城市中文名比如‘北京’”。示例尤其重要模型对具体例子比对抽象描述敏感得多。3. 实操从零搭一个能查天气、算账的 Agent3.1 环境准备与依赖先说环境。我本地用的是 JDK 17 Spring Boot 3.3.x配合 spring-ai-alibaba 的 DashScope Starter。Maven 依赖大致长这样具体版本号建议去官方仓库查最新dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version{以官方最新版本为准}/version /dependency有几个容易踩的坑提前提醒你。第一Spring AI Alibaba 对 Spring Boot 版本有明确适配范围不要自己随便升级 Boot。我一开始用了最新的 Boot 3.4结果启动直接报 Bean 找不到最后退回官方文档建议的版本组合才正常。第二需要准备好 DashScope 的 API Key可以在阿里云百炼控制台申请环境变量里配置好别硬编码进代码。第三建议在 pom 里显式声明 Spring AI BOM避免传递依赖版本冲突。配置文件也很简单spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.1先别急着写业务代码启动一个最简单的 Controller调一次 ChatClient 发一句“你好”确认模型链路是通的。这一步能过滤掉一半的集成环境问题。3.2 定义两个实用工具为了让你有画面感我设计一个场景用户问“北京现在天气适合跑步吗顺便帮我算一下如果跑5公里消耗多少卡路里”。这个场景需要两个工具一个是天气查询一个是卡路里估算。Component public class LifeTools { Tool(查询指定城市当前天气返回温度和天气状况城市参数用中文名例如北京) public String getWeather(String city) { // 实际项目里对接你的天气服务 API if (北京.equals(city)) { return 晴25度微风空气质量良; } return 查询失败城市不存在或暂不支持; } Tool(根据体重和距离估算跑步消耗的卡路里体重单位kg距离单位km返回千卡数) public String estimateCalories(double weightKg, double distanceKm) { double calories weightKg * distanceKm * 1.036; return String.format(大约 %.0f 千卡, calories); } }注意Tool 注解里的描述质量直接决定模型是否会用这个工具、以及是否能用对。描述里最好包含“何时用、参数含义、示例”。比如 estimateCalories 的描述里如果不说“体重单位kg”模型可能就默认给一个“70斤”最后的计算结果完全错误。工具返回值也值得专门说一句。我之前踩过一个坑工具返回了一段很大的 JSON模型读取时被无关字段干扰导致判断失误。后来我养成一个习惯——工具返回值只保留业务判断需要的关键信息能精简就精简别把后端接口整包返回给模型。3.3 构建 Agent 并调用基础调用方式用 Spring AI 的 ChatClient 就可以Service public class AgentService { private final ChatClient chatClient; public AgentService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String ask(String userPrompt) { return chatClient.prompt() .user(userPrompt) .call() .content(); } }不过这里有个关键认知ChatClient 单次调用默认不会自动做多轮 ReAct 循环。它会把工具调用结果返回给你但要不要继续走下一轮取决于框架版本和你的配置。有些版本的 Spring AI 会在一次 call 里把工具调用执行完但能处理的“轮数”有限复杂的多轮链路还是建议显式维护循环。我给出一个思路级的参考代码帮助你理解怎样把循环写清楚。这份代码不是某个版本的原样 API而是我基于 Spring AI 通用抽象的手写模式核心目的是看懂循环public String runAgent(String userPrompt, ListToolCallback tools, int maxIterations) { ListMessage messages new ArrayList(); messages.add(new UserMessage(userPrompt)); for (int i 0; i maxIterations; i) { ChatResponse response chatModel.call( new Prompt(messages, DashScopeChatOptions.builder() .withTools(tools) // 绑定工具列表 .build()) ); // 1. 拿到模型输出内容与工具调用指令 String content response.getResult().getOutput().getText(); ListToolCall toolCalls response.getResult().getOutput().getToolCalls(); // 2. 没有工具调用说明问题已解决结束循环 if (toolCalls null || toolCalls.isEmpty()) { return content; } // 3. 逐个执行工具把结果作为观察结果回填到消息列表 for (ToolCall toolCall : toolCalls) { String result executeTool(toolCall, tools); // 根据 toolName 找到对应 ToolCallback 并执行 messages.add(new ToolResponseMessage(toolCall.id(), result)); } // 4. 进入下一轮循环模型会基于新消息继续决策 } return 达到最大迭代次数任务未完成; }这段代码的灵魂在于 messages 列表不断膨胀模型每一轮都能看到之前的 Thought、Action、Observation 全链路。如果你的 spring-ai-alibaba 版本提供了预置的 Agent 组件就直接用组件没有的话把这份代码改改也能用。3.4 运行效果与结果分析我用上面的 Agent 跑了一遍“北京现在天气适合跑步吗顺便帮我算一下跑5公里消耗多少卡路里”开启 debug 日志后能清楚看到模型内部一步步的动态。第一轮模型思考后调用 getWeather参数是“北京”。工具返回“晴25度微风空气质量良”。第二轮模型看到天气数据确定适合跑步接着调用 estimateCalories参数 JSON 大概是 {weightKg: 70, distanceKm: 5}。工具返回“大约 362 千卡”。第三轮模型不再调用工具直接生成最终回答“北京当前 25 度晴天微风适合跑步。按 70kg 体重跑 5 公里大约消耗 362 千卡。”整个过程从“拿到问题”到“给出完整答案”两次工具调用自动串联完成。注意用户输入里并没有给出体重模型在第二轮估算时默认了 70kg。这种“缺省值处理”能力来自模型对常识的理解但如果你想让它更严谨可以在工具描述里明确“体重默认 70kg”或者让它反问用户。这里的取舍完全看你想要的用户体验。我对比过单轮 Tool Calling 模式最常见的结果是模型只查了天气卡路里就顺手编了。不是它不想调用第二次工具而是单轮机制的决策空间就在那里它没法进入“第二步”。这也是 ReactAgent 和普通调用的分水岭。3.5 完整工程结构参考如果你要从零搭一个 Demo我建议工程结构如下简单清爽src/main/java/com/example/agent/ ├── AgentApplication.java // Spring Boot 启动类 ├── config/ │ └── ChatModelConfig.java // 配置 DashScope ChatModel 和 ChatClient Bean ├── agent/ │ ├── AgentService.java // 核心循环逻辑 │ └── AgentRunner.java // 若框架有预置 Agent则封装在启动阶段调用 ├── tools/ │ └── LifeTools.java // 所有 Tool 工具方法 └── controller/ └── AgentController.java // REST Controller暴露 /api/agent 接口给前端测试先跑通这个最简工程再逐步加日志、加错误处理、加上下文裁剪。一次把复杂度拉满的话出了问题很难定位是模型的问题、工具的问题还是循环逻辑的问题。4. 常见问题与调优实录4.1 循环卡死我遇到最频繁的线上事故就是 Agent 在原地打转表现形式是日志里反复出现同一个工具调用参数一模一样返回结果也一样。最夸张的一次模型连续 30 多次调用同一个查询接口。排查方向有两个。一个是工具返回值太“脏”模型误以为工具还没成功。比如工具返回了一个错误码 JSON模型没理解其中“查询失败”的语义以为只是没查到数据于是再次尝试。解决办法是让工具返回结果带明确语义比如“查询失败订单号不存在”。另一个是模型本身陷入思维循环解决办法就是设置合理的 maxIterations并且在循环里检测“工具名 参数”是否与上一轮完全相同如果相同就直接终止或切换策略。这个探针逻辑很简单但非常有效。条件触发一次说明模型大概率卡住了这时让它换个思路或者直接问用户澄清比继续烧 token 有价值得多。4.2 工具参数解析错误模型调用工具时参数是按 JSON Schema 生成的。如果你的工具参数写得复杂就容易出现解析失败或者参数缺失。我复盘过一个典型问题工具定义要求传入一个“地址对象”包含省、市、区、街道四个字段模型实际操作时只传了一个字符串“北京市海淀区”结果 JSON 解析直接报错Agent 整个链路断掉。如果你也遇到这种问题我的建议有三条。第一工具参数尽量用基础类型加简单对象能用 String 就别用嵌套对象。第二必填参数不是“明显”的一定在描述里写清楚默认值和示例。比如“体重单位kg如果用户没提则默认70”。第三模型对中文写的描述理解更好工具描述里顺手给出一个完整的调用示例比如“example: estimateCalories(70, 5)”。这一个动作能显著降低参数缺失概率。4.3 上下文爆炸与费用控制ReAct 循环每跑一轮messages 里就会多几条记录。迭代多了上下文长度会快速膨胀。这带来两个后果一是 token 成本上涨二是模型可能被早期步骤里的无关信息干扰导致后续判断失准。我现在的经验是三层控制。第一层工具返回结果精简绝不把后端整个响应体塞回去。第二层迭代轮数超过阈值比如 6 轮后将最早的消息做摘要压缩用一句“此前已完成 XXX”替换掉完整对话历史。第三层长流程任务拆分多个小 Agent每个 Agent 只处理一个步骤步骤完成后把结构化结果传给下游。这个套路有点像代码里的函数拆分做一次之后整个系统的稳定性和可排查性都会上一个台阶。费用控制上还有一个容易被忽略的点工具描述也占 token而且是每一轮都占。工具多了光描述就吃掉不少 token。所以工具描述要“准而不啰嗦”写清楚该怎么用就行别写成长篇小说。4.4 模型选型与 temperature 调优我实测过的千问系列模型给一个不太严谨但实用的排序qwen-turbo 适合单轮简单工具调用qwen-plus 是性价比之选qwen-max 在复杂多轮链路上明显更稳但成本和延迟也更高。如果你刚上手我建议直接用 qwen-plus 打通全流程上线前再用典型 case 对比 qwen-plus 和 qwen-max 的输出稳定性。注意对比不是看谁的答案更像人话而是看固定的 20~30 个测试用例里工具调用次数、参数正确率、最终回答准确率各是多少。跑出数据再决策别拍脑袋。temperature 这个参数我又要重复一句Agent 场景直接设 0。原因前面说过你在意的不是创造性而是对指令的执行准确度。我自己还见过一个玄学般的现象temperature 调高之后模型偶尔会虚构一个不存在的工具。调回 0 之后“手滑选错工具”的概率立刻降下来了。4.5 日志与调试技巧速查最后分享一份调试清单是我在排查 Agent 问题时必查的几项。检查项操作预期工具是否被调用开启 debug 日志观察 tool_calls 内容能看到工具名和参数参数是否正确对比模型传入的参数与工具定义类型、必填项一致工具返回值格式查看返回给模型的原始文本语义清晰无冗余大 JSON循环是否终止监控迭代轮数与模型最终输出正常在 3~5 轮内结束上下文长度打印每次请求的 token 数无异常膨胀模型是否频繁换工具统计各工具被调用次数不应出现反复横跳这套排查流程救过我很多次。大多数 Agent“不听话”的问题最后定位出来都不是模型不行而是工具描述不够精确、返回值不够干净、或者循环终止条件不够严格。先把这四样东西检查一遍再考虑换模型否则换个更贵的模型也治不了根子上的毛病。我个人在把 ReactAgent 应用到真实业务之后最深的体会是Agent 的稳定性上限其实不是由模型智商单独决定的而是由“工具边界画得好不好”决定的。你给模型多少工具、每个工具描述得清不清楚、返回值干不干净直接决定了它会不会犯低级错误。如果模型在工具之间反复横跳先别急着换更大的模型回头把工具描述重写一遍往往立竿见影。真要说这个系列还有什么可以继续挖的我觉得是上下文裁剪和工具链自动编排这两块。前者解决成本问题后者能让多个 Agent 协作起来处理真正复杂的业务流程。等我把手头这个“多 Agent 编排”的项目跑完再来接着写第十掌。
返回列表