You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:37:23