ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring AI Alibaba分层的核心能力与工程实践解析

Spring AI Alibaba分层的核心能力与工程实践解析

1. 项目概述:Spring AI Alibaba 到底解决什么问题

先说结论:Spring AI Alibaba 不是一个独立的新框架,它是站在 Spring AI 这层“抽象地基”之上,把阿里系的一套大模型应用基础设施给“接”了进来。如果你已经接触过 Spring AI,知道它有ChatClient、ChatModel、EmbeddingModel这套统一抽象,那 Spring AI Alibaba 要做的就是让你在写业务代码时,不用关心底层走的是通义千问还是 DashScope 兼容接口,也不用自己造轮子去处理对话历史、缓存、提示词模板这些琐碎事。

我在实际项目里最直观的感受是:Spring AI 本身已经帮你把“对接大模型”这件事从“HTTP 调用 + JSON 解析”提升到了“面向接口编程”,但真正落地产线时,你会发现还有很多事它没管。比如:

  • 流式输出的超时和重试策略,需要自己写;
  • 多轮对话的上下文管理,要自己维护;
  • 提示词模板的版本管理和动态参数注入,基本靠拼接字符串;
  • 模型切换时,业务代码里散落着不同 SDK 的调用痕迹。

Spring AI Alibaba 就是在这些“坑”上面做了一层补齐。它的定位不是替代 Spring AI,而是让 Spring AI 在阿里云生态里更好用。你可以把它理解为:Spring AI 是“标准插座”,Spring AI Alibaba 是“国标转接头”,它没有改变电本身,但让你手里的设备能更稳定地插上去。

这篇文章我先带你从整体概览和分层架构入手,搞清楚它到底在 Spring AI 之上加了哪几层东西。后续我会逐步拆解每个模块的用法和源码实现,这篇算是整个系列的地基。

2. 设计思路:为什么选择在 Spring AI 之上做扩展,而不是另起炉灶

2.1 三个痛点逼出来的选择

我在做 AI 应用落地的过程中,被三个问题反复折磨过。

第一个是模型厂商锁定。今天用 A 厂的模型跑通了原型,明天老板说换个成本更低的模型,结果发现代码里到处是 A 厂 SDK 的痕迹,改起来痛不欲生。Spring AI 的核心价值恰恰是抽象了ChatModel接口,理论上换模型只改配置。但实际用下来,不同模型的参数差异(比如温度、top_p、stop 序列)还是会渗透到业务代码里,因为 Spring AI 的ChatOptions是各家模型实现各自定义的。

第二个是AI 能力和业务系统的割裂。业务系统里有用户体系、权限体系、订单状态,AI 能力如果要融入这些业务逻辑,不能只是“调一个接口返回文本”。你需要让模型能读到你数据库里的数据、能调用你们内部的服务、能理解你们业务里的术语。Spring AI 提供了Tool Calling的基础支持,但要做好,还需要跟 Spring 的 Bean 体系深度整合。

第三个是工程化能力缺失。开发一个 AI 接口容易,上线一个 AI 接口难。你需要考虑:提示词被改坏了怎么办?用户多轮对话的上下文怎么存?模型接口超时了是重试还是降级?这些“工程问题”恰恰是 Spring AI 作为“基础抽象层”不愿意做的,也是各个云厂商最擅长做的。

2.2 分层架构的核心理念:像搭积木一样加能力

Spring AI Alibaba 的分层架构,本质上是把“AI 应用开发”这件事拆成了几个关注点。从上往下看:

  • 业务接入层:你写业务代码的地方,接触的是ChatClient、ChatModel这些高层 API,不关心底层协议。
  • 模型适配层:Spring AI Alibaba 通过DashScopeChatModel等实现类,把阿里云的通义千问、百炼平台的各种模型包装成 Spring AI 的统一接口。同时支持通过spring.ai.model.chat这类配置快速切换。
  • 增强能力层:这是它“加了什么”的重点。比如提示词模板管理、对话记忆、输出解析器、多工具调用编排。
  • 基础设施层:连接阿里云的服务,比如 DashScope API、阿里云日志服务、负载均衡等。

这种分层设计的好处是,每一层只解决一类问题,你可以在不破坏其他层的情况下替换某层的实现。比如你今天用通义千问,明天想换一个开源模型部署在本地,只需要在模型适配层加一个实现,上层的业务代码几乎不用动。

注意,这里说的“分层”不是代码里的包结构强制分层,而是一种逻辑边界。你在实际工程中不一定非得建四个包,但心里要有这根弦:业务代码不要直接依赖某个模型 SDK 的类,否则分层就白做了。

2.3 与 Spring AI 官方扩展点的关系

Spring AI 官方本身也留了一些扩展点,比如Advisor(顾问链,用于在调用模型前后做增强)、ChatClient.Builder的定制。Spring AI Alibaba 的核心策略是尽量复用这些官方扩展点,而不是绕过去自己搞一套。

举个例子,Spring AI 的Advisor机制允许你在调用大模型之前插入“消息处理”,比如把用户消息加上系统提示词、把历史对话塞进上下文。Spring AI Alibaba 里面的很多功能(比如通义千问的 prompt 优化、记忆管理)其实都是通过实现自定义Advisor来完成的。这样做的好处显而易见:如果你已经熟悉 Spring AI 的原生写法,迁移到 Spring AI Alibaba 的学习成本很低;如果你不想用某个增强功能,直接不在ChatClient构建时添加对应的Advisor即可,完全是可选开关。

这个“站在扩展点上做事”的思路,比那种“自己重新定义一套 API”的做法高明很多。前者是渐进增强,后者是另起炉灶。渐进增强意味着你可以逐步引入,另起炉灶则意味着团队要重新学习一套东西。

3. 核心模块拆解:它具体加了哪几层东西

3.1 模型接入层:DashScope 与通义千问的封装

Spring AI Alibaba 最基础的能力,是把阿里云百炼平台(DashScope)上面的模型统一封装成 Spring AI 的ChatModel和EmbeddingModel。目前主流的模型包括:

  • 通义千问系列(qwen-plus、qwen-turbo、qwen-max)
  • 通义千问 VL 视觉模型(qwen-vl-plus)
  • 通义千问开源模型的托管版本

配置上非常简单,在application.yml里加上:

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.7

这段配置背后,Spring AI Alibaba 自动帮你完成了几件事:

  1. 根据配置构建DashScopeChatModel实例;
  2. 初始化一个用于调用 DashScope HTTP API 的WebClient,内置连接池和超时参数;
  3. 将ChatModel注入 Spring 容器,让你可以直接在 Service 里依赖注入。

用起来和 Spring AI 官方的方式几乎一致:

@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @PostMapping("/chat") public String chat(@RequestBody String prompt) { return chatClient.prompt() .user(prompt) .call() .content(); } }

不过有一点需要留意:DashScope 的api-key不要硬编码在配置文件里,尤其是多人协作的代码库,建议通过环境变量注入。我在项目里还踩过一个坑:spring.ai.dashscope.api-key和某些旧版本的spring.cloud.alibaba.ai配置前缀不兼容,导致启动时一直报“未找到 api-key”。后来统一按 Spring AI Alibaba 自己的 starter 要求,改成spring.ai.dashscope.api-key就正常了。

3.2 提示词模板与输出解析:把“调模型”变成“填表单”

如果你只在业务代码里写chatClient.prompt().user("请帮我总结..."),那确实不需要多少模板能力。但真实项目里的 prompt 往往很长,包含系统设定、示例、动态变量。Spring AI Alibaba 在这块提供了基于 Spring 的MessageTemplate机制,你可以把 prompt 写成资源文件,然后动态填充变量。

比如在src/main/resources/prompt-templates/下建一个summary.st文件:

你是 {role},请根据以下文本生成摘要,要求不超过 {maxWords} 字。 文本内容: {content}

然后在代码里这样用:

PromptTemplate promptTemplate = new PromptTemplate("classpath:/prompt-templates/summary.st"); Prompt prompt = promptTemplate.create(Map.of( "role", "资深编辑", "maxWords", 200, "content", articleText )); String summary = chatClient.prompt(prompt).call().content();

输出解析器方面,Spring AI Alibaba 支持把模型输出直接映射成 POJO,省去自己解析 JSON 的麻烦。你只需要定义好目标结构:

public record ArticleSummary(String title, String summary, List<String> keywords) {}

然后在调用时传入BeanOutputConverter:

BeanOutputConverter<ArticleSummary> converter = new BeanOutputConverter<>(ArticleSummary.class); String json = chatClient.prompt() .user("请生成摘要") .call() .content(); ArticleSummary result = converter.convert(json);

这里有一个非常实用的经验:实际使用中,模型输出 JSON 并不总是合法 JSON。尤其是要求它输出包含中文长文本的字段时,偶尔会夹带 ```json 标记或者末尾多一个逗号。所以我建议在 converter 转换之前加一层“小清洗”,比如去掉首尾的代码块标记,再交给 Jackson 解析。Spring AI Alibaba 在部分版本里内置了清洗逻辑,但不要完全依赖它。

3.3 记忆与上下文管理:多轮对话的工程化手段

单轮问答只是 demo,多轮对话才是真实业务。Spring AI 原生的方式是调用时手动带上历史消息:

List<Message> messages = List.of( new SystemMessage("你是客服助手"), new UserMessage("我买了东西,什么时候发货?"), new AssistantMessage("您的订单预计 48 小时内发货。"), new UserMessage("那怎么退换货?") );

问题在于,谁来维护这个messages列表?如果是单体应用,内存里放一个Map<sessionId, List<Message>>勉强能跑。但一旦实例多开、服务重启、或者要支持分布式部署,内存方案立刻穿帮。

Spring AI Alibaba 在这块提供了两个实用武器:

  • ChatMemory抽象:定义消息存储的接口,可以接入内存实现、Redis 实现,甚至数据库实现。
  • MessageWindowChatMemory默认实现:按窗口大小保留最近的 N 条消息,防止 context 无限膨胀。

具体用法如下:

ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(20) .build(); ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, "session-id", 20)) .build();

这里面的MessageChatMemoryAdvisor是关键。它会在每次请求时,把当前 session 的历史消息自动加载进来,放到消息列表的最前面;等模型返回后,再把这次的 UserMessage 和 AssistantMessage 保存回去。整个过程中,你的业务代码不需要手动维护消息列表。

我在实际项目中用的方案是RedisChatMemory,把 session 的历史记录序列化成 JSON 存到 Redis,key 是ai:chat:memory:{sessionId}。这样做的好处是,应用重启不丢上下文,多副本部署也能共享同一份记忆。但要注意:Redis 里的 JSON 最好带版本号字段,因为消息结构升级时,旧数据反序列化容易报错。

3.4 工具调用(Function Calling):让模型能操作你的业务系统

如果说前面几点只是“花架子”,那工具调用就是让大模型从“聊天机器人”进化为“业务助手”的关键。Spring AI Alibaba 的 Tool Calling 能力和 Spring AI 官方基本一致,但有两个增强点:

  • 对 DashScope 的工具调用协议做了适配;
  • 支持把 Spring 容器里的 Bean 方法直接暴露为工具,不需要额外封装。

最简单的使用方式,是给某个 Service 的方法加上注解:

@Component public class OrderService { @Tool(description = "根据订单ID查询订单状态") public String getOrderStatus(String orderId) { // 查数据库,返回状态 return switch (orderServiceDao.queryStatus(orderId)) { case "PENDING" -> "待支付"; case "PAID" -> "已支付"; case "SHIPPED" -> "已发货"; default -> "未知状态"; }; } }

然后在构建ChatClient时把这个 Bean 传进去:

ChatClient chatClient = ChatClient.builder(chatModel) .defaultTools(new OrderService()) .build(); String reply = chatClient.prompt() .user("帮我查一下订单 A123 的状态") .call() .content();

模型会先识别出需要调用getOrderStatus工具,生成参数{"orderId": "A123"},然后框架帮你调用这个方法,把返回结果再喂给模型,最终模型生成自然语言回复。整个链路中,业务侧只写了一个普通方法,剩下的交给框架。

这里面的一个关键提醒:工具调用的“所见即所得”是有代价的。模型可能产生不存在的 orderId,也可能在你没有充分授权的情况下调用不该调用的工具。所以在暴露工具之前,一定要加权限校验。我习惯的做法是在@Tool方法内先做参数合法性和当前用户权限的校验,宁可抛出异常,也不能让模型乱调。

3.5 多云模型切换与路由:为“换模型不换代码”留好开关

项目做大了以后,你不会只想用一家模型。可能主模型用 qwen-max,备链路用 qwen-turbo 或者别的开源模型。Spring AI Alibaba 提供了比较灵活的路由配置,最简单的方式是通过多组ChatModelBean 的定义,配合@Primary和@Qualifier选择。

@Configuration public class ChatModelConfig { @Bean @Primary public ChatModel qwenMaxChatModel() { return new DashScopeChatModel.Builder() .model("qwen-max") .temperature(0.7) .build(); } @Bean public ChatModel qwenTurboChatModel() { return new DashScopeChatModel.Builder() .model("qwen-turbo") .temperature(0.3) .build(); } }

使用时通过@Qualifier注入:

@Service public class RecommendService { private final ChatModel fastModel; public RecommendService(@Qualifier("qwenTurboChatModel") ChatModel fastModel) { this.fastModel = fastModel; } public String recommend(String userId) { return fastModel.call( new Prompt("根据用户偏好推荐商品") ).getResult().getOutput().getText(); } }

更进一步,你可以自己写一个“路由ChatModel”,内部根据业务类型、用户级别、成本预算等条件,把请求转发到不同的底层模型。这种做法的好处是,上层业务完全不知道模型切换的存在,你只是在配置中心改一段路由规则而已。

4. 分层架构的完整视角:一张表看懂各层职责

如果你埋头写代码,容易陷入“哪个类放在哪个包”的细节里。我建议你从一个更高的视角来看 Spring AI Alibaba 在 Spring AI 之上加了什么。我把各层对应的核心组件和职责整理成一张表:

层次对应组件职责说明和原生 Spring AI 的关系
业务接入层ChatClient、ChatModelAPI提供给人写业务代码的统一入口直接复用原生 API
模型适配层DashScopeChatModel、DashScopeEmbeddingModel实现 DashScope 协议的模型适配实现了 Spring AI 的 SPI 接口
增强能力层ChatMemory、Advisor、OutputConverter、PromptTemplate提供记忆、模板、解析、工具编排等增强大多基于原生扩展点实现
连接与运维层阿里云 DashScope API、日志、监控负责与大模型服务的物理通信和可观测性相当于 Spring AI 的“transport”
配置与装配层spring.ai.dashscope.*自动配置类根据配置自动创建 Bean,降低使用成本相当于 Spring Boot 自动装配的角色

通过这张表你可以看到,Spring AI Alibaba 并没有动 Spring AI 的“骨架”,而是在“血肉”层面做了大量填充。这带来的好处是:你对 Spring AI 原生知识的所有积累,在这里都还能用上。

反过来说,这也意味着你踩过 Spring AI 原生的一些坑,在这里大概率还会踩。比如ChatClient的流式输出,即使有 Spring AI Alibaba 封装,你依然要自己处理Flux的背压和异常恢复。封装只是帮你省了“对接模型服务端”的功夫,但“应用层容错”这件事,框架很难替你包办。

5. 实操验证:从零搭建一个 Spring AI Alibaba 的最小工程

光讲架构是虚的,我带你把最小工程跑起来。这个过程我实测过,按照下面的步骤走,大概十分钟内能看到效果。

5.1 初始化工程与依赖引入

推荐直接用 Spring Initializr 创建工程,也可以手动建 Maven 项目。这里以 Maven 为例,关键依赖如下:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.x</version> </parent> <dependencies> <!-- Spring AI Alibaba 的 starter --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M3</version> </dependency> <!-- Spring Web 用来写接口测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

注意版本号。Spring AI Alibaba 的版本迭代比较快,有些早期版本对应的 Spring AI 版本较老。建议去 Maven 中央仓库看最新版本,我写的1.0.0-M3只是我当时用的版本。版本选错最常见的问题就是出现NoClassDefFoundError或 Bean 装配失败,因为这货对 Spring Boot 版本有强绑定。

5.2 配置与启动

配置文件里把 api-key 配上(建议环境变量):

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY:} chat: options: model: qwen-plus

写一个简单的测试接口:

@RestController @RequestMapping("/ai") public class AiDemoController { private final ChatClient chatClient; public AiDemoController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @PostMapping("/chat") public String chat(@RequestBody Map<String, String> req) { return chatClient.prompt() .user(req.getOrDefault("message", "你好")) .call() .content(); } }

启动应用后,用 curl 测试:

curl -X POST http://localhost:8080/ai/chat \ -H "Content-Type: application/json" \ -d '{"message": "用一句话介绍你自己"}'

如果一切正常,你会收到模型返回的自我介绍。这个最小工程虽然简单,但它已经跑通了“业务接入 -> 模型适配 -> DashScope 服务”的完整链路,后面的增强能力都是在这个基础上叠加的。

5.3 加一个记忆能力的升级步骤

上面的 demo 是“无状态”的,每次请求都不会带上下文。如果你想体验 Spring AI Alibaba 在增强层面的能力,可以简单加一个 Advisor:

ChatMemory chatMemory = MessageWindowChatMemory.builder().maxMessages(10).build(); ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, "default-session", 10)) .build();

然后连续问两个问题,第一个说“我的名字叫张三”,第二个问“我叫什么名字”,模型能准确回答。这个实验能够直观体现它加的“上下文管理”这一层到底有什么价值。

6. 常见问题与排查技巧实录

6.1 启动时报“无法加载 DashScope ApiKey”

这个报错通常不是代码问题,而是配置问题。排查顺序:

  1. 确认环境变量DASHSCOPE_API_KEY是否真的设置了,echo $DASHSCOPE_API_KEY;
  2. 确认配置前缀是否正确,应该是spring.ai.dashscope.api-key;
  3. 确认你用的 starter 版本里读取配置的类是否改过前缀。曾经出现过新版本把spring.cloud.alibaba.ai.dashscope.api-key也兼容的情况,但如果你依赖的是标准 starter,前缀就是spring.ai开头。

6.2 模型返回的内容偶尔带有奇怪的换行或 markdown 标记

这是大模型输出本身的不确定性导致的,不是框架 bug。如果你的业务对输出格式有严格要求,建议在 OutputConverter 之前加一个清洗函数。一个简单的做法:

String cleaned = rawContent.replaceAll("```json\\n?", "") .replaceAll("\\n```", "") .trim();

6.3 多轮对话时上下文被截断

MessageWindowChatMemory是按窗口大小截断的,如果你设置maxMessages=10,它只会保留最近 5 轮完整对话(每轮包含用户消息和助手消息)。当你需要更长的上下文时,要么调大窗口,要么换用支持持久化存储的ChatMemory实现。但注意,上下文越长,token 消耗越大、响应越慢,需要做一个权衡。

6.4 工具调用时,模型传入的参数和预期不一致

模型生成参数是有随机性的,偶尔会出现字段名对不上、或者类型不对的情况。我的经验是:在@Tool注解的描述里写清楚每个参数的格式要求,比如“orderId 是字符串类型,格式形如 A123”。描述越明确,模型传参的准确率越高。如果仍然经常出错,可以在方法内做一层“防御式解析”,不直接信任入参。

6.5 版本兼容性矩阵

我整理了一张常用的版本对应关系,供参考:

Spring AI Alibaba 版本适配 Spring AI 版本Spring Boot 版本
1.0.0-M11.0.0-M63.2.x
1.0.0-M21.0.0-M63.2.x
1.0.0-M31.0.0-M63.2.x
更新版本请查阅官方 release 说明通常适配 3.3+

版本选型建议:生产环境不要追最新,优先选已经发布至少一两个月的稳定小版本。如果你在公司里用,最好让运维或者架构组出一个“公司内部推荐版本”,避免每个项目组各用各的导致维护混乱。

7. 后续扩展方向与个人经验

写到这里,整个概览和分层的思路已经清楚了。Spring AI Alibaba 在 Spring AI 之上加的东西,总结成一句话:它把“对接大模型”这件事从“能用”提升到了“好用”,尤其是模型适配、记忆管理、工具调用、模板解析这几个方面,确实替开发者省了不少心。

但我个人在实际操作中的体会是,框架帮你省掉的是“重复劳动”,而不是“思考”。比如它提供了ChatMemory的多种实现,但你的业务是要求“按用户隔离记忆”还是“按会话隔离记忆”,这是业务设计问题;它提供了@Tool注解,但你的方法是否要暴露给模型,暴露后怎么鉴权和审计,这还是你自己的责任。所以在团队引入 Spring AI Alibaba 时,我建议先把场景想清楚:

  • 你需要的是单轮问答、多轮对话,还是带工具调用的复杂 Agent?
  • 你的上下文需要持久化吗?存 Redis 还是数据库?
  • 你允许模型调用哪些工具?如何控制风险?

这些问题的回答,决定了你会用到 Spring AI Alibaba 的哪几层能力。

最后再分享一个小技巧:不要只依赖框架内置的ChatClient.Builder默认装配,建议你在项目里自己封装一个ChatClientFactory,把默认系统提示词、默认记忆策略、默认输出解析都集中在工厂里做。这样你的业务代码会非常干净,而且后续框架升级时,你只需要改一个地方就能平滑切换。我目前就在项目里用这个模式,维护成本和踩坑概率都明显降下来了。

下一篇我会重点拆解ChatClient的完整调用链路,把你从“会调接口”带到“理解调用过程的每一步”,敬请关注。

返回列表