Skip to content

附录 C|远程 compact v2:压缩是 provider 能力,不是第二种产品 ​

先别把远端总结理解成「换一个模型把历史重写掉」 ​

B4 把 compact 钉成 Task + 采样前钩子,并说本地还是远端看 RemoteCompactionSupport。没有这一附录,会把 v2 理解成再 POST 一次聊天,或把图片从摘要里随便砍掉导致 <image> 标签断裂。

判断先说清楚:v2 是 Responses 端点上的 compaction_trigger 能力(model-provider/src/provider.rs:46-50)。 Core 只消费 provider 声明的上界;配置可以再关,不能对 Unsupported 的 provider 假装有远程压缩。实现是「请服务端压」,不是本地把 SUMMARIZATION_PROMPT 当用户话再跑一轮 Regular turn。落盘和替换历史仍走同一套 replace_compacted_history——远端改变的是 谁写摘要,不是「禁止 rewrite」这条纪律。

TokenBudget feature 开着时,手动和自动 compact 都不走 v2(tasks/compact.rs:36-38,session/turn.rs:1408-1417)。那是另一条窗口重置,别和 v2 焊在一起。


1. 分叉点:provider 能力,然后才是手动 / 自动 ​

CompactTask 和采样前 auto-compact 用同一条 match(tasks/compact.rs:41-67,turn.rs:1420-1452):

remote_compaction走哪
V2compact_remote_v2::run_remote_compact_task / run_inline_remote_auto_compact_task
Unsupported本地 compact::run_compact_task,输入是总结提示

遥测标签 remote_v2 vs local。ModelClient 的 beta 头会 无条件带上 RemoteCompactionV2 这个 feature key(session/mod.rs:1118-1131),即使菜单里没单独打开——广告的是「客户端懂 v2」,真正打不打仍看当前 provider 的 capabilities。

手动 /compact:standalone turn,新 capture 一份 StepContext(compact_remote_v2.rs:109-112),InitialContextInjection::DoNotInject。自动:复用正在采样前的 step,并可带 fallback_step_context(换模型再试)。轮前失败仍由 run_turn 保住还没入历史的用户话(B4);非 PreTurn 才会立刻发 Error 事件(:200-210)。

Compact 不能 steer(B1)。远端再慢也是这一 Task,不是开第二条用户可见 Turn 让你插话。


2. 一次 attempt:先削工具输出,再打 compaction 流 ​

run_remote_compact_v2_attempt(compact_remote_v2_attempt.rs:31-47)先 clone_history,再 trim_function_call_history_to_fit_context_window:工具输出太大,先截成「Output exceeded the available model context…」(compact_remote_history.rs:16-17),否则 compaction 请求自己都塞不进窗口。

请求种类是 ResponsesStreamRequest::RemoteCompactionV2。流重试 故意比普通 turn 少:MAX_REMOTE_COMPACTION_V2_STREAM_RETRIES = 2(compact_remote_v2.rs:75-77),再和 provider 的 stream_max_retries() 取 min(:372-376)。注释:compact 可能跑很久,传输层预算要更小,避免一次压缩把整段 Session 的 HTTP fallback 耗光(对照 B1:HTTP fallback 是 Session 级)。

只收集 ResponseItem::Compaction 当产出(:431-437)。这是服务端的压缩 item,不是模型闲聊。effort 用 RequestEffortUsage::Compaction,和普通采样分开记账。

第一次 attempt 失败且 should_retry_with_current_model(InvalidRequest、超窗、额度、过载、5xx、RetryLimit,compact_model_fallback.rs:8-19),并且有 fallback_step_context,才换当前模型再打一枪(compact_remote_v2.rs:246-285)。第二次再失败,把 第一次 的 error 抛出去。TurnAborted 不走这条降级。

没有「削输出 + 小重试 + 可选换模型」会怎样?压缩请求自己 ContextWindowExceeded,然后按普通 turn 的重试把 sticky WS 打到 HTTP,后面所有对话都失去预热。v2 把压缩当成长请求、短重试。


3. 装回去:64k 保留窗,图片要整块留 ​

服务端摘要回来之后,build_v2_compacted_history(:483-509)不是「只留 Compaction 一项」:

  1. 按组过滤:哪些边界消息 保留原文(含可选的 client-authored developer 消息,feature RetainClientDeveloperMessages)
  2. truncate_retained_messages(..., RETAINED_MESSAGE_TOKEN_BUDGET = 64_000, image_budget)
  3. 最后 追加 compaction 产出

64k 是保留消息的硬顶,从 最近往前 吃额度(:598-612)。超了丢更早的组,不是改 compaction 正文。

图片:Feature::CompactionImageBudget 开了才 RetainedImageBudget::Enabled(:309-313)。开启时,非 client-developer 消息按图+文一起计费;truncate_message_to_token_budget 把 图和两侧 harness 标签当成原子块(compact_remote_v2_images.rs:28-59),从后往前留。关预算则图不计入这条 64k(仍可能物理留在 item 里,但不和文字抢同一本账)。音频不收费、照旧保留。

没有原子块会怎样?留下半个 InputImage、丢掉 <image> 开标签,下一轮模型看见烂 XML,cache 和 view_image 全乱。v2 的图片策略是「整张图要么留下要么整组丢掉」,不是对 data URL 做 substring。

装入 live history:仍 insert_initial_context_before_last_real_user_or_summary + replace_compacted_history(:317-338)。轮前 DoNotInject,下一 Regular turn 全量再注 world state(B4)。trace 记下 input_history 和 replacement_history,resume 重放的是替换后的 JSONL,不是「再向服务端要一次压缩」。


4. 和本地 compact 什么相同、什么不同 ​

本地远程 v2
谁写摘要本机再采样,输入是总结 promptResponses compaction item
能否 steer否否
失败保用户话是(PreTurn)是
历史替换CompactedItem + fragment同左,外加可选保留消息+图
重试普通模型重试流最多 2 次;可换 fallback 模型一次
Provider任何能聊天的必须声明 V2

抄 harness:先实现本地 compact + JSONL 替换(F3 P1)。v2 是「provider 会压」时的加速路径,不是 P0。API Key 路径有没有 V2,看该 provider 的 capabilities,不要抄 ChatGPT 后端的假能力(A1)。


5. 结语:带走一句话 ​

远程 compact v2 是 provider 声明的 Responses 压缩:少重试、可换模型再打一枪、装回历史时 64k 保留窗且图片整块计费——它不改「采样前压、失败保住用户话、只追加不 rewrite」这三条,只是换了谁写那份摘要。