如何让Spring AI的MessageChatMemoryAdvisor实现会话级隔离?
基于Spring AI实现按会话存储消息历史的方案
问题背景
现有一个基于Spring AI的可用原型,当前配置如下:
@Bean public ChatClient buildClient( MessageChatMemoryAdvisor messageChatMemoryAdvisor ) { return openAiBuilder .defaultAdvisors( messageChatMemoryAdvisor, questionAnswerAdvisor ) .defaultSystem(instructions) .defaultOptions(new OpenAiChatOptions()) .build(); } @Bean public MessageChatMemoryAdvisor messageChatMemoryAdvisor() { return new MessageChatMemoryAdvisor(new InMemoryChatMemory()); }
当前配置会保存所有请求的消息,无法区分不同用户/会话的消息历史。已配置Spring Security并启用会话,需要实现按会话隔离消息历史。
我的猜想
考虑通过会话ID作为对话唯一标识来实现隔离,自定义了CustomMessageChatMemoryAdvisor:
@Slf4j public class CustomMessageChatMemoryAdvisor extends MessageChatMemoryAdvisor { public CustomMessageChatMemoryAdvisor(InMemoryChatMemory chatMemory) { super(chatMemory); } private AdvisedRequest addSessionAsConversationId(AdvisedRequest advisedRequest) { String convId = RequestContextHolder.currentRequestAttributes().getSessionId(); // Create map with session ID as conversation ID Map<String, Object> contextMap = new HashMap<>(); contextMap.put(CHAT_MEMORY_CONVERSATION_ID_KEY, convId); return AdvisedRequest.from(advisedRequest) .withAdviseContext(contextMap) .build(); } @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { return super.aroundCall(addSessionAsConversationId(advisedRequest), chain); } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { return super.aroundStream(advisedRequest, chain); } public List<Message> getMessages(String conversationId, int chatMemoryRetrieveSize) { return this.getChatMemoryStore().get(conversationId, chatMemoryRetrieveSize); } }
不确定该方案是否可行,求验证及最佳实现方式。
方案验证与优化建议
你的猜想方向完全正确,核心就是用会话ID作为对话的唯一标识,让MessageChatMemoryAdvisor基于会话ID隔离存储。需要补充几个细节以保证稳定性:
1. 会话ID获取的安全处理
避免RequestContextHolder.currentRequestAttributes()抛出空指针异常,添加非空判断:
RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (attributes == null) { throw new IllegalStateException("无可用请求上下文,无法获取会话ID"); } String sessionId = attributes.getSessionId();
2. 流式调用的会话ID同步传递
当前aroundStream方法未处理会话ID,需要同步修改:
@Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { return super.aroundStream(addSessionAsConversationId(advisedRequest), chain); }
3. 替换原有Bean配置
需要将原有的MessageChatMemoryAdvisor Bean替换为自定义实现:
@Bean public CustomMessageChatMemoryAdvisor messageChatMemoryAdvisor() { return new CustomMessageChatMemoryAdvisor(new InMemoryChatMemory()); }
4. 内存存储的局限性优化
InMemoryChatMemory在服务重启后会丢失所有会话消息,生产环境建议替换为持久化存储,比如Spring AI提供的RedisChatMemory:
@Bean public ChatMemory redisChatMemory(RedisConnectionFactory connectionFactory) { return new RedisChatMemory(connectionFactory); } @Bean public CustomMessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory redisChatMemory) { return new CustomMessageChatMemoryAdvisor(redisChatMemory); }
完整优化后的自定义Advisor
@Slf4j public class CustomMessageChatMemoryAdvisor extends MessageChatMemoryAdvisor { public CustomMessageChatMemoryAdvisor(ChatMemory chatMemory) { super(chatMemory); } private AdvisedRequest addSessionAsConversationId(AdvisedRequest advisedRequest) { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (attributes == null) { throw new IllegalStateException("无可用请求上下文,无法获取会话ID"); } String sessionId = attributes.getSessionId(); Map<String, Object> contextMap = new HashMap<>(); contextMap.put(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId); return AdvisedRequest.from(advisedRequest) .withAdviseContext(contextMap) .build(); } @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { return super.aroundCall(addSessionAsConversationId(advisedRequest), chain); } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { return super.aroundStream(addSessionAsConversationId(advisedRequest), chain); } }
完成以上配置后,即可实现不同会话之间的消息历史完全隔离,每个用户的对话历史仅存储在自身会话ID对应的存储空间中。
内容的提问来源于stack exchange,提问作者Jackie
相关产品推荐
相关产品推荐

