Skip to content

第 21 章 选型决策与迁移路径 ​

2026 年 6 月,一家做医疗器械售后的团队选定了某托管记忆平台,理由充分:上线快、免运维、按量付费。十个月后,他们接到客户的合规审计要求——所有涉及患者设备使用记录的派生结论必须能证明已彻底删除,且要给出删除时刻的证据。平台提供操作日志,提供不了完整性证明;把日志导出给审计方之后,对方问了一句「你怎么证明日志本身没被改过」,会议室安静了半分钟。

这不是选错了产品,而是选型时没把一个问题提前问出来。那家平台在其定位上做得很好,它只是不承诺「可验证的删除证据」这件事。团队在选型阶段比较的是延迟、价格与召回效果,没有比较「三年后会不会被要求举证」。选型的困难不在信息,而在顺序:先比能力再问约束,几乎必然返工;先问约束再比能力,能砍掉一大半候选。本章按这个顺序展开——21.1 是五个必须先答的问题,21.2 把它们压成一棵决策树,21.3 说明为什么某些选择比其他选择更难撤回,21.4 到 21.6 是三条迁移路径(按改动幅度从小到大排列),21.7 给一份验收清单:迁移是否成功,不由「能跑起来」判定。

术语约定:本节所说的「迁移」指把记忆层从 A 换到 B,且保留既有数据与调用方代码;不包含模型、向量库或编排框架的替换。

21.1 选型前必须回答的五个问题 ​

这五个问题的共同点是:答错的代价不在上线时显现,而在两年后的一次审计、一次扩容或一次团队交接时显现。

问题若答「是」若答「否」
数据主权是硬约束吗只剩自托管路线托管方案可进入候选
团队有运维能力吗自托管可行托管或零依赖方案
记忆需跨 Agent 共享吗需上层权限模型物理隔离即可满足
需要可验证的删除证据吗必须具备哈希链操作日志够用
中文占比高吗必须实测分词策略默认分词通常够用

表 21-1 五个前置问题如何收窄候选集

第一问:数据主权是不是硬约束。 判断标准不是「我们重视数据安全」,而是「数据能否离开我方控制的网络」。医疗、金融、政务与部分制造业客户会把这条写进合同,一旦写进去,托管方案整条路线出局,比较维度随之从「功能多少」变成「自托管能做多全」。这一问答错的代价最高,因为它不可协商。

第二问:团队有没有运维能力。 自托管的真实成本不是装一个服务,而是长期维护图数据库、备份、升级与故障恢复。有 DBA 与 SRE 的团队可以承担;三个人的产品团队承担不了。这里要抵抗一种常见的乐观:把 PoC 阶段「跑起来了」当成运维能力证明。

第三问:记忆是否需要跨 Agent 共享。 物理隔离的实现方式(每个作用域组合一个独立 SQLite 文件)天然支持「不共享」,但一旦要求两个 Agent 读同一份记忆,就需要在上层自建权限模型。这个问题的答案会决定第 23 章那个未解问题是否会砸到自己头上。

第四问:是否需要可验证的删除证据。 区分两种需求:「删除生效」与「能证明删除发生过且不可否认」。前者操作日志够用,后者需要哈希链审计账本。多数团队以为自己只需要前者,直到第一次被审计问到。

第五问:中文占比高吗。 如果记忆内容以中文为主,且查询里含型号、人名、专有名词,纯嵌入召回会失准,必须实测分词与关键词通道的效果。这一问的验证成本很低,跳过它没有理由。

21.2 决策树 ​

把五问压成树,可以在半小时内把候选从六个砍到两个。这棵树不追求完备,它追求的是「先排除明显不合适的」。

text
Q0 数据主权是硬约束?
├─ 是 → 只能自托管
│   ├─ 有图数据库运维能力?
│   │   ├─ 是 → Zep 路线(Graphiti 自建)或 Mnemosyne 自托管
│   │   └─ 否 → Mnemosyne 自托管(单文件 SQLite,零依赖启动)
│   ├─ 需要零依赖快速验证(离线 / 无 key / 无模型)?
│   │   └─ 是 → Mnemosyne(内置 rules 提取器与 128 维嵌入器)
│   └─ 已在 LangGraph 生态且只需 SDK?
│       └─ 是 → LangMem + 自备持久化 store
├─ 否 → 可托管,继续问
│   ├─ 目标是对话个性化快速上线?
│   │   └─ 是 → Mem0(Free 起步,图记忆需 Custom)
│   ├─ 需要 Agent 自主管理记忆与运行时?
│   │   └─ 是 → Letta(npm 安装 + Letta Cloud)
│   └─ 已在 Workers 生态且状态贴着执行环境?
│       └─ 是 → Cloudflare Agents(Durable Objects)
└─ 混合 → 托管做前台个性化,自托管做合规存档
    └─ 注意:两套记忆之间的同步与一致性由你负责

这段决策树的用法是:从 Q0 开始一路走到底,得到的是一个起点而不是终点。走到「混合」分支时要格外小心,因为混合形态把一致性问题的所有权从供应商转移给了自己。

21.3 决策的不可逆性 ​

选型讨论常把六个方案摆成一张平表,暗示它们可以随时互换。实际不是:不同方向的迁移成本差异极大,而这个差异有结构。

从自托管切到托管,成本主要在数据搬迁与接口适配。 数据在你手里,导出是你能单方面决定的事;接口差异有明确的映射表可查。这类迁移通常以周计。从托管切回自托管,成本还包括一段时间的双写与对账。 你不能停掉线上服务去搬数据,只能双写、比对、切换、观察。更麻烦的是那些「平台替你做了但你没意识到」的部分:巩固、去重、衰减排序。切走之后这些工序会突然消失,而它们消失的症状是召回质量缓慢下降,不是报错。

因此有一条默认规则:存在任何数据主权不确定性时,起点选自托管是更安全的默认。 代价是前期投入更高、上线更慢;收益是保留了向任一方向调整的自由。这条规则的失效条件是「上线窗口只有两周」——那时先上线再迁移是唯一可行路径,但必须在架构上预留导出与双写的能力,否则第二次迁移会被数据卡住。

21.4 迁移路径一:从 Mem0 到 Mnemosyne ​

这是三条路径里改动最小的一条,原因是工具名与调用形状都对齐了。

工具面。 Mnemosyne 实现了 11 个常规 agent-memory 工具名:add_memory、search_memories、get_memories、get_memory、update_memory、delete_memory、delete_all_memories、delete_entities、list_entities、list_events、get_event_status。把现有 MCP 客户端指向新端点,提示词不必改。

代码面。 Memory、AsyncMemory 与 MemoryClient 接受常规的 agent-memory 调用形状,因此已经按那个形状写好的代码,改 import 即可:

python
from mnemosyne import Memory          # 原来是从另一处 importm = Memory()m.add([{"role": "user", "content": "我搬到了柏林"}], user_id="alice")hits = m.search("用户住在哪", filters={"user_id": "alice"})

这段代码验证的是调用形状的兼容性:add 接受消息列表加 user_id,search 接受过滤条件。注意返回值结构仍需核对——形状兼容不等于字段完全一致。

一个已经失效的迁移理由。 早期文档常说「Mem0 的两阶段 ADD/UPDATE/DELETE 流水线导致迁移困难」。这条理由在 2026 年已不成立:Mem0 现在也是单遍 ADD-only 提取,一次 LLM 调用,只增不覆盖不删除。既然两边范式相同,迁移的技术障碍就不在写入侧。

真正的差异有三处。 其一,update_memory 的语义:Mnemosyne 的 update_memory 是显式覆盖正文与元数据,而 ADD-only 系统表达「现在是什么」靠检索时的排序而非覆盖;迁移时要检查调用方是否隐含依赖「显式覆盖即当前状态」这个假设,如果有,那个假设在两边都不可靠。其二,图记忆的归属:Mem0 的图记忆(实体链接)是 Platform 能力,开源版官方标注该能力已移除,开源侧不存在可查询的图;Mnemosyne 的图记忆内建在同一个 SQLite 文件里,不需要外部图数据库,若原方案依赖 Platform 的图能力,迁移到自托管后这部分要重新设计。其三,审计能力:Mem0 的审计日志属 Custom 层,是操作日志;Mnemosyne 的哈希链账本能指出具体哪一条被改——这是能力增强,代价是写入路径串行化。

21.5 迁移路径二:从 Zep 到 Mnemosyne ​

改动中等,难点不在工具名,而在返回值的形状。

工具面。 Zep 的 Context MCP Server 暴露 12 个工具,分两组。用户自有图一组 6 个:search_graph、get_user_summary、get_subgraph、get_node_neighbors、list_episodes、add_memory。共享 Context Graph 一组 6 个:list_graphs、search_graph_in、get_subgraph_in、get_node_neighbors_in、list_episodes_in、add_memory_to_graph,另提供 zep://graphs/directory 资源。Mnemosyne 侧没有同名的图遍历工具,对应能力是 graph_query、temporal_query 与 expand_memory 返回的图邻居。

形状差异是关键。 search_graph 在 scope=auto 下返回的是组装好的上下文块,可以直接拼进提示词;Mnemosyne 的 recall 与 expand_memory 返回结构化记忆条目,带分数、信封、证据链,需要调用方自己组装。

text
Zep:       search_graph(scope=auto) → 可直接注入的文本块Mnemosyne: recall(...) → 条目列表 → 自行拼接 → 注入文本

这层组装不是纯粹的成本,它同时给了你控制权:可以按分数阈值过滤、按时间排序、按层分配预算。但如果下游代码里到处是「拿到字符串就拼」的写法,这层组装会散落在十几处,迁移工期主要消耗在这里。

语义粒度的替代方案。 Zep 用 scope 让调用方选粒度(auto / observations / thread_summaries / episodes / nodes / edges)。Mnemosyne 没有同名参数,对应的做法是 recall_catalog 先看目录、expand_memory 再展开 1–2 条。两者解决同一个问题,代价不同:前者要求调用方懂语义层级,后者多一次往返。迁移时要把原来一处调用里的粒度决策,拆成编排层的两次调用。

21.6 迁移路径三:从 Letta 到 Mnemosyne ​

改动最大的一条,因为两者的记忆在架构里的位置不同。位置差异。 Letta 的记忆是 Agent runtime 的一部分:概念面包含 MemFS(git 版本化的 Agent 记忆文件系统,默认没有向量索引)与共享记忆。Agent 通过编辑文件来改自己的记忆,记忆的读写是 Agent 行为的一部分。Mnemosyne 是外部服务,记忆通过工具调用读写,Agent 本身不持有记忆状态。

因此改动落在控制流上,不在接口上。 原来 Agent 的循环是「读记忆文件 → 思考 → 编辑记忆文件」;迁移后是「思考 → 调用 recall 或 recall_catalog → 拿到条目 → 继续」。这不是替换一个函数,而是改写 Agent 的行动空间:从文件编辑这种开放式动作,变成有限个工具调用。

代价有两面。 一面是能力收窄:文件编辑能表达任意结构,工具调用只能表达工具定义过的结构。另一面是可治理性提升:每次读写都留下事件记录,可审计、可回放、可限制。团队要判断自己更需要哪一面——需要 Agent 自由组织记忆结构的场景,这次迁移是降级;需要记忆行为可被审计与约束的场景,这次迁移是升级。一个容易低估的工程量:迁移历史记忆。 MemFS 里的记忆是文件,没有统一的 schema,转成结构化条目需要写解析与归类规则。这部分工作量通常超过接口改造本身,且无法自动化到底。

21.7 迁移的验收清单 ​

迁移是否成功,不由「服务起来了、工具能调」判定。下面五项按顺序执行,任一项不通过就不进入下一项。

项做法不通过的信号
工具名兼容用原提示词跑一遍回归模型反复调错工具名
召回质量 A/B同一批查询双跑,比对 top-5命中率下降且集中在专名
删除语义回归删一条后查派生结论是否失效派生摘要仍可召回
审计链可用性verify_integrity() 应返回 valid返回 first_broken_at
回滚预案保留旧库只读副本与切回开关只能靠重新导入

表 21-2 迁移验收五项与各自的失败信号

第一项最容易被跳过,因为「工具名一样」看起来是显然的。实际上模型对工具描述文本敏感,描述改写会改变调用习惯,必须用原提示词回归而不是新写测试。

第二项的关键是分层看结果,不只看总分。总分持平但专名类查询下降,说明关键词通道没接上;总分下降但时序类查询上升,说明时间信号更强了但语义通道弱了。只看总分会把这两种情况混成一个「差不多」。

第三项针对 ADD-only 的固有弱点:源记录删了,派生结论还在。memory_cascade_delete 的 dry_run=true 能先列出依赖项,验收时应该先跑 dry_run 看清单是否符合预期,再执行。第五项最容易被当成形式,回滚预案的可执行标准是:能在不重新导入的前提下切回旧库,并且旧库在切换期间保持只读但可查。

21.8 本章小结 ​

本章确立三条判断。

第一,选型的顺序比选型的方法更重要。 先答五个前置问题再比能力,能把候选从六个砍到两个;反过来先比能力,几乎必然在合规或运维环节返工。这套顺序成立的条件是五个问题都能被明确回答;它失效的条件是组织内部对「数据主权是否硬约束」本身没有共识——那时先解决共识,再谈选型。

第二,迁移成本的方向不对称,因此默认起点应当偏向可撤回的一侧。 从自托管切到托管以周计,反向以月计,且要处理平台替你做过而你没意识到的工序。这条判断的代价是前期投入更高;它的失效条件是上线窗口极短,那时必须在架构上预留导出与双写能力作为补偿。

第三,三条迁移路径的难度差异来自「记忆在架构里的位置」,不来自接口数量。 Mem0 到 Mnemosyne 只是改 import 与核对字段;Zep 到 Mnemosyne 要补一层组装;Letta 到 Mnemosyne 要改写 Agent 的行动空间。判断自己的迁移属于哪一类,看一个问题就够了:原系统里,记忆是 Agent 的行为对象,还是 Agent 调用的服务?

还有一个问题留在这里:三条路径的验收清单都假设「你知道迁移前的召回质量是多少」。如果迁移前没有基线,A/B 就无从做起。这个空缺会在第 22 章以验收方法的形式再次出现,并在第 23 章成为「基准与真实使用之间的鸿沟」的一部分。


Copyright © 2026 胡景堃(Hu Jingkun). 采用 CC BY-NC-ND 4.0 许可。未经授权禁止传播、复制或用于商业化。