Spring AI 1.1.0 基础使用:构建智能应用实战
本文介绍基于 Spring Boot 3.5.0 和 Spring AI 1.1.0 的大模型集成方案。涵盖项目依赖配置、通义千问接入、ChatClient 同步与流式调用、系统提示词设置及结构化输出(JSON 转 Java 对象)。包含生产级错误处理、性能优化建议及常见问题解答,帮助开发者快速构建智能应用。

本文介绍基于 Spring Boot 3.5.0 和 Spring AI 1.1.0 的大模型集成方案。涵盖项目依赖配置、通义千问接入、ChatClient 同步与流式调用、系统提示词设置及结构化输出(JSON 转 Java 对象)。包含生产级错误处理、性能优化建议及常见问题解答,帮助开发者快速构建智能应用。

在 AI 快速发展的今天,集成大语言模型(LLM)成为了现代应用开发的必备技能。Spring AI 是 Spring 官方提供的一套标准化框架,用于简化与 LLM 的集成开发。本文通过 Spring Boot 3.5.0 + Spring AI 1.1.0 + 通义千问(Qwen)的技术栈,深入讲解 Spring AI 的基础使用方法。
我们以真实的课程项目为例,展示如何从项目构建、基础对话、流式输出、系统提示词,到结构化输出的完整开发流程。无论你是初学者还是有一定经验的 Java 开发者,都能从本文获得实用的开发经验。
首先,我们需要在 pom.xml 中配置 Spring AI 的相关依赖。这是项目的基石,关系到后续所有功能的可用性。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<parent>
<groupId>org.artisan</groupId>
<artifactId>artisan-ai-agent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-ai-demo</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Web 支持 REST 接口 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI OpenAI 集成(支持兼容模式) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- Spring AI 聊天内存管理 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Spring AI 向量存储(Elasticsearch) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
</dependency>
<!-- Spring AI RAG 框架 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
</dependencies>
<!-- Spring AI BOM 管理版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
关键说明:
应用启动类的职责是初始化 Spring AI 的核心组件,特别是 ChatClient。
package com.artisan;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringAIApplication {
/**
* ChatClient 是 Spring AI 的核心,用于与 LLM 交互
* ChatClient.Builder 由 Spring 自动配置,我们只需调用 build() 完成初始化
*/
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder.build();
}
/**
* 聊天内存管理
* MessageWindowChatMemory 维护一个消息窗口,避免历史消息无限增长
*/
@Bean
public ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.build();
}
/**
* 工具执行异常处理器
* DefaultToolExecutionExceptionProcessor(false) 表示不重新抛出异常
*/
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(false);
}
public {
SpringApplication.run(SpringAIApplication.class, args);
}
}
设计要点:
false 表示优雅处理,不中断流程。Spring AI 的配置集中在 application.yml 中。关键是通过 OpenAI 兼容模式接入阿里云 DashScope。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/my_db?useUnicode=true&characterEncoding=utf-8
username: root
password: artisan123456
ai:
openai:
# DashScope 提供的 OpenAI 兼容端点
base-url: https://dashscope.aliyuncs.com/compatible-mode
# 从环境变量读取 API Key,避免硬编码
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen3-max
embedding:
options:
model: text-embedding-v4
server:
port: 8081
理解兼容模式:
阿里云 DashScope 提供的 OpenAI 兼容模式使得开发者无需修改代码就能切换模型提供商。当需要切换到其他服务(如 DeepSeek)时,仅需修改 base-url 即可:
# 切换到 DeepSeek
ai:
openai:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-chat
最简单的方式是进行同步对话。用户发送问题,等待 LLM 返回完整的回答。
@RestController
public class ArtisanController {
@Autowired
private ChatClient chatClient;
/**
* 最基础的对话接口
* GET /chat?question=hello
*/
@GetMapping("/chat")
public String chat(String question) {
// chatClient.prompt() 开始构建一个提示词
// .call() 执行同步调用
// .content() 提取 LLM 的文本响应
return chatClient.prompt(question).call().content();
}
}
工作流程:
chatClient.prompt(question) └─> 创建一个 PromptRequest 对象,包含用户问题.call() └─> 发起同步 HTTP 请求到 DashScope API └─> 阻塞等待 LLM 返回完整响应.content() └─> 从 ChatClientResponse 中提取消息内容 └─> 返回字符串格式的回答优缺点分析:
| 特性 | 说明 |
|---|---|
| 优点 | 实现简单,逻辑清晰,适合后端批处理 |
| 缺点 | 阻塞等待,响应时间长,用户体验差 |
| 适用场景 | 异步任务、定时任务、消息队列处理 |
测试命令:
curl "http://localhost:8081/chat?question=你好,请自我介绍"
现代 AI 应用需要实时显示 LLM 的输出过程,而不是等待完整响应。Spring AI 提供了两种流式处理方案。
最简洁的方案是直接返回 Flux,由 Spring WebFlux 负责流式传输。
/**
* 返回 Flux 流,Spring 自动转换为 Server-Sent Events
* 注意 produces 指定了 Content-Type,包含 UTF-8 编码声明
*/
@GetMapping(value = "/stream", produces = "text/html;charset=UTF-8")
public Flux<String> stream(String question) {
// stream().content() 返回一个 Flux<String>
// 每个元素是一个 token(通常是一个词或一个字)
return chatClient.prompt(question).stream().content();
}
客户端使用示例:
<script>
const eventSource = new EventSource('/stream?question=请写一首诗');
eventSource.addEventListener('message', (event) => {
// event.data 包含每个 token
document.body.innerHTML += event.data;
});
eventSource.addEventListener('error', () => {
console.error('连接异常');
eventSource.close();
});
</script>
某些场景下需要完全控制 HTTP 响应头,特别是确保 UTF-8 编码。此时使用 SseEmitter 并自定义响应头。
/**
* 使用 SseEmitter 提供更细粒度的控制
* 通过重写 extendResponse 方法设置自定义 Content-Type
*/
@GetMapping(value = "/sse")
public SseEmitter sse(String question) {
// 创建 SseEmitter,支持自定义响应头
SseEmitter sseEmitter = new SseEmitter() {
@Override
protected void extendResponse(ServerHttpResponse outputMessage) {
HttpHeaders headers = outputMessage.getHeaders();
// 显式指定 Content-Type 和字符集,确保中文不乱码
headers.setContentType(new MediaType("text", "event-stream", StandardCharsets.UTF_8));
}
};
// 获取流
Flux<String> stream = chatClient.prompt(question).stream().content();
// 订阅流,将每个 token 发送给客户端
stream.subscribe(
// onNext:每当收到一个 token,发送给客户端
token -> {
try {
sseEmitter.send(token);
} catch (IOException e) {
// 网络异常,终止发送
sseEmitter.completeWithError(e);
}
},
// onError:流出现异常
sseEmitter::completeWithError,
// onComplete:流完成
sseEmitter::complete
);
return sseEmitter;
}
关键实现细节:
extendResponse() 方法在发送第一个 SSE 消息前设置响应头,确保 UTF-8 编码。completeWithError() 通知客户端。| 方案 | 实现复杂度 | 控制粒度 | 推荐场景 |
|---|---|---|---|
| Flux 直接返回 | 低 | 低 | 简单场景、不需要特殊编码处理 |
| SseEmitter | 中 | 高 | 需要自定义响应头、特殊编码处理 |
系统提示词(System Prompt)是指导 LLM 行为的关键。通过在每次请求前明确定义 LLM 的身份与行为准则,可以显著提升回答质量。
/**
* 在提示词中加入系统级别的指令
* GET /system?question=你好
*/
@GetMapping("/system")
public String system(String question) {
return this.chatClient
.prompt()
.system("你是周瑜老师") // 设置系统提示词
.user(question) // 设置用户问题
.call()
.content();
}
系统提示词的最佳实践:
// 不推荐:过于简单
.system("你是一个助手")
// 推荐:详细的角色定义与行为约束
.system("""
你是一名资深的 Java 开发者。
你的目标是帮助我学习 Spring AI 框架。
回答时请遵循以下原则:
1. 提供代码示例时必须能正常运行
2. 解释复杂概念时,先给出直观理解,再深入细节
3. 如果不确定答案,请明确说明
""")
LLM 通常返回文本,但应用需要结构化数据。Spring AI 提供了两种方案来解决这个问题。
这是最灵活的方案,允许完全控制 LLM 的输出格式。
/**
* 定义输出数据结构
*/
static class Poem {
private String title; // 诗名
private String author; // 诗人
private String content; // 诗的内容
// getter/setter 省略
}
/**
* 通过 BeanOutputConverter 和 PromptTemplate 实现结构化输出
* GET /output?topic=春天
*/
@GetMapping("/output")
public Poem output(String topic) {
// 1. 创建输出转换器,指定目标类型
BeanOutputConverter<Poem> outputConverter = new BeanOutputConverter<>(new ParameterizedTypeReference<Poem>() {});
// 2. 创建提示词模板,包含占位符
PromptTemplate promptTemplate = new PromptTemplate("写一首关于{topic}的七言绝句,{format}");
// 3. 渲染模板
// outputConverter.getFormat() 返回 JSON Schema 格式说明
// 这告诉 LLM 应该按照什么格式返回数据
String prompt = promptTemplate.render(Map.of("topic", topic, "format", outputConverter.getFormat()));
// 4. 调用 LLM
String content = chatClient.prompt(prompt).call().content();
// 5. 转换 JSON 字符串为 Java 对象
return outputConverter.convert(content);
}
Spring AI 1.1.0 新增了 entity() 方法,可以一行代码实现对象转换。
/**
* 更简洁的方式:使用 entity() 直接转换
* GET /entity?topic=冬天
*/
@GetMapping("/entity")
public Poem entity(String topic) {
PromptTemplate promptTemplate = new PromptTemplate("写一首关于{topic}的七言绝句");
String prompt = promptTemplate.render(Map.of("topic", topic));
// entity() 内部自动使用 BeanOutputConverter
// Spring AI 自动分析 Poem 类的结构,生成 JSON Schema
// 附加到提示词末尾,然后解析返回值
return chatClient.prompt(prompt).call().entity(Poem.class);
}
| 特性 | BeanOutputConverter | entity() |
|---|---|---|
| 代码简洁度 | 较复杂 | 简洁 |
| 定制灵活性 | 高(完全控制格式说明) | 低(自动生成) |
| 适用场景 | 复杂嵌套对象、特殊格式要求 | 简单对象、快速开发 |
Spring AI 的 ChatClient 采用 Builder 模式和流式接口(Fluent Interface)设计,使代码更易读且易于扩展。
// 基础链式调用
chatClient
.prompt()
.system("系统提示词") // 1. 添加系统消息
.user("用户问题") // 2. 添加用户消息
.call() // 3. 执行同步调用
.content(); // 4. 提取响应内容
// 流式调用
chatClient
.prompt("问题")
.stream() // 切换到流式模式
.content(); // 返回 Flux<String>
虽然 ChatClient 的使用很简单,但内部实现却相当复杂。
.content() 提取文本内容。流式调用的关键是利用 Reactor 框架的 Flux。
// 调用链
chatClient.prompt().stream().content()
// 内部实现伪代码
Flux<String> streamContent() {
// 1. 创建长连接到 LLM API
// 2. 监听服务器发来的事件
// 3. 将每个 token 作为一个元素发出
return Flux.create(emitter -> {
// 当 LLM 发送一个 token 时
emitter.next(token);
});
}
关键特点:
onError 回调@GetMapping("/chat")
public ResponseEntity<?> chat(String question) {
try {
String response = chatClient.prompt(question).call().content();
return ResponseEntity.ok(response);
} catch (IllegalStateException e) {
// API Key 配置错误
return ResponseEntity.status(500).body("AI 服务配置错误:" + e.getMessage());
} catch (HttpClientErrorException e) {
// API 调用失败(流量限制、模型不存在等)
if (e.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
return ResponseEntity.status(429).body("请求过于频繁,请稍后再试");
}
return ResponseEntity.status(500).body("AI 服务异常:" + e.getMessage());
}
}
spring:
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen3-max
# 单次请求超时时间(秒)
temperature: 0.7
max-tokens: 2048
/**
* 统计 token 使用情况
*/
@GetMapping("/chat-with-metrics")
public Map<String, Object> chatWithMetrics(String question) {
ChatClientResponse response = chatClient.prompt(question).call();
Map<String, Object> result = new HashMap<>();
result.put("answer", response.getResult().getOutput().getContent());
// 获取 token 统计信息
if (response.getMetadata() != null) {
result.put("tokenUsage", Map.of(
"promptTokens", response.getMetadata().getUsage().getPromptTokens(),
"completionTokens", response.getMetadata().getUsage().getCompletionTokens(),
"totalTokens", response.getMetadata().getUsage().getTotalTokens()
));
}
return result;
}
A: 在以下三个地方确保使用 UTF-8:
pom.xml 中设置 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>MediaType("text", "event-stream", StandardCharsets.UTF_8)useUnicode=true&characterEncoding=utf-8A:
| 方面 | Flux | SseEmitter |
|---|---|---|
| 是否阻塞 | 非阻塞,基于 Reactor | 非阻塞,基于 Servlet 异步 |
| 响应头控制 | 自动处理 | 可完全自定义 |
| 客户端兼容性 | 需要支持 Server-Sent Events | 标准 SSE,广泛支持 |
| 推荐使用 | Spring Boot 3.0+ | 需要特殊控制时 |
A: 修改 application.yml 中的配置:
# 切换到 DeepSeek
spring:
ai:
openai:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-chat
# 或切换到 OpenAI
spring:
ai:
openai:
base-url: https://api.openai.com/v1
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4-turbo
A: Spring AI 支持通过 Spring AI 的日志观察机制:
spring:
ai:
chat:
client:
observations:
log-prompt: true # 记录发送给 LLM 的提示词
log-completion: true # 记录 LLM 的响应
或通过编程方式:
@Bean
public CallAdvisor loggingAdvisor() {
return new CallAdvisor() {
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
logger.info("请求内容:{}", request.prompt());
ChatClientResponse response = chain.nextCall(request);
logger.info("响应内容:{}", response.getResult().getOutput().getContent());
return response;
}
@Override
public String getName() {
return "LoggingAdvisor";
}
};
}
ChatClient 是线程安全的,建议全局使用单一实例:
// 推荐:全局单例
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder.build();
}
// 不推荐:每次创建新实例
public void chat() {
ChatClient client = new ChatClient(); // 错误做法
}
对于相同的问题,可以使用缓存避免重复调用 LLM:
private final Cache cache = new ConcurrentHashMapCache();
@GetMapping("/cached-chat")
public String cachedChat(String question) {
return cache.computeIfAbsent(question, q -> {
return chatClient.prompt(q).call().content();
});
}
对于批量处理任务,使用异步流处理:
@PostMapping("/batch-chat")
public ResponseEntity<?> batchChat(@RequestBody List<String> questions) {
return ResponseEntity.ok(
questions.parallelStream()
.map(q -> chatClient.prompt(q).call().content())
.collect(Collectors.toList())
);
}
Spring AI 生态极其丰富,本文只涉及核心基础。后续可深入学习:
# 1. 设置环境变量
export DASHSCOPE_API_KEY=your-api-key
# 2. 启动应用
cd spring-ai-demo
mvn spring-boot:run
# 3. 测试接口
curl "http://localhost:8081/chat?question=你好"
curl "http://localhost:8081/stream?question=请写一首诗"
curl "http://localhost:8081/entity?topic=春天"
| 功能 | 代码片段 | 说明 |
|---|---|---|
| 基础对话 | chatClient.prompt(q).call().content() | 同步获取完整回答 |
| 流式输出(Flux) | chatClient.prompt(q).stream().content() | 返回 Flux |
| 流式输出(SSE) | new SseEmitter() + stream.subscribe() | 完全控制响应头 |
| 系统提示词 | .system("你是...").user(q) | 定义 AI 角色 |
| 结构化输出 | outputConverter.convert(json) | JSON 转 Java 对象 |
| 直接转换 | .call().entity(Poem.class) | 一行代码转换 |

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online