You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让Spring AI ChatClient等待Chat Memory Advisor完成MongoDB存储操作后再返回响应

如何让Spring AI ChatClient等待Chat Memory Advisor完成MongoDB存储操作后再返回响应

看起来你遇到的问题是:ChatClient已经返回响应给你了,但MongoDB的聊天内存存储操作还在后台跑——这大概率是因为你的ChatMemoryStore实现用了异步操作(比如Reactive MongoDB),或者Advisor里的存储逻辑没有阻塞等待操作完成。结合你的代码和尝试,给你几个可行的解决方案:

方案一:让ChatMemoryStore的存储操作同步阻塞

最直接的方式是修改你的CustomChatMemory,确保它的add方法是同步阻塞的,必须等MongoDB的写入操作完全完成才返回。如果你的userChatMemoryRepository是Reactive类型(比如ReactiveMongoRepository),原来的异步调用会立即返回,导致存储在后台异步执行,这就是问题的根源。

修改CustomChatMemoryadd方法:

public class CustomChatMemory implements ChatMemoryStore {
    private final UserChatMemoryRepository userChatMemoryRepository;

    // 构造函数等...

    @Override
    public void add(String conversationId, Message message) {
        // 1. 把Message对象转换成你的MongoDB实体类
        UserChatMemoryEntity entity = convertToMongoEntity(conversationId, message);
        // 2. 如果是Reactive Repository,用block()强制阻塞等待存储完成
        userChatMemoryRepository.save(entity).block();
        // 如果是普通的同步Repository,直接调用save即可:
        // userChatMemoryRepository.save(entity);
    }

    // 其他方法(比如get)也确保是同步阻塞的
}

这样,当MessageChatMemoryAdvisorbefore方法调用add时,会强制等待MongoDB存储完成后才继续执行,确保用户消息的存储在LLM调用前就完成。另外别忘了处理助手响应的存储:默认的MessageChatMemoryAdvisor会在after方法中把助手响应加入内存,你同样要确保这个add操作也是同步阻塞的。

方案二:自定义同步版的ChatMemoryAdvisor

如果不想修改ChatMemoryStore的实现,你可以自己实现一个ChatRequestAdvisor,完全掌控请求前后的逻辑,确保所有存储操作同步完成(避免原生Advisor的限制)。

步骤1:创建自定义Advisor类

import org.springframework.ai.chat.advisor.AdvisedRequest;
import org.springframework.ai.chat.advisor.ChatRequestAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryStore;

import java.util.ArrayList;
import java.util.List;

public class SyncMessageChatMemoryAdvisor implements ChatRequestAdvisor {

    private final ChatMemory chatMemory;

    public SyncMessageChatMemoryAdvisor(ChatMemory chatMemory) {
        this.chatMemory = chatMemory;
    }

    @Override
    public AdvisedRequest before(AdvisedRequest request) {
        String conversationId = getConversationId(request);
        int chatMemoryRetrieveSize = getRetrieveSize(request);

        // 1. 获取当前会话的历史内存
        List<Message> memoryMessages = chatMemory.getChatMemoryStore().get(conversationId, chatMemoryRetrieveSize);
        // 2. 组装带历史消息的请求
        List<Message> advisedMessages = new ArrayList<>(request.messages());
        advisedMessages.addAll(memoryMessages);
        AdvisedRequest advisedRequest = AdvisedRequest.from(request).messages(advisedMessages).build();

        // 3. 同步添加用户消息到内存(确保阻塞等待完成)
        UserMessage userMessage = new UserMessage(request.userText(), request.media());
        chatMemory.getChatMemoryStore().add(conversationId, userMessage);

        return advisedRequest;
    }

    @Override
    public void after(AdvisedRequest request, ChatResponse response) {
        String conversationId = getConversationId(request);
        // 同步添加助手响应到内存
        AssistantMessage assistantMessage = new AssistantMessage(response.getResult().getOutput().getContent());
        chatMemory.getChatMemoryStore().add(conversationId, assistantMessage);
    }

    // 提取会话ID(和原生Advisor逻辑保持一致)
    private String getConversationId(AdvisedRequest request) {
        return request.adviseContext()
                .getOrDefault(MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, "default-conversation")
                .toString();
    }

    // 提取内存获取数量(和原生Advisor逻辑保持一致)
    private int getRetrieveSize(AdvisedRequest request) {
        return request.adviseContext()
                .getOrDefault(MessageChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)
                .intValue();
    }
}

步骤2:替换配置中的Advisor

修改你的ChatClient配置,用自定义的同步Advisor替换原生的MessageChatMemoryAdvisor

@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder, CustomChatMemory chatMemory) {
    return chatClientBuilder
            .defaultAdvisors(new SyncMessageChatMemoryAdvisor(chatMemory))
            .build();
}

方案三:检查Spring AI版本并正确重写Advisor

你提到原生的before方法是private的,但实际上在Spring AI的稳定版本中,MessageChatMemoryAdvisor实现了ChatRequestAdvisor接口,before方法是public的,可以直接重写。如果你的版本中这个方法是private,可能是版本过旧或你混淆了内部方法。

如果可以重写,创建一个继承类并修改存储逻辑:

public class BlockingMessageChatMemoryAdvisor extends MessageChatMemoryAdvisor {

    public BlockingMessageChatMemoryAdvisor(ChatMemory chatMemory) {
        super(chatMemory);
    }

    @Override
    public AdvisedRequest before(AdvisedRequest request) {
        // 先处理用户消息的同步存储
        String conversationId = super.doGetConversationId(request.adviseContext());
        UserMessage userMessage = new UserMessage(request.userText(), request.media());
        // 强制同步存储(如果你的ChatMemoryStore支持同步方法)
        ((CustomChatMemory) super.getChatMemoryStore()).addSync(conversationId, userMessage);
        // 再调用父类的逻辑
        return super.before(request);
    }

    @Override
    public void after(AdvisedRequest request, ChatResponse response) {
        String conversationId = super.doGetConversationId(request.adviseContext());
        AssistantMessage assistantMessage = new AssistantMessage(response.getResult().getOutput().getContent());
        ((CustomChatMemory) super.getChatMemoryStore()).addSync(conversationId, assistantMessage);
        super.after(request, response);
    }
}

然后在配置中使用这个子类即可。

总结

最推荐的是方案一,因为它只需要修改ChatMemoryStore的实现,代码侵入性最小。核心思路就是:确保所有内存存储操作都是同步阻塞的,直到MongoDB的写入操作完全完成,这样ChatClient就会等待存储完成后才继续执行后续逻辑(返回响应)。

火山引擎 最新活动