1. 从“hindsight”这个词说起:为什么记忆是 Agent 最被低估的能力
“hindsight”这个词本身很有意思,字面意思是“事后的洞察力”,也就是我们常说的“后见之明”。把这个词用在一个 Agent 项目上,指向性其实非常明确:它要解决的不是 Agent 能不能干活,而是 Agent 干完活之后,能不能记住自己干了什么、从中学到了什么、下次遇到类似情况能不能做得更好。
我接触过不少做 Agent 的团队,大家一开始的注意力几乎都放在“工具调用”上——怎么让模型能查数据库、能发请求、能操作文件。这些当然重要,但跑一段时间就会发现一个很尴尬的现象:同一个 Agent,今天你教它处理了一个边界情况,明天它又犯同样的错;用户上周明确说过“这个字段不要动”,这周它照样去改。问题不在于模型不够聪明,而在于它没有一套像样的记忆机制。
这就是agent memory这个方向最近被反复讨论的原因。它和传统的“对话历史拼接”完全不是一回事。对话历史只是把过去几轮的消息塞进上下文窗口,本质上是无状态的、易失的、随窗口滑动而丢失的。而 agent memory 要做的是:把 Agent 在运行过程中产生的关键信息——用户的偏好、任务的中间结论、工具调用的成功与失败模式、环境的状态变化——抽取出来,结构化地存下来,并在需要的时候精准地取回来。
“hindsight”这个项目名,我理解它想强调的是记忆的“回溯性价值”:不是简单地存流水账,而是让 Agent 具备回头看、做复盘、提炼经验的能力。这背后涉及几个核心技术点:记忆的写入策略(什么该记、什么不该记)、记忆的组织结构(working memory 和 long-term memory 怎么分层)、记忆的检索机制(怎么在正确的时机召回正确的记忆),以及记忆的更新与遗忘(过时的、错误的记忆怎么清理)。
关键词里出现的LLM、MCP、Docker三个词,基本勾勒出了这个项目的技术底座:用 LLM 做记忆的抽取、压缩和检索决策;用 MCP 作为 Agent 与外部记忆存储之间的标准接口;用 Docker 做部署和隔离。这三个词放在一起,说明这不是一个纯理论项目,而是一个可以跑起来、可以集成进现有 Agent 框架的工程化方案。
这篇文章我会围绕“hindsight”这个标题,把 agent memory 这件事从需求、原理、架构、实操到踩坑完整讲一遍。不管你是刚接触 Agent 开发的新手,还是已经在做多轮任务编排的老手,应该都能从中拿到一些可以直接用的东西。
2. Agent 记忆到底难在哪:三个绕不过去的核心矛盾
2.1 上下文窗口的物理限制与“什么都想记”的冲突
做 Agent 的人第一个会撞上的墙就是上下文窗口。现在的模型动辄 128K、200K token,看起来很大,但真正跑起长任务来根本不够用。一个稍微复杂点的任务,工具调用的返回结果、中间推理过程、多轮修正,很容易就堆到几十 K。如果你还想把历史记忆全塞进去,窗口瞬间就爆了。
更麻烦的是,即使窗口装得下,塞得越多,模型越容易“分心”。这是有实测依据的:当上下文里混入大量与当前任务弱相关的历史信息时,模型对关键指令的遵循度会明显下降。我见过一个案例,Agent 在处理退款流程时,因为上下文里残留了上一单的物流信息,结果把两个订单的地址搞混了。这不是模型笨,是信息过载导致的注意力稀释。
所以 agent memory 的第一个核心矛盾就是:记忆的价值在于“全”,但记忆的可用性在于“精”。你不能什么都往上下文里塞,必须有一套筛选和压缩机制。hindsight 这类项目要解决的,正是这个“记什么、怎么记、什么时候取”的问题。
2.2 记忆的时效性:working memory 与 long-term memory 的分层
热词里出现了agent 存储 working memory,这个词很关键。working memory 可以类比成人脑的“短期记忆”,它服务于当前正在执行的任务,生命周期短、容量小、访问频率高。比如你正在帮用户订机票,那么“用户要的是靠窗座位”“出发日期是下周三”这些信息就属于 working memory,任务结束就可以释放。
而 long-term memory 是跨任务、跨会话的,它存的是更稳定的东西:用户的长期偏好、领域知识、过去任务中总结出的经验教训。这两层记忆的管理策略完全不同。working memory 追求的是低延迟、高吞吐,通常放在内存或本地缓存里;long-term memory 追求的是可持久化、可检索,通常放在向量数据库或关系型数据库里。
很多 Agent 项目失败的原因,就是把这两层混在一起了。要么把所有东西都当长期记忆存,导致检索噪声极大;要么完全不存长期记忆,每次任务都从零开始。hindsight 的价值就在于它明确区分了这两层,并且定义了它们之间的流转规则——什么情况下 working memory 的内容应该沉淀为 long-term memory,什么情况下应该直接丢弃。
2.3 检索的精准度:为什么“相似”不等于“有用”
记忆存下来之后,最大的挑战是检索。现在主流做法是用向量相似度做召回,但这里有个很隐蔽的坑:语义相似不等于任务相关。
举个例子,用户之前问过“怎么重置密码”,现在问“怎么修改绑定邮箱”。这两句话在向量空间里可能很接近,因为都涉及“账户设置”这个语义域。但如果你把重置密码的操作步骤召回给修改邮箱的任务,那就是干扰。真正有用的记忆可能是“用户上次修改账户信息时,要求先验证手机号”这条元信息,而不是具体的操作步骤。
所以好的记忆检索不能只靠向量相似度,还要结合时间衰减(越近的记忆权重越高)、任务类型匹配(同类任务的记忆优先)、显式标签(用户明确标记为重要的记忆)等多个维度。hindsight 如果要在检索上做出差异化,这一块是必须下功夫的。
3. hindsight 的记忆架构拆解:从写入到召回的全链路
3.1 记忆写入:不是所有对话都值得存
记忆写入的第一步是判断“这条信息值不值得记”。如果每轮对话都写一条记忆,那存储很快就会爆炸,检索质量也会被稀释。我的经验是,至少要过三道筛子:
第一道是信息密度筛。纯寒暄、纯确认类的内容直接丢弃,比如“好的”“收到”“谢谢”。这些对后续任务没有任何价值。
第二道是稳定性筛。区分“一次性事实”和“持久性偏好”。用户说“这次帮我用顺丰”,这是一次性事实,任务结束就失效;用户说“以后都用顺丰”,这是持久性偏好,应该写入长期记忆。这个判断可以交给 LLM 来做,用一个轻量的分类 prompt 就能搞定。
第三道是冲突检测筛。新记忆写入前,要检查是否与已有记忆冲突。比如用户之前说“默认用人民币结算”,现在说“默认用美元结算”,那旧记忆就应该被标记为失效,而不是两条并存。否则检索时召回两条矛盾的信息,模型会无所适从。
在 hindsight 的实现里,我建议把写入流程设计成一个独立的 pipeline:抽取 → 分类 → 去重 → 冲突检测 → 落库。每一步都可以用 LLM 加规则的方式来做,不必全部依赖模型,规则能覆盖的场景用规则更快更稳。
3.2 记忆的组织:结构化字段比纯文本向量更可靠
很多人做记忆存储,直接就把一段文本丢进向量库,靠 embedding 检索。这种做法在 demo 阶段能用,但上了生产就会暴露问题:检索结果不可控、无法做精确过滤、无法做聚合统计。
我的建议是,记忆条目至少包含以下结构化字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| memory_id | string | 唯一标识 |
| content | text | 记忆的正文内容 |
| memory_type | enum | 偏好/事实/经验/约束 |
| scope | enum | 全局/会话级/任务级 |
| source | string | 来源(用户输入/工具返回/模型推理) |
| created_at | timestamp | 创建时间 |
| last_accessed | timestamp | 最后访问时间 |
| access_count | int | 访问次数 |
| confidence | float | 置信度 |
| embedding | vector | 语义向量 |
| tags | array | 显式标签 |
有了这些字段,检索时就可以做复合查询:先按 memory_type 和 scope 过滤,再按向量相似度排序,最后按时间衰减和访问频率加权。这样召回的记忆质量会比纯向量检索高一个档次。
特别说一下confidence这个字段。记忆是有可信度差异的:用户明确说出来的偏好,置信度可以给 0.9 以上;模型从对话中推断出来的,置信度可能只有 0.6;工具返回的临时状态,置信度更低。检索时对低置信度记忆做降权,能有效减少误召回。
3.3 记忆召回:在正确的时机给模型正确的上下文
召回策略是 hindsight 最核心的部分。我的做法是把召回拆成两个触发点:
主动召回:在每轮任务开始前,根据当前用户输入,检索相关记忆,注入到 system prompt 或上下文开头。这部分记忆是“预加载”的,目的是让模型在开始推理前就具备必要的背景知识。
被动召回:在任务执行过程中,当模型显式请求记忆时(比如通过一个recall_memory工具),再按需检索。这种方式更精准,但依赖模型主动发起调用。
两种方式各有优劣。主动召回覆盖全面,但可能引入噪声;被动召回精准,但可能遗漏。实际项目中我倾向于主动召回打底 + 被动召回补充:先用一个轻量检索把高置信度的核心记忆注入,然后在工具列表里暴露 recall 接口,让模型在需要细节时自己去查。
召回的数量也要控制。我的经验值是:主动召回不超过 5 条,被动召回每次不超过 3 条。超过这个数量,上下文噪声会明显上升,模型反而容易忽略关键信息。
3.4 记忆更新与遗忘:让记忆系统保持“新鲜”
记忆系统如果只增不减,用不了多久就会变成垃圾场。遗忘机制不是可选项,是必选项。
遗忘策略我一般分三种:
- 时间衰减:超过一定时间未被访问的记忆,降低权重,最终归档或删除。具体阈值看业务场景,高频交互场景可以设 7 天,低频场景可以设 30 天。
- 冲突替换:新记忆与旧记忆冲突时,旧记忆标记为 superseded,不再参与召回,但保留用于审计。
- 容量淘汰:当某个 scope 下的记忆数量超过上限时,按“访问频率 × 置信度 × 时间新鲜度”打分,淘汰低分项。
这里有个容易忽略的点:遗忘不等于删除。很多场景下,旧记忆虽然不再用于召回,但需要保留用于追溯和审计。比如金融、医疗类 Agent,用户偏好变更的历史记录是有合规价值的。所以物理删除要谨慎,逻辑失效是更稳妥的做法。
4. 用 MCP 把记忆能力标准化:接口设计的取舍
4.1 为什么选 MCP 而不是自定义 API
MCP(Model Context Protocol)这两年被讨论得很多,热词里也反复出现。它的核心价值是把 Agent 与外部能力的交互标准化。在没有 MCP 之前,每个 Agent 框架对接记忆存储都要自己写一套适配层,换一个框架就得重写。MCP 出现之后,记忆服务只要实现一套 MCP Server,理论上可以被任何支持 MCP 的客户端调用。
对于 hindsight 这样的记忆项目,选 MCP 的理由很直接:记忆是一个典型的“跨框架通用能力”。不管你是用哪个 Agent 框架,都需要记忆;而记忆的接口语义是相对稳定的——存、取、查、删、更新。这种场景最适合用标准协议来抽象。
当然,MCP 也不是没有代价。它的抽象层会带来一定的性能开销,而且协议本身还在演进中,某些高级特性(比如流式返回、批量操作)的支持程度因实现而异。我的建议是:核心记忆操作走 MCP,高频低延迟的内部操作走本地直连。不要为了标准化而牺牲所有性能。
4.2 记忆 MCP Server 的工具设计
如果要把 hindsight 的记忆能力封装成 MCP Server,我建议暴露以下工具:
{ "tools": [ { "name": "memory_write", "description": "写入一条记忆", "parameters": { "content": "string, 记忆内容", "memory_type": "enum, 偏好/事实/经验/约束", "scope": "enum, 全局/会话级/任务级", "confidence": "float, 0-1" } }, { "name": "memory_recall", "description": "根据查询召回相关记忆", "parameters": { "query": "string, 查询文本", "top_k": "int, 返回条数", "memory_type_filter": "array, 类型过滤" } }, { "name": "memory_update", "description": "更新已有记忆", "parameters": { "memory_id": "string", "content": "string", "confidence": "float" } }, { "name": "memory_forget", "description": "使记忆失效", "parameters": { "memory_id": "string", "reason": "string" } } ] }工具设计有几个原则:参数尽量扁平,避免嵌套结构,因为模型对扁平参数的填充准确率更高;枚举值要明确,不要让模型自由发挥;每个工具只做一件事,不要把写入和更新混在一个工具里。
4.3 MCP 接入时的常见坑
MCP 接入过程中我踩过的坑主要有几个:
第一个是工具描述过长导致模型忽略。MCP 的工具描述会占用上下文,如果每个工具的描述都写一大段,模型在工具选择时容易犯迷糊。我的做法是描述控制在两句话以内,详细说明放在服务端的文档里,不塞进工具描述。
第二个是超时设置不合理。记忆检索如果走向量库,冷启动时可能比较慢。MCP 客户端默认超时往往偏短,导致检索还没返回就被判定失败。建议把记忆类工具的超时单独调大,比如设到 10 秒。
第三个是错误返回格式不统一。MCP 对错误返回有约定格式,但很多自研 Server 实现时没注意,返回了非标准结构,导致客户端解析失败。这个在联调阶段一定要用标准客户端测一遍。
5. Docker 化部署:让记忆服务真正跑起来
5.1 为什么记忆服务适合容器化
记忆服务是一个典型的有状态服务,它依赖数据库、向量库、缓存,配置项多,环境依赖复杂。用 Docker 部署的好处很直接:环境一致、依赖隔离、迁移方便。
更重要的是,记忆服务往往需要和 Agent 主服务分开部署。Agent 主服务可能是无状态的、可以水平扩展的;但记忆服务是有状态的,扩展策略不同。用 Docker Compose 把两者编排在一起,既能共享网络,又能独立扩缩容,是比较务实的做法。
5.2 一个可用的 docker-compose 编排
下面是我实际用过的一个编排方案,包含记忆服务本体、PostgreSQL(存结构化记忆)、Redis(存 working memory 缓存)、以及一个向量检索组件:
version: "3.8" services: memory-service: build: ./memory-service ports: - "8080:8080" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_NAME=hindsight - DB_USER=hindsight - DB_PASSWORD=${DB_PASSWORD} - REDIS_HOST=redis - REDIS_PORT=6379 - VECTOR_STORE_URL=http://vector-store:8000 depends_on: postgres: condition: service_healthy redis: condition: service_started restart: unless-stopped postgres: image: postgres:16-alpine environment: - POSTGRES_DB=hindsight - POSTGRES_USER=hindsight - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U hindsight"] interval: 5s timeout: 3s retries: 5 redis: image: redis:7-alpine command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data vector-store: image: qdrant/qdrant:latest volumes: - qdrant_data:/qdrant/storage volumes: pg_data: redis_data: qdrant_data:这个编排有几个细节值得说:
PostgreSQL 用了 healthcheck,确保记忆服务在数据库真正就绪后才启动。我见过太多因为启动顺序问题导致的连接失败,加个 healthcheck 能省很多事。
Redis 设了 maxmemory 和淘汰策略。working memory 是缓存性质的数据,不需要无限增长。设成 256MB 加 LRU 淘汰,既控制了内存,又保证了热点数据不丢。
向量库单独一个服务。不要把向量检索塞进 PostgreSQL 的 pgvector 里凑合,除非你的数据量真的很小。专门的向量库在召回性能和过滤能力上要好得多。
5.3 部署时的资源规划与常见故障
记忆服务的资源消耗主要在三块:向量检索的 CPU/内存、数据库的磁盘 IO、LLM 调用的 token 成本。
向量检索这块,如果记忆条目在十万级以内,2 核 4G 的配置基本够用。超过百万级,就要考虑分片或者上专门的向量集群了。
数据库这块,PostgreSQL 的写入压力主要来自记忆写入。如果 Agent 交互频繁,建议把写入做成异步的,不要阻塞主流程。可以用消息队列缓冲,或者直接在服务内部起一个写入队列。
常见故障我列几个:
| 故障现象 | 可能原因 | 排查方向 |
|---|---|---|
| 记忆服务启动后立即退出 | 数据库连接失败 | 检查 depends_on 和 healthcheck |
| 召回结果为空 | 向量库未初始化或索引未建 | 检查向量库日志和集合状态 |
| 召回延迟高 | 向量库数据量过大或索引参数不当 | 调整 HNSW 参数或增加副本 |
| 记忆重复写入 | 去重逻辑未生效 | 检查写入 pipeline 的去重步骤 |
| 内存持续增长 | working memory 未设置过期 | 检查 Redis 淘汰策略和 TTL |
6. 记忆质量调优:从“能跑”到“好用”的关键几步
6.1 记忆抽取的 prompt 设计
记忆抽取的质量直接决定了整个系统的上限。我的经验是,抽取 prompt 要满足几个条件:
明确输出格式。不要让模型自由发挥,用 JSON schema 约束输出。比如要求返回{"should_remember": bool, "memory_type": "...", "content": "...", "confidence": 0.0-1.0}。
给出正反例。在 prompt 里放几个“该记”和“不该记”的例子,比单纯描述规则有效得多。模型对示例的遵循度远高于对抽象规则的理解。
控制抽取粒度。一条记忆只表达一个独立的事实或偏好。不要把“用户喜欢靠窗座位且偏好早班机”合成一条,应该拆成两条。这样检索时才能精准命中。
保留原始上下文引用。记忆条目里最好带上来源对话的 ID 或时间戳,方便后续追溯。当记忆出现问题时,能快速定位是哪次交互产生的。
6.2 检索权重的调参经验
检索排序的权重分配,我一般从这组默认值开始调:
- 向量相似度:0.5
- 时间新鲜度:0.2
- 访问频率:0.15
- 置信度:0.15
然后根据业务反馈调整。如果发现召回的记忆经常“太老”,就加大时间新鲜度的权重;如果发现高频访问的记忆被淹没,就加大访问频率的权重。
这里有个反直觉的点:向量相似度的权重不宜过高。我试过把相似度权重设到 0.8,结果召回的全是语义相近但任务无关的记忆。降到 0.5 左右,配合其他维度,效果反而更好。
6.3 用 LLM as Judge 做记忆质量评估
热词里出现了llm as judge,这个思路用在记忆质量评估上很合适。具体做法是:定期抽样一批召回结果,让一个独立的 LLM 来判断“这些记忆对当前任务是否有帮助”,给出 1-5 分的评分。
评估的 prompt 可以这样设计:
你是一个记忆质量评估员。给定一个任务查询和一组召回的记忆, 请判断每条记忆对完成该任务的价值,评分 1-5: 5 = 直接相关且必要 4 = 相关且有帮助 3 = 弱相关,可能有帮助 2 = 几乎无关 1 = 完全无关或误导 任务查询:{query} 召回记忆: {memories} 请输出每条记忆的评分和简短理由。用这个评估结果反推检索策略的问题:如果大量记忆得分在 2 以下,说明召回策略太宽松;如果得分普遍在 4 以上但任务完成度不高,说明召回数量不够或者关键记忆没被存下来。
6.4 记忆系统的冷启动问题
新部署的记忆系统是空的,前期的召回质量必然很差。这个冷启动问题怎么解?
我的做法是预置领域记忆。在系统上线前,把该领域的通用知识、常见偏好、典型约束预先写入。比如做电商客服 Agent,就预置“退换货政策”“物流时效说明”“常见支付问题”这些记忆。这样即使没有用户交互历史,系统也能提供基本的记忆支撑。
另一个做法是加速学习。在冷启动阶段,把记忆写入的阈值调低,让更多信息进入记忆库;同时把召回的数量调高,用广度换精度。等记忆库积累到一定规模后,再逐步收紧策略。
7. 把 hindsight 接进现有 Agent 框架的实操路径
7.1 接入点的选择:在哪个环节插入记忆
把记忆系统接进 Agent,有三个可选接入点:
接入点一:system prompt 注入。在构造 system prompt 时,把召回的记忆拼进去。这种方式最简单,兼容性最好,但记忆是静态的,任务执行过程中不会更新。
接入点二:工具调用。把记忆操作封装成工具,让模型自己决定什么时候存、什么时候取。这种方式最灵活,但对模型的工具使用能力有要求。
接入点三:中间件拦截。在 Agent 的每轮循环中插入记忆处理逻辑,自动完成召回和写入。这种方式对模型透明,但实现复杂度最高。
我的建议是组合使用:system prompt 注入做基础召回,工具调用做按需补充,中间件做自动写入。三者配合,既能保证基础覆盖,又能兼顾灵活性和自动化。
7.2 与不同框架的适配要点
不同 Agent 框架的扩展机制不一样,适配时要抓住各自的“钩子”。
对于基于 LangChain 的 Agent,可以用BaseMemory接口做适配,把 hindsight 封装成一个自定义 Memory 类。重点实现load_memory_variables和save_context两个方法。
对于基于自研循环的 Agent,接入更直接:在每轮循环开始前调 recall,结束后调 write。关键是要处理好异步,不要让记忆操作阻塞主流程。
对于基于 MCP 客户端的 Agent,直接把记忆 MCP Server 注册进去就行。注意工具命名要有辨识度,避免和其他工具冲突。
7.3 灰度上线与效果验证
记忆系统上线不要一次性全量。我的做法是分三步:
第一步:影子模式。记忆系统正常运行,但召回结果不注入上下文,只记录日志。观察一周,看召回的记忆质量如何,有没有明显的噪声。
第二步:小流量注入。选 10% 的流量,把召回结果注入上下文,对比这 10% 和另外 90% 的任务完成率、用户满意度。如果有正向提升,再扩大比例。
第三步:全量 + 持续监控。全量上线后,建立监控指标:召回命中率、记忆写入量、检索延迟、任务完成率变化。任何一个指标异常,都要能快速回滚。
验证效果时,不要只看“任务完成率”这种粗粒度指标。要拆细:首次成功率(不需要重试就完成的比例)、平均交互轮次(轮次越少说明记忆越有效)、重复错误率(同类错误重复出现的比例)。这些指标对记忆系统的价值更敏感。
8. 几个我踩过的坑和对应的解法
8.1 记忆污染:错误记忆比没有记忆更可怕
记忆系统最危险的情况不是“记不住”,而是“记错了”。一旦错误记忆被写入并反复召回,它会持续误导 Agent,而且很难被发现。
我遇到过一次:用户在一次测试中说“把地址改成测试地址”,结果这条被当成持久偏好存了下来。之后所有订单都往测试地址发。排查了半天才定位到是记忆污染。
解法有两个:一是写入时做置信度分级,明确来自测试环境、临时指令的内容,置信度给低,并且打上 scope 标记,不进入全局召回。二是建立记忆审计机制,定期人工抽查高影响记忆,发现异常及时清理。
8.2 召回时机不对:该记的时候没记,该取的时候没取
记忆系统的效果,很大程度上取决于“时机”。我见过一个案例:Agent 在任务开始时召回了记忆,但任务执行到一半,用户补充了新的约束,这个约束没有被及时写入 working memory,导致后续步骤没有遵守。
解法是在关键节点强制触发记忆操作。比如:用户输入后触发 recall,工具调用返回后触发 write,任务状态变更后触发 update。不要完全依赖模型自觉,用框架层面的钩子来保证。
8.3 向量维度和模型不匹配
这个坑比较技术性,但很常见。换了 embedding 模型之后,向量维度变了,但向量库的集合还是按旧维度建的,导致写入失败或检索异常。
解法是把 embedding 模型的版本和向量库集合绑定。换模型时,要么新建集合,要么做一次全量重嵌入。不要试图在同一个集合里混用不同维度的向量。
8.4 记忆服务的单点故障
记忆服务如果挂了,Agent 是继续跑还是直接失败?这个决策要在架构设计时就定好。
我的做法是降级而非失败。记忆服务不可用时,Agent 退化为无记忆模式继续运行,同时记录降级日志。这样至少保证基本功能可用,不会因为记忆服务的问题导致整个 Agent 瘫痪。
9. 关于 hindsight 这类项目,我的一些个人判断
做了几个 Agent 项目之后,我越来越觉得记忆系统是 Agent 从“玩具”走向“工具”的分水岭。没有记忆的 Agent,每次交互都是重新开始,用户要反复交代同样的背景,体验很差。有了记忆,Agent 才能积累、才能进化、才能真正成为“助手”而不是“问答机”。
hindsight 这个方向选得准,因为它抓住了记忆的核心价值——不是存储,而是回溯和提炼。working memory 和 long-term memory 的分层、MCP 的标准化接口、Docker 的工程化部署,这三件事组合起来,基本覆盖了记忆系统从设计到落地的关键环节。
如果让我给正在做这块的人一个建议,那就是:先把写入和召回的质量做扎实,再考虑复杂的记忆结构。很多项目一上来就搞知识图谱、搞多级索引,结果基础召回都不准,上层结构再花哨也没用。记忆系统的价值最终体现在“召回的每一条都对当前任务有帮助”,这个标准看起来简单,做到位不容易。
另外,记忆系统的评估一定要有数据支撑。不要凭感觉说“效果不错”,要建立可量化的指标,用 LLM as Judge 做定期评估,用 A/B 测试验证效果。记忆是一个长期演进的系统,没有度量就没有优化方向。
最后分享一个我在实际项目里验证过的小技巧:给记忆加一个“最后验证时间”字段。对于偏好类记忆,定期(比如每 30 天)在合适的时机向用户确认一次“您之前提到的 XX 偏好还有效吗”。这样既能保持记忆的新鲜度,又能给用户一种“被记住”的正向体验。这个机制看起来简单,但对记忆准确率的提升非常明显。