使用MessageChatMemoryAdvisor导致System未被正确的放在首位
如下是使用Spring Ai实现多轮对话的官方例子(文档地址:https://docs.spring.io/spring-ai/reference/api/chat-memory.html):
java
@Autowired
ChatMemoryRepository 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人设被放到了倒数第二句):
java
[
{
"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)
java
/**
* 保证SYSTEM在最前面的增强
*/
public class SystemFirstSortingAdvisor implements BaseAdvisor {
@Override
public 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();
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse; // no-op
}
@Override
public int getOrder() {
return 0; // larger than MessageChatMemoryAdvisor so it runs afterwards
}
}
最后正确调用(在advisors中加入SystemFirstSortingAdvisor):
java
@Autowired
ChatMemoryRepository 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来保证正常调用。