Codex 接腾讯 LKEAP 报 401?先别怀疑 Key,看看协议路径是不是错了
这次排错来自一个很容易误判的问题:把 Codex 接到腾讯 LKEAP Token Plan 的 OpenAI 兼容接口后,客户端返回 `401 Unauthorized`。
Find related content
Search the site for tools, terms, comparison pages, or related troubleshooting notes without going back to the blog index.
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。

这个 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

服务端返回 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,但“兼容”通常要看具体兼容哪一层:是兼容鉴权方式、模型参数、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 对不对,要先问:客户端想说的协议,服务端真的听得懂吗?
延伸阅读
Continue exploring
Use a tool first
If you need to format JSON, XML, YAML, or prompts, start with the online tools.
See implementation projects
If you want to see how these methods enter real builds and experiments, continue with projects.
Get checklists and templates
If you need checklists, resource entries, or SOP starter packs, continue with resources.
Download reusable skills
If you want repeatable judgment, search, and cleanup actions, continue with the skill market.