Skip to content

Abstraction Model Provider

源码版本rust-v0.145.0

codex abstrait « à qui parle-t-on » en un trait ModelProvider : une seule config, mais le backend peut être OpenAI, Amazon Bedrock, un Ollama / LM Studio local, voire un endpoint compatible OpenAI auto-construit. Autour du trait : model-provider-info (sérialisation de config), backend-client (appels HTTP), ollama / lmstudio (adaptateurs OSS locaux) et le panneau de sélection de modèle dans la TUI. Cette couche décide à qui l'Agent envoie finalement sa requête, avec quels identifiants, et via quel protocole wire.

Responsabilités

  1. Exposer les métadonnées runtime du provider : info() renvoie un ModelProviderInfo contenant base URL, wire API, env key, config d'auth, etc. (codex-rs/model-provider/src/provider.rs:101-162).
  2. Résoudre l'authentification : login ChatGPT, API key, bearer token issu d'une commande, ou AWS SigV4 — tout converge vers api_auth / api_auth_for_scope (codex-rs/model-provider/src/provider.rs:171-194).
  3. Reporter les plafonds de capacité : ProviderCapabilities indique à l'amont la capacité ou non de faire de la génération d'images / web search / namespace tools ; le provider a un droit de veto (codex-rs/model-provider/src/provider.rs:33-48).
  4. Construire le gestionnaire de catalog de modèles : models_manager décide de tirer le catalog distant depuis /v1/models ou d'utiliser une liste statique bundled (Bedrock) (codex-rs/model-provider/src/provider.rs:197-215).
  5. Adapter les OSS locaux : les crate ollama / lmstudio détectent le service local, tirent les modèles, et mappent leur endpoint compatible OpenAI vers ModelProviderInfo (codex-rs/ollama/src/lib.rs:23-50).

Motivations de conception

Au début, codex ne parlait qu'à OpenAI ; la config se résumait à OPENAI_API_KEY + base_url. Mais quand les utilisateurs ont commencé à brancher Bedrock, des moteurs d'inférence locaux et des gateways auto-construits, ce « OpenAI codé en dur » fuyait partout. Refaire le trait visait à laisser « la partie compatible OpenAI » dans l'impl par défaut, et à comprimer « ce qui diffère entre fournisseurs » en overrides de trait method.

ConfiguredModelProvider est l'impl par défaut — tant que votre backend ressemble à un OpenAI /v1/responses, on peut le configurer avec ModelProviderInfo sans écrire de nouvelle impl de trait. Seul Bedrock, à cause de la signature SigV4, a une impl dédiée amazon_bedrock::AmazonBedrockModelProvider. Le compromis : garder une dizaine de trait methods, dont la plupart des impls par défaut passent par info() + auth_manager() — ce qui laisse des hooks d'override sans imposer une réécriture complète à chaque nouveau provider.

Ollama / LM Studio n'implémentent pas ModelProvider eux-mêmes : ils emballent le service local en « base URL compatible OpenAI » qu'ils confient à ConfiguredModelProvider, avec des helpers comme ensure_oss_ready pour la détection de service et le pull de modèles. Le nombre de traits reste petit, et le chemin OSS local réutilise le plus.

Fichiers clés

codex-rs/model-provider/src/provider.rs:101-162 — corps du trait ModelProvider, entrée de info / auth / capabilities / models_manager.codex-rs/model-provider/src/provider.rs:232-241 — la factory create_model_provider, Bedrock à part, tout le reste via ConfiguredModelProvider.codex-rs/model-provider-info/src/lib.rs:89-141 — structure ModelProviderInfo, dont la forme sérialisée correspond à la section [model_providers.xxx] de config.toml.codex-rs/model-provider-info/src/lib.rs:524-544create_oss_provider_with_base_url, constructeur standard du provider OSS local.codex-rs/model-provider/src/auth.rs:49-71ResolvedProviderAuth, emballe le résultat d'auth avec la télémétrie pour la couche de requête.codex-rs/backend-client/src/client.rs:124-183backend_client::Client, client double-forme WHAM / Codex API pour le backend ChatGPT.codex-rs/ollama/src/client.rs:25-78OllamaClient, dérive host root depuis ModelProviderInfo et sonde /api/tags ou /v1/models.codex-rs/tui/src/oss_selection.rs:316-370select_oss_provider, sonde les ports au démarrage de la TUI ; instance unique sélection auto, double instance ouvre un picker.

La factory sort Bedrock, le reste passe par ConfiguredModelProvider qui consomme la config par défaut de ModelProviderInfo :

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

ModelProviderInfo correspond à la section [model_providers.<id>] de config.toml ; tous les champs sont en Option, les valeurs vides retombent sur les défauts 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,
}

L'adaptation Ollama n'« implémente pas le trait » : elle emballe le service local en endpoint compatible OpenAI, et laisse ConfiguredModelProvider faire le reste :

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

Flux de données

Limites et échecs

  • Champs d'auth mutuellement exclusifs : ModelProviderInfo::validate interdit que aws coexiste avec env_key / experimental_bearer_token / auth / requires_openai_auth, pour éviter des sémantiques de config contradictoires (codex-rs/model-provider-info/src/lib.rs:154-212).
  • Détection de version Ollama : ensure_responses_supported exige Ollama ≥ 0.13.4 pour la Responses API ; les versions plus anciennes renvoient une erreur directe (codex-rs/ollama/src/lib.rs:63-77).
  • Bedrock ne passe pas par le chemin d'auth OpenAI : les tests create_model_provider_builds_command_auth_manager confirment qu'un Bedrock provider ignore l'auth manager OpenAI même si on lui passe, pour éviter une mauvaise utilisation des identifiants (codex-rs/model-provider/src/provider.rs:552-565).
  • Auth first-party sur branche spéciale : provider_uses_first_party_auth_path exige requires_openai_auth=true et l'absence de tout champ env_key/bearer/aws/auth — seul un login ChatGPT pur passe par le chemin d'auth avec scope (codex-rs/model-provider/src/provider.rs:223-229).
  • Échec de sonde OSS non fatal : ensure_oss_ready se contente de tracing::warn si fetch_models échoue, sans fail direct — l'erreur réelle n'apparaîtra qu'au moment de faire tourner le modèle (codex-rs/ollama/src/lib.rs:34-47).

Récapitulatif

Le trait ModelProvider comprime « à qui parle-t-on » en une dizaine de méthodes ; l'impl par défaut ConfiguredModelProvider couvre tous les backend compatibles OpenAI ; Bedrock a sa propre impl à cause de SigV4. Ollama / LM Studio n'implémentent pas le trait directement : ils réutilisent l'impl par défaut via une détection locale + base URL compatible OpenAI, ce qui garde le nombre de traits petit. La forme de config est définie dans ModelProviderInfo, en cohérence avec Système de configuration. L'exécution de tâches dans le cloud passe par Cloud Tasks, qui n'est pas au même niveau que le provider local.