告别 AI 对话 “失忆”!Spring AI 聊天记忆底层原理与全场景落地实战

张开发
2026/4/16 20:49:40 15 分钟阅读

分享文章

告别 AI 对话 “失忆”!Spring AI 聊天记忆底层原理与全场景落地实战
很多开发者在集成AI对话功能时都会遇到一个核心痛点多轮对话中AI完全无法记住之前的沟通内容每次对话都像首次交互用户体验极差。而Spring AI作为Spring官方推出的AI应用开发框架提供了一套完整、低侵入、高扩展的聊天记忆解决方案彻底解决了大模型对话的无状态问题。一、大模型对话的无状态本质与聊天记忆的核心逻辑1.1 大模型为什么会“失忆”大语言模型LLM的核心能力是基于输入的文本序列进行概率推理生成符合语义的下一个token。它本身没有内置的持久化存储能力每一次API调用都是完全独立的无状态请求模型不会自动保存、读取上一次对话的任何内容。 举个通俗的例子你第一次问“Java的三大特性是什么”AI给出了封装、继承、多态的回答当你第二次问“那第一个特性的核心设计思想是什么”如果没有记忆机制模型完全无法感知“第一个特性”对应的上下文只能给出无意义的泛化回答。1.2 聊天记忆的本质与核心三要素聊天记忆的本质是对用户与AI的历史对话消息进行有序管理、持久化存储、按需调度并在每次对话请求时将符合规则的历史内容注入到当前请求的Prompt中让模型能够感知完整的上下文实现连贯的多轮对话。 一套合格的聊天记忆体系必须满足三个核心要素消息有序性严格区分消息类型保证用户提问与AI回复的时间顺序与成对关系避免上下文错乱会话隔离性不同用户、不同会话的记忆数据完全隔离杜绝数据串扰与越权访问窗口可控性可灵活控制历史消息的长度避免内容过多导致模型token超限保障请求稳定性与成本可控二、Spring AI 聊天记忆的核心架构与组件Spring AI的聊天记忆体系基于Spring的原生设计理念采用分层架构解耦了消息管理、持久化、请求拦截三大核心能力具备极高的扩展性。2.1 核心架构图2.2 核心组件详解Spring AI的聊天记忆能力围绕三个核心顶级接口展开配合消息模型与拦截器机制构成了完整的能力闭环。2.2.1 Message 消息体系Spring AI中所有对话内容都被封装为Message接口的实现类核心实现分为三类是记忆体系的最小数据单元SystemMessage系统提示词用于给模型设定角色、行为规则、输出边界通常放在对话序列的最开头单个会话一般仅保留一个UserMessage用户输入的提问内容对应对话中的用户侧消息AssistantMessageAI模型生成的回复内容对应对话中的AI侧消息历史对话序列由UserMessage与AssistantMessage成对组成严格按照交互时间排序是模型感知上下文的核心依据。2.2.2 ChatMemory 会话记忆接口ChatMemory是会话级记忆的核心接口负责管理单个会话的全量消息是记忆体系的内存操作入口核心方法包括add(Message... messages)向当前会话追加消息getMessages()获取当前会话的全量有序消息clear()清空当前会话的所有消息getConversationId()获取当前会话的唯一标识用于会话隔离Spring AI提供了默认的InMemoryChatMemory实现基于ConcurrentHashMap实现内存级的消息管理适合单机测试与低并发场景。2.2.3 ChatMemoryRepository 持久化仓库接口ChatMemoryRepository是持久化层的核心抽象接口负责全量会话记忆的加载与持久化是实现自定义存储的核心扩展点核心方法包括getMessages(String conversationId)根据会话ID加载对应的历史消息序列addMessage(String conversationId, Message message)向指定会话追加单条消息addMessages(String conversationId, ListMessage messages)向指定会话批量追加消息clear(String conversationId)清空指定会话的所有消息listConversationIds()获取全量会话ID列表该接口完全解耦了记忆逻辑与存储介质开发者可以通过实现该接口对接MySQL、Redis、MongoDB等任意存储系统实现生产级的持久化能力。2.2.4 ChatMemoryAdvisor 拦截器ChatMemoryAdvisor是Spring AI记忆机制的核心调度器基于Spring的AOP设计理念实现了记忆能力与对话流程的无侵入集成前置拦截在对话请求发送给大模型之前自动从ChatMemoryRepository中加载对应会话的历史消息注入到当前请求的Prompt中后置拦截在收到大模型的回复后自动将当前用户消息与AI回复追加到ChatMemoryRepository中完成记忆的持久化开发者无需手动处理历史消息的拼接、存储仅需通过配置即可实现完整的记忆能力极大降低了开发成本。三、环境搭建与基础依赖3.1 项目基础环境项目基于JDK 17开发采用Maven进行项目管理基础环境如下Spring Boot 3.4.5Spring AI 1.2.0MyBatis-Plus 3.5.7MySQL 8.0Lombok 1.18.30FastJSON2 2.0.52Guava 33.2.1-jreSpringDoc OpenAPI 2.6.03.2 Maven 核心依赖配置?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.5/version relativePath/ /parent groupIdcom.jam/groupId artifactIdspring-ai-chat-memory-demo/artifactId version0.0.1-SNAPSHOT/version namespring-ai-chat-memory-demo/name descriptionSpring AI Chat Memory Demo/description properties java.version17/java.version spring-ai.version1.2.0/spring-ai.version mybatis-plus.version3.5.7/mybatis-plus.version fastjson2.version2.0.52/fastjson2.version guava.version33.2.1-jre/guava.version springdoc.version2.6.0/springdoc.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-openai/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.30/version scopeprovided/scope /dependency dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version${fastjson2.version}/version /dependency dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version${guava.version}/version /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project3.3 应用配置文件spring: application: name: spring-ai-chat-memory-demo datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/ai_demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password ai: openai: api-key: your_api_key base-url: your_base_url chat: options: model: gpt-3.5-turbo temperature: 0.7 max-tokens: 2048 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: isDeleted logic-delete-value: 1 logic-not-delete-value: 0 springdoc: api-docs: enabled: true swagger-ui: enabled: true path: /swagger-ui.html server: port: 8080四、快速上手内存级聊天记忆实现基于Spring AI内置的InMemoryChatMemory可以快速实现内存级的聊天记忆能力无需额外的存储依赖适合本地测试与功能验证。4.1 ChatClient 配置类package com.jam.demo.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.ChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * ChatClient配置类 * author ken */ Configuration public class ChatClientConfig { /** * 配置内存级会话记忆 * return 会话记忆实例 */ Bean public ChatMemory inMemoryChatMemory() { return new InMemoryChatMemory(); } /** * 配置带记忆能力的ChatClient * param builder ChatClient构建器 * param chatMemory 会话记忆实例 * return 带记忆能力的ChatClient实例 */ Bean public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) { return builder .defaultAdvisors(ChatMemoryAdvisor.builder(chatMemory).build()) .build(); } }4.2 对话服务层实现package com.jam.demo.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * 内存级对话服务实现 * author ken */ Slf4j Service RequiredArgsConstructor public class InMemoryChatService { private final ChatClient chatClient; /** * 带记忆的对话 * param conversationId 会话唯一标识 * param content 用户提问内容 * return AI回复内容 */ public String chatWithMemory(String conversationId, String content) { if (!StringUtils.hasText(conversationId)) { throw new IllegalArgumentException(会话ID不能为空); } if (!StringUtils.hasText(content)) { throw new IllegalArgumentException(提问内容不能为空); } return chatClient.prompt() .user(content) .conversationId(conversationId) .call() .content(); } /** * 清空指定会话的记忆 * param conversationId 会话唯一标识 */ public void clearChatMemory(String conversationId) { if (!StringUtils.hasText(conversationId)) { throw new IllegalArgumentException(会话ID不能为空); } chatClient.prompt() .conversationId(conversationId) .call() .chatMemory() .clear(); log.info(会话{}的记忆已清空, conversationId); } }4.3 对话接口实现package com.jam.demo.controller; import com.jam.demo.service.InMemoryChatService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 内存级对话接口 * author ken */ RestController RequestMapping(/api/chat/in-memory) RequiredArgsConstructor Tag(name 内存级对话接口, description 基于内存的带记忆对话能力) public class InMemoryChatController { private final InMemoryChatService inMemoryChatService; /** * 带记忆的对话接口 */ PostMapping(/send) Operation(summary 发送带记忆的对话请求, description 同一个conversationId的请求会共享上下文记忆) public ResponseEntityMapString, String chat( Parameter(description 会话唯一标识, required true) RequestParam String conversationId, Parameter(description 用户提问内容, required true) RequestParam String content) { String result inMemoryChatService.chatWithMemory(conversationId, content); return ResponseEntity.ok(Map.of(content, result)); } /** * 清空会话记忆接口 */ DeleteMapping(/clear) Operation(summary 清空指定会话的记忆, description 清空后该会话的历史上下文将丢失) public ResponseEntityVoid clearMemory( Parameter(description 会话唯一标识, required true) RequestParam String conversationId) { inMemoryChatService.clearChatMemory(conversationId); return ResponseEntity.noContent().build(); } }4.4 内存级实现的优缺点优点集成简单、无额外存储依赖、请求延迟低、适合本地功能验证缺点应用重启后记忆数据完全丢失、无法在分布式环境中共享会话、内存占用会随会话数量持续增长仅适合测试场景不可用于生产环境五、生产级落地基于MySQL的持久化聊天记忆实现生产环境中我们需要解决内存级实现的核心缺陷通过实现Spring AI的ChatMemoryRepository接口对接MySQL数据库实现记忆数据的持久化支持分布式环境部署保障数据不丢失。5.1 数据库表结构设计CREATE TABLE ai_chat_memory ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, conversation_id varchar(64) NOT NULL COMMENT 会话唯一标识, message_type varchar(16) NOT NULL COMMENT 消息类型SYSTEM/USER/ASSISTANT, content text NOT NULL COMMENT 消息内容, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, is_deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除标识0-未删除1-已删除, PRIMARY KEY (id), UNIQUE KEY uk_conversation_id_create_time (conversation_id,create_time), KEY idx_conversation_id (conversation_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENTAI对话记忆表;5.2 实体类定义package com.jam.demo.entity; import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; /** * 对话记忆实体类 * author ken */ Data TableName(ai_chat_memory) Schema(description 对话记忆实体) public class ChatMemoryEntity { Schema(description 主键ID) TableId(type IdType.AUTO) private Long id; Schema(description 会话唯一标识) TableField(conversation_id) private String conversationId; Schema(description 消息类型SYSTEM/USER/ASSISTANT) TableField(message_type) private String messageType; Schema(description 消息内容) TableField(content) private String content; Schema(description 创建时间) TableField(value create_time, fill FieldFill.INSERT) private LocalDateTime createTime; Schema(description 更新时间) TableField(value update_time, fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; Schema(description 逻辑删除标识) TableField(is_deleted) TableLogic private Integer isDeleted; }5.3 Mapper接口定义package com.jam.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.jam.demo.entity.ChatMemoryEntity; import org.apache.ibatis.annotations.Mapper; /** * 对话记忆Mapper接口 * author ken */ Mapper public interface ChatMemoryMapper extends BaseMapperChatMemoryEntity { }5.4 自定义持久化ChatMemoryRepository实现package com.jam.demo.repository; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.google.common.collect.Lists; import com.jam.demo.entity.ChatMemoryEntity; import com.jam.demo.mapper.ChatMemoryMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.MessageType; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.stereotype.Repository; import org.springframework.transaction.TransactionTemplate; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import java.util.List; /** * 基于MySQL的对话记忆持久化仓库实现 * author ken */ Slf4j Repository RequiredArgsConstructor public class JdbcChatMemoryRepository implements ChatMemoryRepository { private final ChatMemoryMapper chatMemoryMapper; private final TransactionTemplate transactionTemplate; Override public ListMessage getMessages(String conversationId) { if (!StringUtils.hasText(conversationId)) { return Lists.newArrayList(); } ListChatMemoryEntity entityList chatMemoryMapper.selectList( new LambdaQueryWrapperChatMemoryEntity() .eq(ChatMemoryEntity::getConversationId, conversationId) .orderByAsc(ChatMemoryEntity::getCreateTime) ); if (CollectionUtils.isEmpty(entityList)) { return Lists.newArrayList(); } ListMessage messageList Lists.newArrayList(); for (ChatMemoryEntity entity : entityList) { MessageType messageType MessageType.valueOf(entity.getMessageType()); switch (messageType) { case SYSTEM - messageList.add(new SystemMessage(entity.getContent())); case USER - messageList.add(new UserMessage(entity.getContent())); case ASSISTANT - messageList.add(new AssistantMessage(entity.getContent())); default - log.warn(不支持的消息类型:{}, messageType); } } return messageList; } Override public void addMessage(String conversationId, Message message) { if (!StringUtils.hasText(conversationId) || ObjectUtils.isEmpty(message)) { return; } ChatMemoryEntity entity buildChatMemoryEntity(conversationId, message); chatMemoryMapper.insert(entity); log.debug(会话{}新增消息成功消息类型:{}, conversationId, message.getMessageType()); } Override public void addMessages(String conversationId, ListMessage messages) { if (!StringUtils.hasText(conversationId) || CollectionUtils.isEmpty(messages)) { return; } transactionTemplate.execute(status - { ListChatMemoryEntity entityList Lists.newArrayList(); for (Message message : messages) { entityList.add(buildChatMemoryEntity(conversationId, message)); } for (ChatMemoryEntity entity : entityList) { chatMemoryMapper.insert(entity); } return Boolean.TRUE; }); log.debug(会话{}批量新增{}条消息成功, conversationId, messages.size()); } Override public void clear(String conversationId) { if (!StringUtils.hasText(conversationId)) { return; } chatMemoryMapper.delete( new LambdaQueryWrapperChatMemoryEntity() .eq(ChatMemoryEntity::getConversationId, conversationId) ); log.info(会话{}的记忆已清空, conversationId); } Override public ListString listConversationIds() { ListChatMemoryEntity entityList chatMemoryMapper.selectList( new LambdaQueryWrapperChatMemoryEntity() .select(ChatMemoryEntity::getConversationId) .distinct() ); return entityList.stream() .map(ChatMemoryEntity::getConversationId) .toList(); } /** * 构建对话记忆实体 * param conversationId 会话ID * param message 消息对象 * return 对话记忆实体 */ private ChatMemoryEntity buildChatMemoryEntity(String conversationId, Message message) { ChatMemoryEntity entity new ChatMemoryEntity(); entity.setConversationId(conversationId); entity.setMessageType(message.getMessageType().name()); entity.setContent(message.getText()); return entity; } }5.5 持久化ChatClient配置package com.jam.demo.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.ChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; /** * 持久化ChatClient配置类 * author ken */ Configuration public class PersistentChatClientConfig { /** * 配置带滑动窗口的持久化会话记忆 * param chatMemoryRepository 持久化仓库 * return 会话记忆实例 */ Bean Primary public ChatMemory persistentChatMemory(ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder() .maxMessages(20) .chatMemoryRepository(chatMemoryRepository) .build(); } /** * 配置带持久化记忆能力的ChatClient * param builder ChatClient构建器 * param persistentChatMemory 持久化会话记忆 * return 带持久化记忆能力的ChatClient实例 */ Bean Primary public ChatClient persistentChatClient(ChatClient.Builder builder, ChatMemory persistentChatMemory) { return builder .defaultAdvisors(ChatMemoryAdvisor.builder(persistentChatMemory).build()) .build(); } }5.6 持久化对话服务实现package com.jam.demo.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.messages.Message; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.List; /** * 持久化对话服务实现 * author ken */ Slf4j Service RequiredArgsConstructor public class PersistentChatService { private final ChatClient persistentChatClient; private final ChatMemoryRepository chatMemoryRepository; /** * 带持久化记忆的对话 * param conversationId 会话唯一标识 * param content 用户提问内容 * return AI回复内容 */ public String chatWithPersistentMemory(String conversationId, String content) { if (!StringUtils.hasText(conversationId)) { throw new IllegalArgumentException(会话ID不能为空); } if (!StringUtils.hasText(content)) { throw new IllegalArgumentException(提问内容不能为空); } return persistentChatClient.prompt() .user(content) .conversationId(conversationId) .call() .content(); } /** * 清空指定会话的持久化记忆 * param conversationId 会话唯一标识 */ public void clearChatMemory(String conversationId) { if (!StringUtils.hasText(conversationId)) { throw new IllegalArgumentException(会话ID不能为空); } chatMemoryRepository.clear(conversationId); } /** * 获取指定会话的历史消息 * param conversationId 会话唯一标识 * return 历史消息列表 */ public ListMessage getChatHistory(String conversationId) { if (!StringUtils.hasText(conversationId)) { throw new IllegalArgumentException(会话ID不能为空); } return chatMemoryRepository.getMessages(conversationId); } /** * 获取所有会话ID列表 * return 会话ID列表 */ public ListString listAllConversations() { return chatMemoryRepository.listConversationIds(); } }5.7 持久化对话接口实现package com.jam.demo.controller; import com.jam.demo.service.PersistentChatService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.ai.chat.messages.Message; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; /** * 持久化对话接口 * author ken */ RestController RequestMapping(/api/chat/persistent) RequiredArgsConstructor Tag(name 持久化对话接口, description 基于MySQL的带记忆对话能力支持分布式部署) public class PersistentChatController { private final PersistentChatService persistentChatService; /** * 带持久化记忆的对话接口 */ PostMapping(/send) Operation(summary 发送带持久化记忆的对话请求, description 同一个conversationId的请求会共享持久化上下文记忆应用重启不丢失) public ResponseEntityMapString, String chat( Parameter(description 会话唯一标识, required true) RequestParam String conversationId, Parameter(description 用户提问内容, required true) RequestParam String content) { String result persistentChatService.chatWithPersistentMemory(conversationId, content); return ResponseEntity.ok(Map.of(content, result)); } /** * 清空会话记忆接口 */ DeleteMapping(/clear) Operation(summary 清空指定会话的持久化记忆, description 清空后该会话的历史上下文将从数据库中删除) public ResponseEntityVoid clearMemory( Parameter(description 会话唯一标识, required true) RequestParam String conversationId) { persistentChatService.clearChatMemory(conversationId); return ResponseEntity.noContent().build(); } /** * 获取会话历史消息接口 */ GetMapping(/history) Operation(summary 获取指定会话的历史消息, description 查询该会话的所有历史对话消息) public ResponseEntityListMessage getChatHistory( Parameter(description 会话唯一标识, required true) RequestParam String conversationId) { ListMessage history persistentChatService.getChatHistory(conversationId); return ResponseEntity.ok(history); } /** * 获取所有会话ID接口 */ GetMapping(/conversations) Operation(summary 获取所有会话ID列表, description 查询系统中所有的会话ID) public ResponseEntityListString listAllConversations() { ListString conversations persistentChatService.listAllConversations(); return ResponseEntity.ok(conversations); } }六、高级特性滑动窗口与总结式记忆大模型的上下文窗口有固定的token上限无限制累加历史消息会导致token超限、请求失败、成本飙升Spring AI提供了两种核心解决方案分别应对不同的业务场景。6.1 滑动窗口记忆滑动窗口记忆是最常用的token控制方案核心逻辑是仅保留最近的N轮对话当消息数量超过设定的阈值时自动删除最早的历史消息保证消息总数始终在可控范围内。 Spring AI内置的MessageWindowChatMemory已经实现了该能力在之前的持久化配置中我们通过maxMessages(20)设置了最大保留20条消息也就是10轮完整的对话完全满足绝大多数业务场景的需求。滑动窗口记忆的核心配置Bean public ChatMemory messageWindowChatMemory(ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder() .maxMessages(20) .chatMemoryRepository(chatMemoryRepository) .build(); }6.2 总结式记忆总结式记忆适用于需要保留长周期上下文的业务场景核心逻辑是当历史消息超过阈值时不直接删除最早的消息而是通过大模型对历史消息进行核心信息总结用简短的总结文本替换原来的长历史消息既保留了完整的上下文语义又严格控制了token数量。总结式记忆实现示例package com.jam.demo.memory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; /** * 总结式记忆处理器 * author ken */ Slf4j Component RequiredArgsConstructor public class SummarizingChatMemoryHandler { private final ChatClient chatClient; private final ChatMemoryRepository chatMemoryRepository; private static final int MAX_MESSAGES_BEFORE_SUMMARY 30; private static final String SUMMARY_PROMPT 请对以下对话历史进行核心信息总结保留关键的上下文语义去除冗余内容总结内容不超过500字。对话历史\n%s; /** * 处理会话的历史消息总结 * param conversationId 会话ID */ public void summarizeHistoryIfNeeded(String conversationId) { ListMessage messageList chatMemoryRepository.getMessages(conversationId); if (CollectionUtils.isEmpty(messageList) || messageList.size() MAX_MESSAGES_BEFORE_SUMMARY) { return; } StringBuilder historyBuilder new StringBuilder(); for (Message message : messageList) { historyBuilder.append(message.getMessageType().name()) .append(: ) .append(message.getText()) .append(\n); } String summary chatClient.prompt() .user(String.format(SUMMARY_PROMPT, historyBuilder.toString())) .call() .content(); chatMemoryRepository.clear(conversationId); chatMemoryRepository.addMessage(conversationId, new SystemMessage(历史对话总结 summary)); int startIndex Math.max(0, messageList.size() - 10); ListMessage recentMessages messageList.subList(startIndex, messageList.size()); chatMemoryRepository.addMessages(conversationId, recentMessages); log.info(会话{}的历史消息总结完成原消息数量{}总结后保留消息数量{}, conversationId, messageList.size(), recentMessages.size() 1); } }七、带记忆对话的全流程底层原理7.1 全流程执行时序图7.2 核心环节原理拆解拦截器调度ChatMemoryAdvisor基于Spring的AOP机制在ChatClient的请求流程中插入了两个扩展点请求发送前的前置处理和响应返回后的后置处理实现了记忆能力的完全无侵入集成开发者无需修改业务代码即可接入。会话隔离实现通过conversationId作为唯一标识每个会话对应独立的消息列表Spring AI在加载和保存消息时严格按照conversationId进行过滤完全杜绝了不同会话之间的数据串扰。消息注入逻辑在前置处理环节ChatMemoryAdvisor会将加载到的历史消息按照时间顺序插入到当前用户消息的前面保证模型能够按照交互顺序读取完整的上下文同时保证SystemMessage始终在最开头的位置。记忆持久化逻辑在后置处理环节ChatMemoryAdvisor会自动将本次交互的UserMessage和AssistantMessage成对追加到ChatMemoryRepository中无需开发者手动调用保存接口保证了记忆数据的完整性。7.3 易混淆概念区分概念核心职责适用场景ChatMemory单个会话的内存级消息管理负责当前会话的消息读写单次请求的消息处理会话级的内存操作ChatMemoryRepository全量会话的持久化管理负责消息的加载、保存、清空跨请求、跨实例的记忆持久化分布式场景InMemoryChatMemory基于内存的ChatMemory实现数据随应用重启丢失本地测试、单机低并发场景MessageWindowChatMemory带滑动窗口的ChatMemory实现自动控制消息数量生产环境绝大多数业务场景控制token成本八、生产环境最佳实践与坑点规避8.1 会话安全与隔离最佳实践会话ID生成规范采用UUID、雪花算法等非连续的唯一ID生成策略禁止使用自增数字作为会话ID避免被恶意遍历。用户与会话绑定生产环境中必须将conversationId与用户ID进行强绑定在所有记忆操作前校验当前登录用户是否有权限操作该会话ID杜绝越权访问与数据泄露。会话权限控制禁止普通用户查询全量会话ID列表该接口仅对管理员开放且必须做分页与权限控制。8.2 Token成本与稳定性最佳实践强制设置滑动窗口无论使用哪种记忆实现都必须设置合理的消息数量上限禁止无限制累加历史消息根据使用的模型的上下文窗口预留至少30%的token空间给当前请求与模型回复。分级窗口策略针对不同的业务场景设置不同的窗口大小比如客服对话场景可以设置较大的窗口普通问答场景设置较小的窗口平衡体验与成本。Token数校验在请求发送前对全量Prompt的token数进行校验如果超过模型的上限自动触发历史消息的截断或总结避免请求直接失败。8.3 性能优化最佳实践活跃会话缓存针对高频访问的活跃会话采用Redis进行缓存缓存过期时间设置为30分钟减少数据库的查询压力提升请求响应速度。批量操作优化针对批量消息保存场景采用编程式事务进行批量提交减少数据库的IO次数提升写入性能。历史数据归档设置定时任务定期清理超过3个月未活跃的会话数据或者将冷数据归档到离线存储避免主表数据量过大导致的查询性能下降。8.4 常见坑点规避SystemMessage重复累加禁止将SystemMessage重复添加到历史消息中否则会导致token的严重浪费Spring AI的ChatMemoryAdvisor会自动处理SystemMessage的位置无需手动追加。消息顺序错乱必须保证历史消息的顺序是UserMessage与AssistantMessage成对出现禁止手动修改消息的创建时间与顺序否则会导致模型无法正确理解上下文。敏感信息泄露在保存历史消息前必须对用户输入的手机号、身份证号、密码等敏感信息进行脱敏处理禁止明文存储敏感信息到数据库中。分布式环境数据一致性分布式环境中必须使用持久化的ChatMemoryRepository实现禁止使用InMemoryChatMemory否则会导致不同实例之间的会话记忆不一致。九、总结Spring AI的聊天记忆体系为Java开发者提供了一套优雅、低侵入、高扩展的多轮对话解决方案完全屏蔽了底层的消息拼接、持久化、token控制等复杂逻辑开发者仅需通过简单的配置即可实现从本地测试到生产级落地的全流程能力。

更多文章