(最后更新: 2026-05-18T15:30:00+08:00) AI 工具排障

Codex 接腾讯 LKEAP 报 401?先别怀疑 Key,看看协议路径是不是错了

这次排错来自一个很容易误判的问题:把 Codex 接到腾讯 LKEAP Token Plan 的 OpenAI 兼容接口后,客户端返回 `401 Unauthorized`。

#Codex#Tencent LKEAP#OpenAI compatible#Responses API#Chat Completions#Protocol Debugging#Agent Workflow

需要继续找相关内容?

如果你想继续查工具名、术语、对比页或相关问题,可以直接搜全站,不用回到博客列表页重找。

Codex 接腾讯 LKEAP 报 401?先别怀疑 Key,看看协议路径是不是错了

这次排错来自一个很容易误判的问题:把 Codex 接到腾讯 LKEAP Token Plan 的 OpenAI 兼容接口后,客户端返回 401 Unauthorized

很多人看到 401,第一反应都是 API Key 错了、环境变量没生效、账号权限不够。这个方向当然要查,但在这次问题里,真正有价值的证据不是 401 本身,而是错误日志里的请求 URL:

https://api.lkeap.cloud.tencent.com/plan/v3/chat/completions/responses

注意最后这一段:/chat/completions/responses

报 401 不一定是 Key 错了

这个 URL 形状本身就不对。腾讯 LKEAP 这里提供的是 OpenAI Chat Completions 风格入口,而新版 Codex 期望的是 Responses API 风格入口。你把 base_url 配到 /chat/completions,Codex 又按照 wire_api = "responses" 继续追加 /responses,最后就拼出了一个服务端本来不该接收的路径。

所以这篇不是单纯讲“怎么配腾讯 LKEAP”,而是复盘一次更通用的排错方法:当一个 AI 编程工具接入所谓 OpenAI-compatible 服务失败时,不能只盯着 Key,要先确认客户端的 wire protocol、服务端的 endpoint shape,以及最终请求路径是不是同一种协议。

现场现象:看起来像鉴权失败

当时配置大概是这样的:

[model_providers.custom]
name = "custom"
wire_api = "responses"
requires_openai_auth = true
base_url = "https://api.lkeap.cloud.tencent.com/plan/v3/chat/completions"

这段配置表面看起来有道理:腾讯这边给的是 chat completions 路径,Codex 这边需要一个 base URL,于是就把完整路径填进去。

但问题就在这里。新版 Codex 不是在这个 URL 上直接发 Chat Completions 请求,而是根据 wire_api = "responses" 去构造 Responses API 请求。于是最终请求变成:

/plan/v3/chat/completions/responses

错误 URL 暴露了根因

服务端返回 401,表面像鉴权失败,实际已经不是正常的鉴权链路了。路径错了,后面再怎么换 Key、改环境变量,都可能只是在错误方向上反复试。

这也是我后来总结的一个经验:遇到 API 报错,不要只看状态码,要把完整 URL、方法、协议形态一起看。状态码是症状,URL 形状才经常暴露病因。

为什么不能直接改成 Chat Completions?

如果问题只是路径拼错,那能不能把 Codex 改回 Chat Completions 模式?

这正是第二个坑。新版 Codex 已经不接受 wire_api = "chat" 这类配置。你尝试切回 Chat Completions,客户端会直接提示:

invalid configuration: `wire_api = "chat"` is no longer supported.
How to fix: set `wire_api = "responses"` in your provider config.

也就是说,这不是“base_url 少写一层”那么简单,而是客户端和服务端对协议的期待不一致:

Codex 这一侧,希望你提供一个 Responses API 风格的服务;

腾讯 LKEAP 这个入口,实际暴露的是 Chat Completions 风格的服务;

你把 Chat Completions 的 URL 填给一个只会发 Responses 请求的客户端,它当然会拼出奇怪的地址。

OpenAI-compatible 不等于客户端全兼容

这类问题以后会越来越常见。很多平台会说自己是 OpenAI-compatible,但“兼容”通常要看具体兼容哪一层:是兼容鉴权方式、模型参数、Chat Completions 路径,还是完整兼容 Responses API、工具调用、流式事件和多轮状态?

这些不是一个词能概括的。

真正的排错顺序

我现在遇到类似问题,会按这个顺序查。

第一步,看最终请求 URL。

如果 URL 已经出现 /chat/completions/responses/v1/v1/responses/chat/completions 这种明显叠层,就不要急着换 Key。先判断是不是 base URL 和客户端自动追加路径冲突。

第二步,看客户端要求的 wire protocol。

Codex 这里明确要求 responses。这意味着它发出的请求体、响应解析逻辑、流式事件形态,都是围绕 Responses API 设计的。服务端只支持 Chat Completions 时,哪怕鉴权过了,后面也可能在响应字段、工具调用、stream chunk 上继续出问题。

第三步,看服务端实际暴露的 endpoint。

腾讯 LKEAP 这里的目标入口是:

https://api.lkeap.cloud.tencent.com/plan/v3/chat/completions

这个路径本身已经说明,它是 Chat Completions 形态。把它当成 Responses API base URL 使用,风险很高。

第四步,才是检查 Key、环境变量和权限。

当然,鉴权永远要查。但顺序很重要。如果路径都错了,Key 查一小时也不会有结果。

可行思路:本地做一层协议转换

这次记录里的解决思路,是写一个本地 Node.js 代理。

它不让 Codex 直接打腾讯的 /chat/completions,而是让 Codex 先请求本地:

http://127.0.0.1:15722/v1/responses

本地代理再把 Responses 风格的最小请求转换成 Chat Completions 请求,转发到:

https://api.lkeap.cloud.tencent.com/plan/v3/chat/completions

返回时,再把 Chat Completions 的结果包装成 Codex 能接受的 Responses 形态。

本地代理负责翻译协议

这样做的好处是边界清楚:Codex 认为自己在和 Responses API 说话;腾讯 LKEAP 仍然接收 Chat Completions 请求;中间的协议差异由本地代理承担。

配置上,Codex 指向本地代理:

model_provider = "tencent_lkeap_proxy"
model = "glm-5.1"
disable_response_storage = true

[model_providers.tencent_lkeap_proxy]
name = "Tencent LKEAP via local Responses proxy"
wire_api = "responses"
base_url = "http://127.0.0.1:15722/v1"
requires_openai_auth = true
env_key = "OPENAI_API_KEY"

腾讯侧真实 Key 不放进 Codex 配置,而是放在代理进程的环境变量里:

$env:TENCENT_LKEAP_API_KEY="REDACTED"
$env:TENCENT_LKEAP_MODEL="glm-5.1"
node tools\codex-responses-to-chat-proxy.mjs

因为 Codex 这个 provider 形态仍然要求一个 OpenAI auth 变量,可以给它一个本地 dummy 值:

$env:OPENAI_API_KEY="local-proxy-dummy"

本地代理忽略这个 dummy header,只使用 TENCENT_LKEAP_API_KEY 调腾讯上游。这样至少不会把真实密钥散落在多个客户端配置里。

这不是“完美解决”,而是兼容层方案

这里要讲清楚边界。

原始记录里,已经完成的是代理脚本语法检查,比如:

node --check tools\codex-responses-to-chat-proxy.mjs

但完整的端到端 smoke test、复杂工具调用、长任务流式事件,还需要继续验证。第一版本地代理更适合先处理普通文本和基础 streaming,不应该直接宣传成“完全兼容 Codex 所有能力”。

这点很重要。很多技术文章的问题不是没有方案,而是把一个 workaround 写成了最终答案。实际工程里,协议转换层通常要逐步加固:普通文本、流式输出、错误格式、工具调用、取消请求、超时、日志脱敏,每一项都可能有细节。

所以我更愿意把它称为:一个可验证的排错方向,一个局部可行的兼容思路,而不是一键解决所有问题。

什么时候不建议上代理?

本地代理很有用,但不是每次都应该上。

如果上游已经提供原生 Responses API,优先用原生入口。原生入口通常会更好地处理流式事件、工具调用、错误码、请求取消和计费字段。代理只适合在“客户端只能说 A 协议、服务端只会听 B 协议”的过渡场景里使用。

如果只是环境变量没读到、Key 填错、模型名写错,也不需要上代理。先用最小请求把上游跑通,比如直接用 curl 或一个短 Node 脚本请求目标 endpoint,确认模型能回答,再接入 Codex。否则你可能把简单配置问题包装成复杂架构问题。

如果团队里没有人愿意维护这层转换,也要谨慎。代理不是一次性脚本,它会变成运行链路的一部分。后续 Codex 改了 Responses 字段,或者腾讯 LKEAP 调整了返回格式,你都要跟着改。对个人实验来说可以接受,对生产链路来说就必须有日志、版本、回滚和监控。

我更推荐的使用方式是:先把代理当成排错工具,用它证明协议转换这条路能走;确认价值后,再决定要不要沉淀成长期工具。

可以复用的排错清单

以后再遇到类似“OpenAI-compatible 接不上 AI 编程工具”的问题,我会直接套这个清单。

先记录原始报错,不要只截最后一句。至少保留状态码、完整 URL、请求方法、客户端版本、模型名和 provider 配置。

再判断 URL 有没有被重复拼接。/v1/v1/chat/completions/responses/anthropic/v1/messages/messages 这类路径,一眼就能看出 base URL 和客户端自动路径规则冲突。

然后确认客户端到底要哪种协议。Codex、OpenCode、Claude Code、不同 AI SDK 的 provider,背后使用的 wire protocol 可能完全不同。同样叫 OpenAI 兼容,有的要 Chat Completions,有的要 Responses,有的还会要求特定的 stream event。

最后才进入鉴权排查:Key 是否在当前进程环境里,代理是否改写了 header,服务端 token plan 是否支持这个模型,账号是否有对应权限。这样排查会慢一点开始,但整体更快结束。

这次排错带来的通用经验

第一,看到 401 不要立刻判定 Key 错。鉴权问题当然常见,但 URL 形状、请求方法、协议类型更值得先看。

第二,OpenAI-compatible 不是一句万能保证。Chat Completions 兼容、Responses API 兼容、Anthropic-compatible、工具调用兼容,是不同层级的事情。

第三,AI 编程工具越来越依赖客户端协议。以前很多问题是“模型能不能回答”,现在更多问题是“客户端和服务端能不能按同一种事件格式对话”。

第四,本地代理是个实用办法,但要带着边界使用。它适合把路径、鉴权、请求体、响应体做显式转换,也方便打日志;但它也会带来维护成本,不能替代上游原生支持。

第五,排错记录要保留证据。像 /chat/completions/responses 这种错误 URL,比一句“报 401”有价值得多。它能让后面的人少走很多弯路。

这次问题最值得记住的一句话是:

当 AI 编程工具接入第三方兼容接口失败时,不要只问 Key 对不对,要先问:客户端想说的协议,服务端真的听得懂吗?

延伸阅读

继续延伸

评论