
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及更早版本ServicepublicclassChatService{privatefinalChatClientchatClient;publicChatService(ChatClient.Builderbuilder){this.chatClientbuilder.defaultAdvisors(newMessageChatMemoryAdvisor(newInMemoryChatMemory())).build();}publicStringchat(Stringmessage){returnchatClient.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.6ServicepublicclassChatService{privatefinalChatClientchatClient;publicChatService(ChatClient.Builderbuilder){this.chatClientbuilder.defaultAdvisors(newMessageChatMemoryAdvisor(newInMemoryChatMemory())).build();}publicStringchat(StringsessionId,Stringmessage){returnchatClient.prompt().user(message).advisors(advisor-advisor.param(ChatMemory.CONVERSATION_ID,sessionId)// 必须传).call().content();}}关键变化chat()方法签名加了sessionId参数调用时必须通过.advisors()传CONVERSATION_ID2.4 如果你的代码已经部署生产环境怎么办两个选择推荐修改接口让前端传sessionId更规范临时方案后端生成默认sessionId不推荐但能快速止血// 临时方案后端生成默认sessionIdpublicStringchat(Stringmessage){StringsessionIddefault-session;// 或用UUID生成returnchatClient.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 StarterDeepSeek兼容OpenAI API --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-openai-spring-boot-starter/artifactIdversion1.1.6/version/dependencyapplication.yml配置spring:ai:openai:api-key:sk-xxxxx# DeepSeek的API Keybase-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-turboDeepSeek是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还是MySQL4.1 问题背景InMemoryChatMemory重启后对话历史全丢。生产环境需要持久化。Spring AI 1.1.x把对话记忆拆成两层ChatMemory逻辑层 ↓ ChatMemoryRepository存储层存储层可以选择InMemoryChatMemoryRepository默认内存JdbcChatMemoryRepositoryMySQL等数据库自己实现ChatMemoryRepository接口Redis等4.2 MySQL方案pom.xml加依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependency建表SQLCREATETABLEchat_memory(idBIGINTAUTO_INCREMENTPRIMARYKEY,conversation_idVARCHAR(255)NOTNULL,message_typeVARCHAR(50)NOTNULL,contentTEXTNOTNULL,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,INDEXidx_conversation_id(conversation_id));配置ChatMemoryConfigurationpublicclassChatMemoryConfig{BeanpublicChatMemorychatMemory(DataSourcedataSource){JdbcChatMemoryRepositoryrepositorynewJdbcChatMemoryRepository(dataSource);returnnewInMemoryChatMemory(repository);// 注入JDBC仓库}}4.3 Redis方案自己实现Spring AI官方没提供Redis实现需要自己写ComponentpublicclassRedisChatMemoryRepositoryimplementsChatMemoryRepository{privatefinalStringRedisTemplateredisTemplate;privatestaticfinalStringKEY_PREFIXchat:memory:;publicRedisChatMemoryRepository(StringRedisTemplateredisTemplate){this.redisTemplateredisTemplate;}Overridepublicvoidadd(StringconversationId,ListMessagemessages){StringkeyKEY_PREFIXconversationId;ListStringmessageJsonsmessages.stream().map(this::toJson).collect(Collectors.toList());redisTemplate.opsForList().rightPushAll(key,messageJsons);}OverridepublicListMessageget(StringconversationId,intlastN){StringkeyKEY_PREFIXconversationId;longsizeredisTemplate.opsForList().size(key);longstartMath.max(0,size-lastN);ListStringjsonsredisTemplate.opsForList().range(key,start,size-1);returnjsons.stream().map(this::fromJson).collect(Collectors.toList());}Overridepublicvoidclear(StringconversationId){redisTemplate.delete(KEY_PREFIXconversationId);}privateStringtoJson(Messagemessage){// 序列化Message对象为JSONreturnString.format({\type\:\%s\,\content\:\%s\},message.getMessageType(),message.getContent());}privateMessagefromJson(Stringjson){// 反序列化JSON为Message对象// 简化示例实际需要用ObjectMapperreturnnewUserMessage(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.java5.2 ChatConfig.javaConfigurationpublicclassChatConfig{BeanpublicChatClientchatClient(ChatClient.Builderbuilder,ChatMemorychatMemory){returnbuilder.defaultAdvisors(newMessageChatMemoryAdvisor(chatMemory)).build();}BeanpublicChatMemorychatMemory(DataSourcedataSource){JdbcChatMemoryRepositoryrepositorynewJdbcChatMemoryRepository(dataSource);returnnewInMemoryChatMemory(repository);}}5.3 ChatService.javaServicepublicclassChatService{privatefinalChatClientchatClient;publicChatService(ChatClientchatClient){this.chatClientchatClient;}publicStringchat(StringsessionId,Stringmessage){returnchatClient.prompt().user(message).advisors(advisor-advisor.param(ChatMemory.CONVERSATION_ID,sessionId)).call().content();}publicFluxStringchatStream(StringsessionId,Stringmessage){returnchatClient.prompt().user(message).advisors(advisor-advisor.param(ChatMemory.CONVERSATION_ID,sessionId)).stream().content();}}5.4 ChatController.javaRestControllerRequestMapping(/api/chat)publicclassChatController{privatefinalChatServicechatService;publicChatController(ChatServicechatService){this.chatServicechatService;}PostMappingpublicMapString,Stringchat(RequestBodyChatRequestrequest){StringresponsechatService.chat(request.getSessionId(),request.getMessage());returnMap.of(response,response);}PostMapping(/stream)publicFluxStringchatStream(RequestBodyChatRequestrequest){returnchatService.chatStream(request.getSessionId(),request.getMessage());}}DataclassChatRequest{privateStringsessionId;privateStringmessage;}六、总结升级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 null1.1.6移除默认值显式传递sessionId模型名称错误model gpt-4 not foundDeepSeek模型名不同配置model: deepseek-chat对话历史丢失重启后上下文消失用了InMemory切MySQL或RedisSpring AI更新很快1.1.4到1.1.6两个月出了三个版本。版本追得紧坑也踩得快。但踩过的坑记下来下次升级就不慌。这篇文章的代码都在我的GitHub仓库里能跑能测。不放假代码不放假链接。参考Spring AI 1.1.6 Release Noteshttps://docs.spring.io/spring-ai/reference/DeepSeek API文档https://platform.deepseek.com/docsChatMemory源码Spring AI 1.1.6org.springframework.ai.chat.memory.ChatMemory