1. 项目概述与整体设计思路
先坦白一句,这个标题看着像武侠小说目录,其实就是我自己的Spring AI系列实战笔记。降龙十八掌一共十八掌,写到第9篇,正好卡在“或跃在渊”这个位置——易经乾卦九四爻,讲的是龙在深潭中蓄势,准备跃出水面之前那个临界状态。这个意象用来形容React Agent再合适不过了:模型已经有了对话能力,工具也已经注册好了,但真正让它能“独立思考、调用外部能力、再回到对话”的那一步,很多项目都卡在这里。这篇就以Spring AI为基础,配合阿里云生态的场景,完整拆解一个React Agent从零到能用的过程。
项目本身解决的核心问题非常明确:让LLM不再只是“聊天生成器”,而是能围绕一个具体任务,自己决定要调用哪个工具、怎么解析工具返回的结果、什么时候结束循环。举个生活化类比,普通对话模型像一个只会嘴上说“你应该去银行办业务”的顾问,React Agent则像一个真的会拿起手机帮你导航、帮你取号、甚至帮你核对表格的助理。Spring AI框架把这条路径标准化了,我们只需要做适配和策略配置。
适合谁来看?两类人。一类是对Spring AI感兴趣但还没动手做过Agent的Java开发者,另一类是已经有了基础对话机器人、想让它具备工具调用能力的项目负责人。本篇文章我会尽量把“为什么这么设计”讲清楚,而不是只贴一段能跑的代码。
2. 核心原理:React Agent的思考-行动-观察循环
2.1 ReAct模式到底解决了什么问题
ReAct是Reasoning和Acting的组合词,最早来自论文里提出的“让语言模型交替进行推理和行动”的思路。以前我们做任务型机器人,路径是写死的:用户说A,就调API A;用户说B,就调API B。一旦用户需求带了点歧义,或者需要连续调用两个不同系统,逻辑分支就会爆炸式增长。
ReAct的方式完全不同。它不预设分支,而是给模型一套“思维工具”:模型每次输出两条信息,一是“我为什么要这么做”(Thought),二是“我现在要调用哪个工具、传什么参数”(Action)。系统拿到Action后执行真实API调用,把结果以Observation的形式返回给模型。模型看到Observation后再继续思考、行动,直到它认为自己已经拿到足够的答案,输出一个Final Answer。
这套机制的价值在于,复杂任务的编排不再靠代码硬编码,而是由模型在运行时动态规划。相当于你不再给员工一张写满步骤的SOP,而是给他一个目标、几个工具和一套决策规则,让他自己想办法。
2.2 Spring AI里的Agent抽象与工具注册
Spring AI从早期版本就开始逐步完善Agent相关的抽象。核心是ChatClient和Tool Calling的结合。在Spring AI的语境里,一个Agent的本质是“一个带有工具集合的聊天模型客户端”,通过配置文件或Java代码把工具方法暴露给模型。
我最开始接触的时候也有个误解,以为Agent是Spring AI里一个特别高级的类。实际上它更像一个运行模式:你定义若干个用@Tool注解的Bean方法,模型在需要时会自动生成一个工具调用请求,Spring AI通过函数回调机制把请求分发到对应方法上。这个设计非常贴合Spring习惯,也降低了上手门槛。
过程中要特别注意一点:工具方法必须有清晰的描述(description)和参数说明(parameter description),这部分直接决定模型能否准确地选择工具。你写的是“根据用户输入的手机号发送验证码”,模型就知道这个工具是干这个的;你只写一个“sendCode(String phone)”,模型大概率会蒙。我见过太多项目栽在工具描述写得像注释上。
2.3 系统提示词配置的几个关键点
“SpringAI系统提示词怎么配置”是我在社区里被问到最多的问题之一。很多新手把系统提示词当成一段“角色设定”,写完就不管了。但在React Agent里,系统提示词承担的是“行为准则”作用,尤其要定义清楚:
- 什么情况下必须调用工具,什么情况下直接回答;
- 工具调用失败时应该怎么处理,是重试还是转人工;
- 如果多个工具都可以完成同一个目标,优先级是谁;
- 不允许臆造结果,必须以Observation为准。
我的建议是系统提示词里写“硬规则”,不要写“软建议”。比如“收到查询类请求时,优先调用订单查询工具,不得根据记忆猜测订单状态”就比“请尽量查询真实订单信息”好得多。这个阶段可以多跑几种Case观察模型输出,逐步收紧规则。这也是“或跃在渊”这个阶段最值得花时间的部分,模型能力就摆在那里,产出的质量上限基本由提示词规则决定。
3. 实操过程:从零搭建Spring AI ReactAgent
3.1 环境准备与Maven阿里云仓库配置
新建Spring Boot项目时,我一般建议直接用Spring Initializr生成,Java 17起步,Spring Boot 3.x配合Spring AI的当前稳定版本。这里有一个很重要的实操点:如果是在国内网络环境下构建,Maven中央仓库的速度和稳定性不一定理想,建议优先配置阿里云Maven仓库镜像。
Maven的settings.xml里添加镜像配置即可:
<mirrors> <mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors>配好之后,依赖下载速度会有肉眼可见的提升。但注意一个问题:Spring AI有些模块可能还没发布到中央仓库,只在里程碑(Milestone)或快照(Snapshot)仓库里。这时你需要额外配置Spring的里程碑仓库:
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>阿里云镜像默认只代理中央仓库,如果Spring里程碑仓库走代理可能拉不到,我自己的做法是两个仓库都保留,镜像设成阿里云,然后再加Spring官方镜像或者直接从Spring仓库拉取。顺序放对通常都能成功。
3.2 依赖引入与模型选择
以当前Spring AI版本为例,核心依赖需要这样引入:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>之所以用OpenAI starter是因为Spring AI对不同模型提供商的适配方式基本一致,先按OpenAI标准打通流程,再切换到国内模型服务或者阿里云通义千问的时候,改动成本非常低。如果你直接使用阿里云的通义千问,也可以引入对应的starter,配置项大同小异。
模型选择上我做了一个非常实际的建议:做Agent早期调试阶段,优先使用高推理能力模型,哪怕成本高一点。因为Agent跑一步错一步的调试成本远高于模型API的成本。等整个链路跑稳了,再换成更便宜的模型做生产验证。
3.3 代码实现:以“智能审核Agent”为例
下面我用一个最近项目里实际写过的“智能审核Agent”作为核心示例,这个场景很完整:用户提交一段文字内容,Agent需要检查是否包含敏感词、内容质量是否达标,如果需要还可以直接调用审核服务,最后输出审核结论。
首先要定义工具服务类:
@Service public class ContentReviewTools { private final ReviewService reviewService; public ContentReviewTools(ReviewService reviewService) { this.reviewService = reviewService; } @Tool(description = "检测文本是否包含敏感词,返回命中列表") public List<String> detectSensitiveWords(String text) { return reviewService.detectSensitiveWords(text); } @Tool(description = "计算文本质量得分,返回0-100的整形分数") public int scoreContentQuality(String text) { return reviewService.scoreContentQuality(text); } }然后通过ChatClient组装Agent能力:
@Service public class ReviewAgent { private final ChatClient chatClient; public ReviewAgent(ChatClient.Builder builder, ContentReviewTools tools) { this.chatClient = builder .defaultSystem(""" 你是内容审核助手。你的任务是基于工具返回的真实结果进行判断。 规则: 1. 必须先调用工具获取敏感词列表和质量分数,不得凭常识猜测。 2. 如果敏感词列表不为空,结论必须为“不通过”,并列出命中词。 3. 如果敏感词为空但质量分低于60,结论为“需要人工复审”。 4. 最终输出必须包含:结论、依据、建议处理动作。""") .defaultTools(tools) .build(); } public String review(String text) { return chatClient.prompt().user(text).call().content(); } }上面这段代码就是React Agent的核心形态。注意两个关键点:第一个是defaultTools传入工具实例后,Spring AI会自动生成OpenAI格式的functions描述并绑定到模型请求里;第二个是系统提示词里的规则要跟工具返回值强关联,否则模型容易在Observation为空时自行脑补。我实际测试下来,把规则写得越具体,Agent跑偏的概率越低。
3.4 接入阿里云短信与OSS
审核Agent跑通之后,一个很自然的业务扩展是:审核不通过时发短信通知用户,审核通过的附件存到OSS。这块正好体现“阿里云生态配合Spring AI落地”的价值。
短信发送用阿里云短信API,核心代码逻辑如下:
@Component public class SmsSender { private final DefaultProfile profile; private final IAcsClient client; public SmsSender() { this.profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); this.client = new DefaultAcsClient(profile); } public void sendReviewNotification(String phone, String reason) throws ClientException { CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "你的签名"); request.putQueryParameter("TemplateCode", "SMS_123456"); request.putQueryParameter("TemplateParam", "{\"reason\":\"" + reason + "\"}"); client.getCommonResponse(request); } }我在接入短信时踩过一个坑:阿里云SDK的依赖版本比较老,引进来之后和Spring Boot的传递依赖冲突。解决办法是排查classpath里是否有多个不同版本的aliyun core依赖,统一把aliyun sdk版本提到一个兼容版本,或者用BOM管理。另外短信签名和模板都要在阿里云控制台先过审,测试阶段最容易遇到“InvalidSign”或者“isv.SMS_SIGNATURE_ILLEGAL”的报错,这类问题一般不是代码问题,是签名没通过审核或者模板占位符不匹配。
OSS上传这部分相对简单,但也要注意一个原则:生产环境绝对不要把AccessKey写死在代码里。我推荐用阿里云的RAM子账号STS临时凭证,并结合Spring Boot的配置中心或者环境变量来管理密钥,避免密钥泄露。
4. 常见问题与排查技巧实录
4.1 Maven依赖下载慢或拉不到包
这个问题几乎每个做Spring AI的Java开发者都会遇到。除了配置阿里云镜像之外,我建议多做一步:检查项目里使用的Spring AI版本是否为正式发布版。有些版本是里程碑版,默认中央仓库里真的没有,你需要主动配置Spring官方仓库。
还有一个很多人不会注意到的点:IDEA里maven的settings.xml可能被覆盖了。有时候你改了本地maven setting,但IDEA的Maven配置仍然指向它自带的配置,或者多模块项目里父pom覆盖了repository配置。排查方式很简单,执行一下mvn help:effective-settings,看当前生效的settings里镜像是不是你配置的那个。
4.2 Agent工具调用不生效或陷入死循环
这是React Agent最典型的问题。我遇到的情况分两类:一类是模型不调用工具,直接根据上下文瞎猜答案;另一类是模型反复调用同一个工具,始终不给Final Answer。
第一类通常是工具描述写得不够清晰,或者系统提示词没有明确要求“必须先调用工具”。第二类则需要加一个最大迭代次数的控制。Spring AI提供了相关配置项,例如在ChatClient上构造prompt时可以通过.options()指定maxIterations,或者自己维护一个调用计数器,超过阈值强制返回兜底结果。
我个人的经验是:死循环往往不是模型笨,而是工具返回的Observation格式跟模型预期不一致。比如我一次返回了一个JSON字符串,但系统提示词里写的是“按列表形式返回”,模型就会反复解析失败。让工具返回结构化且语义明确的值,比一味调模型温度参数有效得多。
4.3 阿里云短信API发不出去
这个问题排查起来其实很有套路。返回的errorCode如果是isv.INVALID_PARAMETERS,说明请求参数格式有问题,比如TemplateParam不是一个合法JSON、手机号格式不对;如果是isp.SYSTEM_ERROR,大多是服务端暂时异常,稍后重试;如果是isv.BUSINESS_LIMIT_CONTROL,就是触发流控了,短信发送频率超过了限制。
我最想提醒的是:阿里云短信API的返回成功不代表用户真的收到了短信。有些手机安全软件会拦截营销类短信,所以测试阶段建议至少准备两个不同运营商号码交替验证。另外短信签名需要与公司主体资质一致,如果你用的是个人开发者账号,签名审核标准会比较严格。
4.4 SSL证书免费续期与Spring Cloud Gateway配置
如果你把Agent服务放到公网,就一定要处理HTTPS。阿里云提供免费的SSL证书,有效期通常是3个月,到期后要手动续期。很多人在续期之后发现服务没生效,原因往往是网关或负载均衡器缓存了旧证书。
我现在的标准做法是用certbot或者阿里云数字证书管理服务的API做一个自动续期的定时任务,续期成功后通过Actuator的refresh端点或者重启网关来加载新证书。直接用免费证书跑生产环境不是不行,但一定要监控过期时间,最好提前两周做告警,别等证书过期了用户在浏览器上看到大红叉才想起来。
4.5 JSON解析大对象性能问题
有热词在问“阿里json.parsearray转换对象有两万行扛得住吗”,这个问题的本质是:把两万条数据的JSON一次性用JSON.parseArray转成Java List,内存和GC压力都会很大。如果数据量真的是两万行,我建议不要用大JSON一次性传输,而是改用分批拉取或者流式解析。
在Agent场景里也有类似问题:当工具返回的Observation过大时,不仅Java代码处理慢,模型再解析这个超大观察值的速度也会明显下降,Token消耗更是直线上升。我的经验是工具方法返回时要尽量精简,只返回最终结论和必要字段,不要返回完整的大对象列表。如果需要详细数据,可以让Agent再发一个“获取详情”的请求,而不是一股脑全部塞回来。这个设计和人处理信息的方式一样:先看摘要,需要时再查明细。
5. 实操过程中的个人体会
整个“或跃在渊”阶段做下来,我最深的体会是:React Agent的难点不在框架API,而在你对模型的信任边界。框架只负责连接,真正决定Agent质量的是工具设计的完整度、系统提示词规则的清晰度和迭代调试的耐心。
我在项目里跑过十几个业务场景,最后总结出一个简单指标来评判Agent设计是否合格:让同一个Prompt连续跑10次,看成功率的波动幅度。如果结果忽好忽坏,多半是工具描述模糊或提示词约束不足,而不是模型不稳定。
另外一个小技巧是,把每次工具调用的完整链路(包括Thought、Action、Observation)都打到日志里。调试Agent就像破案,没有这套日志你会完全不知道模型是在哪一步走偏的。我现在所有Agent项目都会预留一个调试开关,线上关闭,测试环境打开,出问题的时候直接回放整个思考链路。这个习惯帮我节省了大量排查时间,也推荐你们试试。
最后,如果你也是自己做Agent系列研究,不用太纠结命名是不是“正规”。用一套自己喜欢的节奏记录项目,反而更容易坚持下去。降龙十八掌系列还没写完,下一篇应该会聊更多关于多Agent协作的内容,到时候再继续填坑。