过去一年如果你在做 AI 应用开发,大概率经历过这样的纠结:调用大模型 API 本身并不难,难的是把模型能力嵌进现有业务系统时,要自己处理对话历史、工具调用、结构化输出、多轮上下文、模型切换等一系列工程问题。更麻烦的是,团队里不同项目各写一套对接逻辑,换个模型厂商就要改一遍代码。
Spring AI Alibaba 1.0.0 GA 的发布,正好踩在这个痛点上。它不仅是 Spring AI 官方标准在阿里云生态的落地实现,更关键的是把“AI 能力接入 Java 业务系统”这件事,从拼凑 HTTP 请求和 JSON 解析,变成了像写普通 Spring Boot 服务一样声明式、模块化的工作。这篇文章会用实际代码带你跑通对话补全、结构化输出、Function Calling 和 Agent 构建四个核心场景,并且说清楚 1.0.0 GA 版本和之前 0.9.x 版本的本质差异。
如果你正在做 Java 后端、微服务架构,或者需要在现有 Spring Boot 项目里接入大模型,这篇文章值得收藏。
1. Spring AI Alibaba 到底是什么,和直接调 API 有什么区别
很多人第一次看到 Spring AI Alibaba,会误以为它只是“封装了通义千问 SDK 的又一个工具包”。这种理解只对了一小部分。
Spring AI Alibaba 的真正价值,是它实现了一套与模型厂商无关的 AI 应用开发抽象层。它基于 Spring AI 的官方标准 API,把模型调用、Prompt 模板、结构化输出、工具调用(Function Calling)、Agent 编排这些能力,统一成了 Java 开发者熟悉的 Spring 风格。
对比一下就清楚了。
传统直接调用大模型 API 的流程是:
- 在前端或者业务层手动拼接 Prompt 字符串,包含 system 指令和 user 内容。
- 用 HTTP Client 构造 POST 请求,参数要按各家厂商的 JSON 格式来。
- 解析返回的 JSON,自己处理 choices、content、finish_reason 这些字段。
- 如果要实现多轮对话,还得自己维护历史消息列表,每次请求把整个上下文重新发给模型。
- 如果接入多个厂商,每个厂商的请求格式、认证方式、错误码都要单独适配。
这套流程短期能跑,但长期维护成本很高。尤其当你需要让模型调用业务方法、按固定结构返回数据、或者构建 Agent 流程时,代码会变得非常庞杂,各种样板代码堆在一起,真正的业务逻辑反而看不清楚。
Spring AI Alibaba 改变了这些环节:
@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/chat") public String chat(@RequestParam String message) { return this.chatClient.prompt() .user(message) .call() .content(); } }只需要注入ChatClient,调用.prompt().user().call(),对话补全就完成了。模型厂商的差异被封装在底层,你不需要关心 HTTP 请求怎么拼、JSON 怎么解析。如果需要从通义千问切换到其他兼容 OpenAI 协议的模型,多数场景下只需要调整配置,业务代码基本不用改。
这门技术真正降低的是AI 应用与业务系统的集成成本,而不是“调 API”本身的学习成本。如果只是写个 Python 脚本调用一下大模型,完全不用学 Spring AI Alibaba;但当你要在企业级 Java 项目里稳定、可维护、可测试地接入 AI 能力时,这套抽象层的价值就非常明显了。
2. 1.0.0 GA 版本和 0.9.x 版本的核心差异
我在网上看到不少文章还在写旧版本的用法,实际上 Spring AI Alibaba 1.0.0 GA 在依赖坐标、核心 API、配置方式上都有了明显变化。如果你照着 0.9.x 的教程写 1.0.0 的项目,大概率会在启动阶段就遇到类找不到、配置项不识别之类的问题。
2.1 依赖坐标变更
首先是 groupId 从com.alibaba.cloud.ai调整为com.alibaba.cloud.ai,这点没变,但 artifactId 和版本号管理变化很大。1.0.0 GA 版本对 Spring Boot 3.4.x / 3.5.x 提供了更好的支持,使用 BOM 统一管理依赖版本。
<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>2.2 依赖名称变化
旧版本中你可能见过spring-ai-alibaba-dashscope、spring-ai-alibaba-starter这样的依赖名。1.0.0 GA 里的编排更清晰了:
| 依赖 | 说明 |
|---|---|
spring-ai-alibaba-starter | 核心 Starter,通常只需引入这一个 |
spring-ai-alibaba-dashscope | 阿里云 DashScope 模型实现,包含通义千问系列 |
spring-ai-alibaba-graph | Graph 工作流编排能力 |
spring-ai-alibaba-server | 服务端相关能力 |
2.3 核心 API:从 ChatClient 到 ChatClient
1.0.0 GA 将ChatClient正式化和稳定化。在旧版本中,很多功能还是以ChatModel为主,需要手动写比较多的样板代码。1.0.0 中ChatClient承担了更多工作:Prompt 构建、消息历史管理、工具调用、结构化输出配置,都可以通过链式 API 完成。
ChatResponse response = chatClient.prompt() .system("你是订单助手,只能回答和订单相关的问题") .user("帮我查一下订单 2024001 的状态") .call() .chatResponse();这种 API 演进方向,本质上是把模型交互过程从“底层模型调用”提升到了“业务对话编排”的层面。对应用开发者来说,心智负担少了很多。
3. 环境准备与前置条件
开始写代码之前,需要把环境准备好。我建议使用以下基础环境,版本信息以实际项目为准:
- JDK 17 或更高版本(Spring Boot 3.x 的要求)
- Maven 3.6+ 或 Gradle 8.x
- Spring Boot 3.4.x 或 3.5.x
- 一个可用的通义千问 API Key(通过阿里云百炼平台获取),或者兼容 OpenAI 协议的模型服务地址
如果你还没有 API Key,可以去阿里云百炼控制台创建。注意首次使用可能需要开通服务,部分模型有免费额度,足够开发测试。
新建一个 Spring Boot 项目,pom.xml核心依赖如下:
<dependencies> <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> </dependencies>依赖管理部分引入 BOM,然后配置application.yml:
spring: application: name: spring-ai-alibaba-example ai: dashscope: api-key: ${AI_API_KEY} model: qwen-plus这里AI_API_KEY是你的环境变量名,不要把密钥硬编码在配置文件里。如果是在本地测试,也可以在启动命令里指定:
export SPRING_AI_DASHSCOPE_API_KEY=你的API密钥 mvn spring-boot:run到了这一步,项目的骨架已经搭好。需要注意,如果你的网络环境无法访问阿里云 DashScope 的默认地址,需要通过相关环境配置兼容的模型服务地址。生产环境请以实际的网络策略为准。
4. 从文本对话入手,跑通第一个 AI 接口
4.1 创建 Controller
在src/main/java/com/example/springaialibaba/下新建ChatController.java:
package com.example.springaialibaba.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/chat") public String chat(@RequestParam(defaultValue = "介绍一下你自己") String message) { return chatClient.prompt() .user(message) .call() .content(); } }这段代码的核心在于ChatClient.Builder。它是 Spring AI 中的一个构建器组件,会从 Spring 容器中自动读取已配置的ChatModel,并构建出ChatClient实例。你不需要手动指定模型类型或者 API Key。
4.2 运行与验证
启动应用后,在浏览器或命令行访问:
curl "http://localhost:8080/chat?message=用一句话介绍Java"返回结果就是模型生成的文本内容。先跑通这个最简单的流程,后续所有复杂功能都建立在这个基础之上。
5. 结构化输出:让模型返回 JSON 而不是文本
真实业务中,我们很少需要模型直接返回一段散文。更多时候,我们希望模型输出一个标准 JSON,方便 Java 对象直接反序列化。比如让模型从一段用户反馈中提取“用户情绪、核心需求、建议动作”三个字段。
传统做法是写特别复杂的 Prompt,要求模型“必须返回 JSON,且字段名是 xxx, 字段类型是 yyy”,然后自己解析字符串。问题是模型偶尔会在 JSON 外面加上 markdown 代码块标记,或者多输出一段解释性文字,解析直接报错。
Spring AI Alibaba 提供了结构化输出能力。定义一个 Java Record 或 POJO,运行时直接绑定:
package com.example.springaialibaba.model; public record FeedbackAnalysis( String sentiment, String coreRequirement, String suggestion ) { }然后创建StructuredOutputController.java:
package com.example.springaialibaba.controller; import com.example.springaialibaba.model.FeedbackAnalysis; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class StructuredOutputController { private final ChatClient chatClient; public StructuredOutputController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/analyze") public FeedbackAnalysis analyze(@RequestParam String feedback) { return chatClient.prompt() .system("你是一个用户反馈分析助手。请从反馈中提取用户情绪、核心需求和建议动作,严格按照JSON格式返回。") .user(feedback) .call() .entity(FeedbackAnalysis.class); } }注意看这一段:到了.entity(FeedbackAnalysis.class)这一步,框架会自动把模型返回的内容映射成 Java 对象。如果不满足条件是,框架还会自动重试,让模型重新生成符合目标结构的 JSON。这比自己写字符串解析逻辑可靠得多。
访问测试:
curl "http://localhost:8080/analyze?feedback=你们这个APP登录太慢了,每次都要等很久,希望可以加一个指纹解锁"返回结果类似:
{ "sentiment": "negative", "coreRequirement": "提升登录速度,增加指纹解锁功能", "suggestion": "优化登录流程,引入生物识别" }这里真正容易踩坑的地方:如果模型连续多次无法生成符合目标结构的 JSON,entity()调用会抛出异常。实际项目中,建议为这种调用加上 try-catch,并记录原始返回内容,方便排查是不是 Prompt 写得不够明确,而不是框架问题。
6. Function Calling:让模型调用你的业务方法
结构化输出解决的是“模型怎么回答”,Function Calling 解决的是“模型怎么动手”。
举个例子。用户问:“帮我算一下今年 9 月份订单总金额。”如果让模型直接回答,它没有你数据库里的订单数据,只能编一个数字。正确做法是:把“计算订单总额”这个能力暴露成函数,让模型在需要时自动调用。
6.1 定义函数注册 Bean
package com.example.springaialibaba.service; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; @Component public class OrderTools { @Tool(description = "查询指定月份的订单总金额,入参month格式为yyyy-MM") public String getTotalAmountByMonth(@ToolParam(description = "月份,格式 yyyy-MM") String month) { // 这里模拟数据库查询,实际项目中替换为真实的订单服务调用 if ("2025-08".equals(month)) { return "订单总金额为 158000 元"; } return "该月暂无订单数据"; } }6.2 在 ChatClient 中启用工具调用
package com.example.springaialibaba.controller; import com.example.springaialibaba.service.OrderTools; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ToolCallingController { private final ChatClient chatClient; public ToolCallingController(ChatClient.Builder builder, OrderTools orderTools) { this.chatClient = builder .defaultTools(orderTools) .build(); } @GetMapping("/order/total") public String queryMonthlyTotal(@RequestParam String question) { return chatClient.prompt() .user(question) .call() .content(); } }关键在.defaultTools(orderTools)这一行。它会把OrderTools中标注了@Tool的方法注册为模型可调用的工具。模型会根据用户的问题,判断是否需要调用该函数,并自动传入参数。
访问测试:
curl "http://localhost:8080/order/total?question=帮我查一下2025年8月的订单总金额"返回内容中,模型会先内部调用getTotalAmountByMonth("2025-08"),拿到结果后组织自然语言回复。你不需要自己写任何“如果用户提到订单,就调用 xxx 方法”的判断逻辑。
查记录的话,这个过程在日志里会输出很像 function call 的中间过程,第一次看到会觉得非常神奇,其实这是大模型原本就有的能力,Spring AI Alibaba 只是把它变成了 Java 开发者熟悉的注解方式。这一设计极大降低了工具调用的接入门槛。
7. 构建简单 Agent:多轮推理与自动决策
Function Calling 单独用已经很方便了,但如果把工具调用放进循环里,让模型根据中间结果继续推理、继续调用工具,这就是一个最简形态的 Agent。
我们模拟一个场景:用户提出“请帮我综合分析一下库存和销量数据,给出补货建议。”Agent 需要连续调用两个工具,先查库存、再查销量,最后综合结果生成建议。
7.1 扩充工具类
package com.example.springaialibaba.service; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; @Component public class DataAnalysisTools { @Tool(description = "查询指定商品SKU的当前库存数量,入参skuId为商品编码") public int getStock(@ToolParam(description = "商品SKU编码") String skuId) { if ("SKU-1001".equals(skuId)) { return 35; } return 0; } @Tool(description = "查询指定商品SKU最近30天销量,入参skuId为商品编码") public int getSalesVolume(@ToolParam(description = "商品SKU编码") String skuId) { if ("SKU-1001".equals(skuId)) { return 200; } return 0; } }7.2 创建 Agent 控制器
package com.example.springaialibaba.controller; import com.example.springaialibaba.service.DataAnalysisTools; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AgentController { private final ChatClient chatClient; public AgentController(ChatClient.Builder builder, DataAnalysisTools dataAnalysisTools) { this.chatClient = builder .defaultTools(dataAnalysisTools) .build(); } @GetMapping("/agent/restock") public String restockAdvice(@RequestParam String skuId) { String prompt = "请分析商品 " + skuId + " 的库存和销量,判断是否需要补货,并给出建议。"; return chatClient.prompt() .user(prompt) .call() .content(); } }启动后访问:
curl "http://localhost:8080/agent/restock?skuId=SKU-1001"模型会自主决定先调用getStock还是getSalesVolume,拿到结果后继续推理,最后生成一段类似这样的建议:
商品 SKU-1001 当前库存为 35 件,近 30 天销量为 200 件,平均日销约 6.7 件。按照当前消耗速度,现有库存只能维持约 5 天,建议尽快补货,建议补货量不低于 180 件以满足未来一个月的销售预期。
从表面看,这就是一次普通的对话接口调用;但实际上,模型在单次交互中完成了意图识别、工具调度、结果分析、决策建议四个步骤。这就是一个最基础的 Agent 实现。
8. 多模态与向量模型:还不急着学也没关系
很多读者看到 Spring AI Alibaba 的功能列表里有“多模态”和“向量模型”,会担心一次学不完。我的建议是:初学阶段不要贪多。先把文本对话、结构化输出、工具调用这三板斧用熟练,比什么都强。
多模态解决的问题是“模型能不能看图听音”,例如:
- 上传一张图片,让模型写一段商品描述。
- 输入一段录音,让模型转为文字并总结。
向量模型解决的是“文本相似度计算”,例如:
- 将知识库文本转为向量,存到向量数据库。
- 用户提问时,先从知识库中检索出最相关的段落,再交给大模型生成回答,也就是 RAG 检索增强生成。
这些能力在 Spring AI Alibaba 1.0.0 GA 中都有支持。但你需要先理解向量化、Embedding、相似度检索这些概念,再上手代码,否则很容易卡在奇怪的地方。
9. 常见问题与排查思路
9.1 启动报错:No qualifying bean of type 'ChatClient.Builder'
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动失败,提示找不到 ChatClient.Builder | 没有引入 starter 依赖,或者依赖版本不匹配 | 检查 pom.xml 是否引入 spring-ai-alibaba-starter;检查 BOM 版本是否和 Spring Boot 版本兼容 | 引入正确依赖,统一版本管理 |
| 启动失败,提示 apiKey 缺失 | 没有配置 DashScope API Key | 查看控制台启动日志中的配置提示 | 设置环境变量SPRING_AI_DASHSCOPE_API_KEY |
9.2 对话返回内容是控制台报错,而不是模型结果
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 返回 401 或 Forbidden | API Key 无效或过期 | 检查百炼控制台的 Key 状态 | 重新生成 Key,并更新环境变量 |
| 返回 429 或限流提示 | 并发请求超出模型配额 | 查看 DashScope 控制台用量 | 降低并发或申请提升配额 |
9.3 .entity() 结构化输出一直解析失败
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 抛出 JsonMappingException 或 AI 响应格式错误 | 模型输出与目标结构不匹配;Prompt 没有说明清楚 | 开启日志,打印模型原始返回内容 | 让 system Prompt 明确指出字段含义;给模型增加示例 |
| 返回字段为 null | 模型没有生成对应字段;字段命名不匹配 | 检查 Prompt 中是否有字段约束 | 增加字段说明,甚至给出 JSON 示例 |
9.4 Function Calling 没有触发工具调用
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型直接回答,不调用标注的 @Tool 方法 | Prompt 中没说明可以调用工具;工具描述不够清晰 | 打印请求日志,看模型实际收到的工具定义 | 优化 @Tool description 和 @ToolParam description |
| 多个工具时,模型选择了错误的工具 | 工具描述之间有歧义 | 检查每个工具的描述文本 | 让每个函数的描述更像“自然语言说明书” |
9.5 连接本地或第三方兼容模型服务时不通
如果你的网络环境没法直连默认模型服务地址,可以参考 Spring AI 的通用方式,通过base-url配置项指定兼容 OpenAI 协议的地址。生产环境使用时,请先确认该服务的提供方、数据安全和授权合规情况。
spring: ai: openai: base-url: ${AI_BASE_URL} api-key: ${AI_API_KEY} chat: options: model: qwen-plus10. 最佳实践与工程建议
10.1 API Key 必须走环境变量或配置中心
不要把你的 API Key 提交到 Git 仓库,更不要写在代码里。推荐使用环境变量、K8s Secret 或配置中心管理。
10.2 为 AI 接口设计超时和降级
大模型接口的延迟通常比普通数据库查询高很多,可能从几百毫秒到几秒不等。生产环境必须为 AI 调用设置合理的超时时间,并做好降级逻辑。Spring Boot 的spring.ai.chat.client相关配置以及 WebClient 的超时配置都可以发挥作用。
10.3 日志要记录 Prompt 和 Response
AI 应用最头疼的问题是“模型这次为什么这么回答”。建议在关键 AI 接口上,记录用户输入的 Prompt、模型返回的原始结果、工具调用过程、消耗的 Token 数。这样出了问题才能回溯分析。
10.4 结构化输出优先于自由文本
任何要落到数据库或对接下游系统的模型输出,都建议定义 Record/POJO,让模型走结构化输出,而不是解析自由文本。少踩很多格式坑。
10.5 工具函数必须是幂等的,避免写操作
Function Calling 由模型自主触发,你的函数很可能被同一个问题触发多次。只读查询相对安全,如果是写操作(下单、转账、删除),必须小心设计,避免重复执行产生副作用。生产环境中写操作建议加入人工确认环节。
10.6 版本升级前做好回归测试
Spring AI Alibaba 从 0.9.x 升级到 1.0.0 GA 时,API 有调整。升级前先跑一遍现有的对话、结构化输出、工具调用三个核心流程,不要直接上线。
11. 总结与后续学习方向
Spring AI Alibaba 1.0.0 GA 给 Java 开发者带来的,不是“又多了一个 AI 工具包”,而是一套真正可以落地的 AI 工程化标准。它把模型调用、结构化输出、工具调用、Agent 编排这些 AI 应用的核心能力,统一到了 Spring 生态的编程模型里。对已经有 Spring Boot 基础的同学来说,学习曲线比从零学习 Python AI 框架要平缓得多。
下一步的实践路径,建议按这个顺序来:
- 跑通文本对话,先感受模型调用和配置。
- 掌握结构化输出,让模型返回可靠 JSON。
- 学会 Function Calling,把业务方法暴露给模型。
- 基于多个工具,组合出简单 Agent。
- 再研究多模态、RAG、向量数据库这些进阶方向。
| 学习阶段 | 核心能力 | 建议练习 |
|---|---|---|
| 入门 | 对话补全 | 写一个简单的智能客服接口 |
| 进阶 | 结构化输出 | 从用户反馈中提取结构化字段 |
| 进阶 | Function Calling | 让模型查询真实业务数据库 |
| 高级 | Agent 编排 | 构建自动库存分析 + 补货建议系统 |
| 高级 | RAG | 给文档知识库实现私有问答 |
文章里给到的代码示例,我已经尽量精简成可以独立运行的完整片段。建议你先在本地跑通最简单的对话和结构化输出,再逐步加入工具调用和 Agent 场景,这样遇到问题时容易定位。