Codex CLI接入DeepSeek V4 Flash:本地代理解决思考链报错

发布时间:2026/8/29 23:45:10
Codex CLI接入DeepSeek V4 Flash:本地代理解决思考链报错 最近在折腾把 Codex CLI 接到 DeepSeek V4 Flash 0731 时踩了一个很典型的坑多轮对话只要开启思考模式上游就返回 HTTP 400错误信息直指reasoning_content必须原样回传。网上相关讨论不少但大多只是贴报错没有把协议转换的来龙去脉讲清楚。本文围绕 Hacker News 上发布的 Dsv4 Codex Proxy 这个方案完整拆解它的设计思路并提供一个最小可运行的本地代理实现让你能像使用官方模型一样在 Codex CLI 里用上 DeepSeek V4 Flash 0731。文章适合两类读者一是刚接触 Codex CLI、想换成国产高性价比模型的开发者二是已经在用 CC Switch、opencode 等工具但被reasoning_content400 报错卡住的同学。读完你不仅能跑通还能理解为什么必须保留思考链字段以及生产环境应该怎么加固。1. 背景Codex CLI 与 DeepSeek V4 Flash 之间到底差了什么1.1 Codex CLI 是什么Codex CLI 是 OpenAI 开源的编码 Agent 命令行工具。它不只是“补全代码”的插件而是一个能在终端里完成“读代码、写代码、执行命令、跑测试”的智能体工作流。你给它一个任务它会先规划再逐步修改文件、运行命令验证结果整个过程以多轮对话和工具调用的方式推进。Codex CLI 默认走的是 OpenAI 的 Responses API也就是/v1/responses这个端点。相比传统的 Chat CompletionsResponses API 在消息结构、工具调用、流式事件类型上都有差异。Codex CLI 之所以体验好很大程度上依赖这套协议带来的结构化交互。1.2 为什么需要 Dsv4 Codex ProxyDeepSeek 的 API 是 OpenAI 兼容的但大多数接口走的是 Chat Completions 协议也就是/chat/completions端点。这里就出现了一个协议差Codex CLI 说“Responses API”DeepSeek 说“Chat Completions”两边直接对接必然出问题。Dsv4 Codex Proxy 解决的就是这个协议差。它是一个跑在本地或内网的 API 适配层负责接收 Codex CLI 发出的 Responses API 请求转换成 DeepSeek 能识别的 Chat Completions 请求再把 DeepSeek 的返回结果转换回 Codex CLI 能解析的格式。需要强调的是这里的 Proxy 是开发工具链中的本地 API 适配层用于转发 HTTP 请求、转换消息格式属于常规的开发者工具和网络传输代理是两码事。1.3 “Codex-Native”的含义标题里的“Codex-Native”并不是说 DeepSeek 官方原生支持了 Codex 协议而是指经过 Dsv4 Codex Proxy 这一层转换之后Codex CLI 可以把 DeepSeek V4 Flash 0731 当作一个“原生支持”的模型来使用。换句话说Codex 的 Agent 工作流、工具调用、流式输出、多轮会话能力都保留只是底层模型换成了 DeepSeek V4 Flash。这种“协议兼容层”的做法在 AI 工程化里非常常见相当于给模型加了一个翻译官。1.4 常见应用场景个人开发者在 Codex CLI 里接入 DeepSeek V4 Flash降低 API 成本。团队内部统一模型出口把多个模型接入同一个本地网关。在 VSCode、IDEA、opencode 等支持 OpenAI 兼容接口的工具中复用同一条代理链路。对模型响应做统一日志、限流、审计方便生产环境管控。2. 环境准备与版本说明开始实操之前先把环境准备好。DeepSeek 模型版本更新很快deepseek-v4-flash、0731、vision-exp这类标识都可能在短时间内调整所以本文示例尽量把模型名做成可配置项避免写死在代码里。2.1 安装 Node.js 与 Codex CLIDsv4 Codex Proxy 这类工具通常基于 Node.js因为 Codex CLI 本身就是 Node 生态。先确认 Node 版本node -v npm -v建议 Node 18 及以上因为下面的示例代码会使用内置的fetch和流式读取能力。安装 Codex CLInpm install -g openai/codex codex --version如果你用的是 Codex 桌面版也可以直接把桌面版安装好但命令行模式下用 npm 安装最方便。安装完成后确保codex命令在 PATH 中后面配置 IDE 集成时也需要用到这个路径。2.2 获取 DeepSeek API Key 并确认模型 ID在 DeepSeek 开放平台创建 API Key权限最小化只给自己够用的额度即可。然后确认当前账号可用的模型 ID不同平台的命名可能不一样常见的有deepseek-v4-flashdeepseek-v4-flash-0731带vision-exp后缀的实验版本0731 这类后缀通常是版本发布日期标识并不代表模型会一直叫这个名字。建议在正式使用前通过官方模型列表接口核对或者直接用小请求测试。把 Key 写入环境变量export DEEPSEEK_API_KEYsk-你的key2.3 工具清单工具作用版本说明Node.js运行代理脚本18npm安装依赖与 Node 配套Codex CLI编码 Agent 客户端最新稳定版Express本地代理 HTTP 服务^4.19.2curl验证代理接口系统自带3. 核心原理拆解Responses API 与 Chat Completions 的转换3.1 Codex 的请求长什么样Codex CLI 发出的请求大致如下{ model: deepseek-v4-flash, input: [ { role: user, content: 给这个项目补一个 README } ], tools: [ { type: function, name: shell, parameters: { type: object } } ], reasoning: { effort: medium }, stream: true }几个关键字段input消息数组对应传统 API 里的messages但角色和结构略有差异。tools工具定义Codex 靠它执行 shell 命令、读写文件。reasoning.effort思考强度控制低中高三档。stream是否流式返回。3.2 DeepSeek 的请求长什么样DeepSeek 兼容 OpenAI Chat Completions 格式请求一般是{ model: deepseek-v4-flash, messages: [ { role: user, content: 给这个项目补一个 README } ], tools: [ { type: function, name: shell, parameters: { type: object } } ], stream: true }可以看出messages和tools可以直接映射但reasoning.effort这种码是不是能直接透传取决于模型服务方是否支持。这就是代理层要做转换的原因。3.3 关键坑点reasoning_content 必须回传这是整个方案中最容易踩的坑也是社区里cc switch local proxy failed报错的高频来源。DeepSeek 的推理类模型在思考模式下返回的每个 assistant 消息里会多出一个字段{ role: assistant, content: 这是给用户看的正式回答, reasoning_content: 这是模型内部的思考链 }问题在于当你在多轮对话中把历史消息再次发送给 DeepSeek 时如果开启了 thinking mode服务端要求上一轮 assistant 消息里的reasoning_content必须一并回传。如果代理在转换时只保留了content、丢掉了reasoning_content第二轮请求就会收到类似下面的 400 错误the reasoning_content in the thinking mode must be passed back to the api我之前在 CC Switch 里遇到的就是这个错。它本质上是本地代理转换逻辑不完整不是模型本身的问题。正确的历史消息构造方式{ messages: [ { role: user, content: 用 Python 写一个二分查找 }, { role: assistant, content: 下面是一个示例, reasoning_content: 先分析输入输出再写代码 }, { role: user, content: 改成递归版本 } ] }如果第一轮就没有开启思考模式通常不会触发这条校验。但如果代理层对所有请求都无脑追加reasoning_content空字符串也可能引发奇怪的解析错误所以最稳妥的做法是“有就带上没有就不带”。3.4 代理层要做的事Codex Responses APIDeepSeek Chat Completionsinput[]messages[]tools、tool_choicetools、tool_choicereasoning.effortthinking配置按模型文档调整streamstreamoutput[].content[].textchoices[0].message.content无message.reasoning_content回传历史必须保留流式场景下还要把 DeepSeek 返回的choices[0].delta.content转成 Codex 认识的response.output_text.delta事件。搞清楚这张表代码就有方向了。4. 完整实战搭建一个本地 Codex 兼容代理下面实现一个最小可运行的 Dsv4 Codex Proxy 思路演示。重点不是贴一个大而全的框架而是把请求转换、结果转换、流式转发这三个核心环节讲清楚。实际项目请以对应仓库发布版本为准下面代码可以用作理解和二次开发的基础。4.1 初始化项目与依赖mkdir dsv4-codex-proxy cd dsv4-codex-proxy npm init -y npm install express^4.19.2 touch server.js项目目录结构dsv4-codex-proxy/ ├── package.json └── server.jspackage.json 关键内容{ name: dsv4-codex-proxy, version: 0.1.0, private: true, main: server.js, scripts: { start: node server.js }, dependencies: { express: ^4.19.2 } }4.2 编写请求转换逻辑创建server.js先写环境变量与请求转换部分。// 文件路径dsv4-codex-proxy/server.js const express require(express); const app express(); app.use(express.json()); const UPSTREAM_URL process.env.DEEPSEEK_API_BASE || https://api.deepseek.com/chat/completions; const API_KEY process.env.DEEPSEEK_API_KEY; const MODEL process.env.DSV4_MODEL || deepseek-v4-flash; const PORT process.env.PORT || 4011; // 1. 把 Codex 的 Responses API 请求转换成 DeepSeek Chat Completions 请求 function toChatMessages(input) { if (!Array.isArray(input)) return []; const messages []; for (const item of input) { if (!item || !item.role) continue; if (item.role assistant) { const assistantMsg { role: assistant, content: item.content || }; // 关键thinking mode 下必须把 reasoning_content 原样带回 // 否则 DeepSeek 返回 400reasoning_content must be passed back。 if (item.reasoning_content) { assistantMsg.reasoning_content item.reasoning_content; } if (item.tool_calls) { assistantMsg.tool_calls item.tool_calls; } messages.push(assistantMsg); } else if (item.role user || item.role system) { messages.push({ role: item.role, content: item.content }); } } return messages; } function buildUpstreamRequest(body) { const upstreamBody { model: MODEL, messages: toChatMessages(body.input || []), stream: body.stream ! false, }; if (body.tools) upstreamBody.tools body.tools; if (body.tool_choice) upstreamBody.tool_choice body.tool_choice; // Codex 的 reasoning.effort 映射到 DeepSeek 的 thinking 开关。 // 不同模型版本对 thinking 参数的写法可能不同请以模型文档为准。 if (body.reasoning body.reasoning.effort) { upstreamBody.thinking { type: enabled }; } return upstreamBody; }这段代码的核心就是toChatMessages函数。注意它在处理 assistant 消息时会把reasoning_content原样放回这正是解决 400 报错的关键。实际项目中这里还应该处理 Codex 可能出现的多模态输入、文件块等复杂结构但最小示例先聚焦文本消息。4.3 编写响应转换与流式转发接下来实现非流式结果转换以及流式 SSE 转发。// 2. 把 DeepSeek 的非流式结果转换成 Codex 需要的 Responses 结构 function toResponsesOutput(data) { const choice data.choices data.choices[0]; const message (choice choice.message) || {}; return { id: resp_ Date.now(), object: response, created_at: Math.floor(Date.now() / 1000),