ARTICLE DETAIL

资讯详情

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

给Claude搭建持久记忆:自动提取对话上下文,告别反复自我介绍

给Claude搭建持久记忆:自动提取对话上下文,告别反复自我介绍 1. 不知道第几次对Claude重复自我介绍之后我决定动点手脚先说一个让我彻底受够的场景我每天要在Claude里反复交代同样的事情——代码用Python写注释用中文优先用标准库不要给我整花活上次讨论的项目背景去翻历史消息。一次两次还好天天这样重复我总觉得哪里不太对劲。最后让我下定决心动手的是一个特别低效的下午我在Claude里讨论一个Dockerfile的优化方案讨论到第三轮的时候它把第一轮已经敲定的基础镜像版本又给推翻了。我意识到这根本不是模型笨而是它的上下文在这个会话里被新的内容挤掉了或者说它压根就没打算把信息存到会话之外。问题的根源在于Claude每个会话都是一个独立的记忆空间。技术上叫无状态对话——模型每次收到你的消息都是从头开始理解整个上下文没有任何上一轮长什么样的残留。窗口再大比如200k token也扛不住长对话的持续积累。而会话一关它的记忆就清零了。你当然可以用官方提供的Projects、Memory等托管功能来做一部分记忆固化但用过的朋友都知道那东西像是给你一张白纸让你自己抄笔记并不会自动把你讨论过的决策沉淀下来更不会在下次对话里主动带出来。所以我想找一个更贴近个人工作伙伴的方案它能自动捕捉我和Claude对话里值得被记住的信息在下次会话开始的时候悄悄注入进去让我不用自报家门、不用重讲背景可以直接继续干活。找了一圈现成工具要么太重要跑一堆服务要么太轻只是存草稿最后我决定基于claude-mem这个思路自己攒一套。这篇文章就把整个过程的思路、配置、踩坑和我目前的最终用法完整放出来给同样受困于反复自我介绍的人一个可复制的参考。claude-mem要做的事情说白了就三件在对话结束时扫描聊天记录判断哪些内容值得长期记住把值得记的内容经过压缩、归并、去重后存储到本地在新会话开始前把相关记忆作为上下文注入让Claude一上来就知道你是谁、你偏好什么、做到哪一步了。听起来不复杂但真正做起来会发现每一步都有大量细节决定成败。接下来我按实际推进顺序拆开讲里面有不少参数和坑是我实测过才确认的。2. 记忆系统的选型判断为什么本地文件方案比数据库更适合个人场景在动手之前我实际上先过了一遍各种存储方案的可行性。按照我的判断标准——单机个人使用、需要快速迭代、不想引入外部基础设施、数据要透明可控——本地文件存储是明显的最优解。用数据库SQLite确实也行看起来更专业但对于个人场景数据库带来的事务、索引、持久化连接这些能力几乎都是多余的重量。我最终采用了目录结构化文本的结构。每一类记忆对应一个Markdown文件文件内部用特定块标记来区分条目。为什么要用纯文本原因很简单我能直接打开文件检查记忆内容不会被数据结构挡住修改记忆可以用任何文本编辑器甚至直接让Claude帮我改可以扔进Git仓库做版本管理哪天改错了还能回溯不需要运行数据库服务不占内存随手备份就是一个文件夹。在信息抽取和自然语言处理层面我的路线是先靠规则粗筛、再做语义相似度去重。微信公众号上有篇文章讲得很好——人的记忆本身就不是数据库式的存储更像是一堆笔记被反复翻阅和重写。既然要模拟记忆那就用笔记的方式来组织而不是用表结构。顺便说一句如果你有团队协作或多设备同步的需求可以把这套文件放在WebDAV或Git远程仓库里逻辑完全一样。但如果你是个人电脑单机使用直接本地一个文件夹就够了。最终我的存储格局是这样的维度实现方式存储介质本地目录中的 Markdown 文件记忆粒度一句话一条每条带时间戳和来源会话ID分类方式按领域分文件如 coding.md、life.md、projects.md去重机制新记忆与旧记忆做语义相似度比较阈值低于设定值则丢弃注入方式新会话首轮消息中用可见的文本块把记忆传给Claude这套设计跑到现在很稳定核心原因是它朴素归朴素但每一步都能被你看到、改到、验证到这对个人工具来说太重要了。3. claude-mem 的完整工作链路从对话结束到记忆落地再到下次会话注入这个工具的灵魂不在存储而在判断什么该记、什么不该记的这条流水线。我把它拆成四个环节每一个都有独立的处理逻辑和容错设计。3.1 环节一对话快照的获取与预处理现在Claude的对话数据分散在多个入口网页端、桌面客户端、API调用。想要统一做记忆最理想的做法是确保所有对话都经过同一个API出口。我的做法是所有日常对话尽量通过命令行工具或API方式来进行这样对话记录天然就是结构化的JSON不用去解析网页DOM。这里必须提醒一句在Claude的API里配置了enable_metadata或长期会话保存设置后每次对话结束后系统会返回一个包含完整消息的会话预览。如果没有拿到完整记录用SDK提供的获取聊天记录接口也能拉回来。拿到原始数据之后先做基础清洗去掉工具调用的大段日志、去掉多余的空格和换行、只保留用户消息和助手消息的主体文本。清洗完的消息文本就是后续记忆提取的原料。3.2 环节二记忆候选提取——规则粗筛与语义打分真正的记忆提取我用了一套双层筛选机制。第一层是规则粗筛。按照记忆的基本形态往往是显式的陈述句这个观察我先从文本里抽出带有明显信息量的句子。粗筛规则很简单包含第一人称偏好词我喜欢我不喜欢我更习惯尽量一定要这类句子大概率是用户习惯包含决策性动词决定定了确认敲定以后就这类句子代表一个重要决策包含稳定属性表达我用的是我的名字是我的项目是我住在这类句子描述长期事实包含项目进度词目前做到了完成了下一步要做此类记忆能给下轮对话衔接提供坐标。第二层是语义打分它是整个流程里最关键的过滤带。候选记忆句子送入打分器从三个维度分别给分长期性、重要性、可用性。简单来说这句话下次对话还会用到吗这句话影响后续决策的权重有多大这句话换成另一种说法之后是否还能被理解只有三维评分都达到阈值才允许进入待入库队列。比如今天午饭吃了黄焖鸡这种话重要性得分就极低会被直接过滤掉而项目部署统一走Docker Compose这种三项都高肯定要保留。3.3 环节三归并、去重与时序更新即便经过了粗筛和打分仍然会有大量记忆在语义上是重复的。比如你上周说了一次代码用Python写这周又说了一遍还是习惯用Python如果不做去重记忆库会在两三个月内被同类信息塞成一片浆糊。我的去重策略是基于embedding向量做相似度计算。先把记忆库里已有的条目全部向量化新候选进来后计算与已有条目的余弦相似度。如果相似度超过0.92基本可以判定是同一条记忆的重复表达这时候不是简单丢弃而是执行时序更新——保留新的时间戳因为它更能代表你现在的状态。这一点特别重要。人的记忆是会变的今天你可能还在用Python半年后换成了Rust。如果同名条目一直存储着旧的偏好那记忆注入反而会拖后腿。所以记忆系统必须支持覆盖更新而覆盖更新的触发器就是相似但时间更新的说法。3.4 环节四新会话注入与上下文组装记忆落地不是终点真正让工具产生价值的时刻是下一个会话开始时。我的注入策略很简单新会话的首条用户消息里先带我组装好的一段记忆上下文再带我的真实提问。记忆上下文的组装顺序是用户事实名字、职业、常用语言这部分优先级最高每次必带项目进度跟当前话题相关的项目状态优先带出偏好与禁忌跟当前话题沾边的偏好带出无关的哪怕重要也不带省token决策历史按时间倒序取最近5条。为什么只带一部分不是全量注入三个字省token。Claude的上下文窗口虽然大但每多塞1k个无关token它在回答质量和响应速度上都会有可感知的下降。记忆系统要做的是精准投喂而不是把整个知识库倒进去。实测下来单次注入的记忆块控制在500~1200 token之间效果最好。4. 落地实操我把claude-mem跑起来的完整过程如果只看理论会觉得很顺但实际操作的时候还是会遇到各种意想不到的拧巴事。下面是我把系统跑通的全过程按步骤展开包含所有配置细节和参数。4.1 步骤一搭好基础环境我的主力环境是Windows 11 WSL2Ubuntu 22.04Python 3.11。所有代码和配置都跑在WSL2里。这样做的原因一是shell处理文本方便二是后续如果要上定时任务cron也顺一点。# 在WSL2 Ubuntu 22.04中 mkdir -p ~/claude-mem/{data,logs,scripts} cd ~/claude-mem python -m venv .venv source .venv/bin/activate pip install anthropic numpy装好anthropic SDK是为了直接调Claude的API拿对话记录。numpy是用来做向量计算的后面算余弦相似度会用到。4.2 步骤二配置API访问我通过环境变量而非配置文件来管理密钥这样不会不小心把密钥提交到Git里。export ANTHROPIC_API_KEYsk-ant-xxxxxx export CLAUDE_CONFIG_DIR$HOME/.claude python claude_sessions.py --list-sessions跑通这条命令说明SDK和权限配置都没问题了。如果返回空列表多半是APIKey没有对话读取权限需要到控制台重新生成一把带上读取权限的Key。4.3 步骤三实现记忆提取函数核心代码下面这段我经过多次调整才成型。它完成环节二里的规则粗筛和打分预处理# extract_candidates.py import re import json MEMORY_RULES [ (r(我喜欢|我不喜欢|我更习惯|我更喜欢|千万[别|不]|尽量), preference), (r(决定|定了|确认|敲定|以后就|统一用), decision), (r(我的名字|我是|我住在|我在|我的项目), fact), (r(目前|已完成|做到了|下一步|接下来要), progress), ] def extract_candidates(messages: list[dict]) - list[dict]: candidates [] for msg in messages: text msg[content] if not text: continue for pattern, ctype in MEMORY_RULES: for match in re.finditer(pattern, text): # 抽取句子局部内容并打上初步类别 start max(0, match.start() - 50) end min(len(text), match.end() 100) chunk text[start:end].replace(\\n, ) candidates.append({ text: chunk, type: ctype, role: msg[role], }) return candidates看到这里你可能会问这跟用正则硬提取有什么区别区别在于——它只是粗筛后面还有语义打分做兜底。就是说即使规则漏掉了一些信息后面我在会话总结时用Claude自己生成的摘要里还会再做一次补充提取。4.4 步骤四语义打分与阈值设置打分部分我直接调用Claude来操作。因为判断这值不值得记住这件事本身就是模型擅长的语义任务。我设计了一个非常轻量的prompt让它输出结构化结果# score_memory.py SCORE_PROMPT 以下是提取出的候选记忆片段请从三个维度打分0-10分 1. long_term这条信息是否描述长期稳定的事实或偏好如果经常变化则低分。 2. importance如果遗忘这条信息会对后续对话产生多大影响影响越大分越高。 3. usability下次对话中这条信息是否容易被直接复述/使用碎片化描述则低分。 以JSON格式输出long_term: 分数, importance: 分数, usability: 分数, reason: 一句话理由。 候选片段{text} def scoring_with_llm(candidate): resp client.messages.create( modelclaude-sonnet-4-0, max_tokens200, system你是信息记忆价值判断引擎。只输出JSON。, messages[{role: user, content: SCORE_PROMPT.format(textcandidate[text])}], ) return json.loads(resp.content[0].text)阈值我调试了很多轮最终定为三项总分22且importance7这条记忆才进入待入库队列。阈值低了会让垃圾信息入库高了会让该记的记不住。这组参数在我的场景里是平衡点。4.5 步骤五入库与去重带校验的写入入库之前做向量去重我用的是numpy实现简单向量# dedup_and_store.py import numpy as np def calc_similarity(vec1, vec2): dot np.dot(vec1, vec2) norm np.linalg.norm(vec1) * np.linalg.norm(vec2) return dot / norm if norm ! 0 else 0 def should_store(candidate_vec, existing_vecs, threshold0.92): for vec in existing_vecs: if calc_similarity(candidate_vec, vec) threshold: return False # 近似重复 return True如果判断为近似重复我会做一次时间戳覆盖而不是直接丢弃。比如旧记忆写的是项目用Python新记忆说项目已经切换Rust它俩不会太相似那就不再是一条覆盖的关系而是两条并列存储等下次用到时靠Claude自己去判断哪个才是当前事实。不过实际开发中这里我简化成所有记忆条目如果类型相同且语义相似度超过0.85默认一律覆盖旧时间戳。用了一段时间发现这样做更贴近人的记忆会更新的真实状态。4.6 步骤六注入模板的组装新会话开始前我用一个统一模板把记忆组装成一段透明文本。所谓透明就是让Claude清楚知道这是你的记忆上下文而不是假装它自己想起来。坦白说透明有透明的好处它能减少模型混淆记忆和当轮对话的概率。注入模板长这样# 长期记忆上下文 ## 用户事实 - 用户名xx - 语言偏好中文为主英文术语保留 - 代码偏好Python优先优先标准库 ## 项目状态 - 项目AI部署工具链 - 当前进度已完成Docker基础镜像优化下一步做K8s部署脚本 ## 历史决策 - 2025-XX-XX统一用Docker Compose管理本地服务 - 2025-XX-XX不用pandas处理超大数据集改用polars这个模板直接拼接在当前对话之前一次性发出。5. 用过的记忆策略与踩坑记录哪些方案看着合理实测直接翻车这一部分值得单独拿出来写。因为我从一开始到现在试过好几种策略大部分看起来逻辑自洽实际上手就翻车。下面按踩坑时间顺序罗列每个都给结论。5.1 坑一让Claude自己总结全部记忆刚开始我想得很美每次对话结束直接把全部消息丢给API让它写一个会话摘要然后把摘要存下来作为记忆。听起来就是智能体的标准做法不是么实测后果摘要确实写得漂亮但它是摘要不是记忆。会话摘要会保留大量当时讨论的细节推导过程却忘掉那些最不该忘的偏好。而且多轮对话过去之后我翻这些摘要短则几百字长则上千字根本没有一个结构能告诉系统哪些是稳定偏好、哪些是临时话题。更致命的是摘要之间互相矛盾的情况十分常见两个不同时间点的摘要可能一个说用Docker一个说不要用Docker因为没有字段去标识哪个是更新的决策。最后我彻底推翻了这条路线才转向结构化记忆条目思路。结论不要用摘要当记忆。摘要属于叙事记忆属于索引。叙事让人看懂而索引让系统好用。5.2 坑二把所有偏好都记下来早期我还犯了一个方向性错误——追求全。规则粗筛的阈值放得特别低所有稍微有点信息量的句子都往里塞。结果用了一周记忆库里攒了1100多条。注入的时候惨不忍睹随便一个话题都要带上几百条记忆token消耗飙升模型处理速度肉眼可见地下降。更要命的是因为记忆太多注入文本里必然堆积大量干扰信息。实测里Claude经常被无关记忆带偏回答的内容驴唇不对马嘴。这相当于一个人脑子里塞满了乱七八糟的细节你问他今天天气他却在回忆十年前楼下超市的货架布局。结论记忆系统必须做减法宁缺毋滥。每存一条都要问下个会话真的会用它吗不用果断不存。5.3 坑三只存不清理越用越迟钝这个坑我是过了一个月才发现的。记忆库虽然经过阈值过滤但累计下来还是会持续膨胀。凡是重要一点的旧决策、旧项目进度全都堆着。因为注入策略里没有淘汰机制每次新会话都会把旧进度、旧偏好一字不差地带进去。直到一次我在聊一个新项目Claude却突然提起了三个月前已经结束的那个项目还理直气壮地标注为当前进度。我才反应过来旧记忆必须有过期清理机制。后来我加了生命周期偏好类记忆永久保留事实类记忆如果有新的覆盖则替换项目进度记忆只在当前项目活跃期间有效。项目一旦标记为已结束相关记忆自动降权归档。归档不是删除而是从注入源里移出只在需要回顾历史时才手动翻阅。结论记忆系统和操作系统的内存管理一样不清不楚的缓存越多系统越慢。5.4 坑四向量嵌入方案直接用本地小模型这个坑比较技术向。最开始为了省API费用我用了一个本地的小型embedding模型算相似度模型本身是通用的多语言模型。用在英文场景勉强能跑一到中文语义相似度的准确性就拉胯——两个表达完全不同但意思相近的句子相似度常常只有0.7左右而两个看似相近但语境完全相反的句子又可能打出0.9高分。后来我换成了专门的中文语义向量模型效果立刻好了一个档次。没错embedding模型这个环节语言针对性真的能直接影响去重效果。这里我多说一嘴如果不方便上更好的模型一个替代方案是把句子先用Claude规范成统一句式再做字面相似度。虽然粗暴但在很多场景下比直接做向量靠谱。6. 记忆系统的核心设计选择为什么条目标签化比全文检索更适合长期演进整个系统跑通之后我又花了一周时间重构了一次存储结构。最初我把所有记忆都放在一个大Markdown文件里到500条之后每次查看和改动都开始变得迟钝文件打开卡顿保存时还会乱序。我才决定引入分类文件条目标签的结构。具体是按照记忆类型分成四个文件profiles.md长期偏好、projects.md项目状态、decisions.md决策记录、rules.md行动规则。每条记忆除了正文带一个frontmatter头部记录时间、类别、来源会话、活跃状态。为了说清楚我把旧结构和新的tag结构做了个对比维度旧方案纯文本堆叠新方案标签化查找效率要全文翻找直接按tag过滤冲突处理新旧并存无法判断同一tag自动覆盖旧值注入控制只能全量注入按tag选择性注入生命周期没有概念有active/archive状态这套改造之后整个系统的可用性提升了一个层级。Claude在判断该带上哪些记忆做注入时不用再阅读理解整个文本库而是基于tag做检索和组装。这种做法其实很接近人在回忆时的行为——你不是把整个人生经历都过一遍再开口而是按时间线、按相关性顺藤摸瓜。7. 记忆管理需要一处专门的自然语言控制入口另一件绕不开的事是记忆要能被随时查看和修改否则它就失去了信任基础。这个入口我虽然没做到特别完善但已经有了一套基本可用的自然语言交互方式配套几个独立小脚本。我保留了几个主要命令/remember 记住 xxx手动塞入一条记忆/forget xxx删掉指定的旧记忆/recall xxx查询某个主题相关的全部记忆/forget all about xxx清除某个主题的全部记忆。这套命令不需要写数据库结构它们只是对Markdown文件做增删查改。/remember就是追加一行带tag的条目/forget就是先/recall定位再精确删除。举一个实际用法有段时间我的Claude总在写SQL时默认用双引号我明明之前已经记过SQL语句全用单引号但注入顺序导致它没读到。后来我用/recall sql quote查了一轮发现那条记忆被错误的旧tag归类到了general规则而不是coding规则导致注入时没有被选中。修正tag之后问题就好了。像这种记忆存了但没被正确检索的问题如果完全没有可视化入口你根本查不出来。这也是我强烈建议给记忆系统加一个观察窗口的原因。所谓观察窗口就是一个能让你随时打开看到记忆库里到底塞了什么的命令行面板或文件夹视图。单论这一点保留Markdown的纯文本价值就无法被替代——我可以用任何编辑器、任何grep命令、甚至一句话让Claude去翻文件来检查记忆内容。8. 边界与隐私除了Token成本记忆系统必须考虑的取舍写到这里我想说一个容易被忽略但很重要的话题记忆的边界在哪里这个疑问并非凭空而来而是我在实践中撞了几次墙之后才意识到记住一切本身就是风险。首先是隐私问题。这个系统会把你的日常对话、偏好、项目细节、决策过程全部落到本地文件里。如果你的电脑本身安全性不高或者你把记忆库同步到了云盘那么所有这些信息都暴露给了云服务商。我的做法是记忆文件的目录用一个单独的加密文件夹保存敏感项目的记忆一律不进入常驻记忆库只在需要时按需输入此外涉及密码、密钥的内容我做了关键词黑名单过滤。这个黑名单千万不能省哪怕只是偶尔一句带过长期积累下来的信息量也可能被人利用。其次是记忆污染问题。Claude不是你的大脑它不会自动纠正错误的记忆。如果你某天在对话里随口说了一句以后不用Go了第二天又认真讨论了Go项目它甚至可能根据旧的错误记忆给你一个离谱建议。所以记忆系统一定要有纠错机制。我的做法是记忆写入时必须附带来源会话ID一旦发现某条记忆有问题可以直接定位到原始对话确认后修正或删除。最后是关于信息遗忘的取舍。我做过几次实验发现对大多数个人项目而言能记住最近30天的决策和偏好就已经覆盖了绝大多数使用需求。更早之前的记忆价值会指数级衰减。把时间窗口调到1个月核心3个月回溯之后系统的响应速度、注入精度和整体体验都有了明显改善。由此可见遗忘本身不是缺陷而是让记忆系统保持新鲜和可用的必要能力。每次跟人介绍这套设计时我都愿意强调claude-mem这个名字看似在说给Claude加记忆但它本质上不是给模型加能力而是给我们自己加了一个可观察、可修改、可丢弃的外部认知层。它不神秘不高深关键逻辑不过三件事提取有价值的信息、存成可检索的结构、在恰当时机注入回去。但就是这简简单单三件事做扎实了能让AI从一个每次都聊成新朋友的许愿机变成一个真正积累下来的工作伙伴。
返回列表