第四十章:模型、Provider 与 Capability 怎样一起决定执行边界
拆 provider 路由、模型解析、capability 缓存和工具级能力门控。
1. 为什么这章必须单独拆
Claude Code 里和模型有关的代码,绝对不只是“用户选一个 model id”。
从源码看,这里至少同时在处理:
- provider 路由
- 模型选择与别名解析
- allowlist / deprecation
- capability 拉取与缓存
- 工具级 capability gate
- 订阅形态对默认模型的影响
这说明 Claude Code 真正在做的是:
让“当前会话到底能用哪个模型、能开哪些能力”变成一套统一路由逻辑。
相关源码锚点:
src/utils/model/providers.tssrc/utils/model/model.tssrc/utils/model/modelCapabilities.tssrc/utils/model/modelAllowlist.tssrc/utils/model/modelStrings.tssrc/utils/model/deprecation.tssrc/utils/model/modelSupportOverrides.tssrc/services/api/client.tssrc/tools/WebSearchTool/WebSearchTool.tssrc/main.tsx
2. 先说结论
我对这一块的判断是:
Claude Code 的模型路由是三层叠起来的:provider 选择、model 解析、capability 门控。
最值得抓住的结论有五个:
- provider 先决定调用通道,再谈具体模型。
- 用户设置的 model id 还会经过 allowlist、alias 和默认值解析。
- runtime model 不一定等于静态 settings model。
- capability 是独立缓存层,不和模型字符串混在一起。
- 工具可用性会直接看 provider + model capability。
3. 总体结构图
flowchart TD
A["env / settings / subscription / mode"] --> B["providers.ts<br/>provider 路由"]
A --> C["model.ts<br/>model setting 解析"]
C --> D["allowlist / alias / default / runtime model"]
B --> E["services/api/client.ts<br/>provider-specific client"]
D --> E
E --> F["模型调用"]
B --> G["modelCapabilities.ts"]
D --> G
G --> H["工具 capability gate"]
H --> I["WebSearchTool 等功能开关"]
4. Provider 路由:先确定“走哪条云”,再确定“跑哪个模型”
源码锚点:
src/utils/model/providers.tssrc/services/api/client.ts
getAPIProvider() 这层很干脆,直接按环境路由到:
firstPartybedrockvertexfoundry
然后 getAnthropicClient() 再根据 provider 建不同 client:
- Bedrock 走
@anthropic-ai/bedrock-sdk - Vertex 走
@anthropic-ai/vertex-sdk - Foundry 走
@anthropic-ai/foundry-sdk - first-party 走标准 Anthropic client
这意味着 provider 在 Claude Code 里不是 metadata,而是调用栈分叉点。
5. 模型选择:用户写下去的 setting 不是最终答案
源码锚点:
src/utils/model/model.tssrc/utils/model/modelAllowlist.ts
getUserSpecifiedModelSetting() 的优先级大致是:
- session override
- startup override
ANTHROPIC_MODEL- settings
然后还要经过:
- allowlist 过滤
- alias / family 解析
- 默认模型补位
也就是说,用户写的值只是“意图”,不是运行时最终模型。
6. 默认模型策略:它会看 provider,也会看订阅
源码锚点:
src/utils/model/model.ts
这里有两个很值钱的设计:
6.1 不同 provider 的默认模型不完全一样
例如:
- first-party 默认能更快切到新的 Sonnet / Opus 代际
- 第三方 provider 的默认值可能更保守
这说明默认模型不是纯产品文案,而是实际可用性和上线节奏的一部分。
6.2 订阅形态会影响默认主模型
比如:
- Max / Team Premium 默认更偏 Opus
- 其他用户默认更偏 Sonnet
也就是说,模型选择已经和产品套餐绑在一起了。
7. Runtime Model:为什么它不一定等于 settings model
源码锚点:
src/utils/model/model.ts
getRuntimeMainLoopModel() 还会在运行时进一步调整。
典型例子就是 plan mode:
opusplanhaiku
这些别名不是简单展示名,而是运行时策略名。
这说明 Claude Code 区分得很清楚:
- 用户配置里想要什么
- 当前这一段流程真正该用什么
8. Capability:不要把能力判断写死在 model string 上
源码锚点:
src/utils/model/modelCapabilities.ts
这层很值得学。
Claude Code 没把“最大上下文”“最大输出”等能力直接硬编码进一堆 if/else。
它会:
- 调
models.list - 把结果缓存到
~/.claude/cache/model-capabilities.json - 提供同步查询能力
而且这层还有 eligibility:
- 只在 ant 用户
- first-party provider
- first-party base URL
这意味着 capability cache 也是带信任边界的。
9. 工具 gating:模型能力会直接改执行面
源码锚点:
src/tools/WebSearchTool/WebSearchTool.ts
WebSearchTool 是一个非常直观的例子。
它启用时会看:
- provider 是不是
firstParty vertex下模型是不是 Claude 4.xfoundry是否默认支持
这说明工具不是“注册了就能用”,而是会被模型能力直接裁剪。
所以这层真正值钱的,不只是模型调用本身,而是:
模型路由会反过来重塑工具面。
10. Allowlist / deprecation / canonicalization:这层在保护什么
源码锚点:
src/utils/model/modelAllowlist.tssrc/utils/model/modelStrings.tssrc/utils/model/deprecation.ts
这一层的存在,解决了几个现实问题:
- 家族别名很多
- provider-specific model id 很杂
- 某些老模型需要弃用或升级提示
- 不同后端的具体 id 需要归一成对外稳定名字
这说明 Claude Code 明确不想把业务代码写成一堆散落的 model string 比较。
11. 对 C# 拆分最有用的建议
如果后面做 C# 版,这层非常适合拆成五块:
11.1 ProviderResolver
负责:
- provider 路由
- base URL / env 判定
- provider-specific client family 选择
11.2 ModelSelectionPolicy
负责:
- override 优先级
- 默认模型
- 订阅差异
- plan/runtime 特殊路由
11.3 ModelCatalog
负责:
- alias
- canonical name
- allowlist
- deprecation
11.4 ModelCapabilityStore
负责:
- capability fetch
- 本地缓存
- eligibility
11.5 FeatureGateEvaluator
负责:
- 工具 capability gate
- provider/model 对功能面的裁剪
12. 一句话收口
Claude Code 的模型路由最值得学的地方,不是“支持多个 provider”,而是它把 provider、model 和 capability 做成了一条统一的执行边界判定链。