第 3 章 数据模型详解 memU 的一切 API 返回值都可以还原为四种持久记录 + 向量索引。本章把字段含义讲透,后面调 API 才不会「对着字典猜」。 3.1 四类核心记录 记录 | 角色 | 一句话 | 来源素材 | 「这条记忆从哪来」 | 原子记忆 | 「具体记住什么」 | 主题文件夹 | 「属于哪个话题」 | 归属边 | 「哪条记忆在哪个文件夹里」 3.2 Resource(来源层) Resource 代表一份原始输入,是溯源链的起点。
memU 的一切 API 返回值都可以还原为四种持久记录 + 向量索引。本章把字段含义讲透,后面调 API 才不会「对着字典猜」。
Resource ──产生──► MemoryItem ──归属──► MemoryCategory ▲ ▲ └──── CategoryItem ──┘ (关系边)
| 记录 | 角色 | 一句话 |
|---|---|---|
Resource |
来源素材 | 「这条记忆从哪来」 |
MemoryItem |
原子记忆 | 「具体记住什么」 |
MemoryCategory |
主题文件夹 | 「属于哪个话题」 |
CategoryItem |
归属边 | 「哪条记忆在哪个文件夹里」 |
Resource 代表一份原始输入,是溯源链的起点。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 唯一标识 |
url |
string | 原始地址(本地路径或 HTTP URL) |
modality |
enum | conversation / document / image / video / audio |
local_path |
string | 摄入后在本地的副本路径 |
caption |
string | 预处理生成的文本描述(多模态关键) |
embedding |
float[] | 可选,来源级向量 |
{ "id": "res_01", "url": "https://storage.example.com/meeting.mp4", "modality": "video", "caption": "产品规划会议,讨论 onboarding 简化与上线风险。", "embedding": [0.012, -0.034, "..."] }
工程要点:
caption 文本,再进入提取阶段caption 或原文MemoryItem 是 LLM 从 Resource 中提取的原子记忆,相当于「一个文件」。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 唯一标识 |
memory_type |
enum | profile / event / knowledge / behavior / skill / tool |
summary |
string | 自然语言摘要(检索主字段) |
extra |
object | 扩展元数据(时间、实体等) |
happened_at |
datetime | 事件类记忆的发生时间 |
embedding |
float[] | 项级向量(RAG 模式使用) |
resource_id |
string | 关联的来源 ID |
{ "id": "mem_01", "memory_type": "profile", "summary": "用户偏好简洁的技术文档,不喜欢冗长铺垫。", "happened_at": null, "resource_id": "res_01" }
| memory_type | 何时产生 | retrieve 时的权重建议 |
|---|---|---|
| profile | 稳定特征、偏好 | 个性化回复时优先 |
| event | 决策、会议、里程碑 | 任务规划时优先 |
| knowledge | 客观事实、文档要点 | 问答、RAG 补充 |
| behavior | 使用习惯、频率模式 | proactive 推荐 |
| skill | 方法论、最佳实践 | 编码 / 分析智能体 |
| tool | CLI、API 使用教训 | 工具调用智能体 |
Category 是自动生成的「文件夹」,维护主题级摘要,供宽泛查询快速加载上下文。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 唯一标识 |
name |
string | 规范化主题名(如 user_preferences) |
description |
string | 主题描述 |
summary |
string | 演进式摘要(随新 Item 更新) |
embedding |
float[] | 类级向量 |
{ "id": "cat_01", "name": "product_goals", "description": "产品目标与上线相关决策", "summary": "当前优先级:简化 onboarding;下次评审前需完成风险清单。" }
自组织机制:新 Item 写入时,LLM 决定归入已有 Category 或创建新 Category,并更新 summary。无需人工打标签。
CategoryItem 只表示「哪条 MemoryItem 属于哪个 MemoryCategory」,是多对多关系。
{ "item_id": "mem_01", "category_id": "cat_01" }
检索时利用关系图:
result = await service.memorize(...) # result 结构: { "resource": Resource, # 本次摄入的来源(单条) "items": [MemoryItem, ...], # 新提取的记忆项 "categories": [MemoryCategory, ...], # 新建或更新的主题 "relations": [CategoryItem, ...], # 新建立的归属关系 }
注意:一次 memorize 可能更新已有 Category 的 summary,而不只是追加新 Category。
context = await service.retrieve(...) # context 结构: { "needs_retrieval": bool, # 是否真的需要查记忆 "original_query": str, # 原始查询文本 "rewritten_query": str, # LLM 重写后的查询 "next_step_query": str | None, # 建议的下一步查询(迭代检索) "categories": [MemoryCategory, ...], "items": [MemoryItem, ...], "resources": [Resource, ...], }
| 字段 | 作用 |
|---|---|
needs_retrieval |
若为 False,可能直接回复无需查库 |
rewritten_query |
消歧、补全后的检索 query |
next_step_query |
支持多轮渐进式检索 |
categories |
主题层命中结果 |
items |
细项层命中结果 |
resources |
需读原文时的来源列表 |
当配置 UserConfig 或在 API 传入 user / where 时,作用域字段(如 user_id、agent_id、session_id)会写入各层记录:
# 写入 await service.memorize( resource_url="...", modality="document", user={"user_id": "u123", "agent_id": "assistant_v2"}, ) # 读取(必须匹配) await service.retrieve( queries=[...], where={"user_id": "u123"}, )
常见错误:写入时带了 user_id,检索时忘记 where → 结果为空或串数据。
| 层 | 是否 embedding | RAG 模式用途 |
|---|---|---|
| Resource | 可选 | 来源级召回 |
| MemoryItem | 通常有 | 细粒度语义匹配 |
| MemoryCategory | 通常有 | 主题级快速定位 |
LLM 模式(method="llm")则把格式化后的 Category / Item / Resource 文本交给 LLM 排序,不过度依赖向量。
memU 可将结构化存储导出为人类可读树(第 10 章):
| 存储记录 | 导出产物 |
|---|---|
| 全部 Resource | 资源目录 + 索引页 |
| MemoryCategory | 每个主题一个 Markdown 页 |
| 聚合视图 | MEMORY 总览、SKILL 技能索引 |
导出是只读渲染,不改变数据库中的记录。
memorize 产出 items + categories + relations;retrieve 分层返回 categories / items / resources。memorize 返回的 JSON pretty-print 出来,手工标注每个字段。modality 的数据,观察 categories 是否合并或新建。next_step_query 在什么场景下会非空。本章讲的三层结构(resource / items / categories)可以用下面这个完整脚本的「美化打印」函数直观看到。它把 memorize 返回的字段逐层展开,并统计 memory_type 分布:
"""memorize 返回结构可视化与 memory_type 分布统计完整示例。 把 memorize() 返回的三层结构(resource / items / categories)美化打印, 并统计不同 modality 提取出哪些类型的记忆项。 依赖:pip install memu-py (需配置 LLM API Key) """ import asyncio from memu import MemoryService def print_memorize_result(result, title="memorize 返回"): """美化打印 memorize() 的返回,逐层展开三层结构。""" print(f"=== {title} ===") res = result.get("resource", {}) print(f" resource id: {res.get('id')}") print(f" modality : {res.get('modality')}") items = result.get("items", []) print(f" items : {len(items)} 条") for it in items: print(f" [{it.get('memory_type')}] {str(it.get('summary',''))[:80]}") cats = result.get("categories", []) if cats: print(f" categories : {[c.get('name') for c in cats]}") # memory_type 分布统计 type_counts = {} for it in items: t = it.get("memory_type", "unknown") type_counts[t] = type_counts.get(t, 0) + 1 print(f" 类型分布 : {type_counts}") CONVERSATION = [ {"role": "user", "content": "我是后端工程师,主攻分布式系统,用 Go 较多。"}, {"role": "assistant", "content": "了解了。"}, {"role": "user", "content": "我们团队下季度要做服务网格迁移,从 Istio 换到 Linkerd。"}, {"role": "assistant", "content": "记下了。"}, ] async def main(): service = MemoryService( llm_profiles={"default": {"api_key": "your_api_key", "chat_model": "gpt-4o-mini"}}, database_config={"metadata_store": {"provider": "inmemory"}}, retrieve_config={"method": "rag"}, ) # 实际使用时把 CONVERSATION 写成本地 JSON 文件,resource_url 指向它 import json, tempfile, os p = os.path.join(tempfile.gettempdir(), "memu_conv.json") with open(p, "w", encoding="utf-8") as f: json.dump(CONVERSATION, f, ensure_ascii=False) result = await service.memorize( resource_url=p, modality="conversation", user={"user_id": "demo_user"}, ) print_memorize_result(result) if __name__ == "__main__": asyncio.run(main())
💡 运行后观察 items 的 memory_type 分布:对话类输入通常会提取出 profile(用户画像)、event(决策/待办)、knowledge 等类型。这正是 memU 区别于「整段塞向量库」的地方——它把原始来源编译成了带类型的原子事实。
下一章:第 4 章 — MemoryService 初始化与配置。
参见:第 2 章三层架构;附录 B 字段速查。