1. 项目缘起与核心定位
第一次看到claude-mem这个名字,我的直觉是:这大概率是一个围绕 Claude 生态做“记忆层”的项目。事实也确实如此。它要解决的是一个所有长期使用大模型的人都会遇到的痛点——模型没有持久记忆。每次开新会话,之前聊过的项目背景、代码约定、个人偏好、踩过的坑,全部归零。你不得不反复粘贴同一段上下文,或者维护一份越来越长的“系统提示词”,既费 token 又容易遗漏。
claude-mem的核心价值,就是给 Claude 这类对话式 AI 加一层可持久化、可检索、可自动注入的记忆系统。它让 AI 在跨会话、跨项目、跨时间的场景下,依然能“记得”你是谁、你在做什么、你习惯怎么做。适合谁来参考?三类人最受益:一是长期用 Claude 做开发辅助的工程师,二是需要维护多项目上下文的独立开发者或小团队,三是对 AI 工作流自动化有追求、愿意折腾本地工具链的效率玩家。
我把它理解成一个“外挂大脑”:对话时自动把关键信息写进本地存储,下次对话时再按相关性把记忆捞出来,拼进上下文。听起来简单,但要做好,涉及存储选型、检索策略、注入时机、token 预算控制等一连串工程决策。下面我按自己实际搭建和调试的过程,把整套思路和细节拆开讲。
2. 整体架构设计与选型思路
2.1 为什么是“本地优先 + 检索增强”而不是“全量塞上下文”
很多人第一反应是:既然模型记不住,那就把所有历史对话都塞进上下文不就行了?我实测过,这条路走不通。原因有三:第一,上下文窗口再大也有上限,历史越长越容易触顶;第二,token 成本随长度线性上涨,长期用下来账单很难看;第三,也是最关键的,无关历史会稀释注意力,模型反而抓不住当前重点。
claude-mem采用的是“本地优先 + 检索增强”的思路。所有记忆先落到本地存储,对话时只把与当前问题最相关的片段检索出来注入。这样做的好处是:存储不受窗口限制,成本可控,且注入内容精准。代价是需要一套检索机制,这就引出了向量检索和关键词检索的取舍。
2.2 存储层选型:SQLite + 向量扩展的务实组合
存储层我最终选了SQLite 搭配向量扩展。理由很实际:单机场景下,SQLite 零运维、单文件、易备份,配合向量扩展后既能存结构化元数据(时间、项目、标签),又能做语义相似度检索。相比起单独跑一个向量数据库,它少了一个常驻进程,对个人开发者友好得多。
如果你追求更轻量,也可以先用纯 JSON 文件加本地嵌入模型,但一旦记忆条数上千,检索效率会明显下降。我的建议是:记忆量小于 500 条时用 JSON 够用,超过就尽早迁到 SQLite,迁移成本远低于后期重构。
2.3 检索策略:语义为主,关键词为辅的混合方案
纯语义检索有个坑:它对“精确匹配”不敏感。比如你记得某次讨论里出现过build_id=8848这种具体标识,语义检索可能返回一堆“构建相关”的记忆,却漏掉那条精确的。所以我在实现里做了混合检索:先用向量相似度召回一批候选,再用关键词做二次过滤和加权,最后按综合得分排序。
这个策略的收益在实测中很明显。纯语义检索的命中率大概在 70% 左右,加上关键词加权后能到 85% 以上。尤其是涉及代码符号、文件路径、版本号这类“硬信息”时,关键词那一路几乎是决定性的。
2.4 注入时机:不是越多越好,而是越准越好
记忆注入的时机和数量,直接决定体验。我的做法是:每次用户发消息后,先做一次检索,取 Top-K 条记忆,按 token 预算裁剪后拼进系统提示或前置上下文。K 值我一般设 5 到 8,token 预算控制在 1500 以内。超过这个量,模型反而容易被带偏。
这里有个容易被忽略的细节:记忆要带时间戳和来源标记。模型看到“三天前你在项目 A 里决定用方案 X”和看到“你决定用方案 X”,理解深度完全不同。时间信息能帮模型判断这条记忆是否还适用。
3. 核心模块拆解与实操要点
3.1 记忆写入:什么该记,什么不该记
写入策略是整套系统的地基。我的原则是:记决策、记偏好、记结论,不记寒暄、不记中间过程。具体来说,以下几类内容值得写入:
- 项目级约定:比如“这个项目用 pnpm 不用 npm”“测试框架统一用 vitest”
- 个人偏好:比如“回答尽量给可运行代码,少讲理论”
- 关键决策及其理由:比如“选 SQLite 是因为要单文件部署”
- 具体标识:文件路径、环境变量名、版本号
不该记的:纯问候、重复确认、已经被推翻的中间方案。我一开始什么都记,结果检索时噪音极大,后来加了“写入前过滤”才好转。
注意:写入过滤不要做得太激进。我踩过的坑是过滤规则太严,把“这个方案先不用,但以后可能回头考虑”这类信息也滤掉了,后来真需要时找不到。建议保留“被否决方案”但打上
rejected标签,检索时降权而非丢弃。
3.2 记忆检索:相似度阈值与重排序
检索环节有两个参数最关键:相似度阈值和重排序策略。阈值设太低,会召回一堆弱相关记忆;设太高,又可能漏掉真正有用的。我的经验值是余弦相似度 0.65 作为初筛线,低于这个的直接丢。
重排序我用了三个信号加权:语义相似度占 0.6,关键词匹配占 0.25,时间新鲜度占 0.15。时间权重不能太高,否则老但重要的记忆会被新但无关的记忆挤掉。这个权重是我反复调了七八次才定下来的,你可以根据自己的使用习惯微调。
3.3 上下文注入:格式与 token 预算
注入格式我试过好几种,最后固定为带标签的结构化文本:
[记忆 | 2024-06-12 | 项目A] 决定使用 SQLite + 向量扩展做本地存储,理由是单文件部署、零运维。这种格式的好处是模型能清晰区分“记忆”和“当前对话”,不会混淆。token 预算方面,我建议记忆注入不超过总上下文的 15%。比如 8K 上下文,记忆部分控制在 1200 token 以内。超了就按得分从低到高裁掉。
3.4 记忆更新与去重:避免“记忆膨胀”
长期运行后,记忆库会膨胀,出现大量重复或近似重复的条目。我加了一个定期去重任务:每周跑一次,对相似度超过 0.9 的记忆做合并,保留信息量最大的那条,其余标记为归档。这个任务我用脚本自动化,跑一次大概几秒钟,但能显著提升检索质量。
实操心得:去重时不要简单删除,而是“合并 + 归档”。合并后的记忆保留原始时间范围,归档的仍可被检索但降权。这样既控制了活跃记忆量,又不丢历史信息。
4. 完整搭建流程与关键配置
4.1 环境准备与依赖安装
先把基础环境搭起来。我用的是 Python 3.11,依赖主要是嵌入模型、SQLite 向量扩展和 HTTP 客户端。安装命令如下:
pip install sentence-transformers sqlite-vec httpxsentence-transformers用来做本地嵌入,选它是因为完全离线、不依赖外部 API,隐私和成本都可控。嵌入模型我选的是all-MiniLM-L6-v2,384 维,速度快,质量对记忆检索这个场景够用。如果你对中文支持要求高,可以换成paraphrase-multilingual-MiniLM-L12-v2,代价是慢一些。
4.2 数据库初始化与表结构设计
数据库我设计了三张表:memories存主记录,embeddings存向量,tags存标签关联。核心表结构如下:
CREATE TABLE memories ( id INTEGER PRIMARY KEY, content TEXT NOT NULL, project TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, score REAL DEFAULT 1.0, status TEXT DEFAULT 'active' );score字段用来做时间衰减和人工调权,status区分 active、archived、rejected 三种状态。向量表用sqlite-vec的虚拟表,维度设 384 对应嵌入模型输出。
4.3 嵌入生成与写入流程
写入流程分四步:接收原始文本、过滤、生成嵌入、落库。过滤那步我用了一个简单的规则引擎,匹配到寒暄词或纯确认语句就跳过。生成嵌入时要注意批量处理,单条单条调太慢,我一般攒够 16 条一批。
from sentence_transformers import SentenceTransformer model = SentenceTransformer('all-MiniLM-L6-v2') embeddings = model.encode(texts, batch_size=16, normalize_embeddings=True)normalize_embeddings=True这个参数别漏,归一化后余弦相似度计算更快更稳。
4.4 检索与注入的完整链路
检索链路是:用户消息 → 生成查询嵌入 → 向量召回 Top-20 → 关键词加权 → 重排序 → 取 Top-K → token 裁剪 → 注入。每一步我都加了日志,方便排查为什么某条记忆没被召回。
注入时我把它拼在系统提示之后、用户消息之前,用明确的分隔符隔开。实测下来,这种位置模型对记忆的“采纳率”最高。如果拼在用户消息之后,模型有时会忽略。
4.5 参数调优记录:我实际用的一组值
调参这块我记录了一组自己稳定使用两周以上的配置,供参考:
| 参数 | 取值 | 说明 |
|---|---|---|
| 相似度初筛阈值 | 0.65 | 低于此值不进入候选 |
| 召回数量 | 20 | 向量召回候选池大小 |
| 最终注入数量 K | 6 | 实际注入的记忆条数 |
| 记忆 token 预算 | 1200 | 超出按得分裁剪 |
| 语义权重 | 0.6 | 重排序中语义占比 |
| 关键词权重 | 0.25 | 重排序中关键词占比 |
| 时间权重 | 0.15 | 重排序中新鲜度占比 |
这组值不是最优解,但是一个稳定可用的起点。你可以先照搬,再根据自己的使用反馈微调。
5. 常见问题与排查技巧实录
5.1 记忆召回不准的三种典型原因
召回不准是最常见的问题,我总结了三类原因。第一类是嵌入模型与语言不匹配,用英文模型处理中文记忆,相似度普遍偏低,换多语言模型即可。第二类是记忆内容太短,比如只记了“用 vitest”,没有上下文,嵌入质量差,建议写入时补全成完整句子。第三类是阈值设得过高,把本该召回的挡在外面,适当下调 0.05 试试。
5.2 注入后模型“答非所问”的排查
有时候记忆注入了,模型反而答偏了。我遇到过的原因是记忆之间互相矛盾,比如旧记忆说“用 npm”,新记忆说“改用 pnpm”,两条都注入,模型就懵了。解决办法是写入新决策时,把冲突的旧记忆标记为superseded,检索时排除。
5.3 性能瓶颈定位:嵌入还是检索
系统变慢时,先分清是嵌入慢还是检索慢。我的做法是加计时日志:嵌入超过 200ms 就是模型太大或没批处理;检索超过 100ms 就是候选池太大或没建索引。SQLite 向量检索记得建好索引,否则数据量上来后是线性扫描,慢得离谱。
5.4 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 记忆完全不召回 | 嵌入维度不匹配 | 检查模型输出维度与表定义 |
| 召回但注入后无效 | 注入位置不对 | 移到系统提示后、用户消息前 |
| 记忆库膨胀快 | 写入过滤太松 | 收紧过滤规则,加去重任务 |
| 检索结果重复 | 未做去重 | 加相似度 0.9 合并逻辑 |
| 中文记忆质量差 | 模型语言不匹配 | 换多语言嵌入模型 |
5.5 几个我踩过的坑
第一个坑是忘记归一化嵌入,导致相似度计算全乱,排查了半天才发现。第二个坑是时间戳用了本地时区,跨时区同步时排序错乱,后来统一用 UTC。第三个坑是去重任务误删了重要记忆,因为相似度高但语义其实不同,后来改成“合并而非删除”才稳妥。
提示:任何自动删除记忆的逻辑,上线前先用只读模式跑一周,人工抽查确认无误再开启实际删除。
6. 扩展方向与个人体会
这套系统跑顺之后,我做了几个扩展。一是按项目隔离记忆,不同项目的记忆互不干扰,检索时先按项目过滤再算相似度,准确率又上了一截。二是记忆摘要,对超过一定长度的记忆自动生成一句话摘要,检索时用摘要算相似度,注入时用全文,兼顾速度和信息量。三是手动记忆接口,允许我主动说“记住这条”,绕过自动过滤直接写入,处理那些规则覆盖不到的重要信息。
我在实际使用中最大的体会是:记忆系统的价值不在于“记得多”,而在于“记得准”。早期我追求记忆条数,结果噪音淹没信号,体验反而差。后来把重心放在写入过滤和检索精度上,记忆条数降了一半,但有用信息的召回率翻了一倍。如果你也在搭类似系统,我的建议是先把写入和检索这两个环节做扎实,扩展功能往后放。另外,定期回顾记忆库,手动清理那些过时或矛盾的条目,这个习惯能省掉很多后续麻烦。