# 模型目录与提供方 · 一个 CLI 接多家后端

> 模型能做什么由模型目录决定：每个模型一条 ModelInfo，上下文窗口、推理强度、工具形态、基础指令模板都写在里面，目录来自随附文件、磁盘缓存与服务端的 models 接口。请求发到哪、怎么认证由提供方决定：配置层的 ModelProviderInfo 描述端点与凭据，运行时的 ModelProvider 负责认证、能力上限与目录管理，所有后端都只说 Responses API 这一种协议。

- 作者：David（道雾轩）
- 专栏：Codex 源码解读（https://daiw.net/manual/codex-source.md）
- 最后更新：2026-09-29
- 原文：https://daiw.net/manual/codex-source/models-and-providers
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

# 模型目录与提供方 · 一个 CLI 接多家后端

[指令从哪来](https://daiw.net/manual/codex-source/instructions)提到，基础指令取自模型目录里的模板。这一篇把两个常被混在一起的概念拆开：模型目录回答“这个模型能做什么、该怎么用”，提供方回答“请求发到哪、带什么凭据”。

## 用户看到的样子

选模型用 `/model`、`-m`（`--model`）或配置里的 `model`；换后端则在 `[model_providers.<id>]` 里定义提供方，再用 `model_provider` 指向它。`--oss` 配合 `--local-provider` 或 `oss_provider` 使用本机的 Ollama 与 LM Studio，Amazon Bedrock 是内置提供方。`/fast` 与 `service_tier` 切换服务档位，`model_catalog_json` 用一个 JSON 文件整体替换模型目录，`codex debug models` 打印当前目录，加 `--bundled` 只看随附目录。用法见[模型与推理](https://daiw.net/manual/codex/models)与[配置项速查](https://daiw.net/manual/codex/config-reference)。

## ModelInfo：目录里的一条

`ModelInfo`（`codex-rs/protocol/src/openai_models.rs:404`）有 47 个字段，大致分几组：身份与展示（`slug`、`display_name`、`priority`、`visibility`、`supported_in_api`），推理（`default_reasoning_level`、`supported_reasoning_levels`），工具形态（`shell_type`、`apply_patch_tool_type`、`web_search_tool_type`、`tool_mode`），上下文（`context_window`、`max_context_window`、`auto_compact_token_limit`、`effective_context_window_percent`、`comp_hash`、`truncation_policy`），请求形态（`use_responses_lite`、`supports_reasoning_effort_updates`），服务档位（`service_tiers`、`default_service_tier`），以及放基础指令模板的 `model_messages`。前几篇里的压缩阈值、Responses Lite、工具输出截断，以及基础指令本身，都是读这些字段决定的。core 的非测试代码里，写死的模型名只有实时语音的两个默认模型；审查、记忆这类辅助任务的默认模型写在提供方里。模型之间的差异，几乎全部以数据的形式放在目录里。

随附目录是 `codex-rs/models-manager/models.json`，0.158.0 里有 10 个模型：7 个出现在选择器里（`gpt-6-astra`、`gpt-6-sol`、`gpt-6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`、`gpt-5.5`），3 个隐藏（两个 `gpt-daybreak-*` 与审批审查用的 `codex-auto-review`）。上下文窗口多为 272,000，`max_context_window` 多为 872,000。

## 目录从哪来

`ModelsManager` trait 有两个实现。`StaticModelsManager` 持有一份权威目录，不联网：配置了 `model_catalog_json` 时用它，Amazon Bedrock 也用它装自己的静态目录。`OpenAiModelsManager` 以随附目录起步，再叠加两层：`CODEX_HOME` 下的磁盘缓存 `models_cache.json`（有效期 300 秒，按客户端版本与“身份”匹配），以及提供方的 `/models` 接口（5 秒超时）。身份是对提供方路由、账号信息等算出的 SHA-256 摘要，按注释的说法，ChatGPT 的访问令牌不参与计算，令牌轮换不会让同一账号的缓存失效。

刷新策略有三种：`Online` 总是联网，`Offline` 只读缓存，`OnlineIfUncached` 缓存可用就不联网。根线程启动时用 `OnlineIfUncached`，子代理用 `Offline`。之后模型响应若带 `X-Models-Etag` 头，就和缓存的 ETag 比较，不同就按 `Online` 刷新，相同就只给缓存续期。拿到的远端目录，若在 ChatGPT 账号或 API key 目录发现的场景下，且含选择器可见的模型，就整体取代随附目录；否则按 `slug` 合并进随附目录。

按名字查元数据时走这个函数：

```rust
pub(crate) fn construct_model_info_from_candidates(
    model: &str,
    candidates: &[ModelInfo],
    config: &ModelsManagerConfig,
) -> ModelInfo {
    // First use the normal longest-prefix match. If that misses, allow a narrowly scoped
    // retry for namespaced slugs like `custom/gpt-5.3-codex`.
    let remote = find_model_by_longest_prefix(model, candidates)
        .or_else(|| find_model_by_namespaced_suffix(model, candidates));
    let model_info = if let Some(remote) = remote {
        ModelInfo {
            slug: model.to_string(),
            used_fallback_model_metadata: false,
            ..remote
        }
    } else {
        model_info::model_info_from_slug(model)
    };
    model_info::with_config_overrides(model_info, config)
}
```

（`codex-rs/models-manager/src/manager.rs:782`）

匹配是“最长前缀”：请求的名字以目录里某个 `slug` 开头就算命中，取最长的那个，结果保留请求时的名字。查不到时用回退元数据：窗口 272,000、工具输出按 10,000 字节截断、`used_fallback_model_metadata` 为真，每一轮开始时界面都会收到一条 “Model metadata for … not found” 的警告。最后 `with_config_overrides` 套上配置：`model_context_window`（不超过 `max_context_window`）、`model_auto_compact_token_limit`、`tool_output_token_limit`、自定义基础指令，以及 `personality = "none"` 时删掉模板里的 `# Personality` 一节。选择器里的列表由 `build_available_models` 生成：按 `priority` 排序，不走 Codex 后端的登录方式只保留 `supported_in_api` 的模型，第一个可见的模型标为默认，随附目录里就是 `gpt-6-astra`。

是否联网由 `refresh_available_models` 开头的两道判断决定：

```rust
        if self.uses_api_key_auth()
            && !self.endpoint_client.has_command_auth()
            && (!self.endpoint_client.supports_api_key_models()
                || !self.api_key_model_discovery_enabled.load(Ordering::SeqCst))
        {
            return Ok(());
        }
```

（`codex-rs/models-manager/src/manager.rs:488`）

第二道是 `should_refresh_models`：当前登录走 Codex 后端（ChatGPT 登录等）、提供方配置了命令式认证、或者满足 API key 目录发现的条件，三者有一个成立才联网。用 API key 时（登录方式是 API key，或提供方配了 `env_key`、`experimental_bearer_token`），目录发现还要求功能开关 `api_key_model_discovery` 打开，它处于开发阶段，默认关闭。

<Callout type="warn">
  《Codex 中文手册》写着 `model_catalog_url` 可以指定 Codex 格式模型目录的地址，没有提到前提条件。按 0.158.0 的代码，用 API key 认证时（包括给提供方配了 `env_key`），只有打开开发中的 `api_key_model_discovery` 才会去取这个地址，否则连之前的缓存也不用，只用随附目录；配置了命令式认证（`auth`）的提供方不受这个开关限制。
</Callout>

## 提供方：配置一层，运行时一层

```mermaid
flowchart TB
  C[config.toml<br/>model_provider · model_providers] --> M[merge_configured_model_providers<br/>内置五个 + 自定义]
  M --> P[ModelProviderInfo<br/>端点 · 凭据 · 重试 · 请求头]
  P --> F{create_model_provider}
  F -->|两个 Bedrock ID| B[AmazonBedrockModelProvider]
  F -->|其他| G[ConfiguredModelProvider]
  G -->|配置了 model_catalog_json| S[StaticModelsManager]
  G -->|否则| O[OpenAiModelsManager<br/>随附目录 · 缓存 · models 接口]
  B --> S
  S --> I[get_model_info<br/>最长前缀匹配 + 配置覆盖]
  O --> I
  I --> T[TurnContext 里的 ModelInfo]
  G --> K[ModelClient<br/>认证 · base_url · 能力上限]
  B --> K
```

配置层是 `ModelProviderInfo`（`codex-rs/model-provider-info/src/lib.rs:135`），描述端点（`base_url`、`model_catalog_url`、`query_params`）、凭据（`env_key`、`experimental_bearer_token`、命令式的 `auth`、网关的 `gateway_oauth`、AWS 的 `aws`）、请求头、重试与超时，以及 `requires_openai_auth`、`supports_websockets` 等开关。协议字段 `wire_api` 的枚举只剩 `Responses` 一个值，写 `"chat"` 会在解析配置时报错，提示改成 `responses`。内置的提供方有五个：

```rust
    // We do not want to be in the business of adjucating which third-party
    // providers are bundled with Codex CLI, so we only include the OpenAI and
    // open source ("oss") providers by default. Users are encouraged to add to
    // `model_providers` in config.toml to add their own providers.
    [
        (OPENAI_PROVIDER_ID, openai_provider),
        (AMAZON_BEDROCK_PROVIDER_ID, amazon_bedrock_provider),
        (
            AMAZON_BEDROCK_RUNTIME_PROVIDER_ID,
            amazon_bedrock_runtime_provider,
        ),
        (
            OLLAMA_OSS_PROVIDER_ID,
            create_oss_provider(DEFAULT_OLLAMA_PORT, WireApi::Responses),
        ),
        (
            LMSTUDIO_OSS_PROVIDER_ID,
            create_oss_provider(DEFAULT_LMSTUDIO_PORT, WireApi::Responses),
        ),
    ]
```

（`codex-rs/model-provider-info/src/lib.rs:652`）

注释只提到 OpenAI 与开源两类，列表里却还有两个 Amazon Bedrock 提供方。用户定义的提供方由 `merge_configured_model_providers` 并进来：五个内置 ID 都是保留的，重新定义会报错并建议改名（例如 `openai-custom`），只有两个 Bedrock ID 允许覆盖 `base_url`、`auth`、`aws` 与 `http_headers`；OpenAI 的地址要改用 `openai_base_url`。项目目录里的配置写了 `openai_base_url`、`model_provider`、`model_providers` 会被忽略，按注释的说法，来自仓库内容的配置不该决定用户的凭据发往哪里；`requirements.toml` 则可以强制指定提供方。选中顺序是：托管要求、启动时的覆盖（如 `--oss` 选中的本地提供方）、配置里的 `model_provider`，都没有就是 `openai`。没写 `base_url` 时，`to_api_provider` 按登录方式选默认地址：ChatGPT 一类登录走 `https://chatgpt.com/backend-api/codex`，其余走 `https://api.openai.com/v1`。

运行时一层是 `ModelProvider` trait，由 `create_model_provider` 创建：两个 Bedrock ID 得到 `AmazonBedrockModelProvider`，其余都是 `ConfiguredModelProvider`。它负责解析认证（先用提供方自己的 `env_key` 或 `experimental_bearer_token`；提供方不要求 OpenAI 认证、也没配认证命令时不带凭据；否则用认证命令或登录得到的凭据），在 401 之后尝试恢复，创建上面说的目录管理器，并给出 `ProviderCapabilities`：命名空间工具、图片生成、网络搜索、外部网页访问、远程压缩五项，注释称之为“provider-owned upper bound”，配置只能在此基础上再关，不能打开提供方标为不支持的功能。`ConfiguredModelProvider` 只对 OpenAI 与 Azure 声明支持远程压缩 V2，这就是[上下文压缩](https://daiw.net/manual/codex-source/compaction)里选实现的依据；判断看的是显示名而不是 ID：`name` 为 `OpenAI`（内置的 `openai` 就是），或名字是 `azure`、`base_url` 含 `openai.azure.` 等特征串。

## Fast 是什么

Fast 就是请求里的 `service_tier: "priority"`。它经过两道过滤：会话层的 `get_service_tier`（`codex-rs/core/src/session/mod.rs:1075`）只放行 `flex`，或者在 `fast_mode` 开关打开（稳定功能，默认开启）时放行 `default` 与模型目录声明过的档位；请求层的 `service_tier_for_request` 再去掉 `default` 与模型不支持的值，Bedrock 一律不带档位，Guardian 审查请求也会清掉它。随附目录只给 `gpt-6-sol` 与 `gpt-6-luna` 设了默认档位 `priority`，但套用这个默认值的是 TUI（`effective_service_tier`），core 只认配置或请求里明确给出的档位，所以 `codex exec` 不会自动用 Fast。走 Codex 后端时，请求还会带一个 `x-codex-routing-hint` 头，内容是模型名与档位。

## 本地模型与 Amazon Bedrock

Ollama 与 LM Studio 两个内置提供方的地址默认是 `http://localhost:11434/v1` 与 `http://localhost:1234/v1`，可以用实验性的环境变量 `CODEX_OSS_PORT`、`CODEX_OSS_BASE_URL` 改。`--oss` 启动时先决定用哪一家（`--local-provider`、`oss_provider`，TUI 里都没有时检测本机服务或让用户选，`codex exec` 直接报错），没给 `-m` 就用 `gpt-oss:20b` 或 `openai/gpt-oss-20b`；Ollama 还要检查版本不低于 0.13.4（支持 Responses API 的最低版本），本地没有所请求的模型就拉取，LM Studio 缺模型就下载并在后台加载。

Amazon Bedrock 有两个内置提供方：`amazon-bedrock` 走区域内的 Mantle 端点，`amazon-bedrock-runtime` 走 Bedrock Runtime 端点。它们用 AWS 的认证方式，目录是静态的，由随附目录里的 OpenAI 模型改名而来（如 `openai.gpt-6-sol`），并统一清掉服务档位、把网络搜索改成纯文本形态、多 agent 固定为 V1，还去掉 `ultra` 推理强度并关闭 Responses Lite。

## 和《从 LLM 到 Coding Agent》对照

[错误恢复](https://daiw.net/manual/llm-to-agent/error-recovery)建议主模型过载时自动降级到备用模型重发。Codex 没有这样做：服务端报过载时得到的 `ServerOverloaded` 不在可重试之列，本轮直接结束，提示是 “Selected model is at capacity. Please try a different model.”，换不换模型交给用户。接近“回退”的是线程启动时的 `allow_provider_model_fallback`（app-server 的实验参数）：提供方的静态目录里没有所请求的模型时，改用目录的默认模型。[成本追踪](https://daiw.net/manual/llm-to-agent/cost-tracking)建议按模型维护价目表、在客户端累加花费；Codex 的 `ModelInfo` 没有任何价格字段，core 里也没有按价格计算的代码，用量只以 token 数和服务端经响应头下发的额度快照呈现（见[模型客户端](https://daiw.net/manual/codex-source/model-client)）。

---

上一篇：[指令从哪来 · AGENTS.md、系统提示词与协作模式](https://daiw.net/manual/codex-source/instructions) · 下一篇：[任务类型与 Plan 模式 · 一轮里跑的不只是对话](https://daiw.net/manual/codex-source/tasks-and-plan-mode)
