FEATURED · 精选文章

MemPalace Closet 索引层:从逐字 Drawer 到紧凑指针的内存检索架构

发布时间 / 2026/9/7 4:56:28
来源 / 创域科博编辑部
栏目 / 资讯中心
MemPalace Closet 索引层:从逐字 Drawer 到紧凑指针的内存检索架构 MemPalace Closet 索引层从逐字 Drawer 到紧凑指针的内存检索架构【免费下载链接】mempalaceThe best-benchmarked open-source AI memory system. And its free.项目地址: https://gitcode.com/GitHub_Trending/me/mempalace本文解析 MemPalace 中 Closet壁橱这一可搜索索引层的设计与实现Closet 是如何在mempalace mine期间从逐字内容中抽取主题、实体与引文生成的指针行的字段格式与打包规则如何约束其行为以及搜索器如何利用 Closet 命中的 rank 信号来增强 Drawer 直接检索。读完本文你将理解 MemPalace 抽屉存原文、壁橱存指针 的双层存储模型、 Closet 的完整生命周期创建、清除、重建并能对照源码定位 palace.py、miner.py、searcher.py 中的关键函数。Closet 是什么指针索引层而非内容副本MemPalace 的记忆存储分为两层Drawer抽屉保存逐字verbatim切块后的原始内容是检索的落地页Closet壁橱索引层是一组紧凑的指针告诉检索器该打开哪些抽屉。docs/CLOSETS.md 给出的示例格式如下CLOSET: built auth system|Ben;Igor|→drawer_api_auth_a1b2c3 ↑ topic ↑ entities ↑ points to this drawer一次 who built the auth? 的查询会先命中 Closet对短文本做快速扫描再顺着→drawer_id指针打开对应 Drawer拿到完整逐字内容。这套设计的核心收益是向量检索在小型、高密度的索引文档上进行而不是在大量逐字块之间盲目扫描。从源码结构看Closet 集合与 Drawer 集合是并列的两个 ChromaDB collectionpalace.py 中的get_closets_collection()通过get_collection()获取名为mempalace_closets的集合默认后端为 Chroma也可按配置切换到其他后端。Closet 的生命周期何时创建、何时更新创建时机mempalace mine期间Closet 在mempalace mine挖掘每个文件时创建流程为内容被切分为 Drawer逐字每块约 800 字符从内容中抽取主题topics、实体entities与引文quotes生成指向这些 Drawer 的指针行打包写入 Closet。这一链路在 miner.py 的process_file()中可以看到完整实现顺序是upsert 完所有 drawer 分块 → purge_file_closets(closets_col, source_file) # 无条件清除该源文件旧 closet → build_closet_lines(source_file, drawer_ids, content, wing, room, drawer_metasall_metas) → upsert_closet_lines(closets_col, closet_id_base, closet_lines, closet_meta)两个值得注意的实现细节Closet ID 是确定性的closet_id_base形如closet_{wing}_{room}_{sha256(source_file)[:24]}之后按_01、_02… 编号每次upsert对同一 ID 是整体覆盖而非追加。清除是无条件的即使这次 re-mine 一个 Drawer 都没写入内容过长被过滤等也必须先清除旧 Closet——否则旧指针会悬挂在已被删除的 Drawer ID 上源码注释中引用了该问题的 issue #24。Closet 内部结构每行一条原子主题指针Closet 文档中每一行都是一条完整的主题指针主题绝不会被拆散到两个 Closet 中。文档给出的两行示例topic description|entity1;entity2|→drawer_id_1,drawer_id_2 verbatim quote from the content|entity1|→drawer_id_3build_closet_lines()palace.py进一步揭示了三种指针行的生成规则且存在新旧两种格式格式字段说明旧版 3 段topic\|entities\|→drawer_ids向后兼容格式Tier 6a 4 段topic\|entities\|YYYY-MM-DD:Lstart-Lend\|→drawer_ids当 Drawer 元数据携带line_start/line_end与可解析日期时输出让检索可以直接跳转到文件中的行区间各字段的具体提取逻辑均在 palace.py 中实体第 2 段只统计出现 2 次及以上的专有名词最多取 5 个、按频率降序排列。候选词经由 i18n 感知的正则模式支持西里尔、带重音拉丁等非拉丁专名提取并经过两级清洗_ENTITY_STOPLISTpalace.py 中的句首词/填充词黑名单如 The、When、Monday与 COCA 常见英语实词过滤表mempalace/data/coca_content_words.json避免 Code、Line 这类高频普通词伪装成实体。此外还有已知系统复合词预扫描如 Claude Code、GitHub Copilot保证多词产品名被整体保留而不被拆散。主题第 1 段来源有两类——动作动词短语正则built|fixed|wrote|added|pushed|tested|created|decided|migrated|reviewed|deployed|configured|removed|updated后接 3–40 字符上下文与 Markdown 一二级标题^#{1,3}\s...去重后最多保留 12 条。引文正则匹配 15–150 字符的双引号文本最多保留 3 条。兜底若一条指针都没提取到则退化为wing/room/文件名形式的占位行保证每个文件至少有一条 Closet 行。Drawer 引用截断每条指针最多引用前 3 个 Drawer IDdrawer_ids[:3]——指针是近似定位器不是穷举索引。更新语义按源文件清除后整体重建当文件内容变化或NORMALIZE_VERSION版本推进触发 re-mine 时miner 先调用purge_file_closets()palace.py该函数执行closets_col.delete(where{source_file: source_file})——按source_file元数据删除而不是按 ID。这意味着旧的过时主题不会残留Closet 永远是当前内容的一份快照而非跨次运行的累积即使某次 re-mine 产生的 Closet 数量比上次少上次更大运行遗留的编号 Closet如_05也会被清除因为删除条件与 ID 无关。purge_file_closets与process_file()中的 Drawer 清除步骤是镜像设计二者保证索引层与内容层同步失效、同步重建。大小限制与打包规则docs/CLOSETS.md 列出的限制参数表设置值原因单个 Closet 最大尺寸1,500 字符CLOSET_CHAR_LIMIT在 ChromaDB 工作限制下留缓冲源内容扫描窗口5,000 字符CLOSET_EXTRACT_WINDOW约束长文件的正则抽取成本文件尾部内容目前对 Closet 抽取不可见已列入后续跟踪每文件最大主题数12保持 Closet 聚焦每文件最大引文数3只保留最相关的每指针最大实体数5停用表过滤后按频率取 Top对应源码常量在 palace.pyCLOSET_CHAR_LIMIT 1500 # 装满到 ~1500 字符后开新 Closet CLOSET_EXTRACT_WINDOW 5000 # 用于实体/主题扫描的源内容字符数upsert_closet_lines()palace.py实现贪心打包、绝不切行的写入策略逐行累加字符数当当前字符数 新行长度 1换行符 CLOSET_CHAR_LIMIT时先 flush 当前 Closet、递增编号再开始新 Closet。由于判断发生在写入之前任何一条完整行都不会被拆到两个 Closet 中。Closet 是否随 Palace 重建而消失Closet 存储在mempalace_closets集合中与mempalace_drawers并列。如果删除并重建整个 PalaceCloset 会在下一次mempalace mine时随 Drawer 一起重新生成——无需单独维护。搜索如何使用 Closet文档描述的检索流程是先索引、后落地Query → search mempalace_closets快速文档小 ↓ 取 top Closet 命中 → 解析 →drawer_id_a,drawer_id_b 指针 ↓ 从 mempalace_drawers 精确取这些 Drawer逐字内容 ↓ 应用 max_distance 过滤 ↓ 返回 chunk 级结果与直接搜索同构命中结果携带matched_via: closet回退路径为drawer以及closet_preview字段展示触发命中的那一行。若 Closet 尚不存在功能上线前创建的 Palace或所有 Closet 命中都被max_distance过滤掉则回退到直接的 Drawer 搜索Closet 会在下一次 mine 时补建。对照当前 searcher.py 的实现这套流程落地为两个关键函数_extract_drawer_ids_from_closet()用正则从 Closet 文档中解析所有→drawer_id_a,drawer_id_b指针保序且去重_closet_boosts()对 Closet 集合查询取2 × n_results条按source_file只保留每个源文件的最优命中构建 rank 提升表。值得注意的是当前实现的策略取向Drawer 直接查询是地板——始终执行——而 Closet 命中只作为基于排名的提升信号rank-based boost只帮助、不拦截。源码注释明确解释了动机对叙述性内容做正则抽取容易产生弱信号 Closet若采用硬性的 closet-first 路由反而会遮蔽直接搜索本可命中的 Drawer。因此当前命中的matched_via标记为drawer或drawercloset两者都命中时并附closet_boost与closet_preview字段排序采用 Closet 的序数信号而非原始余弦距离因为 Closet 距离无论匹配质量如何都聚集在较窄区间。对于支持词法搜索的后端如sqlite_exactCloset 查询改用 FTS 而非精确余弦扫描——毕竟 Closet 是指针行BM25 类信号更适合。文档同时指出完整的 BM25 混合重排被推迟到后续 PR与通用LLM_*环境变量支持一同规划。适用范围与边界只有项目 miner 构建 Closet当前仅miner.py::process_file项目文件挖掘路径会生成 Closet。对话挖掘的 wingClaude Code JSONL、ChatGPT 导出等仍走 searcher 的直接 Drawer 搜索回退路径直到 convo-closet PR 落地。diary_ingest.py 的模块注释也确认了主题按CLOSET_CHAR_LIMIT原子打包、绝不在主题中间拆分的同款打包语义。提取窗口的局限5,000 字符扫描窗口意味着超长文件的尾部内容目前不会进入 Closet 指针长文件仍可被直接 Drawer 搜索命中只是索引信号集中在文件前部。行为验证Closet 的构建、打包、清除与指针解析均有独立测试覆盖见 tests/test_closets.py修改build_closet_lines/upsert_closet_lines相关逻辑后可用它回归验证。开发者速查函数/常量位置职责get_closets_collection()palace.py#L340获取mempalace_closetsChromaDB 集合build_closet_lines()palace.py#L643从 Drawer 内容抽取主题/实体/引文输出指针行列表_build_date_line_segment()palace.py#L731生成 Tier 6a 的YYYY-MM-DD:Lstart-Lend定位段purge_file_closets()palace.py#L772按source_file删除某源文件的全部 Closetupsert_closet_lines()palace.py#L785按 1,500 字符限制贪心打包写入覆盖式需先 purgeprocess_file()中的 Closet 段miner.py#L1663mine 流程中的 purge → build → upsert 完整调用链_extract_drawer_ids_from_closet()searcher.py#L411从 Closet 文档解析 Drawer 指针_closet_boosts()searcher.py#L1914构建按源文件的 Closet rank 提升表理解上述八个入口就掌握了 MemPalace Closet 索引层的完整技术面写入侧由 miner 驱动、palace 模块提供打包与清除原语读取侧由 searcher 将 Closet 命中转化为对 Drawer 的精确取数与排名提升。【免费下载链接】mempalaceThe best-benchmarked open-source AI memory system. And its free.项目地址: https://gitcode.com/GitHub_Trending/me/mempalace创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻