使用MessageChatMemoryAdvisor導致System未被正確的放在首位
如下是使用Spring Ai實現多輪對話的官方例子(文檔地址:https://docs.spring.io/spring-ai/reference/api/chat-memory.html):
@AutowiredChatMemoryRepository chatMemoryRepository; //注入對話記憶@GetMapping("/chatMemory")@Operation(summary = "帶記憶的同步調用")String chatMemory(String userInput) {// 1. 構建對話記憶存儲配置// 使用MessageWindowChatMemory實現窗口記憶策略ChatMemory chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository) // 底層記憶存儲倉庫(測試使用內存實現).maxMessages(20) // 設置歷史消息最大保留輪次(滑動窗口大小).build();// 2. 生成唯一會話ID(實際項目中由)String conversationId = "123456789"; // 示例固定值,生產環境需動態生成// 3. 構建對話請求并配置各組件return this.chatClient.prompt()// 3.1 設置對話角色.system(system) // 系統角色設定(AI人設/指令)// 3.2 設置基礎參數.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId) // 綁定當前對話ID到請求上下文)// 3.3 添加增強功能(Advisors).advisors(new SimpleLoggerAdvisor(), // 啟用請求日志記錄(用于調試)MessageChatMemoryAdvisor.builder(chatMemory).build(), // 啟用記憶管理功能)// 3.4 設置當前用戶輸入.user(userInput)// 3.5 執行調用.call() // 發送同步請求到對話服務// 3.6 處理響應.content(); // 提取響應中的文本內容}
如上示例是根據官方文檔寫的,實際測下來是有問題的,例如:有的模型在二輪對話的時候返回順序問題報錯,有的模型在二輪對話的時候丟失人設。(這個和模型的兼容性有關系)
經過排查可以發現二輪對話的Message中內容順序會有問題(System人設被放到了倒數第二句):
[{"role": "user","content": "您好"},{"role": "assistant","content": "\n\n您好!很高興為您提供服務。請問有什么可以幫助您的嗎?"},{"role": "system","content": "你是智能助理小明"},{"role": "user","content": "你叫什么名字啊"}
]
已上述的格式請求大模型會出現各種問題,因為正常規定的順序就是S- U- A-U。
在GitHub中有大佬給出臨時解決辦法,創建一個SystemFirstSortingAdvisor來確保System人設始終保持第一位:(GitHub原問題地址:https://github.com/spring-projects/spring-ai/issues/4170)
/*** 保證SYSTEM在最前面的增強*/
public class SystemFirstSortingAdvisor implements BaseAdvisor {@Overridepublic ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {List<Message> processedMessages = chatClientRequest.prompt().getInstructions();processedMessages.sort(Comparator.comparing(m -> m.getMessageType() == MessageType.SYSTEM ? 0 : 1));return chatClientRequest.mutate().prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()).build();}@Overridepublic ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {return chatClientResponse; // no-op}@Overridepublic int getOrder() {return 0; // larger than MessageChatMemoryAdvisor so it runs afterwards}
}
最后正確調用(在advisors中加入SystemFirstSortingAdvisor):
@AutowiredChatMemoryRepository chatMemoryRepository; //注入對話記憶@GetMapping("/chatMemory")@Operation(summary = "帶記憶的同步調用")String chatMemory(String userInput) {// 1. 構建對話記憶存儲配置// 使用MessageWindowChatMemory實現窗口記憶策略ChatMemory chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository) // 底層記憶存儲倉庫(測試使用內存實現).maxMessages(20) // 設置歷史消息最大保留輪次(滑動窗口大小).build();// 2. 生成唯一會話ID(實際項目中由)String conversationId = "123456789"; // 示例固定值,生產環境需動態生成// 3. 構建對話請求并配置各組件return this.chatClient.prompt()// 3.1 設置對話角色.system(system) // 系統角色設定(AI人設/指令)// 3.2 設置基礎參數.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId) // 綁定當前對話ID到請求上下文)// 3.3 添加增強功能(Advisors).advisors(new SimpleLoggerAdvisor(), // 啟用請求日志記錄(用于調試)MessageChatMemoryAdvisor.builder(chatMemory).build(), // 啟用記憶管理功能new SystemFirstSortingAdvisor() // 確保系統消息優先排序)// 3.4 設置當前用戶輸入.user(userInput)// 3.5 執行調用.call() // 發送同步請求到對話服務// 3.6 處理響應.content(); // 提取響應中的文本內容}
綜上所述,下個版本可能會優化掉這個BUG,目前就先使用SystemFirstSortingAdvisor來保證正常調用。