给Claude装上长期记忆:claude-mem的架构设计与工程实践

发布时间:2026/10/7 4:18:40
给Claude装上长期记忆:claude-mem的架构设计与工程实践 我最近一直在折腾 claude-mem一个给 Claude 对话加长期记忆的小工具。如果你用过 Claude 的 API一定有过这种感觉单次对话里它聪明得像博士关掉窗口再打开就是金鱼。claude-mem 要解决的就是这件事——把散落在多个会话里的关键信息自动攒起来下次开场时问一句“上次我们聊到哪了”它能立刻接上话。这篇文章我就把自己从零搭到实际使用的完整过程、踩过的坑和一些设计取舍摊开讲适合正在给 AI 应用做记忆层的开发者也适合只是好奇想抄作业的朋友。1. 整体设计为什么 Claude 需要一块“外挂记忆”Claude 本身是有上下文窗口的在对话里它能记住前面说的内容但这个记忆有两个硬伤一是会话级一旦会话结束、或者在 API 场景下每次独立请求状态就丢了二是长度有限几十页资料塞进去前面的内容就会被挤掉。做过聊天机器人的朋友都知道这种“金鱼式记忆”在真实业务里非常难受。用户昨天说过的偏好、项目的技术选型、几天前讨论过的 bug 原因第二天再问模型完全不记得。claude-mem 的思路不是去改模型的记忆能力而是给它在外面加一层持久化存储让“记得住”这件事不再依赖模型本身。1.1 记忆层到底放在哪里这里需要分清楚三个容易混淆的概念短期记忆、长期记忆和语义记忆。短期记忆就是我们每次请求里携带的对话历史放在 prompt 里随请求发送长期记忆是跨会话保存下来的事实、用户偏好、决策记录通常存进数据库语义记忆则是对已有信息的理解和关联比如“用户提到过喜欢简洁的回答风格所以后续回复要控制篇幅”。claude-mem 主要做后两层。它的核心结构非常简单每次对话结束后把这段对话的关键信息抽出来变成一条条结构化的记忆记录放进 SQLite下一次新会话开始时根据用户当前问题把最相关的旧记忆捞出来拼接成系统提示词和对话历史一起发给 Claude。整个过程对上层业务透明模型拿到的仍然是正常的 prompt只是提示词里多了一段“你之前了解过的背景”。1.2 方案选型为什么不是把全部历史直接塞回去最朴素的做法是把所有历史对话原封不动存下来下次提问时全部拼进 prompt。我在第一个版本就这么干过结果非常惨。一是 token 消耗巨大聊一天的内容可能几万字全塞进去既贵又容易触发窗口上限二是无效信息太多用户只是问一句“我们上周说的部署方案是哪套”模型却被几千条闲聊淹没回答质量反而下降。所以 claude-mem 选择了“摘要 检索”的组合平时不存原始对话全文而是在每次轮次结束后生成一个浓缩的结构化摘要到了使用阶段先基于当前问题做相关性检索只取最相关的若干条记忆注入。这样既控制住了 token 数量又保证了信息的精准度代价是多了一次检索的延迟但实际体验下来基本可以忽略。1.3 三类数据对应三种处理方式在设计存储时我把数据分成了三类分别用不同策略处理。第一类是用户偏好和基本事实比如“用户在某互联网公司做后端”“喜欢 Python 多于 Java”这类信息会常驻在系统提示里每次请求都带上第二类是具体项目的阶段性结论比如“订单服务的超时时间最后定成 3 秒”“数据库迁移用 Flyway”这类信息按时间衰减只在相关话题出现时检索出来第三类是原始对话的审计日志完整保留但默认不注入只有在调试或用户明确要求时才使用。这个分类让 claude-mem 不会像无头苍蝇一样什么都往 prompt 里塞也为后面的体积控制打下基础。数据类型示例处理策略注入时机用户偏好与事实偏好简洁回答、常用语言长期固定每次请求项目结论超时时间 3 秒检索注入相关话题出现时原始对话日志完整多轮对话存档不注入调试或明确需求时我建议读者在做类似记忆系统时先想清楚这三类数据分别落在哪里。很多人一开始把所有东西塞成一团后面检索范围、清理策略都很难做。2. 核心细节记忆存什么、怎么存、怎么用2.1 记忆表结构设计claude-mem 的存储层我用的是 SQLite没上专门的向量数据库原因很简单个人工具和中小型应用的数据量根本到不了需要 Milvus 或 Qdrant 的程度一个带头向量扩展的 SQLite 足够压住读写。表结构上我设计了四张表conversations 记录会话基本信息messages 按时间线保存每一轮的用户输入和助手输出memory_items 保存抽取出来的结构化记忆memory_tags 给记忆打标签。这里最关键的是 memory_items它的字段包括 id、conversation_id、content、category、importance、source_message_id、created_at、last_accessed_at 和 embedding。importance 是一个 1 到 5 的整数由 Claude 在生成记忆时顺便给出用于控制检索权重last_accessed_at 则用于定期清理长期不用的冷记忆。具体建表语句如下实测用 Python 的 sqlite3 标准库就能跑不需要额外 ORMCREATE TABLE conversations ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, started_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, conversation_id INTEGER REFERENCES conversations(id), role TEXT CHECK(role IN (user, assistant)), content TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE memory_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, conversation_id INTEGER REFERENCES conversations(id), content TEXT NOT NULL, category TEXT, importance INTEGER DEFAULT 3, source_message_id INTEGER REFERENCES messages(id), created_at TEXT DEFAULT CURRENT_TIMESTAMP, last_accessed_at TEXT, embedding BLOB ); CREATE TABLE memory_tags ( memory_id INTEGER REFERENCES memory_items(id), tag TEXT );这个结构是核心中的核心。刚开始我图省事把记忆直接存在 JSON 文件里结果一旦上了多会话并发读写各种覆盖和脏数据立刻出现。换成 SQLite 之后事务和索引都省心了而且数据存在单个文件里备份起来也方便。2.2 记忆抽取让 Claude 自己整理自己的记忆很多记忆系统靠正则或关键词提取关键信息效果一言难尽。claude-mem 直接利用 Claude 自身的语言理解能力每当一段对话结束我会把完整的对话内容交给模型让它按指定 JSON 格式输出该留下的记忆条目。这段提示词我调了很多次现在的版本长这样你是 claude-mem 的记忆抽取器。阅读下面的对话抽取值得长期保留的信息。要求 1. 只抽取明确陈述的事实、偏好、决策和待办不要主观推测。 2. 每条记忆控制在 40 字以内动词明确。 3. 无关的寒暄、重复内容不要抽取。 4. 输出 JSON 数组每项包含 text、category、importance。 category 取 user_fact、project_decision、task_todo、other 之一。 importance 为 1-5 整数5 表示下次对话必须知道。这里有个很重要的经验不要直接用聊天提示词让模型“记一下”而是给它一个独立的、输出格式严格的任务。我在前期经常遇到模型把对话里的废话也存成记忆或者把推理过程写成长篇大论原因就是任务边界不清晰。改成独立 prompt 后抽取的准确率明显提升而且 JSON 解析稳定了很多。2.3 检索与注入怎么在合适的时机想起合适的事记忆存进去只是开始真正决定体验的是怎么把它取出来。claude-mem 采用两阶段策略先按关键词和标签做粗筛把候选集限定在几百条以内再在候选集里计算 embedding 相似度取 top_k 条。为什么不用纯向量因为裸向量检索在数据量小的时候反而容易找偏比如用户问“上次说的超时时间”如果只靠语义相似度可能把“超时”相关的都拉出来但结合 SQL 里 LIKE 匹配“超时时间”这个标签候选质量会高很多。候选集缩小后再算相似度性能和精度都有保障。注入时机上我会把检索到的记忆放在 system prompt 的固定位置并且显式标记“以下是旧记忆如果与用户当前信息冲突以当前信息为准”。这样做的好处是避免记忆和当前对话发生冲突时模型被老信息带偏。实际测试中这个标记能显著降低“幻觉式引用”——模型一本正经地引用了一个以前的、其实已经被否定的方案。2.4 token 预算控制每次注入多少记忆我用三个参数控制max_recent_chars 控制在原始对话历史中最多携带多少字符max_memory_chars 控制检索到的旧记忆最多占多少字符max_total_chars 作为兜底上限。后面两个参数是配合动态调整的。在一个长会话中如果最近几轮已经提到某个话题我会减少旧记忆的配额避免重复信息占用空间。这套规则写成一个简单的预算计算函数在构造请求前调用比每次手工调 prompt 要稳定得多。max_memory_chars min(2000, max_total_chars - len(current_messages))当然这里的数字要按实际模型上下文窗口调整不要照搬。我一开始机械地把所有余量都塞给旧记忆结果模型连当前对话都处理不过来后来把旧记忆上限压到总窗口的 20% 左右效果反而最好。3. 实操过程与核心环节实现3.1 环境准备与项目初始化实操部分我默认你已经有一个可以正常调用 Claude API 的 Python 3.10 环境并且 ANTHROPIC_API_KEY 已经写进了环境变量。项目依赖尽量精简我最终只用了三个包anthropic 官方 SDK、sqlite-vec以及一个本地 embedding 模型。这里我不推荐一上来就把系统做成微服务先写成一个能在命令行里复现流程的脚本验证思路后再拆模块。初始化命令不多大概是这样mkdir claude-mem cd claude-mem python3 -m venv venv source venv/bin/activate pip install anthropic sqlite-vec安装好依赖后先建一个 config.py 统一管理参数包括模型名、窗口大小、记忆检索条数等。把这些参数集中放一个文件里很重要后面调试时不用到处翻代码。3.2 核心流程记录对话并生成记忆第一个核心函数是 handle_turn。它接收用户输入先从 SQLite 里检索相关记忆组装 system prompt然后调用 Claude API 得到回复最后把用户输入和模型回复写入 messages 表。这还没完我会在每一轮结束后把这一轮的文本丢给记忆抽取器把生成的结构化记忆写入 memory_items。刚开始我以为摘要应该在整段对话结束后做后来发现每一轮都即时抽取更合理因为很多关键信息在前面已经出现等到最后再抽很容易遗漏而且长对话的 token 消耗也更大。核心代码示意import sqlite3 import anthropic client anthropic.Anthropic() def handle_turn(user_input: str, conversation_id: int): memories retrieve_memories(user_input, top_k5) system build_system_prompt(memories) resp client.messages.create( modelclaude-3-5-sonnet-20241022, max_tokens1024, systemsystem, messages[{role: user, content: user_input}] ) assistant_text resp.content[0].text store_message(conversation_id, user, user_input) store_message(conversation_id, assistant, assistant_text) extract_and_store_memories(conversation_id) return assistant_text注意这里的 model 参数要按照你实际有权限的模型调整。我在开发时习惯把模型名也放到配置里避免每个函数都重复硬编码。如果后续用 batch 处理历史对话这一段代码的输入换成历史消息数组即可复用性很高。3.3 记忆检索实现的关键步骤retrieve_memories 函数内部可以做得很简单。我用的是两层过滤先按关键词粗筛再算 embedding 相似度。为了少一次 API 调用embedding 向量可以选择在记忆写入时就算好并缓存到 memory_items 表的 embedding 字段这样检索时只需要给当前用户问题算一次向量。以下是检索函数的骨架def retrieve_memories(query: str, top_k: int 5): query_embedding embed_text(query) rows db.execute( SELECT id, content, importance, embedding FROM memory_items ).fetchall() scored [] for row in rows: m_emb deserialize(row[embedding]) score cosine_similarity(query_embedding, m_emb) # 结合重要性和最后访问时间加权 score score * (0.6 0.1 * row[importance]) scored.append((score, row[content])) scored.sort(reverseTrue) return [s[1] for s in scored[:top_k]]这里有几个小细节similarity 用余弦相似度比较稳定importance 加权我控制了幅度只让重要性最高的记忆能稍微提升排名否则用户随口统计一次“我不喜欢红色”也能活很久。其实这一步可以在 SQL 里就近做一部分过滤比如只扫描最近 30 天的记忆能减少无谓计算。3.4 端到端测试模拟跨会话记忆全部代码写完我最先做的一个验证场景是在会话 A 中告诉 Claude“我在做一个日志平台日志保留期定成 30 天”然后结束会话。隔一段时间新开一个会话只问“日志平台的数据保留策略是多少”如果模型能答出 30 天说明记忆链路通了。第一次跑的时候模型答不上来原因是检索到的记忆里相关信息没有被正确抽取。后来排查发现是记忆抽取提示词里 category 只有 user_fact、project_decision、task_todo、other 四种而这条信息被归到 other检索时又没有把 other 类型全部纳入导致漏掉。调整检索条件后终于跑通。这一轮踩坑让我意识到实现跨会话记忆并不是把“存”和“取”做出来就结束中间的记忆类型规则、检索覆盖范围、注入优先级都需要逐项验证。不需要一开始就追求完美先跑通一条最简单的链路再逐步加入复杂功能比一次性搭巨系统要靠谱得多。4. 常见问题与排查技巧实录4.1 上下文超长请求被拒我遇到最多的问题是 400 错误提示 prompt 超出 token 上限。大多数时候不是模型窗口不够而是我的注入逻辑把旧记忆和对话历史同时塞满。解决办法分三步先开 debug 日志把每次请求的 token 数量打出来再调整 max_recent_chars 和 max_memory_chars最后把长对话自动做一次滚动摘要替换最早的部分。滚动摘要这一块我单独写了一个 summarize_old_messages 函数把超过窗口的部分先让模型压缩成几百字的背景说明再保留最近几轮的原文。这样长对话也能稳定续上。4.2 记忆混乱模型引用了过时或被推翻的信息这个问题在项目的第二个星期集中爆发明明用户后来改了决定模型还是拿旧记忆回答。核心原因有两个一是 memory_items 里没有“弃用”状态旧信息永远有效二是注入提示词里没说明以当前对话为准。我给 memory_items 表加了 status 字段支持 active/deprecated当新记忆和旧记忆冲突时在抽取阶段就把旧记忆标记为 deprecated并且在 system prompt 里明确写上“如果旧记忆与当前对话有冲突一律以当前对话为准”。改完之后这种问题基本消失。4.3 本地存储的隐私边界因为所有对话和记忆都落在本地 SQLite隐私安全要提前想好。我在字段层面做了两层处理第一层在代码中过滤明显敏感的输入比如密码、密钥、手机号不写入记忆第二层在写入前用 AES-GCM 对整个 memory_items 表做可选加密密钥存在系统 keychain 或环境变量里。对于单机个人工具这已经足够。如果以后要提供多人服务还需要考虑权限隔离、脱敏和审计这些就超出本文范围了但设计时一定要留出扩展位。4.4 调试时最有用的一招调试记忆系统最烦的就是 prompt 不可见。我后来把每次实际发送给 Claude 的 system prompt、检索到的记忆列表、以及 token 统计全部落盘到 debug_log.jsonl出了任何问题都能回放。这个习惯帮我省了大量排查时间。比如之前提到的检索遗漏就是打开 debug log 后发现检索到的记忆里根本没有 relevant 两条才顺藤摸瓜找到过滤逻辑的问题。强烈建议所有做类似工具的朋友都记一笔“现场快照”不要只在出错时打印堆栈。5. 把 claude-mem 再往前推一步5.1 从命令行工具到轻量服务我现在把 claude-mem 从单一脚本拆成了三层CLI 交互层、记忆服务层、存储层。CLI 层负责接收用户输入、展示回复记忆服务层封装了抽取、存储、检索、注入的完整流程对外提供 add_turn 和 query_with_memory 两个方法存储层仍然是 SQLite。拆层之后写单元测试方便了很多后续如果想接 Web 页面只需要在 CLI 层之外再套一层 HTTP API记忆服务层可以原封不动复用。不过对多数场景保持单一脚本反而更好维护拆层要等复杂度到了再动手。5.2 几个可以继续扩展的方向我下一步想给它加上定时任务式的“大扫除”每隔一段时间把多轮对话中重复出现的结论合并成综述同时清理长期未被访问的记忆让记忆库保持整洁。另一个想法是支持多 Profile把工作记忆和个人记忆分开避免两个语境互相污染。这些功能都不复杂但每一步都会让工具离“真正的 AI 助手”更近一点。如果你也在做类似的事我的建议是先跑起来再去想“完美”记忆系统最忌一开始就陷入完美设计因为真正有价值的判断标准只有一个一个新会话里它能不能在你需要的时候想起确实该想起的事。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询