Spring AI 实战教程:用 Spring Boot 的方式开发 AI 应用
目标读者:有 Spring Boot 开发经验,但从未接触过 AI 模型的 Java 开发者 当前版本:Spring AI 1.0.x(2025 年 5 月 GA 发布)
前言
如果你是一名 Spring Boot 开发者,你一定对 RestTemplate、WebClient、JdbcTemplate 这些模板类非常熟悉——Spring 一贯的哲学是:屏蔽底层复杂性,提供简洁、一致的编程模型。
现在,Spring 把同样的哲学带到了 AI 领域。Spring AI 就是 Spring 生态圈对大语言模型(LLM)的抽象层,它让你用写 Spring Boot 代码的方式调用 OpenAI、Anthropic、Ollama 等各种 AI 模型,并且切换模型提供商时几乎不需要修改业务代码。
1. 必懂的 AI 基础概念(10 分钟速成)
在写代码之前,我们先把几个核心概念搞清楚,否则后面会一头雾水。
1.1 大语言模型(LLM)是什么?
LLM(Large Language Model,大语言模型)是一种经过海量文本训练的 AI 模型,它的本质是一个**“下一个词预测器”**——给定一段输入,它预测接下来最可能出现的内容。ChatGPT、Claude、DeepSeek 都是 LLM。
类比:LLM 就像一个读过互联网上所有文章的博士,你问他问题,他会综合自己的”知识”给出回答。
1.2 Token 是什么?
模型处理文本的基本单位不是”字符”,而是 Token。粗略理解:英文单词平均 1.3 个 Token,中文一个字大约 1 个 Token。Token 越多,调用费用越高,响应越慢。
注意:模型有”上下文窗口”限制(Context Window),超出长度的内容会被截断。
1.3 Prompt 是什么?
你发给模型的输入文本就是 Prompt(提示词)。Prompt 的质量直接决定模型输出的质量,这门学问叫 Prompt Engineering(提示词工程)。
Prompt 通常由两部分组成:
- System Prompt(系统提示):告诉模型它是谁、它的行为规范。例如:“你是一个专业的客服助手,只回答关于我们产品的问题。”
- User Message(用户消息):用户实际发出的请求。
1.4 向量与向量数据库(Vector & Vector Store)
LLM 不知道你公司内部的文档、数据库里的数据——因为它的知识截止于训练时间。解决方案是把你的私有文档转成**向量(Embedding)**存入向量数据库,在对话时把相关内容”喂”给模型,这就是 RAG。
类比:向量是文本的”坐标”,语义相似的文本坐标接近。你可以通过”距离查询”找到语义最相关的文档。
1.5 RAG 是什么?
**RAG(Retrieval Augmented Generation,检索增强生成)**是目前最主流的让 AI 利用私有数据的方案:
用户提问 → 向量数据库检索相关文档 → 将文档 + 原始问题一起发给 LLM → 模型生成答案1.6 Tool Calling 是什么?
LLM 本身只能”说话”,但通过 Tool Calling(工具调用) 机制,你可以给模型注册函数,让它在需要时调用你的代码(查数据库、发邮件、调用第三方 API),从而真正”做事”。
类比:Tool Calling 就是给这位博士配一台电脑,让他不仅能说,还能查资料、发邮件、操作系统。
2. 环境准备
2.1 创建 Spring Boot 项目
访问 Spring Initializr 创建项目,选择:
- Spring Boot 3.3+
- Java 17+
- 添加依赖:Spring Web
然后在 pom.xml 中手动添加 Spring AI 的 BOM 和依赖:
<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> <!-- Spring Web(已选) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<!-- OpenAI 支持(可替换为其他提供商) --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency></dependencies>2.2 获取 API Key
本教程默认使用 OpenAI(也支持 Ollama 本地模型,见第 6 节):
- 访问 platform.openai.com 注册账号
- 在 API Keys 页面创建一个 Key,格式类似
sk-xxxxxx
在 application.properties 中配置:
spring.ai.openai.api-key=${OPENAI_API_KEY}spring.ai.openai.chat.options.model=gpt-4o-mini安全提示:永远不要把 API Key 硬编码在代码里或提交到 Git!推荐通过环境变量注入。
3. 第一个 AI 对话:Hello, Spring AI
3.1 最简单的 ChatClient 调用
Spring AI 的核心 API 是 ChatClient,风格和你熟悉的 WebClient 非常相似:
@RestController@RequestMapping("/ai")public class ChatController {
private final ChatClient chatClient;
// Spring AI 自动配置了 ChatClient.Builder,直接注入即可 public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); }
@GetMapping("/chat") public String chat(@RequestParam String message) { return chatClient.prompt() .user(message) // 用户消息 .call() // 发送请求 .content(); // 获取文本响应 }}启动应用,访问:
GET http://localhost:8080/ai/chat?message=你好,请介绍一下Spring框架就这四行代码,你已经完成了一个 AI 对话接口!
3.2 添加 System Prompt(系统角色)
通过 System Prompt 给模型设定角色,让它更专注:
@GetMapping("/customer-service")public String customerService(@RequestParam String question) { return chatClient.prompt() .system("你是一名专业的 Spring 框架技术支持工程师," + "只回答与 Spring 相关的技术问题," + "回答要简洁专业,使用中文。") .user(question) .call() .content();}3.3 流式响应(Streaming)
对于较长的回答,流式响应可以让用户更快看到内容(类似 ChatGPT 的打字效果):
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> streamChat(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() // 切换为流式模式 .content(); // 返回 Flux<String>}
Flux是 Project Reactor 的响应式类型,Spring Boot 天然支持以 SSE(Server-Sent Events)形式推送给前端。
3.4 结构化输出
很多场景需要 AI 返回 JSON 而非纯文本,Spring AI 内置了结构化输出能力:
// 定义数据结构record MovieRecommendation( String title, String director, int year, String reason) {}
@GetMapping("/recommend-movie")public List<MovieRecommendation> recommendMovies(@RequestParam String genre) { return chatClient.prompt() .user("推荐3部" + genre + "类型的经典电影,说明推荐理由") .call() .entity(new ParameterizedTypeReference<List<MovieRecommendation>>() {});}Spring AI 会自动处理 JSON 解析,你直接拿到类型安全的 Java 对象。
4. Prompt 模板与多轮对话
4.1 PromptTemplate:参数化 Prompt
类似 MyBatis 的 SQL 模板,Spring AI 支持在 Prompt 中使用变量:
@Servicepublic class CodeReviewService {
private final ChatClient chatClient;
public CodeReviewService(ChatClient.Builder builder) { this.chatClient = builder.build(); }
public String reviewCode(String language, String code) { String template = """ 请对以下 {language} 代码进行 Code Review:
```{language} {code} ```
请从以下角度分析: 1. 代码质量与可读性 2. 潜在的 Bug 或安全问题 3. 性能优化建议
请用中文回答。 """;
return chatClient.prompt() .user(u -> u.text(template) .param("language", language) .param("code", code)) .call() .content(); }}4.2 多轮对话(Chat Memory)
单次请求无法记住上下文,实现多轮对话需要借助 Chat Memory:
@Configurationpublic class AiConfig {
@Bean public ChatClient chatClientWithMemory(ChatClient.Builder builder) { return builder // 使用内存存储对话历史(生产环境可换成 Redis、数据库) .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) .build(); }}@RestController@RequestMapping("/ai")public class ConversationController {
private final ChatClient chatClient;
public ConversationController(ChatClient chatClient) { this.chatClient = chatClient; }
@GetMapping("/conversation") public String conversation( @RequestParam String message, @RequestParam(defaultValue = "default") String sessionId) { return chatClient.prompt() .user(message) .advisors(a -> a.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)) .call() .content(); }}现在,相同 sessionId 的请求会共享上下文:
# 第一轮GET /ai/conversation?message=我叫李明&sessionId=user123→ "你好,李明!..."
# 第二轮(模型记得你叫什么)GET /ai/conversation?message=你还记得我的名字吗&sessionId=user123→ "当然,你叫李明!..."5. Tool Calling:让 AI 真正”做事”
5.1 为什么需要 Tool Calling?
LLM 的知识有截止日期,也不了解你的业务数据。Tool Calling 允许 AI 在需要时调用你提供的 Java 方法,从而:
- 查询实时数据(天气、股价、订单状态)
- 执行业务操作(创建工单、发送通知)
- 访问私有数据库
5.2 实战:订单查询助手
第一步:编写工具类,用 @Tool 注解标注可被 AI 调用的方法:
@Componentpublic class OrderTools {
@Autowired private OrderRepository orderRepository;
@Tool(description = "根据订单ID查询订单状态和详情") public OrderInfo getOrderById(String orderId) { return orderRepository.findById(orderId) .map(order -> new OrderInfo( order.getId(), order.getStatus(), order.getTotalAmount(), order.getCreatedAt())) .orElse(null); }
@Tool(description = "查询指定用户最近N笔订单") public List<OrderInfo> getRecentOrders(String userId, int limit) { return orderRepository.findByUserIdOrderByCreatedAtDesc(userId, PageRequest.of(0, limit)) .stream() .map(o -> new OrderInfo(o.getId(), o.getStatus(), o.getTotalAmount(), o.getCreatedAt())) .toList(); }
record OrderInfo(String orderId, String status, BigDecimal amount, LocalDateTime createdAt) {}}第二步:将工具注册到 ChatClient:
@RestController@RequestMapping("/ai")public class CustomerServiceController {
private final ChatClient chatClient; private final OrderTools orderTools;
public CustomerServiceController(ChatClient.Builder builder, OrderTools orderTools) { this.orderTools = orderTools; this.chatClient = builder .defaultSystem("你是一名专业的电商客服,帮助用户查询订单信息。" + "如果需要查询订单,请使用提供的工具获取准确数据,不要编造信息。") .build(); }
@GetMapping("/customer-service") public String handleCustomerQuery( @RequestParam String message, @RequestParam String userId) { return chatClient.prompt() .user(message) .tools(orderTools) // 注册工具 .call() .content(); }}效果:
用户:我的订单 ORD-2025-001 现在什么状态了?AI:查询您的订单 ORD-2025-001... 该订单当前状态为"已发货", 下单金额 299.00 元,预计明天送达。有其他问题需要帮助吗?模型会自动判断何时需要调用工具,调用哪个工具,并将结果融入回答。
5.3 Tool Calling 的执行流程
用户消息 + 工具定义 ↓LLM 决定调用哪个工具(返回 ToolCall 请求) ↓Spring AI 自动执行对应 Java 方法 ↓将工具执行结果返回给 LLM ↓LLM 生成最终回答这整个流程由 Spring AI 自动管理,你只需要专注于编写工具方法的业务逻辑。
6. RAG:让 AI 了解你的私有知识
6.1 场景说明
假设你有公司内部技术文档、产品手册、FAQ 文档,希望搭建一个能回答这些内容的 AI 助手,这就是 RAG 的典型场景。
6.2 添加依赖
使用 PGVector(PostgreSQL 向量扩展)作为向量数据库:
<!-- 向量存储:PGVector --><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId></dependency>
<!-- PDF 文档读取 --><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pdf-document-reader</artifactId></dependency>如果不想安装 PostgreSQL,可以使用内存向量库(仅适合开发测试):
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-simple-vector-store</artifactId></dependency>6.3 配置数据库
spring: datasource: url: jdbc:postgresql://localhost:5432/aidb username: postgres password: postgres ai: vectorstore: pgvector: initialize-schema: true # 自动建表 dimensions: 1536 # OpenAI text-embedding-ada-002 的维度6.4 第一步:导入文档(ETL Pipeline)
@Servicepublic class DocumentIngestionService {
private final VectorStore vectorStore; private final TokenTextSplitter textSplitter;
public DocumentIngestionService(VectorStore vectorStore) { this.vectorStore = vectorStore; this.textSplitter = new TokenTextSplitter(); }
/** * 导入 PDF 文档到向量数据库 */ public void ingestPdfDocument(String filePath) { // 1. 读取 PDF var reader = new PagePdfDocumentReader(filePath); List<Document> documents = reader.get();
// 2. 分块(将大文档切成小片段) List<Document> chunks = textSplitter.apply(documents);
// 3. 向量化并存入数据库(Spring AI 自动调用 Embedding API) vectorStore.add(chunks);
System.out.println("成功导入 " + chunks.size() + " 个文档片段"); }
/** * 导入纯文本内容 */ public void ingestText(String content, Map<String, Object> metadata) { var document = new Document(content, metadata); List<Document> chunks = textSplitter.apply(List.of(document)); vectorStore.add(chunks); }}6.5 第二步:构建 RAG 对话接口
@Configurationpublic class RagConfig {
@Bean public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) { return builder .defaultSystem(""" 你是一个专业的技术文档助手。 请基于提供的上下文信息回答问题。 如果上下文中没有相关信息,请直接说"文档中没有找到相关信息",不要编造答案。 请用中文回答。 """) .defaultAdvisors( // 核心:注入 RAG Advisor,自动进行文档检索和上下文注入 QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder() .topK(5) // 检索最相关的5个片段 .similarityThreshold(0.7) // 相似度阈值 .build()) .build() ) .build(); }}@RestController@RequestMapping("/ai/rag")public class RagController {
private final ChatClient ragChatClient;
public RagController(ChatClient ragChatClient) { this.ragChatClient = ragChatClient; }
@GetMapping("/ask") public String askWithContext(@RequestParam String question) { return ragChatClient.prompt() .user(question) .call() .content(); }}6.6 RAG 工作原理图解
用户问题:"Spring Boot 如何配置数据源?" ↓ Embedding 模型将问题向量化 ↓ 在向量数据库中查找语义最相似的5个文档片段 ↓ 将原始问题 + 检索到的文档片段组合成 Prompt: ┌─────────────────────────────────────┐ │ 上下文: │ │ [文档片段1] Spring Boot 数据源配置… │ │ [文档片段2] spring.datasource.url… │ │ ... │ │ │ │ 用户问题:Spring Boot 如何配置数据源?│ └─────────────────────────────────────┘ ↓ LLM 基于上下文生成精准回答7. 使用 Ollama 运行本地模型(免费、私密)
如果你不想付费或担心数据隐私,可以用 Ollama 在本地运行开源模型(如 DeepSeek、Llama 等)。
7.1 安装 Ollama
访问 ollama.ai 下载安装包,或直接运行:
# macOS / Linuxcurl -fsSL https://ollama.ai/install.sh | sh
# 下载并运行 DeepSeek R1(1.5B 参数,适合测试,约 1GB)ollama run deepseek-r1:1.5b
# 或者运行通义千问(中文效果更好)ollama run qwen2.5:7bOllama 会在本地启动一个 REST API 服务,默认地址:http://localhost:11434
7.2 切换 Spring AI 到 Ollama
只需修改依赖和配置,业务代码一行不改:
pom.xml - 将 OpenAI starter 替换为 Ollama starter:
<!-- 删除这个 --><!-- <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>application.properties:
spring.ai.ollama.base-url=http://localhost:11434spring.ai.ollama.chat.options.model=deepseek-r1:1.5b
# 可选:模型不存在时自动拉取spring.ai.ollama.init.pull-model-strategy=when_missing其他代码完全不需要改动!这就是 Spring AI 抽象层的价值——提供商无关性。
7.3 硬件参考配置
| 模型规模 | 最低显存/内存 | 推荐模型 |
|---|---|---|
| 1.5B | 4GB RAM | deepseek-r1:1.5b |
| 7B | 8GB RAM | qwen2.5:7b, llama3.1:8b |
| 14B | 16GB RAM | qwen2.5:14b |
| 32B | 24GB 显存 | deepseek-r1:32b |
8. 生产环境最佳实践
8.1 错误处理与重试
@Servicepublic class RobustChatService {
private final ChatClient chatClient;
public RobustChatService(ChatClient.Builder builder) { this.chatClient = builder.build(); }
public String chatWithRetry(String message) { try { return chatClient.prompt() .user(message) .call() .content(); } catch (RateLimitException e) { // API 限流,等待后重试 log.warn("AI API 限流,请稍后重试"); throw new ServiceException("服务繁忙,请稍后重试"); } catch (OpenAiApiException e) { log.error("AI API 调用失败: {}", e.getMessage()); throw new ServiceException("AI 服务暂时不可用"); } }}8.2 Token 成本控制
// 在 ChatClient.Builder 中设置最大 Token 限制ChatClient chatClient = builder .defaultOptions(OpenAiChatOptions.builder() .model("gpt-4o-mini") .maxTokens(500) // 限制响应最大 Token 数 .temperature(0.7) // 温度:0=确定性,1=创造性 .build()) .build();常见温度参数建议:
0.0 ~ 0.3:代码生成、数据提取(需要精确性)0.5 ~ 0.7:对话、问答(平衡准确与自然)0.8 ~ 1.0:创意写作、头脑风暴(需要多样性)
8.3 观测与监控
Spring AI 集成了 Spring Boot Actuator,可以通过 Micrometer 上报 AI 调用指标:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId></dependency>会自动收集:gen_ai.client.token.usage(Token 用量)、gen_ai.client.operation.duration(调用耗时)等指标。
9. 完整示例:AI 驱动的知识库问答系统
让我们把前面学到的内容整合成一个完整的示例:
@SpringBootApplicationpublic class KnowledgeBaseApplication { public static void main(String[] args) { SpringApplication.run(KnowledgeBaseApplication.class, args); }}
@Configurationclass AiConfiguration {
@Bean ChatClient knowledgeBaseChatClient( ChatClient.Builder builder, VectorStore vectorStore) { return builder .defaultSystem(""" 你是公司内部知识库助手,专门回答员工关于公司政策、 技术规范和业务流程的问题。 - 只基于提供的上下文回答问题 - 如果不确定,请明确说明并建议联系相关部门 - 回答要简洁、专业,使用中文 """) .defaultAdvisors( new MessageChatMemoryAdvisor(new InMemoryChatMemory()), QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder().topK(4).build()) .build() ) .build(); }}
@RestController@RequestMapping("/knowledge-base")@RequiredArgsConstructorclass KnowledgeBaseController {
private final ChatClient knowledgeBaseChatClient; private final DocumentIngestionService ingestionService;
// 导入文档接口 @PostMapping("/documents") public ResponseEntity<String> uploadDocument(@RequestBody DocumentRequest request) { ingestionService.ingestText(request.content(), request.metadata()); return ResponseEntity.ok("文档导入成功"); }
// 问答接口 @PostMapping("/ask") public ResponseEntity<String> ask(@RequestBody ChatRequest request) { String answer = knowledgeBaseChatClient.prompt() .user(request.question()) .advisors(a -> a.param( AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, request.sessionId())) .call() .content(); return ResponseEntity.ok(answer); }
record DocumentRequest(String content, Map<String, Object> metadata) {} record ChatRequest(String question, String sessionId) {}}10. 与 Spring 生态集成小贴士
作为 Spring Boot 开发者,你已有的技能可以直接复用:
| 你熟悉的技术 | 在 Spring AI 中的应用 |
|---|---|
| Spring Security | 保护 AI 接口,控制访问权限 |
| Spring Data JPA | 持久化对话历史、用户偏好 |
| Spring Cache | 缓存相同问题的 AI 响应,节省 Token |
| Spring Async | 异步执行文档索引任务 |
| Spring Batch | 批量处理文档导入 ETL Pipeline |
| Spring WebFlux | 处理流式 AI 响应 |
| Spring Boot Actuator | 监控 AI 调用指标和健康状态 |
总结
通过本文,你已经学会了:
- AI 基础概念:LLM、Token、Prompt、向量、RAG、Tool Calling
- ChatClient 基础用法:同步/流式调用、结构化输出
- Prompt 管理:模板、System Prompt、多轮对话记忆
- Tool Calling:让 AI 调用你的 Java 方法访问业务数据
- RAG 实战:构建基于私有文档的知识库问答系统
- 本地模型:使用 Ollama 运行免费的本地大模型
- 生产实践:错误处理、成本控制、监控观测
Spring AI 的设计哲学与 Spring 一脉相承:让复杂的事情简单,让简单的事情不臃肿。对于有 Spring Boot 基础的开发者,上手 Spring AI 的成本非常低,但它能帮助你快速构建生产级的 AI 应用。
下一步推荐探索:
- Spring AI 官方文档
- MCP(Model Context Protocol):构建可被多个 AI 共享的工具服务
- Spring AI 1.1 新特性:Reasoning Model 支持、高级 Agent 框架