1. 为什么你的 AI Agent 总是“金鱼记忆”:长期记忆系统到底缺了什么
先说一个我踩过的坑。去年我给自己写了个代码助手 Agent,专门用来记录项目里的技术选型和命名规范。第一次对话我告诉它“这个项目统一用 pydantic v2,别给我生成 v1 的写法”,它答应得好好的。第二天我重新打开,问它“帮我写个配置类”,它反手就给我来了一段class Config:的老式写法。那一刻我意识到,LLM 本身没有记忆,它的“记性”完全依赖当前上下文窗口,窗口一关,前因后果全部蒸发。
这不是模型能力问题,而是架构问题。大语言模型的推理是无状态的,每次请求都是一次全新的前向计算。你看到的“它记得”,其实是把历史对话拼进了 prompt 里。一旦对话轮次变多、token 超限,或者你换了会话、重启了进程,这些临时拼进去的内容就没了。对于陪伴类助手、客服机器人、长期编码 Agent 来说,这种“金鱼记忆”直接让体验断裂。
传统做法是用 RAG(检索增强生成)来补。RAG 的思路是:把历史内容切块、向量化、存进向量库,需要时按语义相似度召回几段塞进 prompt。它像给 AI 配了一本字典,能查资料,但它不懂前因后果。你问“我上次说了啥”,它只是去书堆里把包含“上次”两个字的纸条翻出来,并不理解时间线和人物关系。更麻烦的是,RAG 召回的是静态片段,用户改口了、偏好变了,旧片段还在库里,容易和新信息打架。
MemMachine 走的是另一条路。它把自己定位成“AI Agent 的持久化记忆层”,核心是双层记忆结构:一层叫情景记忆(Episodic Memory),记的是“故事线”,比如你昨天感冒了所以今天想喝热水,它记的是事件的前后顺序和因果;另一层叫档案记忆(Profile Memory),记的是“人设卡”,比如你不吃香菜、你是程序员、你在控制碳水,这些一旦写入就长期生效,不需要反复强调。检索时它会把这两层记忆融合后注入到当前对话里,让模型“想起来”。
这套机制适合谁?如果你在做多轮对话的个人助手、需要跨会话记住用户偏好的客服系统、或者长期跑任务的编码 Agent,那长期记忆系统就是刚需。这篇我会带你从零把 MemMachine 搭起来,走通记忆写入、检索、注入的完整链路,并且用 TaoToken 统一 Key 和 API 通道来接入模型调用,保证整条链路可跑通、可复现。你不需要懂太多底层原理,跟着命令走就行。
2. 搭建前的环境准备:Docker 部署 MemMachine 与 TaoToken 统一接入配置
动手之前先把地基打好。MemMachine 官方推荐用 Docker 部署,所以你的机器上得有 Docker 和 Docker Compose。Windows 用户装 Docker Desktop 就行,Mac 和 Linux 用户装对应版本。装完在终端里验证一下:
docker --version docker compose version两条命令都能打印出版本号,说明环境没问题。接下来是模型 API 通道。MemMachine 默认走 OpenAI 的接口格式,但实际调用哪个模型供应商是可以换的。这里我用 TaoToken 来做统一接入,原因是它兼容 OpenAI 的接口协议,Base URL 和 Key 配一次,后面换模型只改 Model ID 就行,不用动代码结构。
TaoToken 的 API 地址是https://taotoken.net/api,注意这个地址不带任何查询参数。你需要先去控制台创建一个 API Key,入口在https://taotoken.net/console/api-keys。创建完把 Key 复制下来,形如sk-开头的一串字符,后面配置里要用。如果你还没决定用哪个模型,可以先在模型对话页面试试手感,地址是https://taotoken.net/model-chat,确认通道通了再往下走。
现在拉取 MemMachine 的代码。官方仓库在 GitHub 上,我用的是 release 包的方式,这样版本可控。在 PowerShell 里执行下面这段,它会自动下载最新 release 并解压到MemMachine目录:
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/MemMachine/MemMachine/releases/latest" $tarballUrl = $latestRelease.tarball_url $destination = "MemMachine" if (Test-Path $destination) { Remove-Item $destination -Recurse -Force } New-Item -ItemType Directory -Force -Path $destination Invoke-WebRequest -Uri $tarballUrl -OutFile "MemMachine-latest.tar.gz" tar -xzf "MemMachine-latest.tar.gz" -C $destination --strip-components=1 Set-Location $destination如果你用的是 Git Bash 或者 Linux,也可以直接git clone仓库,效果一样。解压完进入目录,你会看到memmachine-compose.sh这个脚本,它是官方给的交互式部署入口。运行它:
./memmachine-compose.sh脚本会依次问你几个问题:用 CPU 还是 GPU 配置、用哪个模型供应商、用哪个对话模型、用哪个 embedding 模型、以及 API Key。这里有个关键点,如果你打算用 TaoToken 统一接入,模型供应商那一步选 OpenAI 兼容模式,然后把 Base URL 指向 TaoToken。不过脚本的交互流程里 Base URL 不一定能直接改,所以更稳妥的做法是先用默认流程跑通,再手动改配置文件。我实测下来,先让它用默认的 OpenAI 配置把容器拉起来,然后改configuration.yml和.env两个文件,重启容器,这样最省事。
等脚本跑完,容器启动需要一两分钟。验证服务是否健康:
curl -v http://localhost:8080/health返回{"status": "healthy"}就说明 MemMachine 服务起来了。这一步如果卡住,多半是端口占用或者镜像没拉下来,先docker ps看看容器状态,再docker logs看具体报错。环境准备好之后,下一节我们进入真正的配置环节,把 TaoToken 的 Key 和 Base URL 写进去,让记忆系统用上统一的模型通道。
3. 可复制配置:configuration.yml 与 .env 接入 TaoToken 的完整片段
这一节是整篇的核心,配置写对了,后面就顺了。MemMachine 的模型调用配置分散在两个文件里:configuration.yml管记忆系统的模型绑定,.env管环境变量和 API Key。我们要做的是把这两处的模型供应商都指向 TaoToken,这样对话模型和 embedding 模型走同一条通道。
先看configuration.yml。这个文件在 MemMachine 根目录下,用文本编辑器打开。找到model和embedder两段,改成下面这样。注意base_url填 TaoToken 的 API 地址,api_key填你在控制台创建的那把 Key,model填你要用的 Model ID。Model ID 的具体取值可以在接入文档里查,地址是https://taotoken.net/doc。
logging: path: /tmp/memory_log level: info long_term_memory: derivative_deriver: sentence metadata_prefix: "" embedder: my_embedder reranker: my_reranker vector_graph_store: my_storage SessionDB: uri: sqlite:///test.db Model: taotoken_model: model_vendor: openai-compatible model: "gpt-4o-mini" api_key: "sk-你的TaoToken密钥" base_url: "https://taotoken.net/api" storage: profile_storage: vendor_name: postgres host: postgres port: 5432 user: memmachine db_name: memmachine password: memmachine_password profile_memory: llm_model: taotoken_model embedding_model: my_embedder database: profile_storage prompt: profile_prompt sessionMemory: model_name: taotoken_model message_capacity: 500 max_message_length: 16000 max_token_num: 8000 embedder: my_embedder: provider: openai config: model_vendor: openai model_name: "text-embedding-3-small" model: "text-embedding-3-small" api_key: "sk-你的TaoToken密钥" base_url: "https://taotoken.net/api" dimensions: 1536 reranker: my_reranker: provider: "rrf-hybrid" config: reranker_ids: - id_ranker_id - bm_ranker_id id_ranker_id: provider: "identity" bm_ranker_id: provider: "bm25" prompt: profile: profile_prompt vector_graph_store: my_storage: provider: neo4j config: uri: "bolt://neo4j:7687" username: neo4j password: neo4j_password这里有三处要替换成你自己的值:api_key两处、model和model_name按你实际想用的模型填。如果你用的是别的 embedding 模型,把text-embedding-3-small换成对应的 Model ID,dimensions也要跟着改,否则向量维度对不上会报错。
再看.env文件。这个文件管容器启动时的环境变量,重点是DASHSCOPE_API_KEY这类变量名可能因版本而异,但核心是让容器内的服务能读到 Key。改成下面这样:
# PostgreSQL / pgvector POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_USER=memmachine POSTGRES_PASSWORD=memmachine_password POSTGRES_DB=memmachine # Neo4j NEO4J_HOST=neo4j NEO4J_PORT=7687 NEO4J_USER=neo4j NEO4J_PASSWORD=neo4j_password NEO4J_HTTP_PORT=7474 NEO4J_HTTPS_PORT=7473 # MemMachine MEMORY_CONFIG=configuration.yml MCP_BASE_URL=http://memmachine:8080 GATEWAY_URL=http://localhost:8080 FAST_MCP_LOG_LEVEL=INFO # TaoToken 统一通道 TAOTOKEN_API_KEY=sk-你的TaoToken密钥 TAOTOKEN_BASE_URL=https://taotoken.net/api # 可选 LOG_LEVEL=INFO MEMORY_SERVER_PORT=8080 DB_POOL_SIZE=10 DB_MAX_OVERFLOW=20 MEMMACHINE_IMAGE=memmachine/memmachine:latest-cpu改完两个文件,必须重启容器让配置生效。在 MemMachine 目录下执行:
docker compose down docker compose up -d --build--build会重新构建镜像,确保配置被读进去。等容器起来后,再跑一次健康检查确认服务正常。这一步如果报401或者invalid api key,八成是 Key 没填对或者 Base URL 多了斜杠。TaoToken 的地址是https://taotoken.net/api,结尾不要加/v1,也不要加多余路径,否则会拼出错误的请求地址。配置这块搞定,下一节我们做记忆写入和检索的验证。
4. 验证记忆链路:写入偏好、重启服务、检查召回是否成功
配置写完不代表链路通了,得用真实数据验证一遍。这一节我们做三件事:把一段用户偏好写进记忆、重启服务模拟“关掉对话再打开”、然后提问看它能不能召回。整个过程可复现,你跟着做就能确认自己的系统是否正常。
先确认 Claude Code 已经装好,因为 MemMachine 是通过 MCP 协议接入 Claude Code 的。检查命令:
claude --version有版本号输出就行。然后在 MemMachine 根目录下创建.mcp.json文件,内容如下。这个文件告诉 Claude Code 去哪里找 MemMachine 的 MCP 服务端:
{ "mcpServers": { "memmachine": { "command": "docker", "args": [ "exec", "-i", "memmachine-app", "/app/.venv/bin/memmachine-mcp-stdio" ], "env": { "MEMORY_CONFIG": "/app/configuration.yml", "MM_USER_ID": "your-username", "PYTHONUNBUFFERED": "1" } } } }MM_USER_ID填一个你自己的标识,比如test-user,它用来区分不同用户的记忆空间。保存后启动 Claude Code:
claude进入交互界面后输入/mcp,如果看到memmachine出现在已连接的服务列表里,说明 MCP 通道打通了。接下来做记忆写入。在 Claude 对话框里输入下面这段文本,让它记住你的饮食习惯:
请记住我的饮食习惯:我喜欢吃辣的,尤其是川菜和湘菜。我不吃香菜,也不喜欢海鲜,特别是贝类。我更喜欢家常炒菜,不喜欢油炸食品。我通常早上7点吃早餐,中午12点半午餐,晚上7点晚餐。我习惯先喝汤再吃饭。我在控制碳水摄入,尽量不吃白米饭和面条。我的目标是增肌,每天至少摄入120克蛋白质。我想减到65公斤,所以晚餐吃得比较少。发送后,MemMachine 会把这段内容拆解成情景记忆和档案记忆分别存储。情景记忆记录“用户在某时刻说了这些”,档案记忆提取出“不吃香菜”“喜欢川菜”“目标增肌”这些长期偏好。写入过程可能需要几秒,取决于模型响应速度。
关键验证步骤来了:完全退出 Claude Code,然后重新启动。这一步模拟“关掉对话再打开”的场景,如果记忆只存在上下文里,重启后就没了;如果 MemMachine 正常工作,重启后偏好应该还在。重新进入后,直接提问:
我一天的用餐时间是怎么安排的?如果系统返回类似“你早上7点吃早餐,中午12点半午餐,晚上7点晚餐”的内容,说明记忆召回成功。再问一个偏好类问题:
我不吃什么?预期它能答出“不吃香菜,不喜欢海鲜特别是贝类”。这两个问题一个考情景记忆的时间线,一个考档案记忆的偏好提取,都答对才算链路完整。
如果你想更直观地看记忆存储情况,可以调 MemMachine 的检索接口。不过对大多数场景来说,通过 Claude Code 的对话验证已经够了。实测下来,第一次写入后召回可能需要一两秒,因为要做向量检索和重排;后续命中缓存会快一些。如果召回失败,先检查MM_USER_ID是否和写入时一致,不同用户 ID 的记忆空间是隔离的,写进 A 用户的内容 B 用户查不到。
5. 常见报错排查:401、local proxy failed、reading choices 与 OAuth 问题
链路跑通之前,报错是常态。这一节我把几个高频错误和对应的排查路径列出来,你遇到时可以直接对照。这些报错我在搭建过程中基本都踩过一遍,原因和解法都比较明确。
401 Unauthorized / invalid api key
这是最常见的。表现是容器日志里出现401,或者 Claude 对话时提示模型调用失败。根因通常是 Key 没填对、Key 过期、或者 Base URL 拼错。排查顺序:先确认.env和configuration.yml里的api_key是同一把 Key,且没有多余空格;再确认base_url是https://taotoken.net/api,结尾没有/v1或斜杠;最后去控制台确认这把 Key 还有效、额度没耗尽。如果三处都对还是 401,把容器日志打出来看具体请求地址:
docker logs memmachine-app --tail 100日志里会打印实际请求的 URL,如果 URL 里出现了双斜杠或者路径拼接错误,就是 Base URL 配置问题。
local proxy failed / connection refused
这个报错通常出现在容器内服务访问外部 API 时。表现是记忆写入卡住,日志提示连接失败。根因可能是容器网络配置问题,或者 Base URL 指向了容器内访问不到的地址。检查两点:一是base_url必须是公网可访问的地址,不能写localhost,因为容器内的localhost指向容器自己;二是确认宿主机网络正常,容器能出网。可以用下面命令进容器测试连通性:
docker exec -it memmachine-app curl -v https://taotoken.net/api如果这条命令在容器内能通,说明网络没问题,问题在配置;如果不通,检查 Docker 的网络模式。
reading choices / 响应解析失败
这个报错说明请求发出去了,但返回的内容格式不符合预期。常见原因是 Model ID 填错了,比如填了一个不存在的模型名,接口返回了错误结构,而客户端按正常响应去解析choices字段,就报reading choices。排查方法是把 Model ID 和接入文档里的可用列表对一遍,确认拼写完全一致。另外 embedding 模型的dimensions如果和实际模型输出维度不一致,也会在写入阶段报解析错误,这时候要改dimensions值。
OAuth / 认证流程异常
如果你在 Claude Code 侧看到 OAuth 相关报错,通常是 MCP 配置里的环境变量没传对。检查.mcp.json里的env段,MEMORY_CONFIG路径要指向容器内的实际路径/app/configuration.yml,MM_USER_ID不能为空。另外 Claude Code 本身的登录态如果失效,也会报认证错误,重新登录一次即可。
记忆写入成功但召回为空
这个不是报错,但很常见。表现是写入时没报错,提问时却答“不知道”。排查方向:确认写入和查询用的是同一个MM_USER_ID;确认两次操作之间没有清空数据库;确认 embedding 模型在写入和查询时是同一个,换了 embedding 模型会导致向量空间不一致,检索不到。如果都正常,把message_capacity调大一点,默认 500 条消息的容量在测试时够用,但如果你写入了大量内容,可能被截断。
排查完这些,链路基本就稳了。下一节说下长期用起来的一些实际建议和通道选择。
6. 长期编码与 Agent 场景的通道选择:把记忆系统跑成日常基础设施
记忆系统搭好只是第一步,真正让它产生价值的是长期跑起来。我自己的用法是把它挂在日常编码 Agent 上,每次开新会话,Agent 会自动召回项目规范、命名习惯、技术栈偏好,不用我重复交代。这种场景对模型通道的稳定性要求比较高,因为记忆检索本身要调 embedding 模型,对话又要调对话模型,两条链路都得通。
如果你的使用频率不高,只是偶尔试试,那用 API Keys 的方式按量调用就够了,去https://taotoken.net/console/api-keys创建 Key,配到 MemMachine 里就能跑。但如果你打算把带记忆的 Agent 当成日常工具,每天都要跑很多轮对话和检索,那 Coding Plan 会更合适,它在长期编码和 Agent 场景下有更稳定的通道保障,入口在https://taotoken.net/coding-plan。我实测下来,高频调用时用 Plan 的通道,响应抖动会小一些,记忆召回的成功率也更稳。
还有一个实际经验:记忆系统刚搭好的时候,别急着灌大量数据。先写几条关键偏好,验证召回正常,再逐步增加。因为记忆检索是按相关性排序的,如果早期写入了太多噪音,反而会干扰召回质量。等链路稳定了,再让它自然积累。另外定期检查一下 Neo4j 和 Postgres 的存储占用,长期跑下来数据量会增长,虽然不大,但心里有数比较好。
如果你在配置过程中卡在某个报错,或者想确认某个 Model ID 是否可用,接入文档里有完整的参数说明,地址是https://taotoken.net/doc。模型对话页面也可以用来快速验证通道是否正常,地址是https://taotoken.net/model-chat。把记忆系统跑成日常基础设施之后,你会发现 Agent 的体验完全不一样了,它不再是每次从零开始的工具,而是一个真正记得你的助手。