1. Agent 记忆问题,比你想的更像人类的遗忘曲线
到现在还有不少人问我,Agent 不就是“大模型 + 提示词 + 工具调用”串起来吗?这句话对了一半。串起来只是让 Agent 有了“动手能力”,但真正决定一个 Agent 是“演示玩具”还是“能持续用的生产力工具”,恰恰是记忆。没有记忆的 Agent,每次对话都是“初次见面”,你上午跟它交代的项目背景、代码风格偏好、踩过的坑,下午再聊它就全忘了,所有上下文都得重新喂一遍。这就像你每天上班都换一个新实习生,天天从零教起,哪个团队也受不了。
我认真玩了几个月 Agent 开发之后最深的感受是:记忆不是“锦上添花”的功能,它是 Agent 从“能用”走到“好用”的分水岭。你看现在市面上那些口碑不错的 Agent 产品,无论是 Claude 的 Projects、ChatGPT 的 Custom Instructions,还是各种本地优先的编程助手,核心壁垒几乎都落在“它记不记得你”这件事上。热搜里那串词挺有意思——“workbuddy 历史对话记录”“本地记忆迁移”“opencode 如何通过记忆召回代码修改情况”“双重记忆模型”“短期记忆和长期记忆”,其实大家关心的都是同一件事:Agent 到底该记住什么、怎么记住、怎么在需要的时候想起来。
这篇我用自己做的一个带记忆的 Agent 项目为例,把记忆模块从设计到落地拆开讲。当你真正动手时,你面对的不只是“把历史消息塞进上下文”这种粗活,而是一整套关于记忆的采集、结构化、存储、召回、遗忘和迁移的工程问题。
2. 先搞清楚一件事:记忆和上下文不是一回事
很多初学的朋友把“记忆”简单理解成“把聊天记录全部拼到提示词里”,然后发现钱烧得飞快、效果还越来越差。这里有一个很关键的概念区分:上下文窗口是“工作台”,记忆是“仓库”。工作台再大也有上限,你不可能把仓库里所有东西都搬到工作台上;而且工作台上的东西越杂,模型专注力越差,注意力被无关信息稀释,回答质量不升反降。
我在设计记忆模块时参考了认知科学里对人类记忆的经典划分,把它映射到 Agent 系统上:
- 工作记忆(短期记忆):当前会话内的上下文,包括用户最近几轮输入、Agent 自己的推理过程、临时工具返回结果。它活在上下文窗口里,会话结束就基本没了。
- 情景记忆(长期记忆):跨会话保留的、关于“你是谁、你做过什么、你喜欢什么”的事实性信息。比如用户的代码风格偏好、项目架构决策、之前讨论过的需求背景。
- 程序性记忆(技能记忆):Agent 学会的做事方法,比如“遇到这个类型的报错优先查哪个方向”“这个项目的测试命令是什么”。本质上是一套经验型规则,可以被沉淀、复用。
大部分教程只聊前两种,但实际用下来,第三种才是让 Agent“越用越顺手”的关键。我自己做的一个偏编程辅助的 Agent,早期只给它配了短期记忆,它每轮都像一个刚入职的程序员——水平不差,但完全不熟悉你这个项目的来龙去脉。后来我把“项目内沉淀的经验规则”也纳入记忆体系,效果提升是肉眼可见的。
记忆设计的第一原则就是:先分类,再存储。不同类型的记忆,存储介质不同,召回策略也不同。一股脑往向量数据库里塞,是新手最容易踩的坑。
3. 双网络记忆模型:短期与长期的协同机制
热搜里那个“双网络记忆模型”的说法,我猜是从认知科学里的“双过程理论”借来的——一个负责快速响应当前情境,一个负责慢速调取长期沉淀。落到 Agent 工程上,我把它实现成两套并行通道:
通道一:会话级短期记忆维护一个滑动窗口,保存最近 N 轮对话。注意,这里保存的不仅是用户输入,还包括 Agent 的内部推理摘要和关键工具调用结果。为什么要存推理摘要?因为 Agent 多步推理时,中间步骤的“思考过程”往往是后文决策的重要依据,你不保存,模型只能靠上下文窗口里残留的内容去猜,一旦窗口滚动挤掉了,前面的推理链就断了。
实现上不用太复杂,一个消息队列或者环形缓冲就够。关键是滑动窗口的淘汰策略,除了简单的 FIFO(先进先出),我还会结合消息的重要性打分。比如用户明确说“记住:以后都用 pnpm 不用 npm”这种指令性内容,即便它发生在 20 轮之前,也不该被挤出窗口。我会用一个轻量规则:
- 包含“记住”“以后都”“千万不要”“偏好”等关键词的消息,标记为高优先级,进长期记忆;
- 包含代码块、报错信息、命令执行结果的消息,标记为中优先级,尽量保留在窗口中;
- 纯寒暄、无信息量的内容,直接丢弃。
这个策略执行起来很简单,但对体验的提升非常明显。
通道二:跨会话长期记忆长期记忆这块,我采用“向量库 + 结构化存储 + 文件快照”三件套的组合方式。
- 向量库负责存储可语义检索的“记忆片段”,比如一段讨论后的结论、一个决策原因的说明、一段代码实现思路;
- 结构化存储(SQLite 或 JSON 文件)负责存属性型记忆,比如用户偏好、项目配置、常用命令;
- 文件快照负责存完整文档和历史对话原文,用于深度回溯。
为什么要三件套而不是一个向量库搞定?因为向量检索适合“模糊想起”,不适合“精确查询”。你问 Agent “我上次让你记住的部署命令是什么”,如果那条命令躺在向量库里,检索出来的可能是语义相近但字面完全不同的另一段话。这种精确信息,用数据库按 key 查,一次命中,又快又准。所以我的原则是:事实用结构化,语义用向量,原文用文件。
双网络模型的“协同”体现在:每次会话启动时,Agent 会先向长期记忆发一个“召回请求”,把当前用户身份、当前项目名、最近的会话主题作为条件,召回一批相关记忆片段,注入到系统提示词的记忆区。然后在会话过程中,短期记忆动态更新;会话结束时,再对短期记忆做一轮“提炼”,把值得长期保留的内容写入长期记忆。这就是一个完整的“写入—召回—再写入”的闭环。
3.1 记忆写入:不是所有对话都值得记住
怎么判断一段对话值不值得写入长期记忆?我的经验是:主动记录的意愿是最重要的信号。用户主动说“记住这一点”,那毫不犹豫地写。用户没有明说但内容是决策型、结论型、偏好型的,也要提炼着写。最容易忽略的是“修正型”内容,比如用户否定了 Agent 之前的建议,说“不对,这里应该用另一种方案”。这种内容信息量极高,它同时包含了“旧方案不合适”和“新方案是对的”两层信息,如果不记录,下次 Agent 大概率会犯同样的错。
写入内容要注意两点。一是抽象化加工。别把原文整段塞进记忆库,而是提炼成“用户在什么场景下,做了什么决定,原因是为什么”的结构化记录。原始聊天记录太长、噪音多,直接存进去不仅浪费存储,召回时还会因为碎片化信息干扰 Agent 判断。二是写入前做冲突检测。比如用户今天说“数据库用 MySQL”,明天又说“换成 PostgreSQL”,两条记忆冲突了。这时候要做的不是并存,而是让新记忆覆盖旧记忆,或者至少标记旧记忆为“已废弃”。不处理冲突的记忆库,时间一长就会变成精神分裂现场。
3.2 记忆召回:什么时候该想起什么
召回策略直接决定记忆系统的体验。我试过“每次会话把所有记忆全部塞进上下文”的粗暴方案,结果上下文爆炸,模型注意力被稀释,回答质量严重下降。后来换了思路:按场景精细化召回。
召回时机分两种。一种是会话开始时的“预热召回”,把用户基础偏好、项目背景这类高频使用的记忆优先注入。另一种是会话过程中的“动态召回”,当 Agent 发现当前讨论的话题和某条历史记忆高度相关时,临时拉起对应的片段。动态召回一般靠判断当前用户消息的 embedding 向量和历史记忆向量的余弦相似度,阈值设在 0.75 左右比较稳,低于 0.7 召回来的信息基本用不上,高于 0.85 又容易漏召回。
召回结果在提示词里的放置位置也很有讲究。长期记忆属于“静态背景”,放在系统提示词里,让模型把它当作既定事实;短期记忆属于“动态上下文”,放在对话历史的位置,让模型感知到这是刚刚发生的对话流。位置放反了,模型对信息的新旧判断会混乱,输出的语气和指代都会出问题。
3.3 记忆遗忘与迁移:做减法比做加法难
热搜里“hindsight 记忆库”和“workbuddy 本地记忆迁移”这两个词,恰好点出了记忆系统里两个最容易被忽略的问题:遗忘和迁移。
先说遗忘。人类记忆会自然衰减,Agent 记忆库如果只增不减,时间长了就会出现“记忆淹没”——过时的、低价值的记忆混在重要记忆里,干扰召回精度。我给记忆加了衰减机制:每条记忆有一个“访问计数”和“最后访问时间”,在每次召回命中时更新。定期扫描时,如果一条记忆超过 90 天未被访问且优先级不高,就降级;超过 180 天未访问,直接归档到冷存储,不再参与日常召回。对于被用户明确否定或废弃的记忆,直接标记删除。这套机制配合人工可干预的记忆管理界面,基本能保证记忆库长期处于健康状态。
再说过迁移。很多人用的 Agent 不只有一个,工作电脑一个、家里电脑一个、手机上一个。每个设备上跑一套独立的记忆系统,数据就分裂了。我做的是“本地优先 + 文件导出/导入”的迁移方案:所有记忆以 Markdown + JSON 的结构化文件存在本地目录里,迁移时只需要把这个目录打包拷贝到新设备,导入时做一次冲突合并。这比云端同步更符合隐私直觉,也比纯数据库导出更可读。你甚至可以打开记忆文件直接阅读、手动修改,这本身就是一种人机协同的“记忆编辑”。
注意:记忆迁移时一定要先对比两边的记忆文件版本,合并策略建议“双向合并,冲突时以修改时间较新者为准”,千万别简单覆盖,否则你会把另一台设备上新增的重要记忆白白冲掉。
4. 从零实现一个带短期与长期记忆的 Agent
理论聊了一堆,该上实操了。我用一个偏通用型的 Python 方案带大家过一遍完整实现,不绑定任何特定的大模型供应商,模型调用层用抽象接口,方便你换成 OpenAI、Claude 或者国产模型。整个记忆模块我拆成五个核心文件:
agent_memory/ ├── memory_types.py # 记忆数据结构定义 ├── short_term.py # 短期记忆环形缓冲 ├── long_term.py # 长期记忆存储与检索 ├── memory_manager.py # 记忆管理器,串联读写召回 └── agent.py # 带记忆的 Agent 主循环4.1 记忆数据结构设计
先在memory_types.py里定义记忆的基础数据结构。我强烈建议把记忆建模成带类型的对象,而不是简单的字符串数组。带上类型、时间戳、来源、重要度这几个字段,后续做筛选和衰减就有据可依。
from dataclasses import dataclass, field from datetime import datetime from typing import Optional, List import uuid @dataclass class Memory: id: str = field(default_factory=lambda: uuid.uuid4().hex) content: str = "" # 记忆内容 memory_type: str = "fact" # fact / preference / skill / decision / correction importance: int = 5 # 1-10,重要度,10 最高 project: str = "" # 关联的项目名,用于场景隔离 user_id: str = "default" # 关联的用户 created_at: str = field(default_factory=lambda: datetime.now().isoformat()) last_accessed_at: str = field(default_factory=lambda: datetime.now().isoformat()) access_count: int = 0 # 访问计数,用于衰减 meta: dict = field(default_factory=dict) # 扩展字段,存原文JSON等 def to_dict(self): return { "id": self.id, "content": self.content, "memory_type": self.memory_type, "importance": self.importance, "project": self.project, "user_id": self.user_id, "created_at": self.created_at, "last_accessed_at": self.last_accessed_at, "access_count": self.access_count, "meta": self.meta, }字段设计里,memory_type用来区分记忆种类,importance影响后续衰减和召回排序,project字段特别重要——如果你的 Agent 同时服务多个项目,不按项目隔离记忆,召回时会串味。比如 A 项目的技术栈偏好被带到 B 项目,就可能给出完全错误的建议。
4.2 短期记忆实现:滑动窗口 + 重要性保护
短期记忆我用一个带容量上限的列表实现。核心设计是双策略淘汰:当窗口满了,先尝试淘汰低优先级消息,如果所有消息优先级都高,才敢挤掉最老的。这里我用deque来保证头部弹出和尾部追加都是 O(1)。
from collections import deque from typing import List, Dict, Any class ShortTermMemory: def __init__(self, max_messages: int = 20): self.max_messages = max_messages self.messages: deque = deque(maxlen=max_messages) def add(self, role: str, content: str, priority: int = 1) -> None: msg = { "role": role, "content": content, "priority": priority, "timestamp": datetime.now().isoformat(), } self.messages.append(msg) def get_recent(self) -> List[Dict[str, Any]]: return list(self.messages) def get_high_priority(self) -> List[Dict[str, Any]]: return [m for m in self.messages if m["priority"] >= 2]20 轮的窗口对大多数场景够用了,但如果你的 Agent 经常要处理代码文件、长文本,窗口可以调到 30。注意,短期记忆的“长度”参数直接关系到消耗的 token 数和模型的注意力范围,不是越大越好。我自己调试下来,20 轮左右是“记得住上下文”和“不淹没注意力”之间的甜点值。
priority字段的判断逻辑放在 Agent 主循环里,当用户消息里出现“记住”“以后”“偏好”这类词时,优先级直接打 3;如果是工具调用结果、报错信息,打 2;普通对话打 1。这个规则简单,但已经能覆盖大部分需要“特殊保护”的消息。
4.3 长期记忆实现:结构化 + 向量双层通道
长期记忆我同时用 SQLite 存结构化字段和 Legend 存向量副本。其实向量库的选择很多,Chroma 是本地优先里最省事的,FAISS 性能更强但要自己管理索引文件。我这边用 Chroma,因为它有现成的持久化接口,适合个人项目和中小型应用。
import chromadb from chromadb.utils import embedding_functions import sqlite3 import json from typing import List, Optional class LongTermMemory: def __init__(self, persist_dir: str = "./mem_store"): self.persist_dir = persist_dir # 向量库:存语义检索的记忆片段 self.client = chromadb.PersistentClient(path=f"{persist_dir}/vector") self.collection = self.client.get_or_create_collection( name="agent_memories", embedding_function=embedding_functions.DefaultEmbeddingFunction(), ) # 结构化库:存事实型记忆,支持精确查询 self.conn = sqlite3.connect(f"{persist_dir}/facts.db") self._init_db() def _init_db(self): cur = self.conn.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS facts ( key TEXT PRIMARY KEY, value TEXT, memory_type TEXT, project TEXT, updated_at TEXT ) """) self.conn.commit() def upsert_fact(self, key: str, value: str, memory_type: str = "fact", project: str = ""): cur = self.conn.cursor() cur.execute(""" INSERT INTO facts (key, value, memory_type, project, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at """, (key, value, memory_type, project, datetime.now().isoformat())) self.conn.commit() def get_fact(self, key: str) -> Optional[str]: cur = self.conn.cursor() cur.execute("SELECT value FROM facts WHERE key = ?", (key,)) row = cur.fetchone() return row[0] if row else None def add_semantic_memory(self, memory: Memory): self.collection.upsert( ids=[memory.id], documents=[memory.content], metadatas=[{ "memory_type": memory.memory_type, "importance": memory.importance, "project": memory.project, "created_at": memory.created_at, }], ) def search_semantic(self, query: str, top_k: int = 5, project: str = "") -> List[Memory]: where_filter = {"project": project} if project else None result = self.collection.query( query_texts=[query], n_results=top_k, where=where_filter, ) memories = [] if result["documents"] and result["documents"][0]: for i, doc in enumerate(result["documents"][0]): memories.append(Memory( id=result["ids"][0][i], content=doc, memory_type=result["metadatas"][0][i].get("memory_type", "fact"), importance=result["metadatas"][0][i].get("importance", 5), project=result["metadatas"][0][i].get("project", ""), )) return memories这套设计的精髓在于“事实与语义分离”。用户问“我的数据库连接串是什么”,这是精确记忆,走get_fact("database_connection_string"),一次命中,不用向量检索。用户问“我之前关于数据库选型聊过什么”,这是模糊记忆,走语义检索,把相关讨论片段拉出来再让模型总结。两套通道相互配合,既快又准。
注意:Chroma 默认的嵌入模型是 ONNX MiniLM,效果够用但对中文长文本的语义理解一般。如果你的场景中文占比高,建议换成
text-embedding-3-small或bge-small-zh这类对中文支持更好的嵌入模型,召回效果会有明显提升。
4.4 记忆管理器:串起读写与召回流程
记忆管理器是整个系统的“大脑”,它对外暴露三个方法:recall(召回)、remember(写入)、forget(遗忘)。Agent 主循环只跟它打交道,不直接操作底层存储。
from typing import List, Optional class MemoryManager: def __init__(self, user_id: str = "default", project: str = "default"): self.short_term = ShortTermMemory() self.long_term = LongTermMemory() self.user_id = user_id self.project = project def recall(self, query: str, top_k: int = 5) -> List[Memory]: """召回相关记忆:先取精确事实,再取语义记忆,加上高优先级短期记忆""" memories = [] # 精确事实查询:用规则抽取出可能的key # 简化版:把 query 里明显的 key 型名词直接查事实库 fact_value = self.long_term.get_fact(query.strip()) if fact_value: memories.append(Memory(content=fact_value, memory_type="fact", importance=8, project=self.project)) # 语义检索 semantic_memories = self.long_term.search_semantic(query, top_k=top_k, project=self.project) memories.extend(semantic_memories) # 高优先级短期记忆 for msg in self.short_term.get_high_priority(): memories.append(Memory(content=f"[近期对话] {msg['role']}: {msg['content']}", memory_type="recent", importance=7, project=self.project)) return memories def remember(self, memory: Memory): """写入记忆:事实型走SQLite,语义型走向量库,外部统一入口""" if memory.memory_type == "fact": self.long_term.upsert_fact(key=memory.content[:50], value=memory.content, project=memory.project) else: self.long_term.add_semantic_memory(memory) def forget(self, memory_id: str): """遗忘一条记忆:从向量库中删除""" self.long_term.collection.delete(ids=[memory_id])召回的优先级排序我做了本地调整:精确事实命中最高、高优短期记忆其次、向量语义召回是兜底。为什么这个顺序?因为精确事实的置信度最高,模型使用时最放心;高优短期记忆代表用户最近的指令,时效性最强;向量召回虽然灵活,但可能出现语义漂移,置信度相对最低。按置信度从高到低组织召回结果,Agent 被误导的概率会小很多。
4.5 带记忆的 Agent 主循环
最后把记忆模块接到 Agent 的主循环里。核心流程分四步:
- 会话开始时,基于用户和项目信息做一次全局召回,作为“背景记忆”注入系统提示词;
- 用户每发一条消息,先做一次动态召回,把相关记忆追加到当前消息前;
- 维护短期记忆滑动窗口,记录对话流转;
- 会话结束时,做一轮“记忆提炼”,把值得长期保存的内容写入长期记忆。
我用一段极简的伪代码展示这个流程:
def run_agent(user_input: str, session_state: dict): # 1. 动态召回 related_memories = memory_manager.recall(user_input) # 2. 构建带记忆的提示词 memory_block = "\n".join([f"- {m.content}" for m in related_memories]) system_prompt = f""" 你是一个有记忆能力的AI助手。以下是与你相关的历史记忆: {memory_block} 请基于以上记忆,结合当前对话,给出回答。 """ # 3. 对话轮次处理 response = llm_call(system_prompt, session_state["recent_messages"] + [{"role": "user", "content": user_input}]) # 4. 更新短期记忆 memory_manager.short_term.add("user", user_input, priority=user_input_priority(user_input)) memory_manager.short_term.add("assistant", response, priority=1) # 5. 判断是否写入长期记忆 if should_remember(user_input, response): new_mem = extract_memory(user_input, response) memory_manager.remember(new_mem) return response实际项目里,should_remember和extract_memory这两个函数需要花不少心思。我最初的实现是让大模型来判断“这段对话是否值得记忆”,每次额外调一次模型,成本高不说,效果还不稳定。后来改成“规则 + 模型”混合:先用关键词规则快速过滤掉明显不值得记的内容,再用模型对候选片段做摘要写入。这样既控制了成本,又保证写入的记忆已经过抽象加工。
5. 项目落地过程中踩过的坑,逐个记下来给你排雷
记忆系统的原理不复杂,真正的难点在工程细节。下面这几个坑是我反复调试后才找到解决方案的,每一个都值得你提前避开。
坑一:记忆冲突导致 Agent “精神分裂”
最早做长期记忆时,我只负责写入,没处理冲突。结果跑了两周后,Agent 一会儿记得用户偏好 Python 写脚本,一会儿又觉得用户是 Java 党,回答自相矛盾。后来加了事实表的ON CONFLICT DO UPDATE逻辑,并且对语义记忆增加了“新旧记忆时间对比”,一律以最新时间为准。记忆系统必须有一个明确的“新信息覆盖旧信息”的共识规则,否则时间一长必然出乱子。
坑二:向量召回的主题漂移
向量检索有个特点:语义接近但主题不同的内容,也可能被召回。比如用户聊“缓存策略”,系统把之前聊“浏览器缓存”和“Redis 缓存”的内容都拉出来了,里面还混着一条“如何清理电脑缓存文件”的记忆。解决方案是做两级过滤:第一级用向量相似度粗筛,第二级用关键词/项目字段精确过滤,只有同时满足“语义相似”和“项目归属一致”的记忆才进入最终提示词。
坑三:记忆文件越来越大,启动越来越慢
一开始我用单个 JSON 文件存所有记忆和聊天记录,跑一个月后文件到了几十 MB,每次启动加载都卡好几秒。痛定思痛后改成 SQLite 存结构化数据、向量库存索引、原文件单独归档。启动只加载索引,用到哪条才加载哪条。如果你的记忆量也上来了,建议尽早做存储分离,别等卡得没法用了才动手。
坑四:用太多规则判断优先级,把系统搞复杂
我早期的优先级判断规则写了十几条,覆盖各种场景,结果规则之间互相冲突,行为不可预测。后来精简成三条:
- 包含记忆关键词(记住、以后都、偏好、千万不要)→ 优先级 3;
- 包含代码/报错/命令 → 优先级 2;
- 其余 → 优先级 1。
规则越简单越可靠,系统行为越可预测。给 Agent 加“智能”不意味着堆规则,恰好相反,把规则做减法的空间留给模型自主判断,效果反而更好。
坑五:召回的内容太多,提示词爆炸
有一版我把所有召回记忆不加筛选地拼进提示词,最长的一次测试里,系统提示词加记忆块超过了 8000 token,模型完全“看不过来”,回答质量暴跌。现在我对最终进入提示词的记忆做了总量限制:默认最多 8 条相关记忆,每条控制在 100 字以内。如果摘要后仍超过字数限制,按置信度从低到高丢弃。记住一句话:记忆系统的目标不是“让模型知道更多”,而是“让模型关注更准”。
坑六:本地记忆迁移时格式不兼容
我用pickle存过一段时间的记忆对象,后来想迁移到另一台机器,发现 Python 版本不一致导致无法反序列化。后来全部改用 JSON 和 SQLite,跨平台、跨版本都稳定。做本地记忆迁移,强烈建议用文本格式或标准数据库格式,别用语言特定的序列化方案。
6. 记忆提炼与人工干预,让人机协作更顺滑
记忆系统自动运行一段时间后,一定会积累噪音和过时信息。这时候就需要一个“记忆编辑器”的角色。我做了两个层面的干预机制:
自动层面:定期跑一个看板脚本,统计每条记忆的召回次数。召回次数高说明这条记忆活跃、有用;召回次数低但重要度高,说明可能是冷门但关键的信息;召回次数低且重要度低的,进入待清理名单。脚本每月生成一份“记忆健康报告”,提醒哪些该删、哪些该补充细节。报告维度包括:
- 记忆总量与类型分布;
- 近 30 天活跃记忆 Top 20;
- 近 90 天零访问记忆列表;
- 冲突记忆检测结果(内容相似但结论相反的记忆对)。
人工层面:所有记忆存储在本地,我用 Markdown + YAML front matter 的格式生成一个“记忆手册”。用户可以直接打开修改手册,改动会同步回记忆系统。为什么用 Markdown 而不是纯数据库?因为可读性和可编辑性,用户看得懂、改得动,才会真正信任这套记忆系统。
记忆手册里每一条长这样:
--- id: 7f3a2c91 type: decision project: shopping-mall-app importance: 8 created: 2025-03-12 --- 数据库选型最终定为 PostgreSQL,原因是需要复杂的JSON查询和全文检索能力。 之前对比过 MySQL,但因为JSON支持较弱且社区版缺少部分全文索引功能,放弃。这种格式的好处是:人可以直读、可以直接编辑、可以版本管理。我用 git 管理整个记忆目录,每次修改都有历史记录,误删大不了回滚。
7. 不同应用场景下的记忆策略差异
记忆系统的设计不是一套走天下,不同场景下侧重点差异很大。我把自己做过的几个类型整理出来:
类型一:个人知识库助手侧重点是长期记忆的准确性和可追溯性。每条记忆都要有来源链接、创建时间、可信度评分。召回时要优先展示“证据链”,让用户知道 Agent 是根据哪条记忆得出这个结论的。这类场景记忆数量大,一定要做好分类和索引,最好建立“主题—文档—片段”的三级结构。
类型二:编程辅助 Agent侧重点是项目级记忆和技能记忆。比如用户的代码风格(缩进用几个空格、是否喜欢类型注解)、项目的构建工具、部署流程、历史问题库。这类场景里,project字段的隔离和按项目召回比其他类型更重要。编程场景还有一个特殊点:大量记忆是代码相关的,代码的语义召回比自然语言难很多,我建议在记忆写入时人工加标签(比如“微服务”“性能优化”“数据库”),召回时标签命中比纯向量检索可靠得多。
类型三:客服/销售型 Agent侧重点是用户画像记忆和交互历史。用户上次聊到哪一步、对什么话题感兴趣、有哪些偏好,这些直接影响本次交互的体验。这类场景召回时效性要求极高,新记忆要近乎实时地参与召回,最好给记忆加一个“活跃期”字段,最近 7 天的记忆权重上调,超过 30 天的记忆权重下降。
类型四:个人助理型 Agent(类似 WorkBuddy 那种)侧重点是“历史对话记录”的完整保存和“本地记忆迁移”的顺畅性。这个场景的用户非常在意隐私,记忆必须本地存储,并且要支持一键导出、一键导入。对话记录最好按日期/主题分文件存储,而不是塞进一个大数据库,因为用户有一个很朴素的需求:我能打开昨天的对话原文看看。
我建议任何做 Agent 的朋友,在动手写代码之前,先明确自己的场景属于哪一型。场景定错了,后面的存储选型、召回策略都会跟着错。
8. 上线运行后的效果与数据
我给自己做的编程辅助 Agent 接上完整记忆系统后,跑了一个月左右,记录了一些对比数据供你参考:
- 跨会话任务完成率:无记忆版只有 24%,有记忆版提升到 61%。这个不难理解,无记忆时用户每次都要重新解释项目背景、技术栈、代码结构,真正干活的时间少得可怜。
- 用户重复说明次数:无记忆时用户每隔几轮就要重复一次关键信息(“我用的 Java 17”“项目结构是 Maven 多模块”),有记忆后基本不用重复。
- 推荐方案被采纳率:无记忆时用户经常否决 Agent 的方案,因为 Agent 不了解用户过往的偏好和历史决策;有记忆后,方案被采纳率从 32% 提升到 58%。这个提升主要来自“决策记忆”的贡献——Agent 知道了用户过去为什么选 A 不选 B,新方案自然会顺着用户偏好走。
记忆系统的构建不是一次性工作,它需要持续观察、持续调整。我实测最影响效果的三个调优点:一是写入时是否做抽象化加工,二是召回时是否做项目隔离,三是记忆过期策略是否合理。这三个点调好了,系统性能能上一个大台阶。
个人经验:宁可让 Agent 少记住一点,也别让它记住一堆没用的细枝末节。记忆的核心价值不是多,而是“在合适的时机想起合适的事”。很多时候,做减法比做加法更能提升体验。
9. 关于记忆工程的后续扩展方向
记忆系统做完基础版本后,有几个方向值得继续深入。一个比较有前景的是“记忆的分层结构化”——不是简单地把记忆分成短期和长期,而是构建一个类似人类“情节—语义—程序”三层结构的记忆体系,每层使用不同的存储和召回策略。另一个方向是“记忆的可解释性”——每次召回都记录召回原因和置信度,让用户可以追问“你为什么认为这条记忆跟我现在的问题相关”。还有一个方向是“多 Agent 间的共享记忆”——不同专长的 Agent 共享一套记忆库,但各自维护独立的上下文,这时候记忆的权限控制和冲突消解就成了新课题。
对刚开始接触 Agent 记忆的朋友,我的建议很直接:别上来就追复杂的框架,先手工设计一个最简单的“短期窗口 + 长期向量库”的组合,跑起来,积累真实的使用数据,再根据数据去迭代。记忆系统的价值要靠时间沉淀,靠持续使用,靠真实场景检验。只有真正用过一段时间,你才能体会“Agent 记得你”和“Agent 不记得你”之间的体验鸿沟有多大。
我现在自己这个带记忆的 Agent 已经用了半年多,它了解我的代码习惯、记得我踩过的坑、知道我做技术选型时最看重维护成本。这种体验一旦习惯,就回不去了。记忆不是 Agent 的一个插件,它应该成为 Agent 与世界交互的基础设施。