news 2026/10/20 0:48:15

给 Claude 装外挂记忆:跨会话语义检索与本地记忆系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
给 Claude 装外挂记忆:跨会话语义检索与本地记忆系统实战

如果你每天都要跟 Claude 对话,大概率遇到过这种崩溃瞬间:昨天刚把项目背景、技术偏好、踩过的坑说清楚,今天新开一个会话,它又完全不记得了。靠复制粘贴救急、靠把需求写成超长固定 prompt,都很累。后来我给自己写了一个小工具,名字就叫 claude-mem,目标很明确:让 Claude 拥有跨会话的长期记忆。它会把每一次对话沉淀为结构化记忆,能做语义检索,也能在合适时候把相关记忆塞回新的会话里。它适合频繁调用 Claude API 的开发者,也适合想给本地助手会话加“记性”的折腾型用户。下面这篇算是我的完整复盘,从架构到实现细节,再到踩坑实录都覆盖了。

1. 整体方案:不改造大模型,只给对话加一个外挂记忆

1.1 无状态对话为什么需要外置记忆

Claude 本身的对话窗口其实是“无状态”的。它记得当前会话里你说了什么,但完全没有跨会话的持久概念。你和模型聊完一轮,这段上下文就只存在于那一次请求里;下一次新建会话,它对你的了解又回到零。

这有点像你每天面对一个记忆力为零的优秀实习生:能力很强,但每次上班都要你重新讲一遍公司规定。你可以每次都把过往信息搬进新的提示词里,可信息量一旦变大,同样的背景描述会反复占用上下文空间,成本高,还容易截断掉真正重要的内容。

所以解决问题的关键不是让模型“记住”,而是在模型外面加一套记忆系统。claude-mem 就承担这个角色:对话结束后,它负责记录、清洗、提取、存储;新对话开始时,它负责检索、筛选、注入。整个过程中,模型本身的接口和能力完全不用改动,你只是给输入输出端各加了一个钩子。

1.2 记忆闭环的五个环节与选型思路

整个工具的闭环可以拆成五段:

  1. 记录:捕获 Claude 收到的用户消息和返回的助手消息。
  2. 清洗:去掉工具调用噪音、重复内容、错误信息,对敏感内容做脱敏。
  3. 提取:把长对话拆成一条条原子化记忆,比如一个决定、一个偏好、一个事实。
  4. 存储与索引:把记忆文本向量化,保存到本地数据库。
  5. 检索与注入:面对新问题,用语义相似度捞回相关记忆,注入下一次请求。

我个人选择的存储方案是 SQLite 加本地向量索引,而不是单独部署一套向量数据库。原因很实际:个人项目的数据规模通常只有几万条记录,SQLite 足够快,不需要额外维护一个重服务。嵌入模型也建议跑在本机,避免把对话内容持续提交到外部接口。下面这些实现,我按社区同类个人工具最常见路径来拆解,你也可以根据自己的偏好换成其他数据库或者在线嵌入服务,整体框架不会变。

2. 核心实现:数据模型、记忆提取与语义检索

2.1 三张表撑起整套存储

claude-mem 的存储层不需要设计得很复杂,三张表就能完整表达“会话、消息、记忆”三者的关系。我用的是 SQLite,先看建表语句:

CREATE TABLE sessions ( id TEXT PRIMARY KEY, project TEXT NOT NULL DEFAULT '', channel TEXT, started_at INTEGER, ended_at INTEGER ); CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, -- user / assistant / tool content TEXT NOT NULL, processed INTEGER DEFAULT 0, created_at INTEGER ); CREATE TABLE memory_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, scope TEXT NOT NULL DEFAULT 'global', -- 项目级、会话级、全局 type TEXT NOT NULL DEFAULT 'fact', -- decision / preference / fact / warning content TEXT NOT NULL, embedding BLOB, importance REAL DEFAULT 1.0, access_count INTEGER DEFAULT 0, last_access_at INTEGER, created_at INTEGER, expires_at INTEGER ); CREATE INDEX idx_memory_scope ON memory_items(scope);

sessions 表记录每一次会话来源和所属项目;messages 表保存原始消息内容,同时用processed字段标记这一轮是否已经做过记忆提取,避免重复处理;memory_items 表是核心,它保存的是“能长期使用的记忆”,不是原始流水账。

这里有个容易被忽略的点:不要把原始消息直接塞进 memory_items。原因有两个。第一,原始消息太长,检索时会浪费大量 top-k 名额;第二,一条消息里的信息往往是复合的,用户可能在同一句话里既提需求又给偏好,直接存整句会大幅降低后续召回的准确率。所以 messages 表负责“留痕”,memory_items 表负责“提纯”,两者职责分开,后期排查问题和调检索效果都会轻松很多。

2.2 记忆不是流水账,是原子化条目

记忆提取这一步直接决定工具好不好用。如果你只是把每轮对话原封不动攒起来,数据量变大以后,检索到的内容会越来越杂,注入时会显得又乱又占空间。我实际使用的规则是:一条 memory item 只记录一个事实、一个决定或一个偏好,长度控制在几十个中文字符以内,最多不超过一百字。

例如某次对话里你确定了图像处理工具的导出方案,存下来的记忆应该是类似这样的结构:

{ "scope": "project:image-tool", "type": "decision", "content": "导出流程采用流式处理,优先降低内存占用,而不是提高吞吐量", "importance": 3, "created_at": 1720000000 }

提取时我建议先用一套轻量规则做清洗:把空消息、错误重试消息、纯噪音文本直接过滤掉;再把工具调用造成的重复片段合并;最后才考虑用模型做摘要。不要每轮都调用大模型去总结,成本高而且不稳定。规则处理完大部分文本后,再对剩余内容做一次摘要提取,效果和性价比都会更好。

除此之外,建议给每条记忆打上类型标签。fact 表示客观事实,decision 表示曾经敲定的决策,preference 表示用户偏好,warning 表示需要避开的坑。类型标签在检索排序里很有用,检索时可以根据当前问题场景提高某种类型的权重。比如当前问题明显是在问偏好,那 preference 类型的记忆就应该排到前面。

2.3 向量检索和相似度排序怎么做

记忆存好了,下一步就是“捞回来”。这里我推荐用向量检索,而不是纯全文搜索。原因是用户提问题的说法常常和当初记录时的说法不一样。比如你当初记录的是“图片导出格式统一用 WebP”,后面问的是“导出图能不能压小一点”,全文搜索匹配不到,但向量检索能通过语义关联找到它。

实现上,先用本地小模型把每一条 memory_item 转成向量,保存进 embedding 字段。查询时把用户当前问题也转成向量,然后计算余弦相似度。这里我写了一个简化版的检索函数:

import numpy as np from sentence_transformers import SentenceTransformer model = SentenceTransformer("your-local-embedding-model") def search_memories(query, scope, top_k=5): query_vec = model.encode(query, normalize_embeddings=True) rows = db.execute( "SELECT id, content, type, embedding, last_access_at FROM memory_items WHERE scope = ?", (scope,) ).fetchall() now = time.time() scored = [] for row in rows: emb = np.frombuffer(row["embedding"], dtype=np.float32) emb = emb / np.linalg.norm(emb) similarity = float(np.dot(query_vec, emb)) # 新鲜度权重:越久没被访问,加分越少 interval = now - row["last_access_at"] if row["last_access_at"] else 0 recency = max(0.0, 1.0 - interval / (30 * 86400)) score = 0.75 * similarity + 0.25 * recency scored.append((score, row)) scored.sort(reverse=True) return scored[:top_k]

这个排序综合了两个维度:语义相关度和新鲜度。只按语义相似度取结果,容易捞回一些“内容相关但已经过期”的决策;只按时间取结果,又容易忽略真正语义匹配的旧记忆。0.75 和 0.25 这两个权重是我调出来的默认值,你可以根据自己的数据特性调整。

另外,建议给检索设置一个相似度下限,比如 0.6 以下的结果直接丢弃。如果不设下限,即使没有任何相关记忆,也会硬塞几条最接近但其实完全无关的内容出来,反而污染新会话。

3. 从零跑通:安装、写入、注入和客户端接入

3.1 初始化数据库和环境

整个项目我建议用 Python 写,因为它处理文本、向量、SQLite 都顺手。初始化步骤大概是这样:

mkdir claude-mem && cd claude-mem python -m venv .venv source .venv/bin/activate pip install sentence-transformers sqlite-vec pyyaml claude-mem init

init 命令会创建 data 目录、初始化三张表、生成默认配置文件。配置文件长这样:

database: ./data/claude_mem.db embedding: model: your-local-embedding-model local: true retrieval: top_k: 5 min_similarity: 0.6 recency_weight: 0.25 injection: enabled: true max_tokens: 600 placement: system scope: global

这里最关键的配置是scope。如果你同时跑多个项目,scope 必须区分开。比如 A 项目相关会话写入时 scope 是project:a,B 项目写入时是project:b。查询时也只查当前会话对应的 scope,能避免绝大多数记忆串味问题。

3.2 写入链路:把对话增量落库

要接 Claude 的对话流,最简单的做法是在请求发送前和收到响应后各插入一个钩子。发送前记录 user 消息,收到后记录 assistant 消息。为了不重复记录,我会为每个会话保存一个last_processed_message_id游标,每次只处理新增部分。

核心写入逻辑简化下来是:

def store_message(session_id, role, content, project): db.execute( "INSERT INTO messages (session_id, role, content, processed, created_at) VALUES (?, ?, ?, 0, ?)", (session_id, role, content, time.time()) ) db.commit() # 如果这一轮已经积累完整,就触发记忆提取 extract_memories_for_session(session_id, project)

记忆提取的函数并不复杂,核心是遍历未处理的消息,清洗后生成 memory_items,再更新 processed 标记。需要注意一个坑:提取操作不要立即同步执行,最好放到异步任务里。否则每次对话结束都要等嵌入模型跑一遍,响应延迟会非常明显。我实际的做法是把提取任务丢进一个后台队列,用户无感,记忆晚几秒写入完全没有影响。

还有一点很重要:失败的尝试不要记。如果 Claude 某次请求因为参数报错或者超时被中断,这条 assistant 消息就不应该进入长期记忆。否则你会把一次错误作为事实记忆下来,后面可能反复误导自己。

3.3 注入链路:构造带记忆的 system prompt

写入完成后,最关键的一步是把记忆正确送回新会话。我建议注入到 system prompt,而不是拼在 user prompt 末尾。因为放进 user prompt 容易被模型当成普通对话内容处理,权重不够;而 system prompt 在模型眼里属于“全局指令”,记忆的存在感更强,行为也更稳定。

核心函数可以写成这样:

def build_memory_block(query, scope, budget_tokens=600): hits = search_memories(query, scope, top_k=8) parts, used = [], 0 for score, row in hits: token_est = max(1, len(row["content"].encode()) // 3) if used + token_est > budget_tokens: continue parts.append(f"- [{row['type']}] {row['content']}") used += token_est if not parts: return "" return "## 长期记忆片段\n" + "\n".join(parts)

然后在每次请求构造 messages 时,把这个 memory block 拼到 system 内容的前面:

你是一个拥有长期记忆的助手。以下内容来自历史会话,可能存在过期信息,请根据当前问题合理参考。 ## 长期记忆片段 - [decision] 导出流程采用流式处理,优先降低内存占用 - [preference] 技术方案文档默认使用中文编写

为什么前面要加一句“可能存在过期信息,请合理参考”?这很重要。没有这句引导时,模型会把记忆当成不可置疑的硬规则,哪怕其中一条已经失效也会照做。加上这句话后,模型遇到明显过时的决策会自动用当前上下文修正,而不是死板执行。这个小改动在实际使用中明显降低了错误回答率。

3.4 桌面客户端的本地 MCP 接入

如果你主要用的是 Claude 桌面客户端,不希望自己写钩子,可以用 MCP 的方式接入。MCP 是一种标准化的上下文协议,这里我把它当作一个本地通道:把 claude-mem 包装成一个 MCP server,客户端通过标准输入输出与它通信,无需开启额外网络服务。

配置片段大概是这样:

{ "mcpServers": { "claude-mem": { "command": "python", "args": ["./mcp_server.py"], "env": { "CLAUDE_MEM_HOME": "/absolute/path/to/claude-mem/data" } } } }

MCP server 只需要暴露两个工具:get_memory和save_memory。get_memory 接收 query 和 scope,返回经过排序和截断的记忆候选;save_memory 接收 content、type、scope,写入记忆库。这样你在客户端里可以直接用自然语言说“帮我查一下之前关于导出流程的决定”,客户端就会调用 get_memory 拿到备选片段。

把读写操作拆开是一个很实用的安全设计。客户端并不需要直接访问数据库文件,所有数据操作都通过工具接口完成。这样即使以后接入别的客户端,权限边界也是清晰的。

4. 长期运行容易踩的坑:污染、超限、过期与隐私

4.1 记忆污染:A 项目的记忆跑到 B 项目

记忆串味是我遇到的第一个大坑,也是最容易翻车的问题。一开始我把所有记忆都存成 global 作用域,结果在某笔记工具项目里聊技术选型时,图像处理项目里的导出方案记录也被注入进来,Claude 给出的建议瞬间变得牛头不对马嘴。

问题根源在于检索时没有限定 scope,或者写入时漏掉了 project 字段。修复办法有三个层级:

  1. 写入时强制校验 project 参数,没有项目归属的一律默认存到 global。
  2. 查询时分类过滤,先查当前项目级记忆,查不到或结果不足再补全局记忆。
  3. 在注入模板里额外标注每条记忆所属项目。

三层都做齐后,串味问题基本就消失了。我现在已经养成一个习惯:每次新开会话,第一件事就是确认 scope 传对了,否则后面所有检索都会白做。

4.2 上下文超限:记忆注入也要做预算

第二个高频问题是上下文窗口被记忆塞爆。刚开始做注入时,我只顾着把 top-k 的结果都塞进去,结果几条又长又重复的记忆占掉了大量上下文,真正要用的业务逻辑反而被挤到后面,回答质量直线下降。

解决思路是给记忆注入设置明确的预算。以下是我目前使用的参数,可以直接参考:

配置项推荐值说明
top_k5候选数量不要超过 8,5 基本够用
min_similarity0.6低于阈值的记忆直接丢弃
memory_budget600 tokens记忆占用上下文的上限
recency_weight0.25新鲜度在排序中的权重
max_item_length80 字单条记忆最长长度

记忆不是越多越好。当相关记忆太多时,应该做动态截断:按分数从高到低依次放,直到达到 budget 上限。宁可少放几条,也不要硬塞把整个上下文占满。我见过不少类似工具最终失败,不是记忆能力不行,而是注入太激进,导致正常对话被记忆挤得无法呼吸。

4.3 过期记忆怎么淘汰

时间一长,记忆里必然开始混入过期信息。最常见的有三种:已经推翻的技术决策、已经更换的参数配置、已经变化的用户偏好。

机械地删掉旧记忆并不明智,因为部分旧信息仍然有参考价值。我更推荐用“冷却+软过期”机制。每条记忆都有 last_access_at 字段,如果连续三十天没有被检索命中,新鲜度分数自动降到接近零,进入候选池尾部。同时,如果某条记忆设置了 expires_at 且已经到期,注入时就直接跳过,直到你手动确认是否续存。

还要提供一个手动遗忘入口。我会定期跑一条类似claude-mem review --inactive 30的命令,列出超过三十天没命中的记忆,人工扫一眼,该删的删,该留的留。真正好用的记忆工具不是把所有东西都留着,而是知道什么该忘。

4.4 隐私底线和遗忘机制

既然记忆工具记录的是你真实对话,隐私就必须当成硬指标,不能只是顺手想想。我的设计原则是本地优先:数据库只存在本机,嵌入模型也只用本地模型,对话内容不因为记忆功能而额外发送到任何外部接口。

再往深一层,对敏感内容要做主动拦截。我在配置里放了forbidden_keywords列表,写入时如果命中关键字就直接跳过,不进入记忆库。同时支持.claude-memignore路径规则,指定某些会话或者某些目录不参与记忆提取。

遗忘机制也必须一删到底。工具里要有claude-mem wipe这样的命令,能清空当前项目所有记忆,包括数据库中的文本记录、向量字段和索引文件。不要只删 metadata 留 content,那等于没删。如果你准备把 claude-mem 用在涉及他人数据或者机密信息的场景,请务必先确认数据存储边界和删除策略,这个底线不能含糊。

5. 沉淀下来的几个使用习惯

5.1 主动检索通常比无脑注入更可靠

工具做到后期,我把“自动注入”改成了“主动检索优先”。早期每一次新会话都会自动把历史记忆全塞进 system prompt,结果发现大部分记忆其实和当前问题无关,模型还要额外花力气判断哪些该用、哪些该忽略。

现在的模式是:默认情况下,只注入项目级的基础背景记忆,比如项目名、技术栈、长期偏好;遇到具体问题时,再主动调用检索接口,把相关的历史决策即时捞回来。这个改动让输出质量提升得非常明显。你可以理解为,想让一个记性不好的同事帮忙,最可靠的方式不是让他开会时念完整本笔记,而是你开口问的时候,他刚好翻到那一页。

5.2 记忆工具应当保持简单稳定

最后分享一点心态上的经验。claude-mem 可以做的功能很多,能加标签系统、全文搜索、多人协作、可视化页面,但我始终没有把工具往复杂方向推。原因是记忆工具的核心价值是“稳定记录 + 准确召回”,一旦引入过多关联功能,检索链路变长,出错的概率也会直线上升。

我对这套小工具的最终定位就是一张聪明的索引卡片:把最不该忘的事实、决策、偏好保存下来,在需要的时候快速翻出来。越简单,越稳定,也就越值得长期用下去。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:23:25

把 Canal client-adapter 升到 ES 7.3:只改一个 .value,远远不够

一次改造实战:版本对齐、编译报错、语义变化和留痕 搜索项目要用 Canal 把 MySQL 的变更增量同步到 Elasticsearch。问题来了:下载的 client-adapter(客户端适配器)默认只支持到 ES 6.4,而项目里用的是 ES 7.3。为了版…

作者头像 李华
网站建设 2026/10/14 13:22:14

TradingAgents vs AutoGen vs LangGraph:谁才是 AI 交易圈的顶配底座

TradingAgents vs AutoGen vs LangGraph:谁才是 AI 交易圈的顶配底座 【免费下载链接】TradingAgents-AI.github.io TradingAgents: Multi-Agents LLM Financial Trading Framework 项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-AI.github.io…

作者头像 李华
网站建设 2026/10/19 19:24:07

AI味从何而来?用Claude Opus 5.5实测解析与去AI味实操指南

Claude Opus 5.5发布之后,我第一时间把手头能想到的文本任务都试了一遍。总结报告的提纲、产品发布文案、公众号长文、客服话术优化、甚至一段vlog的旁白……它确实聪明,逻辑极其严密,几乎不需要我再去做结构性的大改。但把生成的内容并排放在…

作者头像 李华
网站建设 2026/10/15 3:45:05

一键配置JAVA_HOME:跨平台JDK环境变量自动化脚本全解析

换电脑、重装系统、接手一台满是历史遗留问题的服务器,配 JAVA_HOME 这件事几乎每次都会准时找上门。我以前也是老老实实打开.bashrc,看一眼安装路径,手动粘一行 export,再 source 一下,看起来挺顺利。但真正把项目跑起…

作者头像 李华
网站建设 2026/10/19 10:47:30

华力重工固定式液压货梯 5吨载重升降平台 车间楼层货物转运

固定式液压货梯市场前景广阔,工业货物垂直转运需求持续攀升随着制造业转型升级和仓储物流行业的快速发展,车间楼层之间的货物垂直转运效率已成为影响企业生产效率的重要因素。传统的叉车配合坡道转运方式存在效率低、安全隐患多、场地占用大等问题&#…

作者头像 李华
网站建设 2026/10/17 15:06:07

PyCharm安装配置全指南:从环境搭建到高效开发

1. 这不是“装个软件”那么简单:PyCharm背后的真实工作流很多人第一次点开PyCharm官网下载页面时,心里想的只是“赶紧装上,好写Python代码”。但我在某高校实验室带过三届学生、给五家中小技术团队做过开发环境标准化咨询后发现:9…

作者头像 李华