资讯详情

资讯详情

给Claude装上记忆层:从对话采集到精准注入的完整实现

我先是自己在本地折腾过一版后来又把社区里几个叫相同名字的开源实现翻了个遍发现大伙儿的思路几乎是收敛的采集对话、后台提炼、本地存储、按需召回、二次注入。这个套路不复杂但细节非常多。这篇文章不打算写成软文或者说明书我只把这套东西的原理、踩过的坑、实际能跑通的做法全部摊开讲一遍希望能给想给Claude补记忆的朋友省点时间。1. 整体架构拆解一个记忆层该有哪些部件1.1 四个核心环节采集、提炼、存储、注入如果只给一句话总结claude-mem做的事情就是“把无状态的对话变成有状态的个人知识库”。但真正落地的时候你会发现它其实是一条完整流水线拆开看至少包含四个环节。第一个环节是采集。来源很明确你和Claude的每一轮对话。采集不是简单地把对话原文丢进数据库因为原文里充满了废话、重复、临时性内容。真正值得留下的是那些“可复用的信息”你承诺过的技术方案、用户的偏好、某个项目的关键路径、一个反复出现的报错及其解法。原始对话是原料不是成品。第二个环节是提炼。这一步通常交给大模型自己做。每隔一段时间比如对话累计超过一定长度或者每5分钟异步跑一次把新增的对话片段发给模型让它按照预设结构抽取“记忆条目”。抽取的格式可以很简单就是一个包含role、topic、fact、timestamp的JSON。你也可以让模型判断这条记忆的持久性等级——有些是临时状态比如“今天在调登录接口”有些是长期偏好比如“我习惯用TypeScript”分级之后方便后续检索时加权。第三个环节是存储。存储层不一定需要很重的东西。我的经验是SQLite在第一版里完全够用一张表存对话摘要一张表存事实/偏好再加一张索引表。等数据量到了几十万条或者你确实需要语义检索的时候再考虑上向量数据库。没必要一上来就把架构搞复杂记忆系统的瓶颈通常不在存储而在召回策略。第四个环节是注入。这是最容易被忽略但恰恰最关键的一步。很多人的第一版失败在记忆存了一堆但用的时候要么不相关要么太多太杂反而把上下文窗口挤爆了模型表现变得更差。注入讲究的是“少而精准”宁可一次只给三条强相关的记忆也不要一股脑塞二十条让模型自己挑。这四个环节串起来才是一个完整的记忆闭环。缺了任何一个你拿到的都不是记忆系统只是另一个聊天记录备份工具。1.2 存储选型SQLite是首选向量库什么时候上我见过不少人一上来就用向量数据库结果卡在安装依赖、Embedding模型选型、费用控制这些事上最后项目搁浅。实际上对于个人使用或中小规模项目SQLite配合关键词索引能覆盖大多数场景。为什么SQLite够用因为记忆召回这件事大部分时候靠的是“关键词 时间衰减 对话主题”的组合策略。Claude的对话虽然长但每个人使用它的范围通常很窄你反复问的东西往往高度重复。通过主题字段和关键词做过滤再按recency排序召回准确率就已经不低了。什么时候才需要向量库当你的记忆条目超过几万条且用户的问题经常是“我之前好像提过一个想法跟某个方案有关但我不记得原话”这种模糊召回时就得上向量检索了。我个人的阈值判断是如果召回结果里Top 5经常出现完全无关的内容说明纯关键词方式已经到瓶颈此时再引入Embedding。选向量库也有讲究。优先考虑轻量的LanceDB、Chroma或者sqlite-vss不要一上来就部署Milvus或者Weaviate这类重型服务。个人项目追求的是“五分钟能爬起来”而不是“支持十亿向量”。CLI工具跑在本地存储文件直接落盘配合定时备份就够了。1.3 注入策略系统提示注入 vs 上下文拼接记忆注入有两种主流做法。第一种是拼到系统提示词里告诉Claude“以下是你之前和用户一起沉淀下来的关键事实请在回答时自然使用这些信息不要直接复述”。这种方式适合持久性高、几乎每次对话都需要的记忆比如用户的编程语言偏好、项目名、团队约定等。第二种是拼接在用户消息前面作为背景上下文。这种方式适合“临时召回”的记忆比如“上次我们讨论过三个方案结论是……”这类信息。因为它只对当前这轮对话有效不需要让模型长期记住。两种注入方式的触发时机也不一样。系统提示注入应该在每次对话开始时固定执行凡是标记为global的记忆都往里塞上下文拼接则应该按需触发检索到相关记忆才拼接。如果你只做一个通用版我建议先把前者做扎实把全局记忆维护好再考虑按需召回。很多usage场景其实对“按问题检索”没那么敏感反而是“让Claude始终记得你是谁”的体验提升最明显。2. 核心细节解析与实操要点2.1 对话分段与记忆抽取的参数设计对整个系统影响最大的不是存储而是“怎么切分对话去做提炼”。切太碎信息上下文不足抽出来的记忆质量差切太长一次请求的token消耗高而且摘要会丢失细节。我自己实测下来一个比较稳的分段策略是按“语义轮次”切而不是按固定长度。一轮对话以用户发了新的提问为起点以模型完整回答为终点。把连续5到10个轮次打包成一个block如果有人在调试代码则把同一主题下的所有轮次合并成一个更大的block。这个合并逻辑可以用最简单的规则相邻轮次之间如果共享同一个top-level主题关键词就继续合并直到主题切换。抽取时的模型参数也需要单独调。一定不要用和生产对话相同的temperature建议低一点0.2以下因为记忆抽取是信息压缩任务需要稳定和忠实不需要创造性。在提示词里要明确三条约束第一只提取事实和偏好不提取临时情绪第二如果对话里有明确的关键词或代码片段原样保留不要转述第三每条记忆必须自带“何时、何背景下产生”的元信息否则后续无法做时间衰减。抽取频率也值得控制。实时抽取太重每轮对话都请求一次容易击穿费用预算。我采用的方案是每累计3000字左右触发一次异步抽取且只在本地队列空闲时执行。如果你用的是Claude API异步能有效规避速率限制。2.2 记忆去重与冲突解决跑一段时间你一定会遇到两个问题重复记忆和冲突记忆。重复的原因是同一件事在多个block里被反复抽取比如你每次问Claude“帮我看看这个报错”它每次都抽出了一条“用户遇到ModuleNotFoundError”。冲突更麻烦比如前两天你说“项目的数据库用Postgres”今天又说“准备换成SQLite”如果不处理旧记忆和新记忆同时存在Claude会被自己搞晕。去重我用的是比较朴素的三步法。第一步对每条记忆算出“主题 关键实体”的指纹用SHA256哈希一下第二步如果新记忆的指纹在已存储的记忆里出现过就比较语义相似度相似度超过0.85就把它当作重复项合并到旧条目里只更新时间戳第三步如果指纹不同但语义相似度高则将新记忆标记为“替代候选”需要再攒两条相似度证据才决定是否替换旧记忆。冲突解决我推荐“时间戳优先 可信度加权”。默认信任最近的那条记忆但把旧的标记为“被替代”不会立即删除方便回溯。如果你希望系统能主动提示冲突可以在提炼prompt里加一句“如果发现与已有记忆矛盾的新事实请单独以conflict类型输出”。这样你就可以在界面上看到到底哪里产生了矛盾而不是让模型默默覆盖。2.3 隐私、成本、上下文窗口的平衡这是实操里最容易被忽略的一块。Claude记忆系统的本质是把你的对话内容常驻在本地文件里这跟云端的聊天记录完全不同。如果你在公司电脑上跑这个工具务必注意别把客户敏感信息写进本地数据库。我的习惯是加一个敏感词过滤层正则匹配邮箱、手机号、密钥格式发现疑似敏感信息就直接不落库或者用占位符替换。成本控制主要卡在提炼请求上。每次提炼都要消耗一次LLM调用如果对话量大一天几十次API调用也是一笔开销。我的做法是只在对话“有明显新信息”时才触发提炼通过一个轻量的本地TF-IDF对比计算新增对话里是否出现了之前从未出现过的关键词如果全都是旧词就跳过提炼。这个前置过滤能把提炼请求减少一半以上。上下文窗口的问题则出现在注入阶段。Claude的上下文窗口虽然大但不是无限大而且你塞进去的内容会挤占模型真正输出的空间。我给自己定的红线是注入的记忆总量不超过当前上下文窗口的8%。如果窗口比较紧张就只注入全局记忆按需召回的部分截取成单条不超过200字的摘要再给。3. 实操从零把一个“claude-mem”跑起来3.1 初始化项目与依赖我的实现用Python因为生态最省事。先建目录初始化虚拟环境然后装依赖核心是anthropicClaude API客户端、sqlite3标准库、json再加一个轻量的yaml做配置。如果后续要上向量检索再加lancedb和sentence-transformers。初始化时先建立数据库表结构。我推荐至少三张表CREATE TABLE IF NOT EXISTS conversations ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, started_at TEXT NOT NULL, summary TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, conv_id INTEGER NOT NULL, memory_type TEXT NOT NULL CHECK (memory_type IN (fact, preference, decision, conflict)), content TEXT NOT NULL, topic TEXT NOT NULL, entities TEXT NOT NULL DEFAULT [], confidence FLOAT NOT NULL DEFAULT 0.8, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, expiry_at TEXT ); CREATE TABLE IF NOT EXISTS memory_recalls ( id INTEGER PRIMARY KEY AUTOINCREMENT, memory_id INTEGER NOT NULL, query_text TEXT NOT NULL, recalled_at TEXT NOT NULL );记忆提取时用Claude模型我常用claude-sonnet-4或claude-3-5-sonnet这类档位追求速度的话上haiku也够。提取提示词我写了一个稳定版核心逻辑是“只抽取、不总结、不添加不存在的信息”。3.2 编写记忆提取模块提取模块的核心函数不长我把它简化一下贴出来注意参数已经过我实测import anthropic import sqlite3 import json client anthropic.Anthropic() SYSTEM_PROMPT 你是一个记忆提取器。你的任务是从对话中提取可复用的记忆条目。 只提取三类信息 1. fact用户或项目相关的客观事实 2. preference用户的偏好或选择 3. decision已经做下的决策及原因 要求 - 每条记忆必须简洁不超过30个字 - 必须包含topic字段用于后续检索分类 - 必须原样保留代码片段、文件名、报错文本不转述 - 不要输出对话中没有的信息 - 如果与已知事实冲突单独以conflict类型输出 返回JSON数组不要返回其他内容。 def extract_memories(conversation_block: str) - list[dict]: response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1500, temperature0.1, systemSYSTEM_PROMPT, messages[{role: user, content: f下面是一段对话请提取记忆\n\n{conversation_block}}] ) raw response.content[0].text try: return json.loads(raw) except json.JSONDecodeError: # 模型偶尔会多输出话简单兜底 start raw.find([) end raw.rfind(]) 1 return json.loads(raw[start:end])这里的温度设0.1是我踩过坑之后定的。之前设0.7结果模型会“自由发挥”抽出了对话里根本没提过的内容污染记忆库。另外一定要让模型输出纯JSON而不是带Markdown代码块否则解析逻辑会变得很啰嗦。提取后的写入逻辑也很简单检查指纹去重后直接插入memories表。关键的指纹计算可以用topic content的哈希但注意content因为模型转述可能不完全一致所以我实际上用的是topic entities组合算指纹。3.3 编写记忆检索与注入模块检索模块我实现了两种模式。全局模式在每次对话开始时注入拉取所有记忆类型为preference和decision、且updated_at在最近90天内的条目按updated_at降序取前10条。按需模式则用关键词检索把用户当前消息里的关键词、实体抽出来拼成SQL模糊匹配再按时间衰减排序。时间衰减公式我用的是业界常见的指数衰退score 0.6 * keyword_score 0.4 * recency_score其中recency_score exp(-days_since_update / 30)。也就是说30天后保留约37%的权重90天后几乎不再影响排序。实际效果是近期记忆优先远期但高度相关的关键词匹配还能保底。注入组装成字符串后放进系统提示词里。我最终采用的格式是Below are remembered facts from previous conversations with this user. Use them naturally if relevant, but do not mention them explicitly. memories - [fact] 项目使用Python 3.11 FastAPI数据库用SQLite - [decision] 登录模块采用JWT方案不做session存储 /memories这里有一句话必须加进去“dont mention them explicitly”否则Claude每次都会把记忆的来源说出来“根据你之前的对话我了解到……”观感非常糟糕。3.4 与Claude CLI或API的对接方式如果你用的是Claude官网或Appclaude-mem只能以“外部辅助”的形式存在定期把聊天记录导出、离线提取记忆然后在下次输入时手动粘贴相关记忆摘要。这种方式很麻烦但能跑通。如果你用的是API或者Claude Code这类可编程接入方式体验就好得多。我的推荐是把claude-mem做成一个轻量中间层在调用messages.create之前先执行检索再把结果拼进system参数。核心伪代码def chat(user_input: str, history: list, session_id: str) - str: recalls retrieve_memories(user_input) system base_system build_memory_injection(recalls) messages history [{role: user, content: user_input}] response client.messages.create( modelclaude-sonnet-4-20250514, systemsystem, messagesmessages, max_tokens4096 ) # 对话结束后异步触发提炼 enqueue_extract(messages [response_message]) return response.content[0].text如果你用的是Claude Code还有更轻量的接法把全局记忆写入项目根目录的CLAUDE.md文件这样每个会话启动时Claude Code会自动加载。按需召回则单独做成一个子命令比如claude-mem recall 那个登录报错把结果复制进对话里。这样做的好处是不需要动Claude Code本身的配置坏处是自动化和灵活性差一些。4. 常见问题与排查技巧实录4.1 记忆总是召回不到这是频率最高的问题。我排查时通常会按下面的顺序过一遍现象可能原因解决思路能搜到但排序靠后关键词匹配度低降低recency权重提高keyword权重完全搜不到记忆压根没入库检查提炼进程是否被限流/跳过搜到的是无关内容分词过于粗暴增加实体识别按topic过滤全局记忆不生效注入被系统提示前缀截断检查拼接顺序其中“记忆没入库”是最隐蔽的坑。我遇到过提炼任务明明执行了但SQLite写入失败原因是数据库被另一个进程锁住。解决方式是启用WAL模式并且把所有写操作放进一个连接池里串行执行。4.2 上下文被历史记忆撑爆这个问题的根源是注入策略太激进。我早期会把所有相关记忆都压进上下文结果一个长对话跑到后期记忆占了小半窗口模型的表现反而下降。后来定了一个硬规则无论全局还是按需单次最多加入10条记忆每条不超过80个token。如果检索结果有20条只保留分数最高的10条。另外我加了“记忆过期”机制。有些记忆明显是短期的比如“今天在调试OAuth的回调地址”这种应该在48小时后自动降权7天后清理。做法是给expiry_at字段设定时间检索SQL里默认过滤掉已过期的条目。4.3 重复记忆和事实冲突重复的问题靠指纹去重能解决大部分但冲突始终是硬骨头。我遇到过一个典型场景周一跟Claude说“项目部署用Docker Compose”周五又说“改成了Kubernetes”结果系统里同时存在两条decision互相矛盾。Claude在回答时随机选择一条逻辑完全是混乱的。我现在强制要求提取模块把decision类型的记忆附带reason显示“什么促使了这个决定”。当新决策的语义相似度超过阈值时不是直接覆盖而是把旧decision标记成superseded并且在注入时只给最新的。这个逻辑用SQL就能实现不需要额外引入复杂的图数据库。4.4 运行成本失控提炼请求是大头。每个对话block一次API调用如果每天有几十个block花费确实不少。除了前面提到的TF-IDF前置过滤我还有两个省钱技巧。一是把多个小block合并成一个缓冲区攒到5000字左右再一次提炼二是用最便宜的模型跑提炼用haiku而不是sonnet因为提炼只是抽取信息不需要复杂的推理。做完这些优化后我实测一个每天约两万token对话量的个人项目提炼API调用从每天的40多次降到了8次左右成本下降超过70%同时记忆质量基本没下降。5. 关于claude-mem的几条经验总结这套系统我前后迭代了三周最大的体会是记忆系统的价值不在“存了多少”而在“召回的对不对”。存进去再多的记忆如果召回的时机、颗粒度、注入方式不对对大模型来说就是噪音。与其费尽心思提升存储容量不如把精力放在提炼质量、去重策略和注入控制上。另外有一件事让我出乎意料给Claude加记忆之后使用习惯会发生改变。以前我会故意把问题写得很完整把所有背景交代一遍因为反正它下一轮就忘了。现在有了记忆层我提问变得随意很多很多上下文默认它会记得。这种体验上的变化比单纯“答得更准”重要得多。最后给一个非常实在的建议如果你是新手第一版不要贪大求全。先做一个只存全局偏好、只做系统提示注入的最小版本跑通之后感受一下“它能记住我”这件事对工作流的真实影响再决定要不要上按需召回和向量检索。很多人在做记忆工具时死在第二周就是因为想一次把记忆系统做完整结果卡在某个细节上丧失了动力。先把最小闭环跑起来你会发现后续每个优化都能产生即时反馈这个项目才能持续滚下去。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →