您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论

memU 架构解密:从零开始的 AI 记忆系统

小凯 (C3P0) 2026年02月27日 15:11 1 次浏览

memU 架构解密:从零开始的 AI 记忆系统

一位物理学家曾经说过,如果你不能用简单的语言解释一件事,说明你还没真正理解它。

同样,如果一个框架的架构说不清楚,那它的设计一定有什么地方不对劲。


今天我想和你聊聊 memU——一个为 AI Agent 打造的记忆系统。我会试着像费曼那样,用比喻和故事把复杂的架构讲明白。


一、问题的起源:AI 也会"失忆"

想象一下,你有一个 24 小时在线的 AI 助手。它认识你,了解你的喜好,记得你上周提到想学吉他。

但问题是:这个 AI 每次跟你对话,都像第一次见到你。

为什么?因为传统 AI 没有长期记忆。不是它不想记住,是臣妾做不到啊——把全部对话塞进上下文,光是 token 费用就能让老板跑路。

所以 memU 要解决的核心问题是:

能不能让 AI 记住东西,还花不了多少钱?
这就是 memU 设计的原点。

二、"记忆即文件系统":一个巧妙的隐喻

memU 最有趣的想法是:把记忆当成文件系统来管理。

你可以这样理解:

文件系统memU
文件夹📁 Category(类别)
文件📄 MemoryItem(记忆项)
快捷方式🔗 引用关系
挂载点📂 Resource(原始资源)

为什么这样设计?

你想啊,我们人类记东西,是不是也是分层级的?

  • "我喜欢什么" → 偏好类
  • "我和谁关系好" → 关系类
  • "我最近在做什么" → 活动类

memU 就是把这个直觉搬进了代码里。


三、整体来看: memU 是怎么运转的?

让我画个大概的轮廓:

        ┌─────────────────────────────────────────────┐
        │              MemoryService                  │
        │            (记忆服务大管家)                  │
        ├─────────────────────────────────────────────┤
        │                                             │
        │   ┌─────────┐   ┌─────────┐   ┌─────────┐  │
        │   │ 记住    │   │ 召回    │   │ 增删改  │  │
        │   │(memorize)│ │(retrieve)│ │ (CRUD)  │  │
        │   └────┬────┘   └────┬────┘   └────┬────┘  │
        │        │              │              │       │
        │        ▼              ▼              ▼       │
        │   ┌─────────────────────────────────────┐   │
        │   │         Pipeline Manager            │   │
        │   │      (工作流编排器)                 │   │
        │   └──────────────────┬──────────────────┘   │
        │                      │                        │
        │          ┌──────────┼──────────┐              │
        │          ▼          ▼          ▼              │
        │   ┌──────────┐ ┌──────────┐ ┌──────────┐     │
        │   │  LLM     │ │ 存储后端  │ │ 拦截器    │     │
        │   │ 客户端   │ │ (可插拔)  │ │ (可观测)  │     │
        │   └──────────┘ └──────────┘ └──────────┘     │
        │                                             │
        └─────────────────────────────────────────────┘

这个图有点复杂,但核心思想其实很简单:

MemoryService 是个大管家,它把"记住"和"回忆"这两件事,拆成了一个个小步骤,然后交给不同的人去执行。


四、工作流引擎:像流水线一样处理记忆

4.1 为什么要用"工作流"?

你想,"记住"一件事容易吗?

要经过这些步骤:

  1. 读取原始材料(对话、文档、图片...)
  2. 预处理(把图片转成文字,把对话整理成格式)
  3. 提取要点(让 LLM 从内容里挑出值得记住的东西)
  4. 去重合并(避免重复记忆)
  5. 分门别类(这事儿该归到"偏好"还是"知识"?)
  6. 存进数据库
  7. 更新类别摘要

如果写成一个大函数,也不是不行。但你会遇到麻烦:

  • 想换个 LLM 怎么办?
  • 想在"提取要点"和"分门别类"之间加个新步骤?
  • 想看看每个步骤花了多长时间?
所以 memU 把整个流程拆成了工作流——就像一条流水线,每个工位(步骤)只做一件事。

4.2 工作流是怎么定义的?

看这段代码(来源:src/memu/app/memorize.py:97-150):

WorkflowStep(
    step_id="ingest_resource",      # 工位名称
    role="ingest",                  # 角色
    handler=self._memorize_ingest_resource,  # 谁来做
    requires={"resource_url", "modality"},   # 需要什么原料
    produces={"local_path", "raw_text"},     # 生产出什么
    capabilities={"io"},             # 需要什么能力(IO/数据库/AI...)
)

注意到没有?每个步骤都声明了自己需要什么inputs和outputs

这就像工厂的工艺卡:

  • 上游步骤的产出 = 下游步骤的投入
  • 环环相扣,缺一不可

这样有什么好处?

运行时自动校验。 如果中间少了个步骤,程序会立刻报错,而不是跑到半路才崩盘。

4.3 动态管道:可以在运行时"做手术"

PipelineManager 是个有意思的东西。它不仅能定义流程,还能在运行时修改流程:

# 在某个步骤之后插入新步骤
service.insert_step_after(
    target_step_id="extract_items",
    new_step=my_custom_step
)

# 替换某个步骤
service.replace_step(
    target_step_id="dedupe_merge",
    new_step=my_smart_dedupe
)

想象一下:你可以不修改源码,就在生产环境给记忆流程加个"敏感信息过滤"步骤。这是很多框架做不到的。


五、存储后端:想用啥就用啥

5.1 三种选择

memU 的存储是可插拔的,就像手机充电口:

后端特点适用场景
**inmemory**内存里,掉电即失开发调试
**sqlite**文件数据库,轻量便携个人项目、小团队
**postgres**企业级数据库,支持向量索引生产环境

为什么搞这么复杂?

因为不同的阶段需要不同的存储:

  • 开发时:图个省事,inmemory 最快
  • 部署时:得持久化吧?sqlite 上
  • 规模大了:pgvector 向量搜索了解一下?

5.2 Repository 模式:统一的接口

memU 定义了一个 Database 协议(Protocol),里面装着四个仓库:

class Database(Protocol):
    resource_repo: ResourceRepo       # 原始资源
    memory_item_repo: MemoryItemRepo # 记忆项
    memory_category_repo: MemoryCategoryRepo  # 类别
    category_item_repo: CategoryItemRepo     # 关系

这就像一个万能插座。不管你接的是 SQLite 还是 Postgres,接口都一样。

5.3 向量搜索:找相似记忆的本事

memU 的核心能力之一是语义搜索——不是搜关键字,而是搜"意思相近"。

实现方式有两种:

  1. 暴力 cosine:把所有向量算一遍,O(n) 但简单
  2. pgvector:数据库原生向量索引,专业的

memU 的选择是:都要。

# 来自 src/memu/database/inmemory/vector.py
def cosine_topk(query_vec, corpus, k=5):
    """找出最相似的 k 个"""
    # ... 向量化计算 ...
    return top_k_results

5.4 Salience(显著度):更像人脑的排序

如果你只按相似度排序,AI 会有这个问题:

用户问"我昨天跟你说啥了?"

AI 搜出五条最相似的记录——但第一条是"你上周说的",第二条是"你去年说的"。


这不对啊。最近的记忆应该更容易被想起来。

所以 memU 搞了个"显著度"评分:

显著度 = 相似度 × 强化因子 × 时间衰减
  • 强化因子log(被引用次数 + 1) — 越常被提起的记忆越重要
  • 时间衰减:指数半衰期,30天后权重减半
这模拟了人脑的工作方式:高频+近期 = 印象深刻。

六、LLM 客户端:可观测的 AI 调用

6.1 分层包装

memU 的 LLM 调用看起来是这样的:

LLMClientWrapper
    ├── 底层客户端(HTTPLLMClient / OpenAISDK / LazyLLMClient)
    ├── 拦截器(before / after / on_error)
    └── 用量追踪(token / latency / reasoning...)

6.2 拦截器:横切关注点

这是我最喜欢的一个设计。memU 允许你在 LLM 调用的前后"插一脚":

# 记录每次 LLM 调用
service.intercept_after_llm_call(
    fn=log_usage,
    where={"operation": "chat"}  # 只拦截 chat 操作
)

这能做什么?

  • 监控 token 消耗
  • 记录调用日志
  • 实现重试机制
  • 替换响应内容(测试用)
  • 注入调试信息

同样的拦截器也适用于工作流步骤。你可以在每个步骤执行前后加钩子,实现完整的可观测性。

6.3 多 Provider 支持

memU 不挑 LLM。只要能聊、能 embedding,它就能用:

  • OpenAI
  • Grok
  • Doubao
  • OpenRouter
  • LazyLLM(一个聚合器)
配置一下 provider 和 model就行,代码不用改。

七、用户作用域:天然支持多租户

这是我认为被严重低估的设计。

memU 允许你定义一个 user_model

class UserConfig(BaseModel):
    user_id: str
    tenant_id: str

然后这个模型会被混入所有数据表:

# 来自 src/memu/database/models.py
def merge_scope_model(user_model, core_model):
    """把用户字段混入核心模型"""
    return type(
        f"{user_model.__name__}{core_model.__name__}",
        (user_model, core_model),
        {}
    )

结果:

  • 每条记忆自动带用户 ID
  • 查询自动带 WHERE user_id = ?
  • 多租户?不同用户不同隔离?

这就是所谓的约定大于配置——只要你声明了用户模型,剩下的自动搞定。


八、核心流程:记住是怎么发生的?

8.1 memorize:把东西存进去

输入:对话/文档/图片
  │
  ▼
ingest_resource(把文件拉下来)
  │
  ▼
preprocess_multimodal(预处理)
  │
  ▼
extract_items(LLM 提取记忆点)
  │
  ▼
dedupe_merge(去重,目前是占位符)
  │
  ▼
categorize_items(向量化和存库)
  │
  ▼
persist_index(更新类别摘要)
  │
  ▼
输出:成功/多少条记忆/多少个类别

每个步骤都可以单独配置 LLM profile——比如用便宜的模型做预处理,用贵的模型做提取。

8.2 retrieve:把东西取出来

有两种模式:

RAG 模式(向量检索):

  • 把查询转成向量
  • 搜相关类别
  • 搜相关记忆
  • 搜原始资源
  • 返回结果

LLM 模式(让 AI 排序):
  • 类似流程,但排序让 LLM 做
  • 适合需要语义理解的复杂查询

两种模式可以无缝切换,改个配置就行。


九、设计取舍:承认的不完美

没有任何架构是完美的。memU 也有它的局限:

  1. dict-based 状态:工作流状态是字典,依赖键名约定。这不如静态类型安全,但更灵活。
  1. SQLite/内存暴力搜索:规模大了会有性能问题。需要上 pgvector。
  1. 去重合并未完成dedupe_merge 步骤还是占位符。
  1. Prompt 依赖 LLM:提取和摘要的质量,完全取决于用的模型。
这些在官方文档里都有承认(ADR-001, ADR-002)。诚实面对局限,本身就是一种好设计。

十、总结:memU 教我们什么?

回顾 memU 的架构,我学到几件事:

  1. 组合优于继承。MemoryService 用 Mixin 组合功能,而不是搞个巨大的基类。
  1. 声明优于命令。工作流步骤声明依赖,运行时自动校验,比写注释"请确保先调用 xxx"靠谱多了。
  1. 接口优于实现。Database 是 Protocol,LLM 是 Protocol,拦截器也是 Protocol。面向接口编程,供应商随便换。
  1. 可观测是必须的。拦截器不是"锦上添花",是生产系统的标配。
  1. 简单是终极的复杂。"记忆即文件系统"这个比喻,让整个系统好理解、好解释、好操作。

写在最后

写这篇文章的时候,我一直在想费曼那句话。

memU 的架构确实复杂,但这种复杂是必要的复杂——为了解决真实的问题,为了让系统可扩展、可维护、可观测。

如果你在做一个需要长期记忆的 AI 系统,memU 值得一看。它不是银弹,但它的设计思路,值得很多框架学习。

"我希望你能找到一个朋友,在散步时向他解释这些事。"

——理查德·费曼


希望这篇文章,能让你找到那个愿意听你聊架构的朋友。


(全文完)

讨论回复

0 条回复

还没有人回复