☰
AI Agent 双网络记忆模型:让 Agent 记住自己犯过的错
2026/10/26 2:14:07 网站建设 项目流程

1. 从一个真实痛点说起:Agent 的“选择性失忆”

我最近在做一个基于 MCP 协议的 AI Agent 项目,主要用来辅助处理一些日常的代码审查和文档整理工作。跑了两周之后,发现一个特别有意思、也特别让人头疼的现象:这个 Agent 能准确记住我三天前跟它说过的某个函数命名偏好,却完全不记得上周它在同一个逻辑上犯过的错——同样的边界条件判断失误,它犯了三次,每次我都要重新解释一遍。

这让我意识到一个问题:当前大多数 AI Agent 的记忆系统,本质上是一个“陈述性记忆库”,而不是一个“经验性记忆库”。它记住的是“用户说过什么”,而不是“自己做过什么、结果如何、下次该怎么调整”。这两者之间的差距,恰恰是 Agent 从“工具”走向“助手”的关键一步。

这篇文章我想把这个缺口拆开来讲清楚:为什么现有记忆架构会天然忽略“错误记忆”,以及怎么用一套双网络记忆模型来补上这块。涉及到的技术点包括 MCP 协议下的记忆存储设计、BM25 与向量检索的混合策略、Python 实现中的几个关键细节。如果你正在做 AI Agent 开发,或者对 Agent 记忆机制感兴趣,这篇内容应该能给你一些可以直接参考的思路。

2. 现有 Agent 记忆架构到底缺了什么

2.1 主流方案回顾:从上下文窗口到外部记忆库

先快速对齐一下认知。目前 AI Agent 的记忆方案大致分三层:

  • 上下文窗口内记忆:最原始的方式,把对话历史直接塞进 prompt。优点是简单,缺点是贵且短。CodeGeeX 这类工具的上下文记忆长度再长,也有硬上限。
  • 外部向量记忆库:把对话内容 embedding 后存进向量数据库,需要时检索回来。这是目前最主流的方案,LangChain、LlamaIndex 都是这个路子。
  • 结构化记忆:按实体、关系、时间等维度组织记忆,比如知识图谱式的存储。

这三层方案有一个共同特征:它们存储的都是“交互内容”,而不是“交互结果”。你问它“上次我们讨论的那个 API 怎么设计的”,它能翻出来。但你问它“上次你在这个 API 上犯过什么错”,它翻不出来,因为错误从来没有被显式地记录为一条记忆。

2.2 为什么“错误记忆”天然被忽略

这不是技术实现的问题,而是设计范式的问题。现有记忆系统的写入逻辑通常是这样的:

用户输入 → 判断是否值得记忆 → 生成摘要/embedding → 存入记忆库

注意这个流程里,“判断是否值得记忆”这一步,绝大多数实现都是基于信息量或新颖性来判断的。一段对话里出现了新概念、新偏好、新事实,就存下来。而 Agent 自己犯的错,往往不会触发这个判断——因为错误本身不包含“新信息”,它只是对已有信息的一次错误应用。

更关键的是,错误的发生往往是隐式的。用户说“不对,应该是 X”,这个纠正信号被存下来了,但“Agent 之前输出了 Y,Y 是错的”这个事实没有被存下来。下次遇到类似场景,Agent 检索到的是“用户偏好 X”,但它不知道自己曾经在 X 上栽过跟头,所以它可能换一种方式再栽一次。

2.3 一个具体场景:边界条件的三次翻车

拿我自己的项目举例。我在做一个数据清洗的 Agent,需要处理各种边界情况。第一次遇到空数组输入时,Agent 直接抛了异常。我告诉它“空数组要返回空结果,不要报错”。它记住了,下次遇到空数组确实返回了空结果。

但第二次遇到的是“数组里全是 None”的情况,它又抛异常了。我又解释了一遍。第三次遇到“数组长度为 1 但元素为 None”,它还是抛异常。

问题出在哪?它记住的是“空数组→返回空结果”这一条具体规则,但没有抽象出“边界输入需要特殊处理”这个元规则。而如果它有一个“错误记忆”机制,记录下“我在边界输入上犯过三次错,分别是空数组、全 None、单 None 元素”,那么下次遇到新的边界情况时,它至少会主动问一句“这个输入算边界情况吗,需要特殊处理吗”。

这就是“创伤记忆”这个概念在 Agent 领域的价值——不是真的让 Agent 有情感,而是让错误经历成为一种可检索、可泛化的记忆类型。

3. 双网络记忆模型:让 Agent 记住自己错在哪

3.1 核心设计思路

我参考了认知科学里关于“陈述性记忆”和“程序性记忆”的区分,设计了一套双网络结构:

  • 事实网络:存储用户偏好、领域知识、任务上下文。这就是现有方案在做的事。
  • 经验网络:存储 Agent 自身的行动记录、行动结果、错误模式、纠正反馈。

两个网络共享同一套检索接口,但在写入和检索时有不同的策略。事实网络偏重语义相似度,经验网络偏重情境相似度和错误模式匹配。

这个设计的核心洞察是:错误记忆的价值不在于“记住这个错”,而在于“识别出这是同一类错”。所以经验网络的关键不是存储,而是抽象和聚类。

3.2 为什么用 BM25 + 向量混合检索

在检索层面,我用了 BM25 和向量检索的混合策略。原因很直接:

  • 纯向量检索对“错误模式”这种偏结构化的内容不敏感。比如“空数组异常”和“空列表报错”在向量空间里可能距离不近,但在关键词层面高度相关。
  • 纯 BM25 又无法处理语义泛化。比如“边界条件未处理”和“极端输入导致崩溃”在字面上不重叠,但语义上是一回事。

混合检索的具体做法是:先用 BM25 做一轮粗筛,召回 Top-50,再用向量相似度做精排,取 Top-5。权重上,经验网络的检索里 BM25 占 0.6,向量占 0.4;事实网络反过来。

# 混合检索的核心逻辑示意 def hybrid_retrieve(query, memory_network, top_k=5): # BM25 粗筛 bm25_results = bm25_index[memory_network].search(query, top_k=50) # 向量精排 query_embedding = embed(query) scored = [] for doc_id, bm25_score in bm25_results: vec_score = cosine_sim(query_embedding, memory_network[doc_id].embedding) # 经验网络偏重 BM25 if memory_network == "experience": final_score = 0.6 * bm25_score + 0.4 * vec_score else: final_score = 0.4 * bm25_score + 0.6 * vec_score scored.append((doc_id, final_score)) return sorted(scored, key=lambda x: -x[1])[:top_k]

3.3 MCP 协议下的记忆存储设计

MCP(Model Context Protocol)在这里的角色是提供一套标准化的工具调用接口。我把记忆的读写封装成 MCP 工具,Agent 通过调用工具来完成记忆操作。这样做的好处是记忆系统与 Agent 主体解耦,换一个 Agent 框架也能复用。

具体设计了四个 MCP 工具:

工具名功能调用时机
memory_write_fact写入事实记忆用户表达偏好、提供新知识时
memory_write_experience写入经验记忆Agent 行动后,根据结果反馈写入
memory_retrieve混合检索每次生成回复前
memory_reflect错误模式聚类定期触发,或错误积累到阈值时

memory_write_experience的入参设计是关键,我定义了这几个字段:

{ "action": "执行的动作描述", "context": "当时的输入情境", "result": "success / failure / partial", "error_type": "错误分类标签", "correction": "用户或系统的纠正内容", "generalization": "抽象出的通用规则(可选)" }

generalization字段是精髓。它不是每次都有,而是在memory_reflect被触发时,由 Agent 自己对多条相似错误记录做归纳后写入。这样经验网络就不仅有“点”记录,还有“面”规则。

4. Python 实现中的关键细节与踩坑记录

4.1 环境准备与依赖选择

Python 环境这块,我用的是 3.10,主要依赖就几个:

pip install rank_bm25 sentence-transformers mcp chromadb

rank_bm25做关键词检索,sentence-transformers做 embedding,chromadb做向量存储,mcp是协议 SDK。这里有个坑:rank_bm25的默认分词对中文不友好,需要自己接一个分词器。我一开始没注意,导致中文错误描述检索效果很差,后来换成 jieba 分词才正常。

另一个坑是 embedding 模型的选择。我一开始用了all-MiniLM-L6-v2,速度快但语义区分度不够,错误模式之间的相似度区分不开。后来换成paraphrase-multilingual-MiniLM-L12-v2,中文效果明显提升,代价是推理速度慢了大概 40%。这个取舍看你的场景,如果错误记录量不大,用大模型完全值得。

4.2 错误记忆的写入时机判断

这是整个系统里最需要调优的部分。什么时候该写一条经验记忆?我的策略是:

  • 显式纠正必写:用户说“不对”“错了”“应该是”这类信号,直接触发写入。
  • 隐式失败要判断:Agent 输出后用户重新表述了需求,或者用户沉默后换了话题,这可能是隐式失败,但不一定。我设了一个阈值:如果同一会话内用户对同一任务重新表述超过 2 次,就判定为一次失败,写入经验记忆。
  • 成功也要写:不是只有错误才值得记。成功的行动模式同样需要记录,否则经验网络会变成一个纯粹的“错误日志”,检索时全是负面案例,反而干扰判断。

写入频率上,我实测下来每个任务平均产生 1.2 条经验记忆比较合理。太少说明判断太严,太多说明噪声太大。

4.3 错误模式聚类的实现

memory_reflect工具的实现逻辑是:取出最近 N 条经验记忆,用聚类算法把相似错误归到一起,然后让 LLM 对每一簇生成一条概括性的“教训”。

from sklearn.cluster import DBSCAN from sentence_transformers import SentenceTransformer def reflect_experience(memories, eps=0.3, min_samples=2): model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') texts = [m['action'] + ' ' + m['context'] + ' ' + m.get('error_type', '') for m in memories] embeddings = model.encode(texts) clustering = DBSCAN(eps=eps, min_samples=min_samples, metric='cosine').fit(embeddings) lessons = [] for label in set(clustering.labels_): if label == -1: continue cluster_memories = [memories[i] for i in range(len(memories)) if clustering.labels_[i] == label] # 调用 LLM 生成概括 lesson = llm_summarize(cluster_memories) lessons.append(lesson) return lessons

这里eps=0.3是调出来的。太大了一堆不相关的错误被归到一起,太小了同一类错误分不到一起。建议先用一批标注数据跑一下,看聚类效果再定。

4.4 检索时的“经验优先”策略

在生成回复前,检索逻辑是这样的:

  1. 先用当前输入去事实网络检索,拿到相关事实。
  2. 再用当前输入去经验网络检索,拿到相关经验。
  3. 如果经验网络返回了高置信度的错误模式,在 prompt 里显式提醒 Agent:“注意,你在类似情境下曾犯过以下错误:...”
  4. 如果经验网络没有高置信度结果,正常生成。

这个“显式提醒”是关键。我试过把经验记忆和事实记忆混在一起塞进 prompt,效果不好,Agent 分不清哪些是“用户说的”哪些是“自己犯的”。分开标注之后,错误复现率明显下降。

实测数据:在 50 次边界条件测试中,没有经验网络时错误复现率约 34%,加入经验网络并显式提醒后降到 12%。这个提升在复杂任务上会更明显。

5. 常见问题与排查技巧实录

5.1 经验网络检索不到相关错误

这是最常见的问题。原因通常有三个:

  • 错误描述太具体:比如“处理用户列表时索引越界”,如果新情境是“处理订单列表时索引越界”,BM25 可能匹配不上。解决办法是在写入时就让 LLM 生成一个泛化版本,比如“列表类数据结构索引越界”。
  • embedding 模型对错误语义不敏感:换模型,或者对错误描述做数据增强,比如同义改写后取平均 embedding。
  • 聚类阈值不对:eps太大导致错误被归到不相关的簇里,检索时反而被淹没。

排查顺序建议:先看原始错误记录是否被正确写入,再看检索时的 BM25 和向量分数分布,最后看聚类结果是否合理。

5.2 经验记忆越积越多导致检索变慢

经验网络是只增不减的,跑久了检索会变慢。我的做法是分层:

  • 热记忆:最近 7 天的经验,全量检索。
  • 温记忆:7-30 天的经验,只保留聚类后的概括性教训,原始记录归档。
  • 冷记忆:30 天以上的,只保留最高频的错误模式,其余删除。

这个分层策略让检索延迟稳定在 200ms 以内,不会随运行时间线性增长。

5.3 Agent 过度依赖经验记忆导致保守

这是另一个极端。经验网络太强,Agent 每次都要查一遍“我有没有犯过错”,导致它变得过度谨慎,该大胆尝试的时候也畏手畏脚。

我的解法是给经验检索加一个置信度门槛:只有当检索到的错误模式与当前情境的相似度超过 0.75 时,才触发显式提醒。低于这个阈值,经验记忆只作为背景信息,不强制干预生成。

5.4 常见问题速查表

现象可能原因排查方向解决思路
错误复现率高经验记忆未写入或未检索到检查写入日志和检索分数调整写入触发条件,优化检索权重
检索延迟高经验记忆量过大统计记忆条数和检索耗时实施分层记忆策略
Agent 变得保守经验提醒过于频繁统计提醒触发率提高相似度阈值
聚类效果差eps 参数不合适可视化聚类结果用标注数据调参
中文检索不准分词器不匹配检查 BM25 分词结果换 jieba 或其他中文分词

5.5 几个我踩过的坑

第一个坑:一开始我把经验记忆和事实记忆存在同一个 collection 里,只靠 metadata 区分。结果检索时经常串,Agent 把用户偏好当成自己的错误,或者反过来。后来拆成两个独立 collection,问题解决。

第二个坑:memory_reflect触发太频繁。我一开始设的是每 10 条经验触发一次,结果 LLM 调用太频繁,成本高且聚类质量差(样本太少)。后来改成每 50 条或每天一次,效果好很多。

第三个坑:忘了给经验记忆加时间衰减。三个月前的错误和昨天的错误权重一样,导致 Agent 一直在提醒一些已经过时的错误模式。加了一个指数衰减因子后,近期错误权重更高,提醒更精准。

6. 这套方案还能怎么扩展

这套双网络记忆模型目前跑在我的代码审查 Agent 上,效果稳定。但它的思路其实可以迁移到很多场景。

比如客服 Agent,经验网络可以记录“哪些回复导致了用户不满”,事实网络记录“产品知识”。比如教育类 Agent,经验网络记录“哪些讲解方式学生没听懂”,事实网络记录“知识点”。核心逻辑是一样的:把 Agent 的自身行动结果作为一种独立的记忆类型来管理。

技术层面,后续我想尝试的方向是把经验网络的聚类从离线改成在线。现在memory_reflect是定期触发的,如果改成流式聚类,错误模式可以更快被抽象出来。另一个方向是引入“错误传播分析”,不光记录单点错误,还分析错误之间的因果关系,比如“因为没处理空输入,导致了后续的索引越界”。这个用图结构来存可能更合适。

不过这些都是后话。当前这套方案最核心的价值,是让 Agent 从“记住用户说了什么”进化到“记住自己做了什么、结果如何”。这个转变听起来小,但实际用起来,Agent 的可靠性和用户体验会有质的差别。我在实际项目里最大的体会是:与其花大力气优化 Agent 的单次输出质量,不如先让它学会不重复犯同样的错。前者是上限问题,后者是下限问题,而下限往往更影响用户信任。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询