上下文压缩:触发、保留与窗口重建
比较本地摘要、Remote V2 和 TokenBudget,读预算公式、保留白名单和失败处理。
学完你能做到
- 能计算当前分支的触发条件
- 能解释两种摘要路径分别保留哪些原文
- 能区分生成摘要、确定性和无摘要窗口重置
本课目录 · 7 节
1. 先把三件事分开
在本版本中,、摘要和窗口重置都有可能降低活跃输入,但它们的机制不同。单项工具输出发生在入库;整个窗口的 compaction 在特定阶段运行;结果装回后,还要重新建立环境与指令基线。不能把这些全部描述为“自动总结旧消息”。
| 机制 | 谁决定删减 | 保留方式 |
|---|---|---|
| 单项 | 客户端容量算法 | 确定性保留首尾,或将指定输出改成标记。 |
| 本地摘要分支 | 客户端规则 + 一次请求 | 生成交接摘要,客户端按预算选择用户原文,再注入当前环境。 |
| 客户端准备/保留规则 + 服务端结果 | 客户端发送触发项,校验一个 结果;服务端内部不可见。 | |
| 窗口重置 | 客户端新窗口逻辑 | 不生成摘要,重建初始,并按开关保留特定客户端项。 |
2. 触发条件:不是固定的“还剩 10%”
context_window_token_status 把预算拆成活跃总量、自动计量范围、完整窗口上限和结束阈值。活跃总量主要由最近一次服务端 usage 加上之后新增项的本地估算构成;服务器是否已经计入过去的 reasoning,影响客户端是否补估。它不是每次把整段历史交给精确 tokenizer 重算。
活跃使用量的计算 ↗codex-rs/core/src/context_manager/history.rs:935–959
| 变量 / 配置 | 本版本的计算 | 含义 |
|---|---|---|
A:活跃总量 | get_total_token_usage() | 用于判断完整窗口是否达到上限。 |
S:自动计量 | Total 模式:A;BodyAfterPrefix 模式:max(A − P, 0) | P 是已知 基线;没有时以当前 A 作为基线。 |
L:计量额度 | 从本模式的信息或配置解析 | 它不是所有共享的常数。 |
H:可用完整窗口 | resolved_context_window × effective_context_window_percent / 100 | 独立于计量模式的完整窗口上限。 |
F:fallback 余量 | 来自 token_budget.fallback_buffer_tokens() | 有对应 fallback 机制时才保留这个余量。 |
token_limit_reached | S ≥ L + F OR A ≥ H(只比较有值的限额) | 即使只对正文计量,也不能越过完整窗口 H。 |
预算、余量与阶段阈值的完整公式 ↗codex-rs/core/src/session/context_window.rs:59–130
3. 三个时点,加上模型切换
- 前:run_pre_sampling_compact 在常规本步创建前检查容量;此时新输入还没有被正常记录。源码有 TODO:预先估计即将加入的和用户输入,以免刚加入就超过阈值。
- 中:需要继续处理,且请求了新窗口或已达额度时进入,然后重新捕获步骤;并非每次响应后都。
- 后:启用 post-turn 阈值、 未启用、没有待输入且未取消时,可在结束位置运行。阈值按完整可用窗口比例判断。
- 切换:兼容 hash 明确不同,或切到更小窗口且容量条件满足时,尝试用前一个整理旧窗口;当前负责新窗口的。缺失 hash 不足以触发 hash-change 分支。
新输入前的容量检查与 TODO ↗codex-rs/core/src/session/turn.rs:181–188
回合中继续及窗口条件 ↗codex-rs/core/src/session/turn.rs:609–649
回合结束阶段 ↗codex-rs/core/src/session/turn.rs:720–744
兼容 hash 和较小窗口模型切换 ↗codex-rs/core/src/session/turn.rs:1336–1432
4. 本地摘要:20k 用户原文预算,加一份交接摘要
“本地”是客户端组织普通请求,不是离线算法。它克隆历史,加入 compact_prompt(未配置时采用仓库内交接摘要模板),调用,再从返回结果构建新历史。模板要求保存进展、决策、约束、未完成事项和关键资料;这一要求不能保证每个细节无损保留。
摘要提示的选择 ↗codex-rs/core/src/compact.rs:113–150
交接摘要模板 ↗codex-rs/prompts/templates/compact/prompt.md:1–9
客户端先提取真实用户消息,排除旧 summary。build_compacted_history_with_limit 从最新用户消息往前取,预算为 COMPACT_USER_MESSAGE_MAX_TOKENS=20,000 个近似 。能完整容纳且全为文本的消息保留原文本项;越过边界时重建文本 fallback,首尾到剩余预算,并标明来源不完整;最后反转,恢复时间顺序,再追加摘要。媒体不会在文本 fallback 路径被原样克隆。
用户原文选择入口 ↗codex-rs/core/src/compact.rs:573–611
if max_tokens > 0 {
let mut remaining = max_tokens;
for message in user_messages.iter().rev() {
if remaining == 0 {
break;
}
let tokens = approx_token_count(&message.message);
let ResponseItem::Message {
id,
content,
internal_chat_message_metadata_passthrough,从最近消息开始消耗预算。选择优先级在这段代码里是时间顺序,不是相关性评分。
查看原文及相邻代码 ↗边界消息、媒体和摘要项的重建 ↗codex-rs/core/src/compact.rs:718–781
20k 只约束这一组选中的用户原文,不包含摘要、重新注入的指令和其他请求开销。旧链与助手过程通常不作为这条本地分支的原文集合保留;它们依赖摘要延续信息。若生成摘要的请求本身超窗,代码会从旧历史前端删项,成对移除关联结果,然后重试;一般流错误才按退避预算处理。
超窗与一般请求失败的不同处理 ↗codex-rs/core/src/compact.rs:332–371
删除最老项时维护调用配对 ↗codex-rs/core/src/context_manager/history.rs:683–695
5. Remote V2:先救输入容量,再选原文保留形状
远程请求前,trim_function_call_history_to_fit_context_window 从历史尾部往前处理连续可改写的输出,把过大的结果正文替换成固定说明,同时保留调用关联和结果状态。遇到用户/开发者消息等不可改写边界即停止;启用保留客户端输出时,这种输出也构成边界。这不是任意扫描并删掉最大的输出,也不是删最早消息。
远程请求前的尾部输出改写 ↗codex-rs/core/src/compact_remote_history.rs:76–146
然后发送 CompactionTrigger,收集流式结果。collect_compaction_output 要求响应有 completed,并且恰好一个 输出;可以同时到达其他输出项,但缺少或多个项都是错误。客户端不能从这个接口证明服务端采用了怎样的摘要、检索或编码算法。
触发项和请求字段 ↗codex-rs/core/src/compact_remote_v2_attempt.rs:81–113
完成事件与压缩项校验 ↗codex-rs/core/src/compact_remote_v2.rs:435–497
| 原文类型 | Remote V2 安装新历史时是否保留 | 附加条件 |
|---|---|---|
| 真实用户消息、HookPrompt | 进入保留候选 | 按 parse_turn_item 判断,不是所有 role=user 文本。 |
| 客户端 developer 消息 | 功能开启才候选 | 要求 client_authored ;正文角色不能替代来源标记。 |
| 客户端具名独立输出 | 功能开启才候选 | 要求 client_authored、FunctionCallOutput、=None、name 有值;不是所有普通工具结果。 |
| 代理之间的消息 | 部分候选 | 排除后代进度与 FINAL_ANSWER;单项估算 ≤10k。 |
| 普通助手消息、普通调用和对应结果 | 不由该白名单原样保留 | 相关信息能否在 中延续,不能由客户端判断。 |
保留候选白名单 ↗codex-rs/core/src/compact_remote_v2.rs:499–593
被特殊保留的输出身份 ↗codex-rs/core/src/compact_retained_tool_outputs.rs:1–20
候选进入 64,000 保留预算。实现按来源项和附带 notice 分组,从最新组向前扫描;边界文本可,特殊保留输出必须整项保留或停止,不截半条。是否计入图像容量由 RetainedImageBudget 分支决定,不能把文字额度当成媒体容量保证。结果恢复顺序后追加 ,再注入当前状态。64k 同样不是最终请求总上限。
分组预算与整项输出边界 ↗codex-rs/core/src/compact_remote_v2.rs:618–722
6. TokenBudget:没有摘要,换一个窗口
run_auto_compact 首先检查 。若开启,会跳过 与本地摘要,调用 start_new_context_window。后者重建完整初始,只按开关保留特定客户端 developer/独立输出,使用同一 64k 保留预算;普通对话不会在这段函数中自动变成一份摘要。
压缩实现的选择顺序 ↗codex-rs/core/src/session/turn.rs:1461–1513
相同生命周期下跳过摘要 ↗codex-rs/core/src/compact_token_budget.rs:15–69
新窗口实际装入什么 ↗codex-rs/core/src/session/mod.rs:4658–4718
该功能在通用 feature 表中默认 false,但默认值或满足条件的实验模式可以请求启用,管理要求又可能将它固定关闭。因此“仓库默认关闭”与“某次运行没有启用”不是同一结论。窗口间的信息恢复还可能依赖 history-notes 等扩展贡献;不能把可选桥接写成所有安装都具有的记忆系统。
通用 feature 默认值 ↗codex-rs/features/src/lib.rs:1889–1894
模型默认值的启用边界 ↗codex-rs/core/src/session/token_budget.rs:128–158
可选窗口提示与 notes 桥接 ↗codex-rs/core/src/session/mod.rs:4411–4478
7. 安装历史:新摘要不应覆盖当前权限事实
摘要或远程结果产生后,build_compaction_replacement_history 从当前 Session/ 重新生成指令和环境,而不是把旧环境原文全部当摘要的一部分。前缀放在最前;常规优先插到最后一个真实用户、相关代理消息或保留输出之前;没有这些项时放到摘要或项之前。
重建当前状态与前缀 ↗codex-rs/core/src/compact.rs:80–111
插入位置的回退规则 ↗codex-rs/core/src/compact.rs:610–681
Session 安装替换历史、新的参考和状态基线,推进窗口 ID,并重算使用量。前失败时,run_turn 先保留尚未记录的输入,再发出错误;取消、预算错误与普通重试也有不同分支。是一次有生命周期的状态转换,不是“把旧数组换成一句话”就结束。
替换历史与用量重算 ↗codex-rs/core/src/compact.rs:375–423
回合前失败先保留输入 ↗codex-rs/core/src/session/turn.rs:189–221
沿着源码读
以下定位均锁定本课程版本。先读注释与分支,再深入类型定义。
- 压缩触发公式
codex-rs/core/src/session/context_window.rs:59–130 - 时点、模型切换与实现分支
codex-rs/core/src/session/turn.rs:1304–1513 - 本地用户原文预算
codex-rs/core/src/compact.rs:695–781 - 远程原文选择与预算
codex-rs/core/src/compact_remote_v2.rs:499–722 - 请求前救容量
codex-rs/core/src/compact_remote_history.rs:76–146 - 不生成摘要的新窗口
codex-rs/core/src/session/mod.rs:4658–4718
实现推演
设历史依次为用户 U1、工具输出 O1、助手 A1、用户 U2、具名客户端独立输出 O2。分别按本地摘要、(相关保留开关开启)和 新窗口列出原文候选。再给保留预算填入模拟值,判断边界项会被、整项留下还是丢弃。不要推测远程 里隐藏了什么。
阅读练习无需模型账号。示例命令请先确认目录与环境。
自测与边界检查
为什么 20k 或 64k 不能解释为最终输入的最大容量?
展开自测答案
这些常量仅约束原文保留集合;摘要/、重注入的指令、环境和其他请求字段另计。整个窗口还受可用上限约束,近似计数也不等于精确 tokenizer。
所有 compaction 都会调用生成摘要吗?
展开自测答案
不会。 分支直接重建窗口。非该分支再按 provider 能力选择 或本地摘要。功能默认值、激活默认值、实验配置与管理要求都可能影响实际分支。