摘要
上下文是 Agent 的有限资源:窗口容量有硬上限,且长上下文下信息召回精度会随长度衰减。业界常见做法里,截断不可逆,纯 Compaction(摘要压缩)有损,而每轮全量对话 RAG 对短会话开销偏大。本文给出一种可工程落地的混合策略:按 token 水位阈值 触发,将较旧对话的 原文 外置至可检索存储;窗口内仅保留 近期原文 + 结构化笔记;此后每轮经 query 改写 → 向量召回 → 重排序 → top-k 注入。细节以原文形式可逆保存,主线以笔记常驻,并附 评测协议 与 AI 可调用的 memory 工具接口。目标是:长对话不爆窗,且外置后信息是「暂时不在窗口」而非「永久丢失」。
1. 引言:为何在「更大窗口」之外仍要管上下文
Context engineering 已成为构建 Agent 的基础问题之一:优化的不是单句 prompt 措辞,而是 每次推理时,哪些 token 应当进入有限窗口。Anthropic 将其概括为在有限注意力预算下寻找 最小高信号 token 集;业界亦观察到 context rot——随窗口内 token 增加,从上下文中精确召回信息的能力下降。
因此,加大窗口只能推迟问题,不能取消问题。长对话 / 长任务仍需显式的上下文管理。
业界常见手段可放在同一谱系中比较:
| 手段 | 主要作用 | 细节保真 | 主线连续 | 可逆性 |
|---|---|---|---|---|
| Truncation | 砍最老消息 | 差 | 差 | 否 |
| Compaction | 摘要压缩后重启窗口 | 差(有损) | 中–好 | 否 |
| Structured note-taking | 窗口外持久笔记 | 中 | 好 | 部分 |
| Sub-agent 隔离 | 细任务独立上下文 | 中(视回传) | 好(主 Agent) | 视实现 |
| 全量对话 RAG | 每轮检索历史 | 中 | 中 | 是 |
本文方案落在 note-taking 与 just-in-time retrieval 的交界,并增加两点约束:
- 不是每轮都全量向量化,而是 水位触发;
- 外置的是原文而非仅摘要,保证细节可逆。
与纯 Compaction 的本质差别:Compaction 是多对一 有损投影;本文对细节做 可逆搬迁,仅对主线做有损但受控的笔记压缩。
2. 问题形式化
设:
- (C):模型上下文容量(token)
- (u_t):第 (t) 轮入模前的已用 token 预估
- (r):预留空间(输出上限 + 工具 schema 抖动 + 安全边界)
- (\theta):触发水位(默认 0.8)
- (B):每轮允许注入的历史增量预算(token)
触发条件(主):
[ u_t \ge \theta \cdot (C - r) ]
紧急档(兜底):
[ u_t \ge 0.95 \cdot (C - r) ]
触发后执行批量外置,直至 (u) 回落至安全区(例如 (0.6C \sim 0.7C)),并进入冷却期,避免逐轮挤牙膏式抖动。
目标:
- 持续对话中任务状态可延续;
- 外置前存在的事实细节在自然语言追问下可恢复;
- 单轮注入历史不超过 (B);
- 未触发阈值的短对话,路径与基线一致,不引入额外召回延迟。
反目标(明确禁止作为唯一策略):
- 仅按「删除最老 N 轮」推进——信息不可逆丢失,违反「可恢复」目标。
3. 分层状态模型
采用与 working memory / episodic store 对齐的命名,避免自造黑话:
C0 System 系统提示、工具 schema、固定规则
C1 Working 最近窗口内的对话原文(始终保留,不外置)
C2 Scratchpad 结构化笔记:目标 / 已完成 / 未完成 / 关键约束 / 最近决策
C3 Episodic 外置对话原文块 + 元数据 + 向量索引
每轮注入集合:
[ \mathrm{Prompt}_t = C_0 \oplus C_1 \oplus C_2 \oplus \mathrm{TopK}_k \oplus x_t ]
其中 (\mathrm{TopK}_k) 为可选的历史召回片段;(x_t) 为当前用户输入(及必要的工具结果)。
不变式:
- (C_3) 保存 raw text;(C_2) 不得替代 (C_3) 作为细节唯一存储。
- (C_1) 存在轮数与 token 双下界(例如最近 12 轮 或 6k tokens,取更保守者)。
- 摘要只出现在 (C_2),用于任务主线,不承担细节检索职责。
4. 方法
4.1 触发与防抖
| 水位 | 行为 |
|---|---|
| ( < 0.70(C-r) ) | 正常对话,不外置、不强制召回 |
| ( \ge 0.80(C-r) ) | 执行外置(主阈值) |
| ( \ge 0.95(C-r) ) | 紧急外置 + 收紧 (k) |
- 批量:一次迁移到安全区,而不是每次只搬一轮。
- 冷却:外置后 (N) 轮内或水位未重新越界前,不再连续触发。
- 预留 (r):必须覆盖系统提示、工具定义与模型输出,否则「外置后立刻又满」。
4.2 切块与存储契约
外置单元(chunk)建议:
| 字段 | 说明 |
|---|---|
chunk_id | 主键,供工具引用与删除 |
session_id / thread_id | 会话归属 |
turn_range | 起止轮次 |
role | user / assistant / tool |
timestamp | 时间 |
raw_text | 原文(召回时原样取回) |
summary_hint | 可选短标签,仅辅助过滤展示 |
切块策略:
- 主路径:按主题 / 连续相关轮次合并,避免一问一答过碎导致检索噪声。
- 辅路径:超长单轮可再切,设 token 上下界。
- 可选重叠:相邻块少量重叠,降低边界切断实体的概率。
写入动作与摘要更新解耦但同事务语义上成对完成:
- 块写入 (C_3) 并建索引;
- 从窗口移除对应旧轮原文;
- 生成或增量更新 (C_2);
- 记录触发与释放的 token 指标。
4.3 结构化笔记(C2)模板
固定结构,便于跨轮 diff 与评测:
[目标] 当前任务一句话
[已完成] 3–5 条
[未完成/待确认] 优先级排序
[关键约束] 数字、专名、用户偏好、硬性边界
[最近决策] 谁在何时决定了什么
容量目标:数百 token 量级。宁可短而准,不要流水账。
4.4 每轮召回链路
x_t
→ 改写 q'(补全指代 / 抽实体)
→ Embedding
→ 向量宽召回 top-R(R ≫ k,例如 20–50)
→ Rerank
→ 截断至 top-k(默认 3–8),并受预算 B 约束
→ 过滤与去重(session、chunk_id、时间序)
→ 拼入 Prompt
Query 改写(关键):
用户说「刚才那个方案呢?」时,直接嵌入该句召回差。改写可采用:最近 1–2 轮 + 当前句生成独立完整检索句,或规则抽取专名/数字;失败则降级为当前句原文。
何时跳过召回:
- (C_3) 为空(从未触发);
- 明确新开话题且无「之前 / 上次」类信号;
- 可选:仅在出现回指词时强制召回,其余依赖自动轻量路径。
4.5 失败降级
| 故障 | 行为 |
|---|---|
| 向量服务不可用 | 仅 (C_0 \oplus C_1 \oplus C_2),记 fallback |
| 召回为空 | 同上;输出侧约束禁止编造旧细节 |
| Rerank 失败 | 退回向量分数 top-k |
| 笔记生成失败 | 保留上一版 (C_2),标记下轮重建 |
降级路径不得差于「硬截断」——这是最低线。
4.6 为何比纯 Compaction 更不易「丢信息」
将「丢失」定义为 永久不可恢复:
- Compaction:压缩函数 (f: \mathrm{history} \to \mathrm{summary}) 多对一且无逆——细节永久丢失。
- 本文:细节路径为 (\mathrm{history} \to C_3(\mathrm{raw}) \to \mathrm{retrieve}),在检索成功假设下可逆;有损仅发生在 (C_2) 主线摘要。
因此,在 原文入库成立 且 召回评测达标 的前提下,信息风险从「必然有损」变为「召回失败时的条件有损」。
这也意味着:没有召回评测,就不能声称「不易丢」(见 §6)。
5. 工具接口:AI 可调用的记忆能力
上下文管理不应全是框架黑盒。建议 自动保底 + 工具增强:
| 模式 | 触发方 | 职责 |
|---|---|---|
| 自动档 | 水位阈值;可选每轮轻量 top-k | 防爆窗、保底可召回 |
| 工具档 | 模型按需调用 | 主动回忆、主动写入、查询状态 |
5.1 单工具 + 英文 type(能力面收敛)
与「一领域一工具、type 切换、help 必调」的工程约定一致,type 仅英文枚举:
memory {"type": "...", ...}
| type | 作用 | 典型参数 | 默认权限 |
|---|---|---|---|
help | 用法与参数说明 | — | 开 |
recall | 按 query 检索历史 | query, top_k? | 开 |
remember | 写入一条长期要点 | text, tags? | 开(限长限频) |
status | 水位与笔记摘要 | — | 开 |
offload | 手动触发外置 | until_tokens? | 默认关 |
forget | 删除 chunk / 范围 | chunk_id 等 | 默认关 / HITL |
调用示例(示意):
memory {"type":"help"}
memory {"type":"recall","query":"阈值触发与外置的结论","top_k":5}
memory {"type":"remember","text":"type 仅允许英文枚举","tags":["constraint"]}
memory {"type":"status"}
5.2 recall 返回契约
每条结果必须 可直接进上下文且限长,并携带溯源:
[ts | turn a-b | chunk_id]
(raw 片段,截断至单条上限)
- 无结果:显式
no match,禁止模型编造; - 单条与总条数同时限幅,受 (B) 约束;
- 返回
chunk_id,便于后续引用或受控forget。
5.3 与自动路径的关系
- 自动轻量召回可保持开启,避免模型「忘记调工具」时完全失忆;
- 定向、高成本、高精度召回可主要走
memory.recall; remember与自动切块 去重:同一chunk_id/ 近重复文本不双写;remember适合偏好与硬约束,不适合整段闲聊灌库。
5.4 工具描述要点(ACI)
- 声明
type英文枚举;不确定先help; - 写清 Use / Do not use;
- 说明返回格式与
no match语义; - 高危 type 默认关闭,符合最小权限。
6. 评测协议
没有评测,不能声称外置后「信息没丢」。建议最小协议如下。
6.1 任务集
-
Needle 回填(细节可恢复)
外置前埋入 N 条事实(数字、专名、约束);触发外置后,用自然语言(含指代)提问,测是否命中。 -
任务连续性(主线)
多步待办对话;中途强制触发外置;检查后续是否仍按 (C_2) 推进目标与未完成项。 -
窗口压力
合成长度至 2× / 3× 原生窗口;比较完成率、中途失败类型、编造率。 -
基线对比
同一任务集上比较:- Truncation
- Compaction-only
- 每轮全量对话 RAG
- 本方法
6.2 指标
| 指标 | 含义 |
|---|---|
| Recall@k(事实级) | 外置后细节可找回比例 |
| Task completion Δ | 相对未外置基线的任务成功率差 |
| Injected tokens / turn | 注入体积 |
| Latency p50/p95 | embed / recall / rerank 分解 |
| Fallback rate | 降级频率 |
| Hallucination-on-miss | 召回为空时是否编造旧细节 |
6.3 建议合格线(可按产品调整)
- Recall@5 达到预设阈值(例如实验集 ≥ 0.85,以你的数据为准);
- 外置前后 Task completion 下降不超过 (\varepsilon);
- 未触发路径与截断/全量基线在行为与延迟上无显著回归;
- Fallback 时输出不劣于 truncation。
6.4 必埋日志
trigger_ratio, tokens_freed, chunks_offloaded
query_raw, query_rewritten
recall_ids, rerank_scores, final_k
latency_embed / recall / rerank
fallback_flag
用于区分:「没存进去」「没搜到」「搜到了没排进 top-k」「进窗口了模型仍答错」——四类失败责任完全不同。
7. 失败模式与局限
| 失败模式 | 影响 | 缓解 |
|---|---|---|
| 改写失败 | 召回错/空 | 规则兜底 + 回指词强制召回 |
| 块边界切断实体 | 关键事实检不到 | 主题切块 + 重叠 + 实体检查 |
| Embedding / rerank 漂移 | 排序不稳 | 定期离线评测集回归 |
| 笔记过度膨胀 | 吃掉窗口 | (C_2) 硬上限与结构模板 |
| 原文入库隐私 | 合规风险 | 加密、保留策略、分租户 |
| 双路径重复注入 | 冗余与成本 | chunk_id 去重 |
适用边界:
- 适合:单线程长对话、长任务 Agent、个人知识型会话。
- 不适合:短会话(无触发收益)、强实时超低延迟且不愿付 rerank 成本的路径(可只用自动截断或更轻策略)。
第一版明确不做: 跨租户知识融合、以删除向量库代替审计日志、多路图检索融合。
8. 结论
长对话的上下文管理,不必在「全量保留」与「有损摘要」之间二选一。本文策略可概括为三句话:
- 主线用笔记(C2)常驻,保证任务不漂;
- 细节用原文外置(C3)保存,保证可逆;
- 按水位触发搬迁,按改写召回 + 重排序按需注入,保证窗口体积可控。
再加一层 memory 工具面(help / recall / remember / status),让模型在自动保底之外能主动读写记忆,且高危操作默认关闭。
工程上是否合格,不取决于架构图是否好看,而取决于 §6 的 Recall@k、任务完成率与降级行为是否达标。先定义丢失,再定义不丢;先写评测,再谈不易丢失。
参考
- Anthropic. Effective context engineering for AI agents.
- Anthropic. Building effective agents.
- Guo et al. Empowering Working Memory for Large Language Model Agents. arXiv:2312.17259.
- 业界 RAG 与重排序(rerank)通用实践;具体实现栈可按项目替换(向量库 / rerank 模型)。