Skip to content

Model Provider 抽象

源码版本rust-v0.145.0

codex は「誰と話すか」を ModelProvider trait に抽象する:一回設定すれば、バックエンドは OpenAI、Amazon Bedrock、ローカル Ollama / LM Studio、さらにはユーザ定義の OpenAI 互換エンドポイントにもなり得る。trait を巡るには model-provider-info(設定のシリアライズ)、backend-client(HTTP 呼び出し)、ollama / lmstudio(ローカル OSS 適合)、TUI のモデル選択パネルがある。このレイヤーが Agent が最終的にリクエストを誰に送るか、どのクレデンシャルを使うか、どの wire プロトコルを通るかを決める。

責務

  1. 実行時 provider メタ情報の露出:info()ModelProviderInfo を返し、base URL、wire API、env key、auth 設定などを含む (codex-rs/model-provider/src/provider.rs:101-162)。
  2. 認証の解決:ChatGPT ログイン、API key、コマンドが吐く bearer token、または AWS SigV4 で、api_auth / api_auth_for_scope に統一する (codex-rs/model-provider/src/provider.rs:171-194)。
  3. 能力上限の報告:ProviderCapabilities が image generation / web search / namespace tools が可能かを上位に伝える。provider は拒否権を持つ (codex-rs/model-provider/src/provider.rs:33-48)。
  4. モデルカタログマネージャの構築:models_manager/v1/models からリモート catalog を引くか、パッケージ済みの静的リスト(Bedrock)を使うかを決める (codex-rs/model-provider/src/provider.rs:197-215)。
  5. ローカル OSS 適合:ollama / lmstudio crate がローカルサービスの 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-162ModelProvider trait 本体。info / auth / capabilities / models_manager の入口。codex-rs/model-provider/src/provider.rs:232-241create_model_provider ファクトリ。Bedrock を単独分流し、残りは ConfiguredModelProvider に進む。codex-rs/model-provider-info/src/lib.rs:89-141ModelProviderInfo 構造体。シリアライズ形態が config.toml[model_providers.xxx] 段に一致。codex-rs/model-provider-info/src/lib.rs:524-544create_oss_provider_with_base_url。ローカル OSS provider の標準構築。codex-rs/model-provider/src/auth.rs:49-71ResolvedProviderAuth。認証結果を telemetry と一緒にパッキングしてリクエスト層に渡す。codex-rs/backend-client/src/client.rs:124-183backend_client::Client。ChatGPT バックエンド WHAM / Codex API パスの二形態クライアント。codex-rs/ollama/src/client.rs:25-78OllamaClientModelProviderInfo から host root を導出し、/api/tags または /v1/models を probe。codex-rs/tui/src/oss_selection.rs:316-370select_oss_provider。TUI 起動時にポートを probe し、単一インスタンスなら自動選択、二つなら選択ダイアログを出す。

ファクトリ関数は Bedrock だけを切り出し、残りは ConfiguredModelProviderModelProviderInfo のデフォルト設定を食う:

rust
// 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))
    }
}

ModelProviderInfoconfig.toml[model_providers.<id>] 段に直接対応し、フィールドはすべて Option で、空欄なら OpenAI のデフォルトに進む:

rust
// 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 に任せることだ:

rust
// 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::validateawsenv_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_pathrequires_openai_auth=true かつ env_key/bearer/aws/auth フィールドが全くない場合だけで、純粋な ChatGPT ログインだけが scope 付き認証パスを通る (codex-rs/model-provider/src/provider.rs:223-229)。
  • OSS probe は致命的ではない:ensure_oss_readyfetch_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 とは同じレイヤーにない。