给学习助手接入记忆后,它开始主动提到“你主要写 Java”。这看起来更了解用户,但如果用户已经转向 Kotlin,个性化反而增加了错误。评价记忆不能只数系统保存了多少条,也不能只看回答里有没有出现用户信息,必须检查它是否在正确时刻使用了正确事实。
本文是 AI 记忆工程专题的最后一篇。先给出已经运行的 Java 离线测试结果,再设计真实模型实验。两部分使用不同证据标准:前者验证确定性工程行为,后者评估回答收益;本文没有运行付费模型,也没有生成框架准确率排行榜。
已运行的实验条件
运行日期为 2026-09-23,使用 Java 21.0.1、标准库和固定内存样例。时钟固定为 2026-09-23T08:00:00Z。输入是虚构用户 Alice、Bob 的几条声明;没有外部数据集,没有调用模型或 Embedding 服务,也没有访问向量数据库。
实现源码和测试源码可以直接下载复现。执行以下命令,测试失败时进程会以错误结束;不需要开启 Java 的 -ea 选项。
javac -encoding UTF-8 -d out MemoryDemo.java MemoryDemoTest.java
java -cp out MemoryDemoTest
本次实际输出为:
RESULT 20/20 passed; deterministic contracts, not LLM accuracy.
这表示二十个具名测试全部完成,不表示“记忆准确率百分之百”。测试集由示例作者设计,规模很小,输入模式受限,也没有覆盖真实语言分布。源码发生变化后,应重新运行,不能把这次结果永久贴到所有未来版本上。
二十项测试覆盖什么
| 分组 | 测试项数 | 检查内容 |
|---|---|---|
| 提取 | 2 | 支持的句式;无关、否定和问句示例不写入 |
| 写入 | 4 | 同事件幂等、载荷冲突、独立证据合并、目标补充背景 |
| 时间与版本 | 3 | 替换关闭旧区间、到期端点、开始端点 |
| 用户隔离 | 1 | 相同事件 ID 在不同用户下独立处理 |
| 遗忘 | 3 | 删除全部版本、旧事件不复活、新事件可以重建 |
| 检索与装配 | 4 | 无匹配、稳定排序、中英文词项、整条上下文预算 |
| 输入约束 | 3 | 乱序、同时冲突、非法时间及容量参数 |
有些测试检查多个相关断言,但这里按源码中的具名场景计数,避免用断言数量夸大覆盖范围。它们证明的是我们明确实现的契约。例如,删除后仍能搜索到学习进度不算失败,因为删除范围只包含学习目标;测试预期必须先与产品语义一致。
尚未覆盖的能力包括跨进程持久化、多实例并发、队列重放、新事件 ID 携带旧数据的回写、完整中文主体识别、语义向量检索和模型回答。把这些缺口列出来,能防止读者把一个可运行程序当成经过生产验证的服务。
真实模型实验先定义问题
建议先提出一个窄问题:结构化记忆能否在固定上下文预算下改善跨会话学习助手的回答,同时降低旧事实误用?这个问题允许比较正确率、过期信息使用和成本,而不是用“更聪明”这样难以测量的描述。
任务集应包含需要记忆和不需要记忆两类问题。否则系统只要在每次回答里强行引用历史,就可能在表面上表现更好。还要加入无法回答的问题,检验系统能否承认没有证据。例如从未记录用户周末可用时间时,不应根据工作语言猜测学习安排。
LongMemEval 的原始任务组织包含信息提取、跨会话推理、知识更新、时间推理与信息不足时的拒答,可以作为设计维度的参考。本专题查阅的是固定提交下的原始说明,不报告该基准成绩,也不混用仓库中其他版本的数据或结果。固定版本说明
三组对照如何保持公平
| 组别 | 可使用的历史信息 | 目的 |
|---|---|---|
| A:无跨会话记忆 | 当前问题与本次会话 | 建立没有历史帮助时的基线 |
| B:历史摘要 | 同一历史输入的持续摘要 | 检查压缩历史是否已经足够 |
| C:结构化记忆 | 同一历史提取出的带来源、类型、时间的信息 | 检查结构与生命周期治理的增益 |
三组使用相同的回答模型、知识库、问题和生成参数,并设定相同的历史信息预算。B 和 C 的预处理可能消耗不同资源,应分别记录构建成本与单次回答成本。不能只统计 C 的回答费用,却忽略提取、Embedding 和后台维护。
数据划分以用户或完整会话序列为单位,避免同一个人的近似事件同时出现在调参与测试中。提示词、阈值和重排配置应在验证集确定,测试集只用于最终评估。为时变问题保存明确截止时间,检索端和标注端使用同一时间语义。
指标需要对应失败位置
写入阶段可以统计候选事实准确性、应保留信息的覆盖,以及不应写入内容的误写情况;更新阶段检查当前值正确性和过期事实误用;检索阶段衡量正确证据是否进入候选及最终上下文;回答阶段检查任务答案、证据一致性和合理拒答。
“检索命中正确事实”与“回答正确”需要分别打分。前者通过、后者失败时,应优先查看装配或生成;前者失败但回答碰巧正确,也不能认为记忆链路有效,模型可能依靠常识或猜测回答。保留中间证据可以避免把偶然结果当成架构收益。
工程指标则记录写入和读取延迟分布、失败率、存储与索引规模、模型请求次数及实际 Token 用量。字符预算不能换算成通用费用;具体价格应按实验时的模型和计费规则记录。本文没有这些实测数据,因此不填估算数字制造精确感。
怎样避免自我评分带来的偏差
优先对结构化答案使用可检查规则,例如当前语言是否为 Kotlin、引用来源是否存在、时间范围是否正确。需要自然语言判断时,制定评分标准并抽样人工复核。模型裁判可以辅助,但同一个模型生成、解释并给自己评分,容易形成共同偏差。
报告应该保留样本量、分组结果和不确定性,不仅展示平均分。用户之间的历史长度和变化次数可能不同,小样本中几个长历史用户就可能显著影响结果。可以按用户聚合后比较,并提供置信区间;具体统计方法要与样本组织方式一致。
同时保留错误案例。一个体系在稳定偏好问题上表现良好,却在事实更新上更差,平均分可能掩盖风险。把错误按主体混淆、过期使用、证据缺失、否定理解和预算截断分类,往往更容易导出下一轮可以实施的修复。
用消融实验决定该加什么
在结构化记忆组内,可以一次只关闭一个机制:不做时间过滤、不保留来源、不做类型限制或不重排。比较相同样本上的变化,判断模块是否真的贡献收益。不要同时更换模型、增加 Top K 又引入图数据库,然后把所有提升归给某一个组件。
也应测试负收益:记忆错误时是否比没有记忆更容易误导?用户明确要求不要参考过去偏好时,系统是否仍然使用?删除之后,索引延迟是否导致短时间内复现?这些问题决定系统能否长期可信,不只是一次演示是否流畅。
针对学习助手,可以先准备一小组人工审阅的完整时间线,再逐步扩展覆盖。关键是让标注包含当时的正确状态和允许引用的证据,而不仅是一句参考答案。这样后续换框架或索引时,仍能使用同一套行为标准。
从实验结论回到工程决策
如果 B 与 C 在主要任务上差异很小,而 C 明显增加维护成本,摘要方案可能已经满足当前需求。如果 C 在事实变化和删除场景中明显改善,应检查收益来自结构、时间过滤还是更好的提取。只有弄清来源,才能决定下一步投入。
上线后还需要观察用户纠正、删除、重新提问和放弃任务等信号,但不能把这些信号简单解释为准确率。用户可能只是改变了偏好,也可能因为界面问题放弃。离线标注、线上行为和人工案例复盘互相补充,才更接近真实效果。
本专题到这里完成了从职责划分、写入、更新、检索到选型与评测的闭环。下一步实践可以从新增一个失败用例开始:写出预期,再修改实现,最后报告证据。返回专题导读可下载源码并按阅读顺序复现整个案例。