当前位置: 首页 > article >正文

鱼皮超级智能体文件读写报错

Spring AI Kryo 序列化报错Encountered unregistered class ID 解决方案在开发 Spring AI 聊天记忆功能时采用 Kryo 实现消息的文件持久化存储运行过程中突然报出com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID错误排查许久才发现核心问题特此记录解决方案帮各位开发者避坑。一、报错现象精准复现开发场景基于 Spring AI 实现聊天记忆功能自定义FileBasedChatMemory类使用 Kryo 序列化Message消息包含文本、图片等媒体类型并持久化到本地文件。报错核心信息com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: 104 Serialization trace: media (org.springframework.ai.chat.messages.UserMessage) at com.esotericsoftware.kryo.util.DefaultClassResolver.readClass(DefaultClassResolver.java:159) at com.esotericsoftware.kryo.Kryo.readClass(Kryo.java:758) at com.esotericsoftware.kryo.serializers.ReflectField.read(ReflectField.java:117) t com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:130) t com.esotericsoftware.kryo.Kryo.readObjectOrNull(Kryo.java:854) t com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:238) com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:44) com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:777) om.gaokaiyuan.yuaiagent.chatmemory.FileBasedChatMemory.getOrCreateConversation(FileBasedChatMemory.java:67) at c at at a a a补充说明注释掉单参数add方法如下后报错暂时消失但聊天记忆无法正常持久化本质是绕开了自定义的文件存储逻辑。// Override // public void add(String conversationId, Message message) { // saveConversation(conversationId, List.of(message)); // // ChatMemory.super.add(conversationId, message); // } // 注释后不报错但失去文件存储功能二、报错深层原因核心必懂很多开发者会误以为是代码逻辑错误比如消息追加逻辑但实际问题出在Kryo 序列化机制与 Spring AI 消息类型不兼容具体拆解为3点1. 核心矛盾Kryo 不支持 Spring AI Message 的媒体类型Spring AI 的UserMessage类聊天消息的核心类包含media字段用于存储图片、附件等媒体数据。而 Kryo 作为轻量级序列化框架默认不支持 Spring AI 内部封装的媒体类型未注册该类的序列化器导致序列化/反序列化时无法识别类 ID直接抛出异常。简单说Kryo 不认识 Spring AI 消息里的“图片/附件”字段一遇到就炸。2. 注释方法后不报错的“假象”我们自定义的FileBasedChatMemory实现了 Spring AI 的ChatMemory接口该接口有两个add方法// 接口方法1添加单条消息 void add(String conversationId, Message message); // 接口方法2添加多条消息 void add(String conversationId, ListMessage messages);当我们注释掉自定义的单参数add方法后Java 会自动调用接口的默认实现default void add(String conversationId, Message message) { add(conversationId, List.of(message)); }这个默认方法不会调用我们自定义的saveConversationKryo 序列化逻辑相当于“绕开了文件存储”自然不会触发 Kryo 报错但代价是聊天记忆无法持久化到文件重启服务后消息全部丢失。3. 补充误区不是“追加逻辑”的问题排查初期很容易误以为是“消息追加时覆盖了旧文件”但实际测试发现即使只发送一条带图片的消息首次序列化时就会报错——核心问题是“序列化本身失败”和追加逻辑无关。三、两种解决方案按需选择均保留文件存储核心思路要么让 Kryo 支持 Spring AI 媒体类型复杂不推荐要么替换序列化方式简单、稳定推荐。以下提供两种可直接落地的方案均保留文件存储功能。方案一替换序列化方式推荐JSON 序列化放弃 Kryo改用 Jackson 实现 JSON 序列化。JSON 支持所有 Spring AI 消息类型且文件可直接打开查看兼容性强、稳定性高是最推荐的方案。完整可运行代码直接替换原FileBasedChatMemorypackage com.gaokaiyuan.yuaiagent.chatmemory; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.messages.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.*; public class FileBasedChatMemory implements ChatMemory { private final String BASE_DIR; private final ObjectMapper objectMapper new ObjectMapper(); // 构造方法初始化存储目录自动创建不存在的目录 public FileBasedChatMemory(String dir) { this.BASE_DIR dir; File baseDir new File(dir); if (!baseDir.exists()) baseDir.mkdirs(); } // 单条消息追加正确逻辑支持媒体类型 Override public void add(String conversationId, Message message) { ListMessage messages get(conversationId); messages.add(message); save(conversationId, messages); } // 多条消息追加正确逻辑复用单条追加逻辑 Override public void add(String conversationId, ListMessage messages) { ListMessage existing get(conversationId); existing.addAll(messages); save(conversationId, existing); } // 读取指定对话的所有历史消息反序列化 Override public ListMessage get(String conversationId) { File file getFile(conversationId); if (!file.exists()) return new ArrayList(); try { // 读取JSON文件转成Map列表避免直接序列化Message报错 ListMapString, Object list objectMapper.readValue(file, List.class); ListMessage messages new ArrayList(); // 遍历Map还原成Spring AI Message对象 for (MapString, Object map : list) { MessageType type MessageType.valueOf((String) map.get(messageType)); String text (String) map.get(text); Object media map.get(media); Message msg; // 处理带媒体图片/附件的消息 if (media ! null) { msg new UserMessage(text, List.of(media)); } else { // 处理纯文本消息区分消息类型 msg switch (type) { case USER - new UserMessage(text); case ASSISTANT - new AssistantMessage(text); case SYSTEM - new SystemMessage(text); case TOOL - new ToolMessage(text, (ListString) map.get(toolCalls)); }; } messages.add(msg); } return messages; } catch (Exception e) { e.printStackTrace(); return new ArrayList(); } } // 清空指定对话的历史消息删除文件 Override public void clear(String conversationId) { try { Files.deleteIfExists(getFile(conversationId).toPath()); } catch (IOException e) { e.printStackTrace(); } } // 核心将消息列表序列化为JSON保存到文件 private void save(String conversationId, ListMessage messages) { ListMapString, Object list new ArrayList(); for (Message m : messages) { MapString, Objectgt; map new HashMap(); // 存储消息类型、文本内容 map.put(messageType, m.getMessageType().name()); map.put(text, m.getText()); // 单独处理媒体字段避免序列化失败 if (m instanceof UserMessage userMsg userMsg.getMedia() ! null !userMsg.getMedia().isEmpty()) { map.put(media, userMsg.getMedia().getFirst()); } list.add(map); } try { // 写入JSON文件格式化输出可直接打开查看 objectMapper.writerWithDefaultPrettyPrinter().writeValue(getFile(conversationId), list); } catch (IOException e) { e.printStackTrace(); } } // 获取对话对应的文件以conversationId为文件名后缀为json private File getFile(String conversationId) { return new File(BASE_DIR, conversationId .json); } }方案优势支持文本、图片等所有 Spring AI 消息类型不报错文件为 JSON 格式可直接打开查看、调试消息追加逻辑正确历史记录不会丢失完全兼容 Spring AI 的ChatMemory接口无需修改其他代码。方案二Kryo 注册自定义序列化器不推荐复杂易踩坑如果必须使用 Kryo比如追求极致序列化性能需要为 Spring AI 的UserMessage及其内部媒体类型注册自定义序列化器步骤如下仅作参考不推荐生产使用// 1. 初始化Kryo时注册自定义序列化器 static { kryo.setRegistrationRequired(false); kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); // 为UserMessage注册自定义序列化器 kryo.register(UserMessage.class, new UserMessageSerializer()); } // 2. 自定义UserMessage序列化器 public class UserMessageSerializer extends SerializerUserMessage { Override public void write(Kryo kryo, Output output, UserMessage object) { // 手动序列化UserMessage的字段text、media等 output.writeString(object.getText()); // 序列化media字段需根据实际类型处理 kryo.writeObject(output, object.getMedia()); } Override public UserMessage read(Kryo kryo, Input input, ClassUserMessage type) { // 手动反序列化字段还原UserMessage对象 String text input.readString(); ListObject media kryo.readObject(input, ArrayList.class); return new UserMessage(text, media); } }方案缺点需要为 Spring AI 所有相关消息类型如AssistantMessage注册序列化器工作量大Spring AI 版本更新后消息类结构可能变化序列化器需要同步修改维护成本高仍可能出现未知的序列化兼容问题稳定性不如 JSON 方案。四、总结与避坑建议1. 核心结论本次报错的本质是Kryo 不兼容 Spring AI 消息的媒体类型与消息追加逻辑无关注释单参数add方法不报错是因为绕开了自定义的文件存储并非真正解决问题。2. 避坑建议开发 Spring AI 聊天记忆功能如需文件持久化优先使用 JSON 序列化Jackson兼容性强、维护成本低避免使用 Kryo 序列化 Spring AI 相关对象除非明确不需要媒体类型仅纯文本消息遇到 Kryo 序列化报错优先排查“是否有未注册的自定义类型/第三方框架类型”而非业务逻辑。3. 最终推荐直接使用方案一JSON 序列化复制代码替换原类无需修改其他业务代码即可实现“文件存储 支持图片消息 不报错”的需求是最稳定、最高效的解决方案。如果在使用过程中遇到 JSON 序列化的细节问题如日期、特殊字段可留言补充后续将持续完善。

相关文章:

鱼皮超级智能体文件读写报错

Spring AI Kryo 序列化报错:Encountered unregistered class ID 解决方案在开发 Spring AI 聊天记忆功能时,采用 Kryo 实现消息的文件持久化存储,运行过程中突然报出 com.esotericsoftware.kryo.KryoException: Encountered unregistered cl…...

去哪儿商户端分析

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 部分python代码data {"departur…...

安装 Nunchaku

1、查看torch版本 命令行输入 C:\Users\用户名\Documents\ComfyUI\.venv\Scripts> python -m pip show torch 输出 版本信息 Name: torch Version: 2.11.0 2、查看python版本 输入命令: PS C:\Users\用户名\Documents\ComfyUI\.venv\Scripts> python --versio…...

Jetson Nano + 镭神16线雷达:手把手教你将TARE自主探索算法部署到阿克曼机器人

Jetson Nano与镭神16线雷达:TARE算法在阿克曼机器人上的实战部署指南 硬件选型与系统架构设计 当我们需要将TARE自主探索算法部署到真实机器人平台时,硬件选型直接决定了后续开发流程的顺畅程度。经过多次项目实践,我发现Jetson Nano开发板与…...

Docker部署Ollama模型墒

前言 Kubernetes 本身并不复杂,是我们把它搞复杂的。无论是刻意为之还是那种虽然出于好意却将优雅的原语堆砌成 鲁布戈德堡机械 的狂热。平台最初提供的 ReplicaSets、Services、ConfigMaps,这些基础组件简单直接,甚至显得有些枯燥。但后来我…...

嵌入式设备电量显示实战:MCP3421 ADC采集从原理到代码全解析(附避坑指南)

嵌入式设备电量显示实战:MCP3421 ADC采集从原理到代码全解析(附避坑指南) 在移动设备与物联网终端的设计中,精确的电量监测如同设备的"生命体征监测仪"——它不仅关乎用户体验,更直接影响系统稳定性。传统电…...

手机维修师傅的“内功心法”:看懂手机屏排线上的MIPI、I2C、SPI信号,快速定位不开机、花屏、触摸失灵故障

手机维修实战:通过屏排线信号诊断显示与触摸故障的黄金法则 当你面对一部摔落后屏幕全黑但能听见系统声音的iPhone,或是进水后出现彩色条纹的安卓手机时,90%的维修师傅会直接更换屏幕总成。而真正的高手,会拿起万用表和示波器&…...

FastbootEnhance:5步掌握Windows平台最强安卓刷机工具

FastbootEnhance:5步掌握Windows平台最强安卓刷机工具 【免费下载链接】FastbootEnhance A user-friendly Fastboot ToolBox & Payload Dumper for Windows 项目地址: https://gitcode.com/gh_mirrors/fa/FastbootEnhance 还在为复杂的Fastboot命令行操作…...

2026年OpenClaw怎么搭建?阿里云6分钟新手部署OpenClaw,千问大模型安装指南

2026年OpenClaw怎么搭建?阿里云6分钟新手部署OpenClaw,千问大模型安装指南。本文面向零基础用户,完整说明在轻量服务器与本地Windows11、macOS、Linux系统中部署OpenClaw(Clawdbot)的流程,包含环境配置、服…...

代码随想录一刷记录Day25——leetcode491.递增子序列

前言 之前就有刷代码随想录,但奈何总是三天打鱼两天晒网,而且刷的也很囫囵吞枣,于是乎决定参加代码随想录训练营,准备精刷一遍,希望自己能坚持下去,结营后自己的算法水平能更上一个level,冲ing…...

2026年怎么搭建OpenClaw?2分钟新手本地部署OpenClaw及百炼Coding Plan教程

2026年怎么搭建OpenClaw?2分钟新手本地部署OpenClaw及百炼Coding Plan教程。本文面向零基础用户,完整说明在轻量服务器与本地Windows11、macOS、Linux系统中部署OpenClaw(Clawdbot)的流程,包含环境配置、服务启动、Ski…...

藏在底层的“树之家族”:从二叉树到B+树,你天天用却未必懂

写在前面“二叉树、二叉查找树、平衡二叉树、红黑树、B树、B树……这些数据结构,我好像只在课本上见过。平时写业务代码,一个ArrayList、HashMap走天下,谁没事自己写树啊?”这是很多后端开发者的真实想法。包括我自己,…...

平时没感觉突然痛到动不了,颈椎病腰间盘突出早有潜伏信号,成因症状与防护干货速收藏

很多人觉得颈腰椎病是 "慢性病",会慢慢加重,却不知道它常常以 "突然爆发" 的形式出现。 不少患者前一天还正常工作生活,第二天就突然颈痛难忍、腰痛到无法下床,这其实是因为疾病早已在体内潜伏多年&#xff…...

身份证校验码的奥秘:从算法原理到实际应用

1. 身份证号码的结构解析 每次填写身份证号码时,你有没有好奇过这串数字背后的含义?其实这18位数字就像一个人的数字档案,每一段都藏着特定信息。前6位是地址码,相当于你的户籍所在地的"邮政编码"。接着的8位是出生日期…...

奶奶都能看懂的 C# —— 手把手 LINQ懈

一、 什么是 AI Skills:从工具级到框架级的演化 AI Skills(AI 技能) 的概念最早在 Claude Code 等前沿 Agent 实践中被强化。最初,Skills 被视为“工具级”的增强,如简单的文件读写或终端操作,方便用户快速…...

生态研究者的数据工具箱:如何高效获取并利用全国自然保护区边界shp文件

生态研究者的空间数据实战:从自然保护区边界到科学决策的全流程解析 清晨的阳光透过实验室窗户洒在电脑屏幕上,生态学家林教授正在为即将开展的生物多样性研究项目准备基础数据。她深知,精确的自然保护区边界数据是这项研究的基石——它不仅关…...

MySQL进程监控与优化:高效查询与资源释放指南

1. MySQL进程监控基础:从入门到精通 刚接触MySQL数据库管理时,我最头疼的就是遇到服务器突然变慢的情况。后来才发现,学会查看和管理MySQL进程是解决问题的关键第一步。就像医院里的监护仪能显示病人生命体征一样,MySQL也提供了多…...

模拟退火遗传算法路径优化

模拟退火遗传算法 模拟退火遗传算法是将模拟退火算法的概率突跳特性,与遗传算法的群体搜索机制相结合的混合智能优化算法,目的是平衡全局搜索能力与局部寻优精度,避免单一算法易陷入局部最优的问题。 基础原理拆解 1. 遗传算法的底层逻辑 遗传算法借鉴自然选择与基因遗传…...

Java Stream API 的底层逻辑

Java Stream API的底层逻辑探秘 Java Stream API自Java 8引入后,彻底改变了集合操作的方式。它通过声明式编程风格,将复杂的迭代逻辑简化为链式调用,同时隐藏了底层实现的复杂性。但Stream并非简单的语法糖,其背后融合了惰性求值…...

电磁暴露与场域隐身理论

——兼论人类通讯升维对外星文明探测与UFO现象的直接影响 一、核心立论 1. 人类当前主流通讯电磁波广播模式,本质是电磁场二维切片辐射,与爱迪生发明的电灯泡在物理底层完全同源,属于低维、暴露、高能耗的“宇宙灯塔行为”。 2. 高级星际文明…...

算法面试常见题型分类

算法面试常见题型分类指南 在技术面试中,算法能力是考察候选人逻辑思维和问题解决能力的重要环节。无论是校招还是社招,算法题往往是筛选候选人的关键门槛。掌握常见的题型分类,能够帮助面试者高效准备,提升解题能力。本文将介绍…...

兼容FX3U源码的大神级编程资料:增加以太网下载功能,支持MODBUS-TCP与定位指令集

18650锂电池高温热失控引言 在工业控制、汽车电子和分布式系统领域,CAN(Controller Area Network)总线因其高可靠性、实时性和抗干扰能力而成为首选通信协议。本文深入分析基于STM32F10x微控制器的CAN网络通信模块,重点解析其如何…...

用户遇到了Docker镜像拉取超时的问题。我需要提供故障排除和解决方案。

你遇到的 Client.Timeout exceeded while awaiting headers 错误,核心原因是网络连接不稳定。虽然你已经配置了加速器,但从报错看,问题很可能就出在这个加速器地址上。 ⚠️ 配置了镜像源,为何还会超时? 这其实是个很常…...

仅限SITS2026参会者内部流通的NLP架构迁移Checklist(含自动校验脚本),现在获取倒计时72小时

第一章:SITS2026演讲:AI原生自然语言处理 2026奇点智能技术大会(https://ml-summit.org) AI原生自然语言处理(AI-Native NLP)标志着范式迁移的完成——模型不再被“适配”到任务,而是从设计之初即以任务语义、推理闭环…...

音视频AI工程化最后一公里(SITS2026原生框架实测报告:FFmpeg vs WebAssembly vs 原生Kernel Mode)

第一章:音视频AI工程化最后一公里的挑战与SITS2026原生框架定位 2026奇点智能技术大会(https://ml-summit.org) 在音视频AI大规模落地过程中,“最后一公里”并非指部署时长或物理距离,而是指模型能力与真实业务场景之间不可忽视的语义鸿沟—…...

基于springboot+vue红色教育基地管理系统hx0944FHZG

文章目录详细视频演示技术介绍功能介绍核心代码系统效果图源码获取详细视频演示 文章底部名片,获取项目的完整演示视频,免费解答技术疑问 技术介绍 开发语言:Java 框架:ssm JDK版本:JDK1.8 服务器:tomca…...

为什么92%的AI团队还在用传统Scrum硬扛?:揭秘LLM驱动开发下的3层敏捷解耦新模型

第一章:AI原生软件研发敏捷开发方法适配 2026奇点智能技术大会(https://ml-summit.org) AI原生软件的研发范式正从根本上挑战传统敏捷开发的边界——模型迭代、数据漂移、提示工程验证与系统级可观测性耦合,使Scrum的固定Sprint节奏与用户故事拆分逻辑面…...

基于springboot+vue好漂酿自助美甲管理系统hx0749FECS

文章目录详细视频演示技术介绍功能介绍核心代码系统效果图源码获取详细视频演示 文章底部名片,获取项目的完整演示视频,免费解答技术疑问 技术介绍 开发语言:Java 框架:ssm JDK版本:JDK1.8 服务器:tomca…...

OpenCASCADE与CMake实战:从零搭建Visual Studio 2019开发环境

1. 环境准备:搭建OpenCASCADE开发环境的基础条件 在开始配置OpenCASCADE项目之前,我们需要确保开发环境已经具备所有必要的工具和依赖项。这就像盖房子前需要准备好砖块、水泥和图纸一样,缺少任何关键组件都会导致后续工作无法顺利进行。 首先…...

从TwinCAT配置到Simulink-Realtime:打通松下伺服EtherCAT实时控制的实践指南

1. 为什么需要打通TwinCAT和Simulink-Realtime 在工业自动化领域,EtherCAT因其出色的实时性能和灵活的拓扑结构,已经成为伺服控制的首选通信协议。而将TwinCAT与Simulink-Realtime结合使用,可以充分发挥两者的优势:TwinCAT提供专业…...