服务端返回 ContextWindowExceeded 时,Codex 不在同一失败请求上盲目重试,也不会立即把半成品历史就地压缩后续跑。它先把 token 状态标记为已满并结束当前 Turn;下一次采样前的压缩检查才使用完整压缩流程。
具体问题与启用条件
本节区分主动压缩、服务端超窗错误和 previous-model compaction 的模型 fallback。压缩算法本身已在第四章 4.22—4.24 说明。
| 条件来源 | 决定字段或状态 | 对本机制的影响 |
|---|---|---|
| 主动阈值 | auto compact limit/full context window | 采样前或需要 follow-up 时压缩 |
| 服务端错误 | context_length_exceeded | 映射 ContextWindowExceeded |
| 模型切换压缩 | comp_hash 变化或窗口缩小 | 可先用 previous model 压缩 |
协议、类型与状态所有权
| 状态或协议 | 所有者 | 生命周期 | 关键不变量 |
|---|---|---|---|
| token usage/full marker | Session state | 跨 Turn | 服务端超窗后强制视为已满 |
| compaction decision | run_turn/run_pre_sampling_compact | 采样边界 | 不在传输重试函数内 |
| fallback StepContext | previous-model compaction | 一次压缩尝试 | 失败时可用当前模型重试压缩 |
机制调用链如下:
正常路径:token status 达阈值
→ run_auto_compact(local/remote/token-budget)
→ 安装 replacement history
→ 下一采样
错误路径:response.failed context_length_exceeded
→ CodexErr::ContextWindowExceeded
→ set_total_tokens_full
→ 不可 retry → Error/结束当前 Turn
→ 后续 Turn pre-sampling token check
→ run_auto_compact
机制怎样工作
Context error在 SSE mapper成为专用 ApiError,再映射为不可重试 CodexErr。run_sampling_request 捕获后调用 set_total_tokens_full 并立刻返回;外层 Turn 发错误事件后允许用户继续会话。这样传输重试不会反复发送同一过长请求。下一 Turn 的 run_pre_sampling_compact 读取 token status,发现已达限制后捕获新的 StepContext 并运行统一压缩。
正常情况下压缩更早发生:Turn 前检查阈值,同一 Turn 某次 Completed 后若仍需 follow-up 且达到限制,则在下一采样前做 mid-turn compact。模型切换还有另一种 fallback:若需要用 previous model 做兼容压缩,且该尝试出现 InvalidRequest、ContextWindow、Usage、Overloaded、Internal、RetryLimit 等模型相关错误,可用当前模型的 fallback StepContext 重试压缩。这个“模型 fallback”与 WebSocket→HTTP 传输 fallback 完全不同。
Python 风格伪代码
这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。
async def sample_once_or_mark_full(turn: TurnContext):
try:
return await run_sampling_request(turn)
except ContextWindowExceeded:
await session.tokens.mark_full(turn.model.context_window)
raise # 当前 Turn 结束,不做同请求 retry
async def before_sampling(turn: TurnContext, client: ModelTurnSession):
status = await session.context_window_status(turn)
if status.token_limit_reached:
step = await session.capture_step_context(turn, turn.cancel)
await run_auto_compact(step, client, phase="pre_turn")
async def compact_previous_model(previous_step, current_step):
try:
await compact(previous_step)
except CodexError as error:
if current_step is None or not model_specific_compaction_error(error):
raise
await compact(current_step) # 模型 fallback,不是传输 fallback
失败、取消与恢复
| 故障或边界 | 已发生的状态 | 对上层的结果 | 能否直接重试 | 恢复动作 |
|---|---|---|---|---|
| 服务端 context error | 请求未完成,token 标记 full | 当前 Turn Error | 否 | 后续 Turn 先 compact |
| 主动 compact 失败 | 旧 History 仍是权威 | 压缩错误/Turn 结束 | 依错误 | 不安装半成品历史 |
| previous-model compact 失败且可回退 | 尚未替换 History | 当前模型再压缩一次 | 一次模型 fallback | 记录 telemetry |
| 压缩后仍过长 | replacement history 可能仍超限 | 再次错误 | 不盲重试 | 新线程或更强裁剪 |
设计取舍与验证
不在 context error 后自动原地压缩并重跑当前 Turn,牺牲一次无感恢复机会,却避免对已产生部分 Done Item、工具副作用和动态 StepContext 做隐式重写。主动阈值负责大多数正常场景;错误标记保证下一次入口会走受测试的压缩流程。
| 可验证契约 | 证据方式 | 预期结果 |
|---|---|---|
| context_length_exceeded 不走普通 retry | 错误分类与请求计数 | 同 Turn 只发一次过长采样 |
| token full 触发下一入口压缩 | 构造满窗口 Session | 采样前出现 compact |
| previous model 失败可用 current model fallback | compact model fallback 测试 | 第二模型尝试并记录 outcome |
Mini Codex 对照
Mini Codex 应把 ContextWindowExceeded 从 RetryPolicy 排除,并让压缩属于 Agent Loop 而非 HTTP client。可选择自动在同一 Turn 重试,但那是不同产品策略,必须先定义已完成 Item 与工具副作用怎样处理。
本节边界
本节只解释触发与恢复时机,不重复压缩内容算法。5.24 讨论另一个常被误写的边界:Output Schema 约束由谁验证。
评论
登录后即可评论