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 不在同一層。