把 DeepSeek 接进 Codex:从双后端切换到“GPT 主控、DS 执行”

这次折腾最初只是为了给 Codex 安装一个可直接调用的 PowerShell 7,最后却一路延伸成了 OpenAI 与 DeepSeek 双后端、对话继承、独立 worker 和 Token 成本的完整测试。

开始时的想法很简单:保留 Codex 原有的 ChatGPT 登录、项目和历史记录,同时增加一套 DeepSeek 入口。需要复杂分析时继续用 GPT,大量重复工作交给 DS;如果 GPT 额度耗尽,还能让 DS 在同一个工作区接手。

真正做下来后发现,这件事不能简单理解为“在模型下拉框里多放两个名称”。模型目录、provider、对话元数据和 GUI 进程是几层不同的状态。只改其中一层,界面可能看起来已经切换,实际请求却仍然发往原来的服务。

本文记录最后走通的方案,以及中间几次比较有代表性的失败。


一、环境与目标

这次配置时的环境如下:

  • Windows 版 Codex GUI:OpenAI.Codex 26.924.2738.0
  • Codex CLI:0.158.0-alpha.2.1
  • PowerShell:7.6.6
  • CODEX_HOME:C:\Users\Admin\.codex
  • 原默认模型:gpt-5.6-sol

PowerShell 7 安装完成后,pwsh 已能从系统路径直接启动。接下来希望实现四个入口:

1
2
3
4
codex-gpt       OpenAI CLI
codex-ds DeepSeek CLI
codex-gpt-gui OpenAI GUI
codex-ds-gui DeepSeek GUI

同时有几个前提:

  • 不破坏原有 ChatGPT 登录和历史对话
  • GPT 仍是默认后端
  • OpenAI 与 DeepSeek 共用项目目录、MCP、插件和权限配置
  • DeepSeek Key 不写进普通命令参数,也不在日志中输出
  • 所有配置修改可回滚,并且切回 GPT 后应恢复原状态

二、第一版双后端配置

初版方案使用了这些文件:

1
2
3
4
5
C:\Users\Admin\.codex\config.toml
C:\Users\Admin\.codex\deepseek.config.toml
C:\Users\Admin\.codex\models.json
C:\Users\Admin\.codex\bin\switch-codex-backend.ps1
C:\Users\Admin\.codex\bin\invoke-codex-cli.ps1

思路是把 DeepSeek 注册为一个 Responses API provider,并通过 DEEPSEEK_API_KEY 读取认证信息。模型目录中加入:

1
2
deepseek-flash
deepseek-v4-pro

CLI 入口通过 profile 区分;GUI 没有同等稳定的 profile 启动参数,因此切换脚本只修改 config.toml 中受管理的顶层模型字段,再重启同一个 Codex App。

配置本身并不复杂,麻烦主要出在 Windows 的几处细节上。


三、切换脚本踩到的三个坑

1. PowerShell 固定大小集合

第一次输入 Key 后,脚本在向 .env 追加内容时失败:

1
使用“1”个参数调用“Add”时发生异常:“集合的大小是固定的。”

原因是 Get-Content 的结果虽然被转换成了列表类型,但得到的仍是固定大小集合,不能直接调用 Add()。

最后改为显式创建可变列表,再逐行加入原内容:

1
2
3
4
5
$lines = [System.Collections.Generic.List[string]]::new()

foreach ($existingLine in Get-Content -LiteralPath $EnvPath -Encoding UTF8) {
$lines.Add([string]$existingLine)
}

修复后,Key 才能在隐藏输入后正常保存。测试用的占位 Key 也在验证结束后确认没有残留。

2. WindowsApps 内的 EXE 无法直接启动

第二个错误发生在重启 GUI 时:

1
Start-Process:拒绝访问

直接执行 C:\Program Files\WindowsApps 下的 ChatGPT.exe 会受到应用包权限限制。最终没有继续硬调 EXE,而是根据 Appx 包信息取得 AUMID,再通过系统注册的应用入口启动:

1
shell:AppsFolder\<PackageFamilyName>!App

这比写死 WindowsApps 下的版本目录可靠,也不会在应用升级后立即失效。

3. Windows PowerShell 找不到 Codex CLI

后来从新开的 PowerShell 窗口执行 codex-ds-gui,又出现:

1
无法将“codex”项识别为 cmdlet、函数、脚本文件或可运行程序

Codex 桌面应用自己的执行环境里有 CLI 路径,不代表外部启动的 Windows PowerShell 也继承了同一套 PATH。

最终在脚本中增加了逐级解析:

  1. 优先使用显式设置的 CODEX_CLI_PATH
  2. 尝试 Get-Command codex.exe
  3. 在 Codex 本地安装目录中查找最新的 codex.exe

这三个问题修完后,GPT → DS → GPT 的配置往返已经能通过严格检查,切回后主配置也能逐字节恢复。

但真正影响使用的故障还在后面。


四、最隐蔽的问题:模型名称切了,provider 没切

DeepSeek 模型出现在 GUI 下拉框以后,我在原来的 GPT 对话中选择 deepseek-flash,结果请求直接失败:

1
The 'deepseek-flash' model is not supported when using Codex with a ChatGPT account.

切回 GPT 模式后,问题更奇怪:

  • 某些旧对话仍然能正常请求 GPT
  • 模型选择器里却只剩 DeepSeek
  • 一旦在旧对话里选过 DS,就没有 GPT 选项可以切回

检查本地状态后,才发现当时混在一起的是三件事:

状态 决定什么 当时的实际值
models.json 下拉框显示哪些模型 只有 DeepSeek
对话 turn_context 当前选择的模型名 deepseek-flash
对话 session_meta 请求发往哪个 provider openai

因此界面显示“已经选中 DeepSeek”,实际组合却是:

1
2
3
OpenAI / ChatGPT provider
+
DeepSeek 模型名称

请求根本没有到达 DeepSeek API,自然也和 Key 是否正确无关。

这次排查得到的关键结论是:

模型下拉框里出现了某个名称,不代表当前对话已经切换到对应的 provider。


五、正确的 GPT / DS 切换方式

修复后的规则变得很明确。

GPT 模式

切回 GPT 时,移除 DeepSeek 专用的:

1
2
model_provider = "deepseek"
model_catalog_json = ".../models.json"

让 Codex 重新使用内建 OpenAI provider 和 GPT 模型目录。

DS 模式

进入 DS 时,再启用:

  • DeepSeek provider
  • DeepSeek 专用模型目录
  • 对应 API Key 环境变量

并在这个状态下创建新的 DS 对话。

OpenAI 与 DeepSeek 可以共用同一个 CODEX_HOME、工作区、插件和侧栏数据库,但一条已经创建的对话仍带有自己的 provider 元数据。最稳妥的方式不是在原 GPT 对话里直接点选 DS 模型,而是在正确的后端状态下新建对话。

实际 API 最小测试也已经通过:

1
2
3
4
model: deepseek-v4-pro
provider: deepseek
response: DS_OK
exit code: 0

这次可以确认请求确实到达了 DeepSeek,而不是仅仅在界面上改了模型名。


六、对话记录如何继承

完成双后端切换后,下一个问题是:DS 能不能继续 GPT 的现有工作?

这里需要区分“工作区”和“聊天记录”。

同项目新建 DS 对话

新对话可以直接读取同一个项目中的:

  • 代码和文档
  • AGENTS.md
  • Git 提交和当前 diff
  • 测试结果
  • 交接文件

但它不会自动拥有旧 GPT 对话里的全部聊天内容。

这其实是最稳妥的日常接班方式。项目的真实状态本来就应该留在文件和 Git 中,而不是只存在于几十轮聊天里。

Fork 原对话

如果关键背景确实只存在于聊天中,可以 fork 原 thread。这样会复制完整历史并创建一个新的对话 ID,再把新对话绑定到 DeepSeek provider。

实际测试中,GPT 可以读取 DS 对话,DS 也可以读取 GPT 对话;fork 后的 DS 对话同样保留此前的上下文。

它更像是:

1
复制记忆,另开一条时间线

原对话不会被覆盖,fork 之后两边也不会自动同步。

Resume 原对话

CLI 可以尝试用另一 profile resume 原 thread,并保持同一个对话 ID。实际探测时,Codex 成功读取了原历史,也显示目标模型已经变成 DeepSeek,但因为该对话仍被桌面应用占用而停止:

1
This conversation is open in another app

彻底退出 GUI 后可以继续测试,但这种方式还会受到 provider 元数据、模型选择器和客户端锁的影响,不适合作为长期流程。

所以最终选择是:

  • 日常接班:同项目新建 DS 对话,读取交接文件
  • 必须继承聊天:fork
  • 原 ID 的 resume --profile:只用于应急或实验

七、内置子代理不能直接跨到 DeepSeek

配置做到这里时,我原本希望 GPT 主对话直接生成一个使用 DeepSeek 的内置子代理。

实际测试结果是否定的。即使本地自定义 agent 指定了 DeepSeek,ChatGPT 账户的编排通道仍会拒绝第三方模型:

1
The 'deepseek-flash' model is not supported when using Codex with a ChatGPT account.

这和前面的旧对话错配看起来很像,但发生层级不同:这次是内置 worker 的创建仍然经过 ChatGPT/OpenAI 路由,不能仅凭一个本地 agent 配置就跨到第三方 provider。

因此没有保留一个“配置上存在、实际必然报错”的 DS 子代理,而是改成独立进程:

1
codex-ds-worker

它固定使用:

1
2
3
model: deepseek-flash
provider: deepseek
reasoning effort: high

GPT 主控通过命令行把边界明确的任务一次性交给这个 worker。它和主代理共享工作区,但不经过内置子代理的跨 provider 路由。

最小真实调用返回:

1
DS_WORKER_OK

到这里,“GPT 主控 + DS 执行”才算真正可用。


八、目前采用的协作模式

日常使用时,GPT 负责:

  • 理解需求和拆分任务
  • 架构、风险与范围判断
  • 高风险或不确定修改
  • 审查 DS 的实际 diff
  • 运行最终验证并整合结果

DS worker 负责:

  • 批量修改同类文件
  • 补测试和注释
  • 机械性重构
  • 搜索调用点和整理影响范围
  • 修复成批的 lint / type errors
  • 运行测试并归纳失败项

典型调用方式如下:

1
codex-ds-worker exec --ephemeral -C "C:\项目目录" "读取 AGENTS.md。只修改 tests 目录,为现有 service 补充缺失的单元测试,不修改生产代码。完成后运行测试,并报告修改文件、测试结果和遗留问题。"

--ephemeral 适合一次性的批量工作,不额外保存长期聊天记录。如果任务很长、需要保留 DS 对话,则去掉这个参数。

这里真正重要的不是“把任务交给 DS”这句话,而是同时限定:

  • 可以修改哪些文件或目录
  • 哪些行为不能改变
  • 用什么命令验收
  • 最后必须返回什么
  • 是否允许提交 Git commit

此外不要让 GPT 和 DS 同时修改同一批文件。需要并行写入时,应拆到不同模块或独立 worktree;否则顺序执行更省事。


九、DS 是否真的能节省 GPT Token

答案是可以,但并不是只要调用一次 DS 就会节省。

我做过一次“让 DS 设计骑自行车的鹈鹕,再生成图片”的测试。链路是:

1
2
3
4
5
6
7
GPT 主控
↓
DS worker 生成画面方案与提示词
↓
GPT 调用专用图像工具
↓
图像模型生成最终图片

这次 DS 只使用了 527 token,而 GPT 所在对话因为历史很长,多次工具往返累计了大量上下文输入,其中绝大部分虽然命中缓存,仍然会被统计。最终图片也是专用图像模型生成的,DS 只负责提示词,并没有直接输出 PNG。

这个测试证明协作链路可用,却不是一个省 Token 的好例子。为了让 DS 写一小段提示词,GPT 仍然要理解需求、调用 worker、读取结果、调用图像工具并汇总,多了一层协调成本。

真正适合委派的,是能让 DS 独立完成一整块工作的任务。例如:

  • 修改几十个结构相似的文件
  • 完成一批测试并自行运行验证
  • 扫描大范围调用点并形成结果文件
  • 按既定规则完成机械迁移

比较理想的节省方式是:

  1. GPT 一次完成分析和任务定义
  2. DS 一次读取、修改并验证
  3. GPT 最后只检查 diff 和测试结果

如果 GPT 与 DS 反复来回讨论,或者 DS 只负责很小的一步,通常省不到什么,甚至可能更耗。


十、额度耗尽后的交接

如果 GPT 额度已经完全用完,就不能再依赖 GPT 去创建或调度 DS worker。更可靠的做法,是平时就把项目状态写入工作区:

1
2
3
4
5
AGENTS.md             长期规则
PROJECT_HANDOFF.md 当前进度与关键决策
TASKS/*.md 边界明确的具体任务
Git diff / commits 已完成的真实修改
测试结果 可复查的验收证据

然后在同一个项目中新建 DS 对话,先让它读取这些内容和 Git 状态,再继续工作。

只有关键上下文尚未整理、确实全部留在聊天记录中时,才有必要 fork 完整对话。长历史不仅成本更高,其中的旧工具输出、图片和临时判断也未必都值得交给另一个模型重新理解。


十一、托盘图标与进程退出

最后还顺手处理了一个容易误判的问题:切换后,Windows 托盘里的 Codex 图标会在鼠标移上去时突然消失。

原因不是托盘菜单坏了,而是旧切换脚本关闭主窗口后只等待 8 秒,发现还有 ChatGPT.exe 就强制结束全部进程。Explorer 留下了没有对应进程的“幽灵图标”,直到鼠标经过时才刷新掉。

修复后改为:

  • 优先请求应用正常退出
  • 最多等待 30 秒,让它保存状态并关闭辅助进程
  • 不再自动执行强制终止
  • 如果应用仍未退出,停止切换并提示手工处理

这部分和模型无关,但也说明 GUI 切换不能只考虑配置文件。一个能成功改写 TOML 的脚本,如果没有正确处理进程生命周期,依然不算可靠。


十二、总结

这次配置最后形成了两条不同的路径:

1
2
完整后端切换:codex-gpt-gui / codex-ds-gui
任务级委派: GPT 主控 → codex-ds-worker → GPT 验收

前者用于人直接进入某个 provider 的完整对话环境;后者用于在 GPT 项目中,把重复、范围明确、可以独立验证的工作交给 DS。

最值得记住的几点是:

  1. 模型名称、模型目录、provider 和 thread 元数据不是同一个状态。
  2. 已有 GPT 对话不能因为在下拉框中选择 DS,就自动变成 DeepSeek 对话。
  3. 工作区可以共享,对话历史要通过交接文件或 fork 继承。
  4. 当前内置子代理不能可靠地跨到第三方 provider,独立 worker 更实际。
  5. DS 只有承担完整的大块执行工作时,才真正有机会节省 GPT Token。
  6. 无论使用哪个模型,最终都要以 diff、测试和可回滚状态作为验收依据。

一开始我想解决的是“怎么在 Codex 里切换两个模型”,最后得到的答案却是:

模型切换解决的是谁来继续对话;worker 委派解决的是谁来完成工作。

把这两件事分开之后,整个流程反而简单了。


把 DeepSeek 接进 Codex:从双后端切换到“GPT 主控、DS 执行”
http://example.com/2026/10/03/20261003-codex-gpt-deepseek-workflow/
作者
Caleb
发布于
2026年10月3日
许可协议