CC Switch 报 model not found 该查哪几层

2026-08-31

模型 ID 是从供应商控制台复制过来的,端点也填对了,请求打回来却写着 model xxx not found。这类报错最难受的地方在于:文案指着模型,根因经常在别处。在 CC Switch v3.20.1 的仓库里,至少有三条互不相干的路径最终会汇成这一句报错或它的近亲,而其中最反直觉的一条,跟模型名一个字的关系都没有。

先说清楚这篇的依据。本文只静态读了 farion1231/cc-switch 的快照 3217f725(仓库内版本号 3.20.1),核对日 2026-08-31,读的是 src/src-tauri/ 的源码、docs/user-manual/ 下的中文手册和 v3.19.2..v3.20.1 区间的提交记录。我们没有编译过它,没有安装过这个桌面应用,也没有向任何上游发过一次请求,所以下面不描述界面长什么样、操作起来什么感觉,只有文件、行号和字段。

第一步:先分清是谁在比对这个字符串

「模型名」这三个字在这套工具里至少落在三个不同的比对现场,它们失败时的表现完全不同:

  • 上游 API 在比对——请求发出去了,上游说它的目录里没有这个模型,返回体带 model not found 一类文案;
  • 被管理的 CLI 在比对——Codex 的 /model 命令列不出你配的第三方模型名,因为它读的是自己的模型目录文件;
  • CC Switch 自己在比对——用量面板拿模型 ID 去匹配定价表,这一层匹配不上,表现是费用估算与实际对不上,而不是请求被打回。

判定动作很具体:打开设置里的用量 Tab,请求日志把每条请求拆成十来个字段(v3.20.1 手册口径,字段本身会随版本增减),其中「模型」一列记的是计费模型,另有一列「状态」记 HTTP 状态码,筛选器支持按状态码和模型名文本过滤(手册 docs/user-manual/zh/4-proxy/4.4-usage.md:114-125:149-155)。如果日志里根本找不到这条请求,说明它没走代理——手册 4.4:14-17 写明用量有两个数据来源,CLI 会话日志那一路不需要代理拦截,所以「面板里没有」不等于「请求没发出去」。

第二层:共享单槽凭据文件,让几张卡的密钥互相趋同

这是本区间里最值得单独拿出来讲的一条。提交 bbe8bb93(PR #6534,commit body 写着 Fixes #6414)修的问题,最终症状就是 model xxx not found,而根因离它隔了三层。

触发前提是 bearer-token 模式:为了保留 Codex 的官方登录、同时把请求路由到第三方,preserveCodexOfficialAuthOnSwitch 被打开。此时 ~/.codex/auth.json 是一个单槽、且不带供应商身份的共享文件,里面装的可能是另一张供应商卡遗留下来的 key。接着,EditProviderDialog 用 live 配置作表单基底,useCodexConfigState 用这份陈旧的 auth.json 初始化 codexAuth,而 pickCodexApiKey 的取值次序又是auth.OPENAI_API_KEY、后 bearer token。三者叠加的结果是:编辑框里显示的是别人的 key,你一按保存,这个错的 key 就被固化进当前这张卡的数据库记录。反复编辑几轮,共享同一个 base URL 的几张卡密钥互相趋同——最后你用 A 的密钥去请求 B 的模型,上游回你一句「模型不存在」。

v3.20.1 的修法在前端。src/components/providers/EditProviderDialog.tsx:56-84 新增了 reconcileCodexLiveAuth(),逻辑分三段读:category === "official" 的官方卡直接原样返回;从 live 的 config 文本里用 extractCodexExperimentalBearerTokensrc/utils/providerConfigUtils.ts:1109)取 bearer,取不到就直接返回 live——默认模式下这个函数是个 no-op;取到了,才把 auth 模板换成数据库里存的 provider.settingsConfig.auth,而不是 live 的那份。:73-80 还有一道保护:模板里若存在 OAuth 类材料(除 auth_modeOPENAI_API_KEY 之外的非空字段)且没有该供应商自己的 API key,就整段不做替换,源码注释写明这是要和后端的 should_restore_codex_provider_token_for_backfill 保持一致,「一个 OAuth-only 的供应商不能被悄悄转成 API-key 供应商」。调用点在 :242-250,只有 appId === "codex" 且确实取到了 liveSettings 时才走这条路,数据库快照与预设保持原来的 auth 优先次序。配套还有 src/components/providers/forms/hooks/useCodexConfigState.ts:118 附近一处纯顺序调整,把「设置 auth.json」挪到「设置 config.toml」之后。

这条提交的历史本身也值得看一眼:子提交序列里先落了一版后端修法,随即 Revert 掉,再从前端重做。也就是说单读这条 commit message,你会同时读到两套相互矛盾的论证,最终生效的是前端那套。

怎么验证处置到位:这次修复带了两个新测试,tests/components/EditProviderDialog.test.tsxtests/hooks/useCodexConfigState.bearer.test.ts,回归用例覆盖的正是「编辑框显示的 key 与 live bearer 对齐」。落到自己机器上,可执行的动作是逐张卡核对数据库记录里的密钥与 ~/.codex/auth.json 当前内容是否是你以为的那一份。这些都是本机的敏感数据,核对时注意不要把它们贴进任何工单或聊天窗口;这一段是通用运维习惯,不是该项目文档的内容。

第三层:模型目录不是你以为的那一份

如果报错来自 Codex 的 /model 列不出模型,那就换一条线索。手册 docs/user-manual/zh/2-providers/2.1-add.md:580-582 写得很硬:模型映射表会生成 Codex 的 model_catalog_json,表中条目按填写内容原样保存,是模型列表的唯一来源,而且修改后必须重启 Codex 才刷新,因为 model_catalog_json 是在 Codex 启动时加载的。改完映射表不重启,看到的就还是旧目录——这是最省事也最容易被跳过的一次判定。

还有一处跟目录文件的指针有关。提交 413c09e0(PR #6087)修的是 src-tauri/src/codex_config.rs:2133set_codex_model_catalog_json_field:它的 Some 分支曾经无条件model_catalog_json 覆写成 cc-switch 自有的文件名,把用户自定义的 catalog 路径或文件名直接丢掉。修法是把 None 分支里早就存在的所有权检查照搬到 Some 分支——只有在指针缺失、或者这份文件已经归 cc-switch 所有时才认领,用户自管的外部 catalog 文件不动。注意版本口径:这条是 v3.19.2 发布之后的热修,随 v3.20.0 出,v3.20.1 里已经是修好的状态。所以「我明明自己维护了一份 catalog」这个场景,在 v3.19.2 上和在 v3.20.1 上行为是不同的。

第四层:模型 ID 的字符串工程,但它多半不报这个错

最后一层最容易被误当成根因,所以要专门说清楚它负责什么。

CC Switch 在把请求记账的时候,会先对模型 ID 做一轮清洗再去匹配定价条目。v3.20.1 的手册 4.4:216-234 把这套清洗写成六条,规则数量本身会随版本增减,这里只讲每一条在处理什么形状的字符串:去掉最后一个 / 之前的前缀并转小写(处理 <某前缀>/<模型名> 这种带命名空间的写法);去掉 : 之后的后缀,并去掉末尾的 [1m];把 @ 替换成 -;去掉常见的包装前缀、版本后缀和日期后缀(-YYYY-MM-DD-YYYYMMDD 两种形状);部分模型族允许用短 ID 反向匹配带版本号的定价条目。手册据此给出结论:定价配置里要填清洗后的 ID,不是请求里的完整原始模型名。顺带一提,规则里那条「去掉末尾的 [1m]」和手册 2.6:57 从 Claude Code 导入时「把旧的 [1M] 后缀转成 supports1m 标记」,是同一个历史包袱的两处体现——注意这两处连大小写都不一样,一处是小写 [1m]、一处是大写 [1M],照抄时别自作主张把它们统一掉。

这一层没匹配上的后果,是费用估算落空、模型统计里出现一个你不认识的名字,不是 model not found。而且它和上一层对模型名的要求方向恰好相反:定价配置要填清洗后的短 ID,映射表要填上游的真实模型名且原样保存。而且这两处并不挨着:定价配置在设置 → 高级 → 定价配置(手册 4.4:199),模型映射表在供应商这一侧的配置里(2.1:556-582)。同一个工具里两处都要填模型名、规则却相反,是这套配置里最容易记混的一处。

什么情况说明根因不在上面这几层

排查文章最后这一步不能省,否则就成了报错大全。

用 Stream Check 的结果当「模型存在性」证据是不成立的。 这里有一处文档与源码对不上:手册 docs/user-manual/zh/4-proxy/4.5-model-test.md:5 说它「通过发送实际的 API 请求来测试」,:80-83 进一步说会发一句简短 prompt、限制最大输出 token、看流式首字节;而 src-tauri/src/services/stream_check.rs:50-59 那段 impl Default for StreamCheckConfig 的注释与默认值写的是:可达性探测打的是 base_url 的小请求、仅读响应头、不等待模型生成,默认超时也因此从旧的四十多秒降到个位数秒、默认重试降到一次(这些是 v3.20.1 的默认配置,可配,且会随版本变)。两处不一致,以我们实读的仓库状态为准。另外手册 4.5:124-132 自己也提示了一种误判:测试用的模型与你实际请求的模型不是同一个,于是「测试失败但实际可用」。

认证类错误码不要往模型上归因。 手册 2.1:202-205 把自动获取模型的四类常见错误分得很清楚:401/403 是 API Key 的问题;404/405 意味着该供应商没有提供 /v1/models 端点,只能手填模型 ID——这是「端点没有这个能力」,不是「模型不存在」。

端点拼接问题通常表现为 404 而不是模型不存在。 手册 docs/user-manual/zh/2-providers/2.3-edit.md:89-93 的端点格式表里,Codex 的示例比 Claude、Gemini 多一段 /v12.1:516-538 的完整 URL 端点模式改的也是「base_url 当前缀往后拼固定路径」还是「填什么就打什么」。这两处配错,错的是路径。

只有一张供应商卡、也没开 bearer-token 模式的话,第二层根因不成立。 前面读过 reconcileCodexLiveAuth 的第一段短路逻辑:取不到 bearer 就直接返回 live,默认模式下它根本不参与。

一句话收尾:碰到跟名字有关的报错,先别急着核对名字,先问一句「现在是谁在拿这个字符串跟谁比对」。上面四层里,只有第一层是真的在比对模型名。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册、 路由指南与发布说明,以及 src/src-tauri/tests/ 的源码整理, 核对日 2026-08-31,对应仓库快照 3217f725(仓库内版本号 3.20.1)。 本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用, 因此不涉及界面外观、操作手感与切换速度的任何描述。 文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。 该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。

留言讨论

评论发布后会被人工复核,违规内容将被删除。

    还没有人评论,来说说你的看法

    如果发表没有反应,可以前往联系我们告诉我们。

    在 CC Switch 里加一个国内直连的供应商

    力达云网关,注册送 ¥5 额度,一期提供 DeepSeek。

    去添加

    这个页面有问题?

    提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。