
1. 从 Spring AI 到 Spring AI Alibaba一个 Java 开发者的真实困惑Spring AI 刚出来那阵子我身边不少做 Java 后端的兄弟都在群里讨论说终于不用在 Python 和 Java 之间来回切了直接在 Spring Boot 项目里就能调大模型。结果没过多久Spring AI Alibaba 又冒出来了。当时我第一反应是这玩意儿是不是就是把 Spring AI 换了个皮加几个阿里云的 SDK 调用后来真正花时间把源码拉下来翻了一遍才发现事情没那么简单。Spring AI Alibaba 本质上是一个构建在 Spring AI 基础之上的上层框架它做的事情不是重复造轮子而是把 Java 生态里做 AI 应用时最缺的那几块拼图给补上了。Spring AI 解决的是“怎么用统一接口调不同厂商的大模型”这个问题而 Spring AI Alibaba 解决的是“怎么用这套接口真正搭出一个能上线的 AI 应用”这个问题。这两者的关系有点像 JDBC 和 MyBatis——JDBC 给了你标准接口但真写业务的时候你还是需要一个更趁手的上层封装。这篇文章适合谁看如果你是一个 Java 开发者已经大概知道 Spring AI 是什么但还没搞清楚 Spring AI Alibaba 到底在它上面加了什么、值不值得引入、引入之后项目结构会变成什么样那这篇内容就是写给你的。我会从整体架构入手把它的分层逻辑拆开再结合我实际搭 Demo 的经验把每一层的关键细节和踩过的坑都讲清楚。2. 整体架构拆解它到底在 Spring AI 上面叠了几层2.1 先搞清楚 Spring AI 自己做了什么要理解 Spring AI Alibaba 加了什么得先知道 Spring AI 原本的边界在哪里。Spring AI 的核心抽象其实就几个东西ChatClient、ChatModel、EmbeddingModel、VectorStore、Advisor。它定义了一套统一的 API让你可以用类似chatClient.prompt().user(你好).call().content()这样的链式调用来跟大模型交互底层具体是 OpenAI 还是别的厂商通过ChatModel的实现来切换。这个设计思路很 Spring跟当年 Spring 对 JDBC 的抽象是一个路子。但问题在于Spring AI 只给了你“调模型”的能力它没有告诉你“怎么把多个模型串起来完成一个复杂任务”也没有给你“对话记忆怎么持久化”“工具调用怎么编排”“流程怎么可视化”这些偏应用层的东西。你当然可以自己写但每个项目都重复写一遍那就没意思了。2.2 Spring AI Alibaba 的分层逻辑Spring AI Alibaba 的架构我把它理解为三层叠加第一层是模型接入层。这一层它做了两件事一是接入了阿里系自己的模型服务比如通义千问系列让你在 Spring AI 的ChatModel接口下多了一个可选实现二是对 Spring AI 原有的模型接入做了增强比如在参数配置、流式响应处理上做了一些适配。这一层是最“薄”的基本上就是适配器模式。第二层是应用编排层。这是 Spring AI Alibaba 真正花力气的地方。它引入了一个叫 Graph 的概念你可以把它理解成一张有向图每个节点是一个执行单元比如调一次模型、执行一次工具、做一次条件判断边定义了节点之间的流转关系。这个设计跟 LangChain 里的 Chain 和 LangGraph 的思路很像但它是用 Java 写的而且跟 Spring 的依赖注入体系融合得很好。有了这一层你就能把“先让模型理解用户意图再根据意图决定调哪个工具最后把工具结果交给模型生成回复”这种多步骤流程用声明式的方式描述出来。第三层是工程化支撑层。这一层包括可观测性、配置管理、与 Spring Boot 自动装配的集成等等。比如它提供了对模型调用链路的追踪能力你可以看到一次请求经过了哪些节点、每个节点耗时多少、输入输出是什么。这一层的东西看起来不起眼但真到了线上排查问题的时候没有它你会非常痛苦。2.3 为什么要这么分层这个分层逻辑背后的考量我觉得核心就一个词关注点分离。模型接入层的变化频率是最低的因为模型厂商的 API 一旦定下来不会天天改应用编排层的变化频率最高因为业务逻辑一直在变工程化支撑层则相对稳定但需要跟 Spring 生态保持同步。把这三层分开之后每一层的演进节奏可以不一样。比如阿里出了新模型只需要在模型接入层加一个适配器业务方想改流程只需要在编排层调整 Graph 的结构Spring Boot 升级了工程化支撑层跟着适配就行。这种分层方式跟我们在做微服务架构时把网关层、业务层、基础设施层分开是一个道理。注意分层清晰带来的一个副作用是新手容易在层与层之间迷失。我刚开始看的时候经常搞不清楚某个类到底属于哪一层后来我的办法是看包名com.alibaba.cloud.ai下面按功能模块分得很清楚花十分钟把包结构过一遍比看文档还管用。3. 核心模块深度解析每一层里到底有什么3.1 模型接入层的适配细节模型接入层最核心的类是DashScopeChatModel它实现了 Spring AI 的ChatModel接口。你只需要在配置文件里写上 API Key 和模型名称Spring Boot 的自动装配就会帮你把这个 Bean 注册好。但这里有几个细节值得注意。第一个细节是流式响应的处理。Spring AI 的ChatModel接口定义了stream方法返回一个FluxChatResponse。DashScope 的流式接口返回的是 SSE 格式的数据Spring AI Alibaba 在中间做了一层转换把 SSE 的每个事件解析成ChatResponse。这个转换过程里有个坑如果模型返回的内容里包含特殊字符解析的时候可能会出问题。我实测下来大部分情况没问题但如果你的 prompt 里让模型输出 JSON偶尔会遇到流式解析中断的情况。解决办法是在配置里把流式模式关掉或者用非流式的call方法。第二个细节是参数映射。Spring AI 定义了一套通用的ChatOptions比如temperature、topP、maxTokens这些。但 DashScope 的 API 参数名跟这套通用定义不完全一样Spring AI Alibaba 在中间做了一层映射。大部分参数是直接对应的但有些 DashScope 特有的参数比如seed、enableSearch就需要通过扩展的 Options 类来设置。这个设计是合理的因为不同厂商的模型确实有自己特有的参数强行统一反而会丢失能力。3.2 应用编排层的 Graph 设计Graph 是 Spring AI Alibaba 里最值得花时间理解的部分。它的核心概念有这么几个Node一个执行单元可以是调用模型、执行工具、做条件判断、甚至是一段自定义的 Java 代码。Edge定义节点之间的流转关系可以是固定的A 执行完一定到 B也可以是条件的根据 A 的输出决定去 B 还是 C。State在整个 Graph 执行过程中传递的状态对象每个节点都可以读取和修改它。这个设计跟状态机很像但比状态机灵活因为节点之间的流转可以是动态的。我举个例子来说明它的价值。假设你要做一个客服机器人流程是这样的先判断用户的问题类型如果是查订单就调订单查询工具如果是退换货就调退换货工具如果都不是就直接让模型回答。用传统的写法你需要在代码里写一堆 if-else而且这些 if-else 会随着业务复杂度的增加变得越来越难维护。用 Graph 来写你只需要定义三个节点意图识别、订单查询、退换货处理和相应的边整个流程就是声明式的改起来也方便。实操心得定义 Graph 的时候State 的设计很关键。我一开始把 State 设计得太复杂什么信息都往里塞结果每个节点都要处理一大堆不相关的字段。后来我改成按需设计每个 Graph 只维护自己需要的状态节点之间的耦合度一下子就降下来了。3.3 工程化支撑层的可观测性可观测性这块Spring AI Alibaba 提供了对 Micrometer 和 OpenTelemetry 的集成。你可以在配置文件里开启追踪然后就能在控制台或者你用的 APM 系统里看到每次模型调用的详细信息。这个功能看起来简单但实际用起来非常有用。我印象很深的一次线上有个接口响应特别慢查了半天以为是数据库问题后来打开模型调用的追踪一看发现是某次模型调用因为 prompt 太长触发了模型的限流重试了好几次才成功。如果没有这个追踪能力我可能还在数据库那边瞎找。追踪信息里比较有用的几个字段model用的哪个模型、promptTokens输入 token 数、completionTokens输出 token 数、latency耗时。这几个字段结合起来看能帮你快速定位是模型本身慢还是你的 prompt 写得太长导致 token 数过多。4. 实操过程从零搭一个带 Graph 编排的 Demo4.1 环境准备与依赖引入先说一下我的环境JDK 17Spring Boot 3.2.xMaven 3.9。Spring AI Alibaba 对 JDK 版本有要求最低是 17这个要注意JDK 8 和 11 都不行。依赖引入这块核心就两个dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version1.0.0-M2/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-core/artifactId version1.0.0-M2/version /dependency版本号这块要注意Spring AI 和 Spring AI Alibaba 的版本需要对应不能随便混搭。我一开始用了 Spring AI 的 1.0.0-M1 配 Spring AI Alibaba 的 1.0.0-M2结果启动的时候报了一堆类找不到的错误。后来把两个版本对齐到 M2 就好了。配置文件里需要填 API Keyspring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.7这里有个小技巧API Key 不要直接写在配置文件里用环境变量的方式注入。我见过有同学直接把 Key 提交到代码仓库结果被扫出来盗用损失不小。4.2 定义第一个 Graph假设我们要做一个简单的问答流程用户提问 - 模型判断是否需要查天气 - 如果需要就调天气工具 - 把工具结果交给模型生成最终回复。先定义 Statepublic class WeatherState { private String userQuestion; private boolean needWeather; private String weatherInfo; private String finalAnswer; // getter/setter 省略 }然后定义节点。第一个节点是意图识别Node intentNode Node.builder() .id(intent) .action(state - { String question state.getUserQuestion(); // 这里简化处理实际可以用模型来判断 boolean needWeather question.contains(天气); state.setNeedWeather(needWeather); return state; }) .build();第二个节点是天气查询工具Node weatherNode Node.builder() .id(weather) .action(state - { // 调用天气 API这里用模拟数据 state.setWeatherInfo(晴25度); return state; }) .build();第三个节点是生成最终回复Node answerNode Node.builder() .id(answer) .action(state - { String prompt state.getNeedWeather() ? 天气信息 state.getWeatherInfo() 请回答用户问题 state.getUserQuestion() : state.getUserQuestion(); // 调用模型生成回复 String answer chatClient.prompt().user(prompt).call().content(); state.setFinalAnswer(answer); return state; }) .build();最后把这些节点和边组装成 GraphGraph graph Graph.builder() .addNode(intentNode) .addNode(weatherNode) .addNode(answerNode) .addEdge(intent, weather, state - state.isNeedWeather()) .addEdge(intent, answer, state - !state.isNeedWeather()) .addEdge(weather, answer) .build();这个 Graph 的执行逻辑是先走 intent 节点如果 needWeather 为 true就去 weather 节点然后到 answer 节点如果为 false直接到 answer 节点。4.3 执行 Graph 并观察结果执行的时候你需要一个GraphExecutorGraphExecutor executor new GraphExecutor(graph); WeatherState initialState new WeatherState(); initialState.setUserQuestion(今天天气怎么样); WeatherState result executor.execute(initialState); System.out.println(result.getFinalAnswer());跑起来之后你能看到整个流程按照预期的路径执行。如果开了追踪还能在日志里看到每个节点的耗时。注意事项Graph 的执行默认是同步的如果你的节点里有耗时的操作比如调外部 API整个流程会被阻塞。Spring AI Alibaba 也支持异步执行但需要你把节点的 action 改成返回CompletableFuture。我建议在节点数量多、或者有耗时操作的时候优先考虑异步模式。5. 常见问题与排查技巧实录5.1 启动时报 Bean 冲突这个问题我遇到过一次报错信息大概是“expected single matching bean but found 2”。原因是我的项目里同时引入了 Spring AI 的 OpenAI starter 和 Spring AI Alibaba 的 starter两个 starter 都试图注册ChatModel类型的 BeanSpring 不知道该用哪个。解决办法有两种一是用Primary注解指定一个为主 Bean二是在配置文件里排除掉不需要的那个 starter 的自动装配。我一般用第二种因为更干净。5.2 流式响应中断前面提到过流式响应在解析特殊字符时可能会中断。除了关掉流式模式还有一个办法是自定义一个ResponseErrorHandler在解析出错的时候做降级处理比如把已经收到的内容返回而不是直接抛异常。5.3 Graph 执行卡死Graph 执行卡死最常见的原因是边定义错了导致流程进入死循环。比如 A 到 B 的条件永远为 trueB 又回到 A那就永远出不来了。排查的办法是在每个节点的 action 里打日志看看流程到底走到哪一步卡住了。另外Spring AI Alibaba 的 Graph 支持设置最大执行步数可以在构建 Graph 的时候加上.maxSteps(50)之类的限制防止死循环。5.4 模型返回内容为空这个问题比较隐蔽有时候模型明明返回了内容但content()方法拿到的却是空字符串。我排查下来发现原因是模型返回的内容里包含了不可见的控制字符Spring AI 在解析的时候把这些字符过滤掉了结果就变成了空。解决办法是在 prompt 里明确要求模型不要输出特殊字符或者在拿到结果后做一次清洗。问题现象可能原因排查方向解决办法启动报 Bean 冲突多个 ChatModel 实现检查依赖树排除多余 starter 或指定 Primary流式响应中断特殊字符解析失败查看原始 SSE 数据关闭流式或自定义错误处理Graph 执行卡死边条件导致死循环节点日志设置 maxSteps 限制返回内容为空控制字符被过滤打印原始响应清洗 prompt 或结果6. 我对这套架构的真实看法用了一段时间之后我的感受是Spring AI Alibaba 的价值不在于它接入了多少模型而在于它把 Java 生态里做 AI 应用的那套工程化思路给补齐了。Spring AI 给了你一把锤子Spring AI Alibaba 给了你一个工具箱里面除了锤子还有螺丝刀、扳手和卷尺。当然它也不是没有缺点。Graph 的概念对新手来说有一定学习成本而且它的文档目前还不够完善很多细节需要自己翻源码。另外它的版本迭代比较快API 偶尔会有 breaking change升级的时候需要留个心眼。但如果你是一个 Java 团队正在考虑把 AI 能力集成到现有系统里我觉得 Spring AI Alibaba 是值得花时间研究的。它至少让你不用在 Java 和 Python 之间来回切也不用自己从零造一套编排引擎。这个方向我觉得是对的。