大翔:"白歌,给我一个理由——为什么咱不直接调 OpenAI 的 HTTP API,非要用 Spring AI?"白歌推了推眼镜:"因为直接调 API,就像直接用 JDBC 写 SQL——能跑,但三个月后没人敢改。Spring AI 给的是 JdbcTemplate 级别的抽象。"
Spring AI 概述与可移植 API
定义与作用
Spring AI 是 Spring 生态面向 AI 应用开发 的子项目。它不发明新的 AI 算法,也不训练模型,而是将大语言模型(LLM)的 API 调用、向量存储、工具调用等复杂能力封装为 Spring 风格的 Bean 和自动配置。
一句话概括:Spring AI 之于 LLM API,如同 Spring Data 之于数据库——它提供统一的抽象层,让你用 Java 写一次代码,通过 YAML 切换模型提供商。
Spring AI 在 Spring 生态中的位置
上图说明:Spring AI 坐落在 Spring Boot 之上,LLM 提供商之下。它在中间做两件事——对上提供统一的 Java API(ChatClient/Advisor 等),对下屏蔽不同模型提供商的 API 差异。你的业务代码只看到 ChatClient,不知道背后是 OpenAI 还是 Ollama。
核心原理:Portable API 设计
什么是 Portable API
Portable API(可移植 API) 是 Spring AI 最核心的设计理念:应用代码依赖 Spring AI 的抽象接口(如 ChatClient、ChatModel、VectorStore),不直接依赖任何具体模型提供商的 SDK。切换模型只需修改 application.yml,无需改动一行 Java 代码。
图中关键路径:开发者调用 ChatClient → ChatClient 读取 YAML 决定使用哪个 ChatModel 实现 → 由具体的 ChatModel(如 OpenAiChatModel)完成实际的 HTTP 调用。开发者的代码里只有 ChatClient 接口,不出现 OpenAi 或 Ollama 字样。
Portable API 的核心体现
场景:白歌为飞翔科技学生管理系统搭建技术选型原型——用同一套代码,通过 YAML 切换 OpenAI 和 Ollama。
application.yml(OpenAI 配置):
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
application.yml(Ollama 配置):
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3
temperature: 0.7
Java 代码(完全不变):
@RestController
public class AiController {
private final ChatClient chatClient;
public AiController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/ai/ask")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
运行结果对比:
| 配置 | 模型 | /ai/ask?question=你好 的返回 |
|---|---|---|
| OpenAI | gpt-4o | "你好!有什么我可以帮助你的吗?" |
| Ollama | llama3 | "你好!请问有什么需要帮助的吗?" |
两次调用 Java 代码完全相同,输出质量取决于各自模型,但开发体验、日志、监控、异常处理全部统一。
最低环境要求
| 项目 | 版本要求 | 说明 |
|---|---|---|
| Java | 17+ | Spring AI 基于 Spring Boot 3.x,最低 Java 17 |
| Spring Boot | 3.2+ | Spring AI 1.0.x 依赖 Spring Boot 3.2+ |
| Spring AI | 1.0.0+ | 本教程基于 1.0.x GA 版本 |
| Maven/Gradle | 3.8+ / 7.5+ | 构建工具版本 |
前置条件:读者需已具备 Spring Boot 基础(
@RestController、application.yml、依赖注入等概念)。本教程聚焦 Spring AI 独有的能力,不展开 Spring Boot 基础。
完整示例:白歌的技术选型原型
场景说明
孔蓝(产品经理)提出需求:"我们的学生管理系统要加一个 AI 问答功能,学生可以问课程相关的问题。"大翔要求技术选型必须先跑通原型,而且要能随时切换模型提供商——万一 OpenAI 涨价或网络不稳定,能切到本地 Ollama。
依赖引入
<!-- pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 核心依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- 本地模型备用 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
</dependencies>
关键代码
@SpringBootApplication
public class FeixiangAiApplication {
public static void main(String[] args) {
SpringApplication.run(FeixiangAiApplication.class, args);
}
}
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是飞翔科技学生管理系统的智能助手,回答简洁专业。")
.build();
}
}
@RestController
@RequestMapping("/api/ai")
public class StudentAiController {
private final ChatClient chatClient;
public StudentAiController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ask")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
@GetMapping("/stream")
public Flux<String> stream(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.stream()
.map(resp -> resp.getResult().getOutput().getContent());
}
}
运行结果及分析
启动后访问 http://localhost:8080/api/ai/ask?question=如何选课,控制台输出:
2026-06-13 10:00:00.123 INFO --- [nio-8080-exec-1] o.s.ai.chat.client.ChatClient :
Prompt: [UserMessage{content='如何选课'}]
2026-06-13 10:00:01.456 INFO --- [nio-8080-exec-1] o.s.ai.chat.client.ChatClient :
Response: 选课流程如下:1. 登录学生管理系统 2. 进入选课中心...
操作前后对比:
| 维度 | 操作前(直接调 OpenAI API) | 操作后(Spring AI ChatClient) |
|---|---|---|
| 代码行数 | ~40 行(HTTP 连接 + JSON 解析) | ~5 行业务代码 |
| 切换模型 | 重写 HTTP 调用逻辑 | 改一行 YAML 配置 |
| 流式输出 | 手动解析 SSE 事件流 | .stream() 直接返回 Flux<String> |
| 异常处理 | 手动 try-catch HTTP 异常 | Spring 统一异常体系 |
| 日志监控 | 需要手动埋点 | 自动集成 Micrometer |
易错场景与面试考点
易错场景一:同时引入多个 Starter 导致冲突
<!-- ❌ 错误:不指定 active profile,Spring 可能加载多个 ChatModel Bean -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
问题分析
当 classpath 中同时存在多个 Starter 时,Spring 会尝试创建多个 ChatModel Bean,可能导致 NoUniqueBeanDefinitionException。
# ✅ 正确做法:通过 spring.ai.openai.api-key 存在与否让自动配置自行判断
# 或者在其中一个配置中设置 spring.ai.ollama.chat.enabled=false
面试高频题
Q1:Spring AI 的 Portable API 是如何实现的?
通过
ChatModel接口定义统一契约(call(Prompt)/stream(Prompt)),各提供商实现自己的具体类(如OpenAiChatModel、OllamaChatModel)。Spring Boot 的 Auto-Configuration 根据 classpath 中的 Starter 和 YAML 配置属性,通过@ConditionalOnProperty等条件注解决定注入哪个实现。应用代码只依赖接口,不依赖具体实现。
Q2:Spring AI 与 LangChain / LlamaIndex 的定位差异?
Spring AI 是 Java 生态 的 AI 集成框架,深度绑定 Spring Boot 的自动配置和 Bean 管理;LangChain / LlamaIndex 是 Python 生态 的 AI 编排框架。前者适合 Java 企业级应用集成,后者适合 Python 数据科学和快速原型。Spring AI 的优势在于与 Spring 生态(事务、安全、监控)无缝融合。
Q3:为什么 Spring AI 要求 Java 17+?
Spring AI 基于 Spring Boot 3.x,而 Spring Boot 3.x 要求 Java 17+。此外,Spring AI 内部大量使用 Record 类(Java 16 正式特性)来定义不可变数据模型,如
ChatResponse的Generation元数据。
本章小结
- Spring AI 是 Spring 生态的 AI 集成层,提供 Portable API,编写一次代码应对多种模型提供商
- 核心入口是 ChatClient,底层委托给 ChatModel(如 OpenAiChatModel)
- 切换模型只需修改
application.yml,Java 代码零改动 - 要求 Java 17+、Spring Boot 3.2+,适合已有 Spring 技术栈的团队
- 与 LangChain 定位不同——Spring AI 走 Java 企业级路线,LangChain 走 Python 生态路线