凌晨两点,测试同事在群里疯狂 @ 我:「对话机器人的长期记忆完全乱套了,用户明明聊过『我家猫叫豆包』,结果隔了几天再去问,它回顾成『你家狗叫雪饼』。更诡异的是,分页翻到第 3 页又出现了第 1 页的内容。现在线上 2000 多个用户的记忆都在错位,你快看看。」
打开监控,发现存储向量和元数据的那几张表写入 QPS 正常,API 返回 200,没有任何报错。这一刻就知道——不是简单的代码 bug,而是没做一致性回归测试,我们对「记忆存储」这个黑盒的信任从上线第一天就在裸奔。
记忆流水有四步
大模型长记忆存储(Mem0、LangChain 的记忆模块、自研的向量索引服务)通常依赖一套记忆流水:
第一步,用户与模型对话产生事实(memory fact)。第二步,事实经 embedding 后存入向量库(pgvector、Milvus 等),同时保留时间戳、用户 ID、会话 ID 等结构化字段。第三步,后续对话根据相似度加时间衰减召回 topN 记忆,进行分段分页拼接。
出问题的就是第三步:为了保证每次检索结果可复现、分页连贯,必须同时满足排序确定性加分页不重不漏。但实际环境中,很多人直接把 ORDER BY created_at 配上 LIMIT/OFFSET 就上线,并发写入立刻翻车——同一时间戳下插入顺序不确定,OFFSET 会跳过或重复记录。部分服务为了性能还会缓存元数据,一旦缓存更新不及时,就会出现「刚插入的记忆在查询时蒸发」的幽灵现象。
100 行 pytest 把黑盒变成可验证组件
常规的后端 CRUD 测试在这里完全失效——没法用几个手工编造的时间戳模拟生产环境中 2000 条记忆的并发写入、分页漂移、缓存不一致。必须搞一套自动化一致性测试框架,反复蹂躏你的记忆系统直到它服软为止。
目标:构建可对任意长记忆存储后端运行的一致性测试套件。核心思路是生成海量符合真实分布的记忆,批量并发插入,用不同分页和排序策略拉取,校验数学恒等式。
架构很简单:定义一个 MemoryStore 协议,包含 insert(memories) 和 recall(user_id, page, size, sort_order) 两个方法;然后围绕它写 pytest 测试用例,再通过依赖注入适配到任何具体数据库实现。从 Postgres 换成自研向量引擎,测试套件一行不用改。
坑一:LIMIT/OFFSET 分页像抽盲盒
2000 条记忆、页大小 50,翻到第 10 页返回 42 条(明明总数整除页大小后应有 40 页),而且第 11 页开头重复了上页末尾的 3 条。原因:并发插入时 created_at 微秒精度不足,多条记录时间戳完全一致。Postgres 不保证相同排序键下的稳定顺序。
解决:所有分页查询改为游标分页(keyset pagination),用上一次返回的最后一条记录的 (created_at, memory_id) 作为 WHERE 条件,彻底消除偏移量漂移。对外 API 接口需强制要求 after_timestamp 和 after_id 参数。
坑二:datetime 没时区,排序像开盲盒 2.0
测试环境一切正常,上线后记忆排序偶尔错乱。原因:测试机器用 UTC,datetime.now() 不带时区;生产环境的 Python 镜像时区设置为 Asia/Shanghai,同样不带时区,但 ORM 层写入时自动转字符串,导致排序既不是本地时间也不是 UTC。
解决:强制所有记忆时间戳使用带时区的 datetime.now(timezone.utc),存储层面统一采用带时区的 timestamptz 类型。如果底层是 JSON 字段,必须存 ISO8601 加 Z 后缀。
来源:掘金