claude-mem 记忆系统实战:三层架构与检索注入策略

发布时间:2026/10/9 21:19:48
claude-mem 记忆系统实战:三层架构与检索注入策略 1. 项目缘起与核心定位第一次看到claude-mem这个名字我的直觉是这大概率是一个围绕 Claude 生态做“记忆层”的项目。事实也确实如此。它要解决的是一个所有长期使用大模型的人都会遇到的痛点——模型没有持久记忆。每次开新会话之前聊过的项目背景、代码约定、个人偏好、踩过的坑全部归零你得从头再讲一遍。短对话还好一旦涉及跨天、跨周、跨项目的协作这种“失忆”带来的重复沟通成本高得离谱。claude-mem的核心价值就是给 Claude 这类对话式模型外挂一套可检索、可沉淀、可复用的记忆系统。它不是一个官方功能而是社区里一群被“重复解释”折磨久了的人自己动手攒出来的方案。适合谁来参考三类人最值得看一是每天跟 AI 结对写代码的开发者二是需要长期维护多个 AI 助手人设的内容创作者三是想在自己产品里嵌入“有记忆的对话能力”的独立开发者。哪怕你只是普通用户理解这套思路之后也能明显感觉到 AI 从“每次重新认识你”变成“记得你上次说过什么”。我先把结论摆在这里claude-mem这类项目的技术门槛没有想象中高真正难的是记忆的写入策略、检索策略和遗忘策略这三件事的平衡。下面我会从整体设计、核心细节、实操落地、问题排查四个维度把这套东西拆开讲透尽量让你看完就能自己搭一套。2. 整体设计与思路拆解2.1 为什么“记忆”不能只靠加长上下文很多人第一反应是现在上下文窗口都到 20 万 token 了直接把历史对话全塞进去不就行了我实测过这条路走不通原因有三个。第一成本。上下文越长每次请求的 token 消耗越大而且是线性甚至超线性增长。你不可能为了记住三个月前的一句偏好每次都把三个月的聊天记录全带上。第二注意力稀释。上下文里塞的东西越多模型对关键信息的注意力越分散。我做过对比同样一个问题干净上下文里回答准确率明显高于塞了一堆无关历史的情况。这就像你让一个人在一屋子杂物里找钥匙东西越多越难找。第三窗口终究有限。再大的窗口也有上限而人的记忆需求是无限的。靠堆上下文本质上是“用空间换时间”但空间也会满。所以claude-mem的思路必然是把记忆从上下文里剥离出来存到外部按需检索注入。这就是经典的 RAG检索增强生成思路在“个人记忆”场景下的应用。2.2 三层记忆架构的设计考量我参考社区里几个成熟实现结合自己的实践把记忆分成三层这个分层是整套系统的骨架。层级存储内容生命周期检索频率短期记忆当前会话的原始对话会话结束即归档每轮都带工作记忆当前项目的关键约定、进行中的任务项目周期内高频检索长期记忆跨项目的偏好、通用知识、历史决策永久低频但精准为什么要分三层因为不同信息的“保鲜期”和“调用频率”完全不同。当前会话的细节过了这个会话基本就没用了没必要长期存而“我习惯用 TypeScript 严格模式”这种偏好是跨项目长期有效的应该沉到最底层。如果不分层把所有东西混在一起存检索时噪音会非常大召回质量直线下降。这个分层逻辑其实借鉴了认知科学里人类记忆的模型感觉记忆、短期记忆、长期记忆。我们不是要造一个“什么都记得”的怪物而是要造一个“该记的记得住、该忘的忘得掉”的系统。2.3 存储选型为什么我最终选了向量库加结构化表存储层是claude-mem的地基。我试过三种方案最后定在“向量数据库 关系型数据库”的组合上。纯向量库的问题是它擅长语义相似度检索但不擅长精确条件过滤。比如你想查“上周三关于数据库选型的讨论”这是时间范围加主题的复合查询纯向量库做起来很别扭。纯关系型数据库的问题相反它能精确过滤但没法做语义检索你搜“数据库”搜不到“存储引擎”相关的记录。所以我的方案是结构化字段时间、项目、类型、标签存关系库文本内容的向量存向量库两边用同一个 ID 关联。检索时先用结构化条件缩小范围再在候选集里做向量相似度排序。这样既保证了精度又保证了语义召回。实测下来这种混合检索的命中率比单一方案高出不少。3. 核心细节解析与实操要点3.1 记忆写入什么该记什么不该记这是整套系统里最容易被忽视、却最影响效果的一环。很多人一上来就把所有对话全量写入结果记忆库迅速膨胀检索质量暴跌。我的经验是写入要有筛选宁缺毋滥。我总结了一个写入判断清单每次决定是否写入时过一遍是否是稳定的偏好或约定比如“这个项目用 pnpm 不用 npm”值得记。是否是可复用的决策比如“选 PostgreSQL 是因为需要 JSONB 支持”值得记。是否是一次性的临时信息比如“帮我把这行改成大写”不值得记。是否是已经过时的信息比如“当前分支是 feature-x”会话结束就失效不值得记。具体实现上我会在每轮对话结束后用一个轻量的抽取步骤让模型自己判断“这轮对话里有没有值得长期保留的信息”有的话输出结构化 JSON没有就返回空。这个抽取步骤的 prompt 我调了很多版关键是给它明确的判断标准和几个正反例否则它要么什么都记要么什么都不记。注意抽取步骤本身也要消耗 token所以不要每轮都跑。我的做法是每 5 轮或会话结束时批量跑一次成本可控效果也够用。3.2 记忆检索怎么在正确的时候捞出正确的记忆检索策略决定了记忆“用得上用不上”。我的做法是双通道检索一路是语义检索一路是关键词加结构化过滤两路结果合并去重后按综合分排序。语义检索负责“意思相近”比如你问“怎么处理并发”能召回“多线程同步方案”的记录。关键词检索负责“精确命中”比如你提到某个具体的函数名能精准定位到相关记录。两路结合既不会漏也不会偏。排序分数我用了加权公式最终分 0.6 × 语义相似度 0.3 × 时间新鲜度 0.1 × 使用频次。时间新鲜度用指数衰减越近的记忆权重越高使用频次是指这条记忆被召回后如果被用户确认有用就加一次计数下次更容易被召回。这个权重是我反复调出来的你可以根据自己的场景微调但核心思路是语义相关性为主新鲜度为辅热度做微调。检索出来的记忆不能全塞进上下文要控制数量。我的经验是每次注入 3 到 5 条最相关的超过这个数反而干扰模型。每条记忆注入时带上简短的时间戳和来源标签方便模型判断可信度。3.3 记忆遗忘会忘的系统才是好系统这一点很多人不做但它极其重要。记忆库如果只增不减半年后就是一堆垃圾。遗忘机制我分两种主动遗忘和被动衰减。主动遗忘是用户显式删除比如“忘掉我之前说的那个偏好”。这个必须有给用户控制权。被动衰减是系统自动的一条记忆如果长时间没被召回或者被召回了但用户从没确认过它的权重就慢慢降低低到阈值以下就归档或删除。我设的衰减周期是 90 天。超过 90 天没被任何一次检索命中的记忆进入“冷存储”不再参与常规检索但保留可手动找回。这样既控制了活跃记忆库的规模又不会真的丢东西。实操心得遗忘阈值不要设得太激进。我一开始设 30 天结果把一些季度性的项目约定误删了后来调到 90 天才合适。这个值跟你的使用频率强相关高频用户可以用短一点低频用户要长一点。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把基础环境搭起来。我用的是 Python 生态向量库选 Chroma轻量、本地跑、零配置关系库直接用 SQLite单文件、免运维嵌入模型用本地的 sentence-transformers避免依赖外部 API 带来的成本和延迟。python -m venv venv source venv/bin/activate pip install chromadb sqlite-utils sentence-transformers anthropic这里解释一下选型理由。Chroma 相比其他向量库最大的优势是嵌入式运行不需要单独起服务适合个人项目。SQLite 同理一个文件搞定备份就是复制文件。嵌入模型本地跑虽然比 API 慢一点但胜在免费且数据不出本地隐私性好。如果你追求速度可以换成 API 版嵌入但要注意成本和数据流向。4.2 数据库表结构设计关系库这边我建了三张表结构如下CREATE TABLE memories ( id TEXT PRIMARY KEY, content TEXT NOT NULL, memory_type TEXT, -- preference / decision / fact project TEXT, -- 所属项目标识 tags TEXT, -- 逗号分隔的标签 created_at INTEGER, last_accessed INTEGER, access_count INTEGER DEFAULT 0, status TEXT DEFAULT active -- active / cold / deleted ); CREATE TABLE sessions ( id TEXT PRIMARY KEY, project TEXT, started_at INTEGER, ended_at INTEGER ); CREATE TABLE memory_links ( memory_id TEXT, related_id TEXT, relation TEXT );memories是核心表memory_type区分记忆类型status支持软删除和冷存储。memory_links用来存记忆之间的关联比如“决策 A 依赖事实 B”检索时可以做关联扩展。这个关联表是我后来加的一开始觉得没必要后来发现跨记忆的推理很需要它。4.3 写入流程的完整实现写入流程分四步抽取、去重、嵌入、落库。抽取用模型判断去重用向量相似度比对相似度超过 0.95 视为重复更新旧记录而不是新增嵌入生成向量最后同时写入 SQLite 和 Chroma。def write_memory(raw_text, project, session_id): # 第一步抽取值得记的信息 extracted extract_memories(raw_text) if not extracted: return [] written [] for mem in extracted: # 第二步去重检查 vec embed(mem[content]) similar chroma.query(vec, n_results1) if similar and similar[distance][0] 0.05: update_existing(similar[id], mem) continue # 第三步和第四步嵌入并落库 mem_id generate_id() sqlite.insert(memories, { id: mem_id, content: mem[content], memory_type: mem[type], project: project, tags: ,.join(mem.get(tags, [])), created_at: now(), last_accessed: now(), }) chroma.add(mem_id, vec, metadata{project: project}) written.append(mem_id) return written去重这一步很关键。我踩过的坑是同一件事在不同会话里被反复提及如果不做去重记忆库里会有十几条几乎一样的记录检索时全被召回白白占上下文。加了去重之后重复提及会更新旧记录的last_accessed和access_count反而强化了这条记忆的权重一举两得。4.4 检索注入的完整实现检索注入是每次对话开始时跑的。先拿当前用户输入做嵌入然后双通道检索合并排序取前 N 条注入到系统提示里。def retrieve_and_inject(user_input, project, top_k5): vec embed(user_input) # 通道一语义检索 semantic chroma.query(vec, n_results20, where{project: project}) # 通道二关键词加结构化过滤 keywords extract_keywords(user_input) keyword_hits sqlite.query( SELECT * FROM memories WHERE project? AND statusactive AND (content LIKE ? OR tags LIKE ?), [project, f%{keywords}%, f%{keywords}%] ) # 合并去重 candidates merge_dedup(semantic, keyword_hits) # 综合排序 scored [] for c in candidates: score (0.6 * c[similarity] 0.3 * freshness(c[last_accessed]) 0.1 * min(c[access_count] / 10, 1.0)) scored.append((score, c)) scored.sort(reverseTrue) # 注入 top [c for _, c in scored[:top_k]] for m in top: sqlite.update(memories, m[id], {last_accessed: now(), access_count: m[access_count] 1}) return format_for_prompt(top)freshness函数用指数衰减exp(-days_since_access / 30)30 天衰减到约 0.3790 天衰减到约 0.05。这个参数可以根据你的记忆更新频率调整。4.5 与 Claude 的对接方式对接层我做了两种模式。一种是系统提示注入把检索到的记忆格式化成一段文字放在 system prompt 里。这种方式简单直接适合大多数场景。另一种是工具调用模式把记忆检索做成一个 tool让模型自己决定什么时候查、查什么。后者更灵活但需要模型有较强的工具使用能力且会增加一轮交互延迟。我日常用第一种因为延迟低、可控。只有在处理复杂任务、需要模型主动回忆时才切到第二种。两种模式的代码结构可以复用同一套检索逻辑只是触发时机不同。5. 常见问题与排查技巧实录5.1 记忆召回不准的排查路径召回不准是最常见的问题表现是“明明记过但就是捞不出来”。我整理了一个排查顺序按这个顺序走基本能定位。现象可能原因排查方法解决完全召不回写入失败或项目标识不匹配直接查 SQLite 看记录在不在检查 project 字段一致性召回了但排序靠后相似度计算有问题打印候选集和分数调整嵌入模型或权重召回的是旧版本去重逻辑没生效查同内容记录数修复去重阈值召回一堆无关的阈值太低看最低分候选提高相似度门槛我遇到最多的是“项目标识不匹配”。因为我在不同机器上跑project 字段有时用绝对路径有时用相对路径导致检索时过滤条件对不上。后来统一用项目根目录的哈希值做标识问题就没了。5.2 上下文被记忆挤爆的处理注入的记忆太多会把真正重要的当前对话挤掉。我的经验是硬性限制注入条数和总 token 数。条数上限 5 条总 token 上限 800。超过就按分数砍。这个上限不是拍脑袋定的是我观察了很多次对话后得出的超过 800 token 的记忆注入模型对当前问题的关注度明显下降。如果确实需要更多记忆不要一次性全注入而是分轮次。第一轮注入最相关的 3 条如果模型表示信息不足再触发第二轮检索。这种“按需加载”比“一次塞满”效果好得多。5.3 记忆冲突的处理策略同一个问题不同时间记了互相矛盾的记忆怎么办比如三个月前记了“用 Jest 测试”上个月改成了“用 Vitest”。我的处理是时间优先加显式覆盖。检索时如果发现同一主题有多条记忆保留最新的旧的标记为 superseded。同时如果用户显式说“以后都用 Vitest”就主动把旧的 Jest 记忆归档。这个逻辑需要在写入时做主题识别。我给每条记忆打一个topic标签同一 topic 下只保留最新的 active 记录。topic 的粒度要适中太细了冲突检测不到太粗了会误伤。我的经验是按“技术选型”“代码风格”“项目约定”这种粒度来分。5.4 性能优化的几个实操点记忆系统跑久了会变慢主要是检索环节。我做了三个优化。第一向量索引定期重建Chroma 的索引在大量增删后会碎片化我每周重建一次。第二冷记忆分离把 90 天没访问的记忆移到单独的 collection常规检索不扫它。第三嵌入缓存同样的文本不重复计算嵌入用内容哈希做 key。这三个优化做完检索延迟从最初的 800 毫秒降到了 150 毫秒左右日常使用基本无感。5.5 隐私与数据安全的注意事项记忆库里存的是你的偏好、决策、项目细节这些数据敏感度不低。我的做法是全本地存储嵌入模型本地跑向量库和关系库都在本地磁盘不经过任何外部服务。备份用加密压缩包存到自己的移动硬盘。如果你要用云同步务必先加密再上传。另外记忆抽取的 prompt 里不要包含真实的密钥、密码、个人信息。我在抽取前会跑一遍脱敏把疑似密钥的字符串替换成占位符。这个步骤不能省否则记忆库就成了泄密重灾区。6. 扩展方向与个人实践体会这套系统跑了大半年我最大的体会是记忆系统的价值不在于“记得多”而在于“记得准”。一开始我追求全量记录结果检索质量一塌糊涂。后来做减法只记真正稳定的偏好和决策效果反而好了很多。现在我的记忆库里长期活跃的记录也就两三百条但每一条都是精华召回准确率很高。后续我打算扩展两个方向。一是记忆的自动归纳把多条相关的零散记忆合并成一条更高层的总结减少冗余。二是跨设备同步目前是单机换机器要手动拷不够方便。归纳这块我试过用模型做效果还行但需要控制合并的激进程度否则会把有细微差别的记忆错误合并。如果你也想搭一套我的建议是从最小可用版本开始先只做写入和检索不做遗忘和关联跑起来看效果。等你感觉到“记忆太多反而乱”的时候再逐步加遗忘和分层。不要一上来就追求完美架构那样大概率会卡在设计阶段永远跑不起来。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询