凌晨两点,警报电话将我惊醒——我们的生产环境智能体连续三次对话出现了“失忆”现象。用户精心构建的上下文消失殆尽,投诉如潮水般涌来。我眯着眼查看日志,发现内存管理模块中的 回滚 方法被一次看似无害的代码重构破坏了。这次回滚不仅撤销了错误操作,还抹除了整个对话历史。更糟糕的是,我们现有的单元测试从未捕捉到这个缺陷:它们总是从一个全新的空数据库开始,永远无法覆盖像“将脏数据回滚到之前的快照”这样的跨会话场景。我花了三个小时进行调试,手动模拟中间状态,才最终 pinpoint 根本原因。那一刻我恍然大悟:我们缺少的不是测试,而是能够捕获整个“内存状态”的快照测试。
问题剖析
我们的大语言模型记忆系统使用 SQLite 进行本地持久化存储。每个会话拥有一张表,用于存储对话轮次、向量摘要和工具调用记录。两个关键操作如下:
-
save_snapshot(session_id):将会话的完整状态序列化到snapshots表中,创建一个回滚检查点。 -
rollback_to_snapshot(session_id, snapshot_id):当出现错误时,从快照重建会话表,并丢弃该时间点之后所做的所有更改。
这一机制一直运行平稳——直到我进行的一次重构改变了回滚逻辑内部的事务边界。执行回滚后,conversations 表重建正常,但 snapshots 表本身却被意外清空。下一次尝试回滚时,系统找不到任何先前的检查点。
为什么传统的单元测试没有发现这个问题?因为典型的测试流程是这样的:
def test_rollback():
db = create_in_memory_db()
db.save_snapshot("s1")
db.rollback_to_snapshot("s1", ...)
assert db.get_conversation("s1") == expected
所有操作都在单个进程中、在一个临时数据库内完成。然而,生产环境的场景截然不同:进程 A 保存快照并退出,随后进程 B 重新打开同一个数据库文件并执行回滚。文件级的持久化状态、预写式日志合并,甚至 snapshots 表在不同连接间的可见性——这些都没有经过测试。直白地说,我们测试了“逻辑”,却从未测试“存储”。
解决方案设计
我决定引入快照测试,但与使用基于文本的快照不同,我将把 SQLite 数据库文件本身视为一个不可变的制品。
免责声明:本文内容来自互联网,该文观点不代表本站观点。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请到页面底部单击反馈,一经查实,本站将立刻删除。