【AI Agent面试系列】AI Agent记忆机制知识点2

上一篇文章主要讲了如何设计Agent记忆系统,本文主要介绍如何定位记忆系统效果不好的问题原因、如何评测记忆系统等。

1、记忆系统效果不好,你怎么定位是哪个环节的问题?

记忆系统是一条流水线,任何一段坏了,最终表现都是"答得不对"。但不同环节坏掉,症状不同。

做法:先按症状分类,再用对照实验和自检索测试快速排除,最后才逐段深挖。优先怀疑抽取和覆盖,最后才怀疑向量检索——因为前者出错概率高得多,而后者一旦配好就很少出问题。

对照实验
关掉改写,只做原始 query 检索。

  • 如果关掉改写后效果变好,说明改写有害,直接下线。
  • 如果效果不变,说明改写无效,白付成本。
  • 如果效果变差,改写才有价值,再去看它具体哪里改错了。

定位顺序(按成本从低到高)

1. 关改写对照实验 —— 5 分钟,排除一整类问题 2. 自检索测试 —— 10 分钟,验证索引是否可用 3. 抽取离线评测 —— 需要标注集,但能定位漏抽/错抽 4. 覆盖状态检查 —— 查库,看是否有双活跃记录 5. 注入排除法 —— 喂完美结果,看是否还错

具体说明如下:

1.1、先看症状属于哪一类

按照写入 -> 检索 -> 注入流程顺序,主要症状表现如下:

症状出问题环节可能原因
该记的没记住写入被门控过滤、抽取问题
记住了旧值,新值没生效写入覆盖问题
记住了但检索不到检索Query改写有问题
答得对但很慢/很贵检索检索(top_k 过大、无过滤)
时好时坏,不稳定检索改写(需要上下文)
检索到了但答错注入注入问题,比如位置靠中间等

需要先排除非记忆系统问题,比如回答慢、检索到了但答错等也可能是模型问题。

1.2、逐段隔离测试

1.2.1、抽取

测法:拿一批已知答案的对话,只看抽取输出,不看检索结果。

# 输入:对话文本 # 期望输出:应该抽出的记忆列表(人工标注) # 实际输出:系统抽出的记忆列表 # 指标:召回率(漏抽多少)、准确率(错抽多少)

关键:这一步必须脱离检索单独测。如果抽取漏了,后面检索再好也检不到,但你会误以为是检索问题。

常见故障:

  • 漏抽:显式声明(“记住…”)没被识别 → 规则没覆盖
  • 错抽:把临时状态(“这次先用 A”)当决策 → 缺排除规则
  • 槽位错:preference.tone 和 preference.length 混用 → 词表没约束
1.2.2、写入

测法:给定抽取结果,检查写入后的库状态。

# 输入:抽取出的记忆 # 检查:库里是否多了一条?旧记录 superseded_by 是否设了? # 指标:写入成功率、覆盖正确率

常见故障:

  • 唯一约束误伤:(key, scope) 冲突导致新记录被拒
  • 覆盖没生效:superseded_by 没设,新旧两条都活跃 → 检索返回旧值
  • 置信度阈值过高:inferred 类全被过滤
1.2.3、索引

测法:绕过检索,直接算向量相似度。

# 给定一条已知记忆,用它的"标准问法"去检索 # 看它自己能不能排进 top_1 这是自检索测试(self-retrieval):用记忆本身生成 query,看能否检回自己。 如果连自己都检不回,索引一定有问题。

常见故障:

  • embedding 文本构造错:只 embed 了 value(“Go”),语义太短
  • key 的点没换空格:project.tech_stack 被切碎
  • payload 缺 superseded_by:失效记录占满 top_k
1.2.4、改写

测法:对比"改写前检索"和"改写后检索"的结果差异。

# 指标: # - 改写前后结果相同 → 改写无效(白做) # - 改写后结果变差 → 改写有害 # - 改写后空结果率上升 → 改写过度收窄

关键:改写是唯一一个可能让效果变差的环节。其他环节坏了是"没帮上忙",改写坏了是"帮倒忙"。

常见故障:

  • 补全指代补错:把"那个方案"补成 A,实际是 B
  • 推断式改写:替用户提问,检索方向被悄悄改掉
  • 过度收窄:加了太多 filter,把正确结果过滤掉
1.2.5、注入

测法:给定正确的检索结果,看最终回答对不对。

# 输入:人工构造的"完美检索结果" # 输出:模型回答 # 如果这样还答错 → 注入或生成的问题,与检索无关 这一步是排除法:如果喂了正确答案还答错,说明问题不在检索链路,在上下文组织或生成。

常见故障:

  • 注入位置错:记忆放在长上下文中间,被忽略
  • 格式混乱:多条记忆无结构拼接,模型分不清哪条是当前值
  • 缺时间信息:模型不知道哪条更新

2、你怎么评估记忆系统好不好?

评估记忆系统要分层归因——L1 抽取看召回和误收,L2 存储看双活跃率,L3 检索看自检索和改写增益,L4 端到端必须做对照实验。不要用"回答看起来对不对"当主指标,那评估的是模型,不是记忆系统。

记忆系统的评估必须分层、可归因——每个指标要能指向具体环节。
评估的四个层次:

L1 抽取质量 —— 该记的记住了吗?不该记的记了吗? L2 存储正确性 —— 存进去的东西结构对吗?覆盖对吗? L3 检索质量 —— 需要的时候找得到吗?找得准吗? L4 端到端效果 —— 最终回答变好了吗?

必须按照L1->L4的顺序评估。如果 L1 就漏了,L4 再差也说明不了检索有问题。反过来,L4 好也不代表系统好——可能只是这次问题简单,记忆没起作用也答对了。

  • L1 抽取质量
    需要一个人工标注集:给定对话,标出"应该抽出哪些记忆"。

    指标定义关注点
    召回率抽出的 / 应抽出的漏抽是最致命的,漏了后面全白搭
    准确率正确的 / 抽出的错抽会污染库,且难以清理
    槽位准确率key 归对的 / 抽出的槽位错会导致覆盖判断失效
    误收率临时信息被收进库的比例“这次先用 A” 这类不该进

    误收率要单独看。它和准确率不是一回事:准确率看"抽出来的对不对",误收率看"压根不该抽的抽了没"。后者错了代价更大,因为临时信息进库后,靠 expires_at 补救并不可靠。

  • L2 存储正确性
    这一层不需要人工标注,查库就能算:

    指标检查方式
    双活跃率(key, scope) 下 superseded_by=null 的记录数 > 1 的比例,应为 0
    覆盖及时率新值写入后,旧记录 superseded_by 被设置的比例
    版本链完整率superseded_by != null 时 superseded_at 也非空的比例
    置信度合规率source_type=explicit 时 confidence=1.0 的比例
    孤儿向量率向量索引里有、主库没有的 id 比例

    双活跃率是最重要的一个。它一旦 > 0,检索就会返回旧值,而且症状看起来像"排序错",极难定位。这个指标应该做成监控告警,不是事后评估。

  • L3 检索质量

    • 自检索测试(最便宜,先做这个)
      用记忆本身生成 query,看能否检回自己。
    for rec in sample(memories, 200): query = generate_query_from(rec) # 用 quote 或 key+value 生成问法 results = retrieve(query, top_k=10) hit = rec["id"] in [r["id"] for r in results]
    指标含义健康值
    Recall@1自己排第一的比例> 70%
    Recall@10自己进前十的比例> 95%

    Recall@10 < 95%说明索引构造有问题(embedding 文本太短、key 没换空格、payload 过滤错),不用往下测了。

    • 端到端检索评测
      需要标注"这个问题应该检索到哪几条记忆"。
    指标含义
    Recall@k应检索到的记忆里,实际返回的比例
    Precision@k返回结果里,相关的比例
    MRR正确结果排名的倒数均值
    空结果率返回空的比例(高 = 改写过度收窄或索引缺失)
    假阳性率返回了不相关记忆的比例(高 = 改写过度扩展)

    空结果率和假阳性率要一起看。只看 Recall 会掩盖问题:把 top_k 调到 100,Recall 一定高,但假阳性也高,注入上下文后反而干扰模型。

    • 改写增益(必测)
    指标 = 检索质量(改写后) - 检索质量(改写前) 增益 ≈ 0 → 改写无效,白付成本,应下线 增益 < 0 → 改写有害,立即下线 增益 > 0 → 改写有价值,再看具体哪类 query 受益

    这个指标最容易被跳过,但它是唯一能证明改写该不该存在的指标。

  • L4 端到端效果

    • 对照实验(唯一可信的方法)
    A 组:有记忆系统 B 组:无记忆系统(或记忆置空) 同一批问题,比较回答质量

    必须做对照。只测"有记忆时答对率 85%"没有意义——可能无记忆时也有 83%,记忆只贡献了 2 个点,不值得这套复杂度。

    • 分场景看,不要只看总分
    场景记忆应有的贡献
    跨会话偏好(“还是用简洁格式”)高,无记忆必失败
    项目配置(“部署在哪台机器”)高,无记忆必失败
    单轮事实问答零,记忆不该有影响
    需要推理的问题低,记忆只提供素材

    如果"单轮事实问答"场景下记忆系统让效果变差了,说明注入的记忆在干扰模型——这是很常见的隐性损失,总分看不出来。

    • 成本也要算进去
    指标说明
    每次请求额外 token注入的记忆占多少上下文
    额外延迟检索 + 改写耗时
    存储增长速率每天新增多少条,其中多少是垃圾

    记忆系统的隐性成本是"库越来越脏"。如果误收率高,半年后检索质量会自然下降,而单次评测看不出来。所以要监控库的构成:活跃记录数、失效记录数、从未被检索到的记录比例。

一个反直觉但重要的指标:从未被检索到的记忆占比

如果库里 60% 的记录从来没被任何 query 命中过,说明:

  • 要么抽取过度(记了太多没用的)
  • 要么检索覆盖不到(索引或改写有问题)
  • 要么这些记忆本来就该在会话内解决,不该进跨会话库

这个指标直接反映记忆系统的信噪比。库越大、这个比例越高,系统越接近"存了一堆用不上的东西"。

评估的最小可行方案

  • 双活跃率监控(查库,零成本,防最严重的静默错误)
  • 自检索 Recall@10(200 条样本,10 分钟,验证索引可用)
  • 改写开关对照实验(改个配置,验证改写是否值得存在)

这三项覆盖了最容易出问题、且最难通过"看回答"发现的三类故障。端到端人工评测放最后做,因为它最贵、最不可归因,而且容易被"模型本身很强"掩盖。

3、记忆和 RAG 的边界在哪?

技术实现上记忆是 RAG 的特例,但语义上它们是两套不同的系统——RAG 管"外部知识怎么找",记忆管"用户状态怎么维护"。边界不在向量库,在谁产生数据、谁决定写入、失效意味着什么。混用基础设施可以,混用语义模型不行。

3.1、表面看它们几乎一样

环节RAG记忆
切分文档切 chunk对话切记忆条目
索引embedding + 向量库embedding + 向量库
检索query → top_kquery → top_k
注入拼进 prompt拼进 prompt

所以很多人认为"记忆就是 RAG 的一个特例"。这个看法在实现层成立,但在语义层会误导设计——因为两者的关键差异恰恰在那些"看起来一样"的环节里。

3.2、三个真正的边界

边界 1:数据来源——谁产生的

  • RAG:外部语料。文档、网页、知识库。用户不产生它,只是查询它。
  • 记忆:用户自己说的话。用户既是生产者也是消费者。

这条差异的后果很实际:RAG 的语料是只读的,记忆是可写的。可写意味着要处理覆盖、冲突、版本、失效——这些 RAG 完全不需要考虑。

边界 2:写入时机——谁决定存什么

  • RAG:离线批量入库。存什么由语料本身决定,系统不做筛选。
  • 记忆:在线增量写入。存什么由抽取策略决定,系统必须主动判断"这条值不值得记"。

这条差异导致两者的错误模式完全不同:

错误模式RAG记忆
主要风险检索不到(召回不足记错了(污染)
错误可逆性高(补文档即可)低(错误记忆会持续影响后续所有对话)
需要人工介入少多(要清理、要纠正)

记忆系统的核心难题是"写什么",RAG 的核心难题是"怎么找"。 这是两者最本质的分工差异。

边界 3:失效语义——什么时候该消失

  • RAG:文档过期了,重新入库覆盖。失效是内容问题。
  • 记忆:信息被新信息取代,旧值要标记 superseded_by。失效是状态问题。

这条差异最容易被忽略,但它是记忆系统复杂度的主要来源。
RAG 里"同一份文档的两个版本"是运维问题;记忆里"同一事实的两个值"是语义问题——必须判断哪个是当前值、哪个是历史、变更发生在什么时候。

RAG和记忆对照表:

维度RAG记忆
数据来源外部文档用户对话
写入方式离线批量在线增量
写入决策语料决定抽取策略决定
可写性只读可写
核心难题召回写什么 + 覆盖
失效语义内容过期状态被取代
主要风险检索不到记错污染
错误可逆性高低
需要版本化文档版本事实版本(superseded_by)
评估重点Recall / Precision抽取召回 + 双活跃率