> 一位物理学家曾经说过,如果你不能用简单的语言解释一件事,说明你还没真正理解它。
>
> 同样,如果一个框架的架构说不清楚,那它的设计一定有什么地方不对劲。
今天我想和你聊聊 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`):
```python
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 是个有意思的东西。它不仅能定义流程,还能在运行时修改流程:
```python
# 在某个步骤之后插入新步骤
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),里面装着四个仓库:
```python
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 的选择是:都要。
```python
# 来自 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 调用的前后"插一脚":
```python
# 记录每次 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`:
```python
class UserConfig(BaseModel):
user_id: str
tenant_id: str
```
然后这个模型会被**混入**所有数据表:
```python
# 来自 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 状态**:工作流状态是字典,依赖键名约定。这不如静态类型安全,但更灵活。
2. **SQLite/内存暴力搜索**:规模大了会有性能问题。需要上 pgvector。
3. **去重合并未完成**:`dedupe_merge` 步骤还是占位符。
4. **Prompt 依赖 LLM**:提取和摘要的质量,完全取决于用的模型。
这些在官方文档里都有承认(ADR-001, ADR-002)。**诚实面对局限,本身就是一种好设计。**
---
## 十、总结:memU 教我们什么?
回顾 memU 的架构,我学到几件事:
1. **组合优于继承**。MemoryService 用 Mixin 组合功能,而不是搞个巨大的基类。
2. **声明优于命令**。工作流步骤声明依赖,运行时自动校验,比写注释"请确保先调用 xxx"靠谱多了。
3. **接口优于实现**。Database 是 Protocol,LLM 是 Protocol,拦截器也是 Protocol。面向接口编程,供应商随便换。
4. **可观测是必须的**。拦截器不是"锦上添花",是生产系统的标配。
5. **简单是终极的复杂**。"记忆即文件系统"这个比喻,让整个系统好理解、好解释、好操作。
---
## 写在最后
写这篇文章的时候,我一直在想费曼那句话。
memU 的架构确实复杂,但这种复杂是**必要的复杂**——为了解决真实的问题,为了让系统可扩展、可维护、可观测。
如果你在做一个需要长期记忆的 AI 系统,memU 值得一看。它不是银弹,但它的设计思路,值得很多框架学习。
> "我希望你能找到一个朋友,在散步时向他解释这些事。"
>
> ——理查德·费曼
希望这篇文章,能让你找到那个愿意听你聊架构的朋友。
---
*(全文完)*
登录后可参与表态
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!