ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring AI ReactAgent在阿里云上的工程化落地实践

2026/10/7 13:18:25 拓冰建站 浏览量
Spring AI ReactAgent在阿里云上的工程化落地实践 1. 这不是“第九掌”是Spring AI在阿里云生态里的一次真实落地切口“降SpringAI阿里第9掌-或跃在渊-ReactAgent”——这个标题乍看像武侠小说里的秘籍名但如果你最近在Spring Boot项目里反复调试spring-ai依赖、翻遍阿里云Maven仓库镜像配置、对着application.yml里那段spring.ai.chat.memory.conversation-id发呆你就知道这根本不是玄学而是一线开发者正在经历的、具体到每一行代码的实战现场。我上个月接手一个电商后台的智能客服模块升级原系统用的是硬编码规则关键词匹配响应率卡在62%。团队决定接入Spring AI做LLM编排目标很朴素让客服机器人能根据用户当前对话上下文订单数据库状态促销活动规则动态生成回复而不是背诵预设话术。选型时我们对比了LangChain4j、LlamaIndex和Spring AI——最终锁死Spring AI不是因为它“最先进”而是它和Spring Boot生态咬合得最紧自动配置、条件化Bean、Actuator健康检查、甚至RetryableTopic都能无缝集成。而“阿里”二字不是指某家厂商而是指整个开发链路中绕不开的现实约束公司所有Maven依赖必须走阿里云私有仓库所有API调用必须走阿里云百炼平台的模型网关所有日志要打到SLS所有服务注册进ACM。换句话说“阿里”在这里是基础设施层不是技术选型层。至于“ReactAgent”它既不是前端React框架也不是某个开源库而是Spring AI 1.0.0-M3版本引入的基于ReAct范式Reasoning Acting构建的Agent抽象。它的核心不是“调用大模型”而是“在模型输出和本地动作之间建立闭环反馈”。比如用户问“我上周买的iPhone 15 Pro为什么还没发货”传统做法是把这句话喂给大模型让它猜答案而ReactAgent会先执行OrderQueryTool查订单状态再把结果拼进Prompt再调一次模型生成自然语言回复——整个过程由AgentExecutor驱动每一步都可审计、可重试、可熔断。这才是标题里“或跃在渊”的本意不是悬浮在空中喊口号而是扎进业务数据流里一跃一潜稳扎稳打。你可能正面临类似场景老板说“我们要上AI”但没人告诉你怎么把AI能力塞进现有Spring Boot系统你搜“springai系统提示词怎么配置”结果看到的全是Hello World示例没一行代码告诉你如何在多租户环境下隔离不同客户的system prompt你配好maven配置阿里云仓库却在mvn clean install时发现spring-ai-starter-openai拉不到最新快照版——因为阿里云中央仓库同步延迟了48小时。这篇内容就是为你解决这些“非技术但致命”的问题而写。它不讲大模型原理不画架构图只聚焦一件事如何让Spring AI的ReactAgent在阿里云基础设施约束下真正跑通、稳定、可维护。2. ReactAgent不是新玩具是Spring AI对LLM应用范式的重新定义在Spring AI 0.x时代开发者面对的主要是ChatClient和EmbeddingClient——它们像两个功能完备的工具箱但你需要自己搭脚手架手动拼接Prompt、管理历史消息、处理流式响应、封装工具调用逻辑。这种模式在POC阶段够用一旦进入生产环境问题立刻暴露同一个Prompt在不同用户会话中复用导致上下文污染工具调用失败后没有回退机制模型返回JSON格式错误却无法自动重试。ReactAgent的出现不是增加一个新类而是把LLM应用从“函数式调用”升级为“状态机驱动”。2.1 ReAct范式让大模型学会“思考-行动-验证”闭环ReActReasoning Acting由Princeton大学2022年提出核心思想是将LLM的推理过程显式拆解为三个步骤Reasoning推理模型分析用户输入决定下一步该做什么如“需要查询订单状态”Acting行动调用外部工具如数据库查询、API请求获取真实世界数据Observation观察将工具返回结果作为新输入交还模型继续推理。这个循环可以重复多次直到得出最终答案。Spring AI的ReactAgent正是这一范式的工程实现。它内部包含三个关键组件AgentExecutor主控引擎负责调度整个ReAct循环管理状态流转Tool封装外部能力的接口如OrderQueryTool、InventoryCheckTool每个Tool必须实现execute方法并声明description供模型理解用途PromptTemplate预定义的ReAct Prompt模板强制模型按固定格式输出Thought:、Action:、Action Input:、Observation:等字段便于解析。提示Spring AI默认使用ReActJsonOutputParser解析模型输出它要求模型返回严格JSON格式。但实测发现即使使用Qwen-72B-Instruct仍有约15%概率返回非JSON文本如多出换行、漏掉逗号。我的解决方案是在AgentExecutor外层加一层ResilientOutputParser用正则提取Action Input字段失败时触发降级策略——这是官方文档不会写的细节。2.2 与LangChain4j Agent的本质差异Spring Boot原生基因LangChain4j的AgentExecutor设计更通用支持多种Agent类型OpenAiFunctionsAgent、StructuredToolAgent但它需要手动注入ChatModel、ToolProvider、PromptTemplate配置分散。Spring AI的ReactAgent则深度绑定Spring生态Bean声明的Tool自动注册到ToolRegistryChatClient通过Primary注解被AgentExecutor自动装配PromptTemplate可从application.yml加载支持Profile切换如dev环境用mock模型prod环境用百炼所有组件天然支持ConditionalOnProperty可按需启用/禁用。我们曾用LangChain4j实现过类似功能但为了兼容Spring Boot Actuator不得不额外写HealthIndicator和MetricsBinder而Spring AI的ReactAgent只要在pom.xml里加spring-boot-starter-actuator/actuator/health就会自动显示Agent执行器状态/actuator/metrics/spring.ai.agent.executions能直接看到成功率、平均耗时——这种“开箱即用”的体验才是企业级框架该有的样子。2.3 为什么必须用ReactAgent一个电商订单查询的真实案例假设用户提问“我昨天下的单订单号123456现在到哪了”传统方案ChatClient直调String prompt 根据以下订单信息用中文回复用户\n 订单号 orderId \n 状态已发货\n 物流单号SF123456789\n 承运商顺丰; chatClient.call(prompt); // 模型可能胡编物流进度ReactAgent方案模型推理Thought: 需要查询订单123456的物流轨迹→Action: LogisticsQueryTool→Action Input: {orderId: 123456}工具执行调用LogisticsQueryTool.execute()从菜鸟裹裹API获取实时轨迹观察反馈Observation: [{time:2024-06-01 10:00,status:已揽收},{time:2024-06-01 18:30,status:运输中}]模型再推理Thought: 物流已揽收尚未发出应告知用户预计今日发出→Action: FinalAnswer→Action Input: 您的订单已由顺丰揽收预计今日发出稍后可查物流详情。。这个过程的关键价值在于可追溯性每一步Action都有日志记录当用户投诉“机器人说错了”你可以直接查logistics_query_tool_execution表确认是API返回异常还是模型理解偏差而传统方案里所有信息都混在一次HTTP响应里根本无法定位问题根源。3. 阿里云基础设施约束下的Spring AI工程化落地标题里的“阿里”绝不是营销噱头。在真实企业环境中“用Spring AI”和“在阿里云上用Spring AI”是两件事。前者关注API怎么调后者关注Maven怎么拉、模型怎么连、日志怎么查、监控怎么配。我们踩过的坑90%都来自基础设施适配。3.1 Maven配置阿里云仓库不只是改URL更要懂镜像同步机制Spring AI的正式版1.0.0-M3发布在Spring Milestone仓库但阿里云Maven中央仓库默认只同步central和spring-plugin不包含spring-milestones。直接改settings.xml的mirrorOf为*会导致其他依赖拉取失败。正确做法是分仓库配置!-- settings.xml -- profiles profile idaliyun-spring-milestones/id repositories repository idspring-milestones/id urlhttps://maven.aliyun.com/repository/spring-milestones/url releasesenabledfalse/enabled/releases snapshotsenabledtrue/enabled/snapshots /repository /repositories /profile /profiles activeProfiles activeProfilealiyun-spring-milestones/activeProfile /activeProfiles注意两点阿里云提供的spring-milestones镜像地址是https://maven.aliyun.com/repository/spring-milestones不是/maven2/结尾必须设置releasesenabledfalse/enabled/releases因为里程碑版本都是SNAPSHOT开启releases会导致拉取失败。我们曾因忽略第二点在Jenkins流水线里反复报错Could not find artifact org.springframework.ai:spring-ai-core:jar:1.0.0-M3。排查三天才发现是Maven把SNAPSHOT当成Release处理了。后来在pom.xml里显式指定仓库repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://maven.aliyun.com/repository/spring-milestones/url snapshotsenabledtrue/enabled/snapshots /repository /repositories这样即使全局mirrorOf未生效也能兜底。3.2 模型接入百炼平台绕过OpenAI兼容层直连阿里云API网关Spring AI默认支持OpenAI风格API但百炼平台的Endpoint和鉴权方式不同OpenAIhttps://api.openai.com/v1/chat/completionsHeaderAuthorization: Bearer sk-xxx百炼https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generationHeaderAuthorization: Bearer api_keyX-DashScope-SSE: enable流式必需。直接配置spring.ai.openai.api-key会失败。正确姿势是自定义ChatClientBeanBean public ChatClient chatClient() { return ChatClient.builder() .baseUrl(https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation) .defaultHeaders(httpHeaders - { httpHeaders.set(Authorization, Bearer apiKey); httpHeaders.set(X-DashScope-SSE, enable); }) .build(); }但这里有个陷阱百炼的请求体是DashScope格式不是OpenAI格式。Spring AI的OpenAiChatRequest会序列化成{model:qwen-max,messages:[{role:user,content:...}]}而百炼要求{model:qwen-max,input:{messages:[{role:user,content:...}]},parameters:{temperature:0.8}}。解决方案是重写ChatRequestConverterBean public ChatRequestConverter chatRequestConverter() { return new DashScopeChatRequestConverter(); // 自定义转换器包装input字段 }这个转换器要处理三件事将messages数组包进input对象将temperature等参数移到parameters下对tool_calls字段做兼容处理百炼暂不支持function calling需降级为text prompt。3.3 日志与监控把Agent执行过程变成可审计的业务事件在阿里云环境日志必须打到SLS监控指标要推到ARMS。Spring AI的AgentExecutor默认只打印DEBUG日志无法满足审计要求。我们通过AgentExecutionListener实现全链路埋点Component public class AliyunAgentExecutionListener implements AgentExecutionListener { Override public void onExecutionStart(AgentExecutionEvent event) { // 记录开始时间、会话ID、用户ID、初始Prompt SlsLogger.info(AGENT_START, Map.of(sessionId, event.getSessionId(), userId, event.getUserId(), prompt, event.getInitialPrompt())); } Override public void onActionExecuted(AgentExecutionEvent event) { // 记录每次Action调用工具名、输入参数、耗时、结果摘要 long duration System.currentTimeMillis() - event.getStartTime(); SlsLogger.info(AGENT_ACTION, Map.of(toolName, event.getToolName(), input, event.getToolInput(), durationMs, duration, resultSummary, event.getResult().substring(0, Math.min(100, event.getResult().length())))); } Override public void onExecutionEnd(AgentExecutionEvent event) { // 记录最终答案、总耗时、ReAct循环次数 ARMS.timing(spring.ai.agent.execution, event.getDuration()); SlsLogger.info(AGENT_END, Map.of(finalAnswer, event.getFinalAnswer(), totalSteps, event.getStepCount(), totalDurationMs, event.getDuration())); } }这套埋点让我们第一次看清Agent的真实负载平均每次执行耗时2.3秒其中78%花在工具调用数据库查询API请求只有22%是模型推理。这直接指导了后续优化方向——不是换更快的模型而是给OrderQueryTool加Redis缓存。4. ReactAgent实战从零搭建一个可运行的电商客服Agent理论讲完现在动手。以下是一个完整、可直接运行的ReactAgent示例所有代码均经过阿里云环境实测。它解决一个具体问题用户咨询“我的优惠券还能用吗”Agent需查询优惠券状态、校验使用条件、生成友好回复。4.1 环境准备最小可行依赖集pom.xml关键依赖注意版本兼容性properties spring-boot.version3.2.5/spring-boot.version spring-ai.version1.0.0-M3/spring-ai.version dashscope-sdk.version3.12.0/dashscope-sdk.version /properties dependencies !-- Spring Boot Web基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-core/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-chat-model/artifactId version${spring-ai.version}/version /dependency !-- 阿里云百炼SDK替代OpenAI客户端 -- dependency groupIdcom.alibaba.dashscope/groupId artifactIddashscope-sdk/artifactId version${dashscope-sdk.version}/version /dependency !-- Lombok简化代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意不要引入spring-ai-starter-openai它会强制依赖OpenAI客户端与百炼冲突。所有ChatClient必须手动配置。4.2 定义业务工具CouponQueryToolComponent public class CouponQueryTool implements Tool { private final CouponService couponService; // 假设已有优惠券服务 public CouponQueryTool(CouponService couponService) { this.couponService couponService; } Override public String getName() { return coupon_query; } Override public String getDescription() { return 查询优惠券状态和使用条件。输入参数couponCode优惠券码; } Override public Object execute(MapString, Object input) { String couponCode (String) input.get(couponCode); if (StringUtils.isBlank(couponCode)) { return 参数错误couponCode不能为空; } try { CouponDetail detail couponService.queryByCode(couponCode); return Map.of( status, detail.getStatus(), // VALID/EXPIRED/USED discountAmount, detail.getDiscountAmount(), minOrderAmount, detail.getMinOrderAmount(), validUntil, detail.getValidUntil(), usedTimes, detail.getUsedTimes() ); } catch (Exception e) { log.error(Coupon query failed for code: {}, couponCode, e); return 查询失败请稍后重试; } } }这个Tool的getDescription至关重要——它是模型理解工具用途的唯一依据。我们测试发现如果描述写成“查优惠券”模型会误判为“查询优惠券列表”写成“查询优惠券状态和使用条件”后准确率提升到92%。4.3 构建AgentExecutor注入工具与模型Configuration public class AgentConfig { Value(${dashscope.api-key}) private String dashscopeApiKey; Bean public ChatClient chatClient() { return ChatClient.builder() .baseUrl(https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation) .defaultHeaders(httpHeaders - { httpHeaders.set(Authorization, Bearer dashscopeApiKey); httpHeaders.set(X-DashScope-SSE, enable); }) .build(); } Bean public AgentExecutor agentExecutor(ChatClient chatClient, ToolRegistry toolRegistry) { // 使用Spring AI内置的ReActPromptTemplate PromptTemplate promptTemplate PromptTemplate.builder() .template( 你是一个电商客服助手请严格按ReAct格式回答 Thought: 你的思考过程 Action: 要调用的工具名只能是{toolNames}中的一个 Action Input: 工具输入参数JSON格式 Observation: 工具返回结果 ...可重复多次 Thought: 最终思考 Final Answer: 给用户的自然语言回复 可用工具{toolDescriptions} 用户问题{userMessage} ) .build(); return AgentExecutor.builder() .chatClient(chatClient) .toolRegistry(toolRegistry) .promptTemplate(promptTemplate) .maxIterations(5) // 防止无限循环 .build(); } }4.4 控制器暴露REST APIRestController RequestMapping(/api/agent) public class AgentController { private final AgentExecutor agentExecutor; public AgentController(AgentExecutor agentExecutor) { this.agentExecutor agentExecutor; } PostMapping(/chat) public ResponseEntityMapString, Object chat(RequestBody AgentRequest request) { try { AgentResponse response agentExecutor.execute(request.getMessage()); return ResponseEntity.ok(Map.of( answer, response.getFinalAnswer(), steps, response.getExecutionSteps().size(), durationMs, response.getDuration() )); } catch (Exception e) { log.error(Agent execution failed, e); return ResponseEntity.status(500).body(Map.of(error, 服务暂时不可用)); } } } Data AllArgsConstructor public static class AgentRequest { private String message; } Data AllArgsConstructor public static class AgentResponse { private String finalAnswer; private ListAgentExecutionStep executionSteps; private long duration; }启动应用用curl测试curl -X POST http://localhost:8080/api/agent/chat \ -H Content-Type: application/json \ -d {message:我的优惠券ABC123还能用吗}预期返回{ answer: 您的优惠券ABC123状态正常可抵扣50元订单满200元可用有效期至2024-12-31。, steps: 2, durationMs: 1842 }4.5 关键避坑指南那些文档没写的实操细节Prompt模板里的{toolNames}和{toolDescriptions}必须动态注入Spring AI的PromptTemplate不支持自动替换工具列表。必须在构建AgentExecutor前手动拼接String toolNames toolRegistry.getTools().stream() .map(Tool::getName) .collect(Collectors.joining(, )); String toolDescriptions toolRegistry.getTools().stream() .map(tool - tool.getName() : tool.getDescription()) .collect(Collectors.joining(\n));百炼API的流式响应需特殊处理DashScope的SSE流式响应格式与OpenAI不同Spring AI默认的StreamingChatResponseHandler会解析失败。解决方案禁用流式用同步调用Bean public ChatClient chatClient() { return ChatClient.builder() .baseUrl(https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation) .defaultHeaders(...) // 同上 .build(); } // 不设置.streaming(true)让AgentExecutor走同步路径工具参数类型必须是MapString, Object即使你的工具方法签名是execute(String couponCode)Spring AI的ToolExecutor只会传入Map。必须在execute方法里手动提取Override public Object execute(MapString, Object input) { String couponCode (String) input.get(couponCode); // 不能写成input.get(code) // ... }因为模型生成的Action Input是{couponCode: ABC123}字段名必须与input参数的key完全一致。5. 生产就绪性能压测、容灾降级与灰度发布一个能跑通Demo的Agent离生产还有十万八千里。我们花了两周做稳定性加固以下是关键措施。5.1 性能压测找出真正的瓶颈用JMeter模拟100并发用户持续5分钟结果令人意外指标数值分析平均响应时间2.1s可接受95%线3.8s存在长尾错误率0.3%主要来自百炼API超时CPU使用率42%未达瓶颈数据库连接池等待120msCouponQueryTool是瓶颈深入分析发现CouponQueryTool每次调用都查一次DB而同一优惠券码在1分钟内被高频查询。解决方案是加二级缓存Component public class CouponQueryTool implements Tool { private final CouponService couponService; private final CacheManager cacheManager; // Spring Cache public CouponQueryTool(CouponService couponService, CacheManager cacheManager) { this.couponService couponService; this.cacheManager cacheManager; } Override public Object execute(MapString, Object input) { String couponCode (String) input.get(couponCode); // 先查缓存 Cache.ValueWrapper wrapper cacheManager.getCache(coupon).get(couponCode); if (wrapper ! null) { return wrapper.get(); } // 缓存未命中查DB并写入缓存TTL 5分钟 CouponDetail detail couponService.queryByCode(couponCode); cacheManager.getCache(coupon).put(couponCode, detail); return detail; } }压测后95%线降至2.2s错误率归零。5.2 容灾降级当百炼不可用时Agent不能挂我们设计三级降级策略一级降级API超时百炼调用超过3s自动切换到本地Qwen-7B-Chat模型部署在ECS上二级降级模型故障本地模型不可用返回预设话术模板三级降级全链路失败直接跳过Agent转人工客服入口。实现方式是装饰ChatClientBean public ChatClient resilientChatClient() { return new ResilientChatClient( primaryChatClient(), // 百炼 fallbackChatClient(), // 本地Qwen () - 抱歉当前AI服务繁忙请稍后重试或联系人工客服 ); }ResilientChatClient内部用CircuitBreaker控制熔断失败5次后自动切换下游30秒后尝试恢复。5.3 灰度发布用ACM配置中心动态控制流量在阿里云ACM上创建配置项agent.enabledtrue并在代码中监听Value(${agent.enabled:true}) private boolean agentEnabled; PostMapping(/chat) public ResponseEntity? chat(RequestBody AgentRequest request) { if (!agentEnabled) { return ResponseEntity.ok(Map.of(answer, AI客服暂未开放敬请期待)); } // 正常执行Agent }上线后先对1%内部员工开放观察SLS日志中的AGENT_START事件量确认无异常后逐步扩到5%、20%全程在ACM控制台一键开关无需重启服务。6. 我的体会ReactAgent的价值不在“炫技”而在“可控”做完这个项目我最大的体会是ReactAgent真正的价值从来不是让机器人“更聪明”而是让AI能力“更可控”。在传统方案里模型输出是个黑盒你永远不知道它为什么给出某个答案而在ReactAgent里每一次Thought、Action、Observation都被记录你可以像调试Java代码一样逐行查看决策链条。当用户投诉“机器人说我的优惠券过期了其实没过期”你打开SLS日志一眼就能看到Observation字段里返回的validUntil确实是2024-05-31问题出在数据库数据错误而不是模型幻觉。另外“阿里”二字教会我一件事技术选型不能只看文档多漂亮更要问“它在我们的基础设施里能不能活下来”。Spring AI的ReactAgent之所以能落地不是因为它比LangChain4j先进而是因为它愿意为阿里云的Maven镜像、百炼API、SLS日志、ACM配置做适配——这种务实精神比任何技术概念都珍贵。最后分享一个小技巧在AgentExecutor的maxIterations设为3时我们发现模型有时会陷入“查订单→查物流→查订单→查物流”的死循环。解决方法是在PromptTemplate里加一句约束“禁止重复调用同一工具超过两次”。这句看似简单的指令让循环率从12%降到0.3%。有时候最好的工程方案就是用最朴素的语言告诉模型它该怎么做。