mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(models): clamp reasoning effort to supported levels (closes #422)
This commit is contained in:
@@ -17,6 +17,7 @@ use code_core::protocol::SandboxPolicy;
|
||||
use code_core::slash_commands::get_enabled_agents;
|
||||
use code_core::{AuthManager, ModelClient, Prompt, ResponseEvent, TextFormat};
|
||||
use code_core::error::CodexErr;
|
||||
use code_common::model_presets::clamp_reasoning_effort_for_model;
|
||||
use code_protocol::models::{ContentItem, ReasoningItemContent, ResponseItem};
|
||||
use code_core::protocol::TokenUsage;
|
||||
use futures::StreamExt;
|
||||
@@ -922,6 +923,11 @@ fn run_auto_loop(
|
||||
if matches!(config.model_reasoning_effort, ReasoningEffort::None) {
|
||||
config.model_reasoning_effort = ReasoningEffort::High;
|
||||
}
|
||||
let requested_effort: code_protocol::config_types::ReasoningEffort =
|
||||
config.model_reasoning_effort.into();
|
||||
let clamped_effort =
|
||||
clamp_reasoning_effort_for_model(&config.model, requested_effort);
|
||||
config.model_reasoning_effort = ReasoningEffort::from(clamped_effort);
|
||||
let allowed_verbosity = supported_text_verbosity_for_model(&config.model);
|
||||
config.model_text_verbosity = allowed_verbosity
|
||||
.iter()
|
||||
|
||||
@@ -268,14 +268,57 @@ pub fn all_model_presets() -> &'static Vec<ModelPreset> {
|
||||
&PRESETS
|
||||
}
|
||||
|
||||
pub fn allowed_text_verbosity_for_model(model: &str) -> &'static [TextVerbosityConfig] {
|
||||
fn find_preset_for_model(model: &str) -> Option<&'static ModelPreset> {
|
||||
let model_lower = model.to_ascii_lowercase();
|
||||
PRESETS
|
||||
|
||||
PRESETS.iter().find(|preset| {
|
||||
preset.model.eq_ignore_ascii_case(&model_lower)
|
||||
|| preset.id.eq_ignore_ascii_case(&model_lower)
|
||||
|| preset.display_name.eq_ignore_ascii_case(&model_lower)
|
||||
})
|
||||
}
|
||||
|
||||
fn reasoning_effort_rank(effort: ReasoningEffort) -> u8 {
|
||||
match effort {
|
||||
ReasoningEffort::Minimal => 0,
|
||||
ReasoningEffort::Low => 1,
|
||||
ReasoningEffort::Medium => 2,
|
||||
ReasoningEffort::High => 3,
|
||||
ReasoningEffort::XHigh => 4,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_reasoning_effort_for_model(
|
||||
model: &str,
|
||||
requested: ReasoningEffort,
|
||||
) -> ReasoningEffort {
|
||||
let Some(preset) = find_preset_for_model(model) else {
|
||||
return requested;
|
||||
};
|
||||
|
||||
if preset
|
||||
.supported_reasoning_efforts
|
||||
.iter()
|
||||
.find(|preset| {
|
||||
preset.model.eq_ignore_ascii_case(&model_lower)
|
||||
|| preset.id.eq_ignore_ascii_case(&model_lower)
|
||||
.any(|opt| opt.effort == requested)
|
||||
{
|
||||
return requested;
|
||||
}
|
||||
|
||||
let requested_rank = reasoning_effort_rank(requested);
|
||||
|
||||
preset
|
||||
.supported_reasoning_efforts
|
||||
.iter()
|
||||
.min_by_key(|opt| {
|
||||
let rank = reasoning_effort_rank(opt.effort);
|
||||
(requested_rank.abs_diff(rank), u8::MAX - rank)
|
||||
})
|
||||
.map(|opt| opt.effort)
|
||||
.unwrap_or(requested)
|
||||
}
|
||||
|
||||
pub fn allowed_text_verbosity_for_model(model: &str) -> &'static [TextVerbosityConfig] {
|
||||
find_preset_for_model(model)
|
||||
.map(|preset| preset.supported_text_verbosity)
|
||||
.unwrap_or(ALL_TEXT_VERBOSITY)
|
||||
}
|
||||
@@ -296,4 +339,17 @@ mod tests {
|
||||
.iter()
|
||||
.all(|preset| preset.id != "gpt-5.1-codex-max"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_reasoning_effort_downgrades_to_supported_level() {
|
||||
let clamped = clamp_reasoning_effort_for_model(
|
||||
"gpt-5.1-codex",
|
||||
ReasoningEffort::XHigh,
|
||||
);
|
||||
assert_eq!(clamped, ReasoningEffort::High);
|
||||
|
||||
let clamped_minimal =
|
||||
clamp_reasoning_effort_for_model("gpt-5.1-codex-mini", ReasoningEffort::Minimal);
|
||||
assert_eq!(clamped_minimal, ReasoningEffort::Medium);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1432,6 +1432,20 @@ impl From<code_protocol::config_types::ReasoningEffort> for ReasoningEffort {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReasoningEffort> for code_protocol::config_types::ReasoningEffort {
|
||||
fn from(v: ReasoningEffort) -> Self {
|
||||
match v {
|
||||
ReasoningEffort::Minimal | ReasoningEffort::None => {
|
||||
code_protocol::config_types::ReasoningEffort::Minimal
|
||||
}
|
||||
ReasoningEffort::Low => code_protocol::config_types::ReasoningEffort::Low,
|
||||
ReasoningEffort::Medium => code_protocol::config_types::ReasoningEffort::Medium,
|
||||
ReasoningEffort::High => code_protocol::config_types::ReasoningEffort::High,
|
||||
ReasoningEffort::XHigh => code_protocol::config_types::ReasoningEffort::XHigh,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<code_protocol::config_types::ReasoningSummary> for ReasoningSummary {
|
||||
fn from(v: code_protocol::config_types::ReasoningSummary) -> Self {
|
||||
match v {
|
||||
|
||||
@@ -23,7 +23,7 @@ use code_app_server_protocol::ClientRequest;
|
||||
use code_protocol::ConversationId;
|
||||
use code_protocol::protocol::SessionSource;
|
||||
|
||||
use code_common::model_presets::{builtin_model_presets, ModelPreset};
|
||||
use code_common::model_presets::{builtin_model_presets, clamp_reasoning_effort_for_model, ModelPreset};
|
||||
use code_core::AuthManager;
|
||||
use code_core::ConversationManager;
|
||||
use code_core::config_types::{ClientTools, McpServerConfig, McpServerTransportConfig, ReasoningEffort};
|
||||
@@ -1592,6 +1592,10 @@ fn resolve_model_selection(model_id: &acp::ModelId, config: &Config) -> Option<M
|
||||
}
|
||||
|
||||
fn apply_model_selection(config: &mut Config, model: &str, effort: ReasoningEffort) -> bool {
|
||||
let requested_effort: code_protocol::config_types::ReasoningEffort = effort.into();
|
||||
let clamped_effort: ReasoningEffort =
|
||||
clamp_reasoning_effort_for_model(model, requested_effort).into();
|
||||
|
||||
let mut updated = false;
|
||||
if !config.model.eq_ignore_ascii_case(model) {
|
||||
config.model = model.to_string();
|
||||
@@ -1600,8 +1604,8 @@ fn apply_model_selection(config: &mut Config, model: &str, effort: ReasoningEffo
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if config.model_reasoning_effort != effort {
|
||||
config.model_reasoning_effort = effort;
|
||||
if config.model_reasoning_effort != clamped_effort {
|
||||
config.model_reasoning_effort = clamped_effort;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ use crate::spinner;
|
||||
use crate::thread_spawner;
|
||||
|
||||
use code_common::elapsed::format_duration;
|
||||
use code_common::model_presets::ModelPreset;
|
||||
use code_common::model_presets::builtin_model_presets;
|
||||
use code_common::model_presets::clamp_reasoning_effort_for_model;
|
||||
use code_common::model_presets::ModelPreset;
|
||||
use code_core::ConversationManager;
|
||||
use code_core::agent_defaults::{agent_model_spec, enabled_agent_model_specs};
|
||||
use code_core::smoke_test_agent_blocking;
|
||||
@@ -18655,6 +18656,12 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
preset.default_reasoning_effort.into()
|
||||
}
|
||||
|
||||
fn clamp_reasoning_for_model(model: &str, requested: ReasoningEffort) -> ReasoningEffort {
|
||||
let protocol_effort: code_protocol::config_types::ReasoningEffort = requested.into();
|
||||
let clamped = clamp_reasoning_effort_for_model(model, protocol_effort);
|
||||
ReasoningEffort::from(clamped)
|
||||
}
|
||||
|
||||
fn find_model_preset(&self, input: &str, presets: &[ModelPreset]) -> Option<ModelPreset> {
|
||||
if presets.is_empty() {
|
||||
return None;
|
||||
@@ -18848,11 +18855,12 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if let Some(new_effort) = effort {
|
||||
if self.config.model_reasoning_effort != new_effort {
|
||||
self.config.model_reasoning_effort = new_effort;
|
||||
updated = true;
|
||||
}
|
||||
let requested_effort = effort.unwrap_or(self.config.model_reasoning_effort);
|
||||
let clamped_effort = Self::clamp_reasoning_for_model(trimmed, requested_effort);
|
||||
|
||||
if self.config.model_reasoning_effort != clamped_effort {
|
||||
self.config.model_reasoning_effort = clamped_effort;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if updated {
|
||||
@@ -18924,14 +18932,16 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
|
||||
self.config.review_use_chat_model = false;
|
||||
|
||||
let clamped_effort = Self::clamp_reasoning_for_model(trimmed, effort);
|
||||
|
||||
let mut updated = false;
|
||||
if !self.config.review_model.eq_ignore_ascii_case(trimmed) {
|
||||
self.config.review_model = trimmed.to_string();
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if self.config.review_model_reasoning_effort != effort {
|
||||
self.config.review_model_reasoning_effort = effort;
|
||||
if self.config.review_model_reasoning_effort != clamped_effort {
|
||||
self.config.review_model_reasoning_effort = clamped_effort;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
@@ -19077,13 +19087,15 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
|
||||
self.config.planning_use_chat_model = false;
|
||||
|
||||
let clamped_effort = Self::clamp_reasoning_for_model(trimmed, effort);
|
||||
|
||||
let mut updated = false;
|
||||
if !self.config.planning_model.eq_ignore_ascii_case(trimmed) {
|
||||
self.config.planning_model = trimmed.to_string();
|
||||
updated = true;
|
||||
}
|
||||
if self.config.planning_model_reasoning_effort != effort {
|
||||
self.config.planning_model_reasoning_effort = effort;
|
||||
if self.config.planning_model_reasoning_effort != clamped_effort {
|
||||
self.config.planning_model_reasoning_effort = clamped_effort;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
@@ -19231,14 +19243,16 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
|
||||
self.config.auto_drive_use_chat_model = false;
|
||||
|
||||
let clamped_effort = Self::clamp_reasoning_for_model(trimmed, effort);
|
||||
|
||||
let mut updated = false;
|
||||
if !self.config.auto_drive.model.eq_ignore_ascii_case(trimmed) {
|
||||
self.config.auto_drive.model = trimmed.to_string();
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if self.config.auto_drive.model_reasoning_effort != effort {
|
||||
self.config.auto_drive.model_reasoning_effort = effort;
|
||||
if self.config.auto_drive.model_reasoning_effort != clamped_effort {
|
||||
self.config.auto_drive.model_reasoning_effort = clamped_effort;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
@@ -19489,14 +19503,25 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
}
|
||||
|
||||
pub(crate) fn set_reasoning_effort(&mut self, new_effort: ReasoningEffort) {
|
||||
let clamped_effort = Self::clamp_reasoning_for_model(&self.config.model, new_effort);
|
||||
|
||||
if clamped_effort != new_effort {
|
||||
let requested = Self::format_reasoning_effort(new_effort);
|
||||
let applied = Self::format_reasoning_effort(clamped_effort);
|
||||
self.bottom_pane.flash_footer_notice(format!(
|
||||
"{} does not support {} reasoning; using {} instead.",
|
||||
self.config.model, requested, applied
|
||||
));
|
||||
}
|
||||
|
||||
// Update the config
|
||||
self.config.model_reasoning_effort = new_effort;
|
||||
self.config.model_reasoning_effort = clamped_effort;
|
||||
|
||||
// Send ConfigureSession op to update the backend
|
||||
let op = Op::ConfigureSession {
|
||||
provider: self.config.model_provider.clone(),
|
||||
model: self.config.model.clone(),
|
||||
model_reasoning_effort: new_effort,
|
||||
model_reasoning_effort: clamped_effort,
|
||||
model_reasoning_summary: self.config.model_reasoning_summary,
|
||||
model_text_verbosity: self.config.model_text_verbosity,
|
||||
user_instructions: self.config.user_instructions.clone(),
|
||||
@@ -19513,7 +19538,7 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
|
||||
// Add status message to history (replaceable system notice)
|
||||
let placement = self.ui_placement_for_now();
|
||||
let state = history_cell::new_reasoning_output(&new_effort);
|
||||
let state = history_cell::new_reasoning_output(&self.config.model_reasoning_effort);
|
||||
let cell = crate::history_cell::PlainHistoryCell::from_state(state.clone());
|
||||
self.push_system_cell(
|
||||
Box::new(cell),
|
||||
|
||||
Reference in New Issue
Block a user