From 555e4c0709c930864190d46fbcbb4c4fc0b9c9dc Mon Sep 17 00:00:00 2001 From: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 Date: Fri, 31 Jul 2026 14:09:16 -0400 Subject: [PATCH] fix(agent): normalize_effort_for_databricks_v2 must consult generated supported_efforts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenAiStandard arm was delegating to normalize_effort_for_openai_route, which uses the old hand table and returns the uncorrected effort set for models with F1 corrections. Concretely: normalize_effort_for_databricks_v2(XHigh, "databricks-gpt-5-5") → XHigh (wrong) normalize_effort_for_databricks_v2(None, "databricks-gpt-5-5") → None (wrong) The fix: resolve against cap.supported_efforts (the generated axis carrying the F1 corrections) via the existing resolve_openai_effort clamping function. Same peer-fallback and nearest-supported semantics, one authority. Also apply the same fix to the OpenAiClampMaxToXHigh arm for consistency: values other than max now resolve against supported_efforts rather than passing through unchecked if the generated set happens to exclude them. Add four regression tests pinning the corrected behavior: - xhigh → high for databricks-gpt-5-5 (F1: supported=[low,medium,high]) - none → low for databricks-gpt-5-5 - low/medium/high pass through unchanged - max passes through for databricks-gpt-5-6-sol (F1: supported includes max) Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- crates/buzz-agent/src/config.rs | 68 +++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/crates/buzz-agent/src/config.rs b/crates/buzz-agent/src/config.rs index 27e24ef3ee..98aa97f21c 100644 --- a/crates/buzz-agent/src/config.rs +++ b/crates/buzz-agent/src/config.rs @@ -600,9 +600,18 @@ pub fn normalize_effort_for_databricks_v2( raw_model: &str, ) -> ThinkingEffort { use crate::generated_model_capabilities::{resolve_model_capabilities, NormalizationPolicy}; - match resolve_model_capabilities("databricks_v2", raw_model).normalization_policy { - NormalizationPolicy::OpenAiStandard => normalize_effort_for_openai_route(effort, raw_model), + let cap = resolve_model_capabilities("databricks_v2", raw_model); + match cap.normalization_policy { + NormalizationPolicy::OpenAiStandard => { + // Resolve against the generated `supported_efforts` — this is the axis that + // carries exact-record corrections (e.g. databricks-gpt-5-5 → [low,medium,high]). + // Uses the same clamping/peer-fallback semantics as the old hand-table lookup. + resolve_openai_effort(raw_model, effort, cap.supported_efforts.as_ref()) + } NormalizationPolicy::OpenAiClampMaxToXHigh => { + // Only `max` is out-of-range; all other values pass through if supported. + // Resolve against supported_efforts so that unsupported values are clamped + // consistently (not just `max`). if effort == ThinkingEffort::Max { tracing::warn!( requested = "max", @@ -612,7 +621,7 @@ pub fn normalize_effort_for_databricks_v2( ); ThinkingEffort::XHigh } else { - effort + resolve_openai_effort(raw_model, effort, cap.supported_efforts.as_ref()) } } NormalizationPolicy::None => effort, @@ -2868,6 +2877,59 @@ mod tests { } } + // ---- normalize_effort_for_databricks_v2 regression tests (F1 corrections) ---- + // These pin the exact behavior Paul's pre-review probes checked. The key invariant: + // normalize_effort_for_databricks_v2 must resolve against the generated supported_efforts + // (which carries exact-record F1 corrections), NOT the old hand table. + + #[test] + fn normalize_effort_for_databricks_v2_gpt_5_5_xhigh_clamps_to_high() { + // F1 correction: databricks-gpt-5-5 generated supported_efforts = [low, medium, high]. + // XHigh is outside the supported set → nearest supported is High. + assert_eq!( + normalize_effort_for_databricks_v2(ThinkingEffort::XHigh, "databricks-gpt-5-5"), + ThinkingEffort::High, + "databricks-gpt-5-5 XHigh must clamp to High (F1 correction: supported=[low,medium,high])" + ); + } + + #[test] + fn normalize_effort_for_databricks_v2_gpt_5_5_none_clamps_to_low() { + // F1 correction: databricks-gpt-5-5 supported_efforts = [low, medium, high]. + // None is outside the set → nearest supported is Low. + assert_eq!( + normalize_effort_for_databricks_v2(ThinkingEffort::None, "databricks-gpt-5-5"), + ThinkingEffort::Low, + "databricks-gpt-5-5 None must clamp to Low (F1 correction: supported=[low,medium,high])" + ); + } + + #[test] + fn normalize_effort_for_databricks_v2_gpt_5_5_in_range_passes_through() { + // Values within the corrected set must pass through unchanged. + for effort in [ + ThinkingEffort::Low, + ThinkingEffort::Medium, + ThinkingEffort::High, + ] { + assert_eq!( + normalize_effort_for_databricks_v2(effort, "databricks-gpt-5-5"), + effort, + "databricks-gpt-5-5 {effort:?} is in supported set, must pass through" + ); + } + } + + #[test] + fn normalize_effort_for_databricks_v2_gpt_5_6_sol_max_passes_through() { + // databricks-gpt-5-6-sol F1 adoption: [low, medium, high, max] — max is supported. + assert_eq!( + normalize_effort_for_databricks_v2(ThinkingEffort::Max, "databricks-gpt-5-6-sol"), + ThinkingEffort::Max, + "databricks-gpt-5-6-sol Max must pass through (F1: supported includes max)" + ); + } + /// Phase-2 differential: new generated effort config matches old hand-coded helper /// for every entry in effortTable.fixture.json. This gate ensures Phase 2 cutover /// is behavior-preserving except where the allowlist explicitly covers a correction.