文档说发真实请求,代码只探连通性:Stream Check 的三处口径差
如果你按 CC Switch 的用户手册去理解「模型检查」这个功能,再去读后端源码,会发现两边讲的基本不是一件事。手册 docs/user-manual/zh/4-proxy/4.5-model-test.md 开头写它「通过发送实际的 API 请求来测试」,验的是模型是否存在、API Key 是否有效;而 src-tauri/src/services/stream_check.rs 的模块头注释写的是「仅探测供应商 base_url 是否可达,不发送真实大模型请求」。
这不是一处措辞出入,是三处:性质、默认参数、与熔断器的关系。三处都能落到具体行号上,你可以自己去核。
以下全部基于我们本地 clone 的 cc-switch 仓库快照 c39c903(提交日期 2026-08-10),仓库内版本号 3.19.2(package.json 与 src-tauri/Cargo.toml 两处一致)。我们只读源码与文档文本,没有安装也没有运行过这个桌面应用。
第一处:它到底发不发真实请求
手册这一侧:4.5-model-test.md:5-9 把功能说明写成「通过发送实际的 API 请求来测试」,随后列出的测试目标里包括「模型是否存在」「API Key 是否有效」。同一篇的「测试内容」小节(:80-83)进一步描述这个请求长什么样——发送简短的 prompt(如 “Hi”)、限制最大输出 token(通常 10-50)、使用流式响应检测首字节时间。
代码这一侧:src-tauri/src/services/stream_check.rs:1-6 的模块自述写的是另一套判定规则——收到任意 HTTP 响应(含 4xx、5xx)即判定「可达」,只有 DNS 解析失败、连接被拒、TLS 出错、超时这类网络级错误才判「不可达」。
两处不一致,以我们实读的仓库状态为准。说完差异就停,我们不推断哪一处才算数,也不用它去评价这个项目。
真正需要你换个脑子的是那条判定规则本身。 「任意 HTTP 响应都算可达」意味着:一个上游把你的请求以 401 或 403 顶回来,在这个检查里同样是「可达」。也就是说,检查结果是绿的,跟你的 Key 有没有配对、模型名写没写错,是两个独立的问题——前者只回答「能不能连到那台机器」。
如果你把它当成「配置正确性体检」,就会踩空。
那两个永远为空的字段,是这处差异的自证锚点
stream_check.rs:72-78 里有两个字段值得单独拎出来:结果结构里的 model_used 恒为空串,error_category 恒为 None,注释写明它们是为了兼容 stream_check_logs 表结构而保留的。
这两行是本篇最省事的核查入口。一个「发了真实模型请求」的检查,没有理由让 model_used 永远是空的;一个把可达性和鉴权失败区分开的检查,也不需要把 error_category 恒定成 None。你只要看这两行,就能确认代码这一侧的实际语义。
与之并列的另一个事实是:手册 4.5 里有一张按应用配置测试模型的表(该篇合计 163 行,结构见「功能说明 / 每应用测试模型配置 / 检查参数 / 执行方式 / 测试内容 / 结果与健康状态 / 与故障转移的集成 / 常见问题」)。文档层有按应用配的测试模型,代码层的结果里模型名恒空——两个事实并排放着,各自的位置都标出来了,你自己看。
第二处:默认超时与重试次数,两边给的数不一样
这一处最容易直接影响你的判断,因为它是可配置项,你在配的时候需要知道基线是多少。
| 参数 | 用户手册 4.5 写的 | 代码 StreamCheckConfig::default() |
|---|---|---|
| 单次超时 | 默认 45 秒,范围 10-120 秒(4.5-model-test.md:45-47) | timeout_secs: 8(stream_check.rs:50-56) |
| 最大重试 | 默认 2 次,范围 0-5 次(4.5-model-test.md:53-55) | max_retries: 1(stream_check.rs:57) |
| 降级阈值 | 该篇有对应小节 | degraded_threshold_ms: 6000(stream_check.rs:50-61) |
这张表的读法:左列是文档层的口径,右列是代码里 Default 实现给出的值,两列不一致。
值得注意的是代码里的注释——stream_check.rs:50-56 的注释明写这个超时是从旧的真实请求检查「45s → 8s」降下来的。也就是说,45 这个数字在代码注释里以「旧值」的身份出现过一次。我们把这条注释照实记下来,至于文档为什么还是 45,不做推断。
再强调一遍全批通用的分寸:8 秒是代码里的默认配置,不是「你用起来会怎样」的保证。 网络环境、上游位置、本机 DNS 都不在这个数字的管辖范围内。同理,degraded_threshold_ms: 6000 只是一个把结果标成「较慢」的阈值常量,不构成任何延迟承诺,也不能拿它去推算你会不会掉线。
顺带划清一条容易混的边界:本篇讲的连通性检查,和供应商列表里的「测速」是两个服务,测速那套超时钳制常量在 src-tauri/src/services/speedtest.rs 里另有一份,我们另有一篇专门讲它。别把两处的默认值记串了。
第三处:它到底管不管熔断恢复
这是三处里牵扯最大的一处,因为它决定了你排障时该往哪儿看。
手册 4.5 的「与故障转移集成」小节(4.5-model-test.md:106-120)写了两件事:开启代理服务后,系统会定期对故障转移队列中的供应商执行健康检查;以及供应商从熔断状态恢复时,会「执行模型检查验证可用性,检查通过后恢复正常状态」。
代码这一侧把相反的语义写成了一条显式不变量。stream_check.rs:13-17 明写:连通性检查绝不触碰故障转移熔断器——一个返回 403 的供应商在本检查里算「可达」,但它对真实流量是坏的;熔断器只由 proxy/forwarder.rs 转发真实流量的成败被动驱动。命令层 src-tauri/src/commands/stream_check.rs:1-4 的注释又把这句重申了一遍。
那么熔断状态到底靠什么恢复?答案在 src-tauri/src/proxy/circuit_breaker.rs 里,和这个检查无关:
Open → HalfOpen:距last_opened_at已经过了timeout_seconds,在is_available()(circuit_breaker.rs:139-152)和allow_request()(:165-190)两处都会触发。是时间到点,不是检查通过。HalfOpen → Closed:半开态连续成功数达到success_threshold(:215-225)。半开态的并发探测名额写死为 1(:315-333),也就是说,把熔断关回去的是真实转发流量的成败。HalfOpen → Open:半开态一次失败立即转回 Open(:248-256)。
对应的日志码也在这条链路上:CB-001 记 Open→HalfOpen、CB-003 记 HalfOpen 探测失败(src-tauri/src/proxy/log_codes.rs:14-21)。本篇只用到这两条,是因为它们正是「熔断恢复由谁驱动」这个问题的落点;完整的错误码表另有专篇。
这里还有一个数值上的坑:熔断恢复要等多久,取决于 circuit_timeout_seconds,而这个值在不同层不一样。CircuitBreakerConfig::default() 给的是 timeout_seconds: 60(circuit_breaker.rs:63-73),数据库 proxy_config 表的列级 DEFAULT 也是 60(src-tauri/src/database/schema.rs:131-135),但 claude 那一行 seed 值是 90(schema.rs:152)。你算「多久会自己恢复」时,别只看代码默认值。
你自己怎么核一遍
不用装应用,clone 仓库读文本就够。clone 之后四条 sed,按顺序读:
git clone https://github.com/farion1231/cc-switch
cd cc-switch
sed -n '1,20p' src-tauri/src/services/stream_check.rs
sed -n '48,80p' src-tauri/src/services/stream_check.rs
sed -n '1,10p' docs/user-manual/zh/4-proxy/4.5-model-test.md
sed -n '40,60p;76,90p;104,122p' docs/user-manual/zh/4-proxy/4.5-model-test.md
以上为按仓库中的文件路径与行号组合的示例,我们没有在本机跑过这几行,以官方文档与你自己 clone 到的实际文件内容为准。
Windows 侧提醒一句:上面这几行在 Git Bash、WSL 里可以直接跑;在 PowerShell 里没有 sed,用 Get-Content <文件> -TotalCount 20 之类的等价写法,或者干脆用编辑器打开这两个文件跳到对应行。路径分隔符按各自 shell 的习惯写,别把两种混着用。
比对的落点就三组:
stream_check.rs模块头注释 vs4.5-model-test.md:5-9与:80-83——这是性质之差;stream_check.rs的Default实现 vs4.5-model-test.md:45-47、:53-55的参数表——这是默认值之差;stream_check.rs:13-17、commands/stream_check.rs:1-4的不变量注释 vs4.5-model-test.md:106-120——这是与熔断关系之差。
结果怎么读,以及什么情况说明不是这个原因
按代码这一侧的语义,检查结果的信息量是有边界的:
检查通过(可达)说明:域名能解析、端口能连上、TLS 能握手、对端在超时内回了一个 HTTP 响应。仅此而已。
检查通过不能说明:你的 API Key 有效、模型名正确、这家上游对你的真实请求会给出正常结果。一个 401 或 403 在这套规则下同样落进「可达」。
检查不通过说明:出现了 DNS、连接被拒、TLS 或超时这一级的网络错误。
据此就有了一条判断路径。如果你的实际请求在报错,但连通性检查显示可达,那么问题不在「能不能连上」这一层——这时候继续折腾网络、代理、DNS 大概率是白费,方向应该转向鉴权、模型名、上游侧的策略这些连通性检查根本不验的东西。反过来,如果你观察到某个供应商被熔断了,指望「跑一次模型检查把它救回来」也不成立:按代码里的不变量,这个检查不参与熔断器状态机,恢复要等 circuit_timeout_seconds 到点后由真实流量的半开探测来决定。
另外提醒一句和安全有关的:这个检查不验证鉴权,所以它「通过」并不代表你的 Key 配置是对的,更不代表任何安全性结论。CC Switch 会在本机保存 API Key 并读写 ~/.claude、~/.codex 这类真实 CLI 配置文件,这些都是本机敏感数据,是否使用、怎么隔离请结合自身环境判断。
最后再把分寸说清楚:以上三处不一致都是可核实的文本事实,我们把每一处的文件与行号都标了出来。至于哪一处「才是对的」、为什么会出现这种差异,本文不做推断,也不拿它去评价这个项目的质量——核完就停。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、
src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。