尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Codex CLI本地API代理实战:协议转换与模型接入深度指南

Codex CLI本地API代理实战:协议转换与模型接入深度指南 1. 项目概述1.1 这个项目到底在解决什么问题最近我几乎每天都在用 Codex CLI 写代码用得越深就越不满足于官方默认那套配置。Codex 官方接入的是 OpenAI 自己的模型服务但实际开发中我经常需要切换不同的模型供应商比如 DeepSeek、智谱、通义或者是公司内部部署的模型网关。默认情况下 Codex 根本不给你这个自由度你只能在官方限制的配置项里打转。后来我想到一个方案把 Codex 的请求先打到本地一个代理服务上由这个代理去转发到真正的上游 API。这样 Codex 以为自己连的还是 OpenAI 的接口实际上流量已经被我们完全接管了。这个思路其实很多人在用但网上能找到的资料大多数只讲了“如何改 base_url”这一层真正把代理层怎么做、协议怎么转换、报错怎么排查讲透的少之又少。这篇博文我会从零开始把我把一个本地运行的小服务改造成 Codex 专用 API 代理的整个过程完整记录下来。内容会覆盖架构设计、协议转换原理、Node.js 实现细节、配置方式以及我在实战中踩过的各种坑。适合已经用过 Codex 或类似 AI 编程工具、希望进一步定制模型接入方式的开发者也适合对 OpenAI Responses API 和 Chat Completions API 协议差异感兴趣的读者。1.2 需要准备什么先列一下我这次实战用到的工具清单避免大家后面看配置时一头雾水Codex CLIOpenAI 官方的命令行编程代理我使用的是最新版本支持codex命令直接运行。Node.js 或 Python用于运行本地代理服务。我这次用 Node.js 实现纯标准库不依赖任何第三方包方便理解原理。一个上游 API Key作为上游模型提供方这里以 DeepSeek 为例实际上兼容 OpenAI 格式的 API 都可以。本地端口默认使用127.0.0.1:8088大家可以根据自己的情况调整。记住一个核心概念Codex CLI 本身是支持自定义base_url的也就是说它可以指向任何实现了 OpenAI Chat Completions 或 Responses API 的服务。我们要做的就是让本地代理完整实现 Codex 期望的协议然后把请求转发到上游。2. 整体架构与方案选型2.1 Codex 的 API 调用方式Codex CLI 与其他 AI 编程工具最大的不同在于它默认使用的不是传统的 Chat Completions API而是 OpenAI 的Responses API。这个 API 在设计上更偏向 Agent 场景支持流式事件、工具调用、引用解析等能力。我第一次把base_url改成 DeepSeek 的官方地址时发现返回了一堆奇怪的错误后来仔细研究才发现DeepSeek 目前只兼容 Chat Completions 风格的接口并不原生支持 Responses API。所以直接用是不行的。本地代理的核心作用就出来了把 Codex 发出的 Responses API 请求翻译成上游支持的 Chat Completions 格式再把上游的响应翻译回 Codex 能理解的格式。这本质上是一个协议转换层。2.2 代理方案对比在动手之前我调研了几种常见的方案方案优点缺点直接改 base_url 指向官方 API配置简单零成本协议不同导致大量报错不可用使用现成网关工具如 one-api、new-api功能全支持多模型、密钥管理部署重依赖数据库运维成本高自建轻量本地代理灵活可控无依赖容易排查需要自己实现协议转换有一定代码量最终我选择了自建轻量代理。原因很简单本地开发场景下我们只服务 Codex 这一个客户端不需要复杂的密钥管理、计费、用户体系一个能跑在 localhost 上的 Node.js 脚本就足够了。注意这里说的“本地 API”是指把 Codex 的请求转发到任意上游模型服务而不是把模型本身跑在本地。大模型推理依然发生在云端。2.3 协议转换的关键点在动手实现之前我需要先理清两个 API 之间的对应关系。Codex 的 Responses API 请求体大致长这样{ model: codex-mini-latest, instructions: You are a coding agent., input: [ { role: user, content: [ { type: input_text, text: 请帮我写一个 Python 脚本 } ] } ], stream: true }而上游 DeepSeek 期望的是 OpenAI Chat Completions 格式{ model: deepseek-chat, messages: [ { role: user, content: 请帮我写一个 Python 脚本 } ], stream: true }两者的核心差异在于Responses API 使用input字段Chat Completions 使用messages字段。Responses API 的消息内容是一个数组每个元素有type和textChat Completions 直接是字符串。Responses API 的指令instructions需要合并到 system 消息里。Responses API 的流式事件类型更多包括response.output_text.delta、response.function_call_arguments.delta等。这些差异就是我代理层要解决的逻辑。接下来我们一步一步实现。3. 编码实现从零搭建 Codex 本地代理3.1 最小可用的 HTTP 服务器我选择用 Node.js 的http模块来搭建服务完全不需要第三方依赖。第一步先实现一个最简单的 HTTP 服务器监听127.0.0.1:8088接收 Codex 发出的 POST 请求const http require(http); const { URL } require(url); const PORT 8088; const UPSTREAM_BASE https://api.deepseek.com; const server http.createServer(async (req, res) { const url new URL(req.url, http://${req.headers.host}); if (req.method POST url.pathname /v1/responses) { // 处理 Codex 请求 } else { res.writeHead(404, { Content-Type: application/json }); res.end(JSON.stringify({ error: Not Found })); } }); server.listen(PORT, 127.0.0.1, () { console.log(Codex Proxy listening on http://127.0.0.1:${PORT}); });这个骨架很简单但有几个容易忽略的细节必须监听在127.0.0.1不要监听0.0.0.0避免本地服务暴露到局域网。路径匹配要精确到/v1/responsesCodex 默认请求的就是这个路径。非 POST 请求或路径不对时要返回规范的 JSON 错误方便 Codex 识别。3.2 请求转换从 Responses 到 Chat Completions接下来是核心的请求转换函数。这一步的目标是把 Codex 发出的 Responses API 请求体转换成上游模型服务能识别的 Chat Completions 请求体。function convertRequest(body) { const messages []; // 处理 instructions if (body.instructions) { messages.push({ role: system, content: body.instructions }); } // 处理 input 数组 if (Array.isArray(body.input)) { for (const item of body.input) { if (item.role user || item.role assistant) { const content extractTextContent(item.content); messages.push({ role: item.role, content: content }); } } } return { model: UPSTREAM_MODEL, messages: messages, stream: true, temperature: body.temperature || 0.2 }; } function extractTextContent(content) { if (typeof content string) { return content; } if (Array.isArray(content)) { return content .filter(item item.type input_text || item.type output_text || item.type text) .map(item item.text) .join(\n); } return String(content || ); }这里有一个关键决策上游模型我固定使用UPSTREAM_MODEL而不是透传 Codex 请求里的model字段。原因在于Codex 发送的模型名通常是codex-mini-latest或gpt-5这类上游服务根本不认识。我们需要在本地维护一个映射表把 Codex 的模型名映射到实际可用的上游模型。const MODEL_MAP { codex-mini-latest: deepseek-chat, gpt-5: deepseek-chat, default: deepseek-chat }; function mapModel(model) { return MODEL_MAP[model] || MODEL_MAP[default]; }3.3 上游请求转发转换完成后就可以向真正的 API 服务发起请求了。这里需要注意的是必须把流式响应原样转发给 Codex不能等到全部接收完再返回否则 Codex 会一直等待表现为“卡死”。const https require(https); const REQUEST_TIMEOUT 300000; // 5分钟超时 function forwardToUpstream(convertedBody, authKey, res) { return new Promise((resolve, reject) { const upstreamReq https.request(UPSTREAM_BASE /chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${authKey}, Accept: text/event-stream }, timeout: REQUEST_TIMEOUT }, (upstreamRes) { // 先向上游返回状态码 res.writeHead(upstreamRes.statusCode, { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive }); // 逐块转发数据 upstreamRes.on(data, (chunk) { res.write(chunk); }); upstreamRes.on(end, () { res.end(); resolve(); }); }); upstreamReq.on(error, (err) { console.error(Upstream request error:, err.message); if (!res.headersSent) { res.writeHead(502, { Content-Type: application/json }); res.end(JSON.stringify({ error: Bad Gateway, details: err.message })); } reject(err); }); upstreamReq.on(timeout, () { upstreamReq.destroy(new Error(Upstream request timeout)); }); upstreamReq.write(JSON.stringify(convertedBody)); upstreamReq.end(); }); }这里有一个极其重要的细节上游模型服务的响应流是 OpenAI Chat Completions 格式而 Codex 期望的是 Responses API 格式。如果不做转换Codex 收到的数据是它不认识的字段就会报出类似unexpected status的错误。但这里我做了个简化先把流式响应原样透传回去后续再增加流式转换逻辑。为什么可以这样因为部分上游已经实现了 Responses API 兼容或者 Codex 能自动处理部分 Chat 格式。如果遇到协议不匹配就需要增加流式事件转换这部分我们在下一节详细讲。3.4 流式响应的协议转换如果透传方式不可行我们就需要把上游的 Chat Completions 流式数据逐步改写成 Responses API 的事件流。这一步是大多数教程里缺失的内容。Chat Completions 的流式数据一行一行返回每行是data: {json}的 SSE 格式data: {choices:[{delta:{role:assistant,content:你好}}]} data: {choices:[{delta:{content:世界}}]} data: [DONE]而 Responses API 的事件流是这种结构event: response.created data: {type:response.created,response:{}} event: response.output_text.delta data: {type:response.output_text.delta,delta:你好}转换逻辑其实不复杂但要维护一个状态机记录当前输出进度。我写了一个简化版本function transformSSE(line, state) { if (!line.startsWith(data:)) return null; const data line.slice(5).trim(); if (data [DONE]) { return event: response.completed\ndata: {type:response.completed}\n\n; } const parsed JSON.parse(data); const delta parsed.choices?.[0]?.delta?.content; if (!delta) return null; state.outputText (state.outputText || ) delta; return event: response.output_text.delta\ndata: ${JSON.stringify({ type: response.output_text.delta, delta: delta, item_id: state.itemId, output_index: state.outputIndex })}\n\n; }这段代码虽然短但解决了核心问题向上游要文本增量向下游推送 Codex 能识别的事件。实际生产环境还需要处理response.output_item.added、response.content_part.added等事件这些事件 Codex 在初始化对话时是必须的否则可能报错。提示如果你只是临时用一下且上游兼容 Chat Completions可以先用透传方式运行遇到协议报错再补流式转换。两种方式我在实战中都跑通过差异只在于事件的完整度。3.5 组装完整的请求处理流程把所有模块组合起来主处理逻辑如下async function handleRequest(req, res) { const chunks []; for await (const chunk of req) { chunks.push(chunk); } const rawBody Buffer.concat(chunks).toString(utf-8); let body; try { body JSON.parse(rawBody); } catch (e) { res.writeHead(400, { Content-Type: application/json }); res.end(JSON.stringify({ error: Invalid JSON })); return; } // 提取用户的 API Key const authHeader req.headers.authorization || ; const userKey authHeader.replace(/^Bearer\s/i, ); // 判断是使用本地配置的 Key还是透传用户的 Key const finalKey process.env.UPSTREAM_API_KEY || userKey; if (!finalKey) { res.writeHead(401, { Content-Type: application/json }); res.end(JSON.stringify({ error: Missing API Key })); return; } const convertedBody convertRequest(body); await forwardToUpstream(convertedBody, finalKey, res); }这里我特意设计成支持两种 Key 处理方式固定 Key 模式在环境变量里配置UPSTREAM_API_KEY所有 Codex 请求共用这把 Key适合个人使用。透传 Key 模式不配置环境变量直接转发 Codex 请求头里的 Authorization适合在本地同时测试多个账号。如果你希望 Codex 发送什么都原样转发可以只做字段映射不改 Key。但我在实测中发现Codex 默认会带自己的开发者 Key有些人不想用官方 Key 计费就会在本地配置一个假的 Key代理层再把假 Key 替换成真实 Key。这个过程很多人叫“Key 置换”本质上就是鉴权信息的映射。4. 模型映射与配置项深度解读4.1 模型映射的意义Codex CLI 内部有一些硬编码的模型名哪怕你通过配置改掉它某些场景下还是会发固定的模型名。比如内部自动摘要用的gpt-5、代码补全用的codex-mini-latest等。这些名字在 DeepSeek、智谱、通义等国内模型服务上统统不存在。所以代理层必须做翻译把客户端给的模型名换成上游真正支持的模型名。我在实践中维护了一张映射表Codex 端模型名上游模型名适用场景codex-mini-latestdeepseek-chat通用对话、代码生成gpt-5deepseek-reasoner复杂推理任务gpt-5-minideepseek-chat轻量任务这样做的另一个好处是我们只暴露几个固定的 Codex 模型名给客户端不用担心用户乱填模型名导致上游报错。4.2 环境变量与配置管理为了让代理服务更容易复用我把所有可配置项都提取成了环境变量# 本地代理监听端口 PROXY_PORT8088 # 上游 API 地址 UPSTREAM_BASEhttps://api.deepseek.com # 上游 API Key UPSTREAM_API_KEYsk-xxxxx # 默认映射的上游模型 UPSTREAM_MODELdeepseek-chat配合.env文件或者直接在 shell 里export非常方便切换不同上游。比如我想切换到智谱 GLM只需要改UPSTREAM_BASE和UPSTREAM_MODEL不需要动代码。4.3 处理 DeepSeek 的特殊错误实践中最容易踩的坑是 DeepSeek 的thinking模式报错。很多人反复遇到这条错误400 the supported api model names are deepseek-flash, deepseek-v4还有the reasoning_content in the thinking mode must be passed back to the api.这两条背后是同一个问题某些模型服务要求当模型输出了 reasoning_content思考内容时客户端必须在下一轮请求中把它原样带回。但 Codex 作为客户端并不会自动处理这个字段。解决办法有两个方向在代理层把上一轮的reasoning_content缓存起来下一轮请求自动拼到messages里。更简单的方法是配置模型服务关闭 thinking 模式或者在请求参数里加thinking: {type: disabled}。function convertRequest(body) { // 禁用 thinking 模式避免 reasoning_content 问题 const extraParams {}; if (UPSTREAM_MODEL.includes(deepseek)) { extraParams.thinking { type: disabled }; } return { model: UPSTREAM_MODEL, messages: messages, stream: true, temperature: body.temperature || 0.2, ...extraParams }; }这种方法实测有效能彻底避免 thinking 模式下的缓存问题。如果你确实需要 thinking 能力就必须在代理层实现 reasoning_content 的缓存与回传代码量会大很多但对于日常代码生成场景关闭 thinking 并不会明显影响输出质量。5. 实战配置五步让 Codex 走本地代理5.1 第一步准备上游 API Key先去对应平台申请一个 API Key。以 DeepSeek 为例登录控制台在“API Keys”页面创建一个新 Key复制保存。注意 Key 只显示一次丢失需要重新创建。5.2 第二步启动本地代理把上面代码保存为codex-proxy.js然后在终端运行export UPSTREAM_API_KEYsk-你的key export UPSTREAM_BASEhttps://api.deepseek.com export UPSTREAM_MODELdeepseek-chat node codex-proxy.js看到Codex Proxy listening on http://127.0.0.1:8088的日志说明代理已经就绪。5.3 第三步配置 Codex CLICodex CLI 支持config.toml配置文件。在~/.codex/config.toml中添加model codex-mini-latest model_provider local-proxy [model_providers.local-proxy] name Local Proxy base_url http://127.0.0.1:8088/v1 env_key CODEX_API_KEY wire_api responses重点解释几个参数base_url必须指向本地代理的/v1路径Codex 会自动拼接/responses。env_keyCodex 会从这个环境变量读取 API Key。这里我们随便设置一个占位值实际请求会被代理层替换成真正的上游 Key。wire_api指定 Codex 使用 Responses API 协议。然后在 shell 中设置占位 Keyexport CODEX_API_KEYsk-local-proxy-placeholder5.4 第四步验证连通性运行一个简单的 Codex 指令codex 用 Python 写一个快速排序并给出注释正常情况你会看到 Codex 开始流式输出。此时观察代理终端可以看到请求已经被转发到上游并成功返回。5.5 第五步调整模型与参数如果你发现输出质量不理想可以调整config.toml中的模型名或者修改代理里的MODEL_MAP映射。记住修改代理代码后需要重启 Node.js 进程。6. 常见错误与排查清单前面提到过我在搜索热词里看到大量报错案例。这里我把最常见的三类错误单独列出来逐一分析根因和解决办法。6.1 401 Unauthorized / 403 Forbidden报错信息通常是unexpected status 401 unauthorized: cc switch local proxy failed while handling codex endpoint /responses. provider: deepseek根因几乎都是上游 API Key 无效或未正确传递。排查步骤确认UPSTREAM_API_KEY是否设置且能在上游平台正常调用。确认代理层是否真的替换了 Key可以在代码里加一行日志打印finalKey的前几位。确认 Codex 端env_key对应的环境变量已设置且值不为空。这类错误 90% 是配置问题不是代码问题。6.2 400 Invalid Schema / Model Not Found报错信息通常是api error: 400 invalid schema for function artifact或者the supported api model names are deepseek-flash, deepseek-v4根因分析invalid schema for function artifact通常是 Codex 发送了上游不认识的工具调用格式上游在 schema 校验阶段直接拒绝。the supported api model names are...是模型名映射没配置好上游收到一个不存在的模型名。对于第一个问题我建议在代理层过滤或改写tools参数去掉 Codex 特有的复杂 schema。简单来说就是只保留type: function且function.name正常的工具其他一律丢弃。function sanitizeTools(tools) { if (!Array.isArray(tools)) return undefined; return tools .filter(tool tool.type function tool.function tool.function.name) .map(tool ({ type: function, function: { name: tool.function.name, description: tool.function.description || , parameters: tool.function.parameters || { type: object, properties: {} } } })); }经过这个清洗大部分 schema 校验错误都能消除。6.3 502 Bad Gateway / 503 Service Unavailable报错信息通常是unexpected status 502 bad gateway: cc switch local proxy failed while handling codex endpoint /responses根因可能有三类代理进程崩溃。检查 Node.js 输出的错误堆栈。上游服务过载或限流。DeepSeek 高峰期经常出现 502可以重试或稍后再试。代理层超时时间设置过短。调大REQUEST_TIMEOUT。我遇到最多的是第三种。Codex 在生成长代码时模型推理时间可能超过 30 秒如果代理层超时设置太短就会在上游还没返回结果时主动断开连接导致 502。所以务必将超时时间设置为 5 分钟以上。6.4 流式响应卡死Codex 一直转圈、不输出内容多发生在流式转换逻辑不完整时。排查重点是确认上游返回的data:行是否被正确转发。确认是否发送了response.output_item.added事件。Codex 如果没有收到这个事件可能不会渲染任何内容。检查字符编码问题中文字符在流式传输中不能截断 UTF-8 序列。我总结了一个简易排查速查表错误码直接原因首要检查项401Key 无效环境变量配置400 Model模型名不存在MODEL_MAP 映射表400 Schematools 参数不兼容sanitizeTools 清洗函数502上游网关错误请求超时设置503上游过载重试、限流7. 进阶玩法让本地 API 更好用7.1 多上游动态切换一个不够多来几个。你可以在代理里维护多组上游配置通过请求参数动态切换。比如const PROVIDERS { deepseek: { base: https://api.deepseek.com, apiKey: process.env.DEEPSEEK_API_KEY, model: deepseek-chat }, zhipu: { base: https://open.bigmodel.cn/api/paas/v4, apiKey: process.env.ZHIPU_API_KEY, model: glm-4-flash }, dashscope: { base: https://dashscope.aliyuncs.com/compatible-mode/v1, apiKey: process.env.DASHSCOPE_API_KEY, model: qwen-plus } };然后根据请求 header 中的X-Provider字段选择上游。这样你就能在 Codex 里一个base_url后端接多家模型服务随时切换。7.2 记录完整请求日志本地代理最大的好处是可以完整记录 Codex 发出的原始请求。我在调试阶段会把请求体保存成 JSON 文件const fs require(fs); const logDir ./logs; if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); function saveRequestLog(body) { const filename ${logDir}/req-${Date.now()}.json; fs.writeFileSync(filename, JSON.stringify(body, null, 2)); }这些日志对于理解 Codex 的行为、排查复杂问题极有帮助。官方客户端不暴露这些信息只有代理层能看到真实协议。7.3 按需注入系统提示词通过代理你可以在转发前给所有请求注入一段自定义 system prompt。比如让模型始终用中文回答或者始终输出单元测试。这对团队统一编码规范很有用。const EXTRA_SYSTEM_PROMPT You are a senior software engineer. Always provide concise, correct code with Chinese comments.; function injectSystemPrompt(messages) { if (EXTRA_SYSTEM_PROMPT !messages.some(m m.role system m.content EXTRA_SYSTEM_PROMPT)) { messages.unshift({ role: system, content: EXTRA_SYSTEM_PROMPT }); } return messages; }7.4 缓存与重试本地代理再进一步可以加上简单的请求缓存和自动重试。对于 Codex 这种交互式工具缓存意义不大但自动重试很实用。上游偶尔抖动时自动重试两次就能显著提高成功率。async function forwardWithRetry(convertedBody, authKey, res, maxRetries 3) { for (let attempt 1; attempt maxRetries; attempt) { try { await forwardToUpstream(convertedBody, authKey, res); return; } catch (err) { console.error(Attempt ${attempt} failed: ${err.message}); if (attempt maxRetries) { throw err; } await sleep(1000 * attempt); } } } function sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); }注意重试只适合在还没向客户端返回任何数据时进行。如果已经写入了部分流式响应再重试就会导致重复内容。8. 替换官方网关需要注意的代价与收益8.1 自建代理的边界自建代理并不是银弹它有明显的适用范围。如果只是本地个人使用脚本型代理完全够用。但如果要部署到团队供多人同时使用就需要考虑以下问题并发处理能力Node.js 单线程模型能扛住一定并发但高并发时需要对流式转发做精细化管理。密钥安全多用户场景下每个用户应该有自己的 Key代理层需要做鉴权和配额管理。日志脱敏请求日志里可能包含用户代码内容如果存日志要注意脱敏和权限控制。8.2 对比现有网关项目如果不想自己维护代码社区里有不少成熟的网关项目比如 one-api、new-api、LiteLLM 等。它们能实现类似的功能而且支持更多的协议转换和监控面板。我的建议是个人折腾、学习原理自建代理100 行代码就够。团队内部小范围使用可以考虑 one-api 这类项目部署成本可接受。生产环境大规模使用要么买商业 API 网关要么深度定制开源项目务必考虑高可用和审计。8.3 从这次实战中学到了什么这次的本地代理实战本质上让我把 Codex 的运行链路彻底摸清了。以前官方客户端是个黑盒出了问题只能瞎猜。现在所有请求都经过我的代理客户端发了什么、上游返回了什么一清二楚。这种能力在排查问题时极其宝贵。比如 Codex 偶尔出现“上下文空间不足”error running remote compact task的报错以前我只能看提示猜测现在可以直接检查代理日志里 model 的上下文窗口参数定位到是配置问题还是上游限制。9. 踩坑记录与经验总结最后分享几个我实际踩过的坑这些经验不写在官方文档里但对稳定运行非常关键。坑一换行符导致的 SSE 解析失败有一次我重写了流式转发逻辑结果 Codex 始终收不到完整输出。排查了半天发现罪魁祸首是上游返回的 SSE 事件末尾只有\n而没有\n\n。协议层事件分隔符要求连续两个换行标准解析器会丢弃最后一个不完整事件。解决办法是在转发前统一 normalize 换行upstreamRes.on(data, (chunk) { let text chunk.toString(utf-8); // 确保每个 data 块后都有空行 text text.replace(/\r?\n/g, \n); if (!text.endsWith(\n\n)) { text \n; } res.write(text); });坑二工具调用与 artifacts 屏蔽Codex 在编码过程中会频繁发送工具调用请求包括读取文件、执行命令、生成 artifacts 等。很多上游模型并不支持这些工具或者工具 schema 不兼容导致 400 错误。一个非常直接的解决方案是在代理层直接过滤掉你不希望上游处理的工具让 Codex 只走纯文本对话模式。虽然会损失一部分自动能力但换来的是极高的稳定性。坑三不要忘了设置 Accept 头转发到上游时Accept: text/event-stream这个请求头一定要带。有一次我忘记设置上游虽然返回了流式数据但 Content-Type 不对Codex 等了半天没有渲染任何输出。这类隐藏问题最坑人。坑四上下文压缩任务有时失败Codex 在长对话中会自动触发上下文压缩调用一个远端任务来生成摘要。这个行为有时会因为网络问题失败报错信息里带error running remote compact task。解决方案要么是关闭自动压缩要么是调整 Codex 的上下文阈值。如果你用本地代理也可以通过拦截compact事件返回假的成功响应但这个操作比较复杂不建议新手尝试。坑五模型服务的版本差异模型名称经常变端口也时不时调整。今天用deepseek-chat通了明天可能就提示模型已下架。本地代理的优势是你可以随时改配置不用忍受硬编码。这次的实战项目虽然代码量不大但把整个 AI 编程工具的工作链路跑通之后很多之前觉得神神秘秘的概念就变得具体了。你不再只是“会用一个工具”而是理解了它每一步在做什么、每一份数据流到哪里去。如果你也想深度定制 Codex 的模型接入完全可以照着这个方法搭一条属于你自己的本地 API 通道。遇到问题不要怕多看代理日志多抓请求包答案通常就在你眼前。
返回列表