MemPalace 源码级技术方案分析报告
项目定位与总体判断
MemPalace 本质上不是“神奇记忆模型”,而是一套极度克制的本地记忆基础设施
MemPalace 的源码给人的第一印象,并不是“模型创新”,而是工程取舍非常明确。它没有构造新的训练范式,也没有自研向量数据库,更没有把复杂的 agent orchestration 硬塞进一个巨型框架里。相反,它把问题压缩成一个非常朴素但现实的命题:用户与 AI 的长期交互之所以无法形成“记忆”,核心并不是模型不会推理,而是历史信息在会话结束后无法被稳定归档、低成本召回与结构化复用。 因此,MemPalace 的真实产品定义更接近一个“离线可部署的长期记忆操作系统”,而不是一个单点算法。
README 对这一点说得很直白:系统强调的不是“让 AI 判断什么值得记”,而是“把所有内容保留下来,再让系统变得可检索”。这种设计路线,直接反对了大量记忆系统依赖摘要、抽取或 LLM 过滤的常见思路。 从源码实现看,这不是营销措辞,而是被贯穿到底的工程原则:无论是 miner.py 的项目文件入库、convo_miner.py 的会话导入、searcher.py 的原文检索,还是 layers.py 的分层唤醒接口,都尽量把摘要和解释延后,把原文保存和元数据路由前置。
这里最值得注意的反常识点是:MemPalace 的强项不是“懂得多”,而是“忘得少”;不是“压缩得狠”,而是“保存得真”。 README 甚至公开承认,Headline benchmark 的高分来自 raw mode,而不是更“炫”的 AAAK 压缩模式。这在开源项目里并不常见,因为多数项目会把实验性功能包装成核心卖点,但 MemPalace 在源码与文档里反而把最有效的部分落在最朴素的路径上:把原文扔进 ChromaDB,然后用语义检索与轻量元数据过滤做召回。
| 判断维度 | 结论 | 源码依据 |
|---|---|---|
| 产品本质 | 本地长期记忆基础设施,而非新模型 | README.md、cli.py、layers.py |
| 核心哲学 | Store everything, then make it findable | README.md、miner.py、searcher.py |
| 主能力边界 | 导入、归档、检索、唤醒、MCP 工具暴露 | cli.py、mcp_server.py |
| 真实性能来源 | Raw verbatim storage + Chroma semantic search | README.md、benchmarks/longmemeval_bench.py |
| 实验性能力 | AAAK 压缩、图遍历、知识图谱、通用记忆抽取 | dialect.py、palace_graph.py、knowledge_graph.py、general_extractor.py |
为了从架构视角把项目看清楚,可以先把它抽象成四条主链路:初始化链路、导入链路、检索链路、对外能力暴露链路。初始化链路负责生成翼区与房间定义;导入链路负责把项目文件或对话记录变成 drawers;检索链路负责通过 ChromaDB 与 metadata filter 完成查找;对外暴露链路负责把这些能力包装成 CLI、MCP 和 wake-up 文本。
从这个图可以看出,MemPalace 并不复杂,但它的复杂度被有意识地固定在数据进入系统之后的“第二层”:即统一的 collection、统一的 metadata schema、统一的查询接口。一旦这一层稳定下来,前端可以接项目文件、聊天日志、MCP、CLI;后端可以接唤醒层、语义搜索、知识图谱甚至图遍历。这是一个典型的以数据面为中心、以接口面为延展点的架构,而不是“每个功能自己维护一套状态”的拼装式系统。
“Other memory systems try to fix this by letting AI decide what's worth remembering… MemPalace takes a different approach: store everything, then make it findable.” —— 项目 README
这种设计有明显优点。第一,它极大减少了记忆系统最常见的“摘要失真”问题,因为原文仍在;第二,它把系统效果更多地交给检索质量,而不是交给一个不可解释的“记忆提取 prompt”;第三,它天然适合离线环境,因为从导入到检索都不要求远程 API。但它也有代价:一是存储会更大,二是早期结构设计一旦糟糕,后续检索体验会迅速恶化,三是很多“聪明功能”实际上只是建立在已有 metadata 上的轻量操作,而不是真正强语义的知识推理。README 在更新说明里对这些边界也有相当坦诚的说明。
技术栈与运行时骨架
一个依赖极简、接口层清晰、数据层统一的 Python CLI 项目
从 pyproject.toml 可以看出,MemPalace 是一个标准的 Python CLI 包,要求 Python 版本为 3.9 及以上,构建系统使用 hatchling,暴露出的脚本入口为 mempalace = "mempalace:main"。这意味着整个仓库首先是“命令行应用”,其次才是“可嵌入的库”。这一点在 __init__.py 与 __main__.py 的设计里也能得到印证:包级入口几乎只是把 main 转发给 CLI,而大部分核心功能都由独立模块承载。
更值得关注的是它的依赖策略。项目硬依赖只声明了 chromadb 与 pyyaml 两项。这在 AI 基础设施项目里非常少见,因为大多数项目会引入 embedding SDK、HTTP 客户端、prompt orchestration 框架甚至任务图引擎。MemPalace 则把架构重心刻意压在本地数据库与文件系统上:pyyaml 用于读 mempalace.yaml,chromadb 负责持久化向量存储与查询,其余行为尽量依赖 Python 标准库完成,例如 os、pathlib、hashlib、json、datetime、argparse 与 collections。
| 维度 | 选型 | 作用 | 工程含义 |
|---|---|---|---|
| 语言 | Python 3.9+ | 主实现语言 | CLI 与本地数据处理友好 |
| 包管理/构建 | Hatchling | 打包与发布 | 轻量标准化 |
| 配置格式 | JSON + YAML | 全局配置、项目房间配置 | 兼顾可编程与可读性 |
| 核心存储 | ChromaDB PersistentClient | 向量持久化与检索 | 单机场景部署门槛低 |
| 对外接口 | CLI + MCP | 用户操作与 Agent 集成 | 兼容工具型 AI 工作流 |
| 图数据 | 基于 metadata 派生,不引入单独图库 | 图遍历与 tunnel 发现 | 保持数据面统一 |
| 知识图谱 | 本地 SQLite | 实体关系与时序事实 | 与向量库解耦 |
这种“依赖极简”的技术栈,一方面有非常大的工程优势。它让仓库的安装、阅读和扩展门槛都很低,用户可以较快建立心理模型。cli.py 里所有子命令都直连具体模块,调用关系几乎是平铺的:init 对应实体检测和房间检测,mine 分流到项目挖掘或对话挖掘,search 直接走 searcher.search,wake-up 通过 MemoryStack 聚合四层记忆,status 与 repair 分别负责数据巡检和索引修复。这种入口设计极其利于源码级理解,因为主流程没有被框架魔法隐藏。
另一方面,依赖极简也意味着能力上限受到约束。例如,系统默认依赖 ChromaDB 自带 embedding 路径,benchmark 脚本里虽然支持 fastembed 切换为 bge-base、bge-large、nomic、mxbai 等模型,但这部分并未被提升为主产品面的可配置核心,而更像评测与试验通道。这说明项目在“工程稳定性”与“可调优空间”之间,明显更偏前者。对于希望快速落地本地记忆系统的用户,这是优点;对于想把它当成企业级记忆平台底座的人,这又是一种约束。
config.py 则揭示了它的全局运行时骨架。系统默认把 palace 放在 ~/.mempalace/palace,collection 名称默认为 mempalace_drawers,同时保留 people_map、topic wings、hall keywords 等全局配置项。 它的配置优先级是环境变量 > 用户配置文件 > 默认值,这是非常标准且合理的设计。更重要的是,这个全局配置类被多个模块共享,从而在 CLI、Layer、Graph、MCP 等子系统之间提供了一致的路径与集合名来源。
总体而言,MemPalace 的技术栈传递出一个清晰信号:作者并不试图用复杂依赖打造“AI 框架”,而是用最少依赖拼出一个本地长期记忆闭环。 这种选择并不华丽,却与其产品承诺高度一致:免费、本地、低门槛、可验证。
核心原理与数据模型
“宫殿”并不是数据库替代品,而是向量检索之上的语义命名系统
如果只读 README,容易误以为 MemPalace 的“wing / hall / room / drawer”是一种新型数据库结构。但从源码层面看,它并没有造新数据库,而是在 ChromaDB 之上定义了一套命名明确的语义组织协议。换句话说,宫殿模型不是 storage engine,而是 metadata ontology。真正的存储仍然是 Chroma collection;真正决定语义过滤与可导航性的,是每条 drawer metadata 中持续存在的 wing、room、source_file、chunk_index、added_by、filed_at 等字段。
miner.py 的 add_drawer() 很能说明这个问题。每个 chunk 被写入 collection 时,文档内容直接作为 documents 存储,drawer id 由 wing + room + source_file + chunk_index 的哈希派生,而 metadata 则写入 wing、room、source_file、chunk_index、added_by、filed_at。convo_miner.py 走的是相同数据面,只是会额外记录 ingest_mode 与 extract_mode,并在 general 模式下把 memory_type 映射为 room。 这说明项目真正稳定的“公共协议”不是类接口,而是collection 内部的 metadata schema。
| 抽象概念 | 在源码中的真实实现 | 语义作用 |
|---|---|---|
| Wing | metadata 字段 wing |
表示项目、主体或大域 |
| Room | metadata 字段 room |
表示具体议题、模块或记忆类别 |
| Drawer | collection 中的一条文档记录 | 最小可检索原文单元 |
| Hall | 部分模块中 metadata 字段 hall |
更粗粒度的走廊/类别层 |
| Palace | ChromaDB 持久化目录 + collection | 统一数据面 |
这套设计的关键价值,在于它把“结构化”限制在足够轻的层面。项目没有要求每一条历史必须经过复杂的知识抽取,也没有强制建立实体关系图之后才能查询。它接受一个现实:大多数长期记忆需求,首先是“把过去的话找回来”,其次才是“对过去的话做高阶推理”。因此,MemPalace 的一切上层能力——Layer 召回、图遍历、重复检测、MCP 工具——都建立在这个轻量 metadata schema 之上。
需要特别指出的是,这套宫殿结构不等于真正的层级树数据库。 在源码里,wing 与 room 更像标签,而不是严格父子目录。searcher.py 的过滤逻辑也说明了这一点:查询时只是根据 wing、room 生成一个简单的 where 条件,组合方式也只是 {"$and": [{"wing": wing}, {"room": room}]} 或单字段过滤。 这意味着“宫殿”更多是帮助人和模型建立稳定的认知地图,而非数据库层面复杂的层级索引。
这种“命名系统而非新存储”的设计还有一个额外好处:它让系统能同时兼容三种不同复杂度的工作方式。最轻量的用户只需要 raw ingest + search;中等复杂度用户可以利用 wing / room 做精细过滤;更高阶用户则可以叠加图遍历、KG、AAAK 和 general extractor。也就是说,MemPalace 的扩展策略不是替换底座,而是在统一底座上叠加不同的解释层。 这一点,是整个项目最成熟的架构判断之一。
从理论上说,这是一种向量检索 + 轻量语义标签的折中路径。它没有知识图谱系统那样强的结构表达力,也没有全文搜索系统那样对关键词的极致可控性,但在“低配置、离线、可快速起效”的目标下,这种折中非常有效。也正因为如此,README 才会明确承认“metadata filtering is a standard ChromaDB feature, not a novel retrieval mechanism”,这说明作者知道自己的创新点更偏系统组合与协议设计,而不是底层算法发明。
项目文件导入链路
从目录扫描、忽略规则、房间路由到 Drawer 落库的完整实现
miner.py 是 MemPalace 的第一条核心数据入口,也是最适合看出其工程风格的模块。它并不试图做 AST 级语义理解,也不做 LLM 摘要,而是先定义一个足够务实的文件处理流水线:扫描项目目录、应用跳过规则、读取文本、切块、路由到 room、写入 ChromaDB。
扫描阶段首先由 scan_project() 驱动。可读扩展名白名单覆盖了代码、文档、配置与常见数据文件,例如 .py、.ts、.json、.yaml、.sql、.toml、.csv 等;同时硬编码跳过 .git、node_modules、.venv、dist、coverage、.mypy_cache 等生成目录。这种白名单 + skip dirs 的组合,是一种非常稳健的本地文件挖掘策略,因为它把“默认安全”放在第一位,先避免系统把大量噪音文件写进记忆库。
进一步看,miner.py 还自己实现了一套轻量 .gitignore 匹配器 GitignoreMatcher。它支持注释、否定规则、锚定路径、目录规则和 `** 级路径匹配,并通过 load_gitignore_matcher() 与 is_gitignored() 在目录遍历过程中逐层生效。这段代码非常说明问题:作者不愿为了一个局部功能引入额外的第三方解析器,而是用足够小但功能相对完整的实现来覆盖 80% 以上实际场景。技术上看它不算复杂,但产品上看非常关键,因为真实项目目录里 .gitignore 代表着用户最自然的“别碰这些东西”的意图。
| 导入步骤 | 关键函数 | 作用 | 设计判断 |
|---|---|---|---|
| 文件扫描 | scan_project() |
遍历目录、筛选可读文件 | 以白名单保证稳定性 |
| 忽略处理 | GitignoreMatcher / is_gitignored() |
遵守 .gitignore 语义 |
与开发者工作流对齐 |
| 读文件 | process_file() |
读取文本内容 | 只做文本级处理 |
| 路由 | detect_room() |
把文件映射到 room | 轻量启发式而非语义模型 |
| 分块 | chunk_text() |
生成 drawers | 优先保留边界可读性 |
| 入库 | add_drawer() |
写入 ChromaDB | 原文 + metadata 双写 |
在 room 路由方面,detect_room() 的实现非常值得分析。它的优先级是:先看目录路径是否匹配 room 名或关键词,再看文件名是否匹配,最后才是统计内容关键词出现频次,若仍无结果则回退到 general。 这是一种明显“重目录结构、轻深语义”的策略。对于真实软件项目而言,这其实相当合理,因为大多数项目的文件夹命名已经天然携带模块信息,如 auth、billing、infra、docs。如果强行上更重的语义分类,未必能比目录结构更可靠。
分块策略由 chunk_text() 实现,默认 CHUNK_SIZE=800、CHUNK_OVERLAP=100、MIN_CHUNK_SIZE=50。这说明作者倾向于让每个 drawer 保持在一个适中的字符级尺度,既能让向量检索捕捉语义中心,又不至于因为过长而混入太多无关上下文。算法上它会优先尝试在段落边界或换行边界截断,若找不到合适边界再按长度切断。这种“尽量尊重自然边界”的 chunking,对代码和文档都比纯固定窗口更友好。
入库阶段 add_drawer() 用 source_file + chunk_index 做 MD5 哈希,得到稳定的 drawer id。 这意味着同一个文件同一个 chunk 的 ID 基本稳定,从而为幂等处理提供基础。与之配套的 file_already_mined() 则通过 source_file 做快速存在性判断,如果文件已经被处理过,就可以跳过重新导入。这并不完美,因为它更像“按文件级幂等”而不是“按内容级增量更新”;但对本地工作流来说,这是一种很划算的简化。用户通常会在重大变更后重新执行 mine,而不是要求系统做复杂的 patch-level 更新。
从架构角度讲,miner.py 的最大优点是链路足够短、调试成本足够低、行为几乎全可解释。但它的短板也很明确:第一,room 路由是关键词启发式,对命名混乱或跨域文件不够稳;第二,chunking 是字符级,不具备 AST、函数边界或 Markdown section 感知;第三,已导入文件跳过机制偏粗糙,缺乏基于哈希的增量重建。这些并不是 bug,而是成本换来的边界。对一个主打“本地可用”的系统来说,这种边界是可以理解的,但如果未来要支持大型工程仓库,就需要把这些地方逐步系统化。
对话导入链路
格式归一化、交换对分块、通用记忆抽取与会话记忆落库
与项目文件链路相比,MemPalace 的第二条入口——对话导入链路——更能体现其“把异构历史统一成可检索记忆”的能力。这里涉及三个关键模块:normalize.py 负责多格式标准化,convo_miner.py 负责分块与入库,general_extractor.py 则负责在 general 模式下从对话中抽出更偏“记忆类型”的片段。
normalize.py 的角色是把不同平台的导出结构,统一转换为一种近似 transcript 的内部格式:用户发言以前缀 > 标记,后接 assistant 内容。该模块支持 Claude Code JSONL、OpenAI Codex CLI JSONL、Claude.ai JSON、ChatGPT conversations.json、Slack JSON 以及已经带 > 标记的纯文本。其思路不是构建统一消息对象层,而是先把各平台 schema 拉平为 (role, text) 列表,再通过 _messages_to_transcript() 写回统一文本格式。
这一步的工程价值非常高,因为后续 convo_miner.py 就无需再关心来源平台。chunk_exchanges() 只需要对“> 用户消息 + 后续 assistant 响应”做局部解析即可。如果能检测到足够多的 > 行,就走 _chunk_by_exchange(),以一组 Q+A 作为一个 drawer;否则就退回 _chunk_by_paragraph(),按段落或固定行数组合切块。从产品角度,这种设计显然优先服务“对话是以轮次发生的”这一认知模型,因此非常适合记住决策讨论、debug 过程、架构争论等交互式内容。
| 模块 | 核心函数 | 主要责任 | 关键启发 |
|---|---|---|---|
normalize.py |
normalize() |
多平台聊天导出标准化 | 先统一 transcript,再做下游处理 |
convo_miner.py |
chunk_exchanges() |
把对话变成可入库片段 | Q+A 对是天然记忆单元 |
convo_miner.py |
detect_convo_room() |
为整段对话分配 room | 基于主题关键词轻量路由 |
general_extractor.py |
extract_memories() |
提取 decisions / problems 等 | 不依赖 LLM 的规则抽取 |
convo_miner.py 有两种模式。默认的 exchange 模式把每个用户回合与紧随其后的 AI 回复拼成一个 chunk;而 general 模式则把内容交给 general_extractor.extract_memories(),从中抽出 decision、preference、milestone、problem、emotional 五类记忆片段。这一分叉非常重要,因为它体现出项目在“保真存档”和“语义抽取”之间的双轨策略:默认路径保原文结构,增强路径才做抽取分类。
深入看 general_extractor.py,会发现它完全依赖本地启发式规则工作。文件里定义了多个 marker 集,例如 decision marker 会捕捉 “we decided”“because”“trade-off”“framework”“infrastructure”等模式;problem marker 会捕捉 “bug”“error”“root cause”“workaround”等;milestone marker 会捕捉 “it works”“breakthrough”“shipped”“deployed”等。然后它通过 _score_markers() 计算分数,再用 _disambiguate() 结合正负面词汇与“问题是否已解决”做再分类,例如已解决的问题会升级为 milestone,正面情绪显著时也可能转为 emotional。
这种规则系统并不聪明,但它有两个现实优点。其一,可解释且离线可跑;其二,对“开发者与 AI 的技术协作语料”其实非常有效,因为这类语料高度模板化,存在大量稳定措辞,例如 “we should”“the fix was”“it works now”“because”。这解释了为什么 MemPalace 没有在通用记忆抽取上引入 LLM:在目标语域足够窄时,规则系统的收益/成本比反而更高。
在入库数据面上,convo_miner.py 复用了与项目文件同一套 collection,只是在 metadata 中增加了 ingest_mode 与 extract_mode,并在 general 模式下将 memory_type 直接视作 room。这再一次说明,MemPalace 的系统优势来自于数据面的统一:项目文件与对话记录,最终都变成相同的 drawer 对象,只是来源和路由逻辑不同。
不过,对话链路也有明显局限。第一,_chunk_by_exchange() 默认只截取 AI 回复前 8 行左右的内容,极长回答会被强烈裁切;第二,Slack 多人场景通过交替 user/assistant 角色来保持结构,这对复杂多人讨论并不严格准确;第三,通用抽取模块没有上下文窗口概念,跨段落决策可能会被切断。但如果把目标限定在“为 AI 形成可查询的中长期记忆”,这套简化依旧是可以成立的,因为它强调的是让大量历史先被纳入统一检索面,而不是一开始就做到完美语义重建。
检索与四层记忆栈
系统真正的主战场不是导入,而是如何在极低上下文成本下把历史拿回来
如果说导入链路解决的是“记忆能否留下来”,那么 layers.py 与 searcher.py 解决的就是“记忆能否以低成本被重新激活”。从源码看,MemPalace 的核心设计并不是把所有历史塞回 prompt,而是通过四层记忆栈构建一个分层召回系统:Layer0 负责身份,Layer1 负责 essential story,Layer2 负责按 wing/room 的局部召回,Layer3 负责无限深度的语义搜索。
Layer0 很简单:读取 ~/.mempalace/identity.txt,若不存在则返回默认提示文本。它实际上承担的是“人格与角色定义”的静态层,不依赖 ChromaDB。这个设计很合理,因为身份信息通常不是从历史检索里推出来的,而是一个相对稳定的系统前提。
Layer1 则更有意思。Layer1.generate() 会从 collection 中批量取出文档和 metadata,尝试读取 importance、emotional_weight 或 weight 字段作为权重,若没有则默认 3 分;然后按权重排序,最多取 15 条,再按 room 分组,裁成总长不超过 3200 字符的紧凑唤醒文本。注意,这并不是高级 summarization,而是一种加权摘录拼接。换句话说,Layer1 的本质仍然是原文片段的精选组合,而不是新的语义重写。
| 记忆层 | 类 | 典型长度 | 数据来源 | 主要用途 |
|---|---|---|---|---|
| L0 | Layer0 |
~100 tokens | identity.txt |
角色身份与稳定设定 |
| L1 | Layer1 |
~500-800 tokens | Chroma drawers | 每次启动时的“核心故事” |
| L2 | Layer2 |
~200-500 tokens | Chroma drawers | 按 wing/room 的定向回忆 |
| L3 | Layer3 |
不限 | Chroma semantic search | 精确问题查询与深度回溯 |
Layer2.retrieve() 与 searcher.search() 在哲学上也很不一样。Layer2 并不是 semantic query,而更像一个带过滤条件的抽样读取:通过 col.get() 直接按 wing/room 取若干 drawers,主要服务于“话题已知”的场景。Layer3 才是真正的深度语义搜索层:它使用 col.query(),同时可叠加 wing/room 的 metadata filter,再把距离转成 1 - dist 的 similarity 输出。因此,MemPalace 的四层模型本质上是在区分已知话题的回忆与未知位置的搜索。
MemoryStack 则提供统一 façade。它在初始化时装配 Layer0 到 Layer3 四个对象,对外只暴露 wake_up()、recall()、search() 和 status() 等统一接口。这是一个典型的外观模式设计:内部层次明确,但外部只需面向一个聚合对象。CLI 的 cmd_wakeup() 正是通过它来生成唤醒文本。
从检索实现上看,searcher.py 非常克制。search() 主要负责 CLI 输出格式化,而 search_memories() 返回程序可消费的 JSON 结构,供 MCP 服务端复用。它们都采用相同的 where filter 逻辑与 Chroma query 流程。这种拆分很好地遵守了“表现层与数据层分离”的原则:同样一次检索,CLI 需要打印,MCP 需要结构化返回,因此把格式化放在边缘层,而把检索逻辑留在共享模块,是正确的工程选择。
MemPalace 的性能故事,本质上也来自这里。README 宣称 96.6% 的 LongMemEval R@5 来自 raw mode,而 benchmark 脚本证实 raw 模式只是把会话内容直接写入临时 collection,再用 benchmark 问题作为 query 做 Chroma semantic search。也就是说,它的惊艳之处并不在“魔法重排”,而在于:一方面会话内容保留了足够多的原文信号,另一方面 benchmark 的任务形态与向量检索天然适配。这种解释或许不如营销叙事浪漫,但在源码上是成立的。
实验性与扩展子系统
AAAK、Benchmark、Palace Graph、Knowledge Graph 与 MCP Server 的实现关系
若只看主流程,MemPalace 已经成立;但仓库真正显示其野心的,是那些扩展子系统:AAAK 压缩、benchmark 多模式、基于 metadata 的 palace graph、独立 knowledge graph,以及 MCP server。它们共同说明项目并不满足于“做个本地检索工具”,而是试图把检索、记忆、事实与 agent 交互统一到一个长期演进的平台里。
先看 AAAK。dialect.py 开宗明义写明,AAAK 是一种lossy summarization format,它会抽取实体、主题、关键句、情绪与 flags,把原始文本变成一个更紧凑的结构化表示,而且明确声明“96.6% benchmark score is from raw mode, not AAAK mode”。这很重要,因为它说明 AAAK 在当前版本里并不是主存储格式,而是一层可选压缩层。cli.py 的 cmd_compress() 也遵循这一点:它从已有 drawers 中批量读取文档,调用 Dialect.compress() 得到压缩结果与统计信息,而不是把 AAAK 嵌入默认 mine 流程。
再看 benchmark 脚本 longmemeval_bench.py。这个文件几乎就是 README 里性能声明的“可执行解释器”。它分别实现了 raw、aaak、rooms、hybrid、full-turn 等多种检索管线。raw 模式只做原文入库与语义检索;aaak 模式先调用 Dialect.compress() 再入库;rooms 模式为文档与问题打 room 标签,然后对匹配 room 的结果做 20% 距离折减;hybrid 模式则在语义检索结果上按关键词重叠进行再排序。这段代码非常诚实地显示:所谓“架构创新”,大部分都还停留在启发式增强与实验组合层面,而不是形成完全新的检索理论。
| 子系统 | 关键模块 | 当前成熟度 | 技术意义 |
|---|---|---|---|
| AAAK 压缩 | dialect.py |
实验性 | 为大规模 token 压缩做预研 |
| Benchmark 管线 | longmemeval_bench.py |
高 | 把性能叙事落到可执行脚本 |
| Palace Graph | palace_graph.py |
中 | 基于 metadata 的轻量图遍历 |
| Knowledge Graph | knowledge_graph.py |
中 | 时序实体事实层 |
| MCP Server | mcp_server.py |
高 | 面向 Agent 的操作系统接口 |
palace_graph.py 是一个很聪明的设计。它没有引入 Neo4j 或 NetworkX 之类的新依赖,而是直接从 collection metadata 扫描 room / wing / hall / date,动态构建房间图。build_graph() 会统计每个 room 关联到哪些 wings、出现过哪些 halls、出现次数和日期;若一个 room 横跨多个 wing,就形成 tunnel edge。traverse() 通过广度优先搜索从起始 room 出发,查找所有共享 wing 的相邻 room;find_tunnels() 则专门找连接多个 wing 的主题桥梁。这不是复杂图算法,但非常符合“宫殿”比喻,也证明 wing/room 命名体系在架构上确实能支持更多高层能力。
Knowledge Graph 则走了另一条路。虽然当前上下文中没有完整重读它的前半段源码,但从已读部分以及 MCP 接口可以确认,它通过本地 SQLite 存储实体与三元组,并支持 query_entity、invalidate、timeline、stats、seed_from_entity_facts 等操作,用 valid_from / valid_to 表示事实时效性。这说明 MemPalace 明确认识到:向量检索适合找语料片段,但不适合维护“当前仍然为真”的结构化事实。 因此它将 KG 与 Chroma 解耦,分别承担检索与事实两类职责。
mcp_server.py 则是整个项目通向 agent 生态的桥梁。它把状态查询、taxonomy、semantic search、重复检测、drawer 增删、KG 查询、KG 写入、graph traversal、diary 读写等能力统一注册到 TOOLS 字典中。 换言之,MCP server 让 MemPalace 从“人手执行 CLI”升级为“AI 可直接调用的记忆操作系统”。值得强调的是,MCP 工具接口本身并不重新实现业务逻辑,而是大量复用 search_memories()、KnowledgeGraph、palace_graph 等已有模块。这说明其架构是健康的:接口层薄,能力层厚。
从平台化视角看,这些扩展子系统共同证明了一件事:MemPalace 的底层不是为“一个 demo”写的,而是有明显的产品分层意识。只是它目前的很多高级能力仍属于半成品但方向正确的状态。README 里关于 AAAK、contradiction detection 与 rerank 暴露度的修正,也恰好证明了这一点。
源码级优缺点评估、可扩展性判断与后续演进建议
如果站在架构评审的角度,我会把 MemPalace 评价为一个产品判断强于算法噱头、数据协议强于功能堆砌、可用性强于完备性的开源项目。它最大的优点不是每个模块都做得极深,而是主线非常清楚:统一导入、统一元数据、统一 collection、统一检索入口、统一 agent 接口。这使得仓库即便功能在增加,整体仍然没有失控。
第一大优势是系统边界清晰。项目文件导入、对话导入、检索、分层唤醒、MCP 暴露、图遍历、KG,各自都有独立模块和合理职责。尤其是 MemoryStack 与 mcp_server.py 这种 façade 层,让外部使用方式简单,但内部保留了可拆分性。 这意味着后续想替换底层存储、增强路由逻辑或接入不同检索模型,都不必推翻整体架构。
第二大优势是数据面统一。无论来源是项目文件还是聊天记录,最终都变成 drawers;无论上层调用来自 CLI、Layer 还是 MCP,最终都回到同一个 collection 或 KG。统一数据面是长期演化最关键的前提,因为它降低了跨功能协作的复杂度。许多开源 agent 项目失败的原因,正是每个新功能都偷偷引入一套自己的状态结构;MemPalace 目前基本避免了这个问题。
第三大优势是坦诚地把 benchmark 与实验能力分开。源码与 README 都明确把 raw mode 的成功与 AAAK 的实验性区分开来,并通过 benchmark 脚本把不同模式展开给读者看。这在技术社区里非常重要,因为它减少了“论文式叙述”和“代码实际能力”之间的落差。
但与此同时,它的局限也很鲜明。
第一,room 路由与通用抽取仍偏启发式。不论 detect_room() 还是 detect_convo_room(),都主要依赖目录、文件名与关键词命中,这决定了它在结构清晰的技术语料上表现较好,在跨域内容上则容易失真。
第二,增量更新机制不够精细。项目文件是否重复主要由 source_file 判断,而不是基于内容摘要、mtime 或块级 diff,因此大仓库长期使用时容易出现“需要整仓重挖”的粗放体验。
第三,高阶能力尚未形成闭环。KG、Graph、AAAK 已经存在,但它们和主检索路径之间更多是并列关系,而不是深度协同关系。
| 评估维度 | 优势 | 风险/短板 | 建议 |
|---|---|---|---|
| 架构 | 模块职责清晰,主线短 | 高级能力仍偏分散 | 增加统一服务层或 domain model |
| 导入 | 本地、稳健、依赖少 | chunking 与路由较粗 | 引入 AST / Markdown section 感知 |
| 检索 | 原文保真,高解释性 | 过度依赖 embedding 质量 | 增加 BM25 / rerank 插件化接口 |
| 对话记忆 | 多格式兼容强 | 多人语境与长回复切分有限 | 引入 conversation graph / speaker model |
| 可演化性 | 数据面统一 | metadata schema 仍相对隐式 | 正式化 schema 与迁移机制 |
| 平台能力 | MCP 接口完整 | KG/Graph 尚未深度联动 | 做跨系统 query planner |
从更长期的技术演进看,我认为 MemPalace 最值得投资的方向有四个。
第一,把 chunking 升级为结构感知型分块,例如代码按函数/类、Markdown 按标题层级、对话按主题漂移点切换。
第二,把检索层做成可插拔融合检索,不仅有 semantic,也有 keyword、time-aware、metadata prior 与可选 reranker。
第三,把 KG 与 vector search 的关系从并列提升为双层查询协同,例如先从 KG 校正实体,再回到原文 drawer 找证据。
第四,给 metadata schema 增加正式版本与迁移工具,避免未来高级功能越来越多时出现“字段大家都在写,但无人定义”的问题。
最后需要辩证看待“MemPalace 是否真的代表 AI memory 的方向”。如果把“AI memory”理解为模型内部参数级长期记忆,那么答案显然不是;它做的是外部记忆系统。如果把“AI memory”理解为在用户真实工作流中,让模型能够低成本访问过去的事实、决策与上下文,那么 MemPalace 恰恰命中了最现实也最重要的一层。源码给出的结论并不神秘:它不是通过更复杂的模型让 AI 记住,而是通过更清晰的数据协议让系统不再轻易忘记。这或许不浪漫,但很工程,也因此很有生命力。
附录一:关键模块调用关系总览
附录二:适合二次开发者的阅读顺序建议
对于希望二次开发 MemPalace 的工程师,最佳阅读顺序不是从 README 到处,而是先从 cli.py 建立入口视图,再看 miner.py 与 convo_miner.py 理解数据进入方式,然后阅读 searcher.py 与 layers.py 掌握召回方式,最后再补 mcp_server.py、palace_graph.py、knowledge_graph.py 和 dialect.py 理解扩展能力。这种顺序的好处在于,能先抓住系统主干,再看实验性枝叶,避免把图、KG、AAAK 误以为是当前版本的主路径。