先抛个结论:如果你已经在用 Spring AI 写应用,Spring AI Alibaba 并不是一个陌生的替代品,它更像是一层把 Spring AI 抽象翻译成国内模型可执行协议的“落地层”。这篇文章不逐行讲源码,而是从整体概览和分层架构入手,帮你搞清楚它到底在 Spring AI 之上加了什么,适合什么场景用,以及真正动手时会遇到哪些坑。
Spring AI 本身的定位很像当年 Spring JDBC 解决数据库差异:不管底层是 MySQL 还是 Oracle,上层都通过一套统一的模板接口操作。模型这边也一样,OpenAI、Claude、通义千问各有各的 HTTP 协议和参数风格,Spring AI 做的就是让开发者面对统一接口,把模型差异挡在身后。而 Spring AI Alibaba 做的事情,简单说就是把这个统一抽象接到了国内主流模型的实现上,同时补了一批更贴近生产使用的配套能力。
1. 为什么 Spring 生态还需要一个“国产分支”
1.1 Spring AI 先解决了一个什么问题
在没有 Spring AI 之前,Java 里接大模型最原始的做法是直接引入模型厂商的 SDK。当时我做一个小项目接通义千问,SDK 里封装了请求、签名、刷 token 的细节,代码倒是不复杂,问题出在“换模型”这件事上。
今天你想用 qwen-plus,明天想换成 GPT,发现两家 SDK 的请求对象、参数命名、返回结构都不一样。这不是改一个类能解决的事,而是整套调用链都要动。团队里一旦有多个模型在跑,维护成本立刻上来了。
Spring AI 的做法是定义一层抽象,把这套“换模型”的成本降到最低。它把聊天、文生图、向量化、语音等能力抽象成接口,再让各家模型通过 starter 提供实现。业务代码里你面向的是 ChatModel、EmbeddingModel、ChatClient 这些统一入口,底层具体是哪一家模型,由配置和依赖决定。
这个思路本质上和 Spring Data JPA 对数据库的抽象一致。数据库方言千奇百怪,但 Repository 接口是固定的,换数据库不会影响 service 层代码。Spring AI 想做的,就是成为大模型领域的 JPA。
1.2 Spring AI Alibaba 的定位很微妙
问题在于,Spring AI 官方的模型 starter 虽然也在覆盖各家服务商,但对国内模型的跟进速度和深度,确实不如对 OpenAI 那套生态来得积极。DashScope 这类国内服务商,只要是在 Spring AI 官方适配列表里,基本能用,可一些国产模型特有的能力、最新的模型版本接入,还是要有人专门去跟进。
Spring AI Alibaba 就是在这种背景下出现的。它不是一个独立于 Spring AI 之外的框架,而是站在 Spring AI 的肩膀上,把 DashScope 相关的适配、通义系列模型的接入、以及阿里云侧的实践沉淀成一套 starter。
标题里其实已经把这层关系说透了:它是在 Spring AI 之上加东西,不是另起炉灶。你学过的 Spring AI 概念,比如 ChatClient、PromptTemplate、Tool Calling、ChatMemory,在 Spring AI Alibaba 里全部保留,只是底层的模型实现换成了通义家族。
1.3 这篇文章适合谁看
适合三类人。第一类,已经在用 Spring AI 接 OpenAI,但想增加国产模型作为备选;第二类,团队技术栈是 Spring Boot,模型选型偏向通义系列,想知道这套东西值不值得引入;第三类,刚开始调研,想快速建立分层认知,避免后续排查问题时方向混乱。
不是必要的前提,但如果了解过 Spring AI 的 ChatClient 和 Model 接口,读起来会顺畅很多。真不了解也没关系,下面每一层我都会用尽量直白的方式讲。
2. 它到底在 Spring AI 之上加了什么
2.1 最直观的增量:DashScope 模型全家桶的适配
先看最表面的一层。Spring AI Alibaba 提供了一套针对 DashScope 的模型实现,覆盖通义千问的 chat 模型、文本嵌入模型、图片生成模型、语音相关模型等。
拿聊天来说,Spring AI 的 ChatModel 接口有一个核心方法,接收一个包含了消息列表和模型参数的请求对象,返回模型的响应。Spring AI Alibaba 要做的事就很明确:写一个 DashScopeChatModel,把 Spring AI 定义好的这个请求翻译成 DashScope 的 HTTP 请求,再把响应翻译回 Spring AI 的通用返回结构。
这层翻译工作看起来不复杂,但实际上涉及不少细节:流式响应怎么映射成 Flux 数据流、tool call 的协议怎么对齐、多模态消息怎么塞进 Prompt、错误码怎么统一成 Spring AI 的异常。这些不处理干净,上层用得再舒服,一到真实场景还是会被底层细节折磨。
2.2 容易被忽略的增量:和 Spring AI 官方版本节奏同步
这是 Spring AI Alibaba 非常核心但又不太显眼的价值。Spring AI 自身的迭代速度很快,有些版本之间接口变化比较大。一个第三方适配库如果跟不上 Spring AI 的版本节奏,就会出现项目里 Spring Boot 升了级,AI starter 跟不上,最后卡在中间版本不敢动的情况。
Spring AI Alibaba 采取的方式是跟着 Spring AI 的主要版本发版,同时把它自家对 DashScope 的适配组织成单独的 starter。这样你 upgrade Spring AI 主版本的时候,可以一起把 Spring AI Alibaba 升上去,不需要自己去搞清楚这两个库之间哪些接口对齐了。
不要小看这一点。国内团队在引入这类框架时,最大的成本往往不是功能缺失,而是不同库之间的版本兼容矩阵。Spring AI Alibaba 把这层兼容问题收敛进了它自己的发版策略里,对大部分使用者来说,省掉了很多精力和踩坑时间。
2.3 真正的增量:面向生产使用的配套整合
如果只是模型适配,那它和 Spring AI 官方的 dashscope starter 差别不大。Spring AI Alibaba 真正多出来的,是一套更贴近国内生产环境的整合经验。
比如 Developer Guide 里会明确告诉你怎么配置 DashScope 的 api-key、怎么选择适合业务场景的模型规格、怎么在 Spring AI 的体系里使用阿里云侧部署的模型服务。再比如它把一些与 DashScope 云端能力对齐的配置项、模型参数做了更完整的映射,让使用者在 Spring AI 的统一接口下能尽量用到云端模型的新特性。
还有一个点可能不太起眼,但对不少团队很实际:文档和示例代码的中文友好度。排查问题的时候,参考资料的可得性是很影响效率的。Spring AI Alibaba 在这方面的资料更贴近中文开发者的习惯,遇到问题更容易找到对应的解法。
我把三者的差别整理成了一张表,方便快速对照:
| 对比项 | 直接调用 DashScope SDK | Spring AI 官方 dashscope 适配 | Spring AI Alibaba |
|---|---|---|---|
| 模型抽象层 | 无,直接面向 SDK | 有,Spring AI 统一接口 | 有,Spring AI 统一接口 |
| 换模型成本 | 高,需重写调用代码 | 低,换 starter 和配置 | 低,换 starter 和配置 |
| Spring 生态整合度 | 低,需自己管理生命周期 | 中,自动配置可用 | 高,适配和配套文档更完整 |
| 对通义模型新能力跟进 | 最快 | 依官方迭代节奏 | 相对及时 |
| 版本兼容风险 | 低 | 中,需自行对齐版本 | 较低,社区统一发版 |
2.4 注意边界:它不是“万能模型网关”
有一点容易误解。Spring AI Alibaba 不是模型网关,不做请求路由、限流、多模型负载均衡这类事情。它关注的是把 Spring AI 的抽象和国产模型的适配打通。
如果项目需要统一网关能力,那是另一层组件要解决的。Spring AI Alibaba 管的是你的应用进程里,从业务代码到模型服务这一段怎么顺滑地走通。
3. 分层架构拆解:一次聊天请求怎么穿五层
3.1 先看整体分层图
用文字描述一下这个分层结构,方便后面展开。从上到下大概是这样的:
Application(Controller / Service) │ ▼ Spring AI 抽象层(ChatClient / ChatModel / Advisor / Memory) │ ▼ Spring AI Alibaba 适配层(DashScopeChatModel / DashScopeApi) │ ▼ HTTP 协议层(与 DashScope 服务通信) │ ▼ 集群侧模型服务(通义系列模型)别被这么多层吓到。真实代码写起来,你接触最多的只有两层:最上面的 Controller/Service,和中间的 ChatClient。其他层大多数时候是框架自己在跑。
不过理解中间那几层非常重要,因为排查问题时,你能看出来“这一个报错到底发生在哪个环节”。
3.2 从 Application 到 DashScopeChatModel:请求是怎么流转的
用户请求会先到 Controller,然后进 Service,最终到达 ChatClient。ChatClient 是 Spring AI 给应用层提供的高阶客户端,它对下层做了很多便利性封装,比如管理 Prompt 模板、记录上下文消息、调用结构化输出解析等。
ChatClient 内部调用的核心对象是 ChatModel。在 Spring AI Alibaba 里,这个实现在大多数场景下就是 DashScopeChatModel。
这一步是关键。DashScopeChatModel 接收到 Spring AI 的标准请求后,会把它拆解成 DashScope 认识的输入结构。Prompt 里的一系列消息变成 DashScope 要求的 role/content 格式,ChatOptions 里的 model、temperature、maxTokens 等参数被映射到对应的请求字段,流式标记被设置成对应的开关。然后请求交给 DashScopeApi。
DashScopeApi 是更底层的一个组件,负责真正的网络通信。它处理的事情包括:生成带鉴权信息的请求头、拼接请求地址、发起同步或流式调用、解析响应、把云端返回的错误码转换成统一异常。可以说 DashScopeChatModel 负责“翻译”,DashScopeApi 负责“跑腿”。
这套设计和 Spring AI 官方对 OpenAI 的实现方式是对齐的,所以如果你之前看过 OpenAI 的适配源码,会发现整个思路非常接近,只是对接的协议不同。
3.3 Advisor 和 Memory 是横切进请求链路里的
业务代码和模型服务之间,Spring AI 还留了一个横向扩展点,叫 Advisor。这个机制很像 Servlet 里的 Filter,允许在请求给到模型之前做一些通用处理,也允许响应返回给应用之前做一些后处理。
这个位置能干很多事。最常用的是会话记忆:把历史消息自动拼到下一次请求里。Spring AI 的 ChatMemory 就可以在这个环节工作。另外还有日志审计、敏感内容过滤、调用链追踪,都是通过 Advisor 挂上去的。
结合 Spring AI Alibaba 用的时候,不需要自己写 Advisors 来管理上下文,可以直接用现成的内存窗口方案,也可以自定义一个 Advisor 做专属逻辑。这层设计让“通用能力”和“业务能力”分得很开,业务代码不会被这些横切逻辑污染。
3.4 流式与非流式的数据差异
分层架构里还需要注意一组数据差异:同步返回和流式返回,走的不是同一条返回路径。
同步调用是发起 HTTP 请求后,一直等到完整响应返回,然后解析成标准响应对象。流式调用则是建立事件流连接,模型每生成一段文本,就通过 Flux 推送一段内容给上层。
这直接影响 Controller 层的返回类型。用 ChatClient 的 call() 方法拿到的是一整个文本;想用流式效果,得用 stream() 方法,返回 Flux 。Controller 里用 SSE 的方式把 Flux 源源不断推给前端。
换到 Spring AI Alibaba 也是一样的逻辑,只是流式协议转换发生在 DashScopeChatModel 内部。这里提醒一句:如果前端期待打字机效果,后端却用了 call(),等所有字都生成完才返回,体验会差很多。
4. 跑通一个最小能聊天的项目
4.1 Maven 依赖和基础配置
理论讲得再多,不如跑一个最小项目来得直接。这里我以一个 Spring Boot 3.x 项目为例,先加 maven 依赖。
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> </dependency>版本这里要提一下。Spring AI Alibaba 的 groupId 和具体版本号,建议直接参考官方当前发版情况来选,不同阶段推出的 starter 坐标会有差异。重点是 pom 里要引入 Spring AI 的 BOM,这样 AI 相关依赖的版本可以由 BOM 统一管理,减少版本冲突。
然后配置 application.yml:
spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY:} chat: options: model: qwen-plus temperature: 0.7api-key 建议通过环境变量注入,不要硬编码到配置文件里。model 可以根据业务需求换成 qwen-turbo 或 qwen-max。qwen-turbo 便宜速度快,qwen-max 质量更好,但延迟和成本都会上去。
4.2 用 ChatClient 写一个聊天接口
依赖和配置就位后,已经可以在 Spring 容器里使用 ChatClient 了。先建一个 Controller:
@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder .defaultAdvisors(MessageWindowChatMemory.builder().maxMessages(20).build()) .build(); } @GetMapping("/chat") public String chat(@RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }这里用到了一个默认 Advisor,也就是固定窗口大小的消息记忆,最多保留 20 条历史消息。这样多轮对话时,上下文会自动带上最近几次的历史,而不是每次都是全新的会话。
加记忆之后要留意 token 消耗。ChatMemory 不是免费的,每一轮都会把历史消息重新送给模型,窗口设得越大,单次请求开销越高。正常情况下 maxMessages 保持在 20 到 50 之间是可控的,但如果你业务里单条消息体量很大,需要根据实际情况调整。
接下来验证流式效果可以加一个接口:
@GetMapping(value = "/chat/stream", produces = "text/event-stream") public Flux<String> chatStream(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }前端用 fetch 或 EventSource 就可以收到流式返回。如果用的 WebFlux,返回 Flux 配合 SSE 很自然;如果用 Spring MVC,需要确认你的 Spring Boot 版本支持这种返回方式。
4.3 结构化输出和函数调用也能直接玩
Spring AI 一个很受欢迎的能力是结构化输出。把模型返回的文本自动绑定到 Java 对象上,省去自己写 JSON 解析逻辑。用起来是这样:
record Summary(List<String> keyPoints) {} Summary summary = chatClient.prompt() .user("用三条要点总结下面这段话:...") .call() .entity(Summary.class);entity() 方法拿到模型返回后,会经过一个输出解析器,尝试把文本解析成目标类型。这个能力依赖底层模型对 JSON 输出的配合。对通义系列模型,建议在 ChatOptions 里加上 responseFormat 相关的设置,让模型尽量以 JSON 格式返回,否则解析器的失败率会偏高。
函数调用体验也有点像写本地方法:
@Bean public Function<WeatherRequest, WeatherResponse> currentWeather() { return request -> getWeatherData(request.city()); }然后在构建 ChatClient 时注册这个函数:
ChatClient chatClient = builder .defaultFunctions("currentWeather") .build();模型在回答涉及天气的问题时,会根据工具描述判断需不需要调用这个方法。整个过程对调用方来说是透明的,核心逻辑还是写在 Java 里。
对于 Spring AI Alibaba,这些用法和标准 Spring AI 保持一致。也就是说,你已有的 Spring AI 代码,迁移到它上面时,改动主要发生在依赖和配置层面,业务代码基本可以不碰。
5. 切换和升级中,我踩过的值得记录的坑
如果项目规模不小,切换框架最痛苦的不是写新代码,而是升级和兼容问题。下面这些坑是我实际遇到过的,有些排查起来还挺费劲。
5.1 版本三件套不对齐,启动就报错
Spring Boot、Spring AI、Spring AI Alibaba 三者是有版本对应关系的。Spring AI Alibaba 在发版时会适配某个 Spring AI 版本,而这套东西又会要求某个 Spring Boot 版本。
我遇到最典型的问题:项目里 Spring Boot 用的比较新,Spring AI 又用了比较旧的版本,Spring AI Alibaba 再引进来,启动时直接报 Bean 创建失败,错误信息指向一个内部类的构造函数缺少参数。完全没想到是版本不匹配。
现在我的习惯是,新项目直接用一个已经确认兼容的版本组合,不要去猜。具体组合可以参考 Spring AI Alibaba 官方 Release Notes 里列的版本对应关系。老项目升级时,先把 Spring AI 升到目标版本,再引入 Spring AI Alibaba,不要一股脑全换。
另外要小心同一个模型的能力被重复引入。如果项目的依赖里既有 Spring AI 官方的 dashscope starter,又有 Spring AI Alibaba 的 starter,两者可能都想创建相同的 Bean,出现冲突的概率非常高。二选一,别共存。
5.2 想确认模型实际参数,别只看配置文件的默认值
还有一个典型误区:以为配置文件的 model 设置一定生效。实际上,代码里创建 Prompt 时可以用 ChatOptions 覆盖默认配置。
比如:
ChatOptions options = ChatOptions.builder() .model("qwen-max") .temperature(0.1) .build();这样即使配置文件写的是 qwen-plus,实际请求也会走到 qwen-max。多环境配置或者团队协作时,最怕有人在一个不起眼的地方写了硬编码 options,然后大家为了一个“为什么明明配置改了模型没变”的问题排查半天。
我的排查思路是:先看请求里实际带出去的参数,再看配置。你可以临时加一个 Advisors 打印请求报文,或者直接看 DashScope 控制台的调用日志,确认模型名、temperature 这些参数到底是不是你以为的那样。
5.3 流式接口封装不当,打字机效果全没
流式返回最大的坑是把 Flux 转成了 List 再返回。我看到过这样的代码:
List<String> blocks = chatClient.prompt() .user(message) .stream() .content() .collectList() .block(); return blocks;这样写确实拿到了完整结果,但前端收到的就是一次性返回,流式体验完全消失。更糟糕的是,在某些版本里,对 Flux 做 block() 操作可能会在多线程环境下遇到问题。
正确做法是保持响应式链路完整:
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatStream(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }Controller 层的返回类型必须是 Flux,不要中间收集成阻塞类型。日志、监控需要拿完整文本时,可以在 Flux 的 doOnNext 里追加到一个 StringBuilder,或者使用 doOnComplete 做统一处理,不要让日志记录逻辑破坏下游的实时推送。
5.4 ChatMemory 无限增长,上下文越聊越贵
Session 记忆不设上限,这个问题很隐蔽。我见过一个内部演示项目,只加了一个简单的 ChatMemory,每天大家在里面测试各种对话,消息越积越多。到了一定程度,每次请求携带的历史内容非常长,token 消耗和延迟都明显上升。
如果你的场景就是固定窗口记忆,我前面代码里给的 MessageWindowChatMemory 是比较稳妥的选择。想在文档级别做摘要压缩,就要重写相关的 Memory/Advisor 逻辑,把更早的内容摘要后替换掉原文。
有一点还是要在生产环境认真对待:模型商城的 token 费用是实打实的。上下文管理不只是功能需求,还直接影响成本。不要等账单出来再优化。
5.5 遇到 API 调用超时的第一反应别是加大超时时间
遇到网络超时,很多人的第一反应是把超时时间调大。这个方案治标不治本。
常见原因包括:选了超大模型同时参数又复杂,模型推理本身耗时过长;或者是流式场景下,客户端连接断开导致请求还没结束就中断;又或者是网络本身的问题,比如公司网管策略拦截了特定端口或域名。
排查的顺序应该是:先看 DashScope 侧模型返回耗时,再看网络链路,最后再调整超时参数。不要在根因没定位前,盲目把 timeout 从 30 秒调到 120 秒,那样只会让故障更难暴露。
把常见的坑整理成表格,方便参照:
| 表现 | 根因方向 | 处理建议 |
|---|---|---|
| 启动报 Bean 创建失败 | 版本组合不匹配 | 按官方版本对应关系统一升级 |
| 配置的模型没生效 | 代码里 ChatOptions 覆盖默认值 | 打印请求报文确认实际参数 |
| 流式接口无流式效果 | 把 Flux 收集成 List | Controller 直接返回 Flux |
| 上下文越聊越贵 | 记忆容量无限增长 | 使用固定窗口或摘要策略 |
| 调用经常超时 | 网络或模型推理耗时 | 先定位链路时段再调超时 |
6. 哪些情况适合选它,哪些情况别硬上
6.1 它解决的核心场景
如果你的团队已经深度使用 Spring Boot,业务代码都是围绕 Spring 生态组织的,那引入 Spring AI Alibaba 的成本非常低。你原有的面向 ChatClient 编程的思维,在这里可以继续用。
模型选型是通义系列的话,更值得用。Spring AI 官方对 OpenAI 支持得最好,这是事实。但国内模型在合规、部署位置、成本和特定场景能力上有自己的优势。企业做技术选型时,不是每个人都能随意使用境外模型服务,Spring AI Alibaba 这种和国内模型绑定的适配层在这种场景下就有明显价值。
还有一类团队也适合:想保持模型可替换性,但目前主力是通义系列,同时又希望未来能轻松切到其他模型。这种需求用 Spring AI 抽象层是我们已经反复提过的解法,Spring AI Alibaba 则是把通义这条路铺得更平。
6.2 不适合硬上的情况
如果项目只需要调用一个模型,并且可以确定未来不会换,那么引入框架是会带来额外成本的。学习成本加上依赖复杂度,可能比直接用 DashScope SDK 更重。追求极简的项目完全可以直接调用 SDK。
如果你已经是 LangChain4j 的深度用户,团队积累了大量基于它的封装,那也没必要为了“统一”而引入两套框架。无论选哪个生态,团队积累的一致性远比哪个功能多一点更重要。
如果你的场景已经超出了模型的聊天、补全、嵌入这些通用能力,比如要做复杂的智能体编排调度,有专门的工作流引擎在跑任务,那么 Spring AI Alibaba 不解决这些编排问题,或者说它只覆盖了其中比较基础的一部分场景。
6.3 三者的选型对比
结合团队背景来做最后的判断,我这里再来一个更直接的对比:
| 决策维度 | 直接调用 DashScope SDK | LangChain4j + DashScope | Spring AI + Spring AI Alibaba |
|---|---|---|---|
| 团队成员背景 | 偏通用 Java,不介意手写调用链 | 已有 LangChain4j 使用经验 | 深度使用 Spring 全家桶 |
| 换模型概率 | 低,模型已锁定 | 中,抽象支持多模型 | 中,抽象支持多模型 |
| 部署方式 | 普通 Spring Boot 服务 | 普通 Java 服务 | 与 Spring 生态无缝整合 |
| 对 RAG、Agent 需求 | 需自行实现 | 框架内置组件多 | 部分需自己组装 |
| 学习成本 | 最低 | 中等 | 中等偏高,但延续 Spring 心智 |
最后说点个人感受。用过之后,我最大的体会是 Spring AI Alibaba 并没有带来多么颠覆式的 API 设计,它的价值是消解掉了“Spring 技术栈去接国产模型”过程中那个最模糊的灰色地带。你在 Spring AI 官方文档那里学到的思维方式,到这里并不会被推翻,反而是多了一层国产生态可用的底气。
如果真要在这类组件上做投入,建议不要只停留在能力演示阶段。把记忆窗口、函数调用、结构化输出、流式链路这几个基础机制逐一在你的业务场景里验证一遍,比反复纠结用哪个框架更务实。框架本身解决的是接入问题,真正的业务价值,永远在于你用这些能力构建出来的产品逻辑。