ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

claude-mem 记忆系统实战:从存储、抽取到注入的完整实现

claude-mem 记忆系统实战:从存储、抽取到注入的完整实现 1. 从聊完就忘说起claude-mem 到底想解决什么如果你用 Claude 这类对话式 AI 干过稍微长一点的活儿大概率遇到过这种尴尬前面聊了半小时把项目背景、代码规范、命名习惯、甚至几个关键决策都交代清楚了结果新开一个会话它又变回一张白纸你得从头再讲一遍。更气人的是同一个会话里聊到后面早期说过的细节它也开始记不清回答逐渐跑偏。claude-mem这个名字直译过来就是Claude 记忆。它要解决的核心问题非常朴素让 AI 在跨会话、跨时间的使用中记住那些本该记住的东西。注意这里说的不是模型本身的能力升级而是在模型之外给它搭一套外挂记忆系统——把重要的上下文、偏好、决策、事实存下来在需要的时候再喂回去。我先把话说在前头claude-mem不是一个官方标准产品名它更像是一类做法的统称——围绕 Claude 构建持久化记忆层。市面上有各种实现思路有基于本地文件的有基于向量库的也有基于结构化数据库的。所以这篇不会假装有一个官方 claude-mem 安装包让你npm install就完事而是把这类记忆系统为什么这么设计、怎么落地、坑在哪讲透。你读完能自己搭一套也能看懂别人开源的实现。适合谁看三类人一是天天用 Claude 写代码、写文档被重复交代背景折磨的开发者二是想把 AI 接入自己工作流需要它记住业务规则的技术负责人三是对AI 记忆这个概念好奇想搞清楚它和模型微调长上下文到底啥关系的同学。不需要你有多深的 AI 背景但得会点基本的命令行和配置文件操作。2. 记忆系统的三种流派为什么大多数人的第一版都做错了2.1 最直觉的做法把所有对话都存下来新手搭记忆系统第一反应往往是那我全存不就行了。每次对话结束把完整记录追加到一个文件里下次会话开头整个塞回去。听起来很美好实测下来问题一堆。第一个问题是上下文窗口不是无限的。Claude 的上下文再大也有上限你把几十次对话全塞进去要么超限被截断要么把真正重要的信息淹没在噪音里。第二个问题是成本和延迟。每次请求都带上几万 token 的历史钱花得飞快响应也变慢。第三个问题最隐蔽无关信息会干扰模型判断。你三个月前随口说的一句这个方案先试试可能和今天的决策完全冲突模型看到两段矛盾的信息输出就开始摇摆。所以全量存储这条路只适合极短期的场景比如同一个任务连续几轮对话。一旦跨天、跨项目必须做筛选和压缩。2.2 进阶做法只存事实和偏好真正能用的记忆系统核心动作是抽取。不是存原始对话而是从对话里提炼出结构化的、可复用的信息。通常分两类事实型记忆Fact项目用的是什么技术栈、数据库表结构、API 的鉴权方式、某个函数的用途。这类信息相对稳定变更频率低。偏好型记忆Preference用户喜欢用 TypeScript 而不是 JavaScript、注释要写中文、提交信息遵循 Conventional Commits、回答要先给结论再给理由。这类信息跨项目通用价值极高。抽取的好处是信息密度高。一段 2000 字的对话可能提炼出 3 条事实、2 条偏好压缩到 200 字以内。下次会话只需要带上这些精华既省 token 又精准。但抽取本身有难度怎么判断哪句是值得记的我的经验是优先记那些如果忘了会导致返工的信息。比如这个字段不能为空忘了代码就出 bug用户要求所有时间戳用 UTC忘了就得改一遍。至于闲聊、试探性的讨论、被否决的方案一律不记。2.3 高级做法带检索的记忆库当记忆条目多到几百上千条就不能每次全带上了得按需检索。这就引入了向量检索或关键词检索把当前问题转成查询从记忆库里捞出最相关的几条拼进上下文。这里有个关键设计点检索的召回率和精确率要平衡。召回率太低该记的没捞出来等于没记精确率太低捞出一堆无关的反而干扰。实践中我一般把 top-k 设在 3 到 5 条并且加一个相似度阈值低于阈值的直接丢弃宁可少带也别带错。三种流派的对比我整理成一张表方便你按自己的场景选流派存储内容优点缺点适用场景全量存储原始对话实现简单零信息损失贵、慢、易干扰单任务连续对话事实抽取结构化条目密度高、精准抽取逻辑要调跨会话的长期助手检索记忆库条目向量可扩展到海量工程复杂度高多项目、多用户大多数个人开发者从第二种起步就够了等条目真的多起来再上检索。3. 拆解 claude-mem 的核心机制存储、抽取、注入三段式不管具体实现用什么语言、什么数据库一套能用的记忆系统骨架都是三段写入存储、加工抽取、读取注入。我把每一段的关键设计讲清楚。3.1 存储层为什么我推荐从纯文本文件起步很多人一上来就想上数据库Postgres 加 pgvector听着就专业。但我踩过的坑告诉我个人项目阶段纯文本文件Markdown 或 JSONL是性价比最高的选择。理由有三。第一可读可改。记忆抽错了你直接打开文件改一行就行不用写 SQL。第二零依赖。不用起服务、不用管连接池一个脚本读写文件五分钟跑起来。第三天然可版本控制。把记忆文件放进 Git每次变更都有记录出问题能回滚还能看到AI 对我的了解是怎么一步步积累的。具体格式我习惯用 JSONL每行一个 JSON 对象因为追加方便解析也简单。一条记忆大概长这样{id: mem_001, type: preference, content: 用户偏好 TypeScript禁用 any, scope: global, created: 2025-01-15, confidence: 0.9}字段说明一下type区分事实和偏好scope区分是全局偏好还是某个项目专属confidence是置信度抽取时不确定的条目给低分检索时可以过滤掉。这个confidence字段是我后来加的非常有用——AI 抽取难免有误判有了它就能做二次筛选。等条目超过一两千条或者需要多用户隔离再迁移到 SQLite轻量或 Postgres多用户。迁移时 JSONL 直接导入就行不亏。3.2 抽取层让 AI 自己总结但别全信它抽取这一步最省事的做法是再调一次 Claude给它一段对话让它输出结构化记忆。提示词大概是这样从以下对话中提取值得长期记住的信息分两类 1. 事实项目相关的稳定信息 2. 偏好用户的工作习惯和喜好 每条信息用一句话表述并给出 0-1 的置信度。 只提取忘了会导致返工的内容忽略闲聊和临时讨论。 输出 JSON 数组。这里有几个实操要点。第一明确告诉它忽略什么比只告诉它提取什么更有效。模型天生倾向于多提取你不划边界它能把客套话都记下来。第二要求输出置信度方便后续过滤。第三抽取要幂等。同一段对话重复抽取应该得到相似结果不能每次都不一样。如果发现结果飘忽把 temperature 调低。抽取完还有一步去重和合并。比如这次抽出用户喜欢简洁回答上次已经有一条用户要求回答不要啰嗦这俩其实是一回事得合并。简单做法是用字符串相似度比如编辑距离或余弦相似度做粗筛超过阈值的人工确认或自动合并。别小看这步不去重的话记忆库很快就充斥重复条目检索时全是冗余。提示抽取层是整个系统里最需要调优的部分。建议先手动跑几十段真实对话看看抽出来的东西质量如何再决定要不要自动化。我见过太多人直接上自动化结果记忆库里一半是垃圾。3.3 注入层什么时候把记忆喂回去喂多少注入的时机很关键。我的做法是在每次新会话的第一轮请求时注入而不是每轮都注入。原因很简单记忆是背景信息第一轮交代清楚就够了后续对话模型自己会保持。每轮都注入既浪费 token又可能让模型过度关注记忆而忽略当前问题。注入的内容也要控制量。我一般限制在500 到 1000 token以内按相关性和置信度排序取前若干条。格式上用清晰的分隔把它和用户当前问题区分开[背景记忆] - 用户偏好 TypeScript禁用 any - 当前项目使用 PostgreSQL 15 - API 鉴权用 JWT有效期 2 小时 [当前问题] 帮我写一个查询用户的函数这样模型能明确知道哪些是背景、哪些是当前任务。实测下来这种显式分隔比把记忆混在系统提示里效果更好模型更少张冠李戴。还有一个细节记忆要能失效。项目从 PostgreSQL 迁到 MySQL 了那条使用 PostgreSQL的记忆就得更新或删除。我一般给每条记忆加一个last_verified时间戳超过一定时间比如 90 天没被引用或确认的标记为待验证检索时降权。这个机制能防止记忆库变成历史垃圾堆。4. 手把手搭一套最小可用版本理论讲够了直接上代码。我用 Python 写一个最小可用版本总共不到 150 行能跑通存储-抽取-注入全流程。你照着抄半小时能跑起来。4.1 环境准备与目录结构先建目录mkdir claude-mem cd claude-mem mkdir data touch data/memories.jsonl依赖就一个调用 Claude API 的官方 SDK。装一下pip install anthropicAPI key 通过环境变量传别硬编码进代码export ANTHROPIC_API_KEY你的key目录结构保持极简data/memories.jsonl存记忆mem.py是主逻辑。够用了。4.2 写入与抽取把对话变成记忆条目核心函数是extract_memories输入一段对话文本输出记忆条目列表import json import os from anthropic import Anthropic client Anthropic() MEM_FILE data/memories.jsonl EXTRACT_PROMPT 从以下对话中提取值得长期记住的信息分两类 1. fact项目相关的稳定事实 2. preference用户的工作习惯和喜好 每条用一句话表述给出 0-1 置信度。 只提取忘了会导致返工的内容忽略闲聊和临时讨论。 严格输出 JSON 数组不要任何额外文字。 格式[{type:fact,content:...,confidence:0.9}] 对话 {dialog} def extract_memories(dialog): resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1000, temperature0, messages[{role: user, content: EXTRACT_PROMPT.format(dialogdialog)}] ) text resp.content[0].text.strip() # 容错去掉可能的 markdown 代码块包裹 if text.startswith(): text text.split(\n, 1)[1].rsplit(, 1)[0] try: return json.loads(text) except json.JSONDecodeError: return []这里temperature0是为了让抽取结果稳定同一段对话多跑几次结果基本一致。容错那段别省模型有时候会手贱加个代码块标记不加处理直接json.loads会崩。写入函数负责追加到文件并做简单的去重def save_memories(items): existing load_memories() existing_contents {m[content] for m in existing} new_items [] for it in items: if it[content] in existing_contents: continue it[id] fmem_{len(existing) len(new_items):04d} it[scope] global new_items.append(it) with open(MEM_FILE, a, encodingutf-8) as f: for it in new_items: f.write(json.dumps(it, ensure_asciiFalse) \n) return len(new_items)去重用的是精确匹配简单但够用。等条目多了再上相似度匹配。4.3 读取与注入按相关性挑记忆读取时按置信度排序取前 N 条def load_memories(min_confidence0.6, top_k8): if not os.path.exists(MEM_FILE): return [] items [] with open(MEM_FILE, encodingutf-8) as f: for line in f: line line.strip() if not line: continue m json.loads(line) if m.get(confidence, 0) min_confidence: items.append(m) items.sort(keylambda x: x[confidence], reverseTrue) return items[:top_k] def build_context(memories): if not memories: return lines [f- {m[content]} for m in memories] return [背景记忆]\n \n.join(lines) \nmin_confidence0.6这个阈值是我调出来的。太低会把不确定的垃圾带进去太高会漏掉有用信息。你可以根据自己的数据微调但别设成 0.9那样基本啥都留不下。4.4 串起来跑一遍主流程def chat(user_input): memories load_memories() context build_context(memories) full_prompt context [当前问题]\n user_input resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens2000, messages[{role: user, content: full_prompt}] ) return resp.content[0].text if __name__ __main__: # 模拟一段对话抽取记忆 dialog 用户我们这个项目用 TypeScript别用 any。 助手好的明白了。 用户数据库是 PostgreSQL 15连接串在 .env 里。 助手收到。 items extract_memories(dialog) n save_memories(items) print(f新增 {n} 条记忆) print(chat(帮我写个查询用户的函数))跑一次你会看到它抽出了用户偏好 TypeScript 禁用 any项目使用 PostgreSQL 15这类条目存进文件。下次再问问题这些背景就自动带上了。5. 实测中那些文档不会告诉你的坑代码能跑通只是第一步真正用起来坑都在细节里。这部分是我踩过之后总结的含金量最高。5.1 记忆污染错误信息一旦进去就很难清最头疼的问题是记忆污染。比如某次对话里你随口说这个接口暂时用 HTTP抽取时被记成项目使用 HTTP结果后面所有生成的代码都不带 HTTPS。这种错误记忆会持续影响输出而且因为置信度高检索时总被优先带上。我的应对策略是双保险。第一抽取时对临时性词汇敏感比如暂时先试试以后再说遇到这类词要么不记要么把置信度压到 0.5 以下。第二定期人工审查。我每周花十分钟扫一遍新增记忆看到不对的直接删。别嫌麻烦记忆库的质量直接决定 AI 输出的质量。5.2 冲突记忆新旧信息打架怎么办项目演进过程中记忆必然冲突。上个月记的用 REST API这个月改成 GraphQL 了两条记忆并存模型看到就懵。解决办法是给记忆加时效和状态。我现在的做法是每条记忆有status字段active / deprecated抽取新记忆时如果发现和旧记忆语义冲突就把旧的标记为 deprecated而不是直接删。这样既保留了历史又不会干扰当前判断。判断冲突可以用一次额外的模型调用让它对比新旧两条是否矛盾成本很低。5.3 检索失灵明明记了却想不起来有时候你确定记过某条信息但 AI 就是没用上。原因通常是检索没匹配上。如果你用的是纯置信度排序就像我上面的最小版本它根本不看当前问题永远返回那几条高分记忆自然可能漏掉相关的。升级方案是引入相关性排序。把当前问题和每条记忆都转成向量算余弦相似度再和置信度加权。权重我一般设相关性 0.7、置信度 0.3。这样既保证相关又不会把低质量的捞上来。向量化可以用现成的 embedding 接口也可以先用简单的关键词匹配顶着效果差些但零成本。5.4 成本失控记忆系统本身也在烧钱别忘了抽取要调模型检索如果用向量也要调接口这些都是成本。我见过有人把每轮对话都抽一遍一个月账单翻倍。控制成本的关键是批量抽取。不要每轮对话都抽而是攒够一段比如一次任务结束再抽一次。另外抽取用的模型可以选便宜的小模型因为抽取任务相对简单不需要最强的模型。注入时用的才是主力模型。这个抽取用便宜模型、生成用强模型的分工能省不少钱。6. 从个人玩具到团队工具还能怎么扩展最小版本跑顺之后如果你想让它在团队里发挥作用有几个方向可以扩展。多用户隔离。给每条记忆加user_id检索时只捞当前用户的。团队共享的事实型记忆比如项目规范可以设成scope: team所有人可见。这样既有个性化又有共享知识。记忆的可视化。把记忆库渲染成一个网页按类型、时间、置信度分类展示支持搜索和手动编辑。团队新人入职时直接看这个页面就能了解项目的历史决策比翻聊天记录高效得多。和现有工具集成。比如接入你的代码编辑器写代码时自动把相关记忆作为注释提示或者接入项目管理工具每次开新任务自动带上项目背景。这些集成的价值在于让记忆无感地发挥作用而不是每次都要你手动触发。记忆的导出与迁移。用 JSONL 存的好处这时候就体现出来了导出就是复制文件迁移就是换个路径。别用私有格式把自己锁死。最后分享一个我自己的体会记忆系统的价值不在于记得多而在于记得准。一个只有 50 条高质量记忆的系统比一个有 5000 条垃圾的系统好用得多。所以别急着堆量先把抽取质量和去重做好让每一条记忆都经得起推敲。我现在的记忆库稳定在两百条左右覆盖了常用的技术栈偏好和几个主力项目的关键决策日常用起来已经非常顺手了。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表