大模型默认只能单轮对话每次对话完成后就会丢失当前对话记忆我们之前了解过可以通过AssistantMessage把大模型回复结果存储起来下次提问时在发送给大模型不过使用过于麻烦和受限Spring AI 和Spring AI Alibaba都实现了更好实现记忆化的方式1. 内存存储Configuration public class SaaLLMConfig { private final String QWEN_MODEL qwen-plus; Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build(); } Bean public ChatModel qwenChatModel(DashScopeApi dashScopeApi) { return DashScopeChatModel .builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder().model(QWEN_MODEL).build()) .build(); } Bean public ChatClient qwenChatClient(Qualifier(qwenChatModel) ChatModel qwenChatModel) { MessageWindowChatMemory windowChatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(new InMemoryChatMemoryRepository()) .build(); return ChatClient .builder(qwenChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); } }MessageWindowChatMemory维护固定容量的消息窗口默认 20 条。当消息超限时自动移除较早的对话消息始终保留系统消息。InMemoryChatMemoryRepository 基于 ConcurrentHashMap 实现内存存储。默认情况下若未配置其他 RepositorySpring AI 将自动配置 InMemoryChatMemoryRepository 类型的 ChatMemoryRepository Bean供直接使用。2. 源码解析关键在于MessageChatMemoryAdvisor这个类可以看到我们在配置ChatClient时配置了一个该对象这个就是生效的关键虽然名字叫Advisor但它和 Spring AOP 一点关系都没有。它是Spring AI 自己实现的一套独立拦截器机制。Override public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { String conversationId getConversationId(chatClientRequest.context(), this.defaultConversationId); // 1. Retrieve the chat memory for the current conversation. ListMessage memoryMessages this.chatMemory.get(conversationId); // 2. Advise the request messages list. ListMessage processedMessages new ArrayList(memoryMessages); processedMessages.addAll(chatClientRequest.prompt().getInstructions()); // 3. Create a new request with the advised messages. ChatClientRequest processedChatClientRequest chatClientRequest.mutate() .prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()) .build(); // 4. Add the new user message to the conversation memory. UserMessage userMessage processedChatClientRequest.prompt().getUserMessage(); this.chatMemory.add(conversationId, userMessage); return processedChatClientRequest; } Override public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) { ListMessage assistantMessages new ArrayList(); if (chatClientResponse.chatResponse() ! null) { assistantMessages chatClientResponse.chatResponse() .getResults() .stream() .map(g - (Message) g.getOutput()) .toList(); } this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId), assistantMessages); return chatClientResponse; }在该类源码中我们可以看到关键的两个方法before和afterbefore在发送请求前执行先从memoryMessages中获取历史对话再把本次对话的用户问题也存入chatMemory中。after在大模型返回后执行把大模型返回内容存储到chatMemory中3. Redis存储配置redis:spring: data: redis: host: localhost port: 6379 database: 0要使用Redis需要引入依赖!--redis-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Alibaba Spring AI Redis 记忆模块 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-memory-redis/artifactId /dependency可以看到这个依赖适配了Jedis和RedissonRedis以及springBoot的LettuceConfiguration public class SaaLLMConfig { private final String DEEPSEEK_MODEL deepseek-v3.2; Bean public ChatModel deepseekChatModel() { return DashScopeChatModel .builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build()) .defaultOptions(DashScopeChatOptions.builder().model(DEEPSEEK_MODEL).build()) .build(); } Bean public ChatClient deepseekChatClient(Qualifier(deepseekChatModel) ChatModel deepseekChatModel) { LettuceRedisChatMemoryRepository redisCMR LettuceRedisChatMemoryRepository.builder().build(); MessageWindowChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(redisCMR) .build(); return ChatClient .builder(deepseekChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } }同时一个大模型通常会服务多个用户对于不同用户的对话应该分开存储我们可以通过设置CONVERSATION_ID来实现RestController public class ChatMemoryController { Resource(name deepseekChatClient) ChatClient deepseekChatClient; GetMapping(/chatmemory/chat2) public FluxString chat2(String userId, String question) { return deepseekChatClient .prompt() .user(question) .advisors( advisorSpec - advisorSpec.param(CONVERSATION_ID, userId) ) .stream() .content(); } }通过.advisors方法设置如果不设置会使用默认的这样所有用户的对话是共享的可以看到是按id分别存储的4. 关系型数据库存储导入依赖!-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Spring JDBC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !--Spring AI JDBC 聊天记忆启动器-- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId /dependency增加配置spring: ai: chat: #开启Schema初始化,会自动创建表用 memory: repository: jdbc: initialize-schema: always # 数据库连接 datasource: url: jdbc:mysql://localhost:3306/ai_chat?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # JDBC 自动建表 sql: init: mode: always配置ChatClient:Configuration public class SaaLLMConfig { private final String DEEPSEEK_MODEL deepseek-v3.2; private final String QWEN_MODEL qwen-plus; private final String GLM5 glm-5; Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build(); } Bean public ChatModel glmChatModel() { return DashScopeChatModel .builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build()) .defaultOptions(DashScopeChatOptions.builder().model(GLM5).build()) .build(); } Bean public ChatClient glmChatClient(Qualifier(glmChatModel) ChatModel glmChatModel, JdbcChatMemoryRepository jdbcChatMemoryRepository) { MessageWindowChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(jdbcChatMemoryRepository) .build(); return ChatClient .builder(glmChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); }Spring AI 为 JdbcChatMemoryRepository 提供自动配置可直接注入。RestController public class ChatMemoryController { Resource(name glmChatClient) ChatClient glmChatClient; GetMapping(/chatmemory/chat3) public FluxString chat3(String userId, String question) { return glmChatClient .prompt() .user(question) .advisors( advisorSpec - advisorSpec.param(CONVERSATION_ID, userId) ) .stream() .content(); } }
Spring AI Alibaba零基础速成(5) ---- Memory(记忆)
发布时间:2026/5/21 1:10:38
大模型默认只能单轮对话每次对话完成后就会丢失当前对话记忆我们之前了解过可以通过AssistantMessage把大模型回复结果存储起来下次提问时在发送给大模型不过使用过于麻烦和受限Spring AI 和Spring AI Alibaba都实现了更好实现记忆化的方式1. 内存存储Configuration public class SaaLLMConfig { private final String QWEN_MODEL qwen-plus; Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build(); } Bean public ChatModel qwenChatModel(DashScopeApi dashScopeApi) { return DashScopeChatModel .builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder().model(QWEN_MODEL).build()) .build(); } Bean public ChatClient qwenChatClient(Qualifier(qwenChatModel) ChatModel qwenChatModel) { MessageWindowChatMemory windowChatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(new InMemoryChatMemoryRepository()) .build(); return ChatClient .builder(qwenChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); } }MessageWindowChatMemory维护固定容量的消息窗口默认 20 条。当消息超限时自动移除较早的对话消息始终保留系统消息。InMemoryChatMemoryRepository 基于 ConcurrentHashMap 实现内存存储。默认情况下若未配置其他 RepositorySpring AI 将自动配置 InMemoryChatMemoryRepository 类型的 ChatMemoryRepository Bean供直接使用。2. 源码解析关键在于MessageChatMemoryAdvisor这个类可以看到我们在配置ChatClient时配置了一个该对象这个就是生效的关键虽然名字叫Advisor但它和 Spring AOP 一点关系都没有。它是Spring AI 自己实现的一套独立拦截器机制。Override public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { String conversationId getConversationId(chatClientRequest.context(), this.defaultConversationId); // 1. Retrieve the chat memory for the current conversation. ListMessage memoryMessages this.chatMemory.get(conversationId); // 2. Advise the request messages list. ListMessage processedMessages new ArrayList(memoryMessages); processedMessages.addAll(chatClientRequest.prompt().getInstructions()); // 3. Create a new request with the advised messages. ChatClientRequest processedChatClientRequest chatClientRequest.mutate() .prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()) .build(); // 4. Add the new user message to the conversation memory. UserMessage userMessage processedChatClientRequest.prompt().getUserMessage(); this.chatMemory.add(conversationId, userMessage); return processedChatClientRequest; } Override public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) { ListMessage assistantMessages new ArrayList(); if (chatClientResponse.chatResponse() ! null) { assistantMessages chatClientResponse.chatResponse() .getResults() .stream() .map(g - (Message) g.getOutput()) .toList(); } this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId), assistantMessages); return chatClientResponse; }在该类源码中我们可以看到关键的两个方法before和afterbefore在发送请求前执行先从memoryMessages中获取历史对话再把本次对话的用户问题也存入chatMemory中。after在大模型返回后执行把大模型返回内容存储到chatMemory中3. Redis存储配置redis:spring: data: redis: host: localhost port: 6379 database: 0要使用Redis需要引入依赖!--redis-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Alibaba Spring AI Redis 记忆模块 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-memory-redis/artifactId /dependency可以看到这个依赖适配了Jedis和RedissonRedis以及springBoot的LettuceConfiguration public class SaaLLMConfig { private final String DEEPSEEK_MODEL deepseek-v3.2; Bean public ChatModel deepseekChatModel() { return DashScopeChatModel .builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build()) .defaultOptions(DashScopeChatOptions.builder().model(DEEPSEEK_MODEL).build()) .build(); } Bean public ChatClient deepseekChatClient(Qualifier(deepseekChatModel) ChatModel deepseekChatModel) { LettuceRedisChatMemoryRepository redisCMR LettuceRedisChatMemoryRepository.builder().build(); MessageWindowChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(redisCMR) .build(); return ChatClient .builder(deepseekChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } }同时一个大模型通常会服务多个用户对于不同用户的对话应该分开存储我们可以通过设置CONVERSATION_ID来实现RestController public class ChatMemoryController { Resource(name deepseekChatClient) ChatClient deepseekChatClient; GetMapping(/chatmemory/chat2) public FluxString chat2(String userId, String question) { return deepseekChatClient .prompt() .user(question) .advisors( advisorSpec - advisorSpec.param(CONVERSATION_ID, userId) ) .stream() .content(); } }通过.advisors方法设置如果不设置会使用默认的这样所有用户的对话是共享的可以看到是按id分别存储的4. 关系型数据库存储导入依赖!-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Spring JDBC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !--Spring AI JDBC 聊天记忆启动器-- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId /dependency增加配置spring: ai: chat: #开启Schema初始化,会自动创建表用 memory: repository: jdbc: initialize-schema: always # 数据库连接 datasource: url: jdbc:mysql://localhost:3306/ai_chat?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # JDBC 自动建表 sql: init: mode: always配置ChatClient:Configuration public class SaaLLMConfig { private final String DEEPSEEK_MODEL deepseek-v3.2; private final String QWEN_MODEL qwen-plus; private final String GLM5 glm-5; Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build(); } Bean public ChatModel glmChatModel() { return DashScopeChatModel .builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(AliQWenAPIKey)).build()) .defaultOptions(DashScopeChatOptions.builder().model(GLM5).build()) .build(); } Bean public ChatClient glmChatClient(Qualifier(glmChatModel) ChatModel glmChatModel, JdbcChatMemoryRepository jdbcChatMemoryRepository) { MessageWindowChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(10) .chatMemoryRepository(jdbcChatMemoryRepository) .build(); return ChatClient .builder(glmChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); }Spring AI 为 JdbcChatMemoryRepository 提供自动配置可直接注入。RestController public class ChatMemoryController { Resource(name glmChatClient) ChatClient glmChatClient; GetMapping(/chatmemory/chat3) public FluxString chat3(String userId, String question) { return glmChatClient .prompt() .user(question) .advisors( advisorSpec - advisorSpec.param(CONVERSATION_ID, userId) ) .stream() .content(); } }