如何让Spring AI ChatClient等待Chat Memory Advisor完成MongoDB存储操作后再返回响应
看起来你遇到的问题是:ChatClient已经返回响应给你了,但MongoDB的聊天内存存储操作还在后台跑——这大概率是因为你的ChatMemoryStore实现用了异步操作(比如Reactive MongoDB),或者Advisor里的存储逻辑没有阻塞等待操作完成。结合你的代码和尝试,给你几个可行的解决方案:
方案一:让ChatMemoryStore的存储操作同步阻塞
最直接的方式是修改你的CustomChatMemory,确保它的add方法是同步阻塞的,必须等MongoDB的写入操作完全完成才返回。如果你的userChatMemoryRepository是Reactive类型(比如ReactiveMongoRepository),原来的异步调用会立即返回,导致存储在后台异步执行,这就是问题的根源。
修改CustomChatMemory的add方法:
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)也确保是同步阻塞的 }
这样,当MessageChatMemoryAdvisor的before方法调用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就会等待存储完成后才继续执行后续逻辑(返回响应)。




