Skip to content

fix(core,manager): 按模型粒度配置上下文窗口(model_catalog_json + 后缀语法)#1197

Open
jarvislee90s-dot wants to merge 23 commits into
BigPizzaV3:mainfrom
jarvislee90s-dot:codex/per-model-context
Open

fix(core,manager): 按模型粒度配置上下文窗口(model_catalog_json + 后缀语法)#1197
jarvislee90s-dot wants to merge 23 commits into
BigPizzaV3:mainfrom
jarvislee90s-dot:codex/per-model-context

Conversation

@jarvislee90s-dot

Copy link
Copy Markdown

背景

Closes #1171,基于 #931 已确认的运行时根因做 CodexPlusPlus 侧的自动化实现。

目前 CodexPlusPlus 的上下文窗口是按 relay profile 单值配置的:一个 profile 只有一个 model_context_window。当模型列表里同时存在 deepseek-v4-pro(1M)、claude-sonnet-4(200K)等不同窗口的模型时,用户只能二选一:要么统一写成 1M 导致小窗口模型配额浪费,要么统一写成 200K 导致大模型被提前压缩。

本 PR 引入 模型级后缀语法 + 自动 model_catalog_json 生成,让每个模型可以独立声明上下文窗口,切换模型即生效。

用法

在「供应商配置 → 配置模型」或「模型列表」里,给模型名加后缀即可:

deepseek-v4-flash[1M]
glm-5.2[1M]
kimi-k2.6[262K]
minimax-m3[1M]

支持的后缀格式:

写法 含义
[1M] / [1m] 1,000,000 tokens
[200K] / [200k] 200,000 tokens
[1000000] 纯数字

不写后缀的模型继续使用 Codex 默认长度(272K)。

实现原理

采用 codex 原生 model_catalog_json 机制(与 cc-switch 思路一致):

  1. CodexPlusPlus 解析模型列表里的后缀,得到每个模型的 slugcontext_window
  2. 应用 profile 时,生成 <home>/model-catalogs/<profile-id>.json
    • 用 codex 内置 bundled entry 做模板,补齐字段;
    • 覆盖 slugdisplay_namecontext_windowmax_context_windoweffective_context_window_percent=100
    • 无后缀的模型回退到 profile 顶层的 context_window
  3. config.toml 写入 model_catalog_json = "model-catalogs/<profile-id>.json"
  4. codex 客户端启动时读取 catalog,按当前 model 匹配对应窗口。

关键修复

之前存在的 bug

后缀会原样泄漏到 config.tomlmodel 字段:

model = "deepseek-v4-flash[1M]"

codex 本身不理解 [1M],会按这个带后缀的字符串去 catalog 里匹配 slug。catalog 里生成的 slug 是剥离后缀的 deepseek-v4-flash,于是匹配失败,codex 回退到内置 272K 窗口,导致在 ~245K 就触发自动压缩(用户报告的现象)。

本次修复

  • 后端complete_relay_profile_config 写入 model 前调用 parse_model_suffix 剥离后缀。
  • 前端:保存与 config.toml 预览 时也剥离 model 后缀;界面输入框仍保留后缀,方便用户识别配置。
  • 测试:新增回归测试覆盖 profile.model 带后缀、config_contents 带后缀、无后缀条目不生成 catalog、用户手写 model_catalog_json 不被覆盖等场景。

主要改动

后端(Rust)

文件 说明
crates/codex-plus-core/src/model_suffix.rs 新增 parse_model_suffixcollect_catalog_entriesbuild_model_catalog_json,处理后缀解析与 catalog 生成
crates/codex-plus-core/src/relay_config.rs apply_model_catalog_to_config 接入 apply 流程;complete_relay_profile_config 写入 model 前剥离后缀
crates/codex-plus-core/src/model_catalog.rs 复用现有读取能力,catalog 路径自动处理
crates/codex-plus-core/examples/generate_model_catalog.rs 手工验证工具
crates/codex-plus-core/tests/model_suffix.rs 9 个单元测试
crates/codex-plus-core/tests/relay_config.rs 新增 catalog 生成、后缀剥离、兼容性回归测试
crates/codex-plus-core/assets/codex-models.json bundled catalog 模板

前端(React + TS)

文件 说明
apps/codex-plus-manager/src/App.tsx 配置模型 / 模型列表 placeholder 与 hint 增加后缀语法说明;保存时剥离 model 后缀;预览显示剥离后的 model

文档

  • docs/plans/:阶段一实现计划、前端 suffix 提示设计文档
  • docs/research/:A/B 对拍、字段/路径/副作用调研
  • docs/specs/:验证记录(Ark 端点大上下文验证、前端 suffix 提示验证)

验证

  • cargo test -p codex-plus-core --test relay_config --test model_suffix --test model_catalog --test relay_switch:85 + 9 + 6 + 4 = 104 个测试通过
  • cargo test -p codex-plus-manager:39 个测试通过
  • 手工验证:火山引擎 Ark 端点 deepseek-v4-flash[1M] / kimi-k2.6[262K] / minimax-m3[1M],上下文可超过 245K 继续消耗

兼容性

  • 不写后缀的模型保持现有 per-profile 行为,不生成 catalog。
  • 用户已在 config.toml 手写 model_catalog_json 时不会被覆盖。
  • 完全基于 codex 原生字段,不引入非标改动。

后续可扩展

- AGENTS.md: fork 工作规范(安全/编码/测试/上游同步)
- docs/research/01-调研结果.md: cc-switch 机制、CodexPlusPlus 源码根因、BigPizzaV3#931 验证结论
- docs/specs/2026-06-23-model-catalog-prototype-design.md: 阶段一原型设计(后缀语法+catalog 生成)
- docs/plans/三阶段计划.md: 原型验证→后端完整→前端全栈 PR

对应 issue BigPizzaV3#1171 / BigPizzaV3#931
Mac 上是 /usr/local/bin/codex(codex-cli 0.128.0),非 .exe。
BigPizzaV3#931 的 exe 表述是 Windows 用户语境,统一改为 codex 客户端。
6 个任务:后缀解析器→条目收集与JSON构建→apply接入3入口→兼容回归测试→
example工具→A预检+B对拍实跑验证。带结果验证与测试方案,自检通过。
实现 parse_model_suffix、collect_catalog_entries、build_model_catalog_json,
解析 deepseek-v4-pro[1M] 这类后缀语法并生成 codex 原生 catalog 格式。
当前 model 在 model_list 中存在同名后缀条目时采纳其窗口值,避免 catalog 中当前模型窗口丢失。
新增 apply_model_catalog_to_config,接入 3 个 apply 入口(limits 之后、落盘之前)。
有后缀条目才生成 catalog + 写相对路径指针;无后缀 no-op;用户手写指针不覆盖。
无后缀 no-op、用户手写指针不覆盖;确认现有 does_not_write / preserves 用例未破坏。
codex 0.128.0 对 model_catalog_json 字段要求严格,最小字段集报
missing field supported_reasoning_levels/base_instructions 等。
改为 compile-time 引入 assets/codex-models.json 作为 template,
生成每条 entry 时 clone 模板再覆盖关键字段,并显式写
effective_context_window_percent=100 以显示真实窗口。

B 对拍已通过:deepseek-v4-pro=1000000、claude-sonnet-4=200000。
原 or_insert 只记录首次出现的后缀,若同名条目先无后缀后有后缀,
current_model 无法拿到正确窗口。改为 insert 让靠后声明的后缀生效,
并补充覆盖该场景的测试。
问题:用户在「配置模型」或「模型列表」中使用 deepseek-v4-flash[1M] 等后缀语法时,
后缀会原样写入 config.toml 的 model = 。codex 按原串匹配 model_catalog_json 里的 slug,
导致匹配失败并回退到内置 272K 窗口,从而在 ~245K 就触发自动压缩。

修复:
- 后端:complete_relay_profile_config 写入 model 前剥离后缀,保证 catalog slug 与 model 一致
- 后端:新增回归测试覆盖 profile.model / config_contents 带后缀的场景
- 前端:保存与预览 config.toml 时也剥离 model 后缀,界面输入框仍保留后缀便于用户识别
- .gitignore:忽略 pnpm-lock.yaml / pnpm-workspace.yaml
@chenbai365

Copy link
Copy Markdown

👋 感谢贡献!此 PR 当前存在 合并冲突,无法直接合并。
请 rebase 到最新的 main 分支并解决冲突后重新推送。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 按模型粒度配置上下文窗口与自动压缩阈值(参考 cc-switch 的 model_catalog_json 机制)

2 participants