
如果你也在用AI助手处理正经工作肯定遇到过这种场面上午刚和它敲定一个方案的关键参数下午接着聊它居然反问“你说的那个需求是什么意思”。这不是它笨而是对话记忆本身就有寿命。我折腾了一阵子之后把解决方案攒成了一个本地小工具代号就叫 claude-mem。这个名字没啥高深含义claude 是我对“AI助手”的习惯叫法mem 就是 memory。它想解决的事情只有一件让AI对话拥有跨会话的长期记忆。简单说就是把每次聊完之后值得留下的信息抽成一张张结构化的“记忆卡”存在本地文件夹里下次新开对话之前再把最相关的几张塞回去。这篇文章把我从零搭到跑通的全过程、踩过的坑、以及哪些地方值得抄作业一次性写清楚。1. 先看痛点AI对话为什么总在“选择性失忆”1.1 上下文窗口不是万能的很多人以为给AI助手多聊几句、把历史记录全贴回去它就什么都记得了。实际不是这么回事。模型一次能处理的上下文长度确实在膨胀但膨胀不等于无限更不等于“记得住”。窗口是有物理上限的对话一长最早的内容要么被截断要么被压缩到几乎没有权重。你用过的那些“总结历史然后继续聊”的方案本质上也是在做有损压缩压缩完作者是模型自己丢失细节是常态。这就好比一个人手里只攥着一张便利贴事情多了就往上面写写到写不下就必须擦掉旧的。便利贴不是记忆库它只是临时工作台。真正的长期记忆得放到一个能随时翻找的抽屉里。这个抽屉就是 claude-mem 要做的事。1.2 聊天记录和记忆库是两回事一开始我想偷懒直接把聊天记录导出成文本每次都当背景资料喂回去。结果有两个问题很头疼。一是聊天记录里大量内容是寒暄、语气词、临时debug的中间结论噪声太大模型一读就被带偏二是就算我真能把几十万字的记录都塞进上下文成本和响应速度也受不了。所以记忆库不能是流水账得是“提炼过的结论”。claude-mem 的核心思路特别朴素每次对话结束后让AI自己把这段对话里值得长期记住的东西抽出来整理成固定格式的记忆卡。只有通过了重要性判断的内容才会入库。下次开启新对话时不是把聊天记录翻出来而是找跟当前话题最相关的几张卡塞进提示词。这么一来输入简短了信息密度反而高了。2. 整体设计轻量记忆库的四个边界2.1 什么值得记用“重要性过滤”代替全量保存我在最初的版本里犯过一个错让AI把对话里的所有实体、日期、结论全记下来。结果跑了两天文件夹里堆了几百个文件真正要用的时候根本找不到条理。后来我给抽取环节加了一个“重要性评分”的步骤。具体规则是这样的记忆卡必须满足至少一条才算合格。第一是长期事实比如用户的偏好、项目的固定路径、命名规范第二是明确决策比如“最终确定用方案B放弃方案A原因是成本”第三是任务状态比如“已经发给对方审核预计周五反馈”第四是约束条件比如“不能用某种依赖因为授权不兼容”。反过来寒暄、临时数值、一次性问题、包含敏感信息的原始文本都不允许入库。这个过滤逻辑靠提示词约束不写死代码。模型判断重要性的能力其实够用关键是把标准讲清楚。我在抽取提示词里直接列了上面四类要求和禁止项效果比单纯说“请提取关键信息”好出一个量级。2.2 存储选型Markdown 文件比数据库更顺手很多朋友一听说要做记忆库第一反应就是上向量数据库、搞embedding。我的建议是量级没到十万条之前不要把系统搞复杂。claude-mem 的存储层就是一堆 Markdown 文件每个文件一张卡文件名带日期和序号。头部用 YAML 格式写元信息正文写内容摘要。为什么选 Markdown因为它是纯文本能用 Git 做版本管理能直接打开看能用任何编辑器改换设备直接同步文件夹就行。相比之下一上来就引入数据库查询确实强但备份、迁移、人工校对都变麻烦了。对个人记忆库来说文件系统本身就是数据库目录就是索引。2.3 检索选型先关键词后向量检索逻辑也遵循同样的“够用就行”原则。文件量在几千张以内时纯关键词匹配加上标签命中效果已经很好。我是用 Python 写了个简单的评分函数标签命中权重记2分标题和正文关键词命中记1分然后按卡的更新时间和当前时间做过期衰减最后取总分最高的三到五张。只有当你发现自己经常需要“语义层面”的联想检索例如明明没提“成本”却想找出所有跟“省钱”相关的决策时才值得再开一条embedding检索的支线。我现在的做法是主用关键词把向量检索做成了可选插件用的时候才加载不影响日常的轻量启动。3. 实操搭建一套可跑通的“记忆增强”流程3.1 目录结构与记忆卡格式先看目录长什么样memory/ ├── cards/ │ ├── 2025-06-18-001.md │ ├── 2025-06-18-002.md │ └── 2025-06-19-001.md ├── archive/ ├── templates/ └── index.md一张标准记忆卡长这样--- title: 项目X采用模块化重构方案 tags: [项目X, 架构, 决策] created: 2025-06-18T10:30:0008:00 updated: 2025-06-18T10:30:0008:00 importance: 8 source: session_20250617_1530 status: active --- 项目X决定将单体模块拆成四个独立服务理由是可独立部署、团队边界清晰。 保留旧版在feature/legacy分支不再主动迭代。这里的元信息字段我解释一下。tags 是检索的主索引重要性分数 importance 是后来清理时判断保留顺序的依据source 记录这张卡来自哪段对话status 用来标记是否过期。不要小看 source没有它你想回溯原始对话的时候会完全找不到线索。3.2 从对话里抽记忆的核心脚本抽取过程是半自动的。我把每一轮比较重要的对话导出成文本然后丢给模型接口要求它按固定模板返回 JSON。下面是一个高度简化的示意代码读起来像伪代码但结构就是实际在用的。import json import datetime import pathlib SYSTEM_PROMPT 你是记忆抽取器。根据对话内容只提取 1. 长期事实 2. 明确决策 3. 任务状态 4. 约束条件 禁止提取寒暄、临时数值、敏感原始数据。 输出格式[{title: ..., tags: [...], importance: 0-10, content: ..., reason: ...}] 只满足四类要求之一且重要性7的才输出。 def extract_cards(conversation_text: str, api_caller): response api_caller.chat( systemSYSTEM_PROMPT, userf对话记录\n{conversation_text} ) cards json.loads(response) return [c for c in cards if c[importance] 7] def save_card(card: dict): today datetime.date.today().isoformat() seq len(list(pathlib.Path(memory/cards).glob(f{today}-*.md))) 1 path pathlib.Path(fmemory/cards/{today}-{seq:03d}.md) frontmatter ( f---\ntitle: {card[title]}\n ftags: {card[tags]}\n fcreated: {datetime.datetime.now().isoformat(timespecminutes)}\n fimportance: {card[importance]}\n f---\n\n{card[content]}\n ) path.write_text(frontmatter, encodingutf-8)实际运行时要注意一个细节模型输出的 JSON 偶尔会有前后缀文字直接 json.loads 会报错。我在工程里加了解析兜底先提取第一个 “[” 到最后一个 “]” 之间的内容再解析。这个小处理让我少了很多半夜排查的烦恼。3.3 新会话开始时注入相关记忆建好记忆库之后最关键的是让它在对话前“主动想起来”。我在每次开启新会话前跑一个加载函数它做的事就是接收用户刚输入的第一句话用关键词和标签去匹配记忆卡选出最相关的几张压缩后拼进系统提示词。import math import re def score_card(card_text: str, query: str, days_old: int, tags_hit: int) - float: keyword_hits len(re.findall(query, card_text, re.IGNORECASE)) score keyword_hits * 1.0 tags_hit * 2.0 decay math.exp(-0.05 * days_old) return score * decay衰减系数 0.05 的意思是一张卡每过20天权重大约掉到原来的三分之一。这样新决策会自动压过旧决策避免模型总拿半年多以前的信息说事。如果你想让某些长期偏好一直生效可以给它加一个pinned: true字段检索时直接放行不走衰减。我在实测中发现注入三到五张卡、每张控制在200字以内是性价比最高的组合。少于三张模型可能漏关键信息多于五张提示词变长模型反而抓不住重点还会增加响应耗时。3.4 定期清理合并、过期与归档记忆库不能只进不出。我每周会跑一次整理脚本做三件事。第一把标题或者正文高度相似的卡片挑出来请模型合并成一张保留最新的updated时间。第二检查status为 completed 的任务卡如果已经过期超过30天就移入archive/目录。第三把importance小于7且超过90天没有被检索命中的卡直接删除。这样做的好处是文件夹永远保持在一个能手工翻完的体量。我见过有人把记忆库当成垃圾桶什么碎片都往里扔三个月后文件数量破万别说检索了光是同步就卡得不行。记忆库这东西定期断舍离比花里胡哨的存储引擎重要得多。4. 常见问题与排查技巧实录4.1 记忆重复“刷屏”怎么破我遇到的第一个严重问题是重复。同一件事变了几个说法每次对话都被重复抽取记忆库里出现三张意思几乎一样的卡。检索时一次命中三张注入进去的提示词全是废话。后来加了两道防线。第一写入前先对 title 做归一化处理转小写、去空格、去掉结尾的句号然后计算哈希如果哈希已存在就跳过。第二在抽取提示词里要求模型先判断“这条信息和已有记忆是否在说同一件事”如果是返回一个duplicate_of字段主程序直接丢弃。本质上是把去重工作前置给模型判断准确率比单纯算文本相似度要高。4.2 检索结果离题万里怎么办关键词匹配的短板在中文语境下特别明显。比如记忆库里存着“这个项目不需要图形界面”你搜索“UI”完全匹配不上。我试过加同义词表但同义词永远列不全。更可靠的办法有两层。第一层是给每个项目、每个重要概念打一个编号标签比如项目X就永远叫PX对话中一旦出现就关联上。第二层是当关键词匹配结果分数都很低时允许模型根据用户当前输入自己生成几个扩展检索词然后再去匹配。这相当于用模型的语义理解能力来补关键词的机械性缺陷实测下来召回率能提升不少。4.3 隐私边界与数据安全这是想要长期使用记忆库的人绕不开的问题。我的原则是记忆库默认只存“结论”不存“原文”。凡是包含账号、密钥、身份证件号、聊天记录原文的段落一律禁止写入。抽取提示词里也要明确写上“不要输出任何看起来像凭据的完整字符串”。还有一个容易被忽略的点记忆库文件如果同步到公共网盘等于把隐私打包送出去。我自己是只放在本机用 Git 做版本管理远端存私有仓库。模型接口调用时也只发送经过脱敏的对话摘要而不是把整段原始记录都传过去。这个习惯从第一天就该养成后面再补会很痛苦。4.4 记忆库“臃肿”后的瘦身方案文件数量超过几千张以后哪怕每次只检索三张卡磁盘扫描也会开始变慢。我的处理是把记忆库按年份分目录比如cards/2025/和cards/2026/检索时优先看当年目录找不到再往回找。再往后变慢意味着关键词方案确实到了瓶颈。这时我会给全部卡片生成一次 embedding存进本地的向量索引检索逻辑改成“向量召回200张候选再用关键词评分排序取前5张”。这个混合方案把两条路的长处都占了既不会漏语义相关的内容也不会让毫无关键词关联的内容混进来。记住向量化是最后的手段不是第一选择。症状常见原因解决思路同一信息反复被抽取缺少写入前指纹去重对标题做归一化后计算哈希重复即丢弃检索结果不相关中文分词导致关键词错位增加项目编号标签或用模型扩展检索词重要信息被遗漏重要性阈值设得太高把importance阈值降到6再靠定期清理兜底注入记忆后回答变乱一次性塞太多张卡压缩为摘要单次最多注入5张隐私信息出现在卡里抽取提示词缺少禁止项在抽取阶段过滤完整凭据和原始文本5. 扩展玩法从个人助手变成团队知识库5.1 给AI一个稳定的“人设”记忆库最直接的收益是让AI的“人格”变得连续。如果你每次开新对话前都注入同一组关于你的偏好、背景、常用工具的记忆卡它回答问题的口吻和判断标准就非常稳定。我甚至有专门的几张卡只存“工作习惯”喜欢先看结论再看过程、讨论方案时必须给出否决理由、代码示例优先用标准库。这种稳定感在写作和技术决策场景里特别值钱。5.2 把记忆库接入自动化任务流我不只在聊天时用记忆库。写周报的时候脚本会把这一周新建的记忆卡全部捞出来按标签分组自动生成初稿开新项目的时候脚本会搜索跟新技术栈相关的历史决策卡生成一份“踩坑预告清单”。记忆库一旦形成了它就不只是给AI看的也是给自动化脚本看的。5.3 多人在线协作时的冲突处理小团队共用一套记忆库也是完全可行的但要注意冲突问题。两个人可能对同一件事写了两张结论相反的卡。我的建议是每张卡都带上owner字段检索时优先取当前操作人的卡如果检出同主题但有冲突的卡片不要合并而是把它标记为conflict: true显示在界面上让真人拍板。记忆这东西出错不可怕可怕的是出错了自己还不知道。我到现在还是保持着每周花几分钟翻一遍新建的记忆卡不是为了检查而是为了感受这个“外置大脑”正在形成一种怎样的思考习惯。它确实记下了我很多说过一次就忘掉的原则也让我和AI助手之间的每次对话都不必从零开始。这套做法的所有组件都是开源的、本地的、纯文本的你完全可以照着搭一套属于自己名字的版本。真要说有什么秘诀无非是少记废话定期整理别把流水账当成记忆。