scene9
7.50M · 2026-04-10
最近,一个名叫 MemPalace 的开源项目在 AI 开发者圈子里火了。它声称用"记忆宫殿"的方法给 AI 构建记忆系统,在行业标准基准测试中拿下了 96.6% 的零成本得分,甚至达到了 100% 的完美成绩 —— 超越了所有商业竞品。
项目 README 用精美的 ASCII 图展示了一座虚拟建筑:
WING(项目/人物)
└── HALL(记忆类型:决策/事件/偏好...)
└── ROOM(具体主题:auth-migration, graphql-switch...)
└── CLOSET(摘要索引)
└── DRAWER(原始文本)
文案写道:
听起来很酷,对吧?但当我深入代码后,发现了一些有趣的事实。
作为一名技术博主,我决定诚实地剖析:这个项目的真正技术创新是什么?"记忆宫殿"到底是核心技术,还是营销包装?
README 中有一张关键的性能对比表:
搜索所有内容: 60.9% R@10 召回率
搜索特定 wing: 73.1% (+12%)
搜索 wing + hall: 84.8% (+24%)
搜索 wing + room: 94.8% (+34%)
这个数据看起来很有说服力:通过 wing/hall/room 的层级结构,检索准确率提升了 34%!这似乎验证了"记忆宫殿"结构的价值。
但当我翻开 benchmark 代码时,画风突变了。
我找到了项目中得分最高的函数 build_palace_and_retrieve_hybrid_v4 —— 正是这个函数在 LongMemEval 基准测试中拿到了 100% 的完美成绩。
让我们看看它的核心逻辑:
def build_palace_and_retrieve_hybrid_v4(entry, ...):
"""
Hybrid V4: 三个定向修复达到 100%
Fix 1 - 引号短语精确匹配
Fix 2 - 人名权重增强
Fix 3 - 怀旧/回忆模式识别
"""
# 构建语料库
collection.add(
documents=corpus,
metadatas=[{"corpus_id": cid, "timestamp": ts}]
# 注意:只有 ID 和时间戳
# 没有 wing, hall, room 任何一个
)
# 检索优化策略
# 1. 关键词重叠加权
overlap = keyword_overlap(query_keywords, doc)
fused_dist = semantic_dist * (1.0 - 0.3 * overlap)
# 2. 时间邻近度加权
if temporal_distance < threshold:
fused_dist *= (1.0 - 0.4 * proximity_factor)
# 3. 引号短语精确匹配
if quoted_phrases:
fused_dist *= (1.0 - 0.6 * phrase_boost)
# 4. 人名权重增强
if person_names:
fused_dist *= (1.0 - 0.4 * name_boost)
# 5. 怀旧模式识别(合成文档)
# "I still remember..." → synthetic doc
整个函数从头到尾,没有使用 wing、hall、room 中的任何一个。
进一步挖掘 benchmark 结果,我发现了更关键的信息:
Score Progression:
| Mode | R@5 | LLM Required |
|------|-----|--------------|
| Raw ChromaDB | 96.6% | None |
| Hybrid v3 + rerank | 99.4% | Haiku |
| Palace + rerank | 99.4% | Haiku | ← 独立架构
| Hybrid v4 + rerank | 100% | Haiku | ← 最终胜出
原来 MemPalace 内部有两条平行的技术路线:
核心策略: 多层启发式规则 + 语义搜索
# 1. 关键词重叠 - 捕获专有名词
"Tell me about PostgreSQL" → 精确匹配 "PostgreSQL" 的文档得分更高
# 2. 时间加权 - 解决时间模糊问题
"10 days ago" → 计算目标日期,临近的会话降低距离
# 3. 偏好模式提取 - 弥补词汇鸿沟
"I usually prefer X" → 生成合成文档:"User has mentioned: preference for X"
# 4. 引号短语 - 精确匹配带引号的内容
"'sexual compulsions'" → 包含该短语的文档距离减少 60%
# 5. 人名加权 - 增强专有名词权重
"What did I do with Rachel?" → 包含 "Rachel" 的文档距离减少 40%
成绩:100% R@5 (500/500 问题)
核心策略: 话题分类 + 两阶段检索
# 1. 将会话分类到 5 个 hall
halls = ["travel", "work", "health", "relationships", "general"]
session_hall = classify_by_keywords(session_text)
# 2. 两阶段检索
# Pass 1: 在推测的 hall 内搜索
query_hall = detect_hall(question)
results = search(query, where={"hall": query_hall})
# Pass 2: 全局搜索 + hall 匹配加分
global_results = search(query)
rerank_with_hall_bonus(global_results, query_hall)
成绩:99.4% R@5 (比 hybrid 低 0.6%)
在另一个基准测试 LoCoMo 上的对比更明显:
LoCoMo R@10 结果:
Palace v2: 84.8%
Hybrid v5: 88.9% ← 胜出 4.1 个百分点
让我用一个具体例子说明:
查询: "What did Caroline research in college?"
Palace 模式的问题链条:
分类阶段 - 基于关键词将 Caroline 的会话分类
identity_sexuality hall检索阶段 - 查询"research in college"被分到 career_education hall
career_education hall 内搜索 → 找不到(Caroline 的会话在 identity_sexuality hall)career_education 加分 → 由于加分偏向其他人的会话,Caroline 的会话排名靠后结果: 召回失败
Hybrid 模式如何解决:
结果: 成功召回
根本问题:
看到这里你可能会问:既然 Palace 模式性能更低,为什么还要做这个结构?
答案是:它是用户界面层的组织功能,不是核心检索技术。
1. 多项目命名空间
# 场景:你同时维护 3 个项目
mempalace search "auth decision" --wing orion
# → 只在 Orion 项目的记忆中搜索
mempalace search "auth decision" --wing nova
# → 只在 Nova 项目的记忆中搜索
这避免了项目间的噪音干扰 —— 就像 Git 的分支,不是性能优化,是组织工具。
2. MCP 工具的友好接口
当你通过 Claude 的 MCP 协议使用 MemPalace 时:
User: "我们在 Driftwood 项目里关于认证迁移做了什么决策?"
Claude 调用:
mempalace_search(
query="auth migration decisions",
wing="driftwood",
room="auth-migration"
)
wing/room 提供了类似"文件路径"的用户心智模型 —— 易于理解和操作。
3. 跨团队协作时的语义标签
# 团队 A 的记忆
wing_team_a / hall_decisions / auth-migration
# 团队 B 的记忆
wing_team_b / hall_advice / auth-migration
# 同一个 room "auth-migration" 跨 wing 连接
# 这是"tunnel"的概念 - 跨团队的话题连接
这在多人协作场景下提供了语义上的连接,但底层仍然是 metadata 过滤:
# 实际实现非常简单
collection.query(
query_texts=["auth decisions"],
where={"wing": "driftwood", "room": "auth-migration"}
)
回到核心问题:MemPalace 的真正技术价值是什么?
答案是:一套精心设计的、数据驱动的混合召回策略。让我详细拆解每一层优化是如何工作的。
| 阶段 | 策略 | 得分 | 提升 | 解决的失败模式 |
|---|---|---|---|---|
| 基线 | 纯向量检索 | 96.6% | - | - |
| +关键词重叠 | 专有名词精确匹配加分 | 97.8% | +1.2% | "Dr. Chen" vs "my doctor" |
| +时间加权 | 时间距离计算 | 98.4% | +0.6% | "10 days ago" 的模糊性 |
| +偏好提取 | 16 种正则 + 合成文档 | 99.4% | +1.0% | "prefer" vs "usually use" |
| +定向修复 | 引号/人名/怀旧模式 | 100% | +0.6% | 最后 3 个顽固失败案例 |
每一步都基于真实失败案例的分析 —— 这是工程严谨性的体现。
纯向量检索在处理专有名词时权重不足。
失败案例:
查询: "Tell me about Dr. Chen"
文档 A: "I talked with my doctor about..." (语义相似度 0.85)
文档 B: "Dr. Chen suggested..." (语义相似度 0.78)
纯向量检索: A 排第一
期望结果: B 应该排第一
def keyword_overlap(query_keywords, doc_text):
"""计算查询关键词在文档中的命中率"""
# 1. 提取关键词(过滤停用词)
STOP_WORDS = {"what", "when", "the", "a", "is", "are", ...}
keywords = [w for w in query.lower().split()
if w not in STOP_WORDS and len(w) >= 3]
# 2. 计算命中率
doc_lower = doc_text.lower()
hits = sum(1 for kw in keywords if kw in doc_lower)
return hits / len(keywords) if keywords else 0.0
# 3. 融合评分(降低距离 = 提高排名)
overlap = keyword_overlap(query_keywords, doc)
fused_distance = semantic_distance * (1.0 - 0.3 * overlap)
# ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
# 向量距离 关键词加成(最多 30%)
权重选择的权衡:
实验结果:
权重 10%: 97.2% R@5
权重 20%: 97.6% R@5
权重 30%: 97.8% R@5 ← 最优
权重 40%: 97.5% R@5(过高,伤害了语义召回)
修复的典型问题:
+1.2% 提升主要来自:
很多查询包含时间约束,但多个会话可能语义相似。
失败案例:
查询: "What did I do with Rachel 10 days ago?"
会话 A: 2025-11-03(10 天前),提到 Rachel,语义相似 0.80
会话 B: 2024-11-03(一年前),提到 Rachel,语义相似 0.80
纯语义: AB 得分相同
期望: A 应该明显更高
def parse_time_offset_days(question):
"""从问题中提取时间偏移量"""
patterns = [
(r"(d+)s+days?s+ago", lambda m: (int(m.group(1)), 2)),
(r"yesterday", lambda m: (1, 1)),
(r"as+weeks+ago", lambda m: (7, 3)),
(r"(d+)s+weeks?s+ago", lambda m: (int(m.group(1)) * 7, 5)),
(r"lasts+month", lambda m: (30, 7)),
(r"recently", lambda m: (14, 14)),
# 返回:(天数, 容差窗口)
]
for pattern, extractor in patterns:
m = re.search(pattern, question.lower())
if m:
return extractor(m)
return None
def apply_temporal_boost(fused_dist, session_date, target_date, tolerance):
"""应用时间邻近度加权"""
temporal_distance = abs((session_date - target_date).days)
if temporal_distance <= tolerance:
# 在容差窗口内:线性加成
proximity_factor = 1.0 - (temporal_distance / tolerance)
fused_dist *= (1.0 - 0.4 * proximity_factor)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# 最多 40% 的距离减少
return fused_dist
为什么需要容差(tolerance)?
查询: "a week ago" (7 days)
目标日期: 2025-11-06
情况 1: 会话日期 2025-11-06 → 精确匹配,40% 加成
情况 2: 会话日期 2025-11-08 → 偏移 2 天,26% 加成
情况 3: 会话日期 2025-11-10 → 偏移 4 天,0% 加成(超出容差 3 天)
容差窗口设计原则:
这反映了人类对时间的模糊记忆特性。
修复的典型问题:
真实案例:
问题 ID: 4dfccbf8
查询: "What did I do with Rachel on the Wednesday two months ago?"
失败原因: 多个会话提到 Rachel,语义得分相似
修复方法: 时间加权让正确日期的会话排到第 1
人们表达偏好的方式多样,查询和陈述存在词汇鸿沟。
失败案例:
查询: "What database does the user prefer?"
对话: "I find Postgres more reliable in my experience"
词汇不匹配:
- 查询: prefer (动词)
- 对话: find...reliable (形容词短语)
向量相似度: 0.65(不够高)
PREFERENCE_PATTERNS = [
# 习惯模式
(r"I (?:usually|always|typically|generally|tend to) ([^.!?]{5,80})", "habit"),
(r"I'm (?:used to|accustomed to) ([^.!?]{5,80})", "habit"),
# 积极偏好
(r"I (?:prefer|like|love|enjoy|favor) ([^.!?]{5,80})", "positive_pref"),
(r"I'm (?:a fan of|into|passionate about) ([^.!?]{5,80})", "positive_pref"),
# 消极偏好
(r"I (?:don't like|dislike|avoid|hate|can't stand) ([^.!?]{5,80})", "negative_pref"),
(r"I'm not (?:a fan of|into|fond of) ([^.!?]{5,80})", "negative_pref"),
# 比较偏好
(r"I find ([^.!?]{5,80}) (?:better|more|superior|preferable)", "comparative_pref"),
(r"([^.!?]{5,80}) works (?:best|better) for me", "comparative_pref"),
# 经验陈述
(r"In my experience, ([^.!?]{5,80})", "experience"),
(r"I've found that ([^.!?]{5,80})", "experience"),
# 怀旧模式(后加)
(r"I (?:still )?remember ([^.!?]{5,80})", "memory"),
(r"I used to ([^.!?]{5,80})", "past_habit"),
(r"when I was (?:in|at) ([^.!?]{5,80})", "past_context"),
(r"back (?:in|when) ([^.!?]{5,80})", "past_context"),
(r"growing up ([^.!?]{5,80})", "childhood"),
(r"as a (?:kid|child|teenager) ([^.!?]{5,80})", "childhood"),
]
def extract_preferences(session_text):
"""提取偏好并生成合成文档"""
preferences = []
for pattern, pref_type in PREFERENCE_PATTERNS:
for match in re.finditer(pattern, session_text, re.IGNORECASE):
content = match.group(1).strip()
# 生成合成文档
synthetic_doc = f"User has mentioned: {content}"
preferences.append(synthetic_doc)
return preferences
为什么不直接修改原文?
# 错误做法:替换原文
original = "I find Postgres more reliable in my experience"
modified = "User prefers Postgres"
collection.add(documents=[modified]) # 丢失了上下文
# 正确做法:原文 + 合成文档都保留
original = "I find Postgres more reliable in my experience"
synthetic = "User has mentioned: finds Postgres more reliable"
collection.add(documents=[original, synthetic],
metadatas=[
{"type": "verbatim", "session_id": "sess_123"},
{"type": "synthetic_preference", "parent": "sess_123"}
])
优势:
修复的典型问题:
真实案例:
问题 ID: ceb54acb (部分修复)
查询: "Would you enjoy attending a high school reunion?"
对话: "I still remember the happy high school experiences such as
being part of the debate team"
提取的合成文档:
"User has mentioned: positive high school experiences, debate team"
这让查询中的 "high school" 能够匹配到合成文档
前面三层优化后,还剩下 3 个问题在所有模式下都失败。团队对这 3 个问题做了定向分析和修复。
失败问题 ID: d6233ab6
查询: "Remind me what you suggested for 'sexual compulsions'?"
问题分析:
解决方案:
def extract_quoted_phrases(question):
"""提取引号内的短语(支持单引号和双引号)"""
phrases = []
for pattern in [r"'([^']{3,60})'", r'"([^"]{3,60})"']:
phrases.extend(re.findall(pattern, question))
return [p.strip() for p in phrases if len(p.strip()) >= 3]
def apply_quoted_phrase_boost(fused_dist, phrases, doc_text):
"""包含引号短语的文档获得强加成"""
if not phrases:
return fused_dist
doc_lower = doc_text.lower()
hits = sum(1 for phrase in phrases if phrase.lower() in doc_lower)
if hits > 0:
boost = 0.6 * (hits / len(phrases)) # 最多 60% 距离减少
fused_dist *= (1.0 - boost)
return fused_dist
为什么 60% 这么高?
引号表示"精确引用",是最强的信号。如果文档包含精确引用的内容,应该大幅提升排名。
效果: 目标文档从"未排名" → rank 1
失败问题 ID: 4dfccbf8
查询: "What did I do with Rachel on the Wednesday two months ago?"
问题分析:
解决方案:
NOT_NAMES = {
"What", "When", "Where", "Monday", "Tuesday", "January",
"February", "In", "On", "At", ... # 排除常见大写词
}
def extract_person_names(question):
"""提取可能的人名(大写开头的词)"""
words = re.findall(r"b[A-Z][a-z]{2,15}b", question)
return list(set(w for w in words if w not in NOT_NAMES))
def apply_person_name_boost(fused_dist, names, doc_text):
"""包含人名的文档获得加成"""
if not names:
return fused_dist
doc_lower = doc_text.lower()
hits = sum(1 for name in names if name.lower() in doc_lower)
if hits > 0:
boost = 0.4 * (hits / len(names)) # 最多 40% 距离减少
fused_dist *= (1.0 - boost)
return fused_dist
为什么是 40%?
人名是重要的实体,但不如引号短语那么强的信号。40% 在实验中效果最好。
效果: 目标文档从"未排名" → rank 2
失败问题 ID: ceb54acb
查询: "Would you enjoy attending a high school reunion?"
对话: "I still remember the happy high school experiences such as being part of the debate team and taking advanced placement courses."
问题分析:
解决方案: 扩展偏好模式,增加怀旧类模式
# 在 PREFERENCE_PATTERNS 基础上增加
NOSTALGIA_PATTERNS = [
(r"I (?:still )?remember ([^.!?]{5,80})", "memory"),
(r"I used to ([^.!?]{5,80})", "past_habit"),
(r"when I was (?:in|at|a) ([^.!?]{5,80})", "past_context"),
(r"back (?:in|when) ([^.!?]{5,80})", "past_context"),
(r"growing up ([^.!?]{5,80})", "childhood"),
]
# 生成更丰富的合成文档
"I still remember the happy high school experiences..."
→ synthetic: "User has mentioned: positive high school experiences,
debate team, advanced placement courses"
现在查询"high school reunion"能够匹配合成文档中的"high school experiences"。
效果: 目标文档从"未排名" → rank 3
综合所有优化,最终的召回评分公式为:
def hybrid_retrieval_v4(query, documents, metadatas):
# 1. 基础向量检索
semantic_distances = chromadb_query(query, documents)
# 2. 提取查询特征
query_keywords = extract_keywords(query)
quoted_phrases = extract_quoted_phrases(query)
person_names = extract_person_names(query)
time_offset = parse_time_offset_days(query)
# 3. 逐文档重新评分
for doc, semantic_dist, metadata in zip(documents, semantic_distances, metadatas):
fused_dist = semantic_dist
# 优化 1: 关键词重叠(30% 权重)
overlap = keyword_overlap(query_keywords, doc)
fused_dist *= (1.0 - 0.3 * overlap)
# 优化 2: 时间邻近度(40% 权重)
if time_offset:
fused_dist = apply_temporal_boost(
fused_dist, metadata['timestamp'],
query_date - timedelta(days=time_offset[0]),
tolerance=time_offset[1]
)
# 优化 4a: 引号短语(60% 权重)
fused_dist = apply_quoted_phrase_boost(fused_dist, quoted_phrases, doc)
# 优化 4b: 人名(40% 权重)
fused_dist = apply_person_name_boost(fused_dist, person_names, doc)
# 注: 优化 3 的偏好提取在索引时已生成合成文档
# 4. 按融合距离排序
return sorted(zip(documents, fused_dist), key=lambda x: x[1])
整个 AI 记忆领域有一个误区:过度关注如何"智能提取"信息。
传统方案的问题:
MemPalace 的答案极其简单:
# 存储:愚蠢的简单
collection.add(documents=[raw_conversation_text])
# 召回:精心调优的五层启发式
score = (
semantic_similarity
* (1 - 0.3 * keyword_overlap) # 优化 1
* (1 - 0.4 * temporal_proximity) # 优化 2
* (1 - 0.6 * quoted_phrase_match) # 优化 4a
* (1 - 0.4 * person_name_match) # 优化 4b
)
# 优化 3 的偏好提取通过合成文档实现
最终结果:
| 维度 | MemPalace (hybrid v4) | Mem0 | Mastra |
|---|---|---|---|
| 成本 | $0 | $19-249/月 | API 费用 |
| LongMemEval R@5 | 100% | ~85% | 94.87% |
| ConvoMem 准确率 | 92.9% | 30-45% | - |
| 信息完整性 | 100%(原文保留) | 损失(只保留提取的片段) | 损失 |
| 隐私 | 完全本地 | 数据发送到 API | 数据发送到 API |
关键对比 - Mem0 vs MemPalace on ConvoMem:
MemPalace 是 Mem0 的 2 倍以上准确率(92.9% vs 30-45%)。
为什么差距这么大?
Mem0 的提取方式:
原对话: "虽然 Postgres 启动慢一些,但考虑到我们的查询模式,
索引覆盖率更高,实际响应时间更好..."
Mem0 提取: {"user_preference": "Postgres", "context": "database"}
六个月后查询: "我们在性能优化时做了什么权衡?"
→ Mem0 找不到(因为"性能权衡"的细节被丢弃了)
MemPalace 的方式:
原对话: "虽然 Postgres 启动慢一些,但考虑到我们的查询模式,
索引覆盖率更高,实际响应时间更好..."
MemPalace 存储:
1. 原文(完整保留)
2. 合成文档: "User has mentioned: finds Postgres more reliable"
六个月后查询: "我们在性能优化时做了什么权衡?"
→ 关键词"性能"+"权衡"匹配到原文
→ 完整的上下文被召回
这就是"存储原文 + 优化召回"击败"智能提取"的核心原因。
现在让我们诚实地总结 MemPalace 这个项目:
这些让项目在 GitHub 上迅速走红,获得大量关注。这没有问题 —— 好的营销是必要的。
实际的技术栈:
原始文本 → ChromaDB 向量数据库
↓
混合召回策略(关键词 + 时间 + 模式识别)
↓
可选的 LLM 重排序
Palace 结构的真实定位:
诚实的描述应该是:
而不是:
我的观点:没有太大问题,但需要透明度。
项目在 GitHub Issue #43 中收到了社区的批评:
作者的反应值得赞赏 —— 他们发布了一份诚实的更新:
这是正确的态度。
MemPalace 的案例揭示了开源项目营销中的一个普遍现象:
好的做法:
需要避免:
会,但要明确使用场景:
适合使用的场景:
不适合的场景:
MemPalace 的案例给我们的启示是:
好的技术项目需要好的故事,但故事不应该遮蔽技术。
"记忆宫殿"是一个精彩的隐喻 —— 它帮助用户快速理解项目的用途。但当这个隐喻变成营销的核心,而真正的技术创新(混合召回策略)反而被淡化时,就容易产生误导。
理想的平衡是:
MemPalace 在第 4 点做得很好,前 3 点还有改进空间。
最终问题:MemPalace 真的和记忆宫殿有关系吗?
答案是:有,但不是你想的那样。
真正让它达到 100% 的,是那些看起来"笨拙"的关键词重叠、时间加权、正则表达式。
这些不性感的工程优化,才是技术的本质。
参考资料: