首页 / 资讯中心 / 文章详情

基于MCP协议为Claude构建持久化记忆:架构设计与实践

基于MCP协议为Claude构建持久化记忆:架构设计与实践 ★ FEATURED ARTICLE
从“claude-mem”这个名字出发很多朋友第一反应是“给Claude加个记忆插件”这个方向没错但真做起来里面的门道比想象中多得多。我最早接触这个概念是在做一个长期陪伴型对话机器人当时Claude单轮对话能力很强可一关掉窗口它就把上一轮说过的话忘得一干二净用户得反复交代背景体验非常割裂。后来我自己动手把记忆层搭起来踩了不少坑也沉淀了一套比较稳的思路这篇就把完整的方案拆开聊。1. 项目整体设计与思路拆解1.1 核心需求解析“claude-mem”这类项目要解决的核心问题是在会话间给模型一个“持续身份”。你可以把它理解成给一个健忘的朋友配一本随身笔记本每次聊完自动把重要信息记下来下次再聊先把笔记本翻出来温习一遍再开口说话。听着简单但落到设计上必须回答三个问题记忆怎么存才能既支持快速读写又不把数据库搞成垃圾场哪些信息值得记哪些信息记了反而是噪音记忆怎么注入才能让模型在回答时真正利用上而不是塞进上下文当摆设我最初做过一个很笨的方案把每轮完整对话原样存下来下次开场全部拼进系统提示词里。结果不到三四十轮上下文就撑爆了还全是低价值口水话。后来想明白一个道理记忆的关键不是“存储”是“提炼与检索”存储只是地基。1.2 为什么选用MCP协议做接入现在给Claude加记忆市面上有很多路子直接在API调用里拼历史、用LangChain的Memory模块、或者自己写工具函数。我最后选的是MCPModel Context Protocol方案理由很现实。MCP相当于给Claude配了一套“标准USB接口”让外部工具能以统一方式接入。记忆模块只需要实现“检索记忆”和“保存记忆”两个工具Claude就会在合适的时候主动调用比如用户提到上次聊过的话题时它会先查再答。这比粗暴地拼接上下文优雅得多也让记忆模块可以独立维护不必跟着业务代码一起改。另一个好处是隔离性。记忆服务挂在MCP Server里和主业务逻辑解耦哪怕记忆系统崩了Claude本身还能继续对话只是暂时“失忆”而已。在生产环境里这种降级能力比功能本身还重要。1.3 整体架构一览我实际跑的架构分三层接入层MCP Server负责和Claude通信暴露两个工具接口。存储层SQLite做主要存储配FTS5全文索引向量检索用一个小型的余弦相似度计算模块。提炼层用Claude自身对会话做摘要和实体抽取把“对话流水账”变成“结构化记忆”。选择SQLite而不是专门的向量数据库是因为记忆量没到那个量级。个人使用或者中小型项目SQLite的单文件特性反而省心备份就是一个文件迁移也不折腾。等哪天真到了几十万条记忆再平滑切到专门的向量库也不迟。2. 核心细节解析与实操要点2.1 表结构怎么设计才不后悔表结构我改过三版最终稳定下来的只有三张表第一张是messages记录原始对话用来追溯“这条记忆从哪来的”。第二张是memories存提炼后的记忆单元。每条记忆包含内容、类型偏好/事实/事件、重要性评分、最后访问时间、引用次数。第三张是embeddings存每条记忆对应的向量表示方便做相似度检索。这里有个细节向量不必存进主表单独建表能避免查询大字段拖慢主键索引。设计原则就一条——原始对话和提炼记忆分库分表。有一版我把原始对话和记忆硬塞同一张表用type字段区分结果每次检索都要全表扫一遍慢得怀疑人生。分开之后记忆查询走独立索引原始对话只在需要回溯时才查性能完全不在一个量级。2.2 记忆写入策略什么时候记、记什么记忆不是越全越好这是我从失败里换来的教训。早期版本把每轮对话都塞进记忆结果模型经常被无关信息干扰甚至给出“我记得你上次说过某某事”这种莫须有的回答非常尴尬。现在的策略是三段式筛选基础过滤去掉纯寒暄、语气词、临时性操作指令。实体抽取让模型把对话里的关键实体抽出来比如人名、项目名、时间地点、明确的用户偏好。重要性打分1到5分只有3分以上的才写入长期记忆低分的只留在原始对话里。比如用户说“我今天不想吃辣的”这条会写入偏好类记忆但“帮我查一下天气”这种一次性请求只当作普通交互不沉淀为长期记忆。2.3 检索与注入记忆不是一次性全塞进去记忆检索我经历过三个阶段全量注入、关键词匹配、混合检索。前两种效果都不好现在用的是“关键词粗筛 向量精挑 时间衰减”的组合。流程是这样收到对话请求后先把当前问题和历史记忆同时做关键词抽取用FTS5粗筛出一批候选记忆再把这些候选和当前问题的向量做相似度计算按得分排序最后乘一个时间衰减系数比如一周前的记忆权重打八折。注入量也有限制。我默认只往系统提示词里放前5条相关记忆如果某条记忆连续被引用它的权重会提升。这个思路类似“热点升舱”经常被提到的记忆下次检索时自动排前面。2.4 上下文窗口管理Claude的上下文窗口再大也扛不住无限堆记忆。我通常会预留三块空间系统提示词区域固定放角色设定和长期记忆摘要不超过窗口的10%。历史对话区域放最近几轮原文是对话连续性的保障。记忆上下文区放检索出来的历史记忆动态置换。如果记忆内容太长就先让Claude做压缩摘要只保留结构化核心。这里有个小技巧摘要要分两层。短期摘要管最近5轮长期摘要管全局偏好和关键事实两层结合才能既保鲜又不失深度。3. 实操过程与核心环节实现3.1 搭建基础记忆服务下面这是我跑通的MCP Server骨架用Python的fastmcp库搭的算是最轻的实现方式了。先安装依赖pip install fastmcp sqlite-vec openai然后创建memory_server.py核心是注册两个工具save_memory和search_memory。import sqlite3 import json from fastmcp import FastMCP mcp FastMCP(claude-mem) DB_PATH memory.sqlite3 def get_conn(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): conn get_conn() conn.executescript( CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, memory_type TEXT, importance REAL DEFAULT 3.0, access_count INTEGER DEFAULT 0, last_access TEXT DEFAULT current_timestamp ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT, content TEXT, created_at TEXT DEFAULT current_timestamp ); ) conn.commit() conn.close() mcp.tool() def save_memory(content: str, memory_type: str fact, importance: float 3.0) - dict: 保存一条长期记忆 conn get_conn() cur conn.execute( INSERT INTO memories (content, memory_type, importance) VALUES (?, ?, ?), (content, memory_type, importance) ) conn.commit() return {status: ok, id: cur.lastrowid}3.2 检索与相似度排队的实现向量检索我用最简单的余弦相似度没上重型框架因为数据量几千条以内纯Python计算完全够。核心思路是把当前问题和每条记忆调同一个embedding接口然后算两两相似度。def embed_text(text: str) - list[float]: # 对接Claude或其他embedding模型 # 这里以Claude的embedding接口为例实际换成任意模型都行 from openai import OpenAI client OpenAI() resp client.embeddings.create(modeltext-embedding-3-small, inputtext) return resp.data[0].embedding mcp.tool() def search_memory(query: str, top_k: int 5) - dict: 搜索相关记忆 conn get_conn() rows conn.execute(SELECT id, content FROM memories).fetchall() query_vec embed_text(query) scored [] for row in rows: mem_vec embed_text(row[content]) score cosine_similarity(query_vec, mem_vec) # 时间衰减超过3天权重降为原来的0.9 scored.append((score, row[id], row[content])) scored.sort(keylambda x: x[0], reverseTrue) results [{id: r[1], content: r[2], score: r[0]} for r in scored[:top_k]] return {results: results}代码里有两个值得注意的地方第一每次检索都对所有记忆重新算embedding这在数据量上来之后会变成性能瓶颈。我的规避方式是给embedding单独建表缓存插入记忆时就顺手算好检索时只查不重算。第二时间衰减不是硬规则而是软加权。上面代码写的是固定查库时动态处理如果记忆表变大更推荐在写入时就顺手更新分数比如UPDATE memories SET access_count access_count 1, importance importance * 0.99 WHERE id ?3.3 配置Claude接入MCP ServerClaude接入MCP的方式有几种我用的是本地stdio模式最省事。在Claude的配置文件里加一段{ mcpServers: { claude-mem: { command: python, args: [memory_server.py], cwd: /path/to/project } } }重启Claude然后试试让它“记住刚才说的内容”。它会在合适时机调用save_memory。下一轮新会话里直接问“你还记得我说过喜欢吃辣吗”它会先调search_memory再基于检索结果回答。这里有个调试技巧MCP Server的命令行输出里会有每次工具调用的日志强烈建议加一行日志记录当前工具参数和返回值。我第一次调试时全靠print输出定位问题后来才发现直接看日志比问Claude快得多。3.4 会话收尾时自动提炼记忆只是让Claude主动调用工具还不够因为有些关键信息它可能会漏记。我加了个收尾机制在每次会话结束后给Claude一条固定的“总结指令”让它把这轮对话的核心信息抽取出来并调用save_memory写入。实现方式很简单给MCP Server加一个summarize_session工具接收最近N轮消息然后让Claude生成结构化JSON{ memories: [ { content: 用户正在用Python开发一个MCP记忆插件, type: project, importance: 4.5 }, { content: 用户偏好使用本地SQLite存储方案, type: preference, importance: 3.8 } ] }然后批量写入记忆表。这样即使Claude在对话中途没有主动调工具收尾时也能把重要内容捞回来。4. 常见问题与排查技巧实录4.1 记忆污染问题最烦的情况是模型把“错误的推测”当成“既定事实”记下来。比如用户随口说“下周可能去上海”模型就记成“用户下周去上海”下次对话直接关联上海酒店推荐用户一脸懵。我踩过这个坑之后在记忆写入前加了一道“置信度校验”所有带有猜测性质的语句importance直接降一档并且在type里标记为event_pending。只有用户再次确认后才升级为fact。代码上就一个if分支的事# 伪代码判断是否为确定性描述 if any(word in content for word in [可能, 或许, 大概, 也许]): memory_type event_pending importance min(importance, 2.5)4.2 记忆遗忘与覆盖记忆系统不能只进不出。我用两个机制管理一是时间衰减access_count长期不涨的记忆重要性逐日降低二是显式覆盖用户明确说“我以前说错了改成某某某”时旧记忆直接置为archived避免新旧并存造成冲突。实现时注意不要直接物理删除旧记忆而是软删。保留历史记录方便后续追踪真遇到模型答错还能排查是什么时候写入的。4.3 检索召回不准有段时间我特别头疼明明记忆里存了用户所在城市但Claude回答时总想不到用。查了一下原因是search_memory返回的结果里相关记忆排在第7位而我只取top 3它被截掉了。解决办法有两个方向第一提高top_k到5到8条让模型有更多候选。第二做分组召回。把记忆按类型分组用户偏好、事实、项目背景各取top 2混合后全部注入。这样不至于因为一个话题特别热门把所有候选取走其他重要背景完全没机会露面。4.4 多用户场景下的记忆隔离如果这个系统给多人同时用千万别做成全局一张表否则用户A的记忆会飘到用户B的对话里那画面想想都尴尬。我在所有记忆表都加了sender_id字段检索时强制过滤SELECT content FROM memories WHERE sender_id ? ORDER BY importance DESC LIMIT 10这里我给一个实用建议哪怕当前只有一个人用也先把这个字段加上。等到真要加多用户时就不会面临数据拆分的痛苦。加字段比拆表容易得多。5. 实用扩展与后续优化方向基础版跑通之后我发现几个可以优化的方向贴出来供参考第一记忆的跨语言检索。如果用户今天用中文聊明天用英文聊embedding模型的跨语言能力可能成为瓶颈。实测下来好的通用embedding模型在常见语种间基本能保持一致语义但如果你的用户群涉及小语种建议做一轮针对性评测。第二主动记忆提醒。现有机制是“检索到才用”未来可以做成“根据当前对话状态主动推送相关记忆”比如用户问起吃饭这件事时系统先把最近的口味偏好置顶。这个在MCP协议下也不难实现本质是在检索结果上增加业务规则加权。第三记忆可视化。我后来写了一个简单的网页界面用时间轴展示所有记忆的写入和引用过程排查问题时非常好用。不仅能看到模型记住了什么还能看到它在哪一轮引用了哪条记忆bug定位效率翻倍。基于我个人的实践体会claude-mem这类项目的核心不是“让模型拥有无限记忆”而是“让模型知道该记什么、该查什么、该忘什么”。一套好的记忆系统应该是越用越准而不是越用越乱。如果你也正在做类似的项目建议从最小闭环开始一张表、两个工具、一个检索函数把流程跑通后再逐步加机制、加规则。这样路径最稳也最容易定位问题。
阅读完成 · 觉得有帮助?
咨询建站