Mnemosyne OS

给 AI Agent 加记忆:从零到可用的完整路径

如果你的 Agent 每次对话都要用户重复交代背景,问题不在提示词,在于它没有记忆层。本文给出四种接入形态的完整代码、作用域怎么划、检索参数怎么调,以及上线后会遇到的那几个坑。所有代码在 mnemosyne-os 8.1.0.post1 上实测可运行。

先看结论:如果你只要「十分钟内跑起来」,跳到第一节装完直接复制第二段代码即可。如果你在选技术方案,直接看第四节(四种接入形态对比)。如果你已经接了但效果不好,看第七节(故障排查表)。

一、安装:零依赖意味着什么

pip install mnemosyne-os

实测(8.1.0.post1):核心包的 install_requires 是空列表。安装过程不拉取任何第三方包,不需要编译器,不需要 C 扩展,不需要预先装数据库。可选扩展才需要额外包:

扩展装什么什么时候需要
numpynumpy向量打分想从纯 Python 换成矩阵运算时(性能优化,非正确性依赖)
transformerstransformers想用 HuggingFace 系列嵌入模型时
tiktokentiktoken想用 OpenAI 的 tokenizer 精确计数时

这个特性在真实环境里很实用:气隙网络、受限容器、没有编译工具链的 Windows 机器、CI 里不想为装一个记忆库拉 2 GB 依赖——这些场景下「零依赖」不是营销词,是能不能装上的区别。

二、第一条记忆:写入与召回

from mnemosyne import Memory
from mnemosyne.api.config import MemoryConfig
import tempfile

# 指定记忆库位置(生产环境用固定路径)
cfg = MemoryConfig(brain_dir=tempfile.mkdtemp(), namespace="default")
m = Memory(cfg)

# 写入:一句话,指定作用域
r = m.add("Alice 偏好深色模式,用 vim 键位,时区 Asia/Shanghai",
          user_id="alice")
print(r)
# {"results": [{"id": "00443a84648d1344", "memory": "Alice 偏好深色模式…",
#               "event": "ADD", "categories": ["miscellaneous"],
#               "confidence": 0.85}]}

# 召回
hits = m.search("alice 喜欢什么主题", filters={"user_id": "alice"}, top_k=5)
for h in hits["results"]:
    print(h["score"], h["memory"])

关键点一:add() 返回的是 results 数组,不是单个对象。因为一次写入可能被抽取成多条记忆——实测中,一段 2400 字符、含 5 句话的文本,被切成 5 条独立记忆,每句一条。这是刻意的设计:记忆的检索粒度是「一条事实」,不是「一篇文章」。

关键点二:search() 的过滤条件必须写在 filters={} 里,不能作为顶层参数传 user_id。旧写法会抛 [VALIDATION_007] 错误——这是 8.x 的显式破坏性变更,目的是强制作用域显式化。

关键点三:默认配置下不需要任何 API Key,也不下载模型。用的是内置 128 维确定性嵌入 + 规则抽取器。这意味着 add() 在没有配置 LLM 的情况下也能工作——不是降级,是设计如此。

三、作用域:四个维度怎么划

作用域决定「谁看到谁的记忆」,划错了要么串数据,要么查不到。

维度含义典型取值用错会怎样
user_id记忆属于谁用户 ID、邮箱哈希不传 → 所有人共享一个库,A 能看到 B 的偏好
agent_id哪个智能体写的support-bot、coder多 Agent 协作时无法区分来源,排查困难
run_id哪一次任务/会话任务 UUID无法按任务回溯,也无法清理单次任务的中间产物
app_id哪个应用web、cli、mobile多端共用时统计混淆

Mnemosyne OS 的隔离是物理隔离:每个作用域对应一个独立的 SQLite 文件,而不是同一张表加过滤条件。区别在失败模式——逻辑隔离一旦某处漏写 WHERE,就是跨用户泄露;物理隔离下,查错库需要显式传错参数,且由文件系统兜底。

实践建议:user_id 必传。agent_id 在多 Agent 场景必传。run_id 用于短期任务记忆,配合 TTL 自动过期。app_id 按需。

四、四种接入形态,怎么选

形态适合场景需要写代码吗跨语言
Python 库自己的 Python 服务、脚本、Notebook要否
CLI运维排查、批量导入、shell 脚本不用是(任何 shell)
REST API非 Python 技术栈、微服务要(HTTP 调用)是
MCPClaude Desktop / Cursor 等已支持 MCP 的客户端不用是

形态 A:Python 库

见第二节。适合把记忆能力嵌进自己的进程,零网络开销。

形态 B:命令行

# 初始化记忆库
mnemosyne --dir ./mem init

# 写入与召回
mnemosyne --dir ./mem retain --content "Apple Inc. was founded in 1976"
mnemosyne --dir ./mem recall "Apple" --k 5

# 完整性校验(检测篡改并定位到具体记录)
mnemosyne --dir ./mem verify-integrity --json

# 图谱遍历
mnemosyne --dir ./mem graph-query "Steve Jobs" --depth 2 --json

形态 C:REST API

# 写入
curl -X POST https://ai-memory.net/v1/memories   -H "Authorization: Bearer mn-your-key"   -H "Content-Type: application/json"   -d '{"content":"Alice 偏好深色模式","user_id":"alice"}'

# 召回
curl "https://ai-memory.net/v1/recall?query=alice&top_k=5"   -H "Authorization: Bearer mn-your-key"

自托管时把域名换成自己的地址;使用托管版则在 /auth/keys 领取密钥。API 有 v1 / v2 / v3 三个版本,v1 覆盖面最广。

形态 D:MCP(推荐给已支持 MCP 的客户端)

# stdio 方式(本地进程)
export MNEMOSYNE_MCP_TOKEN="your-secret-token"
python -m mnemosyne.webui.mcp_server --brain-dir ./mem --namespace default

在 Claude Desktop 的配置文件里加:

{
  "mcpServers": {
    "mnemosyne": {
      "command": "python",
      "args": ["-m", "mnemosyne.webui.mcp_server",
               "--brain-dir", "./mem", "--namespace", "default"],
      "env": { "MNEMOSYNE_MCP_TOKEN": "your-secret-token" }
    }
  }
}

MCP 服务器暴露 52 个工具(41 个原生 + 11 个兼容 Mem0 命名的别名)。如果你已经在用 Mem0 的 MCP 工具,把客户端指向 Mnemosyne 即可,工具名一致,提示词不用改。

五、检索参数:三个真正需要调的

参数默认调它的时机
top_k20注入上下文时通常 3–5 足够。太大等于用噪声淹没模型
threshold0.1相关性下限。调高 = 宁缺毋滥;调低 = 宁多勿漏
rerankfalse候选多且要求高精度时开,代价是多一次模型调用

关于 threshold 的实话:很多实现「永远填满 top_k」,导致最高分恒为 1.0,分数失去绝对意义。校准过的下限能让「0.85」真的意味着「比较相关」,而不是「这批里最相关的那个」。代价是有时候返回空——而返回空是特性,不是 bug:与其把不相关的内容塞进上下文污染推理,不如什么都不给。

六、把记忆接进对话循环

记忆不是「存了就会用」。要让它生效,需要在对话循环里加两个钩子:

def chat(user_input, user_id):
    # 钩子 1:召回——在构造提示词之前
    hits = m.search(user_input, filters={"user_id": user_id}, top_k=5)
    context = "\n".join(h["memory"] for h in hits["results"])

    prompt = (
        "你是一个有记忆的助手。\n"
        "已知关于用户的信息:\n" + context + "\n\n"
        "用户说:" + user_input
    )

    reply = llm(prompt)

    # 钩子 2:写入——在回复之后(异步,别阻塞用户)
    m.add(f"用户说:{user_input}\n助手答:{reply}", user_id=user_id)
    return reply

两个容易做错的地方:

七、故障排查表

现象原因修法
[VALIDATION_007] get_all() no longer accepts user_id as top-level8.x 破坏性变更:作用域必须放进 filters改成 get_all(filters={"user_id": "x"})
召回总是返回空作用域不匹配(写入用 A,查询用 B),或 threshold 过高先 get_all(filters=...) 确认数据在,再放宽 threshold
召回结果和查询无关默认用内置 128 维嵌入,语义区分度有限配置真实嵌入模型(如 BGE-M3),或加关键词检索兜底
写入返回多条结果文本被按句切分,这是设计属正常。需要整体时用 get_all 按批次取
数据在重启后消失brain_dir 指向了临时目录改成固定路径,并确认进程对该目录有写权限
容器里写入很慢brain_dir 落在网络卷或 overlay 层挂载本地卷;SQLite 对网络文件系统不友好

八、上线前检查清单

  1. 作用域必传:user_id 不允许为空。空作用域 = 所有人共享一个库。
  2. 记忆库在持久卷上:不在容器根文件系统,否则每次部署都丢。
  3. 定期跑完整性校验:verify_integrity() 实测 4.5 ms(120 条),可以放进每日任务。
  4. 日志不记记忆正文:记忆内容属用户隐私,日志只记 method / 工具名 / 作用域。
  5. 准备删除路径:用户要求删除时,要能真的删干净。delete_all + 级联删除派生结论。
  6. 备份:整个记忆库就是一个目录,直接打包即可,不需要数据库导出工具。

九、提示词怎么写才让记忆生效

接了记忆但效果不好,十次有八次是提示词的问题,不是检索的问题。三个常见错误:

错误一:把记忆当文档塞进提示词

# 差:一大段无结构文本
已知信息:Alice 喜欢深色模式。Alice 用 vim。Alice 在上海。Bob 是 PM…

# 好:分组 + 标注来源时间
【用户画像】
- 偏好:深色模式、vim 键位
- 时区:Asia/Shanghai
- 最近更新:2026-10-27

【相关历史】
- [2026-10-20] 提到正在迁移到上海

差别在于模型能否区分「稳定偏好」和「临时状态」。前者可以长期依赖,后者需要确认是否仍然成立。

错误二:不说「可以不知道」

如果提示词只给记忆内容,模型会倾向于把记忆当作完备信息,对没记住的事也硬答。加一句明确指令:

以下信息可能不完整。若用户问到的内容不在其中,
直接说「我这边没有相关记录」,不要推测。

错误三:把召回结果全部塞进去

top_k=20 意味着 20 条记忆进上下文。其中大部分与当前问题无关,反而稀释了真正相关的那几条。实践值:对话场景 top_k=3~5,加 threshold 下限。

一个反直觉的结论:召回返回空,比返回一堆弱相关内容更好。弱相关内容会诱导模型编造关联,而空结果会让模型老实说「不知道」。这也是为什么相关性下限(threshold)值得调。

十、多 Agent 场景:怎么划分记忆

一个系统里有多个 Agent 时,记忆归属容易乱。三种常见模式:

模式作用域写法适合风险
共享用户记忆只传 user_id客服 + 销售协作服务同一用户Agent 之间互相污染(一个的临时判断被另一个当事实)
按 Agent 隔离user_id + agent_id各 Agent 职责差异大用户要在多个 Agent 间重复交代背景
分层:用户层 + Agent 层稳定事实写用户层,工作记忆写 Agent 层推荐需要判断「什么算稳定事实」

分层模式的具体做法:

# 用户层:跨 Agent 共享的稳定事实
m.add("用户是左撇子,偏好简洁回答", user_id="alice")

# Agent 层:某个 Agent 的工作记忆
m.add("本次排查已排除 DNS 问题", user_id="alice", agent_id="ops-bot",
      run_id="task-2026-10-27-001")

好处是:ops-bot 的排查过程不会污染其他 Agent 对用户的认知,但它仍能读到用户的稳定偏好。

十一、成本与性能:什么时候该优化

症状先查什么优化手段
回复变慢写入是否同步阻塞改 async_mode=True,或后台队列
召回变慢记忆总量到多少了万级以上考虑装 numpy;十万级考虑外接向量库
召回不准作用域是否混了、threshold 是否过高先 get_all 确认数据在,再调参
上下文太长top_k 设了多少降到 3–5,或改用目录式召回
存储增长过快是否存了太多闲聊写入前过滤;定期 dedup + consolidate

实测基线(200 条记忆规模):写入中位 11.91 ms、召回中位 3.924 ms。低于这个数量级说明还有优化空间;远高于它,通常是作用域或存储位置的问题,不是引擎本身慢。

十二、实测性能参考

本机实测(Windows / Python 3.13.12 / 8.1.0.post1 / 默认零配置):

操作耗时
引擎初始化0.24 ms
写入一条(中位)11.91 ms
写入一条(P95)21.11 ms
召回一次(中位)3.924 ms
召回一次(P95)5.955 ms

配置真实嵌入模型后延迟会上升,但语义召回质量明显提升。这是可调的取舍,不是固定值。

十三、下一步

看 MCP 的 52 个工具 →在 GitHub 上看源码