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 がローカルサービスの probe、モデルの pull を担い、それらの 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 などの補助関数でサービス probe とモデル pull を行う。これで 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 を probe。codex-rs/tui/src/oss_selection.rs:316-370 — select_oss_provider。TUI 起動時にポートを probe し、単一インスタンスなら自動選択、二つなら選択ダイアログを出す。ファクトリ関数は 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 probe は致命的ではない:
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 は trait を直接 impl せず、ローカル probe + OpenAI 互換 base URL でデフォルト実装を再利用し、trait 数を小さく保つ。具体的な設定形態は ModelProviderInfo で定義され、設定システム と一脈相通じる。クラウドタスク実行は Cloud Tasks パスを通り、ローカル provider とは同じレイヤーにない。