尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Dsv4 Codex Proxy:让Codex CLI接入DeepSeek V4 Flash 0731的本地代理方案

Dsv4 Codex Proxy:让Codex CLI接入DeepSeek V4 Flash 0731的本地代理方案 Codex CLI 默认只连接 OpenAI 的模型服务而 DeepSeek V4 Flash 0731 这类第三方模型需要走 DeepSeek API。很多人在 Codex CLI 里配置了 DeepSeek 的 Chat Completions 地址后发现要么 400 报错要么推理过程不可见要么上游返回reasoning_content必须原样回传的硬性要求。Dsv4 Codex Proxy 的思路是在本机启动一个轻量 API 代理把 Codex CLI 发向/v1/responses的请求转换成 DeepSeek Chat Completions 请求并把上下文中必需的reasoning_content原样回传从而让 DeepSeek V4 Flash 0731 能以接近 OpenAI 原生协议的体验运行在 Codex 中。这篇文章会沿着这条主线展开先说明为什么 Codex CLI 不直接配置 DeepSeek 地址而是需要一个本地代理再拆解 Dsv4 Codex Proxy 的工作机制尤其是reasoning_content的坑然后给出最小可运行部署步骤演示如何让 Codex CLI 真正用上 DeepSeek V4 Flash 0731最后汇总 CC Switch、Codex CLI 常见报错的排查路径以及在生产环境中使用时的注意事项。1. 为什么 Codex CLI 接 DeepSeek V4 Flash 0731还需要加一个本地代理1.1 Codex CLI 的默认模型和协议限制Codex CLI 是 OpenAI 开源的命令行编程代理可以直接在终端里让它读文件、改代码、执行命令。它默认连接的是 OpenAI 的模型服务使用的接口协议并不是简单的 Chat Completions而是更接近 OpenAI Responses API 的调用方式。Codex CLI 启动后会把用户的自然语言指令、系统提示词、工具定义、会话上下文等打包成结构化请求发送到/v1/responses端点。这个端点返回的也不是传统的一条消息而是一组output对象可能包含reasoning、message、function_call等多种类型。Codex CLI 依赖这些类型来恢复完整对话状态。第三方模型如果只实现 OpenAI 的 Chat Completions 接口就不能直接被 Codex CLI 使用因为 Chat Completions 的请求格式和 Responses API 不一样。例如 Codex 的请求里有instructions、input、tools、tool_choice等字段而 Chat Completions 用messages、functions或tools。响应的结构差异也很大Chat Completions 返回choicesResponses API 返回output。1.2 DeepSeek V4 Flash 0731 的接入难点DeepSeek V4 Flash 0731 是 DeepSeek 提供的模型标识。从社区热词和错误日志来看它已经具备较强的编程能力很多开发者在 Codex CLI、CC Switch 等工具里尝试接入。但 DeepSeek API 目前对外主要暴露的是 OpenAI 风格 Chat Completions 兼容接口或者说从 Codex CLI 的角度看它不能直接假设上游一定支持 Responses API。直接改 Codex 配置文件里的base_url指向 DeepSeek 官方 API往往会有两个问题。第一协议不匹配。Codex 按 Responses API 格式发请求DeepSeek 期望的是 Chat Completions 格式两边字段对不上上游就会返回 400 或 404。看到cc switch local proxy failed while handling codex endpoint /responses. ... upstream_status: http 400这类日志本质上就是本地工具把 Codex 的/responses请求转发给了不支持该端点的模型服务。第二推理内容处理不完整。DeepSeek 的思考模型在返回结果时会在消息里携带reasoning_content这是模型内部推理过程的文本。Codex 如果要维持多轮对话需要在上下文里保留这段推理信息。社区里那个典型报错写得很清楚the reasoning_content in the thinking mode must be passed back to the api.也就是说上一轮模型生成的reasoning_content下一轮请求时还要原样带回去。如果中间代理把这段字段过滤掉DeepSeek 会直接拒绝请求。1.3 “Codex-Native”到底指什么Dsv4 Codex Proxy 强调Codex-Native意思是它并不是简单地把请求重定向到 DeepSeek而是尽量让 Codex CLI 感觉自己在和一个原生支持 Responses API 的模型服务通信。具体来说代理需要做三件事对外暴露/v1/responses端点接收 Codex CLI 的请求。把 Responses API 请求转换成 DeepSeek Chat Completions 请求。把 DeepSeek 的返回结果再转换回 Responses API 结构并正确处理reasoning_content。这样用户不需要改动 Codex CLI 的底层发送逻辑只需要把它当成一个 OpenAI 兼容服务来配置。从 Codex 的视角看本地代理就是“原生”支持 Codex 的模型服务。2. Dsv4 Codex Proxy 的工作机制2.1 端点转发与协议转换代理最核心的工作是协议转换。Codex CLI 发来的请求通常长这样{ model: deepseek-v4-flash-0731, instructions: 你是一个资深工程师, input: [ { type: message, role: user, content: [ {type: input_text, text: 帮我修复这个 Python 脚本} ] } ], tools: [], stream: true }代理拿到这个请求后需要把它转换成 Chat Completions 格式{ model: deepseek-v4-flash-0731, messages: [ {role: system, content: 你是一个资深工程师}, {role: user, content: 帮我修复这个 Python 脚本} ], stream: true }响应方向同理。Chat Completions 返回{ choices: [ { message: { role: assistant, content: 我检查了脚本问题出在..., reasoning_content: 用户希望修复脚本我先看报错... } } ] }代理需要转换成 Codex 能识别的 Responses API 结构{ id: resp_xxx, object: response, status: completed, output: [ { type: reasoning, id: reasoning_xxx, content: 用户希望修复脚本我先看报错... }, { type: message, id: msg_xxx, role: assistant, content: [ {type: output_text, text: 我检查了脚本问题出在...} ] } ] }这一步不能省略否则 Codex CLI 无法把reasoning_content纳入会话上下文下一轮继续对话时就会报 400。2.2 reasoning_content 为什么必须原样回传reasoning_content是 DeepSeek 思考模型特有的字段。它和普通content的区别在于它代表模型在生成最终答案之前的一段内部推理过程。对多轮对话来说这段推理对于后续语义理解很重要DeepSeek 要求客户端在下一轮请求时把它放回messages中。如果代理只转发content丢掉reasoning_content那么 DeepSeek 会认为自己失去了上下文的一部分直接返回 400。错误信息里的原文就是前面提到的那句。正确做法是在把用户多轮历史转换为 Chat Completions 的messages时如果某条历史消息是 assistant 产生的并且包含reasoning_content或等价信息就要把这段内容一起放回 assistant 消息里。例如 Codex 的 Responses API 请求中输入数组里可能包含类型为reasoning的对象{ type: reasoning, id: reasoning_xxx, content: [{type: reasoning_text, text: 上一轮的推理内容}], summary: [{type: summary_text, text: 推理摘要}] }代理在转换时应该把content或summary中的文本提取出来放到 Chat Completions 的 assistant 消息中{ role: assistant, content: 最终答案文本, reasoning_content: 上一轮的推理内容 }如果一个工具没有做这一步即使前面配置看起来完全正确也会在最不起眼的多轮对话里突然失败。2.3 流式与上下文处理Codex CLI 默认使用流式输出因为它要在终端里实时展示输出过程。代理如果只处理非流式请求接入体验会大打折扣。流式场景下代理需要把 DeepSeek 的 SSE 流转换成 Codex 期待的 Responses API SSE 事件。DeepSeek Chat Completions 的流式事件通常是这样data: {choices:[{delta:{content:你}}]}Responses API 的流式事件则是另一套结构Codex 会监听多种事件类型例如response.output_item.added、response.output_text.delta、response.completed等。代理可以做一个简化处理先把上游流全部读完再按 Responses API 的流式格式重新发送。这种方式的缺点是首字延迟会变高但优点是代码简单、不容易丢数据。更成熟的方案是边读上游事件边转换成下游事件但需要对 Delta 内容做状态管理。在多轮对话中代理还需要维护一个上下文缓冲区把每次请求中的reasoning_content、assistant 消息、工具调用结果都保存下来并在下一次请求时拼回去。这个缓冲区可以放在内存里也可以依赖上行的历史消息重点是不能把reasoning_content漏掉。3. 环境准备与最小部署3.1 环境要求在部署 Dsv4 Codex Proxy 之前先把环境统一好。下面是一个经过验证的常见组合。组件建议要求说明操作系统macOS 或 LinuxWindows 也可以运行但路径和终端命令会有差异Python3.10 及以上FastAPI 需要较新的版本Codex CLI最新稳定版老版本对 model_providers 支持不完整DeepSeek API Key官方账号可用需要能正常访问 DeepSeek 官方 API网络能访问 DeepSeek API本地代理不需要额外网络穿透只需要访问公网 API学习环境里把这些组件装在同一台机器上即可。生产环境需要额外考虑日志、监控、密钥管理和容错后面会专门说。3.2 获取 DeepSeek API Key登录 DeepSeek 开放平台后在 API Keys 页面创建密钥。密钥以sk-开头。这个密钥要传给本地代理由代理转发给 DeepSeek API。不要在 Codex 配置里直接写死密钥到代码仓库建议放到环境变量中。export DEEPSEEK_API_KEYsk-你的密钥如果 DeepSeek 官方有多个模型版本需要确认账号能够访问deepseek-v4-flash-0731。不同账号可能开通了不同的模型权限如果后续请求返回 403 或模型不存在优先检查账号是否有该模型的访问权限。3.3 启动本地代理这里给出一个最小可运行示例说明 Dsv4 Codex Proxy 的核心逻辑。代码做了简化非流式场景下可以跑通生产环境还需要补全错误处理、流式转换和日志。# dsv4_codex_proxy.py import os import json import httpx from fastapi import FastAPI, Request from fastapi.responses import JSONResponse, StreamingResponse app FastAPI() client httpx.AsyncClient(timeout120) DS_BASE os.getenv(DS_BASE, https://api.deepseek.com) DS_API_KEY os.getenv(DEEPSEEK_API_KEY, ) MODEL os.getenv(DS_MODEL, deepseek-v4-flash-0731) def extract_text(content): if isinstance(content, str): return content if isinstance(content, list): parts [] for item in content: if isinstance(item, dict): parts.append(item.get(text, )) return \n.join(parts) return app.post(/v1/responses) async def proxy_responses(request: Request): body await request.json() instructions body.get(instructions, ) raw_input body.get(input, ) if isinstance(raw_input, str): messages [] if instructions: messages.append({role: system, content: instructions}) messages.append({role: user, content: raw_input}) else: messages [] if instructions: messages.append({role: system, content: instructions}) for item in raw_input: if not isinstance(item, dict): continue item_type item.get(type, ) if item_type message: role item.get(role, user) content extract_text(item.get(content, [])) messages.append({role: role, content: content}) elif item_type reasoning: text extract_text(item.get(content, [])) messages.append({ role: assistant, content: , reasoning_content: text }) elif item_type function_call: messages.append({ role: assistant, content: , tool_calls: [{ id: item.get(call_id, ), type: function, function: { name: item.get(name, ), arguments: json.dumps(item.get(arguments, {})) } }] }) stream body.get(stream, False) payload { model: MODEL, messages: messages, stream: stream, } if body.get(temperature) is not None: payload[temperature] body[temperature] if body.get(max_output_tokens): payload[max_tokens] body[max_output_tokens] headers { Authorization: fBearer {DS_API_KEY}, Content-Type: application/json, } upstream await client.post(f{DS_BASE}/chat/completions, jsonpayload, headersheaders) if upstream.status_code ! 200: return JSONResponse(status_codeupstream.status_code, content{ error: { message: upstream.text, type: upstream_error, upstream_status: upstream.status_code } }) if stream: # 简化流式场景先把上游数据全部读完再转成 Responses SSE lines upstream.text.strip().splitlines() full_content for line in lines: if not line.startswith(data:): continue data line[5:].strip() if data [DONE]: continue try: chunk json.loads(data) delta chunk[choices][0][delta] if content in delta: full_content delta[content] except Exception: continue events [ { type: response.output_item.added, output_index: 0, item: { type: message, id: msg_1, role: assistant, content: [] } }, { type: response.output_text.delta, item_id: msg_1, output_index: 0, content_index: 0, delta: full_content }, { type: response.completed, response: { id: resp_1, object: response, status: completed, output: [ { type: message, id: msg_1, role: assistant, content: [ {type: output_text, text: full_content} ] } ] } } ] def event_stream(): for event in events: yield fevent: {event[type]}\n yield fdata: {json.dumps(event)}\n\n return StreamingResponse(event_stream(), media_typetext/event-stream) data upstream.json() message data[choices][0][message] content message.get(content, ) reasoning_content message.get(reasoning_content, ) output [] if reasoning_content: output.append({ type: reasoning, id: reasoning_1, content: [{type: reasoning_text, text: reasoning_content}] }) output.append({ type: message, id: msg_1, role: assistant, content: [{type: output_text, text: content}] }) return { id: data.get(id, resp_1), object: response, status: completed, output: output }安装依赖pip install fastapi[standard] httpx uvicorn启动服务export DS_BASEhttps://api.deepseek.com export DEEPSEEK_API_KEYsk-你的密钥 export DS_MODELdeepseek-v4-flash-0731 uvicorn dsv4_codex_proxy:app --host 127.0.0.1 --port 8787启动后本地会监听8787端口。DS_BASE指向 DeepSeek 官方 APIDS_MODEL指定模型名。注意上面的代码是一个最小演示用于解释工作原理。实际项目中还需要处理上游 401、403、429、超时、网络断开、SSE 解析异常等情况不能直接作为生产服务使用。4. 配置 Codex CLI 连接本地代理4.1 修改 Codex 配置文件Codex CLI 使用model_providers定义自定义模型服务。配置文件通常是~/.codex/config.toml。把base_url指向本地代理的/v1前缀。model deepseek-v4-flash-0731 model_provider dsv4 [model_providers.dsv4] name DeepSeek V4 Flash 0731 via Dsv4 Proxy base_url http://127.0.0.1:8787/v1 env_key DEEPSEEK_API_KEY wire_api responses关键点model要与代理中的DS_MODEL保持一致。model_provider的值对应下方[model_providers.dsv4]的键名。base_url一定要写本地代理地址不能写https://api.deepseek.com。写错之后请求不会经过本地代理协议转换也就不会发生。env_key指定 Codex CLI 从哪个环境变量读取 API Key。由于本地代理才是真正转发给 DeepSeek 的一方这个 Key 只要不是空字符串即可但为了和后续 curl 测试一致建议设置成真实DEEPSEEK_API_KEY。wire_api设为responses让 Codex 走/v1/responses端点。4.2 通过环境变量注入 API KeyCodex CLI 读取env_key指定的环境变量。在启动 Codex 之前先设置好export DEEPSEEK_API_KEYsk-你的密钥如果本地代理和 Codex CLI 在同一台机器也可以让 Codex 使用任意字符串作为占位因为 Key 本身不会传到 DeepSeek只有本地代理才会把真实 Key 拼进上游请求。不过不建议用空字符串部分 Codex 版本会直接报 401。4.3 使用 CC Switch 等工具切换模型CC Switch 是社区里常用的模型切换工具它本质上是帮助你修改 Codex 或 Claude Code 等工具配置。使用 CC Switch 时如果你看到类似这样的日志cc switch local proxy failed while handling codex endpoint /responses. provider: deepseek model: deepseek-v4-flash upstream_status: http 400 cause: the reasoning_content in the thinking mode must be passed back to the api.这说明 CC Switch 已经把 Codex 的流量转给了某个本地代理或 DeepSeek 地址但上游协议或reasoning_content处理出了问题。此时要确认CC Switch 里配置的 base_url 是不是本地代理。本地代理是否有完整的 Responses API 转换逻辑。是否使用了一个不做reasoning_content处理的简单转发中间层。如果 CC Switch 本身不提供协议转换只是把base_url改成了 DeepSeek 官方地址那 Codex 发出的/responses请求就会失败因为 DeepSeek 官方并不接受这个路径。正确做法是把流量指向 Dsv4 Codex Proxy 的本地端口再由代理完成转换。5. 用 curl 和 Codex 会话验证代理是否生效5.1 先用 curl 验证代理在配置 Codex 之前先用 curl 验证本地代理是否正常。这样可以隔离问题避免 Codex 配置错误和上游错误混在一起。curl http://127.0.0.1:8787/v1/responses \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -H Content-Type: application/json \ -d { model: deepseek-v4-flash-0731, input: 用 Python 写一个二分查找函数, stream: false }如果一切正常会返回类似这样的 JSON{ id: resp_1, object: response, status: completed, output: [ { type: message, id: msg_1, role: assistant, content: [ { type: output_text, text: def binary_search(arr, target): ... } ] } ] }注意观察两点返回的对象是否是response而不是chat.completion。output里是否包含message对象。如果返回的是 Chat Completions 的choices结构说明代理没有做响应转换只做了透明转发Codex CLI 会无法识别。5.2 再在 Codex 里发起一次真实对话curl 通过后打开终端运行codex 读取当前目录的 README并总结项目功能Codex 会通过配置好的base_url把请求发给本地代理。可以观察到Codex 能正常收到响应而不是直接报unexpected status 400。如果模型有思考过程Codex 界面里会显示推理过程或类似内容。多轮对话时不会突然中断。5.3 查看代理日志和错误响应本地代理的日志里会打印请求路径、上游状态码和耗时。如果请求失败先看代理终端输出再看 Codex 终端报错。手动用 curl 复现 Codex 的请求是最快的定位方式。例如如果 Codex 报错unexpected status 401 unauthorized: cc switch local proxy failed while handling而 curl 用同样的 Key 又能通过说明问题出在环境变量没有同步给 Codex CLI或者 CC Switch 改写了认证头。检查 Codex 是否真的读到了DEEPSEEK_API_KEY。6. 高频错误排查从 400 到 5026.1 400 与 reasoning_content 回传问题这是社区里最典型的错误。日志表现为upstream_status: http 400 cause: the reasoning_content in the thinking mode must be passed back to the api.原因是代理或中间层在把多轮请求发给 DeepSeek 时把上一轮的reasoning_content删掉了。DeepSeek 的思考模式严格要求上下文里包含这段信息否则无法确认当前请求的推理状态。排查路径检查本地代理是否解析了 Codex 请求里的reasoning类型对象。检查转换后的messages里assistant 消息是否包含reasoning_content字段。检查多轮对话是否确实把上一条响应保存了下来。解决方案是在代理中把 Reasoning 内容拼回 assistant 消息。示例代码在上一章的elif item_type reasoning分支里已经体现。6.2 401、402、403、404 的检查路径这些状态码都属于“请求已经到达某个服务器但服务器不同意或找不到资源”需要按不同层次排查。状态码常见现象排查方向解决建议400请求格式错误先看代理日志里上游返回的原始文本确认协议转换是否完整重点检查 reasoning_content401认证失败Codex 或代理是否拿到了正确的 API Key检查环境变量确认 Key 未被覆盖402付款要求DeepSeek 账号余额或配额不足登录 DeepSeek 平台确认余额403禁止访问账号无权访问模型或触发了访问控制检查模型名、账号权限和 IP 是否在允许名单404端点不存在base_url 路径写错确认代理监听/v1/responses且 Codex 的 base_url 以/v1结尾401 的一个隐藏原因是Codex 配置里env_key指向的环境变量不存在Codex 可能使用空值发送。可以先用echo $DEEPSEEK_API_KEY确认。6.3 502、超时和 HTTP_PROXY 干扰看到以下错误时要区分“本地代理连不上 DeepSeek”和“DeepSeek 返回异常”502 Bad Gateway connection timed out: getsockopt. if you are behind an http proxy, please configure502 通常表示本地代理收到请求但上游连接失败。检查顺序DS_BASE是否写成了不可达地址。DeepSeek API 是否临时故障。本地网络是否能访问 DeepSeek API。connection timed out还有一个常见来源终端里设置了HTTP_PROXY或HTTPS_PROXY导致 Python httpx 请求走了错误路径。如果当前环境存在系统代理且不该走代理启动代理前可以先取消unset HTTP_PROXY HTTPS_PROXY ALL_PROXY然后再启动 uvicorn。6.4 找不到 Codex CLI 二进制使用 Codex 桌面版或 CC Switch 时经常出现unable to locate the codex cli binary. set codex cli path or ensure the electron app can find it这不是模型问题而是桌面工具找不到codex命令。原因通常是Codex CLI 没有安装到系统 PATH。桌面应用没有继承终端环境变量。PATH 里有多个 Codex 版本。解决方案是手动指定 Codex CLI 路径或者在启动前先执行which codex确认安装位置。在 CC Switch 的配置里也需要把 CLI 路径指向实际二进制。6.5 模型不被支持的提示有时会看到the gpt-5.6-sol model is not supported when using codex with a ...这通常说明 Codex 配置文件里的model和model_provider不一致。例如把模型名写成了默认的gpt-5.6-sol但 provider 配置的是 DeepSeekCodex 检测到模型名不是 provider 支持的模型就会直接拒绝。解决办法是把model明确改成deepseek-v4-flash-0731并确保model_provider指向本地代理配置。7. 生产使用建议与扩展方向7.1 学习环境与生产环境差异本地跑通后不要直接把同样的方式平移到生产环境。学习环境只需要保证功能正确生产环境还需要考虑稳定性、安全性和可观测性。维度学习环境生产环境配置环境变量写死配置外置化使用密钥管理日志终端输出结构化日志记录请求 ID 和耗时流式可以先不做必须支持完整 SSE 流式错误处理直接返回错误文本统一错误码记录上游状态限流不需要需要做多用户隔离和配额回滚重启即可需要保留多版本部署策略7.2 日志、监控与多模型路由生产环境中本地代理最好输出结构化的请求日志包含时间戳、请求 ID、模型、输入 token 数、输出 token 数、上游状态码、耗时等字段。这样在 Codex 报错时可以直接通过请求 ID 关联上游日志。多模型路由是另一个扩展方向。Dsv4 Codex Proxy 可以根据请求里的model字段决定转发到 DeepSeek 还是其他兼容服务也可以在同一模型下做灰度切换。这样的代理更像一个 API 网关而不仅仅是单个模型的适配层。建议在代理层增加一个/health健康检查端点用于确认本地服务是否可用。Codex CLI 本身不需要这个端点但运维和监控系统需要。7.3 安全与成本控制本地代理会拿到用户的 DeepSeek API Key因此不能把它暴露给不可信的网络。生产环境至少要加一层访问控制例如限制监听地址为内网 IP或者在代理前面加一个身份认证。成本控制方面DeepSeek 按 token 计费Codex 多轮对话很容易消耗大量上下文。建议在代理层记录每条请求的 token 数并在服务端配置模型侧的最大输出长度避免单次请求消耗过大。不要在高频请求里打印完整的 Key 或完整的大模型输出到日志日志里只保留必要字段避免敏感信息泄露。7.4 下一步可以完善的功能以一个最小代理跑通 Codex 接入 DeepSeek V4 Flash 0731 之后可以按这个顺序继续完善补全流式转换降低首字延迟。支持工具调用让 Codex 可以真正使用函数调用能力。增加上下文缓存避免每次请求都重复传输历史。支持多模型按模型名路由。增加可观测性指标输出到 Prometheus 或日志平台。编写自动化测试用真实 DeepSeek API 验证多轮对话和异常场景。对新手来说最重要的练习不是把代理写得多么完整而是先用 curl 手动构造请求理解 Codex 发送的结构和 DeepSeek 期望的结构差异。只要能把一个/v1/responses请求完整地转成/chat/completions请求再把响应转回来Codex-Native 的关键原理就已经掌握了。
返回列表