Aklman · Library

04 / 第四章

记忆系统 · 上下文、RAG 与持久存储.

上下文窗口是工作记忆,RAG 是参考书架,持久存储是长期记忆 —— 三层记忆各司其职。


研究助手一旦真去搜、去抓,messages 就越滚越长 —— 几十个页面塞进去,上下文窗口会满、账单会涨,而且模型会变笨。这不是比喻,是 context rot:上下文越长,它从里面准确召回的能力越差。所以记忆不是「记更多」,是「在有限的注意力预算里,放对的东西」。 三层记忆,每层管对:上下文工程治窗口,RAG 治检索,持久存储治跨会话。三层混用,是大多数记忆 bug 的根源。

I

三层记忆,别用一层干三层的活.

上下文窗口是工作记忆,RAG 是参考书架,持久存储是长期记忆。三层各司其职,错配就出 bug。 判断一条信息该放哪层,一句话:每轮都要看的放窗口,偶尔按需查的放 RAG,跨会话要留的落盘。用窗口当长期记忆会烧钱又撑爆,用 RAG 存当前对话会慢又检索不准 —— 错层是大多数记忆问题的真正来源。下面两节,把窗口和检索分别管对。

II

上下文工程 —— 在注意力预算里做减法.

窗口不是越满越好。模型有「注意力预算」,token 越多,它越分心、召回越差 —— 底层是自注意力随序列长度的 O(n²) 代价。5注 5Vaswani et al. · 「Attention Is All You Need」(2017,arXiv:1706.03762)—— Transformer 与自注意力,你编排的底座。自注意力的计算量随序列长度成 O(n²),这正是上下文越长越贵、窗口是硬约束的架构根因。记忆工程的第一课是减法,不是加法。 这就是 context rot:needle-in-a-haystack 研究发现,上下文越长,模型从里面准确召回的能力越差 —— 所有模型都有,只是曲线陡缓不同。2注 2Anthropic · 「Effective context engineering for AI agents」(2025-09-29)—— 注意力预算(attention budget)、context rot(上下文越长召回越差);减法手段:compaction / tool result clearing、structured note-taking(外部记忆)、sub-agent 隔离。所以目标不是「装更多」,是找「最小的高信号 token 集」。具体三种减法:
压缩 Compaction
把旧历史总结成几句,保留关键决定和未解的 bug,丢掉冗余的工具输出(Claude Code 就这么做)。最轻的一种是 tool result clearing —— 工具的原始输出用完即清。
记笔记 Note-taking
把笔记记在窗口之外,按需取回。Claude 玩宝可梦能跨几小时记住「我已经练了 1234 步」,靠的就是这种外部记忆,而不是把一切塞进窗口。
子 agent 隔离
让子 agent 用干净的窗口干细活,只把 1000–2000 token 的摘要交回协调者(呼应第八章)。细节留在子窗口里,主窗口只拿结论。
还有省钱那一面:system 和 tools 每轮都不变,缓存掉,cache read 只花基础输入 token 价的约 10%。1注 1Anthropic 文档 · Prompt caching —— 用 cache_control: {type: ephemeral} 缓存稳定前缀(tools → system → messages);cache read 约为基础输入 token 的 10%,5 分钟 / 1 小时两档 TTL。示例模型 claude-opus-4-8。截至 2026-05。
resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=1024,
    system=[{"type": "text", "text": SYSTEM_PROMPT,
             "cache_control": {"type": "ephemeral"}}],   # 缓存断点
    tools=TOOLS,                                          # tools 也一并缓存
    messages=messages,
)
assert resp.usage.cache_read_input_tokens > 0            # 验证命中

缓存每轮都不变的前缀(system + tools)

减法是有判断的,丢错了下一轮就少了关键信息。规矩是:保留决定和未解问题,丢掉冗余和已消费的工具输出。缓存省的是钱和延迟,不是窗口空间 —— 窗口还是会满,所以减法照样要做。

III

RAG 要做对,长期记忆要管对.

放不进窗口的,要么 RAG 检索,要么落盘长期记住 —— 但两者都比「塞进一个向量库」难。 RAG 的真问题是检索质量,不是「能不能检索」。4注 4Lewis et al. · 「Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks」(2020,arXiv:2005.11401)—— RAG 的奠基论文:把外部知识按需检索后注入生成。朴素分块会丢上下文:一个 chunk 写「营收同比增长 3%」,但哪家公司、哪个季度都没了,检索自然不准。Anthropic 的 Contextual Retrieval 解法是给每个 chunk 加 50–100 token 的情境前缀,再做 embedding + BM25:单用 contextual embeddings 失败率降 35%,加 BM25 降 49%,再加 reranking(先取 top-150、重排后留 top-20)降 67%(5.7% → 1.9%)。3注 3Anthropic · 「Introducing Contextual Retrieval」(2024-09-19)—— 给每个 chunk 加 50–100 token 情境前缀再做 embedding + BM25;contextual embeddings 失败率降 35%,加 BM25 降 49%,再加 reranking(top-150 → top-20)降 67%(5.7% → 1.9%)。这串数字说明一件事 —— RAG 的提升在检索管线里,不在换一个更大的模型。 长期记忆是另一回事:跨会话要留的(偏好、任务进度、学到的事实)写盘,下次会话开头加载回 system。第二节的 note-taking 既是上下文减法,也是长期记忆的载体 —— 笔记落了盘,就跨了会话。别什么都塞 RAG:检索不准就是往窗口里注入噪音,先问「这几条固定事实直接写进 system 是不是更省事」。长期记忆的坑是隐私 —— 它在你盘上越记越多,是资产也是攻击面,第九章再画这条线。

IV

带权限的检索 —— 先过滤,再检索.

上面三层管的是「记得准不准」。企业里还有一条独立的线:这条记忆,当前这个用户、这个租户,有没有权限看。这不是 RAG 的一个参数,是一道必须画在检索之前的线。 差别在过滤的位置。检索后过滤(post-filter):先在全库里检索,拿到 top-K,再把用户无权看的删掉。它的问题不在最终结果 —— 结果是对的 —— 在于过程会泄露:命中数、相关性分数、分面计数(「财务类命中 12 条」),都在告诉用户「有一份你不该知道存在的文档,和你的查询高度相关」。存在性本身就是信息。检索前过滤(pre-filter):先按 tenant_id / ACL 把可见集合圈出来,只在这个集合里检索。用户无权看的东西,从一开始就不在候选里,连「有没有」都问不出来。企业级检索必须是后者。
# ✗ 检索后过滤:全库检索,结果虽对,但命中数/分数泄露了存在性
hits = index.search(query, top_k=20)
visible = [h for h in hits if can_see(user, h)]     # 太晚了
 
# ✓ 检索前过滤:先按身份圈定可见集合,只在里面检索
scope = visible_ids(user.tenant_id, user.acl)        # 身份章的 tenant_id 在这里落地
hits = index.search(query, top_k=20, restrict_to=scope)

先过滤再检索:可见集合圈定在检索之前(企业篇 · 多租户)

还有两条随之而来。一是新鲜度与来源分层:system-of-record(数据库、工单系统里的当前事实)和 memory(agent 自己攒的笔记)不是一回事 —— 前者是权威、要实时查,后者是助记、可能已过时。别让一条三个月前记进笔记的「余额」冒充当前余额。二是这条权限线是租户隔离的落点:第十四章会讲,多租户里最硬的一条就是租户 A 的检索永远碰不到租户 B 的数据 —— 而它就画在这里,检索之前,不在 prompt 里靠一句「别串租户」。身份章给 Principal 装的 tenant_id,正是为了在这一层被用上。 动手 · 亲眼看见 context rot,再治它:

记忆不是记更多, 是知道什么丢、什么留。.

Memory isn't remembering more — it's knowing what to drop and what to keep..

Aklman Library

讨论

讨论.

评论区初始化中…