ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

为Claude构建长期记忆系统:从无状态到持久记忆的完整实践

为Claude构建长期记忆系统:从无状态到持久记忆的完整实践 做了一段时间的 AI 应用之后我最大的一个感受是Claude 这类模型确实聪明但“记忆力”差得离谱。新开一个会话它就像完全失忆一样不记得你上周定的方案不记得用户反复强调的偏好甚至同一个问题能问三遍。后来我开始折腾 claude-mem给 Claude 补上一层长期记忆系统总算把这副“金鱼脑”治得差不多了。这篇文章我会把从设计到落地的完整过程写下来包括架构取舍、代码实现、踩坑记录和评测方法。不管你是做 AI 助手、知识库问答还是纯个人效率工具都可以直接照着抄。1. 为什么需要 claude-mem先理解 Claude 的“记忆缺口”1.1 无状态 API 与对话上下文的本质限制很多人第一次接触 Claude API 时会忽略一个关键前提它本质上是无状态的。每次请求你都要把完整的 messages 数组发送过去模型只基于当前输入和上下文窗口做推理服务端不会帮你保存任何“历史记忆”。这意味着多轮对话的记忆责任完全落在客户端应用身上。上下文窗口再大也有上限比如 200k token但长上下文并不等于长期记忆。你把所有历史消息一股脑塞进去首先 token 成本会快速膨胀其次无关信息会稀释真正重要的指令模型容易被海量旧的寒暄带偏。更麻烦的是跨会话场景——用户今天聊完明天新开一个窗口历史消息根本没有机会被一起送进去模型自然什么都不记得。这就像一个每周换班的临时助理每次交接只靠一张越写越长的纸条纸条总有一天写不下、看不清、找不到重点。传统方案就是把纸条无限加长也就是在每次调用里拼接所有历史消息但这条路走到最后一定会撞上成本和效果的双重天花板。所以我们需要一种更结构化的方式让应用层帮模型“记住”该记住的东西。1.2 从“缓存”到“长期记忆”的思维转变很多团队一开始会把思路放在 prompt 缓存上但缓存解决的只是“重复计算的成本”它不产生任何新记忆。也就是说缓存能让你便宜地重复发送同一段历史但跨会话、跨窗口时那段历史根本不在缓存里。另一派思路是 RAG把文档切块、向量化、检索后拼进 prompt这解决的是“外部知识问答”而不是“用户和项目的持续状态”。claude-mem 这类方案的核心是把“记忆”当作独立的基础设施。它不再追求让模型自己记住任何事情而是在模型之外维护一套持续更新的记忆库从每轮对话中抽取重要信息、按类型组织、做冲突处理然后在下次对话前自动把最相关的记忆取回来注入到 prompt 里。这个转变很关键你不再依赖模型的隐性记忆能力而是显式地管理数据资产。只要记忆库存在哪怕今天用 Claude明天换成别的模型记忆本身不会丢。这也是我认为所有认真做 AI 产品的人都需要补上的一课。2. claude-mem 的设计拆解记忆层不是简单存聊天记录2.1 记忆单元对话切片、实体关系与偏好元数据大多数初学者做记忆系统最容易犯的错误是把“记忆”等同于“聊天记录”。但 claude-mem 真正该存的不是流水账而是从流水账里提炼出来的高价值信息。我的做法是把记忆分成三类每一类对应不同的用途。第一类是事实记忆包括用户的行业背景、当前项目名称、技术栈、团队成员、截止日期等客观信息。第二类是偏好记忆包括用户喜欢的回答风格、必须避免的表达方式、对某些方案的倾向等。第三类是任务状态记忆比如进行中的事项、已经拍板的决策、尚未完成的 TODO 列表。每条记忆中除了正文内容我还会带上几个关键元数据字段type 表示记忆类型timestamp 记录发生时间source_session_id 记录来源会话confidence 表示置信度scope 表示这条记忆属于哪个用户或项目。这个结构看起来简单但后来排查问题时帮了大忙——没有元数据支撑的记忆库只是一堆无法追溯的文本碎片。打个比方这就像写日记不是把每天几点吃饭、几点出门全部记下来而是只记“今天认识了谁、决定了什么、有什么待办”。模型也一样你让它记住所有细节它反而什么都用不好。2.2 记忆的写入与更新提取→结构化→回填记忆系统的第一个核心环节是写入也就是把对话内容变成结构化记忆。我不会把原始对话直接存进长期库而是先调用 Claude 对最近一段对话做一次“记忆提取”。提取 prompt 大概长这样要求模型从对话中找出用户明确陈述的事实、表达出的偏好、正在推进的任务并对每条候选记忆分类、去重、标记置信度最终输出 JSON。拿到 JSON 后下一步是冲突检测。先按实体和类型找到现有记忆比较新旧内容。如果新记忆与旧记忆说的是同一件事就根据时间戳和置信度决定是覆盖、合并还是保留旧版。如果新记忆和旧记忆直接矛盾不能盲目覆盖需要进入人工确认或标记为“待校验”。这一步的意义在于防止记忆库在不知不觉中变成一团互相矛盾的浆糊。全部处理完成后才把记忆回填到存储里。我建议把提取过程放在对话结束之后异步执行而不是每轮都同步阻塞。这样不影响主对话体验也方便批量条数控制。简单说写入逻辑不是“转写”而是“二次加工”。模型产出的记忆质量直接决定整个系统后续的效果上限。2.3 记忆的读取与检索相关性优先于时间顺序第二个核心环节是读取。用户发起新问题的时候你不能把整个记忆库都塞给模型也不能只拿最近几条。正确做法是先做相关性召回再做精排最后控制注入量。具体流程是先把用户当前 query 向量化在记忆库中做相似度检索取回 top-k 候选记忆。这一步用 embedding 模型成本低、速度也快。然后第二步会做一个小型 rerank判断候选记忆和当前问题的相关程度。我会额外加一个时间衰减权重因为对于任务状态类记忆越新的越重要但对于用户长期偏好时间衰减的影响可以忽略。召回结果按类型分组后会被拼装成一个memory块放到 system prompt 里再交给 Claude 回答。需要特别控制 token 上限。以 200k 上下文模型为例我会把注入的长期记忆控制在 1200 token 以内超过就只保留相关性最高的片段。历史对话太多时还可以先用摘要压缩再注入而不是全文堆砌。3. 从零搭建 claude-mem实操配置与代码示例3.1 环境准备与依赖选型先说依赖选型。整套 claude-mem 实现里最核心的组件包括 Claude API、一个 embedding 模型、一个向量存储以及少量胶水代码。Claude API 用官方 SDK也就是 anthropic 包。embedding 我建议先选 text-embedding-3-small理由很简单效果稳定维度低成本几乎可以忽略。如果你有本地化需求也可以换 bge-small 系列效果差别不大。向量存储这里需要看数据规模个人项目和中小团队场景直接用 Chroma 就足够零配置、免运维等到记忆条目超过百万级、需要高并发检索时再迁移到 Qdrant 这类服务。不要一上手就上重型组件维护成本会吃掉你的开发精力。还有一个选择是是否用 LangChain 之类的框架。我个人的建议是记忆系统这种业务逻辑非常定制化的东西直接用原生代码反而更清爽避免被框架的抽象绕来绕去。下面我给出的实现不依赖任何编排框架只用了最基础的库你也可以直接照搬。环境安装命令大概是这样pip install anthropic chromadb openai sqlite-utils如果你用本地 embedding 模型再加一个 sentence-transformers如果不需要本地模型保持简单即可。3.2 核心调用流程实现直接看代码。我先把记忆存储封装成一个极简的二维接口写入和召回。写入负责把结构化记忆存进 Chroma召回负责按 query 找到最相关的记忆。from chromadb import PersistentClient from openai import OpenAI embed_client OpenAI() class MemoryStore: def __init__(self, collection_nameclaude_mem): self.chroma PersistentClient(path./mem_db) self.collection self.chroma.get_or_create_collection( namecollection_name, metadata{hnsw:space: cosine}, ) def add_memory(self, content, mem_type, timestamp, session_id, confidence0.9, scopedefault): self.collection.add( documents[content], metadatas[{ type: mem_type, timestamp: timestamp, session_id: session_id, confidence: confidence, scope: scope, }], ids[f{session_id}-{timestamp}-{hash(content) % 100000}], ) def recall(self, query, top_k5, scopedefault): results self.collection.query( query_texts[query], n_resultstop_k, where{scope: scope}, ) mems [] for doc, meta in zip(results[documents][0], results[metadatas][0]): mems.append({ content: doc, type: meta[type], timestamp: meta[timestamp], confidence: meta[confidence], }) return mems这里有几个细节想强调一下。Chroma 的 PersistentClient 会把数据持久化到本地目录不需要额外启动服务。相似度空间我选 cosine因为它对文本向量的效果稳定。id 生成时我把 session_id、timestamp 和内容哈希拼在一起保证同一轮里重复写入不冲突。接下来是主对话流程。用户发来消息后先召回记忆再组装 system prompt再调用 Claude。对话结束后异步提取记忆并写回。from anthropic import Anthropic claude Anthropic() mem_store MemoryStore() def chat_with_memory(user_message, scopedefault): mems mem_store.recall(user_message, top_k5, scopescope) memory_block \n.join([f- [{m[type]}] {m[content]} for m in mems]) system f你是用户的长期协作助手。 以下是从历史对话中召回的相关记忆 memory {memory_block} /memory 如果记忆与当前对话明显冲突以当前对话为准并在回复中提醒用户可能发生了变更。 response claude.messages.create( modelclaude-sonnet-4-5, max_tokens1024, systemsystem, messages[{role: user, content: user_message}], ) async_extract_and_store(user_message, response.text, scopescope) return response.text这段代码里的 async_extract_and_store 就是刚才说的提取与回填。你可以把它做成一个后台任务比如用 FastAPI 的 BackgroundTasks也可以用一个简单的线程池来跑。这里不展开完整实现但核心逻辑就是把最新一轮 user message 和 assistant response 一起交给 Claude让它输出 JSON 记忆候选然后通过 mem_store.add_memory 写回。3.3 记忆注入模板与 system prompt 设计记忆召回之后怎么放进 prompt 里很有讲究。我的标准模板长这样你是用户的长期协作助手。以下是从历史对话中召回的相关记忆它们可能是事实、偏好或任务状态 memory item typefact timestamp2025-06-10T10:00:00Z用户主力开发语言是 Python团队规模约 10 人/item item typepreference timestamp2025-06-11T19:30:00Z用户不喜欢回复里出现客套话希望直接给结论/item item typetask timestamp2025-06-12T09:00:00Z正在推进登录模块重构计划两周内上线/item /memory 请遵守以下规则 1. 优先相信记忆中的事实 2. 若当前对话与记忆明显冲突以当前对话为准并提醒用户可能发生了信息变化 3. 不要因为缺少记忆就编造内容无法确定时可以明确说明。我特意把记忆放到memoryXML 块里而不是直接混在 system prompt 的纯文本里。实测下来Claude 对这类结构化标签的理解非常稳定既能准确读取也知道“memory”是一个外部注入的信息源而不是它自己前世留下的幻觉。多个不同类型条目分组排列也便于模型在回答时按类型取用。你还可以在模板里加入“如果记忆不足建议主动追问”的选项让助手在必要时主动补信息而不是硬答。4. 实战中容易踩的坑记忆污染、冲突与成本失控4.1 记忆污染错误信息会像谣言一样扩散如果只允许我说一个 claude-mem 实战里最危险的坑那一定是记忆污染。记忆系统的特点是一旦写入错误信息它会无条件参与后续所有对话的 prompt 组装。也就是说一条错误的记忆会像谣言一样传遍整个记忆网络反复影响模型输出。举个例子某次对话中模型猜错了用户所在公司的规模提取模块没有对信息源做甄别把“公司约 100 人”当成事实写进了长期库。之后用户再问任何与组织相关的问题模型都会基于这个错误前提回答而用户往往会困惑怎么今天说的话和之前完全对不上。要避免这个问题我的经验是三层过滤。第一层在提取 prompt 里明确要求只提取用户明确陈述的信息模型自己的推断不能作为 fact 保存第二层在写入前做置信度判断置信度低于 0.8 的候选记忆写入“待确认区”需要用户确认后才升为长期记忆第三层是周期性的记忆审计比如每周导出记忆库里的高影响 fact人工或自动检查是否有过时信息。千万别觉得这三步繁琐初始不做后面纠错的成本高十倍。4.2 冲突与过时多条记忆相互矛盾怎么办记忆库运行一段时间后几乎必然出现矛盾。用户上周说“我更喜欢 Python”这周说“新项目切到 Go 了”昨天说“上线时间是月底”今天说“计划推迟到下个月”。这些冲突如果处理不好模型就会时而按旧记忆答时而按新记忆答完全看检索结果心情。我总结了一套简单的冲突处理规则直接放进更新流程里判断场景旧记忆置信度新记忆置信度处理策略同一事实新时间戳高高新覆盖旧保留旧版用于审计同一事实新时间戳高低不覆盖新记忆进入待确认区同一事实新时间戳低高直接覆盖并记录变更来源明显相反偏好任何任何标记为冲突对优先使用最新同时请求用户澄清这套规则用代码实现也非常简单。每次写入前先按 scope 和实体名查询现有记忆如果存在同类型且实体相似度高的条目就按上表逻辑决定是否覆盖。处理完成后我会额外给被覆盖的记忆写一个 deprecated 标记而不是直接删除。这样万一用户回退到旧方案你还能找回历史依据。4.3 成本与效果平衡不是所有内容都值得进长期记忆记忆系统带来的成本增加是显式的主要来自三部分提取调用消耗的 token、embedding 的 API 费用、以及每次对话把记忆注入 prompt 后多消耗的 token。提取调用通常每轮会消耗 500 到 1000 tokenembedding 几乎可以忽略真正需要重视的是注入 token——它会变成每一次请求的固定开销。我的建议是不要每轮对话都做完整提取。比较合理的策略是每 3 到 5 轮对话或者系统空闲时做一次批量提取也可以设定“只在上一个提取摘要有实质变化时才更新”。另一个思路是分级记忆短期会话摘要负责保留最近几轮细节长期记忆库只保存高价值的偏好与决策。这样既能控制成本也不会丢失关键信息。做效果评估时我最看重的指标是“记忆注入命中率”。也就是被注入的那些记忆里有多少条真正对当前回答起到了正向作用。这个指标没法直接从日志里看但可以通过对比实验估算。比如随机抽 20 个用户问题分别用有记忆和无记忆的 prompt让同一模型回答人工判断回答质量差异。如果大多数场景下加了记忆和没加记忆差不多说明召回逻辑或者记忆质量出了问题需要回头调。5. 进阶方向从个人助手到多智能体共享记忆5.1 多会话、多用户的记忆隔离与共享当 claude-mem 从个人项目走向团队应用第一个绕不开的问题就是记忆隔离。你不能让用户 A 的偏好污染用户 B 的回答也不能让新项目的记忆变成老项目的干扰。我在设计里引入了 scope 概念可以理解成命名空间每个记忆条目都挂在一条完整的路径下。mem://user/123/project/456/fact/xxx mem://team/ops/preference/xxx这个设计的优点是recall 的时候可以直接按 scope 过滤存储层天然支持。使用上个人助手按 user_id 隔离团队项目里项目级记忆共享给所有成员但个人偏好只对自己可见。多智能体场景下你还可以让不同的 agent 共享同一份项目记忆但各自维护独立的状态记忆。需要注意的是共享记忆的并发更新比如两个 agent 同时写同一条事实可能会互相覆盖。解决办法是给记忆条目加 version 字段写入时做乐观锁判断版本不一致就重读再写。5.2 记忆可视化、导出与可解释性记忆系统一旦上线你就不能只把它当黑盒。用户会好奇 AI 到底“记得”了他什么研发也需要一个直观的工具来调试。我的做法是用一个轻量管理页面把记忆库里的条目按 scope 和 type 列出来支持手动删除、修改置信度、合并相似条目。导出功能也很重要。每周任务会把全部记忆导出成 JSONL 文件方便离线审计和备份。很多记忆污染问题单纯靠回答质量很难定位但看一眼记忆列表就一目了然。比如某天模型突然开始乱答打开管理页面发现有一条“用户是 CTO”的错误记忆答案就清楚了。可以说可解释性是记忆系统生产化的基础没有可视化的时候你不知道模型每一句话背后的“记忆依据”是什么。5.3 评测闭环怎么判断记忆系统变好了判断一个记忆系统有没有变好不能只靠感觉。我建议建立一套小型评测集和三项核心指标。评测集不用太大30 到 50 个问题即可但每个问题都要对应一条必须被召回的特定记忆。这三项指标分别是召回率即评测问题中被正确定位到相应记忆的比例增益率即有记忆与无记忆情况下回答准确率的提升幅度修正频率即用户每次说“不对我之前说的是 X”这类纠正的次数。前两项可以离线跑第三项需要在真实使用中记录。离线评估跑起来也比较简单把评测问题的 query 单独输入 recall 接口检查 top-k 结果里是否包含目标记忆然后把同一批问题用有记忆和无记忆两套 prompt 各跑一遍模型人工或用一个更强大的模型当裁判打分。我自己的经验是只要召回率低于 70%整个记忆系统带来的体验就会非常不稳定召回率上到 90% 以上配合明确的注入模板回答质量的提升才真正可感知。6. 写在最后记忆层会是 AI 应用的下一个标配做完这个 claude-mem 项目之后我最大的感受是把记忆做成显式的数据资产比靠 prompt 让模型自己记住可靠得多。模型的能力更新换代很快但记忆层一旦建好是可以跨越模型版本持续积累的资产。它本质上是在给 AI 应用建立一份“用户模型”和“项目状态机”。没有记忆层的 agent 只能活在当下有了记忆层才具备了和用户长期协作的基础。如果你想在自己的项目里落地这套思路我给新人的建议是不要一上来就做复杂的知识图谱也不要想着把所有对话都存下来。先从最基础的三类结构化记忆开始也就是偏好、事实、任务状态配合一个简单的向量检索把闭环跑通。跑通之后再逐步加实体关系、跨会话推理和更多自动化更新逻辑。最后分享一个我自己常用的排查技巧在记忆提取的 prompt 里明确要求模型区分“这是用户亲口说的信息”和“这是模型根据上下文推断的信息”并分别打上不同标签。只把前者作为高置信度事实入库后者一律标为低置信度或直接丢弃。这一条规则能帮你躲掉大半记忆污染带来的头疼问题。
返回列表