Codex CLI 默认把推理请求打到 OpenAI 官方端点,很多同学接 DeepSeek 的第一步就是改~/.codex/config.toml,改完跑一条codex exec,结果迎面撞上stream error: unexpected status 401 Unauthorized;也有人拿到的是更隐蔽的404 Not Found——前者多半是 Key 没被识别,后者几乎全是 base_url 多写或少写了一层路径。要一次性绕开这两类问题,最省事的做法是先去 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-baseurl-intro )拿一个可用的 Key,把 Base URL 固定成统一入口,再写进 Codex 的配置文件。本文按「DeepSeek 任务侧」视角拆解:Codex 是发起方,DeepSeek 是执行方,Token 消耗算在 Codex 发起的每一次补全请求上。全流程给出地址获取步骤、可复制的 config.toml、真实任务命令与返回结果,照着做就能在本地把 Codex 跑在 DeepSeek 上。
1. Codex 的请求链路:谁发起、谁计费、Base URL 写在哪一层
先把链路说清楚,不然后面所有配置都是瞎猜。
Codex CLI 本身不是一个模型,它是一层「任务编排壳」:读你指定的文件、拼上下文、决定要不要调用 shell、把结果写回磁盘。真正产出 token 的那一步,是它向某个 OpenAI 兼容端点发一次/chat/completions(或 responses)请求。这个端点的地址由三样东西共同决定:
model_provider:告诉 Codex 用哪个 provider 段落;model:告诉服务端你要哪个模型,比如deepseek-chat;- provider 段落里的
base_url:请求实际发往哪里。
默认情况下,Codex 走的是官方 provider,base_url指向 OpenAI。你要做的替换动作其实只有一件事:把base_url指到一个能同时承接 DeepSeek 模型请求的 OpenAI 兼容入口,并把鉴权头换成对应的 Key。
这里有个容易被忽略的点:Token 消耗方是 Codex 发起的 DeepSeek 请求。它不是你在网页对话框里手敲两句话的那种消耗量。Codex 一次任务可能读进十几个文件、反复追加工具输出、在多轮里保持上下文,单次任务的输入 token 很容易是普通聊天的几十倍。所以本文的视角始终放在「Codex 侧发起了什么请求」上,而不是「DeepSeek 侧返回了什么」。
那 Base URL 从哪拿?不要再从各种来路不明的截图里抄地址了。统一去 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-baseurl-chain )注册后在控制台取 Key,Base URL 用站点给出的统一入口https://taotoken.net/api。这样你的 config.toml 里就只有一处需要改的地址,后续换模型、加 profile 都不用动它。
2. 在 TaoToken 拿 Base URL 与 Key 的四步复现流程
这一节是可跟做的操作步骤,建议边看边做。
第一步:进入官网并完成账号准备。
打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-baseurl-signup ,完成注册与登录。不要在这一步急着找配置文件,先把账号状态确认好。
第二步:进控制台创建 API Key。
登录后进入控制台的 API Keys 页面(这个页面在文末 CTA 里也给了直达链接)。点击创建,系统会生成一串以sk-开头的字符串。这串东西只会在创建时完整展示一次,复制出来先放到一个临时文本里。
第三步:把 Key 落到环境变量,而不是硬写进配置文件。
这一步是很多教程跳过的,但它是后面 401 报错的根源。Codex 的 provider 段落支持env_key字段,意思是「从这个环境变量里读 Key」。所以配置文件里写的是变量名,而不是 Key 本身。Linux / macOS:
# 写入当前 shell 会话,临时验证用 export TAOTOKEN_API_KEY="YOUR_API_KEY" # 长期生效,追加到 ~/.zshrc 或 ~/.bashrc echo 'export TAOTOKEN_API_KEY="YOUR_API_KEY"' >> ~/.zshrc source ~/.zshrcWindows PowerShell:
$env:TAOTOKEN_API_KEY = "YOUR_API_KEY"如果想让它在新的 PowerShell 窗口里也生效:
[Environment]::SetEnvironmentVariable("TAOTOKEN_API_KEY", "YOUR_API_KEY", "User")第四步:把 Base URL 固定下来。
统一写https://taotoken.net/api。这里有一个非常关键的细节:只写到/api这一层,不要自己往下拼/v1/chat/completions。Codex 这类客户端会自己补路径,你多写一层,它就变成/api/v1/chat/completions/v1/chat/completions,返回的正是那个让人一头雾水的 404。
做完这四步,你手上应该有两样东西:
Base URL : https://taotoken.net/api API Key : YOUR_API_KEY下面正式进配置文件。
3. Codex config.toml 完整配置:把 DeepSeek 写成默认 provider
Codex 的配置文件位于用户目录下的.codex/config.toml:
- Linux / macOS:
~/.codex/config.toml - Windows:
C:\Users\<你的用户名>\.codex\config.toml
如果.codex目录不存在,手动建一个即可。下面是完整可运行的内容:
# ~/.codex/config.toml # 默认使用的模型与 provider model = "deepseek-chat" model_provider = "taotoken" # 全局推理强度,可选 low / medium / high model_reasoning_effort = "medium" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"逐字段解释一下,避免抄完不知道在改什么:
model:DeepSeek 的对话模型标识,常规任务用deepseek-chat;model_provider:指向下面[model_providers.taotoken]这一段,名字随便起,但两边必须一致;base_url:统一入口,不要加尾部的/v1;env_key:环境变量名,必须和你在第 2 节里export的名字一模一样,大小写敏感;wire_api = "chat":告诉 Codex 走 chat completions 协议,DeepSeek 侧走这条线最稳。
如果你希望同时保留多套配置,用 profile 会更顺手:
# ~/.codex/config.toml model = "deepseek-chat" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat" # 轻量任务:快速问答、改注释、写 commit message [profiles.fast] model = "deepseek-chat" model_provider = "taotoken" model_reasoning_effort = "low" # 复杂任务:跨文件重构、读大仓库、补测试 [profiles.deep] model = "deepseek-chat" model_provider = "taotoken" model_reasoning_effort = "high"调用方式就是加一个参数:
codex exec --profile deep "阅读 src/ 下所有 Python 文件,输出模块依赖关系图"配置写完后,先做一次最小连通性验证,不要直接上大任务。最直接的办法是绕开 Codex,单独测一次端点:
curl -sS https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", "messages": [{"role": "user", "content": "只回复两个字:通了"}], "max_tokens": 32 }'正常返回会长这样:
{ "id": "chatcmpl-9f2c1a7b4e", "object": "chat.completion", "created": 1730000000, "model": "deepseek-chat", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "通了" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 12, "completion_tokens": 2, "total_tokens": 14 } }只要usage字段回来了,说明 Key 和 Base URL 这一层是通的。接下来所有问题都只可能出在 Codex 的配置写法上,排查范围直接缩小一半。
4. 任务实测:让 Codex 用 DeepSeek 跑一个真实仓库任务
连通性验证完,跑一个真实任务。这里刻意选一个「消耗不大但能看出效果」的场景:给已有函数补单元测试。它能同时检验三件事——Codex 能不能读到文件、DeepSeek 能不能理解代码、返回能不能落盘。
假设仓库结构如下:
demo-repo/ ├── src/ │ └── parser.py └── tests/ └── (空目录)src/parser.py里有一个待测函数:
def parse_amount(text: str) -> float: cleaned = text.strip().replace(",", "").replace("¥", "") if not cleaned: raise ValueError("empty amount") return float(cleaned)执行命令:
cd demo-repo codex exec --profile deep \ "阅读 src/parser.py,为 parse_amount 函数补充 pytest 用例,\ 覆盖正常值、带千分位、带货币符号、空字符串四种情况,\ 写入 tests/test_parser.py。不要修改 src/parser.py 的原有实现。"Codex 侧会先把文件内容拼进上下文发出去,DeepSeek 返回一段结构化改动。返回结果大致是这种形态(终端输出):
[codex] reading src/parser.py [codex] planning 1 file change [codex] writing tests/test_parser.py --- tests/test_parser.py (new) --- import pytest from src.parser import parse_amount def test_plain_number(): assert parse_amount("128.5") == 128.5 def test_with_thousand_separator(): assert parse_amount("1,280.00") == 1280.0 def test_with_currency_symbol(): assert parse_amount(" ¥ 99.9 ") == 99.9 def test_empty_string_raises(): with pytest.raises(ValueError): parse_amount(" ") 4 passed in 0.12s跑完之后立刻验证:
pytest -q tests/test_parser.py这一步的价值不只是「测通了」,而是让你直观看到请求量级:Codex 为了这一个任务读了源文件、生成了四个用例、又执行了一次 pytest。它发起的是一次输入 token 明显大于你手写提问的请求。这就是前面强调的「Token 消耗方是 Codex 发起的 DeepSeek 请求」在真实场景里的样子。
如果你想更直观地观察,可以再跑一条更重的命令:
codex exec --profile deep "扫描 src/ 与 tests/,列出所有缺少断言的测试函数,用表格输出"这条会读入更多文件,输入 token 随之上涨。建议把它当作「成本感知」练习,第一次跑的时候留意账单页面的变化。
5. 401 / 404 / stream 中断:接入后的高频报错排查表
配置这件事,90% 的时间花在排错上。下面这张表按报错原文归类,遇到问题直接对号入座。
| 报错关键片段 | 真实原因 | 处理动作 |
|---|---|---|
unexpected status 401 Unauthorized | Key 没被读进去,或环境变量名不一致 | 确认env_key的值与环境变量名逐字符相同;在同一个终端里echo $TAOTOKEN_API_KEY验证非空 |
unexpected status 403 | Key 被复制时带了换行或空格 | 重新复制,或echo -n "YOUR_API_KEY" | wc -c检查长度 |
unexpected status 404 Not Found | base_url多写了一层路径 | 改回https://taotoken.net/api,删掉自己拼的/v1/chat/completions |
model not found/invalid model | model字段拼错或用了不存在的名字 | 常规任务统一用deepseek-chat,不要写显示名称 |
stream error: ... context length | 单次塞进去的文件太多,超出上下文 | 缩小任务范围,明确指定「只读某几个文件」 |
stream disconnected before completion | 单次响应时间过长被中断 | 拆分任务;把model_reasoning_effort从 high 降到 medium |
| 配置文件改了但完全不生效 | 改错了路径,或存在多份 config | 确认是~/.codex/config.toml,并检查有没有-c参数在命令行覆盖 |
有两类错误值得单独展开。
第一类是「配置没生效但看起来生效了」。典型表现是:你把model_provider改成了taotoken,但[model_providers.taotoken]这段的段落名拼成了taotoken_或别的,TOML 解析不会报错,Codex 会退回默认 provider,于是请求又发往了原来的地址,得到 401。排查办法很简单:把段落名和环境变量名做成同一个字符串,taotoken就从头到尾都是taotoken。
第二类是「HTTP 通了但 Codex 不通」。也就是第 3 节的 curl 返回正常,但codex exec报错。这十有八九是wire_api的问题。DeepSeek 走 chat completions 更稳,所以显式写wire_api = "chat";如果你从别处抄来了 responses 相关的写法,删掉它。
再补一个排查习惯:把 curl 的返回和 Codex 的返回放在一起看。curl 通了 Codex 不通,问题必然在 Codex 的配置文件里;两边都不通,问题在 Key 或 Base URL 本身。这个二分法能省掉大量时间。
6. 顺手把同一套 Key 接到 Claude Code 与 CC Switch
Codex 跑通之后,很多人会问:同一套 Key 能不能顺手接到别的工具上?可以,但配置方式完全不同,绝对不能照抄。这是最容易踩坑的地方。
Claude Code 走的是ANTHROPIC_*系列环境变量,不是 config.toml。它的配置文件通常放在~/.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "deepseek-chat" } }对应的环境变量写法:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"请注意:上面这段只属于 Claude Code。不要把它搬进 Codex 的 config.toml,Codex 不认识ANTHROPIC_*,写了也不会报错,只会静默退回默认 provider,然后你又会看到那个熟悉的 401。
CC Switch 的「三件套」指的是这三个字段,理解了就不会配错:
1. 供应商名称 :随便起,例如 taotoken 2. Base URL :https://taotoken.net/api 3. API Key :YOUR_API_KEY(或引用环境变量)不同工具只是把这「三件套」放进不同的容器:Codex 放进config.toml的[model_providers.*]段落,Claude Code 放进settings.json的env或系统环境变量。三件套本身不变,容器随工具变。把这句话记住,以后无论换什么 CLI 都能三分钟内接上。
如果你想看 Claude Code 侧的完整说明,文末给了官方文档直达链接,这里不展开,避免和 Codex 的配置混在一起。
7. Token 用量与成本:怎么判断钱花在哪了
接入完成之后,最实际的问题是用量。回到本文的视角槽:消耗方是 Codex 发起的 DeepSeek 请求。这意味着三件事:
第一,用量和你手敲的字数基本无关。你发一句「帮我重构这个模块」,Codex 可能已经把 8 个文件、2000 行代码塞进了上下文。这就是为什么同样一句话,在 Codex 里的输入 token 可能是网页聊天的几十倍。
第二,任务颗粒度直接决定成本。拆成「只读 src/parser.py,只改这一个函数」和「扫描整个 src/ 目录」,输入 token 可能差一个数量级。养成在提示词里明确限定文件范围的习惯:
# 成本友好写法 codex exec --profile fast "只阅读 src/parser.py,给 parse_amount 加一行类型注解" # 成本较高写法 codex exec --profile deep "分析整个仓库的代码质量并给出重构方案"第三,profile 是成本旋钮。前面配的fast和deep两个 profile,实际差异体现在推理强度上。日常小改用fast,跨文件重构才上deep。别把所有任务都挂在高强度档位。
想观察实际消耗,可以在跑完任务后回控制台看用量记录。判断标准很简单:如果某个任务你觉得「产出不值这个量」,下次就在提示词里把文件范围限死。
8. 快速上手清单与完整入口
最后把整条路径压缩成一份可执行清单,方便你回过头逐项核对。
[ ] 1. 在官网完成注册登录 [ ] 2. 控制台创建 API Key,得到 YOUR_API_KEY [ ] 3. export TAOTOKEN_API_KEY="YOUR_API_KEY" [ ] 4. 写 ~/.codex/config.toml,base_url = https://taotoken.net/api [ ] 5. 用 curl 验证 /chat/completions 返回 usage [ ] 6. codex exec 跑一个最小任务验证链路 [ ] 7. 需要时再加 profiles 控制推理强度关于配置本身,只需要记住三句话:Base URL 只写到/api;Key 走环境变量、配置文件里只写变量名;Codex 用config.toml,Claude Code 用settings.json和ANTHROPIC_*,两者不通用。
如果你还没拿到 Key,建议先去官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-baseurl-final )确认账号状态。接下来的四个入口按顺序走一遍,基本能覆盖从试跑到长期使用的全部环节:
- 想先试试 DeepSeek 在真实任务里的表现,从这里发起一次模型对话:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-cta-chat
- 打算把 Codex 这类 CLI 长期挂在日常开发流里,先看 Coding Plan 的额度与适用场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-cta-plan
- 需要新建或轮换 Key,直接进控制台:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-cta-keys
- 同一套 Key 还想接到 Claude Code,看这份文档就够了:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=codex-deepseek-cta-doc
Codex 接 DeepSeek 这件事本身不难,难的是第一次遇到 401 和 404 时不知道往哪查。把 Base URL 固定成https://taotoken.net/api、把 Key 放进环境变量、把 provider 段落名对齐,这三步做完,后面就只剩下按任务颗粒度调 profile 了。