Skip to content

ModelInfo能力模型

追踪 ModelInfo 如何把模型元数据转换为上下文、工具、请求和提示行为。

基于rust-v0.150.0
CodexRustModelModelInfo

ModelInfo能力模型 ​

本文承接 Provider解析流程 和 Provider配置字段,研究对象从“连接哪个 provider”切换为“当前模型具备哪些能力”。本文聚焦 ModelInfo、模型匹配与 fallback、ModelsManagerConfig 覆盖,以及 core 对模型能力的消费;远端 /models 的刷新策略和 Responses 事件解析不在本文展开。

读者需要能阅读 Rust 结构体、serde 默认值、Option 和异步调用。读完后,你应能从一个 ModelInfo 字段定位它影响的消费者,解释 context_window 与 auto_compact_token_limit 的关系,区分 ModelPreset 与 ModelInfo,并说明未知 slug 为什么仍能创建 turn、同时触发 warning。文章不把“字段存在”当作能力已验证:能力是否真实可用,仍取决于 provider、feature 和服务端行为。

1. 能力边界 ​

ModelInfo 是后端目录返回并由 core 补充内部标记的模型快照;ModelPreset 是面向 picker 和协议消费者的较小展示对象。前者参与请求和 turn 行为,后者主要参与可选模型列表。

ModelInfo 的字段可以分成四条消费链:窗口链、请求链、工具链和提示链。used_fallback_model_metadata 则是一条诊断链,不直接改变请求字段,却会在 turn 创建后产生用户可见 warning。

源码位置:codex-rs/protocol/src/openai_models.rs :: ModelInfo

rust
pub struct ModelInfo {
    pub slug: String,
    pub display_name: String,
    pub description: Option<String>,
    pub default_reasoning_level: Option<ReasoningEffort>,
    pub supported_reasoning_levels: Vec<ReasoningEffortPreset>,
    pub shell_type: ConfigShellToolType,
    pub visibility: ModelVisibility,
    pub supported_in_api: bool,
    pub priority: i32,
    pub service_tiers: Vec<ModelServiceTier>,
    pub model_messages: Option<ModelMessages>,
    pub truncation_policy: TruncationPolicyConfig,
    pub support_verbosity: bool,
    pub default_verbosity: Option<Verbosity>,
    pub web_search_tool_type: WebSearchToolType,
    pub context_window: Option<i64>,
    pub max_context_window: Option<i64>,
    pub auto_compact_token_limit: Option<i64>,
    pub comp_hash: Option<String>,
    pub effective_context_window_percent: i64,
    pub input_modalities: Vec<InputModality>,
    pub used_fallback_model_metadata: bool,
    pub supports_search_tool: bool,
    pub use_responses_lite: bool,
    pub node_repl_auto_review_required: bool,
    pub node_repl_disabled: bool,
    pub tool_mode: Option<ToolMode>,
    pub multi_agent_version: Option<MultiAgentVersion>,
}

上面按本文的四条消费链摘取了字段;完整结构还包含 service tier、升级提示、插件/App 指令注入、verbosity 和 review 等元数据,不能用这段摘录重新定义协议类型。

2. 匹配来源 ​

模型管理器从候选目录构造最终 ModelInfo 时,不要求输入 slug 必须与目录完全相等:先做最长前缀匹配,失败后只剥离一层合法 namespace,再次做最长前缀匹配;都失败才进入 fallback。匹配到远端条目时,返回对象的 slug 会改成用户实际请求的 slug,但 used_fallback_model_metadata 明确置为 false。

源码位置:codex-rs/models-manager/src/manager.rs :: construct_model_info_from_candidates

rust
pub(crate) fn construct_model_info_from_candidates(
    model: &str,
    candidates: &[ModelInfo],
    config: &ModelsManagerConfig,
) -> ModelInfo {
    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)
}

namespace 只允许一层,且 namespace 字符必须是 ASCII 字母、数字、_ 或 -。因此 a/b/c 不会被当作 b/c 继续匹配;这是刻意收窄的 alias 规则,不是通用路径解析。

3. 上下文窗口 ​

窗口相关字段有三个层次:context_window 是当前实际窗口,max_context_window 是配置覆盖上限,effective_context_window_percent 是输入可用比例的元数据;自动压缩还会使用 auto_compact_token_limit,并将其与窗口的 90% 上限取最小值。

源码位置:codex-rs/protocol/src/openai_models.rs :: ModelInfo::auto_compact_token_limit

rust
pub fn resolved_context_window(&self) -> Option<i64> {
    self.context_window.or(self.max_context_window)
}

pub fn auto_compact_token_limit(&self) -> Option<i64> {
    let context_limit = self
        .resolved_context_window()
        .map(|context_window| (context_window * 9) / 10);
    let config_limit = self.auto_compact_token_limit;
    if let Some(context_limit) = context_limit {
        return Some(
            config_limit.map_or(context_limit, |limit| std::cmp::min(limit, context_limit)),
        );
    }
    config_limit
}

这里的 90% 是 ModelInfo 方法的硬编码派生规则;effective_context_window_percent 并不参与这个方法的计算。不能看到字段名相近就把 95% 的有效窗口比例当成 90% 的自动压缩阈值。

context_window_token_status 再把这个模型级结果与 session 当前 token 使用量、配置的 scope 和 fallback buffer 组合。模型字段提供上限,session 状态提供当前消耗,两者缺一不可。

4. 请求能力 ​

在 ModelClient::build_responses_request 中,模型元数据直接影响 reasoning、verbosity、service tier、Responses Lite 和请求字段。并行工具调用先由 prompt/step 能力决定,request 构造还会因 Responses Lite 再次关闭;它不再是 ModelInfo 中一个独立布尔字段。

源码位置:codex-rs/core/src/client.rs :: build_responses_request

rust
let reasoning = Self::build_reasoning(model_info, effort, summary);
let verbosity = if model_info.support_verbosity {
    self.state.model_verbosity.or(model_info.default_verbosity)
} else {
    if self.state.model_verbosity.is_some() {
        warn!(
            "model_verbosity is set but ignored as the model does not support verbosity: {}",
            model_info.slug
        );
    }
    None
};
let service_tier = model_info.service_tier_for_request(service_tier);
let request = ResponsesApiRequest {
    model: model_info.slug.clone(),
    instructions,
    input,
    tools,
    tool_choice: "auto".to_string(),
    parallel_tool_calls: prompt.parallel_tool_calls && !model_info.use_responses_lite,
    reasoning: Some(reasoning),
    store: provider.is_azure_responses_endpoint(),
    stream: true,
    stream_options,
    include,
    service_tier,
    prompt_cache_key,
    text,
    client_metadata: Some(responses_metadata.client_metadata()),
};

supports_parallel_tool_calls 在 core 的 build_prompt 中先决定 Prompt 是否允许并行;请求构造还会额外禁止 Responses Lite。最终行为是多个条件的交集,不是只看 ModelInfo 一个布尔字段。

5. 工具能力 ​

工具规划同时读取 ModelInfo 和 provider capability。supports_search_tool 控制模型是否可发出搜索工具,web_search_tool_type 选择搜索工具形态,use_responses_lite 会让 hosted tool specs 直接为空;provider 的 namespace/web-search 上限仍会继续门控。

源码位置:codex-rs/core/src/tools/spec_plan.rs :: hosted_model_tool_specs

rust
if turn_context.model_info.use_responses_lite
    || crate::guardian::is_guardian_reviewer_source(&turn_context.session_source)
{
    return Vec::new();
}

let standalone_web_search_available = standalone_web_search_enabled(turn_context)
    && registered_extension_tool_names.contains(&ToolName::namespaced("web", "run"));
let web_search_mode = (!standalone_web_search_available
    && turn_context.provider.capabilities().web_search)
    .then_some(turn_context.config.web_search_mode.value());

这段路径说明一个常见误区:模型支持搜索工具,不代表本回合一定会收到搜索工具;extension 是否注册、provider 是否允许 hosted web search、feature 和 guardian 来源都会改变结果。

6. 回退元数据 ​

未知模型不会让 construct_model_info_from_candidates 返回 None,而是创建最小 fallback。它保留 API 可调用性,但把 picker visibility 设为 None、priority 设为 99、并行工具和搜索设为 false,同时把 used_fallback_model_metadata 设为 true。

源码位置:codex-rs/models-manager/src/model_info.rs :: model_info_from_slug

rust
pub fn model_info_from_slug(slug: &str) -> ModelInfo {
    warn!("Unknown model {slug} is used. This will use fallback model metadata.");
    ModelInfo {
        slug: slug.to_string(),
        display_name: slug.to_string(),
        visibility: ModelVisibility::None,
        supported_in_api: true,
        priority: 99,
        truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
        supports_parallel_tool_calls: false,
        context_window: Some(272_000),
        max_context_window: Some(272_000),
        effective_context_window_percent: 95,
        used_fallback_model_metadata: true,
        supports_search_tool: false,
        use_responses_lite: false,
        auto_review_model_override: None,
        model_specialty: None,
        tool_mode: None,
        multi_agent_version: None,
    }
}

这段只省略了中间与当前结论无关、但已在结构定义中列出的字段;结尾字段保持上游源码原顺序,避免用 Rust update syntax 伪装省略。

TurnContext::maybe_emit_model_warnings_for_turn 在创建回合后读取这个 marker 并发出 warning。warning 说明元数据缺失可能降低性能或造成问题,但不会阻止请求;这正是 fallback 的“可运行但不等价”边界。

7. 覆盖顺序 ​

with_config_overrides 在匹配或 fallback 之后执行。当前版本支持上下文窗口、自动压缩阈值、工具输出截断和 base instructions/personality 相关覆盖。它不会把未知模型变成已知模型,也不会凭配置开启模型目录中不存在的任意工具。

源码位置:codex-rs/models-manager/src/model_info.rs :: with_config_overrides

rust
if let Some(context_window) = config.model_context_window {
    model.context_window = Some(
        model
            .max_context_window
            .map_or(context_window, |max_context_window| {
                context_window.min(max_context_window)
            }),
    );
}
if let Some(token_limit) = config.tool_output_token_limit {
    model.truncation_policy = match model.truncation_policy.mode {
        TruncationMode::Bytes => {
            let byte_limit = i64::try_from(approx_bytes_for_tokens(token_limit))
                .unwrap_or(i64::MAX);
            TruncationPolicyConfig::bytes(byte_limit)
        }
        TruncationMode::Tokens => {
            TruncationPolicyConfig::tokens(i64::try_from(token_limit).unwrap_or(i64::MAX))
        }
    };
}

覆盖工具输出限制时保留原模式:原模型按 bytes 截断,就把 token 配置转换成近似 bytes;原模型按 tokens 截断,就直接使用 token 数。配置值不是无条件的 bytes,也不是无条件的 tokens。

8. 指令模板 ​

model_messages 承载的不只有人格,还包括 approvals、collaboration modes、auto review、permissions 和 token budget 消息。base_instructions 覆盖只替换 instructions_template 并清除变量,其他消息子结构保留;这与“清空整个 model_messages”不同。

源码位置:codex-rs/models-manager/src/model_info.rs :: with_config_overrides

rust
if let Some(base_instructions) = &config.base_instructions {
    let model_messages = model.model_messages.get_or_insert(ModelMessages {
        instructions_template: None,
        instructions_variables: None,
        approvals: None,
        collaboration_modes: None,
        auto_review: None,
        permissions: None,
        token_budget: None,
    });
    model_messages.instructions_template = Some(base_instructions.clone());
    model_messages.instructions_variables = None;
}

当没有显式基础指令且人格关闭时,代码分两类处理:fallback 的本地人格模型恢复为 BASE_INSTRUCTIONS;目录提供的模板则把默认人格消息烘焙进模板。Personality::None 且人格启用时,则只移除顶层 # Personality 段落,直到下一个 H1。

9. 能力测试 ​

第一组测试验证能力归一化:model_context_window_override_clamps_to_max_context_window 输入 500000 的覆盖值和 400000 的最大值,断言结果为 400000;offline_model_info_with_tool_output_override 输入 123 token,断言已有 token 模式保留 tokens。它们证明覆盖算法,不证明真实模型上下文容量。

第二组测试验证提示语义:base_instruction_override_is_literal_and_preserves_catalog_messages 断言模板被字面替换、变量清除,但 approvals/collaboration/permissions 等消息仍保留;disabled_personality_uses_plain_base_instructions_for_local_personality_models 断言两个本地人格 slug 在关闭人格后回到基础指令。

源码位置:codex-rs/models-manager/src/model_info_tests.rs :: model_context_window_override_clamps_to_max_context_window

rust
let mut model = model_info_from_slug("unknown-model");
model.context_window = Some(273_000);
model.max_context_window = Some(400_000);
let config = ModelsManagerConfig {
    model_context_window: Some(500_000),
    ..Default::default()
};

let updated = with_config_overrides(model.clone(), &config);
let mut expected = model;
expected.context_window = Some(400_000);

assert_eq!(updated, expected);

这些单元测试没有证明远端 catalog JSON 的 schema 完整性、provider capability 交集、真实模型对工具的接受度,也没有证明 warning 一定能被每个 UI 消费者显示。

10. 复现方法 ​

在本版本源码对应的 codex-rs workspace 中执行:

bash
cd codex-rs
RUST_MIN_STACK=16777216 cargo test -p codex-models-manager model_context_window_override
RUST_MIN_STACK=16777216 cargo test -p codex-models-manager personality
RUST_MIN_STACK=16777216 cargo test -p codex-models-manager offline_model_info_with_tool_output_override

源码练习:先把未知 slug 改成一个候选目录中存在的 slug,比较 used_fallback_model_metadata 和 turn warning;再把 supports_parallel_tool_calls 设为 true,但保持 use_responses_lite 为 true,观察请求是否仍关闭 parallel_tool_calls。这两个实验分别验证元数据来源和多条件能力门控。

11. 导航 ​

本文把 ModelInfo 讲到“字段如何改变行为”的边界;下一步阅读模型目录专题时,应沿 ModelsManager::get_model_info 和 build_available_models 追踪远端、缓存与 picker 的关系。请求构造则从 ModelClient::build_responses_request 进入,前文 模型子系统总览 已给出它与 provider、TurnContext 的生命周期位置。