Model Provider 抽象
codex 把"跟谁对话"这件事抽成 ModelProvider trait:一次配置,后端可以是 OpenAI、Amazon Bedrock,也可以是本地 Ollama / LM Studio,甚至是用户自定义的 OpenAI 兼容端点。围绕 trait 的有 model-provider-info(配置序列化)、backend-client(HTTP 调用)、ollama / lmstudio(本地 OSS 适配)和 TUI 里的模型选择面板。这一层决定了 Agent 最终把请求发给谁、用什么凭证、走哪条 wire 协议。
职责
- 暴露运行时 provider 元信息:
info()返回ModelProviderInfo,包含 base URL、wire API、env key、auth 配置等 (codex-rs/model-provider/src/provider.rs:101-162)。 - 解决鉴权:走 ChatGPT 登录、API key、命令吐出的 bearer token,或 AWS SigV4,统一收口到
api_auth/api_auth_for_scope(codex-rs/model-provider/src/provider.rs:171-194)。 - 报告能力上界:
ProviderCapabilities告诉上层能否做 image generation / web search / namespace tools,provider 拥有否决权 (codex-rs/model-provider/src/provider.rs:33-48)。 - 构造模型目录管理器:
models_manager决定是从/v1/models拉远程 catalog,还是用打包的静态列表 (Bedrock) (codex-rs/model-provider/src/provider.rs:197-215)。 - 适配本地 OSS:
ollama/lmstudiocrate 负责探测本地服务、拉取模型,把它们的 OpenAI 兼容端点映射回ModelProviderInfo(codex-rs/ollama/src/lib.rs:23-50)。
设计动机
早期 codex 只跟 OpenAI 对话,配置就是 OPENAI_API_KEY + base_url。但用户开始接 Bedrock、本地推理引擎、自建网关时,这种"硬编码 OpenAI"的写法就到处漏出来。重新做 trait 抽象的目的是把"OpenAI 兼容的部分"留在默认实现里,把"各家不一样的地方"压成 trait method 的 override。
ConfiguredModelProvider 是默认实现——只要你的后端长得像 OpenAI /v1/responses,就能直接用 ModelProviderInfo 配置出来,不用写新 trait impl。只有 Bedrock 因为走 SigV4 签名、有独立的 amazon_bedrock::AmazonBedrockModelProvider 实现。这套设计的取舍点:把 trait method 控制在十几条,大部分默认实现走 info() + auth_manager(),既给了 override 钩子,又避免每接一个 provider 都要重写一大堆方法。
Ollama / LM Studio 没有自己实现 ModelProvider,而是把本地服务包装成"OpenAI 兼容 base URL"喂给 ConfiguredModelProvider,再用 ensure_oss_ready 之类的辅助函数做服务探测和模型拉取。这样 trait 数量保持小,本地 OSS 路径的复用度反而最高。
关键文件
codex-rs/model-provider/src/provider.rs:101-162 — ModelProvider trait 主体,info / auth / capabilities / models_manager 的入口。codex-rs/model-provider/src/provider.rs:232-241 — create_model_provider 工厂,Bedrock 单独分流,其余走 ConfiguredModelProvider。codex-rs/model-provider-info/src/lib.rs:89-141 — ModelProviderInfo 结构,序列化形态即 config.toml 里 [model_providers.xxx] 段。codex-rs/model-provider-info/src/lib.rs:524-544 — create_oss_provider_with_base_url,本地 OSS provider 的标准构造。codex-rs/model-provider/src/auth.rs:49-71 — ResolvedProviderAuth,把鉴权结果连同 telemetry 一起打包供请求层使用。codex-rs/backend-client/src/client.rs:124-183 — backend_client::Client,ChatGPT 后端 WHAM / Codex API 路径双形态客户端。codex-rs/ollama/src/client.rs:25-78 — OllamaClient,从 ModelProviderInfo 推出 host root 并探活 /api/tags 或 /v1/models。codex-rs/tui/src/oss_selection.rs:316-370 — select_oss_provider,在 TUI 启动时探端口,单实例自动选,双实例弹选择框。工厂函数把 Bedrock 单拎出来,其余都靠 ConfiguredModelProvider 吃 ModelProviderInfo 默认配置:
// provider.rs:232-241 — 工厂按 provider 类型分流
pub fn create_model_provider(
provider_info: ModelProviderInfo,
auth_manager: Option<Arc<AuthManager>>,
) -> SharedModelProvider {
if provider_info.is_amazon_bedrock() {
Arc::new(AmazonBedrockModelProvider::new(provider_info, auth_manager))
} else {
Arc::new(ConfiguredModelProvider::new(provider_info, auth_manager))
}
}ModelProviderInfo 直接对应 config.toml 的 [model_providers.<id>] 段,字段都是 Option,留白就走 OpenAI 默认值:
// model-provider-info/src/lib.rs:89-141 — provider 配置的序列化形态
pub struct ModelProviderInfo {
pub name: String,
pub base_url: Option<String>,
pub env_key: Option<String>,
pub env_key_instructions: Option<String>,
pub experimental_bearer_token: Option<String>,
pub auth: Option<ModelProviderAuthInfo>,
pub aws: Option<ModelProviderAwsAuthInfo>,
pub wire_api: WireApi,
// ...还有 query_params / http_headers / stream_* 等字段
pub requires_openai_auth: bool,
pub supports_websockets: bool,
}Ollama 适配的关键不是"实现 trait",而是把本地服务包装成 OpenAI 兼容端点,剩下交给 ConfiguredModelProvider:
// ollama/src/client.rs:59-78 — 从 provider 配置构造客户端并探活
pub(crate) async fn try_from_provider(provider: &ModelProviderInfo) -> io::Result<Self> {
let base_url = provider.base_url.as_ref().expect("oss provider must have a base_url");
let uses_openai_compat = is_openai_compatible_base_url(base_url);
let host_root = base_url_to_host_root(base_url);
let client = reqwest::Client::builder()
.connect_timeout(std::time::Duration::from_secs(5))
.build()
.unwrap_or_else(|_| reqwest::Client::new());
let client = Self { client, host_root, uses_openai_compat };
client.probe_server().await?;
Ok(client)
}数据流
边界与失败
- 多 auth 字段互斥:
ModelProviderInfo::validate禁止aws跟env_key/experimental_bearer_token/auth/requires_openai_auth同时出现,防止配置语义打架 (codex-rs/model-provider-info/src/lib.rs:154-212)。 - Ollama 版本检测:
ensure_responses_supported要求 Ollama ≥ 0.13.4 才能用 Responses API,旧版本直接报错(codex-rs/ollama/src/lib.rs:63-77)。 - Bedrock 不走 OpenAI auth 路径:
create_model_provider_builds_command_auth_manager等测试明确 Bedrock provider 即使传入 OpenAI auth manager 也会被忽略,避免凭证错用 (codex-rs/model-provider/src/provider.rs:552-565)。 - first-party auth 走特殊分支:
provider_uses_first_party_auth_path要求requires_openai_auth=true且没有任何 env_key/bearer/aws/auth 字段,只有纯 ChatGPT 登录才走带 scope 的鉴权路径 (codex-rs/model-provider/src/provider.rs:223-229)。 - OSS 探活不致命:
ensure_oss_ready在fetch_models失败时只tracing::warn,不直接 fail,让上层跑模型时再撞出真实错误 (codex-rs/ollama/src/lib.rs:34-47)。
小结
ModelProvider trait 把"跟谁对话"压成十几条方法,默认实现 ConfiguredModelProvider 覆盖所有 OpenAI 兼容后端,Bedrock 因为 SigV4 单独实现。Ollama / LM Studio 不直接 impl trait,而是用本地探测 + OpenAI 兼容 base URL 的方式复用默认实现,trait 数量保持小。具体的配置形态在 ModelProviderInfo 里定义,跟 配置系统 一脉相承,云端任务执行走的是 Cloud Tasks 路径,跟本地 provider 不在同一层。