本文基于 paicli 项目的实践,记录对 Memory 系统的深水区思考。不是答案清单,是问题清单。这是 Memory 系列的第三篇。
设计做完了,但我把自己问倒了
前两篇讲了 Memory 为什么存在、怎么设计。代码写完了,测试跑了 96 个用例全绿。我以为自己懂了。
直到我盯着这个流程图发呆:
用户说话 → 存入 → 存储(短期/长期)→ 压缩(超预算时)→ 检索(需要回忆时)→ 注入到 LLM prompt ↑ 预算管理(全程监控)每多看一秒就多冒出一个问题。而且这个流程看着像个链条,但感觉记忆应该是个循环吧?那是不是缺了什么?想着想着问题就涌出来了:
- 记忆怎么更新?
- 记忆如果是错的怎么办?旧的记忆和新的记忆冲突了怎么办?
- 存入的时候,什么该存什么不该存?
- 压缩的时候怎么保证压缩质量?再多问一句,压缩质量怎么量化?
- 超预算时,这个预算到底指什么?
- 检索除了关键词匹配和向量语义检索之外,还有啥方法?检索质量怎么量化?
- 注入到 LLM 就是直接拼接吗?有没有更深度的考量?
都不用等面试官来问,我自己就能把自己问倒。我能提出问题,但试图去解决 — 真的是头都大了。
淘汰策略:FIFO 够用吗?
我们的短期记忆用 FIFO 淘汰。对话天然是时间有序的,大多数情况下够用。
但我马上想到一个场景:用户在第 1 轮问了「项目配置在哪」,Agent 回答了。然后聊了 48 轮其他话题。第 49 轮用户问「你刚才说的配置路径是什么?」— 第 1 轮的消息早就被 FIFO 淘汰了。用 LRU 的话就不会有这个问题,但 LRU 代码更复杂,对话场景下收益有限。那到底该怎么选?
更麻烦的是,消息之间是有因果关系的。一个 tool_call 和对应的 tool_result 是一体的,如果 FIFO 逐条淘汰,把 tool_result 淘汰了但保留了 tool_call,LLM 会完全困惑。消息组的概念怎么设计?按什么规则分组?分组之后淘汰逻辑是不是要重写?
还有大象消息的问题 — 工具返回的结果可能几千字,一条消息就把预算吃光了,几十条正常消息被连带淘汰。截断到 500 字符是目前的处理,但截断可能丢掉关键信息。那到底是该截断、该压缩、还是该把大消息单独处理?而且单纯的截断消息之后,这样保存的记忆还能用吗?每种方案都引入新的复杂度。
事实提取:怎么判断什么值得记住?
我们用 LLM 提取 + 规则二次过滤。但 LLM 会犯错。
它会把临时任务当成事实 — 用户说「帮我创建 test.py」,LLM 提取出「用户创建了 test.py」。这只是个临时任务,不是稳定事实。过滤规则能拦截「创建」这个前缀,但 LLM 换一种表述呢?规则永远跟不上 LLM 的花样。
它会把推测当成事实 — 对话里说「可能有缓冲区溢出的风险」,LLM 把「可能」丢了,提取出「存在缓冲区溢出漏洞」。推测变成了确定事实,存进长期记忆。更可怕的是你存了一条错误的事实,后续每次检索都会命中它。
它会遗漏重要事实 — 对话里提到「团队约定用 TypeScript 写前端」,LLM 压根没提取。这种错误完全无法检测,你不提取的事实,你根本不知道它存在过。
更头疼的是过时的事实 — 用户之前说用 Python 3.10,现在换成了 3.12。新的「Python 版本:3.12」被提取了,但旧的「Python 版本:3.10」还在长期记忆里。两条矛盾的事实同时存在,Agent 不知道该信哪条。我们只有「存」和「删」,没有「更新」。那要不要做事实更新?怎么做语义级别的冲突检测?一旦做了,复杂度又上来了。
压缩质量:怎么衡量?
压缩一定会丢信息。那丢多少是可接受的?
我试着想了几个衡量维度,但每个都有问题:
信息保留率?压缩前 10 个关键事实,压缩后保留 7 个。听起来直观,但怎么定义「关键事实」?谁来标注?
任务影响度?压缩前后让 Agent 执行同样的任务,看成功率是否下降。但 Agent 的输出本身就不确定 — 同一个 prompt 每次回复都可能不同,怎么定义「成功」?
Token 节省率?压缩前 5000 tokens 压缩后 500。最容易量化,但只衡量了「省了多少空间」,不衡量「丢了多少信息」。省了 90% 的空间但丢了 50% 的事实,算好的压缩吗?
而且还有一个风险 — 摘要本身可能是错的。LLM 摘要时可能张冠李戴、混淆因果。错误的摘要比没有摘要更可怕,因为 Agent 会基于错误的历史信息做决策。
越想越觉得,没有一个维度能单独说明问题,但把它们组合起来又不知道怎么综合评分。
检索:关键词匹配的天花板
我们的检索用的是关键词匹配(n-gram 分词 + 子串匹配)。零依赖,够用。
但有一个硬伤:用户说「数据库」,记忆里存的是「DB」。用户说「优化性能」,记忆里存的是「提速方案」。关键词匹配解决不了「用不同的词表达同一个意思」。
要解决就得引入向量检索 — embedding 模型 + 向量数据库,有依赖、有成本。工业界用混合检索(关键词 + 向量),但权重怎么设?什么场景关键词优先、什么场景向量优先?这些都需要实验调优,我又没有现成的方法论。
而且检索还有个成本问题。用户每次说话都要检索一次,记忆库大了之后响应速度会变慢。用户问「今天星期几」这种简单问题,根本不需要去记忆系统里搜,但当前系统对每个问题都一视同仁地检索。要不要做个判断,简单问题跳过检索?怎么判断问题简不简单?这又是一个新问题。
注入:不只是拼接吧?
记忆检索出来后怎么给 LLM 用?当前实现是直接拼接到 system prompt。但注入什么、注入多少、注入的顺序有没有影响?
注入太多占上下文空间,注入太少可能遗漏关键信息。我们限制了 1000 tokens 的注入预算,但这个值是怎么定的?我不知道。
还有,什么时候注入?当前是新会话开始时一次性注入。但如果用户在对话中提到了新话题,是否需要动态检索注入?这又涉及到延迟问题。
关于注入我能想到的问题很多,但怎么做更好,我确实没什么思路。
Agent 评测:这才是最大的问题
上面的所有问题归根到底指向一个更根本的问题:你怎么验证 Memory 系统是有效的?
我让AI写了 96 个单元测试,全绿。但那验证的是「机制正确」— 存了能取出来、超预算会淘汰、规则能过滤。Memory 的真正价值在于「对 Agent 效果的改善」— 有了 Memory 的 Agent 是否比没有的表现更好?
这个问题我想了很久,越想越觉得难:
没有标准答案。摘要质量好不好?事实提取是否完整?这些都没有确定的标准答案。传统测试是「输入 → 断言输出」,但 Agent 的输出每次都可能不同。
效果是间接的。Memory 不直接产出结果。要评测 Memory,必须评测 Agent 的端到端表现,然后归因到 Memory。但 Agent 的表现受太多因素影响,怎么隔离出 Memory 的影响?
需要大量数据。单个测试用例不够。需要模拟上百轮真实对话才能暴露边界问题。这些数据从哪来?
我问了AI工业界的做法 — 基准测试、LLM-as-Judge、A/B 测试 — 但没有一个能完美解决上面的问题。Agent 评测之所以比普通的前后端测试难,根本原因是 LLM 输出的不确定性。同一个 prompt 每次回复可能不同,你没法精确断言。
问题比答案更有价值
回过头看,这些问题不是孤立的:
淘汰策略 → 压缩质量 → 事实提取准确性 → 长期记忆质量 → 检索精准度 → 注入效果 → Agent 表现 → 评测链条上任何一个环节出问题,最终都会体现在 Agent 的表现上。而 Agent 表现不好,你又很难定位是哪个环节的锅。
我能提的出问题,但试图去解决这些 — 真的是头都大了。不过能提出这些问题本身就很重要的。面试官不会期望你解决所有这些问题,他想看的是你对问题的感知力。知道哪里有坑,比掉进去再爬出来更重要。
这些问题大部分没有标准答案,需要根据具体场景权衡取舍。Memory 系统的设计永远在「简单 vs 精确」「成本 vs 效果」「自动化 vs 可控性」之间找平衡。