3982 字
20 分钟
Spring AI 实战教程:用 Spring Boot 的方式开发 AI 应用

Spring AI 实战教程:用 Spring Boot 的方式开发 AI 应用#

目标读者:有 Spring Boot 开发经验,但从未接触过 AI 模型的 Java 开发者 当前版本:Spring AI 1.0.x(2025 年 5 月 GA 发布)

前言#

如果你是一名 Spring Boot 开发者,你一定对 RestTemplateWebClientJdbcTemplate 这些模板类非常熟悉——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 节):

  1. 访问 platform.openai.com 注册账号
  2. 在 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 中使用变量:

@Service
public 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

@Configuration
public 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 调用的方法:

@Component
public 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)#

@Service
public 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 对话接口#

@Configuration
public 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 下载安装包,或直接运行:

Terminal window
# macOS / Linux
curl -fsSL https://ollama.ai/install.sh | sh
# 下载并运行 DeepSeek R1(1.5B 参数,适合测试,约 1GB)
ollama run deepseek-r1:1.5b
# 或者运行通义千问(中文效果更好)
ollama run qwen2.5:7b

Ollama 会在本地启动一个 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:11434
spring.ai.ollama.chat.options.model=deepseek-r1:1.5b
# 可选:模型不存在时自动拉取
spring.ai.ollama.init.pull-model-strategy=when_missing

其他代码完全不需要改动!这就是 Spring AI 抽象层的价值——提供商无关性

7.3 硬件参考配置#

模型规模最低显存/内存推荐模型
1.5B4GB RAMdeepseek-r1:1.5b
7B8GB RAMqwen2.5:7b, llama3.1:8b
14B16GB RAMqwen2.5:14b
32B24GB 显存deepseek-r1:32b

8. 生产环境最佳实践#

8.1 错误处理与重试#

@Service
public 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 驱动的知识库问答系统#

让我们把前面学到的内容整合成一个完整的示例:

@SpringBootApplication
public class KnowledgeBaseApplication {
public static void main(String[] args) {
SpringApplication.run(KnowledgeBaseApplication.class, args);
}
}
@Configuration
class 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")
@RequiredArgsConstructor
class 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 调用指标和健康状态

总结#

通过本文,你已经学会了:

  1. AI 基础概念:LLM、Token、Prompt、向量、RAG、Tool Calling
  2. ChatClient 基础用法:同步/流式调用、结构化输出
  3. Prompt 管理:模板、System Prompt、多轮对话记忆
  4. Tool Calling:让 AI 调用你的 Java 方法访问业务数据
  5. RAG 实战:构建基于私有文档的知识库问答系统
  6. 本地模型:使用 Ollama 运行免费的本地大模型
  7. 生产实践:错误处理、成本控制、监控观测

Spring AI 的设计哲学与 Spring 一脉相承:让复杂的事情简单,让简单的事情不臃肿。对于有 Spring Boot 基础的开发者,上手 Spring AI 的成本非常低,但它能帮助你快速构建生产级的 AI 应用。

下一步推荐探索:

  • Spring AI 官方文档
  • MCP(Model Context Protocol):构建可被多个 AI 共享的工具服务
  • Spring AI 1.1 新特性:Reasoning Model 支持、高级 Agent 框架

参考资料#

Spring AI 实战教程:用 Spring Boot 的方式开发 AI 应用
https://fuwari.vercel.app/posts/spring-ai-tutorial/
作者
CC代码日志
发布于
2026-03-17
许可协议
CC BY-NC-SA 4.0