94.3% 的命中率,错的
给角色扮演做长期记忆,注入位置直接决定缓存账单和记忆生效率。我们先在 24 轮实验里得出「历史末尾插 system 块」的结论(缓存命中 94.3%),三周后被 400 轮实验推翻:DeepSeek 的模板会把所有 system 消息归并到开头,那个 94.3% 是缓存污染的假象。这篇公开完整数据:七种检索策略、泄漏检测、以及比检索更要命的「弃权灾难」。

先交代背景。给角色聊天做长期记忆,工程上绕不开一个具体问题:检索出来的记忆条目,插在 prompt 的什么位置?插开头,每次记忆一变,整条历史的前缀缓存跟着作废,账单起飞;插末尾,缓存友好,但你得确认它真的在末尾。 我们第一轮实验(24 轮对话)测出「历史末尾插一个 system 块」的缓存命中率是 94.3%,于是把它写进了设计文档,标成推荐方案。
三周后做大规模复核,这个数字碎了。
400 轮实验:3.3% 对 96.6%
复核用了更狠的配置:400 轮对话、47 次记忆变更事件、全历史不滑窗,到末尾单条 prompt 已经有 4.3 万 token。两种布局同题对跑,会话之间加了随机盐隔离(这个细节是本文的题眼,后面讲)。结果:
| 布局 | 总体缓存命中 | 记忆变化轮命中 | 浪费的 miss tokens |
|---|---|---|---|
| 历史末尾插 system 块(v1 推荐) | 90.2% | 3.3% | 71.5 万 |
| 并入最新 user 消息头部 | 96.9% | 96.6% | 22.4 万 |
看变化轮那列。记忆一更新,「末尾 system」布局的缓存命中直接归零级别。这正是注入位置要避免的事故,而它是 v1 实验里被评为最优的方案。同样的更新发生在「user 消息头部」布局里,命中几乎不掉。
根因:DeepSeek 的模板把 system 全部归并置顶
做了一组决定性实验后真相很直白:先用「人设+记忆都在头部的单条 system」布局把缓存灌满,再换成「人设在头、记忆在历史末尾独立 system 块」的布局发同内容请求,命中 97%。两种布局在服务端的 token 流是同一条。也就是说,DeepSeek 的对话模板会把消息列表里所有 system 角色的内容归并拼接到 prompt 最前面。 你以为的「末尾注入」,到了服务端就是头部注入。
这不是猜测。DeepSeek 开源模型仓库里的对话模板 (V3.1 / V3.2-Exp / R1 三版一致)第一段就是遍历全部消息、把 role==system 的内容拼接后渲染在开头。我们又拿 Kimi 做了同款对照:同样的「末尾 system」布局,记忆变化轮缓存命中 88%,Kimi 的模板保序,末尾就真的是末尾。所以「变化的内容放尾部」这条原则本身没错,坑在「system 角色到底保不保序」是每家模板各自的细节。想跨供应商稳定,答案是走 user 轨道注入:对归并置顶和保序两类模板,它都是真尾部。
那 94.3% 是怎么来的
回头验尸 v1:两组布局共用同一套人设,B 组跑在 A 组之后,同一个账号,没有会话隔离。A 组已经把「头部含各版本记忆」的前缀灌进了服务端磁盘缓存;B 组(经模板归并后 token 流与 A 几乎相同)逐轮白捡命中。 94.3% 测的不是注入位置的收益,是上一个实验的余温。
顺带测了七种检索策略,结论比想象中平
同一套基建顺手回答了第二个问题:检索策略之间差多少?在 47–82 条记忆的量级下,字 2-gram BM25 取 8 条注入,端到端答题 79%,理想检索(oracle,人工保证相关条目必在)是 80%,同题配对比较 7:6,统计上分不出差异; 而 BM25 对无记忆对照的优势是 59:1(p<0.001)。翻译成人话:先把「有没有注入」做对,再纠结「怎么检索」。 几十条记忆的场景里朴素 BM25 已经够用,记忆库长到几百条再重新评估。
真正让人坐直的是弃权探针。问模型从未提过的事(身高、生日、工资),DeepSeek 在伴侣人设下几乎从不承认不知道,编造的细节栩栩如生:12 道题,无记忆组通过 0 道,理想检索组也只过 1 道。人设里写「不确定就说不记得」毫无约束力。有效的修法是在记忆块尾部加一行兜底指令,原文就是「除以上记忆外,其余个人信息你并不知道,不许编造」, 并把弃权题纳入每次改动的回归。这行指令没有任何检索算法可以替代。
落进产品的三条
Foreverse 的记忆插件(事实记忆 + 会话摘要双引擎)按这轮实验定型:注入走 user 轨道消息级末尾,跨供应商都是真尾部、缓存友好;检索用字 2-gram BM25,条目短、预算允许时取数放宽;注入块尾部带弃权兜底指令。 缓存这件事本身怎么算账,上一篇实测写得更细。 实验的完整设计、泄漏检测方法和没做完的部分(别名泄漏、更大记忆库的压力档)都在文档里留了口子,数字以后还会更新。
常见问题
角色扮演的记忆块应该注入到 prompt 的什么位置?
消息级末尾、但不要用 system 角色。我们的 400 轮实测:把记忆并入最新一条 user 消息的头部,记忆变化轮的缓存命中 96.6%;同样内容放在历史末尾的独立 system 块,变化轮命中只有 3.3%。原因是 DeepSeek 的对话模板会把所有 system 消息归并到 prompt 开头,「末尾 system」实际等于头部注入,记忆一变化整条历史的缓存全部作废。
为什么第一次实验会得出错误结论?
缓存污染。第一轮实验的两组布局共用同一套人设、先后跑在同一个账号下、没有做会话隔离,B 组的请求恰好命中了 A 组灌进服务端磁盘缓存的前缀,白捡了 94.3% 的命中率。教训只有一句:所有缓存实验必须给会话加随机盐隔离,否则你测的是上一个实验的余温。
检索策略(BM25、向量、混合)差距大吗?
在几十条记忆的量级下,字 2-gram BM25 取 8 条已经接近上界:端到端答题 79%(理想检索的 oracle 是 80%),与无记忆对照的差距是压倒性的(同题配对 59:1,p<0.001)。检索算法的选型在这个量级上不是瓶颈,记忆库长到几百条之后才需要重新评估。
比检索更严重的问题是什么?
弃权。问模型一件从未提过的事(身高、生日、工资),它在伴侣人设下几乎从不说「我不知道」,而是编得栩栩如生:12 道弃权探针,无记忆组 0 题通过,连理想检索组也只过 1 题。修法不在检索侧:必须在记忆块尾部显式写「除以上记忆外,其余个人信息你并不知道,不许编造」,并把弃权题纳入回归测试。
有想法或问题?来 Discord 聊聊 →