当我们利用大模型进行开发时,有时会因为项目重启而丢失模型的记忆,会给开发的过程带来不方便
接下来我将介绍如何将模型的记忆持久化,并保证在项目重启后依然能能够正常加载记忆上下文。
我们在配置ChatClient时,由于想要实现模型的记忆,便需要管理会话信息,而Spring AI给我们提供了Advisors的自动管理机制,其有两种实现方式,即内存记忆和缓存记忆,但是其根源都是建立在缓存中,主机重启都会数据丢失。
在模型会话记忆ChatMemory的底层是通过创建不同类型的Message存储会话,所以我们可以利用这个机制,在每次模型响应后,将模型的响应和用户的请求信息分别手动创建不同类型的Message进行存储,而存储则可以选择数据库。
但是首先要给ChatClient配置记忆容器
this.chatClient = chatClient.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)).build();
我将分别来讲记忆的持久化以及记忆的读取
模型记忆持久化
以下是ChatClient的捕获响应方式
public Flux<String> chat(@RequestBody GetRequest request) {//用户消息String userMessage = request.getMessage();//保存会话idchatHistoryService.save("chat",request.getChatId(),request.getSid());//构建提示词,用户提示词,调用模型,取出响应//流式响应// 创建响应收集器,不断加载响应内容,用于在中断时保存已生成内容StringBuilder assistantResponse = new StringBuilder();System.out.println("调用"+modelName+"模型进行响应");Flux<String> response = chatClient.prompt().system(System_Prompt) // 设置系统提示词.user(userMessage) // 设置用户提示词.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY,request.getChatId())) // 设置会话ID.stream() //流式响应.content() //获取响应内容.doOnNext(assistantResponse::append) // 追加到响应收集器中
// .doOnSubscribe(sub -> activeSubscriptions.put(request.getChatId(), sub)) // 直接存储Subscription.doFinally(Message->{//将响应信息存储到数据库String type = "assistant"; //将用户信息保存到数据库saveChatHistory(request.getChatId(),"user", userMessage);//将响应信息存储到数据库saveChatHistory(request.getChatId(),type, assistantResponse.toString());});return response;}
可以看到在相应开始前首先创建一个builder容器用于不断追加模型的响应,在ChatClient的dofinally中调用方法分别存储用户和模型信息。
以下是存储方法
可以看到方法参数为会话ID,类型和内容,其中类型是user或者assistant,这是底层源码所定义的,我们需要保持一致以便模型能够正确读取
//将会话信息存储到数据库public void saveChatHistory(String chatId, String type, String content) {ChatDetail chatDetail = new ChatDetail();chatDetail.setChatId(chatId);chatDetail.setMessageType(type);chatDetail.setContent(content);chatDetailMapper.insert(chatDetail);}
这里注意需要提前定义数据库表,有类型字段和内容字段
最终存储的效果:
加载模型记忆
已经将对话数据持久化,那么在进行项目重启后为了不丢失原有记忆,我们需要想办法吧数据库中存储的内容加载到模型的记忆中
前面已经说到记忆的存储在底层时Message类型,而其有不同的实现类,其中UserMessage和AssistantMessage是我们所需要的只要根据从数据库中消息类型的不同分别创建不同的Message并将其存储到ChatMemory中即可。
以下是具体流程:
当用户访问某个会话时,先将其余会话的记忆清除,再根据会话id从数据库取得对应记忆,并遍历存入模型记忆上下文
List<ChatDetail> chatDetails = chatDetailMapper.selectList(new LambdaQueryWrapper<ChatDetail>().eq(ChatDetail::getChatId, chatId));//将当前会话的信息保存到模型记忆上下文//清除其他会话记忆chatHistoryService.getChatIds(type).forEach(chat->chatMemory.clear(chat.getChatId()));activeSubscriptions.clear(); // 同时清理订阅关系//加入当前会话记忆if (!chatDetails.isEmpty()) {chatDetails.forEach(c -> {if (c.getMessageType().equals("user")) {chatMemory.add(chatId, new UserMessage(c.getContent())); //用户信息} else if (c.getMessageType().equals("assistant")) {chatMemory.add(chatId, new AssistantMessage(c.getContent())); //模型回复信息}});System.err.println("当前模型记忆为:"+chatMemory.get(chatId, Integer.MAX_VALUE));
这样一来便实现了模型记忆的持久化功能,并在多个会话的情况下保证内存的容量问题。