如果你在 2025 年之后才开始用 AI 编程助手,大概率经历过这样一幕:网上教程让你装一个本地代理,把 Responses API 的请求翻译成 Chat Completions,再转发给国内某个模型。代理一挂,整个 Codex CLI 就报错;代理端口变了,你得回来改配置;代理升级了,Codex 又读不懂它吐出来的数据流。这套链路脆弱得像用胶带粘起来的积木。
而 DeepSeek 的这条接入路径,恰恰是目前少数能把这个中间层整个拿掉的方案——它的 API 原生支持 Responses API。这意味着 Codex CLI 发出的请求,DeepSeek 能原样接收、原样回应,**不需要任何协议转换**。这不是”兼容性更好一点”的改良,而是架构层面的减法。
这篇文章要做的,是把这条路径从准备、配置、验证到排错,完整地拆开。重点不在”抄一段配置能跑”,而在”每条报错发生时,你能说出它为什么发生”。因为能跑通的人很多,能自己修的人很少,而这中间的差距,就是这篇文章的全部价值。
一、先理解链路:Codex CLI 为什么能直连 DeepSeek
在动手改任何一个字之前,你需要在脑子里建立一张图。这张图画错了,后面所有的配置都是盲改。
1.1 Codex 与 Responses API 的关系
Codex 是 OpenAI 推出的编程助手,它的工作方式不是”一问一答”,而是”给一个目标 → 自己规划步骤 → 调用工具(读写文件、跑命令、搜索代码)→ 观察结果 → 继续”。支撑这种多轮工具调用循环的,是 Responses API。
Responses API 和老的 Chat Completions 在协议形态上完全不同。Chat Completions 是”messages 数组进、一条 message 出”,工具调用要在一轮里塞进 tool_calls 字段;而 Responses API 是”输入条目流进、事件流出”,有自己的 response.output_item 事件体系、自己的推理内容承载字段、自己的一套状态指代(如 previous_response_id)。
简单说:Chat Completions 是回合制的,Responses API 是流式事件驱动的。Codex CLI 当前版本说的一直是后一种语言。
1.2 为什么大多数国内模型需要代理,而 DeepSeek 不需要
这里就是分水岭。大多数国内模型厂商的 API 只实现了 Chat Completions 接口规约——因为它们最早对标的就是 OpenAI 的那个老接口,生态、SDK、文档全是围绕它建的。当你把 Codex CLI 指向这类模型时,Codex 说的是 Responses,模型只听懂 Chat Completions,鸡同鸭讲。
解决办法只有一个:在中间插一层代理。代理对外伪装成 Responses API 服务端,收到请求后拆解成 Chat Completions 格式转给模型,拿到结果再重新组装成 Responses 事件流吐回来。这个代理可以是本地跑的 Node 或 Python 进程,也可以是某个开源网关。
| 对比维度 | 走代理转换的路径 | DeepSeek 原生 Responses 路径 |
|---|---|---|
| 协议链路 | Codex CLI → 本地代理 → Chat Completions → 模型 | Codex CLI → Responses API → 模型 |
| 需要额外进程 | 需要,且必须常驻 | 不需要 |
| 推理内容透传 | 依赖代理实现,常丢失 reasoning 字段 | 原生支持,可开启原始推理显示 |
| 工具调用可靠性 | 字段映射易出错,偶发参数截断 | 协议一致,无需映射 |
| 故障排查范围 | Codex + 代理 + 模型三层 | Codex + 模型两层 |
| 升级维护成本 | Codex 升级后代理常需跟着改 | 跟随官方文档调整即可 |
最后一行是很多人忽略的。Codex CLI 迭代很快,Responses 协议本身也在演进。代理层的实现者要不断追着上游改字段映射,而你作为使用者,只能等代理作者更新。原生支持则把这个问题交还给官方,你只需要照着官方最新文档调参数。
把上面两点合起来看,就能理解为什么很多人在 Codex CLI 上反复出错:他们以为自己在调一个”API 客户端”,实际上在调一个”有自己协议、有自己模型目录、有自己客户端生态的编程助手”。这个助手对配置的读取是有约定有假设的,你不满足它的假设,它就用默认值兜底,而默认值往往不会给你任何提示。
1.3 一份配置,三个客户端
还有一个结构性事实值得单独说:Codex 的所有客户端共享同一份配置文件。Codex CLI、ChatGPT 桌面应用、VS Code 里的 Codex IDE 扩展,读的都是 ~/.codex/config.toml(Windows 下是用户目录下的 .codex\config.toml)。
这个设计的直接后果是:你只要在 CLI 里配通一次,桌面应用和 VS Code 扩展天然就通了,不需要重复设置。反过来说,如果你只在桌面应用里点了半天,发现没有新模型,那很可能是因为配置文件根本没写对——而 CLI 才是那个能让你看到真实报错的地方。这个因果关系会在后面的报错章节反复出现。
1.4 这次接入涉及的模型与文件
本文涉及两类模型条目:deepseek-flash(也接受图片输入)和 deepseek-v4-pro。涉及三个文件:~/.codex/config.toml(主配置)、~/.codex/models.json(模型目录,声明上下文窗口、推理档位、工具调用格式等元数据)、以及脚本自动生成的 ~/.codex/backup-deepseek/ 备份目录。
需要提前打个预防针:模型名、推理档位的取值、脚本菜单编号,都可能随官方文档和脚本版本变化。本文给出的参数以目前官方文档为准,遇到不一致时,以官方最新文档为唯一参照。这不是免责声明式的敷衍,而是这套生态的真实节奏。
二、前置准备:动手之前必须确认的四件事
绝大多数”配置完还是不行”的案例,根因都在准备阶段,而不是配置阶段。花五分钟把这些确认掉,能省掉后面两小时。
2.1 确认 Codex CLI 已安装且至少启动过一次
这是使用一键脚本的硬前提。为什么?因为脚本要修改的是 ~/.codex/config.toml,而这个目录是 Codex 首次启动时才会创建的。如果你刚装完 Codex CLI 还没运行过,目录不存在,脚本要么直接失败,要么给你写到一个后来被覆盖的位置。
验证方式很朴素:跑一次 codex,让它自己把目录建出来,然后退出。或者直接检查目录是否存在。
# macOS / Linux:确认配置目录已存在
ls -la ~/.codex/
# 如果不存在,先启动一次 Codex CLI 再退出
codex
# 首次启动可能要求登录,这里不需要真的登录成功,
# 只要 ~/.codex 目录被创建出来就满足前提
Windows 用户对应路径是 $env:USERPROFILE\.codex。同样地,用 PowerShell 检查一下即可。
2.2 确认 Codex CLI 版本
Codex CLI 当前版本使用的是 responses 协议,这一点很关键——它意味着你没法通过环境变量把它切回 Chat Completions。有些旧教程会教你设一个环境变量让 Codex 走老接口,在当前版本下那是无效的。先确认版本,避免照着过时教程走冤枉路。
codex --version
如果版本很老,建议先升级到当前版本再按本文配置。老版本对 model_catalog_json、enabled-reasoning-efforts 这些字段的识别行为可能不同,容易产生”配置写了但不生效”的错觉。
2.3 准备好 DeepSeek API Key
API Key 以 sk- 开头。请提前从 DeepSeek 控制台获取,并且确保它就是你打算长期用的那把。这里有个建议:不要用临时测试用的 Key 去做一次”能跑通就行”的验证。跑通之后你大概率会直接切到日常工作流,临时 Key 过期那天你会以为是配置出了问题。
另一个细节:如果你之前已经把 Key 放在 shell 环境变量里(比如 export DEEPSEEK_API_KEY=...),要清楚这个环境变量的作用域。它只存在于当前这个 shell 会话及其子进程中。开一个新终端窗口,它就没了。后面讲认证失败时,这会是最常见的根因之一。
2.4 确认网络与账户额度
确认你的机器能访问 api.deepseek.com,且账户有可用额度。这一条听起来像废话,但它能区分”真配置错误”和”看起来像配置错误”。很多时候一条 401 或 404 并不会告诉你真正原因,先排除掉网络和余额这两个外部变量,剩下的才值得深挖。
动手前用五分钟过一遍检查清单
[ ] 已安装 Codex CLI
[ ] 已运行过至少一次 codex 命令(~/.codex 目录存在)
[ ] codex --version 显示为当前版本
[ ] 已获取以 sk- 开头的 DeepSeek API Key
[ ] 能访问 https://api.deepseek.com/
[ ] 账户额度充足
[ ] 当前 shell 会话中记下了 API Key 的来源(文件 or 环境变量)
最后一条尤其值得强调。你要随时能回答”这个 Key 到底从哪来、谁在什么时候读它”,否则排错时就会陷入”我明明设了啊”的循环。
三、方式一:一键脚本,官方推荐的稳妥路径
官方给了一键脚本,而且明确推荐。很多人对一键脚本有本能抵触——”不知道它改了我什么”。这个警惕是对的,但这次的脚本恰好设计得相对克制,理解它做了什么,你才能放心用,也才能在它出问题时自己判断。
3.1 脚本命令与平台差异
macOS / Linux 用户执行:
bash <(curl -fsSL https://cdn.deepseek.com/api-docs/codex-deepseek-setup-en.sh)
Windows 用户在 PowerShell 中执行:
irm https://cdn.deepseek.com/api-docs/codex-deepseek-setup-en.ps1 | iex
两个命令都是"下载即执行"的形态。curl -fsSL 里,-f 让 HTTP 错误不输出到文件,-s 静默,-S 出错仍显示错误,-L 跟随重定向;bash <(...) 是把下载内容当脚本喂给 bash。irm ... | iex 是 PowerShell 的等价写法:irm 是 Invoke-RestMethod 的别名,iex 是 Invoke-Expression。
3.2 菜单选项怎么选
脚本启动后会给你一个菜单,核心是三项:
- 选 1:配置为
deepseek-flash,同时支持图片输入。这是大多数人的日常选择——速度快,能看截图。 - 选 2:配置为
deepseek-v4-pro。适合需要更强推理的复杂任务。 - 选 9:恢复默认 Codex 配置。这是个逃生舱,你随时能回退。
首次运行会要求你输入 API Key。这个交互设计值得注意——脚本没有让你提前 export 环境变量,而是当场读取并直接写进配置。这从根上规避了"环境变量只存在于另一个 shell"的问题。
3.3 脚本到底改了什么:五个动作逐条拆
这是整节最重要部分。知道脚本动了哪些文件、按什么顺序动,你才能在出问题时定位到具体环节。
动作一:备份现有配置
脚本会先把 ~/.codex/config.toml 备份到 ~/.codex/backup-deepseek/。这一步的价值在你需要回退时才真正体现,但它也意味着:反复运行脚本会产生多个备份。如果你发现 backup 目录里有七八份文件,说明你已经跑过很多次了,这时候应该停下来想想,而不是再跑一次。
动作二:写入模型目录 models.json
这个文件为什么不能省
因为它决定了 Codex CLI 对模型能力的判断。上下文窗口多大、支持哪几档推理、工具调用格式是什么,都从这里来。缺了它,Codex CLI 会用为 OpenAI 模型准备的内置默认值,对 DeepSeek 并不合适,于是你能用,但用得别扭。
脚本会把一份 models.json 写到 ~/.codex/models.json。这个文件不是可选的装饰品——它声明了每个模型的上下文窗口大小、支持的 reasoning effort 级别、工具调用格式等元数据。Codex CLI 和桌面应用靠它来决定"这个模型能干什么、该给它多大的上下文、该展示哪些推理档位"。
缺了这个文件,或者文件格式不对,就会出现后面报错表里那条"模型目录警告 / 元数据缺失"。很多人手动配置时只改 config.toml 不管 models.json,结果模型能出字,但推理档位选项是灰的。
动作三:修改 config.toml,只重写必要字段
脚本对 config.toml 的修改是"外科手术式"的:只重写必要字段,新增一个 [model_providers.deepseek] 段,并把 enabled-reasoning-efforts 写进已有的 [desktop] 段——注意,是写进已有段,不是新建第二个。
更关键的是:你已有的 MCP 服务器配置与项目信任设置全部保留。这一点非常重要。MCP 服务器是很多人的工作流核心(接了文件系统、数据库、浏览器),项目信任设置决定哪些目录可以自由读写。一个粗暴的脚本会把这些一键清空,而这个脚本不会。这正是它"推荐"的底气所在。
动作四:移除冲突字段并逐条打印原因
如果你的旧配置里存在与新配置冲突的字段,脚本会移除它们,并且逐条打印原因。请认真看这段输出,不要一路回车。它是你理解"为什么我之前的某个设置不见了"的唯一线索。
常见的冲突包括:指向老 provider 的 model、残留的 web_search = "live"、以及旧版脚本留下的模型条目。
动作五:写盘前校验语法
为什么"不改动任何文件"比"改对"更重要
因为改错之后你连报错都看不到了。配置解析失败时,Codex CLI 可能直接不启动,你失去了唯一的诊断通道。脚本选择整体中止,是在保护这条通道。
这一步是设计上的亮点:脚本在真正写盘之前,会校验 config.toml 和 models.json 的语法。校验失败则中止,且不改动任何文件。也就是说,脚本要么全成功,要么全不动,不存在"改了一半"的中间状态。
这个"原子性"很重要。想象一下:如果脚本先把 config.toml 改坏、再去生成 models.json 时崩溃,你手上就只剩一份坏配置和一个不完整的目录,Codex CLI 直接启动不了,连报错都看不到。
如果你在跑脚本时看到"已移除若干字段"的提示,先别慌。这不是脚本在"擅自删你的配置",而是它在把配置文件收敛到一套自洽的状态。把它的输出当成一份变更记录存档,将来出问题时可以对照。
3.4 旧版脚本残留的处理
如果你之前用旧版脚本装过 deepseek-v4-flash 或 deepseek-v4-flash-vision-exp 这两个条目,重跑脚本会把它们移除,只保留 deepseek-flash 与 deepseek-v4-pro。
为什么这个细节值得单独提?因为模型名变更本身就是一个隐蔽的故障源。你的旧配置里写着 deepseek-v4-flash,服务端已经不认这个名字了,你得到的报错却是"404 / 找不到模型",然后你开始怀疑 base_url、怀疑 Key、怀疑网络——唯独没想到是模型名过期了。脚本自动清理这类残留,本质上是在帮你消除一类"报错信息误导"。
| 对比项 | 方式一:一键脚本 | 方式二:手动配置 |
|---|---|---|
| 适用人群 | 希望尽快跑通、不想研究细节 | 需要精细控制或理解每一项 |
| 准备成本 | 低,只需跑过 codex | 中,需手写两个文件 |
| 自动备份 | 有,写入 backup-deepseek 目录 | 无,需自己先备份 |
| 保留 MCP 与信任设置 | 自动保留 | 需手工保留,易误删 |
| 语法预校验 | 有,失败不改动任何文件 | 无,写错就是写错 |
| 清理旧模型条目 | 自动 | 需自己检查删除 |
| 出错时的可读性 | 有逐条冲突说明 | 完全靠自己对照字段 |
| 可控性 | 受限于脚本版本 | 完全可控 |
| 适合排错场景 | 一般,黑盒多 | 最适合,每一步可见 |
我的建议是:第一次用脚本,第二次用手动。先用脚本把"能跑通"这个基线拿到手,再用脚本产出的配置文件当参照,手动重写一遍。这样做的好处是,当未来出现问题时,你知道每一行是干什么的。
四、方式二:手动配置,把每一行都握在手里
手动配置的价值不在于"显得高级",而在于它是唯一能让你在报错时知道该改哪一行的方式。下面按文件顺序来。
4.1 第一步:创建 models.json 模型目录
~/.codex/models.json 的内容应当与脚本写入的一致,包含 deepseek-flash 与 deepseek-v4-pro 两个条目。其中 deepseek-flash 的 input_modalities 需要包含 image,否则桌面端的图片输入能力不会激活。
这个文件的结构是"模型目录":一个对象数组,每个对象描述一个模型的元数据。最省事的做法是直接运行一次脚本,然后把生成的 models.json 复制出来作为模板——它由官方维护,比任何第三方文章里的示例都可靠。如果一定要手写,务必保证:
- JSON 语法严格合法(不允许尾随逗号、不允许注释)
- 模型 ID 显式写出,不要依赖任何"自动推断"
deepseek-flash的input_modalities中包含image- 两个条目的 ID 与
config.toml里model字段完全一致(大小写、连字符都不能差)
别把 input_modalities 当成"自动看图开关"
不少人以为只要在配置里写了某个模型支持图片,Codex 就会自动传图片。实际上"能传"和"会传"是两件事:input_modalities 声明的是能力边界,是一个能力声明;Codex 是否真的把图片作为输入项发出去,还取决于你交互时是否提供了图片、以及当前上下文是否允许。声明缺失只会导致能力不激活,不会导致请求被拒。
4.2 第二步:写完整的 config.toml
下面这份是完整可用的模板,可以直接复制后替换 API Key 使用。
model = "deepseek-flash"
model_provider = "deepseek"
preferred_auth_method = "apikey"
forced_login_method = "api"
model_reasoning_effort = "high"
web_search = "disabled"
show_raw_agent_reasoning = true
model_catalog_json = "~/.codex/models.json"
[model_providers.deepseek]
name = "deepseek"
base_url = "https://api.deepseek.com/"
wire_api = "responses"
experimental_bearer_token = "<your DeepSeek API Key>"
[desktop]
enabled-reasoning-efforts = ["low", "medium", "high", "xhigh", "ultra", "max"]
注意模板里的 experimental_bearer_token 用了转义写法,实际文件里就是普通的双引号字符串。复制时请把整个占位符替换成你的真实 Key,包括尖括号。
4.3 逐字段说明:每一项为什么存在
逐字段解释比"照抄能跑"重要,因为排错时你需要判断"这一行删掉会怎样"。
model 与 model_provider:一对必须对齐的键
model 是默认模型,决定你敲 codex 回车后实际调用谁。model_provider 必须与下面 [model_providers.xxx] 段的 id 完全一致——这里是 deepseek。这两个字段的关系像"表名和 schema 名":写错任何一个,Codex 就找不到该用哪个 provider 发请求。
这是认证失败报错的第一大根因,后文会详细展开。
preferred_auth_method 与 forced_login_method
这两个字段的作用是用 API Key 认证,跳过 ChatGPT 账号登录。preferred_auth_method = "apikey" 表明优先用 API Key,forced_login_method = "api" 则强制走 API 认证这条路,不去弹 ChatGPT 登录。
很多人卡在"启动后一直让我登录 ChatGPT 账号",就是因为漏了这两行。少了它们,Codex 会认为你想走订阅账号那条链路,而你的 DeepSeek Key 跟那个链路毫无关系。
model_reasoning_effort
推理强度,越高思考越深、响应越慢。这是一个纯体感参数,没有"正确值",只有"匹配场景的值"。后文有一张专门的档位选用表。
web_search
Codex 内置的联网搜索。在 DeepSeek 模型下必须设为 disabled。原因很简单:这个搜索能力依赖 OpenAI 侧的搜索服务,DeepSeek 的 API 不提供对应的原生搜索端点。你设成 live 或类似值,请求里就会带一个模型端不支持的能力声明,服务端直接拒。
show_raw_agent_reasoning
显示模型原始推理内容,仅 Codex CLI 生效,按 Ctrl + T 查看。这个开关对调试极有价值:当 Agent 走了弯路,你能看到它"当时在想什么",而不是只看结果猜原因。
注意它的作用域——桌面应用和 VS Code 扩展不认这个开关。这也是很多人疑惑"我开了怎么桌面端没反应"的答案:不是没生效,是这个开关本来就不在那两个客户端里生效。
model_catalog_json
它和 models.json 是什么关系
一个指路,一个给内容。model_catalog_json 告诉 Codex CLI "目录在哪",models.json 里才是真正的条目。两者缺一不可:路径写对了但文件没建,仍然报缺失。
自定义模型目录文件路径,指向刚创建的 ~/.codex/models.json。这行是把 config.toml 和 models.json 绑起来的绳子。漏掉它,Codex 会用内置目录,而内置目录里没有 DeepSeek 的条目,于是出现"元数据缺失"类告警。
wire_api 与 experimental_bearer_token
wire_api = "responses" 是最关键的一行。它表示走 Responses API。少了它或写成别的值,Codex 会退回 Chat Completions 并给出一条 warning——这是整个接入里最容易犯、也最容易忽略的错误。
experimental_bearer_token 让 API Key 直接写在配置文件里。这省掉了环境变量作用域的问题,代价是 Key 以明文落盘。请确保 ~/.codex/ 目录权限正确,且不要把这份配置提交到任何仓库。
base_url 必须带结尾斜杠
base_url = "https://api.deepseek.com/" 结尾的斜杠不是风格问题。Codex 在拼接具体端点时通常采用"基址 + 路径"的方式,如果基址缺少结尾斜杠,拼出来的路径就会缺一层分隔。这条是 404 报错的第一号元凶。
enabled-reasoning-efforts
桌面应用里可选的推理档位。它必须写在 [desktop] 段下。如果这个数组里列出了模型不支持的档位,桌面端的档位选择器会表现异常——要么灰掉,要么选不中。
4.4 那个必须避开的坑:重复的 [desktop] 段
这是手动配置里最经典的"看起来完全正确但就是不工作"的场景。
假设你的 config.toml 末尾本来已经有一段:
[desktop]
some-existing-key = "value"
然后你照着某篇教程,把新配置追加到文件底部,于是文件里出现了两个 [desktop]:
[desktop]
some-existing-key = "value"
# ... 中间是别的配置 ...
[desktop]
enabled-reasoning-efforts = ["low", "medium", "high", "xhigh", "ultra", "max"]
TOML 规范明确禁止同一张表中重复定义段头。解析器会直接报错,整个文件失效。注意是"整个文件失效",不是"后一个段覆盖前一个"。所以你看到的症状往往不是"档位选不了",而是 Codex CLI 一启动就报配置解析失败,或者干脆读不到任何配置、退回到默认值。
正确做法:找到那个已有的 [desktop] 段,把 enabled-reasoning-efforts 这一行加进去,而不是新建第二个。这也是为什么脚本要"写进已有的 [desktop] 段"——它在从结构上规避这个坑。
怎么快速判断自己有没有踩这个坑
一条命令就够:
# macOS / Linux
grep -n "^\[desktop\]" ~/.codex/config.toml
# Windows PowerShell
Select-String -Path "$env:USERPROFILE\.codex\config.toml" -Pattern "^\[desktop\]"
输出多于一行,就是问题所在。同样的方法也可以检查 [model_providers.deepseek] 有没有重复。
五、验证:三种方式确认配置真的生效了
配置写完不等于生效。这一步要做的不是"感觉应该行",而是拿到明确的证据。三个客户端各有各的证据要拿。
5.1 Codex CLI 的验证:看启动横幅
进入任意项目目录执行 codex。启动横幅上会显示 model: deepseek-flash(或你配置的模型名)。看到这一行,就说明配置被正确读取了。
cd /path/to/your/project
codex
如果横幅显示的还是默认的 OpenAI 模型,说明 model 或 model_provider 没被读到——往回查是不是文件路径不对、是不是有多份配置文件互相干扰、是不是 [model_providers.deepseek] 的 id 拼错。
5.2 桌面应用的验证:看模型选择器里的名字
桌面应用的模型选择器显示 "Custom" 或具体模型名(如 DeepSeek-Flash),即为生效。这里有个因果要理清:桌面应用本身不能"帮你"配好",它只是读配置。如果它显示不对,问题几乎必然在配置文件里,而不在桌面应用本身。
5.3 VS Code 扩展的验证:和 CLI 对一次答案
VS Code 的 Codex IDE 扩展同样读取同一份配置,无需单独设置。打开扩展面板,模型信息应与 CLI 一致。如果不一致,先检查你 VS Code 所在的用户环境和终端里的用户是否同一个——这一点在多用户机器或 WSL 混合环境下很容易出问题。
5.4 脚本化任务的验证
除了交互模式,Codex CLI 还能跑非交互任务,这是把它接进 CI 或自动化流程的关键:
codex exec --sandbox workspace-write "Fix the failing test and run it again"
这里的 --sandbox workspace-write 表示允许在工作区内写文件,但不越界到系统其他位置。跑这个命令能顺便验证两件事:模型调用通了、工具调用(读文件、改文件、执行命令)也通了。交互模式下你可能只是"聊明白了",非交互模式才是真正的端到端验收。
验证清单模板
[ ] 方式一:codex 启动横幅显示 model: deepseek-flash
[ ] 方式二:桌面应用模型选择器显示 Custom 或 DeepSeek-Flash
[ ] 方式三:VS Code 扩展显示的模型与 CLI 一致
[ ] 方式四:codex exec --sandbox workspace-write "..." 能正常返回并完成一次文件修改
[ ] 方式五:CLI 中按 Ctrl + T 能看到原始推理内容
[ ] 方式六:grep "^\[desktop\]" ~/.codex/config.toml 只输出一行
[ ] 方式七:~/.codex/backup-deepseek/ 中有本次配置之前的备份
[ ] 方式八:桌面端推理档位选择器可选(不是全灰)
八条里,第四条和第五条最容易被跳过,但它们价值最高。第四条证明整条链路(协议 + 工具调用 + 沙箱)都通了;第五条证明 reasoning 字段真的透传了,而不只是"文字能出来"。
六、报错对照表:七种症状的因果解剖
这一节是全文核心。我把它写成"现象 → 可能原因 → 为什么会这样 → 处理方式"的结构,因为只说"改成 X 就好",你下次遇到变体还是不会。
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 认证失败 | model_provider 与 provider 表不一致;API Key 没在同一个 shell 里 export / 写错 | 核对 provider id;确认 Key 在配置文件或同 shell 环境变量中 |
| 404 / 找不到模型 | base_url 没以 / 结尾或没指对;模型 ID 写错 | base_url 用 https://api.deepseek.com/;模型用 deepseek-flash |
| 退回到 Chat Completions 并给 warning | wire_api 没设成 responses | 改 wire_api = "responses" |
| TOML 解析失败 | 出现了两个 [desktop] 段 | 合并到一个 [desktop] 段 |
| 模型目录警告 / 元数据缺失 | models.json 缺失或格式不对 | 重新生成 models.json,用显式模型 ID |
| 联邦搜索报错 | 给不支持原生搜索的模型设了 web_search = "live" | 删掉该项或改为 disabled |
| 桌面应用没有新模型 | 没启动过 CLI / 配置未生效 | 先在 CLI 跑通,桌面端读同一份配置 |
6.1 认证失败:两个 id 的故事
为什么发生。Codex 在发起请求前需要两步定位:先根据 model_provider 的值,去 [model_providers.*] 下找到同名的那张表;再从表里取 base_url、wire_api、token。如果 model_provider = "deepseek",而实际表头写成了 [model_providers.deepseek-api],查找就失败。
关键在于失败后的表现:Codex 找不到 provider 表时,会退回一个默认认证路径,而那个路径需要的是 ChatGPT 账号凭证,不是你写着的 DeepSeek Key。于是你看到的报错是"认证失败",而不是"找不到 provider"。报错信息指向了错误的方向,这是这条错误最阴险的地方。
第二个根因是环境变量的作用域。export DEEPSEEK_API_KEY=... 只在当前 shell 会话有效。你在终端 A 里 export,然后在 VS Code 里打开的集成终端却是另一个会话,或者在桌面应用里操作——这些都读不到。更隐蔽的是:你 export 之后重启了终端,忘了重新 export。
怎么处理。分两步排查。第一,直接比对两个字符串:
# 查看 model_provider 的值
grep "^model_provider" ~/.codex/config.toml
# 查看 provider 表的段头
grep "^\[model_providers\." ~/.codex/config.toml
两个输出里的 id 必须逐字符一致。deepseek 和 DeepSeek 在 TOML 表名里是不同的键。
第二,把 Key 写进配置文件,而不是依赖环境变量。experimental_bearer_token 就是这个用途。它把认证信息从"运行时环境"变成了"静态配置",彻底消灭作用域问题。代价是明文落盘,请自行评估风险。
反常识:配置文件的 Key 往往比环境变量更稳
很多人出于安全考虑坚持把 Key 放环境变量。但在 Codex CLI 这个场景里,写进配置文件往往比放环境变量更可靠,也更少出事故。原因在于 Codex 有三个客户端,而环境变量是 per-process 的——你几乎不可能让 CLI、桌面应用、VS Code 扩展三个进程都拿到同一个环境变量。配置文件则天然共享。安全性的正确解法是控制文件权限,而不是把 Key 藏在一个读不到的地方。
6.2 404 / 找不到模型:一个斜杠和一个名字
为什么发生。两个完全不同的原因,会撞出同一种报错。
第一个是 base_url 少了结尾斜杠。Codex 拼接端点时通常拿 base_url 当"目录"来用,如果写成 https://api.deepseek.com(无斜杠),拼接结果可能变成 https://api.deepseek.comv1/responses 这类畸形地址——也可能只是路径少一层。无论哪种,服务端都会告诉你"没这个资源"。你看到的 404 其实是"路径不存在",却被误读成"模型不存在"。
第二个是模型 ID 写错,或者用了过期的模型名。比如 deepseek-v4-flash 这类旧条目,在服务端已不被认作有效模型。此时路径是对的,但请求落在了一个不存在的模型上,同样回 404。
怎么处理。先看 base_url 最后一个字符:
grep "^base_url" ~/.codex/config.toml
# 期望输出:base_url = "https://api.deepseek.com/"
确认结尾有斜杠。然后确认 model 用的是 deepseek-flash(或 deepseek-v4-pro),且这个 ID 在 models.json 里也有对应条目。两处 ID 必须一致。
鉴别技巧:404 到底是路径问题还是模型问题
有一个简单办法能区分。把 base_url 改成带斜杠的正确值,其他都不动,重试一次。如果报错从 404 变成了别的东西(比如 401 或正常返回),那就是路径问题;如果还是 404,那就是模型 ID 问题。这一招能省掉大量猜测。
6.3 退回 Chat Completions 并给 warning
为什么发生。wire_api 决定 Codex 用哪套协议说话。它没被设成 responses 时,Codex 会采用一个更保守的默认值。DeepSeek 的接口两边都认,所以请求不一定失败——但会产生 warning,并且功能会降级。
这里有个很多人误解的点:"能跑"不等于"跑对了"。退回到 Chat Completions 之后,你可能看到文字正常输出,以为万事大吉。实际上 reasoning 的承载方式变了,工具调用的字段映射也不一样了,长任务里会开始出现莫名其妙的工具参数丢失。
还有一个更硬的约束需要记住:Codex 当前版本使用 responses 协议,改环境变量无法切回 Chat Completions。也就是说,方向是单向的——你只能把 wire_api 设成 responses 让它走对,没法通过某些开关把它"顺滑地"切到老协议。那些教你设置环境变量切协议的教程,在当前版本下是过时的。
怎么处理。确认 [model_providers.deepseek] 段里有这一行:
wire_api = "responses"
注意引号和大小写。改完重启 Codex CLI 再看 warning 是否消失。
6.4 TOML 解析失败:重复段头
为什么发生。TOML 规范规定,同一张表只能声明一次段头。出现两个 [desktop],解析器就无从判断"后一个段里的键该并到前一个,还是覆盖它",于是选择直接失败。这是一个规范级的硬错误,不是 Codex 的宽松策略问题。
这类错误通常来自"追加式"的配置习惯:很多人改配置的方式是"把网上的配置复制到文件末尾",而不是"找到对应段落合并"。一旦目标段落已存在,就自动制造了重复。
除了 [desktop],[model_providers.deepseek] 重复也是同样的问题。有些人先手动写了一遍,又跑脚本追加了一遍,就撞上了。
怎么处理。用前面给的 grep 命令定位所有重复段头,手动合并。合并时要注意:同名的键只能保留一个值,要么删旧的,要么改新的。不要留两个不同的值,那同样会解析失败。
合并时该保留哪个值:一个可执行的优先级
如果两个 [desktop] 段里同一个键有不同的值,保留哪个?我的原则是:保留与新配置语义一致的那个。比如两个 enabled-reasoning-efforts,一个只有 ["low","high"],一个是完整的六档,保留完整的。因为前者往往是你"过去实验留下的残骸",后者才是你"现在想要的配置"。
6.5 模型目录警告 / 元数据缺失:Codex CLI 无从判断能力
为什么发生。Codex 需要知道每个模型的上下文窗口、支持的推理档位、工具调用格式。这些信息不在服务端下发,而在本地的 models.json 里。文件缺失、路径没写对、或者 JSON 语法不合法,都会导致元数据加载失败。
症状往往不是"用不了",而是"用起来怪怪的":推理档位选项缺失、上下文超限的报错来得比预期早、工具调用在某些场景下表现异常。因为 Codex 缺了这些参数,只能按内置默认值来——而内置默认值是为 OpenAI 模型设的,对 DeepSeek 并不合适。
怎么处理。最可靠的办法是重新生成:跑一次一键脚本,它会写入一份官方维护的 models.json,然后你把它复制出来保存。手动修的话,重点检查:
- 文件路径与
model_catalog_json里的值完全一致 - JSON 里没有注释、没有尾随逗号
- 模型 ID 是显式写出的(不要省略、不要依赖推断)
- 每个 ID 都能在
config.toml的model里找到对应
路径里的波浪号:一个不显眼的坑
model_catalog_json = "~/.codex/models.json" 里的 ~ 能否被正确展开,取决于 Codex 的读取实现。某些环境下它可能被当成字面量,导致去一个名为 ~ 的目录里找文件。如果你照着模板写了却始终报"文件不存在",试着写成绝对路径:
# 先看绝对路径是什么
echo $HOME/.codex/models.json
# 然后把 config.toml 里那行改成展开后的绝对路径
model_catalog_json = "/Users/yourname/.codex/models.json"
这不是一个"官方 bug"式的指控,而是一个稳妥的排查手段:当相对写法不确定时,绝对路径永远是最没歧义的。
6.6 联邦搜索报错:一次失败的能力协商
为什么发生。web_search 控制 Codex 是否启用内置联网搜索。这个搜索能力依赖 OpenAI 侧的搜索服务编排,而 DeepSeek 的 API 并不提供对应的原生搜索端点。当你把 web_search 设成 live(或类似的启用值),请求里就带了一个模型端无法兑现的能力声明。
这类错误的本质是能力协商失败:客户端以为服务端支持某个能力,服务端说"我这没有"。报错信息里会出现 search 相关的字眼,很容易被误判为"网络问题"。
怎么处理。删掉该项,或改为:
web_search = "disabled"
如果你的工作流确实需要联网,正确做法是用 MCP 接一个搜索工具,而不是依赖 Codex 内置的搜索开关。内置开关是"OpenAI 模型专享"的能力位,不要指望它在第三方 provider 上工作。
6.7 桌面应用没有新模型:Codex CLI 没跑通的前兆
为什么发生。这条的根因几乎总是同一个:配置没生效。而"没生效"又分两种情况——配置写错了,或者配置压根没被读到。
为什么推荐"先在 CLI 跑通"?因为 CLI 是唯一能把配置错误暴露出来的客户端。它会打印 TOML 解析错误、会提示 provider 找不到、会把 warning 直接刷在终端里。桌面应用则倾向于静默失败:模型列表里没有新条目,就只是没有,不告诉你为什么。
还有一个前置条件:没启动过 CLI,~/.codex 目录可能根本不存在,那你写配置文件时写在哪个目录下就成了问题。如果你的配置飘到了别的地方,桌面应用自然读不到。
怎么处理。顺序永远是:先在 CLI 里跑到启动横幅显示正确模型,再去开桌面应用。桌面端读的就是同一份配置,CLI 通了,桌面端必然通。如果桌面端仍然没反应,检查你是不是用不同用户身份运行的——配置是按用户目录走的。
桌面端缓存:一个值得试的经验做法
极少数情况下,桌面应用的模型列表有缓存,改完配置需要完全退出再启动(不是关窗口,是退出进程)。这只是经验做法,不是官方机制,但在你确认配置文件无误却依然看不到新模型时,值得试一试。
七、大多数人做错的地方
上面讲的是"怎么修"。这一节讲"为什么你总是在同一类地方翻车"。这些是我认为最值得单独拎出来说的判断。
7.1 错误一:把"能出字"当成 Codex CLI 接入成功
这是最普遍也最危险的一种。当 wire_api 没设对,Codex 退回 Chat Completions,你敲一句话,它答上来了,你长舒一口气说"成了"。然后第二天开始跑一个多步骤重构任务,发现 Agent 改到第三轮就忘了前面在干嘛,或者工具调用参数莫名其妙地少了一半。
我的判断是:验证 Codex CLI 接入,必须用 codex exec --sandbox workspace-write 跑一个真实任务,而不是闲聊一句。因为只有真实任务才会触发完整的工具调用链和 reasoning 透传,才会暴露协议层的降级。闲聊验证的是"能不能连上",不是"接得对不对"。
7.2 错误二:只在桌面应用里折腾
桌面应用看起来更友好,于是新手的第一反应是打开它、找模型设置、点来点去。但桌面应用是个"只读配置、不问原因"的客户端——它不会告诉你 TOML 哪里写错了。
正确的心智模型是:桌面应用是消费者,CLI 是诊断工具。你永远应该在 CLI 里完成配置和验证,把桌面应用当成"确认配置生效"的额外证据,而不是第一现场。
7.3 错误三:指望环境变量能切回 Chat Completions
网上仍然流传着"改某个环境变量让 Codex 走老协议"的说法。在当前版本下这是无效的——Codex 使用 responses 协议,没有那个后门。如果你照着做了,得到的不是"切回了",而是"看起来变了但实际没变",然后你会在一堆莫名其妙的报错里迷失。
更深一层的判断:这个"无效"其实是好事。如果真能随意切换协议,就意味着你要同时理解两套工具调用语义、两种 reasoning 承载方式,排错的复杂度会翻倍。协议单一是可维护性的来源,不是限制。
7.4 错误四:手动配置只改 config.toml 却不管 models.json
因为 config.toml 看起来"更重要"——它里面有 key、有 url、有模型名。而 models.json 只是一堆元数据,写着上下文窗口多大、支持哪几档推理。
但这些元数据恰恰决定了 Codex 的行为参数。缺了它,Codex 用 OpenAI 模型的内置默认值来对待 DeepSeek:上下文可能被过早截断,推理档位按下去了却没用,工具调用格式按 OpenAI 的方式组装。结果就是"能用但很别扭",而你会归咎于"DeepSeek 不如预期"。
7.5 错误五:用追加而非合并的方式改配置
这是导致 [desktop] 重复的根源,但它影响的远不止 [desktop]。凡是"把别人的配置贴到文件末尾",都在增加重复段头和键冲突的概率。
我的建议几乎是强迫性的:改 TOML 之前,先把整个文件读一遍。文件通常不超过 fifty 行,读完花不了一分钟,但能避免 90% 的格式类错误。如果文件很大、里面有很多 MCP 服务器,那就更应该小心——那些恰恰是最容易在"重新生成配置"时被误删的东西。
7.6 错误六:把 Key 到处 export,却不知道哪个进程读得到
这条在 6.1 里提过,但值得再强调一次,因为它有一个心理层面的陷阱:export 是一个"有反馈"的动作——你敲了,没报错,觉得自己做完了。但环境变量是进程私有的,它不会告诉你"桌面应用看不见我"。
在 Codex 这种"一份配置三个客户端"的架构下,静态配置优于运行时环境,这不是偏好,是架构决定的。理解这一点,你就不会再在环境变量上反复折腾。
以上七条,单独看都是小问题,合在一起就是一条规律:Codex CLI 的配置问题几乎全部来自"假设错位"——假设配置会自己生效、假设环境变量全局可见、假设追加等于合并、假设协议一致等于能力一致。把假设一个个验掉,问题就没了。
7.7 错误七:把"原生支持"理解成"什么都能用"
DeepSeek 原生支持 Responses API,这意味着协议层不需要转换。但它不意味着 OpenAI 侧的所有附加能力都能用——最典型的就是 web_search。协议一致是必要条件,不是充分条件。
把这两个概念分开,你就能理解为什么报错表里"原生直连"和"搜索报错"会同时存在:前者说的是通信协议,后者说的是服务端能力。它们不在同一个层面。
八、推理档位与模型的选用
配置跑通之后,下一个问题就是"用哪档、用哪个模型"。这不是玄学,但也确实没有标准答案,只有场景匹配。
8.1 推理档位的含义
model_reasoning_effort 控制模型在给出答案前"思考多深"。档位越高,思考越深、响应越慢。桌面端可选的档位由 [desktop] 段下的 enabled-reasoning-efforts 决定,目前列出的档位包括 low、medium、high、xhigh、ultra、max。
这里要提醒:档位列表可能随版本变化。如果你在桌面端看到的选择项与这里列的不一致,以你实际配置文件和官方文档为准。不要因为"文章里写了六档,我这里只有四档"就认为自己配置错了——先确认当前版本支持哪些。
8.2 推理档位选用表
| 档位 | 思考深度 | 响应速度 | 适用场景 |
|---|---|---|---|
| low | 浅 | 最快 | 格式化、重命名、写注释、单文件小改 |
| medium | 中 | 快 | 日常编码、补测试、修 lint、小重构 |
| high | 较深 | 中等 | 默认推荐档,多文件改动、常规 bug 定位 |
| xhigh | 深 | 偏慢 | 跨模块重构、复杂依赖分析 |
| ultra | 很深 | 慢 | 架构级方案权衡、疑难问题根因分析 |
| max | 最深 | 最慢 | 离线深挖、一次性的高价值难题 |
模板里默认给的 model_reasoning_effort = "high",是一个比较稳妥的起点。低于 high,复杂任务容易走弯路;高于 high,简单任务会显得"磨蹭"。
8.3 deepseek-flash 与 deepseek-v4-pro 怎么取舍
deepseek-flash 支持图片输入,适合日常高频使用——截图报错、看 UI 草图、快速改代码。它的价值在于响应快、能看多模态输入,是"主力干活"的那一个。
deepseek-v4-pro 适合需要更强推理的复杂任务。当 flash 在某个任务上反复走弯路时,切到 pro 往往是更省时间的选择。
实用策略:先 flash 后 pro 的两段式切换
为什么"卡两次再升级"是个好阈值
一次失败可能只是表达不清,两次失败通常意味着任务复杂度超过了当前档位。用两次作为阈值,既不会过早切到大模型浪费等待时间,也不会在一个搞不定的任务上反复消耗。
不要一开始就用 pro 跑所有事。我的建议是:默认 flash + high,卡住两次再升到 pro。理由是 flash 的响应速度会显著影响你与 Agent 的交互节奏——快模型让你更愿意多试几轮、多给反馈,这种"高频小步"的使用方式在编程场景里往往比"一次问个大的"效果更好。
只有当任务本身就需要长链路推理(比如"为什么会话泄漏,从入口查到根因并给修复方案"),才值得一上来就用 pro。
8.4 临时切换模型:改配置或看 help
临时切换最直接的方式是改 config.toml 里的 model,或者重跑一键脚本选 2。如果只是某一次任务想换个模型,CLI 侧通常也支持在启动参数里指定模型——具体参数以 codex --help 当前输出为准,不要凭记忆写。
# 以当前版本 help 为准,确认模型参数的准确写法
codex --help
九、稳定运行与维护
跑通只是开始。这套配置要在日常里稳定工作,还需要一些维护习惯。
9.1 备份与回退:给 Codex CLI 留一条后路
一键脚本会把旧配置备份到 ~/.codex/backup-deepseek/。选 9 可以恢复默认 Codex 配置。这两条是配套的逃生路径:出问题时,先回退到一个已知可用的状态,再慢慢找原因,而不是在坏配置上继续叠加修改。
手动配置的话,自己养成先备份的习惯:
# 手动配置前先备份一份
cp ~/.codex/config.toml ~/.codex/config.toml.bak
cp ~/.codex/models.json ~/.codex/models.json.bak
手动配置时改错一次就回不去了,是一键脚本的真正优势。这个优势在你对配置还不熟的时候价值最大。
9.2 版本升级后的检查
Codex CLI 升级后,建议重跑一次验证清单。重点看两处:model_catalog_json 是否还被识别、enabled-reasoning-efforts 在桌面端是否还生效。协议在演进,字段语义也可能微调。
9.3 Key 的轮换与 Codex CLI 的重新加载
因为 Key 是明文写在 experimental_bearer_token 里的,轮换时你要同时修改配置文件,并确保三个客户端都已经重启。这里有个容易漏掉的点:桌面应用如果在后台运行,可能还持着旧 Key。改完配置后完全退出再启动,比关窗口更保险。
9.4 配置是否纳入版本管理的取舍
config.toml 里既有 MCP 服务器配置(值得版本管理),又有明文 Key(绝对不能提交)。合理的做法是把 Key 抽到单独的文件或者用环境变量注入,但那样就回到了作用域问题。
我的判断是分环境处理:个人机器上直接写配置,接受明文风险;任何共享机器或 CI 环境,用环境变量注入,并接受它的作用域约束。没有一刀切的最优解,只有"你的风险模型是什么"。
建议你在跑通之后,花两分钟记录一份"本机现状":Codex CLI 的版本号、配置文件的修改时间、models.json 里包含的模型 ID、桌面应用里看到的模型名。这份记录在后面任何一次故障里都能帮你快速回答"上次好的时候是什么样"。
排错最贵的成本不是修复,而是失去参照系。有了这份快照,你就能做差分对比:现在我看到的和上次好的时候差了什么。这比从头推理要快得多。
十、FAQ:六个高频疑问
问:为什么 DeepSeek 不需要代理,其他国内模型却要?
因为需要代理的根本原因不在于"国内"或"国外",而在于协议。大多数模型的 API 只实现了 Chat Completions,而 Codex CLI 说的是 Responses API,两者字段结构不同,必须在中间做转换。DeepSeek 的 API 原生支持 Responses API,请求能原样被接收、原样被回应,所以转换层可以整个拿掉。判断一个模型能不能直连 Codex CLI,只看它是否支持 Responses API 就够了。
问:一键脚本和手动配置,我该用哪个?
第一次用脚本,第二次用手动。脚本的价值在于它会备份、会预校验语法、会保留你的 MCP 和项目信任设置、会清理冲突字段并告诉你原因,能让你最快拿到一个可用基线。手动配置的价值在于你能看懂每一行,出问题时知道该改哪。先跑脚本拿到能用的配置,再照着手动重写一遍,是性价比最高的路径。
问:我在桌面应用里改了半天,模型列表一直是空的,为什么?
因为桌面应用只是读配置,不负责配配置。正确顺序是:先在 Codex CLI 里把配置配好,跑到启动横幅显示 model: deepseek-flash,再去开桌面应用。如果 CLI 里都通不了,桌面端肯定也不行。另外要确认 CLI 至少启动过一次,否则 ~/.codex 目录可能根本不存在,你配置写到了别的地方。
问:出现 404,我该先查 base_url 还是先查模型名?
先查 base_url 结尾有没有斜杠。具体写法是 https://api.deepseek.com/,斜杠不能省。改完立刻重试,如果报错从 404 变成了别的类型,说明就是路径问题;如果还是 404,再查模型 ID(应为 deepseek-flash),并确认它和 models.json 里的条目一致。这个"改一步看一步"的方法,比同时改三处再猜是哪个起了作用要高效得多。
问:能不能通过环境变量让 Codex 切回 Chat Completions?
不能。Codex 当前版本使用 responses 协议,改环境变量无法切回 Chat Completions。网上流传的这类做法在当前版本下是失效的,照做只会让你误以为协议已经切换,然后在一堆不一致的表现里迷失。正确的做法是在 [model_providers.deepseek] 段里明确写 wire_api = "responses",让 Codex 用对的协议说话。
问:这个接入方式有什么局限,我该担心什么?
三个要清醒认识的点。第一,协议相等不等于能力相等,web_search 这类 OpenAI 侧能力在 DeepSeek 模型下必须关掉。第二,模型名、推理档位、脚本菜单都可能随官方文档和版本变化,本文参数以当前官方文档为准,遇到差异以官方最新文档为唯一参照。第三,Key 明文写在配置文件里,需要你自己管理文件权限与轮换节奏。理解这三点,你就不会把"直连成功"误当成"无限逼近原生体验"。
结语:少一层,就少一类故障
回头看整篇文章,其实只讲了一件事:把中间层拿掉,故障面就缩小了。走代理的路径要排查三层——Codex、代理、模型;原生直连只有两层。报错表里那七条,没有一条是"代理挂了"或"代理版本不兼容"这类问题,因为它们在这条路径上根本不会发生。
但"少一层"也意味着"少一层缓冲"。配置写错时,错误会更直接地暴露出来,variance(波动)会显得更大。所以这份报错对照表的价值,不在于它列了七条,而在于它把每条都还原成了因果——你需要知道 model_provider 拼错表现为"认证失败"是因为查找失败后退回了错误的认证路径,你需要知道 base_url 少个斜杠是路径拼接问题而不是模型问题。
参数会变,模型名会变,档位会变,脚本菜单编号也会变。唯一不变的是这套因果结构。以官方最新文档为准,把这份结构化理解带走,比把这段配置带走有用得多。








暂无评论内容