kryo 序列化失败问题
```java static { kryo.setRegistrationRequired(false); // 注册 Spring AI 中的消息类 kryo.register(org.springframework.ai.chat.messages.UserMessage.class); kryo.register(org.springframework.ai.chat.messages.AssistantMessage.class); kryo.register(org.springframework.ai.chat.messages.SystemMessage.class); // kryo.register(org.springframework.ai.chat.messages.Media.class); // 设置实例化策略 kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); } ```如上代码,已经设置了是否需要注册类的类型为 false,也就是说不需要注册,但实际上还是会报类为注册错误,把相关的类在 kryo 初始化的时候加上即可
kryo 从文件序列化后再次给 Spring AI,有些值是 null
因为 Spring AI 有断言检查机制,如果遇到 null 的会报错在获取或创建会话消息列表的时候,也就是从文件反序列化的时候过滤掉 null
代码如下
/**
* 获取或创建会话消息的列表
* @param conversationId
* @return
*/
private List<Message> getOrCreateConversation(String conversationId) {
File file = getConversationFile(conversationId);
List<Message> messages = new ArrayList<>();
if (file.exists()) {
try (Input input = new Input(new FileInputStream(file))) {
List<Message> loadedMessages = kryo.readObject(input, ArrayList.class);
if (loadedMessages != null) {
// 过滤掉 null 消息
for (Message msg : loadedMessages) {
if (msg != null) {
messages.add(msg);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return messages;
}
基于 mysql 的对话记忆功能,
引入了当前 mp 生成的基础 crud 类,conversationService报 this.conversationService 为 null,因为当前类没有被 IOC 容器管理,打上 @Component 注解即可,
然后再 App 类使用的时候,使用构造器方式注入
完整代码如下
app 类
package com.wfh.aiagent.app;
import com.wfh.aiagent.advisor.MyLoggerAdvisor;
import com.wfh.aiagent.advisor.ReReadingAdvisor;
import com.wfh.aiagent.chatmemory.DbBasedMemory;
import com.wfh.aiagent.chatmemory.FileBasedMemory;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.stereotype.Component;
import java.util.List;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* @Author FengHuan Wang
* @Date 2025/5/25 10:06
* @Version 1.0
*/
@Component
@Slf4j
public class ProgramingApp {
private final ChatClient chatClient;
private static final String SYSTEM_PROMPT_ADVANCE = "你是一名拥有15年经验的首席Java工程师,擅长处理高并发分布式系统及JVM性能调优。请按照以下结构化流程解决问题:\n" +
"\n" +
"1. 需求分析阶段\n" +
"- 确认问题边界和技术约束\n" +
"- 识别潜在的并发、内存泄漏、GC问题\n" +
"- 评估O(n)复杂度并提出优化方向\n" +
"- 列出可能的设计模式应用场景\n" +
"\n" +
"2. 架构设计阶段(针对复杂问题)\n" +
"□ 绘制UML类图/时序图核心要素\n" +
"□ 选择合适的技术栈组合(如Spring生态组件)\n" +
"□ 设计分层架构(Controller/Service/DAO)\n" +
"□ 确定线程模型和并发控制策略\n" +
"□ 规划监控指标(Metrics/Logging/Tracing)\n" +
"\n" +
"3. 代码实现规范\n" +
"√ 严格遵循Java Effective规范\n" +
"√ 使用防御性编程和空对象模式\n" +
"√ 添加必要的Javadoc(包含@param @return @throws)\n" +
"√ 分离接口与实现(面向接口编程)\n" +
"√ 采用恰当的异常处理策略(Checked/Unchecked异常区分)\n" +
"\n" +
"4. 质量保障要求\n" +
"▶ 编写JUnit5测试用例(包括正常流、边界条件、异常场景)\n" +
"▶ 提供压力测试方案(JMeter/Gatling模板)\n" +
"▶ 添加性能基准测试(JMH示例)\n" +
"▶ 生成安全审计要点(OWASP TOP10相关检查项)\n" +
"\n" +
"5. 交付文档\n" +
"※ 架构决策记录(ADR)\n" +
"※ 运维部署手册(含JVM调优参数建议)\n" +
"※ 监控看板配置说明(Grafana模板ID)\n" +
"※ 技术债清单和技术演进路线\n" +
"\n" +
"当前待解决问题:[用户具体问题]\n" +
"\n" +
"请按照以下格式响应:\n" +
"【架构概要】\n" +
"用ASCII图形展示核心组件关系\n" +
"\n" +
"【代码实现】\n" +
"展示关键代码段(标注设计模式应用点)\n" +
"\n" +
"【质量检查点】\n" +
"列出必须验证的测试场景\n" +
"\n" +
"【性能优化】\n" +
"提出至少3个JVM层优化建议\n" +
"\n" +
"【知识扩展】\n" +
"推荐2个相关论文/技术文档链接";
private static final String SYSTEM_PROMPT_DEFAULT2 = "你是一名资深Java技术专家,专注于高效解决具体编程问题。请按以下结构处理问题:\n" +
"\n" +
"【问题澄清】\n" +
"1. 确认问题的核心矛盾点(明确输入输出)\n" +
"2. 识别潜在的技术风险(如并发竞态、资源泄漏等)\n" +
"3. 确定关键约束条件(如JDK版本、性能要求等)\n" +
"\n" +
"【解决方案设计】\n" +
"1. 给出2-3种可行方案及其优劣对比\n" +
"2. 标注方案适用的场景边界\n" +
"3. 指出可能的设计模式应用点\n" +
"\n" +
"【精准代码实现】\n" +
"1. 提供最小可行代码段(包含核心逻辑)\n" +
"2. 使用防御性编程和空安全处理\n" +
"3. 标注关键算法复杂度\n" +
"4. 添加必要异常处理\n" +
"\n" +
"【验证要点】\n" +
"1. 列出必须覆盖的测试用例\n" +
"2. 提供诊断问题的方法(如jstack使用示例)\n" +
"3. 给出性能验证的快速检查命令\n" +
"\n" +
"【深度优化】 \n" +
"1. JVM层面调优建议(与问题相关)\n" +
"2. 代码级优化策略(如热点方法优化)\n" +
"3. 可选扩展方案(如需要长期维护时的改进)\n" +
"\n" +
"请避免工程文档输出,聚焦技术问题本身。当前问题:[用户具体问题]";
private static final String SYSTEM_PROMPT_DEFAULT = "你好啊";
record ProgramingReport(String title, List<String> suggestions){
}
/**
* 初始化chatclient
* @param dashscopeChatModel
*/
public ProgramingApp(ChatModel dashscopeChatModel, DbBasedMemory chatMemory) {
// 初始化基于文件的对话记忆
String fileDir = System.getProperty("user.dir") + "/tmp/.chatmemory";
// ChatMemory chatMemory = new FileBasedMemory(fileDir);
// 初始化基于内存的对话记忆
// ChatMemory chatMemory = new InMemoryChatMemory();
// ChatMemory chatMemory = new DbBasedMemory();
chatClient = ChatClient.builder(dashscopeChatModel)
.defaultSystem(SYSTEM_PROMPT_DEFAULT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
// 自定义日志
new MyLoggerAdvisor()
//new ReReadingAdvisor()
).build();
}
/**
* AI基础对话(支持多轮对话记忆)
* @param messsage
* @param chatId
* @return
*/
public String doChat(String messsage, String chatId){
ChatResponse chatResponse = this.chatClient
.prompt()
.user(messsage)
.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.chatResponse();
String content = chatResponse.getResult().getOutput().getText();
log.info("content:{}", content);
return content;
}
/**
* AI编程报告(实战结构化输出)
* @param messsage
* @param chatId
* @return
*/
public ProgramingReport doChatWithReport(String messsage, String chatId){
ProgramingReport programingReport = chatClient
.prompt()
.user(messsage)
.system(SYSTEM_PROMPT_DEFAULT + "每次对话后都要生成问题结果报告,标题为{用户名}的编程宝典,内容为建议列表")
.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.entity(ProgramingReport.class);
log.info("programingReport:{}", programingReport);
return programingReport;
}
}
基于 mysql 的,这里在存入mysql时使用的时base64编码后的
package com.wfh.aiagent.chatmemory;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.wfh.aiagent.model.Conversation;
import com.wfh.aiagent.service.ConversationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
/**
* @Author FengHuan Wang
* @Date 2025/5/25 13:24
* @Version 1.0
*/
@Component
@Slf4j
public class DbBasedMemory implements ChatMemory {
@Resource
private ConversationService conversationService;
private static final Kryo kryo = new Kryo();
static {
kryo.setRegistrationRequired(false);
kryo.register(Conversation.class);
kryo.register(ArrayList.class);
kryo.register(MessageType.class);
kryo.register(HashMap.class);
kryo.register(org.springframework.ai.chat.messages.UserMessage.class);
kryo.register(org.springframework.ai.chat.messages.AssistantMessage.class);
kryo.register(org.springframework.ai.chat.messages.SystemMessage.class);
// 设置实例化策略
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
}
public DbBasedMemory() {
}
@Override
public void add(String conversationId, List<Message> messages) {
List<Message> messageList = getOrCreateConversation(conversationId);
messageList.addAll(messages);
saveConversation(conversationId, messageList);
}
@Override
public void add(String conversationId, Message messages) {
saveConversation(conversationId, List.of(messages));
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<Message> messageList = getOrCreateConversation(conversationId);
return messageList.stream()
.skip(Math.max(messageList.size() - lastN, 0)).toList();
}
@Override
public void clear(String conversationId) {
// 从数据库中删除对话
Conversation conversation = conversationService.getById(conversationId);
if (conversation != null) {
conversationService.getBaseMapper().deleteById(conversation.getId());
}
}
/**
* 获取或创建会话消息的列表(从mysql数据库中读取)
* @param conversationId
* @return
*/
private List<Message> getOrCreateConversation(String conversationId) {
try {
Conversation conversation = conversationService.getById(conversationId);
if (conversation != null && conversation.getMessage() != null) {
byte[] bytes = Base64.getDecoder().decode(conversation.getMessage());
try (Input input = new Input(bytes)) {
return kryo.readObject(input, ArrayList.class);
}catch (Exception e){
log.error("反序列化失败:{}", e.getMessage());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return new ArrayList<>();
}
/**
* 保存会话信息
* @param conversationId
* @param messages
*/
private void saveConversation(String conversationId, List<Message> messages) {
try (Output output = new Output(1024, -1)) {
kryo.writeObject(output, messages);
byte[] bytes = output.toBytes();
Conversation conversation = new Conversation();
conversation.setId(Integer.valueOf(conversationId));
conversation.setMessage(Base64.getEncoder().encodeToString(bytes));
if (conversationService.getById(conversationId) != null) {
conversationService.updateById(conversation);
} else {
conversationService.save(conversation);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是会有一个反序列化错误尚未解决
至此,基于mysql的对话记忆功能经过测试可以正常跑通流程 如图
建表语句非常简单,没做过多的字段
CREATE TABLE `conversation` (
`id` int NOT NULL COMMENT '对话id',
`message` text COLLATE utf8mb4_general_ci COMMENT '消息列表',
PRIMARY KEY (`id`),
KEY `conversation_id_index` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='历史消息对话表'