Spring AI 1.1.6实战:从OpenAI切到DeepSeek,我踩了三个坑
Spring AI 1.1.6实战:从OpenAI切到DeepSeek,我踩了三个坑
2026年5月11日,Spring AI 1.1.6发布。
这次更新不大,但有一个breaking change(破坏性变更):聊天记忆的会话ID必须显式传递了。
如果你之前写的代码依赖默认会话ID,升级后直接报错。
这篇文章记录我升级Spring AI 1.1.6 + 接DeepSeek模型的全过程,三个真实踩坑,每个都有解决方案。
一、背景:为什么要升级到1.1.6?
先说结论:为了稳定性和安全修复。
Spring AI 1.1.6主要更新:
| 类型 | 数量 | 说明 |
|---|---|---|
| 新特性 | 2个 | 会话ID强制传递、运行时动态禁用结构化输出 |
| Bug修复 | 11个 | 包括潜在的拒绝服务漏洞(PDF解析导致内存溢出) |
| 安全增强 | 多项 | Transformer模型缓存目录权限加固 |
那个PDF解析漏洞必须修——恶意构造的PDF能让服务内存爆掉。
二、坑一:会话ID从可选变成必填
2.1 旧版本代码(能跑)
// Spring AI 1.1.5及更早版本
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
}
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
这段代码在1.1.5及更早版本能跑,会话ID自动用"default"。
2.2 升级后直接报错
IllegalArgumentException: conversationId must not be null
原因:Spring AI 1.1.6移除了DEFAULT_CONVERSATION_ID常量,不再提供默认值。
2.3 解决方案:显式传会话ID
// Spring AI 1.1.6
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
}
public String chat(String sessionId, String message) {
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(ChatMemory.CONVERSATION_ID, sessionId) // 必须传
)
.call()
.content();
}
}
关键变化:
chat()方法签名加了sessionId参数- 调用时必须通过
.advisors()传CONVERSATION_ID
2.4 如果你的代码已经部署生产环境怎么办?
两个选择:
- 推荐:修改接口,让前端传sessionId(更规范)
- 临时方案:后端生成默认sessionId(不推荐,但能快速止血)
// 临时方案:后端生成默认sessionId
public String chat(String message) {
String sessionId = "default-session"; // 或用UUID生成
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(ChatMemory.CONVERSATION_ID, sessionId)
)
.call()
.content();
}
三、坑二:从OpenAI切到DeepSeek,配置怎么改?
3.1 背景
OpenAI API贵,而且国内访问不稳定。DeepSeek便宜(1块钱100万token),国内直连。
Spring AI的设计理念是"一次编码,多模型运行",理论上换个配置就行。
3.2 实际配置
pom.xml依赖:
<!-- Spring AI OpenAI Starter(DeepSeek兼容OpenAI API) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.1.6</version>
</dependency>
application.yml配置:
spring:
ai:
openai:
api-key: sk-xxxxx # DeepSeek的API Key
base-url: https://api.deepseek.com # 关键:换成DeepSeek的地址
chat:
options:
model: deepseek-chat # DeepSeek模型名称
temperature: 0.7
就这么简单?
是的,Spring AI的抽象层确实做到了"换配置不换代码"。
3.3 踩坑:模型名称要改
OpenAI默认模型是gpt-4或gpt-3.5-turbo,DeepSeek是deepseek-chat。
如果你忘了改模型名称,会报:
Error: model 'gpt-4' not found
解决:在yaml里明确指定model: deepseek-chat,或者在代码里动态指定:
chatClient.prompt()
.user(message)
.options(OpenAiChatOptions.builder()
.withModel("deepseek-chat")
.build())
.call()
.content();
四、坑三:ChatMemory持久化,Redis还是MySQL?
4.1 问题背景
InMemoryChatMemory重启后对话历史全丢。生产环境需要持久化。
Spring AI 1.1.x把对话记忆拆成两层:
ChatMemory(逻辑层)
↓
ChatMemoryRepository(存储层)
存储层可以选择:
InMemoryChatMemoryRepository(默认,内存)JdbcChatMemoryRepository(MySQL等数据库)- 自己实现
ChatMemoryRepository接口(Redis等)
4.2 MySQL方案
pom.xml加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
建表SQL:
CREATE TABLE chat_memory (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
conversation_id VARCHAR(255) NOT NULL,
message_type VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_conversation_id (conversation_id)
);
配置ChatMemory:
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemory chatMemory(DataSource dataSource) {
JdbcChatMemoryRepository repository = new JdbcChatMemoryRepository(dataSource);
return new InMemoryChatMemory(repository); // 注入JDBC仓库
}
}
4.3 Redis方案(自己实现)
Spring AI官方没提供Redis实现,需要自己写:
@Component
public class RedisChatMemoryRepository implements ChatMemoryRepository {
private final StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = "chat:memory:";
public RedisChatMemoryRepository(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void add(String conversationId, List<Message> messages) {
String key = KEY_PREFIX + conversationId;
List<String> messageJsons = messages.stream()
.map(this::toJson)
.collect(Collectors.toList());
redisTemplate.opsForList().rightPushAll(key, messageJsons);
}
@Override
public List<Message> get(String conversationId, int lastN) {
String key = KEY_PREFIX + conversationId;
long size = redisTemplate.opsForList().size(key);
long start = Math.max(0, size - lastN);
List<String> jsons = redisTemplate.opsForList().range(key, start, size - 1);
return jsons.stream()
.map(this::fromJson)
.collect(Collectors.toList());
}
@Override
public void clear(String conversationId) {
redisTemplate.delete(KEY_PREFIX + conversationId);
}
private String toJson(Message message) {
// 序列化Message对象为JSON
return String.format("{\"type\":\"%s\",\"content\":\"%s\"}",
message.getMessageType(), message.getContent());
}
private Message fromJson(String json) {
// 反序列化JSON为Message对象
// 简化示例,实际需要用ObjectMapper
return new UserMessage(json);
}
}
4.4 选型建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| InMemory | 简单、无依赖 | 重启丢失 | 开发测试 |
| MySQL | 持久化、易查询 | 性能一般、需要建表 | 中小规模、需要审计日志 |
| Redis | 性能高、天然过期 | 需要自己实现 | 大规模、高并发 |
我的选择:先上MySQL,有性能瓶颈再切Redis。
五、完整代码示例
5.1 项目结构
src/main/java/com/example/
├── config/
│ └── ChatConfig.java
├── controller/
│ └── ChatController.java
├── service/
│ └── ChatService.java
└── Application.java
5.2 ChatConfig.java
@Configuration
public class ChatConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
ChatMemory chatMemory) {
return builder
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}
@Bean
public ChatMemory chatMemory(DataSource dataSource) {
JdbcChatMemoryRepository repository = new JdbcChatMemoryRepository(dataSource);
return new InMemoryChatMemory(repository);
}
}
5.3 ChatService.java
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient chatClient) {
this.chatClient = chatClient;
}
public String chat(String sessionId, String message) {
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(ChatMemory.CONVERSATION_ID, sessionId)
)
.call()
.content();
}
public Flux<String> chatStream(String sessionId, String message) {
return chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(ChatMemory.CONVERSATION_ID, sessionId)
)
.stream()
.content();
}
}
5.4 ChatController.java
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping
public Map<String, String> chat(@RequestBody ChatRequest request) {
String response = chatService.chat(request.getSessionId(), request.getMessage());
return Map.of("response", response);
}
@PostMapping("/stream")
public Flux<String> chatStream(@RequestBody ChatRequest request) {
return chatService.chatStream(request.getSessionId(), request.getMessage());
}
}
@Data
class ChatRequest {
private String sessionId;
private String message;
}
六、总结:升级Checklist
如果你要从旧版本升级到Spring AI 1.1.6,按这个清单检查:
- 检查所有使用ChatMemory的地方,确保传递了
CONVERSATION_ID - 如果用OpenAI以外的模型,检查
base-url和model配置 - 评估是否需要持久化ChatMemory(生产环境必须)
- 测试PDF解析场景,确认安全漏洞已修复
- 关注Spring AI 2.0.0 M6进展(下一个大版本)
七、踩坑记录
| 坑 | 现象 | 原因 | 解决 |
|---|---|---|---|
| 会话ID必填 | IllegalArgumentException: conversationId must not be null |
1.1.6移除默认值 | 显式传递sessionId |
| 模型名称错误 | model 'gpt-4' not found |
DeepSeek模型名不同 | 配置model: deepseek-chat |
| 对话历史丢失 | 重启后上下文消失 | 用了InMemory | 切MySQL或Redis |
Spring AI更新很快,1.1.4到1.1.6两个月出了三个版本。
版本追得紧,坑也踩得快。但踩过的坑记下来,下次升级就不慌。
这篇文章的代码都在我的GitHub仓库里,能跑能测。不放假代码,不放假链接。
参考:
- Spring AI 1.1.6 Release Notes:https://docs.spring.io/spring-ai/reference/
- DeepSeek API文档:https://platform.deepseek.com/docs
- ChatMemory源码:Spring AI 1.1.6
org.springframework.ai.chat.memory.ChatMemory
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)