☰
claude-mem:为Claude构建本地长期记忆系统的架构设计与实操指南
2026/10/28 21:31:08 网站建设 项目流程

1. 项目概述与核心定位

1.1 这个工具到底解决什么问题

claude-mem 是一个围绕 Claude 对话记忆持久化展开的开源项目。它的核心目标很直接:让 Claude 在不同会话之间记住你是谁、你在做什么、你之前交代过什么。用过 Claude 的人都知道,默认情况下每次新开一个对话窗口,模型对之前的交流一无所知,你得重新交代背景、重新贴代码、重新说明偏好。claude-mem 就是冲着这个痛点来的。

它的基本工作方式是在本地维护一套记忆存储层,把对话中的关键信息提取出来,压缩成结构化条目,然后在后续对话中按需注入到上下文里。你可以把它理解成一个外挂的“长期记忆模块”,不依赖模型本身的上下文窗口,而是用外部存储来扩展记忆容量。

适合谁来用?三类人最需要:一是每天高频使用 Claude 写代码的开发者,二是需要跨会话维护项目上下文的独立创作者,三是想研究对话记忆机制的技术爱好者。如果你只是偶尔问几个问题,这个工具的价值不大;但如果你把 Claude 当成日常生产力工具,记忆断裂就是每天都在折磨你的问题。

1.2 为什么不能只靠上下文窗口

很多人第一反应是:Claude 的上下文窗口已经很大了,直接把历史对话全塞进去不就行了?理论上可行,实际用起来有三个硬伤。

第一,成本问题。上下文窗口里的每一个 token 都要计费,你把过去十次对话全量塞进去,每次请求的输入 token 可能是几万甚至十几万,费用会迅速膨胀。第二,注意力稀释。上下文里塞的东西越多,模型对关键信息的注意力就越分散,该记住的反而记不住。第三,窗口有上限。再大的窗口也有填满的时候,一旦超出就得截断,截断就意味着丢信息。

claude-mem 的思路是反过来的:不追求把所有原始对话都保留,而是在对话过程中做信息抽取和压缩,只保留那些真正有价值的记忆条目。这就像人脑的工作方式——你不会记住每一次对话的每一个字,但你会记住关键结论、重要偏好和待办事项。

1.3 核心架构拆解

claude-mem 的架构可以分成四层来理解。

存储层负责持久化记忆数据,通常用本地文件系统或轻量数据库。选择本地存储而不是云端,主要是出于隐私和延迟的考虑——你的对话内容不应该离开你的机器。

提取层负责从对话中识别值得记住的信息。这一步是核心难点,因为不是所有内容都值得记住。项目通常采用规则匹配加模型判断的混合策略:规则匹配负责捕捉明显的信号(比如“记住”“以后都这样”“我的偏好是”),模型判断负责处理更隐晦的上下文。

压缩层把提取出来的原始信息压缩成简洁的记忆条目。压缩的目的是减少后续注入时的 token 消耗,同时提高信息密度。一个好的记忆条目应该是一句话能说清楚的事实或偏好,而不是一段模糊的描述。

注入层负责在合适的时机把相关记忆注入到当前对话的上下文中。这里的关键是相关性匹配——不是把所有记忆都塞进去,而是根据当前对话的主题,检索出最相关的几条。

2. 核心细节解析与实操要点

2.1 记忆条目的数据结构设计

记忆条目长什么样,直接决定了整个系统的可用性。设计得太简单,检索时区分度不够;设计得太复杂,提取和压缩的成本又太高。经过实际使用,我总结出一个比较平衡的结构:

{ "id": "mem_20250101_001", "type": "preference", "content": "用户偏好用 Python 而非 JavaScript 写脚本", "source": "session_20250101", "timestamp": 1735689600, "tags": ["language", "coding-style"], "confidence": 0.92, "access_count": 3, "last_accessed": 1735776000 }

几个字段值得单独说。type字段区分记忆类型,常见的有 preference(偏好)、fact(事实)、task(待办)、context(背景)。不同类型的记忆在注入时的优先级不同——偏好类记忆几乎每次都该注入,事实类记忆按相关性注入,待办类记忆在特定触发条件下注入。

confidence字段记录这条记忆的可信度。不是所有提取出来的信息都百分之百准确,模型可能会误判。给每条记忆打一个置信度分数,低置信度的记忆在注入时降权处理,可以避免错误记忆污染对话。

access_count和last_accessed用于记忆的衰减管理。长期不被访问的记忆应该逐渐降低优先级,甚至被清理掉。这模拟了人类记忆的遗忘机制,防止记忆库无限膨胀。

2.2 信息提取的触发策略

什么时候该提取记忆?如果每轮对话都提取,计算开销太大;如果提取频率太低,又会漏掉关键信息。我的经验是采用三层触发机制。

第一层是关键词触发。当对话中出现“记住”“以后”“总是”“永远”“我的偏好”“不要再”这类词时,立即触发提取。这层触发覆盖了大部分显式记忆需求,准确率很高。

第二层是话题切换触发。当对话从一个话题切换到另一个话题时,对刚结束的话题做一次总结性提取。这层触发捕捉的是那些没有显式说“记住”但实际有价值的信息。

第三层是会话结束触发。当用户关闭会话或明确表示结束时,对整个会话做一次全面扫描,提取遗漏的重要信息。这层是兜底机制,确保不遗漏。

注意:三层触发的优先级不同,第一层实时执行,第二层延迟执行(可以批量处理),第三层在会话结束时执行。不要把所有触发都做成实时的,否则会严重影响对话流畅度。

2.3 记忆注入的相关性匹配

注入环节最容易犯的错误是“全量注入”——把所有记忆都塞进上下文。这样做等于没有做记忆管理,只是把问题从上下文窗口转移到了记忆库。

正确的做法是基于相关性做检索。具体来说,可以用以下信号来计算相关性得分:

  • 语义相似度:当前对话的 embedding 与记忆条目的 embedding 之间的余弦相似度。这是最主要的信号。
  • 时间衰减:越近期的记忆相关性越高,但衰减不能太快,否则长期偏好会被忽略。
  • 访问频率:经常被访问的记忆说明它确实有用,应该获得更高的注入优先级。
  • 类型权重:偏好类记忆的基础权重高于事实类记忆。

最终得分可以用加权求和的方式计算:

score = 0.5 * semantic_similarity + 0.2 * time_decay_factor + 0.2 * access_frequency_norm + 0.1 * type_weight

权重系数可以根据实际使用情况调整。我自己的经验是语义相似度的权重不应该低于 0.4,否则会注入太多不相关的记忆。

2.4 存储格式的选择

claude-mem 的存储格式有几种常见选择,各有优劣。

存储方案优势劣势适用场景
JSON 文件简单直观,易于调试并发写入有风险,检索效率低记忆条目少于 500 条
SQLite支持复杂查询,单文件便携需要处理 schema 迁移记忆条目 500-10000 条
向量数据库语义检索效率高部署复杂,资源占用大记忆条目超过 10000 条
Markdown 文件人类可读,易于手动编辑结构化程度低偏好手动管理记忆的用户

我个人的建议是:起步阶段用 JSON 文件,够用且好调试;条目超过 500 条后迁移到 SQLite;除非记忆量真的很大,否则没必要上向量数据库。很多人一上来就搞向量数据库,结果发现维护成本远高于收益。

3. 实操过程与核心环节实现

3.1 环境准备与依赖安装

claude-mem 的运行环境要求不高,Python 3.9 以上即可。核心依赖包括:

pip install anthropic pip install numpy pip install sentence-transformers pip install sqlite-utils

anthropic是调用 Claude API 的官方 SDK,sentence-transformers用于生成文本 embedding,sqlite-utils简化 SQLite 操作。如果你打算用 JSON 文件存储,sqlite-utils可以暂时不装。

环境变量方面,需要设置ANTHROPIC_API_KEY。建议把它写在.env文件里,不要硬编码在代码中:

# .env ANTHROPIC_API_KEY=your_key_here CLAUDE_MEM_STORAGE_PATH=~/.claude-mem/memories.db CLAUDE_MEM_MAX_INJECT=5

CLAUDE_MEM_MAX_INJECT控制每次注入的最大记忆条数,默认 5 条。这个值不要设太大,3 到 7 之间比较合理。设太大等于没做筛选,设太小又可能漏掉关键记忆。

3.2 记忆提取模块的实现

提取模块的核心是一个函数,接收对话文本,输出记忆条目列表。实现思路如下:

import re from datetime import datetime TRIGGER_PATTERNS = [ r"记住[::]\s*(.+)", r"以后[都总].{0,3}(.+)", r"我的偏好是[::]?\s*(.+)", r"不要再(.+)", ] def extract_memories(conversation_text, session_id): memories = [] for pattern in TRIGGER_PATTERNS: matches = re.findall(pattern, conversation_text) for match in matches: memories.append({ "id": f"mem_{datetime.now().strftime('%Y%m%d%H%M%S')}", "type": classify_memory(match), "content": match.strip(), "source": session_id, "timestamp": int(datetime.now().timestamp()), "tags": extract_tags(match), "confidence": 0.9, "access_count": 0, "last_accessed": 0 }) return memories

这段代码只处理了显式触发的情况。实际使用中还需要处理隐式提取,也就是用模型来判断哪些内容值得记住。隐式提取的 prompt 可以这样设计:

以下是一段对话记录。请提取其中值得长期记住的信息, 包括用户的偏好、重要事实、待办事项。以 JSON 数组格式输出, 每条包含 content 和 type 两个字段。如果没有值得记住的信息, 输出空数组。 对话记录: {conversation_text}

提示:隐式提取不要每轮都做,建议每 5 到 10 轮做一次,或者只在话题切换时做。频繁调用模型做提取会显著增加成本。

3.3 记忆注入模块的实现

注入模块负责在对话开始前,检索相关记忆并拼接到 system prompt 中。核心逻辑:

def build_memory_context(current_query, storage, max_items=5): all_memories = storage.load_all() if not all_memories: return "" query_embedding = encode(current_query) scored = [] for mem in all_memories: mem_embedding = encode(mem["content"]) sim = cosine_similarity(query_embedding, mem_embedding) time_factor = compute_time_decay(mem["timestamp"]) freq_factor = min(mem["access_count"] / 10, 1.0) type_weight = TYPE_WEIGHTS.get(mem["type"], 0.5) score = (0.5 * sim + 0.2 * time_factor + 0.2 * freq_factor + 0.1 * type_weight) scored.append((score, mem)) scored.sort(key=lambda x: x[0], reverse=True) top_memories = [m for _, m in scored[:max_items]] context = "以下是关于用户的长期记忆信息,请在回答时参考:\n" for mem in top_memories: context += f"- [{mem['type']}] {mem['content']}\n" storage.increment_access(mem["id"]) return context

compute_time_decay函数控制时间衰减的速度。我用的公式是:

def compute_time_decay(timestamp, half_life_days=30): days_elapsed = (time.time() - timestamp) / 86400 return 0.5 ** (days_elapsed / half_life_days)

半衰期设为 30 天,意思是 30 天前的记忆权重降为一半。这个值可以根据你的使用习惯调整。如果你希望长期偏好被更稳定地保留,可以把半衰期设长一些,比如 90 天。

3.4 与 Claude API 的集成

把记忆模块接入 Claude API 调用流程,关键是在构造 messages 之前先构造好 system prompt:

def chat_with_memory(user_input, storage, conversation_history): memory_context = build_memory_context(user_input, storage) system_prompt = "你是一个有帮助的助手。" if memory_context: system_prompt += "\n\n" + memory_context messages = conversation_history + [ {"role": "user", "content": user_input} ] response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=system_prompt, messages=messages ) return response.content[0].text

这里有个细节值得注意:记忆上下文放在 system prompt 里,而不是混在 messages 里。原因是 system prompt 的优先级更高,模型会更认真地对待其中的信息。如果把记忆混在对话历史里,模型可能会把它当成普通对话内容,重视程度不够。

3.5 记忆库的维护与清理

记忆库用久了会膨胀,需要定期维护。维护策略包括三个操作:

去重:语义相似度超过 0.95 的两条记忆视为重复,保留时间更新、访问次数更多的那条。去重建议每周执行一次。

衰减清理:超过 180 天未被访问且置信度低于 0.5 的记忆,直接删除。这类记忆大概率是当初误提取的噪音。

合并:同一主题下的多条细碎记忆可以合并成一条更概括的记忆。比如“用户喜欢 Python”“用户不喜欢 Java”“用户用 VS Code”可以合并成“用户的技术栈偏好:Python + VS Code,不喜欢 Java”。合并操作建议手动触发,不要自动化,因为合并的质量很依赖判断。

def cleanup_memories(storage, similarity_threshold=0.95, max_age_days=180, min_confidence=0.5): memories = storage.load_all() to_delete = set() for i, m1 in enumerate(memories): if m1["id"] in to_delete: continue for m2 in memories[i+1:]: if m2["id"] in to_delete: continue sim = cosine_similarity( encode(m1["content"]), encode(m2["content"]) ) if sim > similarity_threshold: keep = m1 if m1["access_count"] >= m2["access_count"] else m2 drop = m2 if keep == m1 else m1 to_delete.add(drop["id"]) for m in memories: age_days = (time.time() - m["timestamp"]) / 86400 if age_days > max_age_days and m["confidence"] < min_confidence: to_delete.add(m["id"]) storage.delete_batch(list(to_delete)) return len(to_delete)

4. 常见问题与排查技巧实录

4.1 记忆注入后模型反而不听话了

这是最常见的问题之一。表现是:注入记忆之前模型回答正常,注入之后模型开始胡言乱语,或者过度关注某条不相关的记忆。

根本原因通常是注入的记忆与当前对话不相关,模型被误导了。排查步骤:先打印出本次注入的所有记忆条目,逐条检查是否与当前问题相关。如果发现不相关的记忆被注入了,说明相关性匹配的阈值太低,需要提高语义相似度的权重,或者降低max_items。

另一个可能的原因是记忆条目的表述有歧义。比如“用户不喜欢冗长的解释”这条记忆,模型可能理解成“用户不喜欢任何解释”,导致回答过于简略。解决办法是让记忆条目的表述尽量具体和明确,避免模糊词汇。

实操心得:我习惯在注入的记忆前面加一个限定语,比如“以下记忆仅供参考,如果与当前问题无关请忽略”。这句话能显著降低模型被不相关记忆带偏的概率。

4.2 记忆提取遗漏关键信息

有时候用户明确说了“记住这个”,但后续对话中模型并没有表现出记住了。排查思路:

首先检查触发正则是否匹配到了。很多遗漏是因为用户的表述不在预设的触发词列表里。比如用户说“这个很重要”,没有出现“记住”二字,正则就匹配不到。解决办法是扩充触发词列表,同时增加隐式提取的频率。

其次检查存储是否成功。有时候提取到了但写入失败,比如并发写入冲突或者磁盘权限问题。建议在提取和存储环节都加上日志,方便排查。

最后检查注入环节是否检索到了。即使存储成功,如果相关性得分太低,也可能不会被注入。可以临时把max_items调大,看看这条记忆是否出现在候选列表中。

4.3 记忆库膨胀导致检索变慢

当记忆条目超过几千条时,每次对话都做全量语义相似度计算会明显变慢。优化方案有三个层次:

第一层是加缓存。对 embedding 做缓存,避免重复计算。一条记忆的 embedding 只需要计算一次,之后存起来复用。

第二层是预筛选。先用关键词匹配做粗筛,只对粗筛结果做语义相似度计算。这样可以把计算量降低一个数量级。

第三层是分层检索。把记忆按类型分成几个子库,检索时先确定当前对话需要哪类记忆,只在该子库内检索。比如当前对话是技术讨论,就只在 fact 和 preference 子库里检索,跳过 task 子库。

问题现象可能原因排查方法解决方案
模型不记得之前说过的偏好提取遗漏或注入未命中检查提取日志和注入日志扩充触发词,提高注入权重
模型被不相关记忆带偏相关性阈值过低打印注入的记忆列表提高语义相似度权重,降低 max_items
检索速度明显变慢记忆库膨胀统计记忆条目数量加缓存、预筛选、分层检索
记忆内容前后矛盾去重和合并没做好搜索相似记忆条目定期执行去重和合并
记忆写入失败并发冲突或权限问题检查错误日志加写入锁,检查文件权限

4.4 记忆的隐私与安全考量

claude-mem 存储的是你的对话内容,其中可能包含敏感信息。几个基本的安全实践:

存储文件权限设为仅当前用户可读写(chmod 600)。不要把记忆库放在云同步目录里,除非你确认同步服务是加密的。定期审查记忆内容,删除那些不再需要保留的敏感条目。如果多人共用一台机器,考虑对记忆库做加密存储。

另外要注意的是,注入记忆时这些内容会发送给模型 API。虽然 API 调用本身是加密的,但你要清楚哪些信息被发送出去了。如果某条记忆包含你不希望发送给 API 的内容,应该在注入前过滤掉。

4.5 与其他记忆方案的对比

市面上做对话记忆的方案不止 claude-mem 一种,选型时可以做個对比:

方案记忆粒度存储位置检索方式适用场景
claude-mem条目级本地语义+规则个人开发者日常使用
上下文窗口全量内存无检索短对话,临时使用
RAG 检索增强文档级向量库语义检索知识库问答
微调参数级模型权重隐式长期固定领域

claude-mem 的定位介于上下文窗口和 RAG 之间。它比上下文窗口更节省 token,比 RAG 更轻量。如果你需要的是知识库级别的记忆,RAG 更合适;如果你需要的是个性化的对话记忆,claude-mem 更对路。

4.6 实际使用中的几个坑

第一个坑是过度提取。刚开始用的时候容易兴奋,觉得什么信息都值得记住,结果记忆库里塞满了“用户今天问了天气”“用户说谢谢”这类无价值条目。这些噪音会稀释真正有用的记忆。解决办法是提高提取的置信度阈值,只保留置信度 0.8 以上的条目。

第二个坑是忘记更新。记忆不是一成不变的,用户的偏好会变。如果一条记忆说“用户喜欢用 React”,但用户后来转到了 Vue,这条旧记忆就会造成困扰。解决办法是当检测到与现有记忆矛盾的新信息时,降低旧记忆的置信度或直接标记为过期。

第三个坑是注入时机不对。有些实现是在每轮对话都注入记忆,这会导致模型每轮都被记忆内容干扰。更好的做法是只在会话开始时注入一次,或者在话题发生明显切换时重新注入。这样既保证了记忆的可用性,又减少了对对话流畅度的干扰。

第四个坑是忽略了记忆的时效性。有些记忆是有有效期的,比如“用户下周要交一个项目”。过了下周这条记忆就没用了,但如果不做时效管理,它会一直留在记忆库里。解决办法是在提取时识别时间敏感的记忆,给它设置一个过期时间,到期自动清理。

4.7 性能优化的几个实用技巧

embedding 计算是性能瓶颈。如果每次检索都对所有记忆重新计算 embedding,速度会很慢。解决办法是把 embedding 缓存到存储里,只在记忆内容更新时重新计算。

批量处理比逐条处理快得多。提取和注入环节都尽量做批量操作,减少 API 调用次数和数据库查询次数。

异步写入可以避免阻塞主流程。记忆的提取和存储不需要实时完成,可以放到后台队列里异步执行。这样用户感知到的对话延迟不会因为记忆操作而增加。

定期做索引重建。如果用的是 SQLite,定期执行VACUUM和ANALYZE可以保持查询性能。如果用的是向量索引,定期重建索引可以避免索引碎片化导致的检索变慢。

我在实际使用中最大的体会是:记忆系统的价值不在于记住多少,而在于记住的东西是否在正确的时机被正确地使用。一个只有 50 条高质量记忆的系统,效果远好于一个有 5000 条噪音记忆的系统。与其追求记忆的数量,不如把精力花在提取质量和检索精度上。另外,记忆系统需要定期“体检”,我一般每个月会花十分钟浏览一遍记忆库,手动清理明显过时或错误的条目,这个习惯让我的记忆库始终保持在一个健康的状态。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询