039、Agent的记忆持久化:Redis与SQLite

发布时间:2026/9/12 0:49:22

039、Agent的记忆持久化:Redis与SQLite 039、Agent的记忆持久化Redis与SQLite那天下午我差点把服务器砸了。客户那边报了个诡异的问题Agent跟用户聊了二十分钟一切正常。但只要服务一重启Agent就像失忆了一样用户刚才报的工单号、车牌号、甚至自己刚才说过的话全都不记得了。用户当场炸毛“这破AI是金鱼吗”我上去一看代码发现对话历史存在一个全局MapString, ListMessage里。进程活着的时候一切美好进程一死记忆跟着GC一起烟消云散。这事儿不怪Agent怪我没给Agent装上“永久记忆”——也就是所谓的记忆持久化。今天这期博客就聊聊我做Agent记忆持久化时趟过的两条路Redis 和 SQLite。别指望我给你列个表对比完事我会直接把调试现场和踩坑记录摊开给你看。先说Agent的记忆到底长什么样。一个正经Agent它的记忆分两层短期记忆是当前会话的上下文窗口比如最近几轮对话、临时状态长期记忆是跨会话的持久信息比如用户偏好、历史事实、业务数据。这两层需求完全不一样短期要高吞吐低延迟长期要稳定可靠不丢失。于是很多人犯了和我一样的错什么都往内存里塞或者什么都往数据库里写。我后来把短期记忆放在Redis里把长期记忆丢给SQLite。先说Redis。用Redis做Agent的短期记忆核心价值在一个“快”字。对话过程中每一轮消息都要读写用户还在等响应你搞个SQL查询动辄几十毫秒体验直接崩了。Redis 是纯内存操作延迟通常在亚毫秒级刚好满足这个场景。我用的是redis-py典型的结构是这样importredisimportjson rredis.Redis(hostlocalhost,port6379,db0)# 把一整轮对话存成一条hash记录key是session_id# 别用string硬拼后面取字段会哭的session_keyfagent:session:{session_id}r.hset(session_key,mapping{round:round_num,role:role,content:content,time:timestamp})# 同时设置过期时间比如30分钟无操作就自动清空r.expire(session_key,1800)这里有个坑我差点没哭出来。Redis的key如果设置了TTL过期之后整个会话就没了。我当时给所有会话key都配了60分钟过期结果用户聊到第59分钟时打了个长电话回来继续聊Agent直接失忆。更尴尬的是因为Redis里的历史没了Agent还跟用户说“我们第一次聊天吧”。你说气不气。解决方式很简单每次有新消息写入时都要重新刷新过期时间或者干脆在业务逻辑里做成滑动窗口——每次读写都重新expire。再说说为什么别把所有历史一股脑全塞进Redis。内存不是无限的而且Redis本身是缓存定位你把所有用户几十万条聊天记录都塞进去等机器内存报警就等着被运维请喝茶吧。我的建议是Redis里只保存当前会话的近期上下文比如最近10轮对话。更早的打包丢到SQLite里去存档。这个“导出压缩”的动作我用的是异步任务不影响主流程。代码大概长这样# 注意这里别同步做会卡住对话响应defarchive_memory(session_id,cutoff_time):# 从Redis取出这个session下早于cutoff_time的所有消息# 批量写入SQLite对应的表中# 然后从Redis里删掉这些消息或者只保留最近N轮pass接下来是SQLite。一开始我有点瞧不上这玩意儿觉得就是个小文件数据库能行吗后来发现行得很。Agent的长期记忆恰恰不需要每秒几千次的写入它要的是可靠和结构化。SQLite 单文件、零运维、事务支持好特别适合在单机环境里存长期记忆。我建了一张表记录用户长期事实比如姓名、车辆信息、服务偏好还有一张表存历史会话的归档消息。每次Agent启动时先查SQLite把该用户的事实加载到自己的长期知识库里。SQLite 的写入锁是个真坑。刚开始我用多线程写SQLite结果一有并发写直接报database is locked。后来才明白SQLite同一时刻只允许一个写事务写多读少的场景还好如果你边聊天边写历史用户正在疯狂发消息多个线程同时写就会阻塞。解决方案有两个一是把所有SQLite写操作丢到同一个单线程队列里二是开启 WAL 模式它能让读和写并发执行。我建议两个都上importsqlite3 connsqlite3.connect(agent_memory.db,check_same_threadFalse)# 开启WAL模式这里亲自试过并发读写靠谱多了conn.execute(PRAGMA journal_modeWAL;)# 顺便把busy_timeout设长一点别一锁就报错conn.execute(PRAGMA busy_timeout5000;)写入的时候一定要用参数化查询别拼字符串。有次我把用户输入直接拼进SQL结果用户发了句“are you sure”单引号差点没把库给炸了。这个教训和Python的SQL注入警告一样深刻。长期记忆的表结构我建议别搞太死板。因为Agent的记忆类型会变今天记的是用户生日明天可能记的是用户关注的股票代码。这时候最好用“实体-属性-值”的方式或者干脆存JSON。SQLite 支持JSON字段配合Python的字典操作舒服得很。比如# 别用text字段硬存你迟早会想查某个属性的conn.execute( CREATE TABLE IF NOT EXISTS long_term_memory ( user_id TEXT, memory_key TEXT, memory_value TEXT, updated_at TIMESTAMP, PRIMARY KEY (user_id, memory_key) ) )# 存的时候序列化memory_valuejson.dumps({stock:[AAPL,TSLA],preference:low_risk})conn.execute(INSERT OR REPLACE INTO long_term_memory (user_id, memory_key, memory_value, updated_at) VALUES (?, ?, ?, ?),(user_id,investment_preferences,memory_value,datetime.now()))读出来的时候再json.loads一下搞定。再聊聊两个方案怎么配合。我的习惯是用户进来先查SQLite把长期事实加载到Agent的上下文里对话过程中每轮消息先写Redis的短期窗口保证当前逻辑能快速取到上下文当一轮对话结束或者达到归档阈值就把Redis中的本轮记录搬进SQLite的历史表同时把Redis里对应的key清掉或者缩短TTL。这样Agent既有短期的高频响应能力又有长期的稳定记忆。还有一个血泪教训记忆持久化一定要加版本号或者时间戳。Agent的记忆不是静态的用户今天说喜欢运动风明天可能就改成商务风了。如果你只存一个值没有时间维度Agent就会把昨天的旧信息和今天的新信息混在一起产生“记忆混乱”。我后来在SQLite表里加了个version字段每次用户确认新信息都会递增版本读取的时候取最高版本。虽然有点丑但确实有效。顺便提一句Redis的持久化配置也别忽视。虽然Redis是内存数据库但你至少可以开启AOF或RDB来做落盘。我遇到过一台机器断电重启Redis所有会话数据全没的情况。后来我开了AOF但代价是写入性能略有下降。取个平衡如果你只是把Redis当短期缓存丢了也不致命那就别开AOF省点事但如果Redis里存了用户当下没归档的重要上下文那就开AOF而且注意设置appendfsync everysec别每写一次都同步磁盘慢。那到底该选Redis还是SQLite说实话不冲突。有一类Agent只用Redis做短期长期直接扔给后端MySQLSQLite反而多余。但如果你做的是本地运行、单机部署、边缘设备上的AgentSQLite就是最佳选择因为它不需要额外服务存在本地文件里随拿随走。反过来如果你的Agent是云端多实例部署需要多个进程共享同一个短期记忆那SQLite的文件锁就不合适了必须上Redis这种独立的KV服务。我见过有的方案把SQLite放在网络盘上然后多实例共用。别这么干SQLite的锁机制在网络文件系统上会出各种怪异问题比你想象中难缠得多。再聊聊序列化的事。Agent记忆里不光有文本还可能包含状态机、对象图、甚至函数调用的缓存结果。你在存Redis或者SQLite之前一定要设计好序列化格式。我现在统一用JSON但记住JSON只能保存基本数据类型。如果你的记忆里有datetime、Decimal、bytes这类对象得先转换成字符串或整型。不然json.dumps直接报TypeError。这里有个小技巧自定义一个JSON编码器把常见类型都转好免得每个地方都写转换逻辑。importjsonfromdatetimeimportdatetimeclassMemoryEncoder(json.JSONEncoder):defdefault(self,o):ifisinstance(o,(datetime,)):returno.isoformat()ifisinstance(o,bytes):returno.decode(utf-8,errorsignore)# 还有别的类型自己扩展returnstr(o)然后json.dumps(memory_obj, clsMemoryEncoder)省心。最后说下测试。我建议你写一套模拟“重启后记忆恢复”的集成测试。别等到线上用户骂娘才来后悔。做法很简单启动Agent跟它聊几轮存一些关键信息然后模拟进程崩溃强制退出别客气直接kill -9再重启Agent看它能不能正确回忆出刚才聊的内容。这个测试跑通了你的持久化策略才算基本合格。我自己的经验是记忆持久化不要追求一步到位。先做最简单的方案跑起来再根据真实使用场景慢慢优化。如果你刚开始就把Redis和SQLite玩出花来反而容易被复杂逻辑带偏。毕竟Agent的核心是智能记忆只是它的地基。地基塌了楼再漂亮也没用。下次你的Agent再“失忆”先查一下它到底有没有长期记忆的落库别只怪模型不行。
延伸阅读

更多相关文章

2026/9/12 0:49:22

锦鲤保,把“专业、利他、诚信“做进每一次服务

买保险,普通人最纠结的从来不是"要不要买",而是"该信谁"。 一边是看不懂的条款、听不完的销售话术,一边是万一出险赔不下来的后顾之忧。要破解这道难题,光靠一篇科普文章不够,得有一个真正站在你这…

2026/9/12 0:44:21

基于AD5933阻抗谱的同轴电缆长度与负载检测装置设计

简介:2023年电赛B题同轴电缆长度与终端负载检测装置配套资料包,面向电子设计竞赛参赛者、通信/仪器方向学生及硬件工程师。内容围绕TDR时域反射、阻抗分析等测量原理,覆盖赛题解析、硬件设计、代码实现与调试方法等完整流程。资源共42个文件&…

2026/9/12 0:44:21

B站注册时间查询指南:从个人主页到API接口,快速获取入站日期

1. 先搞明白:注册时间在B站到底怎么记录今天这篇只聊一件事:怎么快速查出自己哔哩哔哩账号的注册时间。很多朋友想给账号过个“入站纪念日”,或者想确认自己是不是某位UP主的首批粉丝,结果在个人空间里翻半天也找不到那一行“入站…

2026/9/12 2:34:35

三步跑通大模型推理加速:TensorRT-LLM 实战指南

三步跑通大模型推理加速:TensorRT-LLM 实战指南 【免费下载链接】TensorRT-LLM TensorRT LLM provides users with an easy-to-use Python API to define Large Language Models (LLMs) and supports state-of-the-art optimizations to perform inference efficien…

2026/9/12 2:34:35

开源音乐播放器定制开发与合法技术方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 2:34:35

Spark交通大数据实时分析实战:轨迹清洗、OD矩阵与特征工程

简介:本资源是一套基于Apache Spark构建的交通数据分析系统完整实现,面向计算机、电子信息工程及数学等专业的本科生与研究生,适用于课程设计、期末大作业及毕业设计等实践场景,聚焦交通流统计、实时车速监测、异常事件预警等典型…

2026/9/12 2:29:35

基于YOLOv5与Dlib的疲劳驾驶检测系统:从目标框选到PERCLOS判定

简介:这份基于YOLOv5、dlib与OpenCV的疲劳驾驶检测完整项目,面向正在准备毕业设计或课程设计的计算机专业学生,也适合需要实战练习的开发者。整套方案包含算法源代码、预训练权重文件与详细文档,从人脸关键点定位、眼部纵横比计算…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码