
AdapterRegistry 查不到模型名让 Codex 走 TaoToken 对着 LLMGateway 排查降级链路TaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。当LLMGatewayImpl.chat抛出RuntimeException(All models failed)时多数人第一反应是模型服务挂了但在这套 Java 可插拔网关里更常见的原因是 AdapterRegistry 的modelToAdapter里根本没有登记当前modelNamegetAdapter返回 nullpreferredModel和fallbackModels逐个试完也没找到可用适配器最终把异常统一包成All models failed。本文按排障视角走一遍用 Codex 对着AdapterRegistry.register、supports兜底分支和isAvailable判断逐行核对同时把 Codex 的请求指向 TaoToken 的 Key 与 Base URL确认是网关自身路由问题还是适配器可用性配置问题。TaoToken 在这里只提供 Key 和 API Base URL不接手网关的 Adapter、模型路由或降级逻辑。排查完在本地跑一次 chat 请求看 Codex 侧调用是否正常返回再去 TaoToken 后台确认这次排查消耗的调用记录回到官网就能把这把 Key 继续配到其它走同一网关排查的 AI 编程工具上。All models failed 从哪来LLMGatewayImpl.chat 的降级链路先定位报错位置。LLMGatewayImpl.chat的主流程通常是这样调用determineModel(request)确定本次要用的模型名。构造modelsToTry先放首选模型如果enableFallback为 true再把fallbackModels追加进去。遍历modelsToTry对每个模型执行getAdapter(model)。只有adapter ! null adapter.isAvailable()都成立才会把 request 的 model 替换掉然后调用adapter.chat(request)。如果整个循环走完都没有返回最后抛出RuntimeException(All models failed, lastException)。问题就藏在第 3 步和第 4 步之间。getAdapter会先查modelToAdapter查不到再遍历所有适配器调supports(modelName)兜底。如果modelToAdapter里没有登记而supports也没命中adapter就是 null。此时循环不会抛异常只会继续试下一个模型如果所有模型都这样最终报错信息只有一句All models failed看不到“到底是哪个模型没适配器”。另一种情况更隐蔽adapter能查到但isAvailable()返回 false。BaseModelAdapter.isAvailable()常见实现是判断apiKey ! null !apiKey.isEmpty()。如果项目里改成了走 TaoToken但 Adapter 仍然从旧厂商环境变量读 KeyapiKey为空适配器就被判为不可用。getAvailableAdapters()也会把它过滤掉chat循环同样跳过最后仍然是All models failed。所以排查目标可以拆成两条线路由线deepseek-chat、qwen系列等 modelName 有没有进入modelToAdapterfallbackModels和映射 key 是否一致supports兜底能不能命中别名。可用线Adapter 的apiKey、baseUrl有没有正确注入isAvailable为什么返回 false请求有没有真正发到 TaoToken 的 Base URL。TaoToken 前置给 Codex 准备 Key 与 Base URL这一步不写 Adapter 实现也不改网关降级逻辑只把外部调用入口准备好。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号在控制台创建一个 API Key。这个 Key 就是后续填进 Codex 和 Adapter 配置里的YOUR_API_KEY。API Base URL 使用https://taotoken.net/api注意这个地址不加 UTM 参数直接作为请求根地址。TaoToken 在这里的角色是提供统一 Key 与 Base URL让 Codex 侧和网关侧有一个稳定的外部调用入口。它不负责注册 Adapter不维护modelToAdapter映射也不参与preferredModel fallbackModels的降级选择。排查时仍然要回到项目里的AdapterRegistry、DeepSeekAdapter、QwenAdapter和LLMGatewayImpl逐行核对模型名和可用性判断。如果你习惯先确认 Key 能通可以在创建后去模型对话页面发一条简单消息但本篇重点是排障所以拿完 Key 就进入配置和代码核对。可复制配置Codex config.toml 与 AdapterRegistry 映射核对先让 Codex 走 TaoToken。Codex 的配置文件通常是~/.codex/config.toml可以这样写# ~/.codex/config.toml model deepseek-chat model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY然后在终端导出 Keyexport TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell 可以写成$env:TAOTOKEN_API_KEYYOUR_API_KEY这里要确认三件事base_url是https://taotoken.net/api不是带/v1的重复路径env_key与实际环境变量名一致model填的是你准备排查的模型名比如deepseek-chat或qwen-max。如果 Codex 仍然报旧地址说明配置没被读取或者当前 shell 没有继承环境变量。接下来让 Codex 对着AdapterRegistry.register的映射写入和supports兜底分支逐行核对。可以把下面这段核对清单交给 Codex要求它只读代码并输出结论请只读取当前仓库不要改代码。按顺序输出 1. AdapterRegistry.register 中 modelToAdapter.put 的所有 key 2. DeepSeekAdapter.getSupportedModels() 和 QwenAdapter.getSupportedModels() 的实际返回值 3. fallbackModels 从哪个配置读取实际值是什么 4. supports 兜底分支是否处理 deepseek-*、qwen-* 前缀还是只做 equals 5. getAvailableAdapters 中 isAvailable 依赖的 apiKey、baseUrl 分别从哪里注入。重点看AdapterRegistry.registerpublic void register(ModelAdapter adapter) { String adapterName adapter.getName(); adapters.put(adapterName, adapter); for (String modelName : adapter.getSupportedModels()) { modelToAdapter.put(modelName, adapterName); } }如果DeepSeekAdapter.getSupportedModels()漏了deepseek-chat或者QwenAdapter.getSupportedModels()只写了qwen-max而请求用的是qwen-plus那么modelToAdapter里就没有对应 key。getAdapter第一步查表返回 null第二步遍历supports。如果supports只做了等值判断qwen-plus同样命中不了adapter 就是 null。再看supports兜底分支public ModelAdapter getAdapter(String modelName) { String adapterName modelToAdapter.get(modelName); if (adapterName ! null) { return adapters.get(adapterName); } for (ModelAdapter adapter : adapters.values()) { if (adapter.supports(modelName)) { return adapter; } } return null; }排查时不要只看modelToAdapter也要看supports是否真的被调用。可以在兜底分支加一行临时日志把modelName和每个 adapter 的getName()打出来。确认入参没有多余空格、大小写一致、版本后缀一致。最后看isAvailableOverride public boolean isAvailable() { return apiKey ! null !apiKey.isEmpty(); }走 TaoToken 后Adapter 的apiKey应该填 TaoToken 的 KeybaseUrl应该填https://taotoken.net/api。如果apiKey仍从旧厂商变量读取或者配置加载顺序导致它被覆盖成空字符串isAvailable()就会返回 false。getAvailableAdapters()也会把该适配器过滤掉看起来像“一个可用适配器都没有”。验证请求本地 chat 成功与 TaoToken 调用记录配置和映射核对完后先在本地直接发一次 chat 请求确认 TaoToken 侧 Key 和 Base URL 可用curl -sS https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer YOUR_API_KEY \ -H Content-Type: application/json \ -d { model: deepseek-chat, messages: [ {role: user, content: ping} ], stream: false }如果返回 JSON 里包含choices说明 Key 与 Base URL 没问题。接着回到 Codex让它发起一次走同一网关的调用观察日志里是否还出现No adapter mapped for model...Adapter unavailable for model...All models failed理想结果是 Codex 侧正常返回内容网关日志显示某个模型命中了适配器并且adapter.isAvailable()为 true。然后再去 TaoToken 后台确认这次排查消耗的调用记录看请求时间、模型名和调用状态是否对上。如果后台没有记录说明请求根本没发到 TaoToken需要回头检查 Codex 的config.toml、环境变量和 Adapter 的baseUrl。如果后台有记录但网关仍报错说明问题在适配器返回处理或模型名映射不在外部入口。本篇常见错排查AdapterRegistry、supports 与 isAvailable下面这些点按出现频率从高到低排建议逐条核对。第一getSupportedModels漏了实际模型名。deepseek-chat、deepseek-coder、qwen-max、qwen-plus、qwen-turbo这些名字只要有一个没写进注册列表modelToAdapter就查不到。核对方法是把getSupportedModels()的返回值和请求里的model字段逐字对比注意版本后缀和连字符。第二fallbackModels里的名字和modelToAdapter的 key 对不上。比如 fallback 配的是qwen-max但注册 key 是qwen-max-latest或者首选模型写deepseek但映射只登记了deepseek-chat。降级链路会继续往下试但每个都查不到 adapter最后统一报All models failed。第三supports兜底分支没有生效。如果modelToAdapter没有精确 keygetAdapter会遍历所有 adapter 调supports。此时要检查supports是否只做了equals是否漏了startsWith(deepseek-)、startsWith(qwen-)这类前缀匹配。如果前缀匹配写错或者入参带了空格兜底同样返回 null。第四isAvailable被判空。BaseModelAdapter.isAvailable()通常只看apiKey是否非空。走 TaoToken 后要确认 Adapter 注入的是 TaoToken 的 Key而不是旧厂商 Key。如果配置来源是环境变量检查变量名是否和代码读取的一致如果配置来源是配置文件检查加载顺序和默认值。第五determineModel选出的模型本身不可路由。请求显式指定的 model 拼错、preferredModel为空、getSupportedModels()第一个模型取不到都会让首选模型进入不了映射。建议在determineModel返回后打印一次最终模型名再和modelToAdapter.keySet()对比。第六Codex 配置没生效。config.toml的base_url多写/v1、env_key没导出、model_provider选错都会让请求走不到 TaoToken。先用 curl 确认 TaoToken 侧通再回来看 Codex 配置能快速区分是外部入口问题还是网关内部问题。第七异常被吞。chat循环里的 catch 如果只打印简略信息最终All models failed的 cause 可能被覆盖。排查阶段建议把每个模型的失败原因打全明确区分adapter null、!adapter.isAvailable()和adapter.chat抛出的业务异常。排查完成后TaoToken API Keys 与接入文档这套排查链路里TaoToken 只负责 Key 和 Base URLhttps://taotoken.net/api。Adapter 注册、modelToAdapter映射、preferredModel fallbackModels降级顺序、supports兜底和isAvailable判断仍然在 Java 网关项目里完成。排查完本地 chat 请求并确认 TaoToken 后台有调用记录后就可以把同一把 Key 复用到其它走这套网关的 AI 编程工具。需要创建或管理 Key可以直接去 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys。需要核对 Base URL、鉴权头和 OpenAI 兼容路径可以看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc。如果后续要把这把 Key 长期接到 Codex、Agent 或持续编码流程里可以再看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan。排查时优先用 API Keys 和接入文档确认入口再回到AdapterRegistry把模型名、fallback 列表和可用性判断逐一对齐All models failed基本就能收敛到具体那一行。