AdapterRegistry 查不到模型名?让 Codex 走 TaoToken 对着 LLMGateway 排查降级链路
TaoToken 官网:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。当LLMGatewayImpl.chat抛出RuntimeException("All models failed")时,多数人第一反应是模型服务挂了,但在这套 Java 可插拔网关里,更常见的原因是 AdapterRegistry 的modelToAdapter里根本没有登记当前modelName,getAdapter返回 null,preferredModel和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 仍然从旧厂商环境变量读 Key,apiKey为空,适配器就被判为不可用。getAvailableAdapters()也会把它过滤掉,chat循环同样跳过,最后仍然是All models failed。
所以排查目标可以拆成两条线:
- 路由线:
deepseek-chat、qwen系列等 modelName 有没有进入modelToAdapter,fallbackModels和映射 key 是否一致,supports兜底能不能命中别名。 - 可用线:Adapter 的
apiKey、baseUrl有没有正确注入,isAvailable为什么返回 false,请求有没有真正发到 TaoToken 的 Base URL。
TaoToken 前置:给 Codex 准备 Key 与 Base URL
这一步不写 Adapter 实现,也不改网关降级逻辑,只把外部调用入口准备好。打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_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"然后在终端导出 Key:
export TAOTOKEN_API_KEY=YOUR_API_KEYWindows PowerShell 可以写成:
$env:TAOTOKEN_API_KEY="YOUR_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.register:
public 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()打出来。确认入参没有多余空格、大小写一致、版本后缀一致。
最后看isAvailable:
@Override public boolean isAvailable() { return apiKey != null && !apiKey.isEmpty(); }走 TaoToken 后,Adapter 的apiKey应该填 TaoToken 的 Key,baseUrl应该填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没有精确 key,getAdapter会遍历所有 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 URL:https://taotoken.net/api。Adapter 注册、modelToAdapter映射、preferredModel + fallbackModels降级顺序、supports兜底和isAvailable判断,仍然在 Java 网关项目里完成。排查完本地 chat 请求并确认 TaoToken 后台有调用记录后,就可以把同一把 Key 复用到其它走这套网关的 AI 编程工具。
需要创建或管理 Key,可以直接去 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys。需要核对 Base URL、鉴权头和 OpenAI 兼容路径,可以看接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc。如果后续要把这把 Key 长期接到 Codex、Agent 或持续编码流程里,可以再看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan。排查时优先用 API Keys 和接入文档确认入口,再回到AdapterRegistry把模型名、fallback 列表和可用性判断逐一对齐,All models failed基本就能收敛到具体那一行。