Skip to content

Abstracción de Model Provider

源码版本rust-v0.145.0

codex abstrae «con quién hablar» en el trait ModelProvider: una sola configuración, y el backend puede ser OpenAI, Amazon Bedrock, o un Ollama / LM Studio local, o incluso un endpoint compatible con OpenAI definido por el usuario. Alrededor del trait hay model-provider-info (serialización de config), backend-client (llamadas HTTP), ollama / lmstudio (adaptadores de OSS local) y el panel de selección de modelo en el TUI. Esta capa decide a quién envía al final el Agent la petición, con qué credenciales y por cuál protocolo wire.

Responsabilidades

  1. Exponer la metadata del provider en runtime: info() devuelve ModelProviderInfo, con base URL, wire API, env key, configuración de auth, etc. (codex-rs/model-provider/src/provider.rs:101-162)
  2. Resolver la autenticación: login de ChatGPT, API key, bearer token escupido por un comando, o AWS SigV4, todo unificado en api_auth / api_auth_for_scope (codex-rs/model-provider/src/provider.rs:171-194)
  3. Reportar el techo de capacidades: ProviderCapabilities dice a la capa superior si se puede image generation / web search / namespace tools; el provider tiene poder de veto (codex-rs/model-provider/src/provider.rs:33-48)
  4. Construir el gestor del catálogo de modelos: models_manager decide si se hace pull del catálogo remoto desde /v1/models o si se usa la lista estática empaquetada (Bedrock) (codex-rs/model-provider/src/provider.rs:197-215)
  5. Adaptar OSS local: los crates ollama / lmstudio detectan el servicio local, tiran de modelos, y mapean su endpoint compatible con OpenAI de vuelta a ModelProviderInfo (codex-rs/ollama/src/lib.rs:23-50)

Motivación de diseño

Al principio codex solo hablaba con OpenAI, y la configuración era OPENAI_API_KEY + base_url. Pero al empezar los usuarios a conectar Bedrock, motores de inferencia local, y gateways propios, este «OpenAI hardcodeado» se colaba por todas partes. Rediseñar con un trait abstracto busca dejar «lo que es compatible con OpenAI» en la implementación por defecto, y comprimir «lo que cambia entre proveedores» en overrides de métodos del trait.

ConfiguredModelProvider es la implementación por defecto: si tu backend se parece a OpenAI /v1/responses, se puede configurar directamente con ModelProviderInfo, sin escribir un impl nuevo del trait. Solo Bedrock, por su firma SigV4, tiene una implementación amazon_bedrock::AmazonBedrockModelProvider aparte. La trade-off de este diseño: mantener el trait en una docena de métodos, con la mayoría de las implementaciones por defecto yendo por info() + auth_manager(), dando ganchos de override y evitando que cada provider nuevo implante un gran bloque de métodos.

Ollama / LM Studio no implementan ModelProvider directamente, sino que envuelven el servicio local como una «base URL compatible con OpenAI» que se le pasa a ConfiguredModelProvider, y usan funciones auxiliares como ensure_oss_ready para detectar el servicio y tirar de modelos. Así el trait se mantiene pequeño y la reutilización de la ruta OSS local es la mayor.

Archivos clave

codex-rs/model-provider/src/provider.rs:101-162 — cuerpo del trait ModelProvider, entrada de info / auth / capabilities / models_manager.codex-rs/model-provider/src/provider.rs:232-241 — factory create_model_provider, con Bedrock en una rama aparte y el resto a ConfiguredModelProvider.codex-rs/model-provider-info/src/lib.rs:89-141 — struct ModelProviderInfo, cuya forma serializada es exactamente la sección [model_providers.xxx] del config.toml.codex-rs/model-provider-info/src/lib.rs:524-544create_oss_provider_with_base_url, constructor estándar de provider OSS local.codex-rs/model-provider/src/auth.rs:49-71ResolvedProviderAuth, empaqueta el resultado de la autenticación junto con telemetry para la capa de peticiones.codex-rs/backend-client/src/client.rs:124-183backend_client::Client, cliente de doble forma para el backend de ChatGPT: WHAM / Codex API.codex-rs/ollama/src/client.rs:25-78OllamaClient, deriva el host root del ModelProviderInfo y hace probe a /api/tags o /v1/models.codex-rs/tui/src/oss_selection.rs:316-370select_oss_provider, al arrancar el TUI sondea puertos; elige automáticamente si hay una sola instancia, abre un selector si hay dos.

La función factory saca a Bedrock aparte; el resto se apoya en ConfiguredModelProvider con la configuración por defecto de ModelProviderInfo:

rust
// provider.rs:232-241 — la factory se reparte por tipo de 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 se corresponde directamente con la sección [model_providers.<id>] del config.toml; los campos son Option, y los huecos en blanco caen al valor por defecto de OpenAI:

rust
// model-provider-info/src/lib.rs:89-141 — forma serializada de la configuración del 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,
    // ...hay más campos como query_params / http_headers / stream_*
    pub requires_openai_auth: bool,
    pub supports_websockets: bool,
}

La clave del adaptador de Ollama no es «implementar el trait», sino envolver el servicio local como un endpoint compatible con OpenAI y dejar el resto a ConfiguredModelProvider:

rust
// ollama/src/client.rs:59-78 — construye el cliente desde la configuración del provider y hace probe
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)
}

Flujo de datos

Bordes y fallos

  • Campos de auth mutuamente excluyentes: ModelProviderInfo::validate prohibe que aws coexista con env_key / experimental_bearer_token / auth / requires_openai_auth, para evitar que la semántica de la configuración se contradiga (codex-rs/model-provider-info/src/lib.rs:154-212).
  • Detección de versión de Ollama: ensure_responses_supported exige Ollama ≥ 0.13.4 para usar Responses API; las versiones más antiguas dan error directo (codex-rs/ollama/src/lib.rs:63-77).
  • Bedrock no pasa por la ruta de auth de OpenAI: los tests create_model_provider_builds_command_auth_manager y similares dejan claro que el provider Bedrock, incluso recibiendo un auth manager de OpenAI, lo ignora, para evitar usar credenciales equivocadas (codex-rs/model-provider/src/provider.rs:552-565).
  • Auth first-party por ruta especial: provider_uses_first_party_auth_path exige requires_openai_auth=true y ningún campo env_key/bearer/aws/auth; solo el login puro de ChatGPT va por la ruta de auth con scope (codex-rs/model-provider/src/provider.rs:223-229).
  • Probe de OSS no es fatal: si ensure_oss_ready falla al hacer fetch_models, solo suelta tracing::warn sin abortar, dejando que el error real salga al correr el modelo (codex-rs/ollama/src/lib.rs:34-47).

Resumen

El trait ModelProvider comprime «con quién hablar» en una docena de métodos; la implementación por defecto ConfiguredModelProvider cubre todos los backends compatibles con OpenAI, y Bedrock tiene impl aparte por SigV4. Ollama / LM Studio no implementan el trait directamente, sino que reutilizan la implementación por defecto con detección local + base URL compatible con OpenAI, manteniendo el trait pequeño. La forma concreta de la configuración se define en ModelProviderInfo, en sintonía con Sistema de configuración; la ejecución de tareas en la nube va por la ruta Cloud Tasks, que no está en la misma capa que el provider local.