一位物理学家曾经说过,如果你不能用简单的语言解释一件事,说明你还没真正理解它。同样,如果一个框架的架构说不清楚,那它的设计一定有什么地方不对劲。
想象一下,你有一个 24 小时在线的 AI 助手。它认识你,了解你的喜好,记得你上周提到想学吉他。
但问题是:这个 AI 每次跟你对话,都像第一次见到你。
为什么?因为传统 AI 没有长期记忆。不是它不想记住,是臣妾做不到啊——把全部对话塞进上下文,光是 token 费用就能让老板跑路。
所以 memU 要解决的核心问题是:
能不能让 AI 记住东西,还花不了多少钱?这就是 memU 设计的原点。
memU 最有趣的想法是:把记忆当成文件系统来管理。
你可以这样理解:
| 文件系统 | memU |
|---|---|
| 文件夹 | 📁 Category(类别) |
| 文件 | 📄 MemoryItem(记忆项) |
| 快捷方式 | 🔗 引用关系 |
| 挂载点 | 📂 Resource(原始资源) |
为什么这样设计?
你想啊,我们人类记东西,是不是也是分层级的?
让我画个大概的轮廓:
┌─────────────────────────────────────────────┐
│ MemoryService │
│ (记忆服务大管家) │
├─────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 记住 │ │ 召回 │ │ 增删改 │ │
│ │(memorize)│ │(retrieve)│ │ (CRUD) │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Pipeline Manager │ │
│ │ (工作流编排器) │ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ LLM │ │ 存储后端 │ │ 拦截器 │ │
│ │ 客户端 │ │ (可插拔) │ │ (可观测) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────┘
这个图有点复杂,但核心思想其实很简单:
MemoryService 是个大管家,它把"记住"和"回忆"这两件事,拆成了一个个小步骤,然后交给不同的人去执行。
你想,"记住"一件事容易吗?
要经过这些步骤:
看这段代码(来源: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。
这就像工厂的工艺卡:
运行时自动校验。 如果中间少了个步骤,程序会立刻报错,而不是跑到半路才崩盘。
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
)
想象一下:你可以不修改源码,就在生产环境给记忆流程加个"敏感信息过滤"步骤。这是很多框架做不到的。
memU 的存储是可插拔的,就像手机充电口:
| 后端 | 特点 | 适用场景 |
|---|---|---|
| **inmemory** | 内存里,掉电即失 | 开发调试 |
| **sqlite** | 文件数据库,轻量便携 | 个人项目、小团队 |
| **postgres** | 企业级数据库,支持向量索引 | 生产环境 |
为什么搞这么复杂?
因为不同的阶段需要不同的存储:
memU 定义了一个 Database 协议(Protocol),里面装着四个仓库:
class Database(Protocol):
resource_repo: ResourceRepo # 原始资源
memory_item_repo: MemoryItemRepo # 记忆项
memory_category_repo: MemoryCategoryRepo # 类别
category_item_repo: CategoryItemRepo # 关系
这就像一个万能插座。不管你接的是 SQLite 还是 Postgres,接口都一样。
memU 的核心能力之一是语义搜索——不是搜关键字,而是搜"意思相近"。
实现方式有两种:
# 来自 src/memu/database/inmemory/vector.py
def cosine_topk(query_vec, corpus, k=5):
"""找出最相似的 k 个"""
# ... 向量化计算 ...
return top_k_results
如果你只按相似度排序,AI 会有这个问题:
用户问"我昨天跟你说啥了?"AI 搜出五条最相似的记录——但第一条是"你上周说的",第二条是"你去年说的"。
所以 memU 搞了个"显著度"评分:
显著度 = 相似度 × 强化因子 × 时间衰减
log(被引用次数 + 1) — 越常被提起的记忆越重要memU 的 LLM 调用看起来是这样的:
LLMClientWrapper
├── 底层客户端(HTTPLLMClient / OpenAISDK / LazyLLMClient)
├── 拦截器(before / after / on_error)
└── 用量追踪(token / latency / reasoning...)
这是我最喜欢的一个设计。memU 允许你在 LLM 调用的前后"插一脚":
# 记录每次 LLM 调用
service.intercept_after_llm_call(
fn=log_usage,
where={"operation": "chat"} # 只拦截 chat 操作
)
这能做什么?
memU 不挑 LLM。只要能聊、能 embedding,它就能用:
这是我认为被严重低估的设计。
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),
{}
)
结果:
WHERE user_id = ?输入:对话/文档/图片
│
▼
ingest_resource(把文件拉下来)
│
▼
preprocess_multimodal(预处理)
│
▼
extract_items(LLM 提取记忆点)
│
▼
dedupe_merge(去重,目前是占位符)
│
▼
categorize_items(向量化和存库)
│
▼
persist_index(更新类别摘要)
│
▼
输出:成功/多少条记忆/多少个类别
每个步骤都可以单独配置 LLM profile——比如用便宜的模型做预处理,用贵的模型做提取。
有两种模式:
RAG 模式(向量检索):
没有任何架构是完美的。memU 也有它的局限:
dedupe_merge 步骤还是占位符。回顾 memU 的架构,我学到几件事:
写这篇文章的时候,我一直在想费曼那句话。
memU 的架构确实复杂,但这种复杂是必要的复杂——为了解决真实的问题,为了让系统可扩展、可维护、可观测。
如果你在做一个需要长期记忆的 AI 系统,memU 值得一看。它不是银弹,但它的设计思路,值得很多框架学习。
"我希望你能找到一个朋友,在散步时向他解释这些事。"——理查德·费曼
(全文完)
还没有人回复