當我們利用大模型進行開發時,有時會因為項目重啟而丟失模型的記憶,會給開發的過程帶來不方便
接下來我將介紹如何將模型的記憶持久化,并保證在項目重啟后依然能能夠正常加載記憶上下文。
我們在配置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));
這樣一來便實現了模型記憶的持久化功能,并在多個會話的情況下保證內存的容量問題。