检索增强记忆:以嵌入+检索作为回忆机制。
长期记忆是惰性的,除非有东西取出正确的切片并把它拼接进工作上下文。那次取出是一个检索问题——与 RAG 同样的机制,但带有记忆特有的转折:查询是智能体自身不断演变的状态,时近性和显著性与相似度同等重要,而一次糟糕的回忆会主动毒化推理,而不只是没用。
回忆即检索。复用栈,更换输入。
如果你读过实战指南的检索章节,机制是熟悉的:切块、嵌入、索引、混合检索、重排序。记忆回忆复用了全部这些。改变的是你索引什么以及如何查询:
- 语料是智能体自己的历史,而非一个静态文档集。它每轮增长,必须保持有界(见
context-compaction)。 - 查询不是用户的问题。它是从当前目标、活跃子任务和草稿区派生出的构造线索——智能体的"心境",而非它最后一句话。
- 排序不是纯相似度。一条记忆的价值是相似度加上时近性加上显著性——改编自 Generative Agents 的检索评分。
从智能体状态构造回忆查询,而非从最后一条消息。
把用户最后一条消息嵌入然后当作记忆查询,是最常见的错误。相关的记忆往往与任务有关,而非与措辞有关。构造一个显式线索:
# memory/recall_query.py def build_cue(state) -> str: # A compact natural-language description of what the # agent is trying to do RIGHT NOW — this is what we embed. return ( f"Goal: {state.goal}\n" f"Current step: {state.active_subtask}\n" f"Open questions: {'; '.join(state.open_loops)}" )
对于用户字面措辞也重要的任务(一个特定错误字符串、一个标识符),运行混合检索——在原始话语上做词法检索,在构造线索上做稠密检索——然后融合,与检索章节中完全一样。线索处理"我在做什么";词法分支捕获"用户敲入的确切令牌"。
按相关性、时近性和显著性评分。
纯余弦相似度检索出最相似的记忆,而那不是最有用的那条。一条相似度略低、但写于五轮前且被标记为重要的记忆,通常应当胜过一条过期的、勉强相关的近重复项。复合评分:
# memory/score.py import math def recency(age_seconds: float, half_life: float) -> float: return 0.5 ** (age_seconds / half_life) def score(mem, sim: float, now: float, w=(0.6, 0.25, 0.15)) -> float: rec = recency(now - mem.last_used, half_life=7 * 86400) sal = mem.salience # 0..1, set at write a, b, c = w return a * sim + b * rec + c * sal def recall(store, cue, embed, now, k=5) -> list: cand = store.search(embed(cue), k=30) # wide net ranked = sorted( cand, key=lambda h: score(h.mem, h.sim, now), reverse=True) for h in ranked[:k]: h.mem.last_used = now # used → refreshes recency return ranked[:k]
last_used 刷新创造了一个有用的动态:不断被检索的记忆保持"温热"、易于回忆;从不被检索的记忆冷却,成为驱逐候选。这白白得到一个访问频率信号,也是 memory-stores 中衰减逻辑的读取侧对应物。
权重取决于工作负载。一个执行长任务的编码智能体希望时近性高(最近 20 轮主导)。一个回忆用户偏好的个人助理希望显著性高(一个月前陈述的偏好仍然重要)。用 evaluating-memory 中的评估框架调优 w,不要猜一次就冻结。
先阈值再截断,并对存活下来的内容做预算。
排序列表与提示之间有两道不可妥协的关卡:
- 绝对相关性下限。只在丢掉所有低于最小复合分的项之后才取前 k 个。没有下限的"前 5 个"会在一个离题的轮次里,注入五条无关记忆并配上自信的表述。当没有任何东西相关时,空回忆才是正确且安全的。
- 工作集预算。被回忆的记忆争夺
context-budgeting中的long_term预算。如果存活者超额,把它们摘要成单一压缩块,而不是任意丢弃某些。
recall("deploy the tier migration")
candidates: 30
after composite scoring + floor(0.35): 3 survive
[sem] prod deploys gated on staging check 0.71
[epi] last migration rollback was turn 41 0.52
[proc] migration apply checklist 0.48
18 below floor, dropped (correctly)
3 memories, 420 tok — under long_term budget, no compaction
检索漂移:当智能体的状态在任务中途演变时,一个未更新的线索会持续回忆同样的早期记忆,而真正相关的记忆已经向前推进。每轮都从当前状态重建线索,而非在任务开始时构造一次。过期线索是一种静默的、累积的失效——它看起来像记忆系统"在工作",因为它确实返回了结果。
渲染检索到的记忆,让模型以正确方式信任它。
回忆到的记忆如何被格式化进提示,决定了模型把它当作真理、提示,还是干扰项。三条规则:
- 标注来源与类型。"[回忆到的语义记忆,写于第 12 轮]"让模型恰当地为它加权,也让你能调试是哪条记忆驱动了某个决策。
- 标记不确定性。一条反思出的结论是推断,不是事实。把它渲染为"先前推断",这样模型不会把猜测当成圣旨。
- 别把它放进权威的系统块。检索到的记忆是证据,不是策略。把一条回忆到的(且可能错误的)记忆放在模型期待不可变指令的位置,正是记忆毒化变成记忆服从的方式。
检索增强记忆是整个栈的承重机制:类型决定存什么,存储决定存哪里,压缩保持其有界——但回忆才是真正在正确时刻把正确的过去摆在模型面前的那一步。如果回忆错了,其他每一层都是白费功夫。