第十章:Agent 记忆与上下文压缩¶
本章讨论应用层上下文管理;JSON、时间、数量与预算均为教学示例,不是生产配置或实测结果。
10.1 为什么需要记忆压缩¶
上下文太长时,能不能直接总结一下再继续?可以,但要先区分两类信息:精确状态不能靠自由摘要维持,历史叙述则可以在保留证据入口的前提下压缩。例如,“付款结果未知,不要重试”应留在状态与约束中,不能变成一句“付款遇到问题”。
Agent 在长任务中会持续产生:
- 用户与模型消息;
- Tool Call 与 Tool Result;
- 计划和状态;
- 搜索文档;
- 代码、日志和表格;
- 中间结论;
- 错误与重试记录。
如果将所有内容不断追加到 Context,可能遇到:
- 超出模型 Context Window;
- 输入 Token 成本增加;
- Prefill(生成前处理输入)的开销增加,实际延迟还取决于缓存和服务实现;
- 关键信息被噪音稀释;
- 模型更难找到当前目标;
- 旧错误和无关信息持续影响后续决策。
记忆压缩不是单纯把文本变短,更重要的是:
在有限 Token Budget 内,尽可能保留完成当前任务所需的信息。
长窗口能容纳更多内容,但不能保证每项证据都被正确使用;反过来,短上下文也不天然更准确。删除必要的原文、多跳证据或反例会降低质量,应以具体模型和任务的对照实验选择压缩强度,而不是假设“越短越好”。
写成一个简单目标函数,就是:
并满足:
其中:
C是压缩后的 Context;U(C)是保留信息对当前任务的效用;L(C)是 Context 长度;B是可用 Token Budget;λ表示长度成本权重。
这是设计目标的示意,不是可以直接计算出最优摘要的算法。U(C) 通常只能由下游任务表现近似评估,授权范围、必需证据及消息协议应先作为硬约束;高效用不能抵消违规。
10.2 四类基础方法¶
四种常见方法解决的是不同问题:
| 方法 | 解决的问题 | 核心动作 | 是否有损 |
|---|---|---|---|
| Sliding Window | 历史太长,保留哪一段 | 删除最早内容 | 是 |
| Summarization | 历史太长,如何提炼 | 用摘要替换原文 | 是 |
| Importance Filtering | 信息价值不同,保留什么 | 按任务价值选择 | 通常是 |
| Structured Extraction | 对话文本是否是最佳表示 | 转换为结构化状态 | 一般有损;仅对完整可逆表示例外 |
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart LR
H["Long<br/>Interaction<br/>History"] --> W["Sliding<br/>Window<br/>按时间截断"]
H --> S["Summarization<br/>语义压缩"]
H --> I["Importance<br/>Filtering<br/>按价值选择"]
H --> E["Structured<br/>Extraction<br/>改变表示"]
W --> C["Compact<br/>Context"]
S --> C
I --> C
E --> C
这四种方法通常组合使用,而不是互相替代。
外部化是把内容搬出活跃 Context,不一定减少总存储;从窗口移除也不等于从记忆库删除。应用层摘要、服务端 compaction、KV Cache 量化/淘汰分别改变文本表示、服务托管上下文或推理状态,不应统称为同一种“记忆压缩”。
10.3 Sliding Window:按时间截断¶
Sliding Window 只在活跃 Context 保留最近若干轮或若干 Token,移除更早内容;是否删除持久化原记录由另一套保留策略决定。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
M1["Message 1"] --> M2["Message 2"]
M2 --> M3["Message 3"]
M3 --> M4["Message 4"]
M4 --> M5["Message 5"]
M5 --> M6["Message 6"]
M1 -.丢弃.-> X["Evicted"]
M2 -.丢弃.-> X
M3 --> K["Current Window"]
M4 --> K
M5 --> K
M6 --> K
10.3.1 常见实现¶
按消息轮数¶
只保留最近 N 轮对话。
优点:
- 实现简单;
- 速度快;
- 行为容易预测。
缺点:
- 不同消息长度差异很大;
- 不能精确控制 Token。
按 Token 数¶
从最新消息向前装入,直到达到预算。
优点:
- 能控制模型输入大小;
- 更适合不同长度的消息。
缺点:
- 仍然只按时间,不考虑价值;
- 可能删除早期关键约束。
按任务阶段¶
保留当前阶段的详细记录,将已完成阶段移出活跃窗口。
如果阶段之间的交接边界清楚,这比按轮数截断更容易保留完整过程。不过,“阶段完成”不代表其中证据再也无用;下一阶段依赖的结论、失败路径和来源仍要保留或能按需取回。
10.3.2 不能被普通窗口淘汰的信息¶
以下内容通常需要 Pin:
- System Instructions;
- 用户当前目标;
- 安全和权限规则;
- 成功标准;
- 当前计划和状态;
- 未解决问题;
- 高风险操作约束。
Pin 的是当前仍有效的受信内容,不是永久固定历史文本。用户改了目标或权限已撤销,应更新或撤销相应项;权限判断始终由运行时执行,不能只依靠上下文中的一句规则。若必需项本身超过预算,应拆分任务或停止请求,而不是静默截断。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart LR
PIN["Pinned Context"] --> CTX["Current Context"]
RECENT["Recent Window"] --> CTX
RET["Retrieved Memory"] --> CTX
10.3.3 不要切断 Tool 交互¶
Tool Call 和对应 Tool Result 应视为一个逻辑单元。只保留调用、不保留结果,或只保留结果、不保留调用,都可能破坏上下文。
还可能直接违反 API 的消息格式要求。并行调用要核对各个 call ID 与结果;未完成调用不能伪造为成功结果。某些 API 还要求保留特定 continuation/reasoning item,应遵循其协议。需要裁剪时,可整体移除已完成交互并保存摘要/引用,而不是留下悬空的 tool result。
同样需要避免切断:
- 用户问题与 Agent 回答;
- 错误与对应修复;
- 计划步骤与执行结果;
- 引用与其支持的结论。
10.3.4 Sliding Window 的适用场景¶
- 最近内容明显比早期内容重要;
- 对话任务短;
- 可以从外部 State 恢复关键信息;
- 需要低成本、低延迟压缩。
它是截断策略,不是理解策略。
10.4 Summarization:用摘要替换历史¶
Summarization 在删除早期历史前,先提取重要信息形成更短表示。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
H["Old Messages"] --> S["Summarizer"]
S --> SUM["Compact Summary"]
SUM --> CTX["Current Context"]
R["Recent Messages"] --> CTX
10.4.1 Rolling Summary¶
维护一个持续更新的摘要:
优点:
- 实现简单;
- 可通过输出预算约束摘要长度;
- 适合连续对话。
风险:
- 多次重写会产生 Summary Drift;
- 早期细节可能逐渐丢失;
- 模型生成的错误可能进入后续摘要。
例如原文是“测试超时,支付结果未知,不要重试”,摘要变成“支付失败,可以重试”,既丢了不确定性,也反转了操作约束。后续再摘要不会自动恢复这些信息,必须回到原始事件或权威业务状态核对。
10.4.2 Hierarchical Summary¶
先生成局部摘要,再合并成阶段或任务摘要:
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart TB
M1["Messages<br/>1-10"] --> S1["Stage<br/>Summary A"]
M2["Messages<br/>11-20"] --> S2["Stage<br/>Summary B"]
M3["Messages<br/>21-30"] --> S3["Stage<br/>Summary C"]
S1 --> T["Task Summary"]
S2 --> T
S3 --> T
它比不断重写同一个摘要更容易:
- 保留来源;
- 定位丢失信息;
- 按阶段展开;
- 支持长周期任务。
10.4.3 Query-focused Summary¶
摘要只保留与当前任务或阶段相关的信息。
例如,Agent 从“资料收集”进入“报告撰写”阶段时,可以重点保留:
- 已验证事实;
- 来源;
- 比较结论;
- 未解决冲突。
不再保留每一次搜索尝试的详细过程。
但应保留已排除路径及排除理由的简记,避免下一阶段重复失败搜索。任务切换后重新评估摘要,不能把针对旧问题生成的摘要当作通用知识索引。
10.4.4 Event Summary¶
按关键事件总结:
- 决策;
- Tool 成功或失败;
- 计划变化;
- 用户确认;
- 发现的新约束。
10.4.5 高质量摘要应保留什么¶
- 原始目标;
- 用户明确约束;
- 已完成和未完成步骤;
- 关键事实;
- 重要 Tool 结果;
- 决策及依据;
- 错误与恢复状态;
- 来源和 Artifact 引用。
10.4.6 Summary Drift¶
多轮摘要可能发生:
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
RAW["Raw History"] --> S1["Summary 1"]
S1 --> S2["Summary 2"]
S2 --> S3["Summary 3"]
S3 --> D["Meaning Drift"]
缓解方式:
- 保存原始历史或 Artifact;
- 摘要附带来源引用;
- 不重复总结稳定结构化字段;
- 周期性从原始数据重新生成摘要;
- 对目标、权限和数字使用结构化状态;
- 使用 Verifier 检查遗漏和矛盾。
Verifier 也可能漏判,尤其当它与摘要器只读取同一份有缺陷的摘要时。需要把摘要和原始证据对照,精确核验 ID、数字、否定、时间范围及状态;对丢失项重新取回原文。引用还应带来源事件、版本或内容哈希,只有 URL 而网页已变化时,不能恢复当时证据。
摘要是派生数据,权限通常不能宽于其所含来源的共同可读范围;若需要扩大读者范围,应经过独立脱敏与发布审核。来源被撤回、删除或替代后,要失效或重建依赖它的摘要,而不是只删向量记录。
10.5 Importance Filtering:按价值选择¶
时间顺序不代表信息价值。用户第一轮给出的安全约束,可能比最近十轮普通消息更重要。
Importance Filtering 根据当前任务选择应保留的内容。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
H["History Items"] --> SCORE["Importance Scoring"]
SCORE --> HIGH["High Value<br/>保留"]
SCORE --> LOW["Low Value<br/>删除或外部化"]
10.5.1 重要性信号¶
- 与当前目标的相关性;
- 是否为用户明确约束;
- 是否影响安全和权限;
- 是否是未解决问题;
- 是否被后续任务依赖;
- 来源可信度;
- 是否包含独特信息;
- 时间新鲜度;
- 是否能从外部系统重新获取。
可以用一个基础效用分数表示:
其中:
Rᵢ:Goal Relevance;Iᵢ:Importance;Dᵢ:Dependency Value;Tᵢ:Trust;Nᵢ:Novelty;Cᵢ:Token Cost。
这些分量需要在任务样本上校准,公式并不意味着有现成准确的“重要性分数”。同一信息在不同问题下价值不同:摘要中的结论可用于概览,财务核对却可能必须读取整行账目和单位。
10.5.2 Hard Rules 与 Model Scoring¶
不应让模型独自决定所有信息的重要性。
Hard Rules¶
必须保留:
- System Instructions;
- 安全策略;
- 用户明确目标;
- 权限;
- 当前任务状态;
- 尚未解决的错误。
Model Scoring¶
可以用于:
- 判断历史事实与当前阶段的相关性;
- 选择代表性 Episode;
- 从重复 Tool 结果中提取重点。
10.5.3 Importance Filtering 的风险¶
- 模型错误删除真正重要的信息;
- 当前看似无关的信息之后可能变得重要;
- 重要性评分受当前 Prompt 偏置;
- 恶意内容可能伪装成高优先级指令。
因此,仍有合法用途的原文可按保留政策外部化,支持后续取回;敏感或未授权内容不能借“以后可能有用”永久保存。
10.6 Structured Extraction:改变信息表示¶
自然语言对话通常冗长、重复且难以精确更新。Structured Extraction 将历史转换为高密度状态。
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart LR
CHAT["Conversation"] --> EX["Extractor +<br/>Evidence<br/>Validation"]
EX --> FACTS["Facts"]
EX --> STATE["Task State"]
EX --> TODO["Todo"]
EX --> DEC["Decisions"]
EX --> ENT["Entities"]
EX --> ART["Artifact<br/>References"]
10.6.1 示例¶
原始对话:
结构化后:
{
"subject": "user-42",
"repository": "example/knowledge-base",
"publishing_preference": {
"target_branch": "main",
"create_pull_request": false
},
"source": "explicit_user_instruction",
"source_event_id": "event-example",
"verification_status": "user_stated",
"scope": "current_repository"
}
结构化表示:
- 对冗长重复对话可能减少 Token,短句转 JSON 反而可能更长;
- 更容易精确更新;
- 更适合规则执行;
- 更容易检测冲突;
- 验证后可以按字段查询,不必每次重新解释整段对话。
抽取本身仍可能误读。“用户偏好直接提交”不能变成绕过分支保护的权限。这里只有数据表示变化,没有发生发布,也没有证明用户拥有发布权限。
10.6.2 适合抽取的内容¶
Task State¶
{
"goal": "完成 Agent 知识图谱",
"current_chapter": 10,
"status": "writing",
"pending": [
"validate formatting",
"publish to main"
]
}
Decisions¶
Open Questions¶
Entity Facts¶
10.6.3 Structured Extraction 的风险¶
- Schema 设计遗漏信息;
- 模型抽取错误;
- 难以表达模糊和不确定内容;
- 结构化字段可能失去原始语境;
- Schema 版本变化需要迁移。
因此,应保留来源引用,并允许在需要时返回原文。
Schema 合法只证明字段形状正确,不证明内容真实。运行状态应由执行器及工具确认结果更新;模型可提出补丁,但不能仅从“我准备执行”抽取出 completed。含糊的声明应保留 unknown、适用范围和原话,不能强行填成确定事实。
10.7 四种方法如何组合¶
工程上通常把这几种方法串起来用:
分离历史前,先保护约束并提取状态。
flowchart TB
H[Full History]
PIN[Pin Hard Constraints]
EXT[Externalize Large Artifacts]
STR[Structured Extraction]
SPLIT[划分近期交互与较早历史]
H --> PIN
PIN --> EXT
EXT --> STR
STR --> SPLIT
较早与近期交互沿不同路径汇入同一装配器。
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart TB
SPLIT["划分近期交互与较早历史"]
IMP["较早历史 Importance<br/>Filtering"]
SUM["Summarize Older History"]
WIN["Keep Recent Sliding<br/>Window"]
PACK["Context Packing"]
SPLIT --> IMP
IMP --> SUM
SPLIT --> WIN
SUM --> PACK
WIN --> PACK
受保护的信息也直接进入该装配器。
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart TB
PIN["Pin Hard Constraints"]
EXT["Externalize Large<br/>Artifacts"]
STR["Structured Extraction"]
PACK["Context Packing"]
PIN --> EXT
EXT --> STR
PIN -->|有效约束| PACK
STR -->|核对后的状态| PACK
EXT -->|Artifact 引用| PACK
图中的并行入口很重要:当前约束、结构化状态与 Artifact 引用直接参与装箱,不必先经过历史摘要器。历史摘要负责补充背景,不能成为恢复精确状态的唯一来源。
一种常见 Context 结构是:
1. System and safety instructions
2. Current goal and acceptance criteria
3. Structured task state
4. Important long-term memories
5. Summary of earlier stages
6. Recent message window
7. Current Tool results
输出预算是请求配置和容量预留,不是要在 Prompt 中写入的一段文字。上述序号是组织示例,不是模型指令优先级;历史摘要与召回数据不能因放在前面就获得系统指令权限。
各方法分工如下:
- Sliding Window 保留近期细节;
- Summary 保留早期整体语义;
- Importance Filtering 保留跨时间的关键内容;
- Structured Extraction 保留精确状态和事实;
- External Artifact 保存大体积原始数据。
10.8 额外方法:Deduplication¶
Agent 常产生大量重复内容:
- 多轮重复说明目标;
- 搜索结果重复引用同一网页;
- Tool 重试返回相同错误;
- 多个 Agent 生成相似结论。
Deduplication 可以:
- 使用内容哈希删除完全重复;
- 使用 Embedding 识别近似重复;
- 合并同一实体的相同事实;
- 将重复来源折叠为引用列表。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
I["Context Items"] --> H["Exact Hash"]
H --> S["Semantic Similarity"]
S --> E["Entity / Fact Merge"]
E --> O["Deduplicated Items"]
去重时应避免误删:
- 来自不同可信来源的相同结论;
- 看似相似但时间不同的事件;
- 数字不同的近似句子;
- 正向与否定表达。
同一错误发生多次还可能说明故障持续,不能去重后丢掉发生次数和时间跨度。多个来源相同结论若都转载自同一原文,只能保留一条来源链,不能当作独立佐证。
10.9 额外方法:Externalization¶
Externalization 将大内容移出 Context,只保留摘要和引用。
适合:
- 长文档;
- 代码库;
- 日志;
- 搜索结果;
- 表格;
- 图片和多模态数据;
- 已完成阶段的详细轨迹。
{
"artifact_id": "tool-result-123",
"uri": "artifact://tool-result-123.json",
"summary": "包含 200 条搜索结果,已筛选 12 条高可信来源",
"content_hash": "sha256:...",
"schema": "search-results"
}
需要时通过 Tool 按片段重新读取。
这种方法不是删除信息,而是将“始终在 Context 中”改为“按需加载”。
前提是引用可解析、有版本、未过期,且 Agent 有可用的读取工具。URI 和哈希不包含原文;对象已删、权限撤销、链接过期或检索失败时,恢复就不成立。按片段读取应返回位置、范围及是否截断,并在工具端鉴权;摘要泄露敏感结论也属于泄露,不能只保护原始对象。
10.10 额外方法:Hierarchical Memory¶
分层记忆同时保留不同粒度:
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 16, "padding": 6}}}%%
flowchart LR
TASK["Task Summary"] --> S1["Stage<br/>Summary A"]
TASK --> S2["Stage<br/>Summary B"]
S1 --> E1["Raw Events"]
S1 --> E2["Artifacts"]
S2 --> E3["Raw Events"]
S2 --> E4["Artifacts"]
Agent 先读取 Task Summary;只有需要细节时,才展开 Stage Summary 或 Raw Event。
这是一种 Progressive Disclosure,可能减少无关上下文,但会增加检索轮次和遗漏风险。概要若没提到关键细节,Agent 可能不会展开正确分支,因此要保留直接检索原文的路径,并测量端到端延迟。
10.11 额外方法:Delta 与 State Compaction¶
对持续变化的状态,不需要每次保存完整副本。
例如计划更新:
系统可以:
- 用 Delta 记录变化;
- 周期性生成 Snapshot;
- 从 Snapshot + Delta 恢复当前状态。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
SNAP["Snapshot"] --> D1["Delta 1"]
D1 --> D2["Delta 2"]
D2 --> D3["Delta N"]
D3 --> NEW["New Snapshot"]
这种方法主要减少状态存储和传输,不等同于自然语言摘要。
恢复要有确定的事件顺序、稳定事件 ID、状态版本和幂等应用规则,且 Snapshot 与已纳入的日志位点一致。不能只凭时间戳推断所有并发事件的先后。事件回放也不应重新执行付款、发信等副作用;已发生的外部效果需要独立的执行记录与核对。
10.12 压缩触发时机¶
10.12.1 Token 阈值¶
当 Context 使用量接近预算时触发。
不应等到窗口完全耗尽,因为还需要为以下内容保留空间:
- 新 Tool Result;
- 模型输出;
- 错误恢复;
- 用户追加信息。
10.12.2 阶段切换¶
一个里程碑完成后:
- 生成阶段摘要;
- 提取结构化状态;
- 外部化 Artifact;
- 清理阶段内临时消息。
10.12.3 Tool Result 过大¶
大型 Tool 输出应立即外部化,而不是先塞入完整 Context 再压缩。
10.12.4 Checkpoint¶
任务暂停或转交 Agent 时可生成压缩交接信息,但保存 checkpoint 不应以 LLM 摘要成功为前提。先可靠保存精确状态、事件位点和工具结果,再异步生成可替换的摘要,避免压缩失败时连恢复点一起丢失。
10.12.5 Context 质量下降¶
即使尚未接近长度上限,如果出现:
- 目标被遗忘;
- 重复动作;
- 无关历史持续干扰;
- Tool 选择变差;
也应重新构建 Context。
这些只是诊断信号,也可能来自错误工具或不准确计划,不能一律归咎于上下文太长。重构失败时应保留旧状态与失败原因,回退到原文选择或分阶段执行,而不是继续基于不完整摘要行动。
10.13 Token Budget 分配¶
总 Context Budget 不应全部用于历史:
其中:
B_instruction:系统、安全和工具说明;B_goal:目标与成功标准;B_state:结构化状态;B_memory:长期记忆;B_recent:最近消息;B_tool:当前 Tool 结果;B_output:模型输出预留。
上式是预算分账示意:先确定该模型 API 的容量口径,再分配输入、输出与安全余量。某些推理模型还将推理 Token 计入输出额度或共享窗口;工具 Schema、图片/音频表示、消息封装也会消耗容量。应以实际 tokenizer 或服务端计数为准,不能按字符数估计后当作硬保证。
B_total 是实际分配的预算,不必等于模型公布的最大窗口;未分配的容量用于计数误差和恢复余量。计入工具 Schema 等开销后若必需内容仍装不下,应分阶段执行,而不是继续压缩精确 ID 或未决状态来凑长度。
设压缩触发阈值时,应预留最大可接受工具结果和下一轮输出,而不采用通用的“使用到某个百分比再压缩”。工具结果无上限时,应先限制、分页或外部化,避免一条响应挤爆窗口。
预算应随任务阶段动态调整。例如:
- 搜索阶段给 Tool Result 更多空间;
- 写作阶段给 Evidence 和 Outline 更多空间;
- 调试阶段给错误日志和代码更多空间。
10.14 Prompt Caching 是什么¶
Prompt Caching 处理的是跨请求的前缀计算复用,不直接缓存 Embedding 或向量索引;与 RAG 入库时上下文增强的关系,见RAG:语义被切断怎么办。
Prompt Caching 缓存重复 Prompt 前缀的中间计算结果,使后续请求可以复用。
%%{init: {"sequence": {"width": 75, "height": 45, "actorMargin": 10, "diagramMarginX": 5, "messageMargin": 18, "wrap": true, "wrapPadding": 5}}}%%
sequenceDiagram
participant A as Application
participant C as Prompt Cache
participant M as Model
A->>M: Stable Prefix + New<br/>Suffix
M->>C: 缓存稳定前缀计算
A->>M: Same Prefix + Another<br/>Suffix
C-->>M: 复用前缀计算
M-->>A: 命中时可减少重复 Prefill<br/>开销
适合缓存:
- 长 System Prompt;
- Tool Definitions;
- 稳定项目说明;
- 大段重复文档;
- 多轮共享的固定前缀。
10.15 Prompt Caching 与记忆压缩的区别¶
| 维度 | Memory Compression | Prompt Caching |
|---|---|---|
| 优化层次 | 信息层 | 计算层 |
| 核心问题 | 带哪些信息进入 Context | 重复 Context 如何少算一次 |
| 是否减少 Context 长度 | 目标是减少;需实测,结构化后也可能更长 | 否 |
| 是否改变信息内容 | 可能改变或删除 | 不改变 |
| 是否释放 Context Window | 只有实际减少输入 Token 时才释放 | 否 |
| 是否降低重复 Prefill 成本 | 可能间接降低,需计入压缩本身成本 | 命中时可降低重复计算,总费用取决于写入和读取规则 |
| 是否解决噪音问题 | 可能减少噪音,也可能误删证据 | 否 |
这里最容易被误解的是:
Prompt Caching 通常不会让 Context Window 变大,缓存 Token 仍属于模型输入上下文。
即使缓存命中,模型仍然“看到”相同内容,只是服务端可能复用计算,从而降低成本或延迟。
10.16 Prompt Caching 与压缩如何配合¶
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 16, "padding": 10}}}%%
flowchart TB
RAW["Raw Information"] --> COMP["Memory Compression"]
COMP --> KEEP["Selected Context"]
KEEP --> CACHE["Prompt Caching"]
CACHE --> MODEL["Model"]
实践里通常先做两步:
- 先决定哪些信息真正需要进入 Context;
- 再对其中稳定、重复的前缀使用 Prompt Caching。
例如:
- System Instructions 和 Tool Definitions:适合缓存;
- 当前任务状态:需要压缩和动态更新;
- 旧对话:需要摘要或过滤;
- 大型 Artifact:需要外部化和按需检索。
不要只比较“本轮输入少了多少”。如果摘要花费超过后续调用节省的费用,或频繁重写摘要使稳定前缀失效,总成本反而可能上升。可以先测一次压缩的成本,再看剩余任务预计会复用多少轮;任务快结束时,简单裁剪往往更值得作为对照。
10.17 Prompt Caching 的限制¶
- 对这里讨论的前缀缓存,要求可复用前缀精确匹配,而不是语义相似或“高度一致”;
- 缓存有生命周期;
- 不同模型或配置可能不能共享;
- 动态内容放在前缀中会降低命中率;
- 不能消除 Context 中的错误和噪音;
- 不能替代权限过滤;
- 不能替代长期记忆;
- 具体费用和缓存规则由模型服务商决定。
Prompt 设计时通常将稳定内容放在前面,动态内容放在后面,以提高缓存复用。
OpenAI 官方文档【80】说明,需匹配实际渲染的前缀;模型、工具 Schema、顺序及相关设置变化都可能改变可复用部分。缓存最低长度、断点方式、写入费用和保留时间依模型与服务版本而异,不能把某个型号的数字推广为统一规则。
频繁改写前面的摘要可能使后续缓存失效,压缩调用自身也有成本。对照总账单和首 Token 延迟,分别测量冷缓存、热缓存及压缩后的请求;不要为了缓存命中继续发送失效权限或过期敏感数据。
10.18 KV Cache 与 Prompt Caching¶
KV Cache 是 Transformer 推理中缓存 Attention Key/Value 状态的底层机制。
Prompt Caching 是模型服务对应用暴露的跨请求复用能力,底层可能利用 KV 或其他缓存实现。
| KV Cache | Prompt Caching |
|---|---|
| 模型推理内部机制 | 服务或 API 产品能力 |
| 常用于一次生成过程 | 通常跨请求复用前缀 |
| 开发者未必直接控制 | 开发者可以通过 Prompt 结构优化命中 |
两者都属于计算优化,不属于语义记忆压缩。
但 KV Cache 的量化、淘汰、卸载又是另一组推理系统技术,它们可能改变精度、显存与延迟,并不等价于生成可审计摘要。应用若只使用托管 API,不应假定能操作这些底层状态。
10.18.1 服务端 Compaction 也不等于 Prompt Cache¶
OpenAI Responses 的 Compaction【495】会产生不透明的加密 compaction item,供后续请求继续使用。这是缩减后续 Context 的服务能力,不是仅复用前缀计算,也不能假定其内部就是可读的自然语言摘要。
要区分两条接口路径:
- 显式调用
/responses/compact:提交的窗口必须仍能装入所用模型。返回值是新的完整压缩窗口,除了 compaction item,还可能保留其他消息;应整体作为后续请求的基础,不能只取出加密 item 就丢弃其余输出。 - 在
/responses中启用服务端自动压缩:由服务端按配置阈值触发。无状态数组续接和previous_response_id续接有不同的历史传递规则,不能混用手工裁剪逻辑。
应用不应解析或改写加密 item;模型支持范围及服务端状态保留选项需查对应接口文档。这两种方式都不能替代应用自己的任务状态、事实来源、权限及删除管理,也不能当成跨供应商可移植的审计记录。
10.19 压缩质量怎么评估¶
10.19.1 Compression Ratio¶
这里将 CR 定义为 Token 减少率,要求 L_before 为正;若摘要或 JSON 比原文更长,CR 会为负,不能把它报告成节省。其他资料可能将压缩比定义为 L_before / L_after,报告时必须注明口径。
计数应使用同一 tokenizer、相同内容边界。这个指标只描述当前上下文缩短了多少;若要声称端到端节省,还必须计入摘要生成、外部化后重新读取和额外模型调用的费用。
压缩比例高不代表质量高。如果关键约束被删除,再短也没有价值。
10.19.2 Constraint Retention¶
检查:
- 用户目标;
- 安全规则;
- 验收条件;
- 未解决问题;
- 关键数字和实体;
是否仍然保留。
10.19.3 State Accuracy¶
压缩后的状态是否准确反映:
- 已完成步骤;
- 当前步骤;
- 待执行步骤;
- 错误和重试;
- Artifact。
10.19.4 Task Success¶
比较压缩前后:
- 任务成功率;
- Tool 选择正确率;
- 重复调用;
- 幻觉率;
- 成本和延迟。
10.19.5 Recoverability¶
Agent 能否从压缩后的 Context 和外部 State:
- 恢复任务;
- 解释已完成内容;
- 找到原始证据;
- 继续下一步。
10.20 压缩测试方法¶
10.20.1 Needle Test¶
在长历史中放入关键约束,检查压缩后是否保留并正确使用。
单个 needle 测试不足以证明长程推理能力,还要覆盖多个相关证据、干扰项、时间更新、否定和没有答案的情况。
10.20.2 Replay Test¶
从 Checkpoint 和压缩 Context 恢复 Agent,观察能否继续任务。
分别在“工具执行前”“执行后但状态落盘前”“摘要生成中”中断;断言不会重复副作用、不会把未知结果改为成功、不会读取已撤销权限的 Artifact。恢复正确性应检查实际状态,不只检查回答文本。
10.20.3 Differential Test¶
使用完整历史和压缩历史分别执行相同任务,比较结果差异。
完整历史必须能装入模型,并说明是否发生服务端裁剪;它是基线,不是正确答案。再加入无记忆、近期窗口、直接提供人工标注证据的对照,固定模型版本及任务分布,多次运行并报告波动。除了最终正确率,还统计约束保留、证据可追溯性和压缩造成的退化。
10.20.4 Adversarial Test¶
测试:
- 早期安全约束;
- 后期冲突指令;
- 重复噪音;
- 恶意 Prompt Injection;
- 关键数字和否定关系。
10.20.5 Long-horizon Test¶
让 Agent 执行几十或上百步,检查:
- Summary Drift;
- 目标遗忘;
- 状态错乱;
- 重复动作;
- Artifact 丢失。
测试步数来自业务轨迹分布,不是可靠性的统一门槛。要记录累计压缩次数,检查远期信息是否逐轮丢失;总成本应包含摘要生成、写入索引、额外检索和恢复调用。
公开数据可以参考 LoCoMo【481】 的事件摘要与问答、LongMemEval【480】 的知识更新与弃答,以及 LongMemEval-V2【482】 的轨迹经验检索。它们不直接验证你自己的 checkpoint、ACL 或副作用恢复;固定数据版本,按会话/轨迹分组统计,避免把相关问题当作独立用户样本。
10.21 常见反模式¶
10.21.1 只保留最近 N 轮¶
可能删除最初目标和安全约束。
10.21.2 每轮都重写一个总摘要¶
容易产生累积失真。
10.21.3 让模型自由判断什么都可以删¶
缺少 Hard Rules 和结构化状态保护。
10.21.4 把代码和错误日志全部摘要¶
可能丢失精确行号、错误码和调用栈。应外部化并保留引用。
10.21.5 压缩后删除所有原始数据¶
如果后续仍需精确取证,却只留下有损摘要,就无法审计、验证或重新生成。但这不是无限保留原文的理由;应按合法用途设定保留期,到期后清理原文及派生数据,并明确哪些恢复能力随之结束。
10.21.6 把 Prompt Caching 当作扩展窗口¶
缓存不减少输入长度,也不会消除信息噪音。
10.21.7 只追求最高 Compression Ratio¶
会鼓励系统删除真正有价值的信息。
10.21.8 摘要把「未验证的结论」写成「已确认的事实」¶
这种失效容易在多轮任务中传播,尤其会影响下一步操作判断。
典型场景:某个命令因超时或被中断而只输出了部分结果,摘要却把它记录为「已执行成功,结果为 X」。如果后续没有独立验证,这条虚假的确定性就可能传播到后面的计划和操作。
缓解方式有三条:
- 分开保留执行状态(
pending/succeeded/failed/unknown)与结论验证状态(verified/unverified),而不是只记结论; - 保留工具调用的退出码与截断标记,不要在摘要阶段丢弃;
- 对关键结论保留原始引用(日志位置、文件路径),使其可被重新核对。
10.21.9 只依赖上下文内的摘要链承载长期决策¶
多轮压缩会累积信息损耗,早期的关键决策与约束在若干轮压缩后可能彻底消失,形成难以追溯的「历史债」。
可将重要决策同时写入外部文件或结构化状态(即 10.9 节的 Externalization),并保存来源、版本和适用范围。外部保存不代表模型自动能读到它,还需有稳定加载入口和读回校验;决策变化时同步失效旧记录,不能让旧文件一直充当有效约束。
10.22 推荐的生产级压缩管道¶
flowchart TB
CLASS[输入分类] --> PACK[上下文装配器]
PACK --> CACHE[提示词缓存稳定前缀]
CACHE --> MODEL[模型]
MODEL --> OBS[新观察结果]
OBS --> CLASS
输入包含消息、工具结果与状态。分类时区分当前有效的固定约束、经执行证据核对的结构化状态、大型产物与历史消息。约束与已验证状态直接进入装配器,大型产物先外置并替换为引用。
历史按完整交互与阶段分组。近期完整交互直接进入装配器;较早历史先去重、按重要性筛选,再生成分层摘要。长期记忆同样可以进入装配器,但必须先复核权限与版本。装配后的上下文通过稳定前缀使用提示词缓存,再交给模型;新观察结果重新进入输入分类。
可按任务需要选择以下组合;短任务不一定需要摘要和分层存储:
- 保留当前有效的系统、安全、目标和成功标准,并随权限和目标变化更新;
- 将任务状态抽取为结构化数据,并用执行证据核对;
- 大型 Tool Result 立即外部化;
- 对历史做去重和重要性过滤;
- 已完成阶段使用分层摘要;
- 保留最近交互窗口;
- 长期记忆按当前任务检索;
- 按 Token Budget 组装 Context;
- 对稳定前缀使用 Prompt Caching;
- 在授权与保留期限内保留原始来源,验证引用可读和恢复路径可用。
10.23 方法选择表¶
| 问题 | 推荐方法 |
|---|---|
| 最近对话最重要 | Sliding Window |
| 需要保留早期整体语义 | Summarization |
| 关键内容分散在整个历史 | Importance Filtering |
| 需要精确状态和事实 | Structured Extraction |
| 存在大量重复信息 | Deduplication |
| Tool Result 或文档太大 | Externalization |
| 任务跨多个阶段 | Hierarchical Memory |
| 状态频繁变化 | Delta + Snapshot |
| 重复使用稳定 Prompt 前缀 | Prompt Caching |
10.24 本章总结¶
四种基础压缩方法解决不同维度:
- Sliding Window:按时间截断历史;
- Summarization:在截断前提炼语义;
- Importance Filtering:打破时间顺序,按价值选择;
- Structured Extraction:提取候选状态并核对证据;Schema 合法不保证事实正确,也不保证 Token 更少。
现代系统通常还会结合:
- Deduplication;
- Artifact Externalization;
- Hierarchical Memory;
- Delta 与 Snapshot;
- Retrieval;
- Token Budget Packing。
Prompt Caching 与这些方法位于不同层次:
记忆压缩决定带什么信息,Prompt Caching 决定重复信息如何减少计算。
两者互补,但 Prompt Caching 不会释放 Context Window,也不能替代摘要、过滤和结构化抽取。
压缩后至少核对一次目标、未决状态、关键证据和引用可读性。若这些无法恢复,应回到原始记录或明确报告信息不足,而不是把流畅的摘要当作完整历史。
参考资料¶
本章的参考资料、阅读建议与来源说明见集中参考资料章节。