资讯详情

资讯详情

JAVA25+Spring Boot 4.1.0 +Spring AI 2.0.1 调用模型之长记忆

前置文章JAVA25 Spring Boot 4.1.0 Spring AI 2.0.1 调用 DeepSeek 模型前言大型语言模型LLM本身是无状态的也就是说它不会保留此前交互过的信息。当我们需要在多次交互之间维持上下文或状态时这便成了一个明显的限制。为此Spring AI 提供了聊天记忆Chat Memory功能让我们可以在与 LLM 的多轮交互中存储和检索信息。借助 ChatMemory 这一抽象层我们可以实现多种类型的记忆存储以适配不同的使用场景。本文在前置文章的基础上进一步扩展讲解如何在项目中调用 Chat Memory 功能。按照 Spring AI 2.0.1 中记忆存储方式的不同本篇把最常用的方式归纳为四类第一类是内存存储消息保存在内存中第二类是缓存存储消息保存在 Redis 缓存中第三类是 JDBC 存储消息保存到项目所连接的结构化数据库第四类是向量存储消息保存到项目配置的向量数据库。实际使用时可以根据业务需要选择合适的方式。当然Spring AI 2.0.1 的记忆存储并不止本篇介绍的这几种更多用法可以查阅 Spring AI 2.0.1 官网 API Chat Memory。一、内存存储内存存储无需引入任何第三方依赖开箱即可使用。1、修改前置文章中访问模型请求参数对象AiChatRequest/** * description: 业务接口请求参数对象 * author: LIHAO * createDate: 2026-10-27 14:48:25 */BuilderSchema(title业务接口请求参数对象)publicrecordAiChatRequest(NotBlank(message用户消息不能为空)Schema(description用户消息,requiredModeSchema.RequiredMode.REQUIRED)Stringmessage,Schema(description对话标识 可为空由业务传递保存记忆和保存链路日志时使用)StringconversationId,Schema(description是否启用jdbc长记忆保存启用后会保存问题到jdbc数据库并时带上上下文访问模型)BooleanisAddJDBCMemoryAdvisor,Schema(description是否启用向量长记忆保存启用后会保存问题到向量数据库并带上问题相关的上下文访问模型)BooleanisAddVectorMemoryAdvisor)implementsSerializable{SerialprivatestaticfinallongserialVersionUID1L;}2、修改前置文章中 Service修改有参构造方法//记忆存储对象privatefinalChatMemorychatMemory;/** * 构造函数通过依赖注入获取 ChatClient.Builder。 * * Spring AI 的自动配置会创建 DeepSeekChatClient 实例 * 并通过 Builder 模式提供给我们。 * */publicAiChatService(Qualifier(deepSeekChatModel)ChatModeldeepSeekChatModel,Qualifier(openAiChatModel)ChatModelopenAiChatModel,Qualifier(anthropicChatModel)ChatModelanthropicChatModel,ChatMemorychatMemory){this.chatMemorychatMemory;// 1. 使用 builder 配置默认系统提示词// defaultSystem()设置系统级提示词在所有对话中生效// build()最终创建 ChatClient 实例this.chatClientChatClient.builder(deepSeekChatModel).defaultSystem(请用中文回复).build();}注意ChatMemory 是 Spring AI 自动配置好的一个 ChatMemory Bean可以直接在应用程序中使用。默认情况下它使用内存存储库 InMemoryChatMemoryRepository 来保存消息并通过 MessageWindowChatMemory 实现来管理对话历史记录。如果已经配置了其他存储库例如 Cassandra、Redis、JDBC 或 Neo4jSpring AI 的 ChatMemory 就会改用该存储库。添加存储记忆顾问/** * description: 添加记忆顾问包含结构数据库和向量数据库 * author: LIHAO * createDate: 2026-10-29 11:06:03 */privatevoidaddMemoryAdvisor(AiChatRequestrequest,ChatClient.ChatClientRequestSpecchatClientRequestSpec){if(ObjectUtils.isNotEmpty(request.isAddJDBCMemoryAdvisor())request.isAddJDBCMemoryAdvisor()||ObjectUtils.isNotEmpty(request.isAddVectorMemoryAdvisor())request.isAddVectorMemoryAdvisor()){//会话标识不能为空由业务传递StringconversationIdrequest.conversationId();//判断值Assert.notNull(conversationId,SysAiChatServiceImpl#addMemoryAdvisor添加记忆顾问参数conversationId不能为空);//判断是否添加jdbc存储if(ObjectUtils.isNotEmpty(request.isAddJDBCMemoryAdvisor())request.isAddJDBCMemoryAdvisor()){//判断长时记忆顾问是否为空if(ObjectUtils.isNotEmpty(chatMemory)){//添加长时记忆顾问顾问//添加长时记忆顾问顾问chatClientRequestSpec.advisors(MessageChatMemoryAdvisor.builder(chatMemory).order(1).build());}}//判断是否添加向量存储if(ObjectUtils.isNotEmpty(request.isAddVectorMemoryAdvisor())request.isAddVectorMemoryAdvisor()){//判断向量长时记忆顾问是否为空}//传递 conversationIdif(StringUtils.isNotEmpty(conversationId)){chatClientRequestSpec.advisors(advisor-advisor.param(ChatMemory.CONVERSATION_ID,conversationId));//配置记忆顾问对应业务参数配置默认顾问调用时必须传值给顾问否则会报错);}}}注意conversationId 是业务侧传入的自定义值代表一组对话的 ID。不同组对话的 conversationId 可以不同但同一组对话的 conversationId 必须保持一致否则就无法找到对应的历史记录。所有调用模型方法添加addMemoryAdvisor方法调用/** * description: 单次模型访问一次性返回文本结果 * author: LIHAO * createDate: 2026-10-27 15:05:11 * param: [request] * return: java.lang.String */publicStringgenerateText(AiChatRequestrequest){Assert.notNull(request,SysAiChatService#generateText参数request不能为空);//添加文件信息并返回用户消息Stringmessagerequest.message();//创建 配置单次请求的参数对象ChatClient.ChatClientRequestSpecchatClientRequestSpecchatClient.prompt().user(message);//添加记忆顾问addMemoryAdvisor(request,chatClientRequestSpec);//访问模型并返回returnchatClientRequestSpec.call().content();}/** * description: 单次模型访问一次性返回实体对象结果 * author: LIHAO * createDate: 2026-10-27 16:20:00 */publicActorsFilmsgenerateEntity(AiChatRequestrequest){Assert.notNull(request,SysAiChatService#generateEntity参数request不能为空);//添加文件信息并返回用户消息Stringmessagerequest.message();//创建 配置单次请求的参数对象ChatClient.ChatClientRequestSpecchatClientRequestSpecchatClient.prompt().user(message);//添加记忆顾问addMemoryAdvisor(request,chatClientRequestSpec);//访问模型并返回returnchatClientRequestSpec.call()// 调用大模型// 指定目标类型并开启自修正.entity(ActorsFilms.class,spec-spec.validateSchema()//验证模型返回数据格式是否符合实体对象类型);}/** * description: 单次模型访问流式返回文本结果 * author: LIHAO * createDate: 2026-10-27 16:25:11 * param: [request] * return: reactor.core.publisher.Fluxjava.lang.String */publicFluxStringstreamText(AiChatRequestrequest){Assert.notNull(request,SysAiChatService#streamText参数request不能为空);//添加文件信息并返回用户消息Stringmessagerequest.message();//创建 配置单次请求的参数对象ChatClient.ChatClientRequestSpecchatClientRequestSpecchatClient.prompt().user(message);//添加记忆顾问addMemoryAdvisor(request,chatClientRequestSpec);//访问returnchatClientRequestSpec.stream().content();}/** * description: 单次模型访问流式返回结构化数据对象结果 * author: LIHAO * createDate: 2026-10-27 16:58:14 * param: [request] * return: reactor.core.publisher.Fluxcom.lh.dto.StreamEvent */publicFluxStreamEventstreamEvents(AiChatRequestrequest){Assert.notNull(request,SysAiChatService#streamText参数request不能为空);//添加文件信息并返回用户消息Stringmessagerequest.message();//创建 配置单次请求的参数对象ChatClient.ChatClientRequestSpecchatClientRequestSpecchatClient.prompt().user(message);//添加记忆顾问addMemoryAdvisor(request,chatClientRequestSpec);//AtomicReference 多线程原子性toEvents 跑在 concatMap 的 mapper 里和 supplier 很可能不在同一个线程AtomicReferenceStringfinishReasonnewAtomicReference();AtomicReferenceMapString,ObjectusagenewAtomicReference();returnchatClientRequestSpec.stream()// .chatResponse() 拿到完整 ChatResponse可取 finishReason / usage / metadata.chatResponse()//流每次返回数据触发.concatMap(resp-toEvents(resp,finishReason,usage))// concatWith 拦截的是主流的 onComplete事件不让它提前传给下游// 待上游完成后再订阅第二个流把 finish 帧追加到尾部// 等第二个流也完成后才把 onComplete 传给下游。.concatWith(// Mono.fromSupplier惰性求值 —— concatWith 等上游 complete 之后才订阅第二个源Mono.fromSupplier(()-StreamEvent.finish(finishReason.get()null?stop:finishReason.get(),// 拿不到就兜底 stop别让前端猜usage.get())));}二、Redis 储存博主使用的 Redis 8.8.0服务器上安装的 Redis 版本最好在 6.0 及以上否则项目启动连接低版本 Redis 会报错。1、Maven 引入!-- redis记忆储存 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-chat-memory-repository-redis/artifactId/dependency2、添加 application.yml 配置# 配置文件# 生产环境配置文件server:# 启动端口port:8001# Spring 配置spring:# 应用程序名称application:name:knowledge-base-agent# Spring AI 配置 ai:# DeepSeek 配置 deepseek:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址国内可正常访问base-url:https://api.deepseek.comchat:# 推荐使用最新模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096# 思考模式thinking:type:disabled# 关键配置禁用思考模式 (enabled:开启思考模式/disabled:禁用思考模式)options:timeout:5000# 5 秒超时# OpenAI 配置 openai:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址base-url:https://api.deepseek.comchat:# 推荐使用的模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096#配置模型关闭思考模式extra-body下的数据会合并到参数bodyextra-body:thinking:type:disabledoptions:timeout:50000# 5 秒超时# Anthropic 配置 anthropic:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址base-url:https://api.deepseek.com/anthropicchat:# 推荐使用的模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096# Chat 配置 chat:# 记忆存储配置 memory:repository:# redis记忆储存配置 redis:host:localhostport:6379# knife4j配置(在线接口测试)knife4j:# 开启增强配置enable:true# 开启Swagger的Basic认证功能,默认是falsebasic:enable:false# Basic认证用户名username:admin# Basic认证密码password:123# 开启生产环境屏蔽(当我们部署系统到生产系统,为了接口安全,需要屏蔽所有Swagger的相关资源)production:false3、修改前置文章中 Service这里的修改内容与「一、内存存储」中「1、修改前置文章中 Service」完全一致无需再做改动。原因在于ChatMemory 是 Spring AI 自动配置好的一个 ChatMemory Bean可以直接在应用程序中注入使用。默认情况下它采用内存存储库来保存消息一旦配置了其他存储库例如 Cassandra、Redis、JDBC 或 Neo4jSpring AI 的 ChatMemory 便会自动切换到该存储库。三、JDBC储存博主项目数据库使用的是 MySQL 5.7 MyBatis-Plus要先确保项目已经可以正常连接数据库且可以正常操作业务数据。注意JDBC 储存和 Redis 储存只能二选一不能同时存在。如果 Maven 同时引入Spring AI 创建 ChatMemory Bean会覆盖掉前一个配置。1、Maven 引入!-- jdbc记忆储存 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId/dependency2、添加 application.yml 配置# 配置文件# 生产环境配置文件server:# 启动端口port:8001# Spring 配置spring:# 应用程序名称application:name:knowledge-base-agent# Spring AI 配置 ai:# DeepSeek 配置 deepseek:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址国内可正常访问base-url:https://api.deepseek.comchat:# 推荐使用最新模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096# 思考模式thinking:type:disabled# 关键配置禁用思考模式 (enabled:开启思考模式/disabled:禁用思考模式)options:timeout:5000# 5 秒超时# OpenAI 配置 openai:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址base-url:https://api.deepseek.comchat:# 推荐使用的模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096#配置模型关闭思考模式extra-body下的数据会合并到参数bodyextra-body:thinking:type:disabledoptions:timeout:50000# 5 秒超时# Anthropic 配置 anthropic:# API Key从环境变量读取安全也可直接填写api-key:自己账号deepseek的key# DeepSeek 官方 API 地址base-url:https://api.deepseek.com/anthropicchat:# 推荐使用的模型model:deepseek-v4-flash# 温度0~2值越高输出越随机0.7 平衡创意与准确性temperature:0.7# 最大输出 Token 数可根据需要调整max-tokens:4096# Chat 配置 chat:# 记忆存储配置 memory:repository:# jdbc记忆储存配置 jdbc:initialize-schema:always# 开发时自动建表# knife4j配置(在线接口测试)knife4j:# 开启增强配置enable:true# 开启Swagger的Basic认证功能,默认是falsebasic:enable:false# Basic认证用户名username:admin# Basic认证密码password:123# 开启生产环境屏蔽(当我们部署系统到生产系统,为了接口安全,需要屏蔽所有Swagger的相关资源)production:false注意写好jdbc配置启动项目时会自动创建spring_ai_chat_memory的表来保存记录。3、修改前置文章中 Service这里的修改内容与「一、内存存储」中「1、修改前置文章中 Service」完全一致无需再做改动。原因在于ChatMemory 是 Spring AI 自动配置好的一个 ChatMemory Bean可以直接在应用程序中注入使用。默认情况下它采用内存存储库来保存消息一旦配置了其他存储库例如 Cassandra、Redis、JDBC 或 Neo4jSpring AI 的 ChatMemory 便会自动切换到该存储库。四、向量存储向量存储与前三类不同可以与前三类同时存在具体操作会在后面引入向量数据时再详细说明。五、接口测试启动项目访问 http://localhost:8001/doc.html六、总结本文围绕 Spring AI 2.0.1 的聊天记忆Chat Memory能力从前置文章的模型调用代码出发依次演示了内存、Redis、JDBC 三种存储方式的接入过程并对向量存储做了简要说明。整体来看Spring AI 把「记忆」这件事抽象得非常干净业务代码只需要注入一个ChatMemoryBean再通过MessageChatMemoryAdvisor把conversationId传进去剩下的消息存取、窗口裁剪都由框架完成切换存储介质时几乎不用改动业务逻辑。几种存储方式的选型可以参考下表存储方式依赖适用场景注意事项内存存储无需额外依赖本地开发、单机演示、临时会话应用重启后记忆丢失多实例之间不共享Redis 存储spring-ai-starter-model-chat-memory-repository-redis多实例部署、需要跨节点共享会话Redis 版本建议 6.0否则启动连接会报错JDBC 存储spring-ai-starter-model-chat-memory-repository-jdbc已有关系型数据库、希望记忆持久化可查与 Redis 存储互斥同时引入会被覆盖initialize-schema: always会自动建表向量存储向量数据库相关依赖需要语义检索、长期知识沉淀可与前三类共存属于另一条能力线几个实践中的要点值得再强调一次conversationId是记忆的钥匙。同一组对话必须始终传同一个值否则模型找不到历史记录表现为「记忆失效」。建议由业务侧统一生成并持久化例如「用户 ID 会话 ID」的组合。存储实现只能选一个向量存储除外。内存、Redis、JDBC 三者最终都会产出同一个ChatMemoryBean同时引入时后加载的配置会覆盖前一个排查问题时容易误判建议在 Maven 依赖层面就做好隔离。记忆不是无限的。默认的MessageWindowChatMemory只保留最近若干条消息超出窗口的会被丢弃。如果业务对上下文长度有更高要求可以自定义ChatMemory实现或结合向量存储做长期记忆召回。聊天记忆 ≠ 聊天历史。ChatMemory面向的是「当前对话上下文」而不是全量消息归档需要完整审计或回溯时应另建表用 Spring Data 等方式单独存储。至此Spring AI 聊天记忆的几种常用落地方式就介绍完了。后续文章会继续围绕向量存储与 RAG 展开把「短期记忆 长期知识」组合成一套完整的对话应用方案。如果本文对你有帮助欢迎点赞收藏也欢迎在评论区交流你在实际项目中遇到的记忆管理问题。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →