ARTICLE DETAIL

资讯详情

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

Spring AI + 阿里云构建React Agent工程实践

Spring AI + 阿里云构建React Agent工程实践 1. 项目概述这不是一个“掌法”而是一次Spring AI工程化落地的深度实践“降SpringAI阿里第9掌-或跃在渊-ReactAgent”——这个标题乍看像武侠小说里的秘籍名实则浓缩了当前Java生态中一个极具现实张力的技术落地场景在阿里云基础设施上用Spring AI框架构建具备自主推理与工具调用能力的React Agent应用。它不是玄学而是把LLM能力真正嵌入企业级Java服务的一次系统性工程实践。核心关键词“SpringAI”“阿里”“ReactAgent”三者叠加指向一个明确的技术交点以Spring Boot为底座依托阿里云提供的稳定算力、可观测性与中间件支持实现基于ReAct范式的智能代理Agent闭环。我带团队在去年下半年落地了三个类似项目其中两个部署在阿里云ECSACK集群一个跑在阿里云函数计算FC上。所谓“第9掌”并非真有八式前序而是开发团队内部对“第九次重大架构迭代”的戏称——前八次踩坑覆盖了模型加载失败、提示词注入漏洞、工具调用超时熔断、上下文长度溢出、异步流式响应中断、RAG检索漂移、本地缓存击穿、OpenTelemetry链路追踪断点等典型问题。“或跃在渊”则精准描述了当前阶段的状态Agent已能稳定调用阿里云RDS查询订单、调用阿里云短信API发验证码、调用阿里云OSS上传文件但尚未接入DataWorks做数据血缘分析也未打通阿里云百炼平台的私有模型微调通道正处于能力跃升前的临界蓄力期。适合谁参考如果你正在用Spring Boot开发后台服务且已有明确业务需要引入LLM能力比如客服对话路由、合同关键条款提取、运维日志异常归因又恰好使用阿里云作为主力云厂商那么这篇内容就是为你写的。它不讲大道理只拆解真实环境里怎么配、怎么调、怎么防崩、怎么查漏。下面所有内容都来自我们压测2000QPS、连续运行187天的生产环境复盘。2. 整体架构设计与技术选型逻辑2.1 为什么必须是Spring AI而非原生LangChain很多人第一反应是直接上LangChain4j——毕竟文档多、社区火。但我们放弃它的根本原因是工程交付节奏与团队技术栈的刚性约束。LangChain4j虽灵活但其Bean生命周期管理、事务传播、线程上下文继承全部需手动缝合而Spring AI天然集成Spring Boot的自动装配、AOP拦截、Transactional声明式事务、WebMvcConfigurer定制化配置。举个具体例子当Agent需要调用一个带数据库事务的订单创建服务时LangChain4j里你得自己写ThreadLocal透传TransactionSynchronizationManager而Spring AI只需在PromptTemplate里用Value注入${spring.datasource.url}再配合Async标注工具方法事务就自动沿用主线程上下文。我们测算过同样功能LangChain4j需额外编写370行胶水代码Spring AI仅需23行配置1个Service注解。更关键的是阿里云SDK的无缝适配。阿里云所有官方Java SDK如alibabacloud-java-sdk-ecs、alibabacloud-java-sdk-rds均基于Spring Boot Starter规范发布。Spring AI的Tool抽象层与阿里云SDK的ClientBuilder模式高度契合你只需将AliyunRdsClient封装成Spring Bean再用Tool注解标记queryOrderList方法Spring AI就能自动将其注册为可调用工具。而LangChain4j需额外实现ToolExecutor接口并手动维护工具元数据Map一旦阿里云SDK升级比如v5.0新增了retryPolicy配置LangChain4j侧就得同步改工具定义。提示Spring AI 1.0.0-M3版本起正式支持Tool Discovery机制但默认只扫描Component/Service类。若你的阿里云SDK Client是通过FactoryBean创建的如AlibabaCloud.createClient()必须显式在application.yml中配置spring.ai.tool.discovery.enabledtrue并指定base-package。2.2 “阿里”二字究竟指代哪些基础设施网络热词里混杂着“阿里云盘”“阿里云RDS”“阿里云短信API”等不同层级服务但本项目中的“阿里”特指阿里云PaaS层能力组合而非IaaS或SaaS。具体包括计算层ECSCentOS Stream 9镜像或ACKKubernetes 1.26承载Spring Boot应用CPU核数按Agent并发量预估每100QPS预留2核因LLM推理本身不占CPU但工具调用的HTTP客户端、JSON序列化、上下文切片会消耗资源存储层RDS MySQL 8.0开启并行查询、OSS用于存档Agent执行轨迹日志、Redis 7.0缓存Tool Schema描述避免每次调用都反射解析网络层VPC内网直连所有阿里云SDK调用走内网Endpoint如rds.cn-shanghai.aliyuncs.com禁用公网SLB规避DNS解析延迟与安全组策略冲突可观测层ARMS应用监控埋点Spring AI的ChatClient调用耗时、SLS日志服务采集Agent执行链路TraceID、PTS压测平台模拟真实用户对话流。特别注意热词中出现的“阿里云frp管理器-1.1无法进入Web页面”“阿里云盘总是打不开”等问题与本项目完全无关。FRP是内网穿透工具云盘是个人存储服务它们既不参与Agent决策链路也不在技术栈依赖清单中。混淆这些概念会导致架构设计失焦。2.3 ReactAgent的“React”到底React什么ReActReasoning Acting范式常被误解为“前端React框架”这是致命误区。本项目的ReactAgent本质是LLM驱动的推理-行动循环引擎其核心流程为Reasoning推理LLM基于System PromptHistoryCurrent Input生成Thought思考过程、Action要调用的工具名、Action Input工具参数JSONActing行动Agent框架解析Action匹配已注册Tool执行对应Java方法Observation观察捕获工具返回结果成功/失败Payload格式化为Observation字符串Loop循环将Observation追加到对话历史触发LLM下一轮推理直至生成Final Answer。关键设计点在于Action的确定性约束。我们强制要求所有Tool方法签名必须满足public String execute(RequestBody MapString, Object params)。这样做的好处是LLM输出的Action Input JSON能被Jackson直接反序列化为Map无需为每个工具定义DTO类。例如短信发送工具LLM只需输出{phone:138****1234,templateCode:SMS_123456789,params:{\code\:\123456\}}而不用关心阿里云SendSmsRequest类的字段命名规则。这大幅降低了Prompt工程复杂度实测使工具调用成功率从73%提升至98.6%。3. 核心模块实现与关键配置细节3.1 Spring Boot工程初始化Maven配置阿里云仓库的深层意义网络热词中高频出现“maven配置阿里云仓库”但多数人只知其然不知其所以然。配置阿里云Maven镜像https://maven.aliyun.com/repository/public绝非单纯为了下载加速而是保障Spring AI及其依赖链的二进制一致性。Spring AI 1.0.0-M3依赖的spring-ai-core-1.0.0-M3.jar其内部引用的langchain4j-core-0.10.0.jar在中央仓库存在多个SNAPSHOT版本而阿里云仓库只同步Release版。若未配置镜像Maven可能拉取到langchain4j-core-0.10.0-20240315.123456-123.jar含未修复的JSON注入漏洞导致Agent在解析Observation时抛出StackOverflowError。正确配置方式pom.xmlrepositories repository idaliyun/id urlhttps://maven.aliyun.com/repository/public/url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /repository /repositories pluginRepositories pluginRepository idaliyun-plugin/id urlhttps://maven.aliyun.com/repository/public/url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /pluginRepository /pluginRepositories注意必须同时配置repositories和pluginRepositories否则maven-compiler-plugin等插件仍会从中央仓库下载引发编译时依赖版本冲突。我们曾因此导致JDK17编译失败错误信息为“cannot access class sun.misc.Unsafe”根源是plugin拉取了旧版asm库。3.2 Spring AI核心Bean装配绕过官方文档的实战配置Spring AI官方文档推荐用EnableAi启用自动配置但在阿里云环境下此方式存在隐患它会默认启用InMemoryChatMemory而内存型聊天记录无法跨ECS实例共享导致负载均衡后用户对话状态丢失。我们必须手动装配Redis-backed ChatMemory。关键配置代码Configuration public class AiConfig { Bean public ChatMemory chatMemory(RedisTemplateString, Object redisTemplate) { // 使用Redis的Hash结构存储key为chat:memory:{sessionId}field为messages return new RedisChatMemory(redisTemplate, chat:memory); } Bean public ChatClient chatClient( ChatModel chatModel, ChatMemory chatMemory, ToolProvider toolProvider) { return ChatClient.builder(chatModel) .defaultSystem(你是一个电商客服助手请严格按以下规则响应1. 订单查询必须调用queryOrderList工具2. 发送短信必须调用sendSms工具3. 禁止虚构订单号或手机号) .memory(chatMemory) .tools(toolProvider.getTools()) // 注入所有Tool标记的方法 .build(); } Bean public ToolProvider toolProvider( AliyunRdsClient rdsClient, AliyunSmsClient smsClient, AliyunOssClient ossClient) { return new DefaultToolProvider( new QueryOrderListTool(rdsClient), new SendSmsTool(smsClient), new UploadFileTool(ossClient) ); } }这里的关键技巧是DefaultToolProvider构造器接收Tool实例数组而非Class类型。这意味着你可以对每个Tool做个性化增强——比如SendSmsTool内部封装了阿里云短信API的重试逻辑指数退避最大3次、签名验签失败时的降级策略转为站内信、敏感参数脱敏手机号中间4位替换为*。这些增强无法通过Tool注解实现必须在Bean装配时注入。3.3 阿里云SDK工具封装从API调用到Agent工具的转化要点以阿里云RDS订单查询为例原始SDK调用如下DescribeDBInstancesRequest request new DescribeDBInstancesRequest(); request.setDBInstanceId(rm-xxxxx); request.setRegionId(cn-shanghai); DescribeDBInstancesResponse response client.getAcsResponse(request);但直接将其封装为Tool会暴露严重风险DBInstanceId是云资源ID不应由LLM生成RegionId应固定为部署地域。正确做法是定义领域语义化的Tool参数Component public class QueryOrderListTool implements Tool { private final AliyunRdsClient rdsClient; public QueryOrderListTool(AliyunRdsClient rdsClient) { this.rdsClient rdsClient; } Override public String getName() { return queryOrderList; // 必须与Prompt中Action名一致 } Override public String getDescription() { return 根据用户手机号查询最近3笔订单输入参数{ \phone\: \138****1234\ }; } Override public String execute(MapString, Object params) { String phone (String) params.get(phone); if (!phone.matches(^1[3-9]\\d{9}$)) { return ERROR: 手机号格式错误; } try { // 构建SQL查询实际项目中应使用MyBatis动态SQL String sql SELECT order_id, amount, status FROM orders WHERE phone ? ORDER BY create_time DESC LIMIT 3; ListMapString, Object result rdsClient.query(sql, phone); return new ObjectMapper().writeValueAsString(result); } catch (Exception e) { return ERROR: 查询失败 - e.getMessage(); } } }注意getDescription()返回的字符串会作为System Prompt的一部分喂给LLM因此必须用自然语言描述参数规则如手机号正则而非Java类型声明。我们测试发现当描述写成“Input: MapString,String”时LLM生成的Action Input中phone字段值为null的概率高达41%改为自然语言描述后降至0.3%。3.4 React循环控制防止无限递归的硬核防护ReAct的最大风险是LLM陷入“Thought→Action→Observation→Thought…”死循环。我们设计了三层防护Token级熔断在ChatClient.builder()中设置maxTokens(2048)当单次响应超过阈值时强制截断避免LLM持续生成无意义ThoughtStep级计数为每个ChatRequest添加stepCount0每次循环1当stepCount5时Agent自动终止并返回“已尝试5次仍未解决请联系人工客服”Action黑名单维护一个运行时HashSet 记录本轮对话中已执行过的Action名称。若LLM再次生成相同Action如连续两次queryOrderList则直接拒绝执行并返回错误。防护代码片段public class SafeReactExecutor { private static final int MAX_STEPS 5; private final SetString executedActions ConcurrentHashMap.newKeySet(); public String execute(ChatRequest request) { if (request.getStepCount() MAX_STEPS) { return MAX_STEP_EXCEEDED; } String action parseActionFromLlmOutput(request.getLastMessage()); if (executedActions.contains(action)) { return ACTION_BLACKLISTED: action; } executedActions.add(action); // 执行工具调用... return observation; } }这套机制上线后Agent无限循环率从初期的12.7%降至0.003%且所有失败Case均可追溯到具体哪一步骤、哪个Action触发了防护。4. 生产环境部署与性能调优实录4.1 阿里云ECS部署CentOS Stream 9镜像的兼容性陷阱热词中提到“阿里云 centos stream 9 镜像”我们选择它并非跟风而是因Spring AI 1.0.0-M3依赖的Netty 4.1.100.Final要求glibc 2.34而CentOS 7的glibc 2.17不满足。Stream 9的glibc 2.34完美匹配但带来新问题默认SELinux策略会阻止Java进程访问OSS内网Endpoint。解决方案分三步检查SELinux状态sestatus确认为enforcing临时放行setsebool -P httpd_can_network_connect 1允许HTTP客户端联网永久生效编辑/etc/selinux/targeted/setrans.conf添加http_port_t 8080重启auditd服务。实操心得不要盲目setenforce 0关闭SELinux这会破坏阿里云安全基线审计。我们曾因此被云安全中心标记为“高危配置”触发自动告警。4.2 JVM参数调优针对Agent工作负载的定制化配置Agent应用的内存特征与传统Web服务迥异短时高频GC因JSON序列化/反序列化、堆外内存压力大Netty ByteBuf、元空间增长快动态生成Tool代理类。我们最终采用的JVM参数-Xms4g -Xmx4g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -XX:G1HeapRegionSize4M \ -XX:MetaspaceSize512m \ -XX:MaxMetaspaceSize1024m \ -XX:UseStringDeduplication \ -Dio.netty.leakDetection.levelDISABLED \ -Dsun.net.inetaddr.ttl60关键点解析-XX:G1HeapRegionSize4M避免G1 Region过小导致频繁Mixed GC实测4M Region使Full GC频率降低83%-Dio.netty.leakDetection.levelDISABLEDAgent中Netty仅用于HTTP客户端禁用内存泄漏检测可减少15% CPU开销-Dsun.net.inetaddr.ttl60强制DNS缓存60秒避免频繁解析阿里云内网Endpoint如rds.cn-shanghai.aliyuncs.com。压测数据显示同等QPS下优化后JVM GC时间占比从32%降至9%平均响应延迟从842ms降至317ms。4.3 阿里云RDS连接池Druid配置的隐蔽瓶颈热词中“阿里云RDS使用”常被简化为“配置URL和账号密码”但Agent场景下RDS连接池极易成为瓶颈。原因在于每个Tool调用都是独立数据库操作且LLM可能并发发起多个Action如同时查订单发短信上传文件导致连接争抢。我们弃用HikariCP选用Druid 1.2.18阿里云官方推荐关键配置spring: datasource: druid: initial-size: 20 max-active: 100 min-idle: 10 # 关键防止连接被RDS主动断开 validation-query: SELECT 1 FROM DUAL test-while-idle: true time-between-eviction-runs-millis: 60000 # 关键避免长事务阻塞连接池 remove-abandoned-on-borrow: true remove-abandoned-timeout-millis: 60000 # 关键适配RDS的wait_timeout300秒 max-wait: 30000特别注意max-wait: 30000必须≤RDS的wait_timeout值阿里云RDS默认300秒否则连接池会等待超时后抛出SQLException: connection closed。我们曾因此导致Agent在高峰期出现37%的工具调用失败根源正是max-wait设为60000而RDS未调整wait_timeout。4.4 阿里云短信API发不出去的根因定位热词中高频出现“阿里云短信api发不出去”在Agent场景下这通常不是SDK问题而是鉴权凭证与网络策略的双重校验失败。我们排查路径如下检查AccessKey权限登录RAM控制台确认该AK拥有AliyunSMSFullAccess策略且未被限制IP白名单Agent部署在ECSIP不固定验证Endpoint可用性在ECS上执行curl -v https://dyvmsapi.aliyuncs.com确认返回HTTP 200而非SSL证书错误阿里云新证书链需JDK8u292抓包分析用tcpdump捕获出向流量发现请求被丢弃最终定位到安全组规则——只开放了80/443端口而短信API实际走443但ECS安全组的“放行所有端口”规则被误删SDK日志开关在application.yml中添加aliyun.sms.debugtrue输出完整HTTP Request/Response发现Signature不匹配根源是系统时间偏差15分钟NTP未同步。实操心得阿里云所有API调用均校验时间戳ECS实例必须配置NTP自动同步。我们用timedatectl set-ntp true启用systemd-timesyncd并指定阿里云NTP服务器cn.pool.ntp.org。5. 常见问题与独家排查技巧5.1 SpringAI系统提示词配置失效的5种可能网络热词“springai系统提示词怎么配置”背后是大量开发者遭遇的配置静默失败。我们整理出TOP5根因及验证方法问题现象根本原因验证方法解决方案LLM完全忽略System PromptChatClient未调用.defaultSystem()在ChatClient.builder()后打印toString()确认包含defaultSystemxxx显式调用.defaultSystem(...)勿依赖自动配置Prompt中中文乱码application.yml文件编码非UTF-8file -i application.yml检查编码用VS Code另存为UTF-8 without BOM提示词被截断YAML缩进错误导致多行字符串解析失败将提示词改为单行用\n换行使用工具描述未生效Tool.getDescription()返回空字符串Debug模式下断点ToolProvider.getTools()确保getDescription()返回非空字符串且不含特殊字符提示词在日志中显示为nullSpring Boot配置文件未被正确加载检查启动日志是否有Loading config file: application.yml确认application.yml位于src/main/resources且无同名application.properties5.2 阿里云OSS上传文件失败的链路诊断Agent调用UploadFileTool时OSS返回NoSuchBucket错误但控制台确认Bucket存在。排查步骤检查Endpoint拼写OSS Endpoint格式为oss-cn-shanghai.aliyuncs.com常见错误是写成oss.cn-shanghai.aliyuncs.com少横线或oss-shanghai.aliyuncs.com缺区域验证Bucket ACLBucket权限必须为public-read-write或privateAgent用STS Token访问禁止public-read仅读确认Object Key合法性OSS Key不能以/开头且不能包含\\、、等非法字符。Agent生成的Key如/order/20240501/abc.pdf需修正为order/20240501/abc.pdf检查STS Token时效若使用临时凭证Token过期时间必须≥Agent单次执行最大耗时我们设为30分钟抓包验证Host头Wireshark抓包发现HTTP请求Host头为bucket-name.oss-cn-shanghai.aliyuncs.com但Bucket实际为bucket-name-aliyun需在OSSClient构造时显式设置endpointhttps://oss-cn-shanghai.aliyuncs.com。5.3 ReactAgent响应延迟突增的3个隐藏诱因压测中出现P99延迟从300ms飙升至2.3s常规手段查CPU、内存、GC均正常。最终定位到Redis连接池耗尽ChatMemory使用Redis存储当并发QPS超100时JedisPool默认maxTotal8导致大量线程阻塞在jedis.getResource()。解决方案spring.redis.jedis.pool.max-active200阿里云RDS慢查询未索引queryOrderList工具执行的SQL未在phone字段建索引全表扫描耗时2.1s。解决方案ALTER TABLE orders ADD INDEX idx_phone (phone)LLM响应流式中断Spring AI默认启用StreamingChatClient但阿里云SLB默认超时60秒而LLM生成长文本需82秒。解决方案SLB监听器超时调至120秒并在ChatClient配置streamingfalse牺牲流式体验换稳定性。5.4 阿里云SSL证书免费续期的自动化脚本热词“阿里云ssl证书免费续期”在Agent场景中指为Agent对外提供HTTPS服务的Nginx配置续期。我们采用acme.sh全自动续期关键步骤安装acme.shcurl https://get.acme.sh | sh -s emailyouremail.com申请证书~/.acme.sh/acme.sh --issue -d agent.yourdomain.com --webroot /usr/share/nginx/html部署证书~/.acme.sh/acme.sh --install-cert -d agent.yourdomain.com --key-file /etc/nginx/ssl/agent.key --fullchain-file /etc/nginx/ssl/agent.crt添加crontab0 0 1 * * /root/.acme.sh/acme.sh --renew -d agent.yourdomain.com --force nginx -s reload。注意必须在Nginx配置中指定ssl_certificate_key为/etc/nginx/ssl/agent.key而非acme.sh默认路径否则续期后Nginx仍用旧证书。6. 后续演进方向与经验沉淀这个“或跃在渊”阶段的ReactAgent已稳定支撑日均12万次对话调用。但真正的“跃”还在路上——我们正在推进三个方向第一接入阿里云百炼平台私有模型。当前使用开源Qwen-7B-Chat但电商场景下对“优惠券叠加规则”“预售定金膨胀逻辑”等专有知识理解不足。百炼平台支持LoRA微调我们已用2000条客服对话QA对完成首轮微调准确率从68%提升至89%。关键动作是改造Spring AI的ChatModel Bean将QwenChatModel替换为BailianChatModel并传入百炼专属Endpoint与API Key。第二构建Agent能力图谱。把每个Tool抽象为图节点参数关系为边自动生成可视化能力地图。当LLM输出Action时系统实时校验该Action是否在图谱中可达如“查物流”必须先“查订单”避免无效调用。技术栈用Neo4j存储图谱Spring Data Neo4j做ORM映射。第三实现跨Agent协同。当前单Agent处理单会话但复杂需求如“帮我退订会员并补偿50元优惠券”需协调订单Agent、支付Agent、营销Agent。我们设计了轻量级Agent Router基于意图识别结果用阿里云NLP SDK分发子任务并用Redis Stream做跨Agent消息传递。最后分享一个血泪教训永远不要相信LLM生成的SQL。我们曾让Agent直接生成SELECT语句查询RDS结果LLM在prompt中看到“查最近3笔订单”就生成SELECT * FROM orders ORDER BY id DESC LIMIT 3却忽略了id非时间序导致返回错误订单。现在所有数据库操作均由预编译SQL参数化查询完成LLM只负责生成WHERE条件参数。技术可以激进但生产环境的底线必须守住——这是我在阿里云上跑过187天后最深的体会。
返回列表