首页 / 资讯中心 / 文章详情

Spring AI对接阿里云实现React Agent落地实践

Spring AI对接阿里云实现React Agent落地实践 ★ FEATURED ARTICLE
1. 项目概述这不是“降龙十八掌”而是Spring AI在阿里云生态里的真实落地切口“降SpringAI阿里第9掌-或跃在渊-ReactAgent”——这个标题乍看像武侠小说混搭技术文档但拆开来看它其实精准锚定了当前Java开发者在国产化云环境落地AI Agent的三个关键坐标框架选型Spring AI、基础设施依赖阿里云、交互范式React Agent。我带团队在金融和政务类客户现场推进AI能力集成时反复遇到同一个卡点Spring Boot应用想接入大模型能力但官方starter默认走OpenAI或Azure路径国内企业受限于网络策略、合规要求和成本控制必须把模型调用链路完全收束到阿里云百炼平台或通义千问API上而更棘手的是业务系统不是要一个静态问答框而是需要让AI能主动调用内部CRM、工单、审批等后端服务——这恰恰是React Agent的核心价值让AI从“被动应答者”变成“主动协作者”。所谓“或跃在渊”指的就是这个临界状态模型能力已就位但尚未真正沉入业务毛细血管。我们不是在造轮子而是在Spring生态里用最小侵入方式把阿里云的AI能力像插件一样拧进现有系统。关键词“SpringAI”“阿里”“ReactAgent”不是堆砌而是技术栈三要素的硬性约束你得用Spring AI做编排层必须对接阿里云提供的模型服务与工具平台最终交付形态必须是具备自主规划、工具调用、多步推理能力的React Agent。适合正在做智能客服升级、内部知识助手重构、或审批流程自动化改造的Java工程师尤其适合那些已经用着Spring BootMyBatisRedis的老项目不想推倒重来只想给系统加个“会思考的副驾驶”。2. 整体设计思路为什么放弃“全量迁移”选择“协议桥接能力注入”2.1 拒绝重写拥抱存量Spring AI的扩展性设计是破局关键很多团队一上来就想把整个AI模块抽成独立微服务用Python重写再通过HTTP调用。我试过两次结果都踩了坑第一次是金融客户核心交易系统用的是JDK8Spring Boot 2.3强行引入Python服务导致运维链路断裂监控指标对不上第二次是政务项目安全审计要求所有代码必须可审计、可追溯Python脚本无法纳入统一CI/CD流水线。后来我们彻底转向Spring AI原生方案核心逻辑就一条Spring AI的设计哲学不是替代Spring而是成为Spring的一部分。它的ChatClient抽象层天然支持SPI机制只要你实现ChatModel接口就能把任何模型服务包括阿里云百炼的REST API包装成Spring容器里的Bean。这意味着你不需要改一行业务代码只需要在application.yml里换掉spring.ai.chat.model的实现类整个对话链路就切换到了阿里云。我们没动Controller没碰Service只新增了一个AliyunQwenChatModel类继承AbstractStreamingChatModel重写call方法把Spring AI的ChatRequest对象序列化成阿里云百炼要求的JSON格式再把返回的流式响应反向解析回ChatResponse。这种“协议桥接”比“服务拆分”轻量十倍上线灰度期从两周压缩到两天。2.2 React Agent不是魔法是“任务分解器工具调度器”的组合网上很多教程把React Agent讲得神乎其技仿佛AI自己会写SQL、调API。实操中我们发现真正的难点不在LLM本身而在如何让AI理解你的业务工具边界并生成可执行的、无歧义的调用指令。阿里云百炼平台的Function Calling能力虽强但它的Schema定义和Spring Boot的REST接口描述存在语义鸿沟。比如你有一个查询用户余额的接口GET /api/user/balance?userId{id}百炼要求你提供JSON Schema描述参数但Spring MVC的RequestParam注解不会自动生成OpenAPI规范。我们的解法是用Spring DocSwagger作为中间翻译层。先用springdoc-openapi-ui生成标准OpenAPI 3.0文档再用自研的OpenApiToFunctionSchemaConverter工具把paths./api/user/balance.get.parameters节点自动转成百炼所需的function定义包括name映射为userBalanceQuery、description从Swagger的summary字段提取、parameters按schema.type映射为string/integer。这样当Agent说“我需要查用户ID为12345的余额”百炼就能精准匹配到userBalanceQuery函数并把12345填入userId参数。我们不追求AI“懂业务”只确保它“懂接口契约”。这套机制让工具注册从手动编写JSON Schema变成mvn clean compile后自动生成错误率下降90%。22.3 阿里云不是“云厂商”而是“AI能力集成中枢”标题里强调“阿里”不是因为品牌偏好而是技术现实。阿里云百炼平台提供了三样不可替代的东西国产化模型托管、企业级鉴权体系、以及与阿里系PaaS的深度耦合。比如客户用的是阿里云RDS MySQL我们Agent需要查库传统做法是让AI生成SQL再由Java执行——但SQL注入风险极高。百炼的“数据库连接器”功能允许你直接绑定RDS实例配置白名单IP和只读账号AI生成的查询语句会在百炼侧完成语法校验和权限过滤再下发执行。这比在Java层做SQL解析安全得多。再比如客户有大量非结构化文档存放在OSSAgent需要检索。百炼的“知识库”功能支持OSS桶直连自动完成PDF/PPT/Word的文本抽取、向量化和索引构建而不用我们自己搭MinIOLangChainChroma。我们测算过如果全部自建光向量数据库的运维成本就占AI项目总投入的35%。所以“阿里”在这里是技术选型的刚性约束不是“能不能用”而是“不用阿里云很多能力根本落不了地”。我们甚至把百炼的modelId如qwen-max和endpoint如https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation都做成配置中心的动态参数方便不同环境测试/预发/生产切换模型版本。3. 核心细节解析从Maven依赖到系统提示词的每一处硬核配置3.1 Maven依赖为什么必须用阿里云Maven仓库且版本要卡死Spring AI官方starterspring-ai-starter-*在2024年Q2才正式支持阿里云百炼早期版本如0.8.x只认OpenAI。我们一开始用spring-ai-starter-openai强行适配结果发现OpenAiChatModel的构造函数硬编码了https://api.openai.com连baseUrl都不让改。后来我们切到spring-ai-starter-*1.0.0-M3版本但发现它依赖的spring-ai-core1.0.0-M3又和Spring Boot 3.2.0的spring-webflux存在Mono类型冲突。最终解决方案是放弃starter手写核心依赖并强制指定阿里云Maven镜像。pom.xml关键片段如下repositories repository idaliyun-maven/id urlhttps://maven.aliyun.com/repository/public/url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /repository /repositories dependencies !-- Spring AI 核心不走starter -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-core/artifactId version1.0.0-M4/version !-- 卡死版本M4修复了阿里云token刷新bug -- /dependency !-- 阿里云SDK用于签名 -- dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-alimt/artifactId version4.2.0/version !-- 注意不是最新版4.2.0兼容JDK8 -- /dependency !-- JSON处理避免Jackson版本冲突 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies提示阿里云Maven仓库地址必须显式声明否则spring-ai-core的传递依赖会从中央仓库拉取而中央仓库的1.0.0-M4包缺失阿里云适配代码。我们曾因漏配仓库导致AliyunQwenChatModel类找不到排查了6小时才发现是依赖拉错了。3.2 系统提示词配置不是“写作文”而是“定义AI的行为契约”网上流传的“SpringAI系统提示词怎么配置”教程大多教你怎么在application.yml里写一段华丽的中文描述。实操中我们发现提示词不是越长越好而是越“契约化”越稳。我们把提示词拆成三层基础角色、工具约束、输出格式。基础角色Role明确告诉AI“你是一个银行信贷审批助手只回答与贷款额度、还款计划、征信查询相关的问题其他问题一律回复‘该问题超出我的职责范围’。”工具约束Tool Constraint则用结构化语言“你只能调用以下3个工具1.creditLimitQuery查授信额度参数customerId字符串2.repaymentPlanGenerate生成还款计划参数loanAmount数字、termMonths整数3.creditReportFetch获取征信报告参数idCardNumber字符串。禁止自行构造新工具名。”最后是输出格式Output Format“所有响应必须是JSON包含thought你的推理过程、action调用的工具名、actionInput参数JSON对象、finalAnswer最终回答仅当无需调用工具时填写。”这三层提示词被拼接成一个String注入到ChatClient的systemMessage中。我们做过AB测试纯自然语言提示词的工具调用准确率是72%而契约化提示词提升到98.3%。关键在于它把模糊的“理解”变成了可验证的“模式匹配”。3.3 React Agent工作流四步闭环每一步都有防错机制React Agent的“思考-行动-观察-反思”循环在Spring AI里对应四个核心组件。我们没用官方ReActChatClient而是基于ChatClient手写了一个AliyunReActAgent确保每一步可控Thought推理调用chatClient.call()发送带systemMessage和userMessage的请求返回的ChatResponse中提取content字段。这里我们加了超时熔断chatClient配置timeout8000ms若百炼响应超时立即返回{thought:系统繁忙请稍后再试,finalAnswer:系统繁忙请稍后再试}避免前端无限等待。Action行动用正则action:\s*([^])提取工具名再用actionInput:\s*(\{.*?\})提取参数JSON。关键防错正则匹配失败时不抛异常而是返回{thought:未识别到有效动作,finalAnswer:请明确告诉我您需要办理什么业务}。我们发现LLM有时会输出action: queryCredit少了个Limit正则能容错而JSON Schema校验会直接报错。Observation观察根据工具名反射调用对应Service方法。例如creditLimitQuery对应CreditService.queryLimit(String customerId)。关键防错所有Service方法都加了Transactional(propagation Propagation.NOT_SUPPORTED)确保AI调用不污染主事务同时捕获RuntimeException包装成{error:查询失败原因网络超时}返回绝不让底层异常透出。Reflection反思把Observation结果拼成新消息再次调用chatClient.call()但这次systemMessage追加一句“你已获得工具返回结果请据此给出最终答案不要重复调用工具。”关键防错设置最大迭代次数为3次防止AI陷入“查余额→再查余额→再查余额”的死循环。超过3次强制返回{finalAnswer:已尝试多次仍无法完成操作请联系人工客服。}。这套闭环在压测中稳定支撑200 QPS平均响应时间1.2秒远优于前端直连百炼的3.5秒少了HTTP跳转和跨域开销。4. 实操过程从零开始搭建一个可运行的信贷审批Agent4.1 环境准备三台机器五分钟搞定基础骨架我们用最简配置演示不依赖Docker或K8s所有服务跑在一台开发机上但架构完全可水平扩展机器A开发机MacBook ProJDK 17IDEA 2023.3Maven 3.9.2机器B阿里云ECSCentOS 7.9部署Spring Boot应用端口8080机器C阿里云百炼无需额外部署开通百炼服务即可第一步创建Spring Boot 3.2.0项目勾选Spring Web、Lombok、Spring Configuration Processor。第二步在application.yml里配置阿里云百炼spring: ai: chat: model: aliyun-qwen # 自定义模型标识 aliyun: dashscope: api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 百炼控制台获取 endpoint: https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation model-id: qwen-max # 可选qwen-plus, qwen-turbo第三步创建config/AliyunQwenChatModelConfig.java定义BeanConfiguration public class AliyunQwenChatModelConfig { Bean ConditionalOnProperty(name spring.ai.chat.model, havingValue aliyun-qwen) public ChatModel aliyunQwenChatModel( Value(${aliyun.dashscope.api-key}) String apiKey, Value(${aliyun.dashscope.endpoint}) String endpoint, Value(${aliyun.dashscope.model-id}) String modelId) { return new AliyunQwenChatModel(apiKey, endpoint, modelId); } }第四步实现AliyunQwenChatModel。核心是重写doCall方法把ChatRequest转成百炼要求的格式public class AliyunQwenChatModel extends AbstractStreamingChatModel { private final String apiKey; private final String endpoint; private final String modelId; public AliyunQwenChatModel(String apiKey, String endpoint, String modelId) { this.apiKey apiKey; this.endpoint endpoint; this.modelId modelId; } Override protected ChatResponse doCall(ChatRequest request) { // 1. 构造百炼请求体 MapString, Object requestBody new HashMap(); requestBody.put(model, modelId); requestBody.put(input, Map.of(messages, buildMessages(request))); requestBody.put(parameters, Map.of(temperature, 0.3)); // 2. 签名阿里云要求 String signature generateSignature(requestBody, apiKey); // 3. HTTP调用 HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); headers.set(X-DashScope-Signature, signature); headers.set(X-DashScope-Date, getISO8601Time()); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object entity new HttpEntity(requestBody, headers); RestTemplate restTemplate new RestTemplate(); ResponseEntityMap response restTemplate.postForEntity(endpoint, entity, Map.class); // 4. 解析响应 MapString, Object result response.getBody(); String content (String) ((Map) ((Map) result.get(output)).get(text)); return new ChatResponse(new AiMessage(content)); } private ListMapString, String buildMessages(ChatRequest request) { return request.getMessages().stream() .map(msg - { MapString, String m new HashMap(); if (msg instanceof UserMessage) { m.put(role, user); m.put(content, msg.getContent()); } else if (msg instanceof SystemMessage) { m.put(role, system); m.put(content, msg.getContent()); } return m; }) .collect(Collectors.toList()); } // 签名生成逻辑省略调用aliyun-java-sdk-alimt private String generateSignature(MapString, Object body, String apiKey) { ... } }注意generateSignature方法必须严格遵循阿里云百炼的HMAC-SHA256签名规则包括X-DashScope-Date头的时间格式UTC0精确到秒少一个字符都会返回401。我们封装了一个DashScopeSigner工具类复用率100%。4.2 工具注册让Agent“认识”你的业务系统假设我们有三个业务接口Service public class CreditService { Autowired private RestTemplate restTemplate; // 调用内部微服务 // 查询授信额度 public BigDecimal queryLimit(String customerId) { String url http://internal-api:8081/credit/limit?customerId customerId; return restTemplate.getForObject(url, BigDecimal.class); } // 生成还款计划 public ListRepaymentPlan generatePlan(BigDecimal loanAmount, Integer termMonths) { // 实现略 } // 获取征信报告摘要 public String fetchCreditReport(String idCardNumber) { // 实现略 } }在AliyunReActAgent构造时注册这些工具Component public class AliyunReActAgent { private final ChatClient chatClient; private final CreditService creditService; public AliyunReActAgent(ChatClient chatClient, CreditService creditService) { this.chatClient chatClient; this.creditService creditService; } public String run(String userQuery) { // 初始化工具映射 MapString, FunctionJsonNode, String tools new HashMap(); tools.put(creditLimitQuery, this::handleCreditLimitQuery); tools.put(repaymentPlanGenerate, this::handleRepaymentPlan); tools.put(creditReportFetch, this::handleCreditReport); // 执行React循环 String currentQuery userQuery; for (int i 0; i 3; i) { ChatResponse response chatClient.call( ChatRequest.builder() .systemMessage(buildSystemPrompt(tools)) .messages(List.of(new UserMessage(currentQuery))) .build() ); String content response.getResult().getOutput().getContent(); JsonNode jsonNode new ObjectMapper().readTree(content); if (jsonNode.has(finalAnswer)) { return jsonNode.get(finalAnswer).asText(); } String action jsonNode.get(action).asText(); JsonNode actionInput jsonNode.get(actionInput); if (!tools.containsKey(action)) { return 不支持的操作 action; } String observation tools.get(action).apply(actionInput); currentQuery Observation: observation; } return 操作超时; } private String handleCreditLimitQuery(JsonNode input) { try { String customerId input.get(customerId).asText(); BigDecimal limit creditService.queryLimit(customerId); return 用户 customerId 的授信额度为 limit 元; } catch (Exception e) { return 查询失败 e.getMessage(); } } // 其他handle方法略 }4.3 Controller暴露一个Endpoint承载所有AI能力最后创建AgentController把Agent能力暴露为REST接口RestController RequestMapping(/api/agent) public class AgentController { private final AliyunReActAgent agent; public AgentController(AliyunReActAgent agent) { this.agent agent; } PostMapping(/chat) public ResponseEntityMapString, String chat(RequestBody MapString, String request) { String userQuery request.get(query); if (StringUtils.isBlank(userQuery)) { return ResponseEntity.badRequest().body(Map.of(error, query不能为空)); } long start System.currentTimeMillis(); String answer agent.run(userQuery); long cost System.currentTimeMillis() - start; MapString, String result new HashMap(); result.put(answer, answer); result.put(costMs, String.valueOf(cost)); result.put(timestamp, String.valueOf(System.currentTimeMillis())); return ResponseEntity.ok(result); } }启动应用用curl测试curl -X POST http://localhost:8080/api/agent/chat \ -H Content-Type: application/json \ -d {query:帮我查一下客户10086的授信额度}返回{ answer: 用户10086的授信额度为500000元, costMs: 1245, timestamp: 1717023456789 }整个过程从创建项目到返回第一条AI响应耗时不到8分钟。我们刻意没用任何第三方AI框架所有代码都在Spring生态内可调试、可监控、可审计。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 百炼API返回401不是密钥错了是签名时间戳漂移了现象本地测试一切正常部署到阿里云ECS后AliyunQwenChatModel持续返回401 Unauthorized。检查apiKey确认无误endpoint也正确。排查过程抓包发现ECS服务器的系统时间比NTP服务器慢了32秒。百炼签名要求X-DashScope-Date头的时间与服务器时间误差不超过30秒否则拒绝请求。解决方案在ECS上执行sudo ntpdate -u ntp1.aliyun.com强制同步时间并设置crontab每5分钟同步一次*/5 * * * * /usr/sbin/ntpdate -u ntp1.aliyun.com /dev/null 21。经验所有涉及时间敏感签名的服务百炼、OSS、短信API必须把NTP同步作为上线Checklist的第一项。我们曾因忽略此点在生产环境停服2小时。5.2 Agent死循环不是AI太聪明是工具返回值没做清洗现象Agent收到“查询用户10086余额”指令后反复调用creditLimitQuery每次返回“用户10086的授信额度为500000元”但始终不生成finalAnswer。根因分析LLM的finalAnswer生成逻辑依赖Observation内容是否包含“已完成”语义。而我们的handleCreditLimitQuery返回的是纯文本“用户10086的授信额度为500000元”LLM认为这只是中间结果需要继续追问。解决方法在工具返回前强制添加结束标记。修改handleCreditLimitQueryprivate String handleCreditLimitQuery(JsonNode input) { try { String customerId input.get(customerId).asText(); BigDecimal limit creditService.queryLimit(customerId); return 【已完成】用户 customerId 的授信额度为 limit 元; // 加【已完成】标记 } catch (Exception e) { return 【已失败】查询失败 e.getMessage(); } }同时在systemMessage里加一句“当你看到Observation以【已完成】开头时必须生成finalAnswer以【已失败】开头时必须告知用户失败原因。”效果死循环概率从100%降至0.3%。5.3 Maven依赖冲突不是版本太高是传递依赖的scope搞错了现象引入aliyun-java-sdk-alimt后项目启动报错java.lang.NoSuchMethodError: com.fasterxml.jackson.databind.ObjectMapper.setDefaultPropertyInclusion。诊断aliyun-java-sdk-alimt4.2.0传递依赖了jackson-databind2.12.3而Spring Boot 3.2.0要求2.15.x。Maven的依赖调解机制选择了旧版本。解法在pom.xml中显式排除冲突依赖dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-alimt/artifactId version4.2.0/version exclusions exclusion groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /exclusion /exclusions /dependency然后单独引入jackson-databind2.15.2。心得所有SDK类依赖务必检查其pom.xml的传递依赖尤其是Jackson、Guava、Netty这类基础库。我们维护了一个dependency-check脚本每次引入新SDK都自动扫描冲突。5.4 提示词失效不是LLM不听话是JSON解析器太严格现象Agent返回的JSON里actionInput字段是{customerId: 10086}但我们的正则actionInput:\s*(\{.*?\})匹配失败因为实际响应是actionInput: {customerId: 10086}冒号后多了一个空格。根源LLM生成JSON时格式不固定。正则.*?在遇到换行符时会截断。终极解法放弃正则用Jackson的JsonNode解析String content response.getResult().getOutput().getContent(); JsonNode rootNode new ObjectMapper().readTree(content); if (rootNode.has(actionInput)) { JsonNode actionInputNode rootNode.get(actionInput); // 直接用actionInputNode.get(customerId).asText()取值 }虽然性能略低但100%可靠。我们把JSON解析封装成SafeJsonParser工具类所有Agent环节统一调用。教训对LLM输出做任何字符串操作正则、substring都是危险的。必须用标准JSON解析器哪怕慢10ms。6. 进阶实战把Agent嵌入现有审批流实现“AI辅助决策”6.1 场景还原信贷经理提交申请后Agent自动补全风控信息客户原有审批流是业务员在OA系统填表 → 提交至信贷经理 → 经理登录内部系统查征信、算额度 → 手动填写审批意见 → 提交。整个过程平均耗时47分钟。我们把Agent嵌入到“经理查看申请”环节当经理点击“查看详情”按钮前端发起/api/agent/credit-assist请求传入申请单号Agent自动完成三件事1. 调用creditReportFetch获取征信摘要2. 调用creditLimitQuery查当前授信3. 调用repaymentPlanGenerate生成3套还款方案。最终返回结构化数据{ creditReportSummary: 近24个月逾期0次当前负债率62%, currentCreditLimit: 500000.00, repaymentPlans: [ {term: 12个月, monthly: 4523.12, total: 54277.44}, {term: 24个月, monthly: 2389.56, total: 57349.44}, {term: 36个月, monthly: 1678.34, total: 60420.24} ], aiRecommendation: 建议批准推荐24期方案月供压力适中 }信贷经理只需阅读AI生成的aiRecommendation点击“采纳建议”按钮系统自动填充审批意见。审批时长从47分钟压缩到8分钟。6.2 技术实现Agent不暴露给前端而是作为Service层增强关键设计原则Agent不是新功能而是对现有Service的增强。我们没新建Controller而是在CreditApplicationService里注入AliyunReActAgentService public class CreditApplicationService { private final AliyunReActAgent agent; public CreditApplicationService(AliyunReActAgent agent) { this.agent agent; } public CreditAssistResult assist(String applicationId) { // 1. 从DB查申请单详情 CreditApplication app applicationMapper.selectById(applicationId); // 2. 构造Agent输入 String prompt String.format( 请基于以下信息生成风控辅助报告申请人姓名%s身份证号%s申请金额%s元期限%s个月。, app.getCustomerName(), app.getIdCard(), app.getLoanAmount(), app.getTermMonths() ); // 3. 调用Agent String rawResponse agent.run(prompt); // 4. 解析并封装 return parseAssistResult(rawResponse); } }这样OA系统调用CreditApplicationService.assist()即可完全无感。Agent的调用日志、耗时、错误率全部走Spring AOP统一埋点和业务日志在一个ELK集群里。体会最好的AI集成是让人感觉不到AI的存在。它应该像数据库连接池一样是基础设施而不是一个炫技的功能入口。6.3 安全加固三道防线守住企业数据不出域网络层面ECS安全组只开放8080端口给OA系统IP段百炼API调用走VPC内网阿里云百炼支持VPC Endpoint杜绝公网传输。数据层面Agent所有工具方法对敏感字段身份证号、银行卡号做脱敏处理。handleCreditReport返回时自动把idCardNumber的中间8位替换为*。审计层面每次Agent调用记录applicationId、toolName、inputParams脱敏后、outputSummary、costMs到独立审计表。审计表开启MySQL Binlog对接公司SIEM系统。我们曾拦截到一次异常某天凌晨3点creditReportFetch被高频调用参数全是随机身份证号。审计日志显示来源IP是OA系统的定时任务服务器——原来是运维误配了cron把测试脚本上线了。没有这三道防线数据泄露风险就在毫秒之间。我在实际项目里跑通这套方案时最大的感触是所谓“第9掌”从来不是什么玄妙招式而是把Spring的约定优于配置、阿里的云原生能力、React的工程化思想三者拧成一股绳。它不追求技术炫技只解决一个朴素问题让老系统也能长出AI的翅膀。
阅读完成 · 觉得有帮助?
咨询建站