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
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
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
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
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
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
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
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
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
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 中执行:
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 的生命周期位置。
