ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agent Memory架构实战:从Working Memory到Long-term Memory的完整设计

2026/10/3 9:37:33 拓冰建站 浏览量
Agent Memory架构实战:从Working Memory到Long-term Memory的完整设计 1. 从“hindsight”说起为什么我们需要给Agent装上“后视镜”“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在Agent Memory这个领域里它指向一个非常具体且关键的问题当LLM Agent完成一次任务后它能不能记住自己刚才做了什么、为什么这么做、结果如何并在下一次遇到类似场景时调用这些经验我接触过不少做Agent开发的团队大家一开始都把精力放在工具调用、提示词工程、多轮对话管理上但跑了一段时间后普遍会遇到同一个瓶颈——Agent像金鱼一样每次对话都是“失忆”状态。用户昨天刚纠正过的错误今天它又犯一遍上周已经确认过的业务规则这周重新问一遍它还是不知道。这不是模型能力的问题而是记忆架构缺失的问题。“hindsight”这个项目标题本质上就是在解决Agent的长期记忆与经验回溯问题。它要做的不是简单的对话历史存储而是让Agent具备“回头看”的能力能够从过去的交互中提取结构化经验能够在需要的时候检索到相关的历史决策能够根据反馈调整未来的行为策略。这套东西做得好不好直接决定了Agent是“一次性工具”还是“越用越聪明的助手”。这篇文章适合三类人看第一类是正在做Agent应用开发、被记忆问题困扰的工程师第二类是对LLM Agent架构感兴趣、想了解记忆模块怎么设计的技术爱好者第三类是用过Docker、MCP这些工具想看看它们在Agent记忆场景下怎么配合落地的实践者。我会从架构设计、核心实现、实操部署、问题排查几个维度把“hindsight”这类Agent Memory系统的完整面貌拆开来讲。2. Agent Memory的核心架构Working Memory与Long-term Memory怎么分工2.1 为什么不能只靠上下文窗口很多人第一反应是现在LLM的上下文窗口都到128K甚至1M token了直接把所有历史对话塞进去不就行了这个思路在Demo阶段能跑通但放到生产环境立刻崩掉。原因有三个成本问题。每次请求都把几万token的历史带上按API计费模式算一个月下来账单能吓死人。我见过一个客服Agent项目没做记忆压缩之前单次对话成本是做了记忆分层之后的17倍。注意力稀释。上下文越长模型对关键信息的注意力越容易被稀释。你把50轮对话塞进去模型很可能“忘记”第3轮用户明确说过的偏好设置。这不是模型不行是注意力机制本身的特性决定的。时效性冲突。用户上周说“我喜欢简洁的回答”这周说“给我详细解释一下”如果两段记忆同等权重放在上下文里模型很难判断该听哪个。所以Agent Memory的第一条设计原则就是分层。Working Memory负责当前会话的即时上下文Long-term Memory负责跨会话的经验沉淀两者通过检索机制按需桥接。2.2 Working Memory的设计要点Working Memory可以理解为Agent的“桌面”当前任务需要的信息就摊在桌面上任务结束就收走。它的核心挑战不是存储而是压缩与摘要。我一般建议采用滑动窗口加摘要的混合策略保留最近N轮完整对话N通常取5到10更早的内容用LLM生成结构化摘要。摘要不是简单概括而是提取出“用户意图、关键约束、已确认事实、待办事项”这几个字段。这样即使原始对话被丢弃关键信息仍然以紧凑形式保留。注意摘要生成本身也要消耗token所以不要每轮都重新生成全量摘要。我的做法是每5轮触发一次增量摘要把新内容合并到已有摘要里这样成本可控。2.3 Long-term Memory的存储与检索Long-term Memory是“hindsight”真正发挥价值的地方。它要解决的是当新任务到来时如何从海量历史经验中找到最相关的那几条。这里涉及三个关键决策存什么。不是所有对话都值得长期保存。我的经验是只存三类内容用户明确纠正过的错误、成功完成复杂任务的决策路径、用户显式表达的偏好。其他日常闲聊、中间过程全部丢弃。怎么存。向量数据库是标配但纯向量检索有个坑——它擅长语义相似不擅长精确匹配。比如用户问“上次那个订单号是多少”向量检索可能返回一堆语义相近但订单号不对的记录。所以实际系统里通常是向量检索加结构化过滤双路并行先用元数据时间范围、用户ID、任务类型缩小范围再做语义排序。怎么取。检索回来的记忆不能直接塞进上下文需要做相关性重排序。我常用的是“LLM as judge”模式让模型对检索结果打分只保留置信度高的。虽然多了一次LLM调用但能显著降低噪声干扰。2.4 MCP在记忆系统中的角色MCPModel Context Protocol在这里扮演的是标准化接口层的角色。你可以把它理解成Agent和外部工具之间的“USB协议”——不管后面接的是数据库、文件系统还是APIAgent都通过统一的协议去调用。在Agent Memory场景下MCP的价值在于把记忆的读写操作标准化。比如定义一个memory_store工具和一个memory_retrieve工具Agent不需要知道底层用的是Redis还是PostgreSQL只需要按MCP格式发请求就行。这样换存储后端的时候Agent侧的代码几乎不用改。提示MCP是软件协议层面的概念和硬件接口协议不是一回事。它的核心是定义模型与工具之间的通信格式让不同厂商的工具能以统一方式接入。3. 核心细节解析从Token三元组到记忆生命周期管理3.1 理解LLM的Token三元组Key、Query、Value热词里有一条说得挺形象“LLM的token三个点key我是谁、query我在找什么、value我能提供什么”。这其实就是注意力机制的本质。在Agent Memory系统里这个机制被放大到了记忆检索层面。每条记忆在存入时需要生成三个维度的表示Key我是谁这条记忆是关于什么的通常用摘要向量表示。Query我在找什么当前任务需要什么信息用当前对话的意图向量表示。Value我能提供什么这条记忆的具体内容包括原始文本、结构化字段、时间戳等。检索时计算Query和Key的相似度返回对应的Value。听起来简单但实际调优时Key的生成质量直接决定检索准确率。我的经验是Key不要用原始文本的embedding而要用LLM生成的“记忆标题”的embedding这样语义更集中。3.2 记忆的生命周期写入、索引、检索、衰减、淘汰一套完整的记忆系统需要管理记忆的完整生命周期阶段操作关键考量写入从对话中提取值得记忆的内容提取策略决定信噪比索引生成向量和元数据索引质量决定检索上限检索根据当前上下文召回相关记忆重排序策略决定精度衰减根据时间和使用频率降低权重避免过时信息干扰淘汰删除低价值记忆控制存储成本和噪声衰减机制特别值得展开说。我一般用指数衰减加使用频率加权记忆的权重 基础权重 × exp(-λ × 天数) × (1 使用次数 × α)。λ取0.01到0.05之间α取0.1左右。这样一条三个月前从未被使用的记忆权重会降到很低但不会完全消失而一条经常被调用的记忆即使时间久远也能保持较高权重。3.3 记忆冲突的处理策略实际系统里经常遇到记忆冲突用户上周说“预算控制在5000以内”这周说“预算可以到8000”。两条记忆都存着检索时都返回了Agent该听谁的我的处理策略是时间优先加显式覆盖新记忆默认覆盖旧记忆但如果旧记忆被标记为“长期有效”比如用户说“这是公司规定”则保留并标注冲突。检索时如果发现冲突把两条都返回给LLM让模型根据当前上下文判断。实测下来LLM处理这种冲突的能力比规则引擎强得多。注意不要试图用规则解决所有冲突规则越复杂越容易出bug。把判断权交给模型但要在提示词里明确告诉它“如果发现记忆冲突优先采用时间更近的除非旧记忆被标记为长期有效”。3.4 与Docker的配合容器化部署记忆服务Agent Memory服务通常需要独立部署因为它要维护向量数据库、缓存、持久化存储等多个组件。Docker Compose是这里的最佳搭档。一个典型的docker-compose.yml结构大概是这样的version: 3.8 services: memory-api: build: ./memory-api ports: - 8080:8080 environment: - VECTOR_DB_URLhttp://vector-db:6333 - REDIS_URLredis://redis:6379 depends_on: - vector-db - redis vector-db: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - ./data/qdrant:/qdrant/storage redis: image: redis:7-alpine ports: - 6379:6379 volumes: - ./data/redis:/data这样一套跑起来记忆服务就有了独立的API层、向量存储层和缓存层。Docker的网络配置让这几个容器通过服务名互相访问不需要暴露额外端口到宿主机。4. 实操过程从零搭建一套Agent Memory系统4.1 环境准备与依赖安装先确认基础环境。Windows用户建议用WSL2因为Docker Desktop在WSL2下的性能明显好于Hyper-V后端。安装Docker Desktop时如果遇到“Virtualization support not detected”报错通常是BIOS里没开虚拟化进BIOS把Intel VT-x或AMD-V打开就行。安装完Docker Desktop后验证一下docker --version docker compose version两个命令都能正常输出版本号说明环境OK。接下来拉取必要的镜像docker pull qdrant/qdrant:latest docker pull redis:7-alpine docker pull python:3.11-slim4.2 记忆服务的核心代码实现记忆服务的API层我用FastAPI写核心就三个接口写入记忆、检索记忆、更新记忆权重。from fastapi import FastAPI from pydantic import BaseModel from typing import List, Optional import uuid import time app FastAPI() class MemoryItem(BaseModel): content: str memory_type: str # correction, preference, procedure user_id: str metadata: Optional[dict] {} class RetrieveQuery(BaseModel): query: str user_id: str top_k: int 5 app.post(/memory/write) async def write_memory(item: MemoryItem): memory_id str(uuid.uuid4()) # 生成记忆标题用于索引 title generate_memory_title(item.content) # 存入向量数据库 vector_db.upsert( collection_nameagent_memory, points[{ id: memory_id, vector: embed(title), payload: { content: item.content, type: item.memory_type, user_id: item.user_id, created_at: time.time(), access_count: 0, metadata: item.metadata } }] ) return {memory_id: memory_id, status: stored} app.post(/memory/retrieve) async def retrieve_memory(query: RetrieveQuery): query_vector embed(query.query) results vector_db.search( collection_nameagent_memory, query_vectorquery_vector, query_filter{ must: [{key: user_id, match: {value: query.user_id}}] }, limitquery.top_k * 2 # 多召回一些用于重排序 ) # 重排序结合相似度、时间衰减、使用频率 reranked rerank(results, query.query) # 更新访问计数 for r in reranked[:query.top_k]: vector_db.set_payload( collection_nameagent_memory, payload{access_count: r.payload[access_count] 1}, points[r.id] ) return {memories: reranked[:query.top_k]}generate_memory_title这个函数很关键它用LLM把原始内容压缩成一句话标题比如把“用户说以后回答不要用表格用纯文本就行”压缩成“用户偏好纯文本回答禁用表格”。这样生成的向量语义更集中检索准确率能提升不少。4.3 记忆提取的提示词设计从对话中提取值得记忆的内容提示词设计直接决定信噪比。我用的模板大概是这样的你是一个记忆提取器。从以下对话中提取值得长期记忆的信息。 只提取以下三类 1. 用户明确纠正的错误correction 2. 用户表达的偏好preference 3. 成功完成复杂任务的步骤procedure 不要提取日常问候、中间推理过程、模型自己的解释。 输出格式为JSON数组每个元素包含 - content: 记忆内容一句话 - type: correction/preference/procedure - confidence: 0-1之间的置信度 对话内容 {conversation}实测下来这个提示词能把信噪比控制在可接受范围内。confidence低于0.6的直接丢弃不存入长期记忆。4.4 与MCP的对接如果Agent框架支持MCP可以把记忆服务包装成MCP工具。核心是定义一个工具描述文件{ name: agent_memory, description: 读写Agent长期记忆, tools: [ { name: memory_write, description: 写入一条长期记忆, parameters: { content: {type: string, description: 记忆内容}, type: {type: string, enum: [correction, preference, procedure]} } }, { name: memory_retrieve, description: 检索相关长期记忆, parameters: { query: {type: string, description: 检索查询}, top_k: {type: integer, default: 5} } } ] }Agent在需要的时候调用这两个工具不需要关心底层存储细节。这样即使以后把Qdrant换成MilvusAgent侧完全无感。4.5 完整部署流程把代码和配置准备好之后部署流程如下在项目根目录创建docker-compose.yml内容参考3.4节的配置。创建memory-api目录放入FastAPI代码和requirements.txt。创建DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8080]执行docker compose up -d启动所有服务。用docker compose logs -f memory-api查看日志确认服务正常启动。用curl测试写入和检索接口curl -X POST http://localhost:8080/memory/write \ -H Content-Type: application/json \ -d {content:用户偏好纯文本回答,memory_type:preference,user_id:user_001} curl -X POST http://localhost:8080/memory/retrieve \ -H Content-Type: application/json \ -d {query:回答格式偏好,user_id:user_001,top_k:3}如果两个接口都正常返回说明基础链路通了。5. 常见问题与排查技巧实录5.1 记忆检索不准确怎么办这是最高频的问题。排查顺序建议从索引质量开始查起。先看记忆标题生成得怎么样。如果标题太泛比如“用户说了一些话”向量检索肯定不准。改进方法是优化generate_memory_title的提示词要求标题必须包含具体实体和动作。再看检索时的过滤条件。如果只按user_id过滤范围太大噪声自然多。可以加上时间范围过滤比如只检索最近30天的记忆或者按memory_type过滤当前任务需要偏好类记忆就只查preference类型。最后看重排序策略。如果相似度分数普遍在0.7以下说明要么索引有问题要么查询向量和记忆向量不在同一语义空间。检查embedding模型是否一致写入和检索必须用同一个模型。5.2 Docker网络不通的排查Docker Compose环境下容器之间通过服务名通信。如果memory-api连不上vector-db先确认两点第一depends_on只保证启动顺序不保证服务就绪。vector-db启动可能需要几秒钟memory-api如果启动太快会连接失败。解决办法是在memory-api里加重试逻辑或者用healthcheckvector-db: image: qdrant/qdrant:latest healthcheck: test: [CMD, curl, -f, http://localhost:6333/health] interval: 5s retries: 5第二确认端口配置。容器内部通信走的是容器端口比如6333不是宿主机映射端口。memory-api里配置的VECTOR_DB_URL应该是http://vector-db:6333而不是http://localhost:6333。5.3 记忆膨胀导致成本失控跑了一段时间后记忆库越来越大检索变慢存储成本上升。这时候需要做记忆淘汰。我的策略是每周跑一次清理任务删除access_count为0且创建时间超过90天的记忆删除confidence低于0.5的记忆对同一用户的同类记忆做去重合并。清理任务本身也用Docker Cron Job跑不占用主服务资源。提示淘汰之前先备份。我吃过亏有一次清理脚本写错了条件把用户明确标记为“长期有效”的记忆也删了导致Agent行为异常。后来加了备份步骤清理前先导出到冷存储。5.4 常见问题速查表问题现象可能原因排查方向解决方案检索返回空过滤条件太严检查user_id和type过滤放宽过滤条件或增加召回数量检索结果不相关索引质量差检查记忆标题生成优化标题提示词服务启动失败依赖服务未就绪查看容器日志加healthcheck和重试记忆冲突新旧记忆同时召回检查时间戳和权重启用时间衰减和冲突标记成本过高记忆无淘汰统计记忆总量和调用量启用淘汰策略和摘要压缩写入失败向量维度不匹配检查embedding模型统一写入和检索的模型5.5 几个踩过的坑第一个坑是embedding模型不一致。有一次升级了embedding模型但只更新了检索侧写入侧还是旧模型导致新写入的记忆检索不到。后来在代码里加了模型版本校验不匹配直接报错。第二个坑是摘要生成死循环。摘要生成本身调用LLM如果摘要内容又触发了新的记忆提取会无限循环。解决办法是在提取提示词里明确排除“摘要内容”这个来源。第三个坑是Docker volume权限问题。Qdrant容器写入宿主机目录时如果目录权限不对会启动失败。Linux下用chown -R 1000:1000 ./data/qdrant解决Windows下一般没这个问题。5.6 性能调优的几个方向如果记忆服务响应变慢可以从这几个方向优化批量写入。不要一条一条写攒够10条批量upsertQdrant的批量写入性能比单条高一个数量级。索引预热。服务启动后先跑一次全量索引加载避免第一次检索时冷启动。缓存热点记忆。用Redis缓存最近被频繁访问的记忆减少向量数据库查询次数。缓存过期时间设短一点比如5分钟保证一致性。异步写入。记忆写入不需要同步等待结果可以丢到消息队列里异步处理。这样Agent的主流程不会被写入操作阻塞。6. 记忆系统的扩展方向与个人实践体会这套架构跑通之后扩展方向其实挺多的。比如可以加一个记忆可视化面板让用户看到Agent记住了什么、哪些记忆被调用了、哪些记忆在冲突。这对调试和建立信任都很有帮助。还可以做跨Agent记忆共享。多个Agent共用一套记忆库但通过命名空间隔离。这样客服Agent学到的用户偏好推荐Agent也能用上用户体验更连贯。另外记忆的主动遗忘也值得研究。不是所有记忆都值得保留有些敏感信息用户可能希望被遗忘。提供一个“忘记这条”的接口既是功能也是合规需要。我自己在实际操作中的体会是Agent Memory系统最难的不是技术实现而是信噪比的平衡。存太多检索噪声大存太少Agent又不够聪明。这个平衡点没有标准答案需要根据具体业务场景反复调。我的建议是先跑起来用真实数据观察哪些记忆被调用了、哪些从来没被用过然后根据数据调整提取策略和淘汰策略。迭代几轮之后系统会慢慢收敛到一个比较舒服的状态。最后分享一个小技巧在记忆的metadata里加一个source字段记录这条记忆是从哪次对话、哪个任务里提取的。排查问题的时候能直接追溯到原始上下文比只看记忆内容高效得多。