fix(agent): normalize_effort_for_databricks_v2 must consult generated supported_efforts

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 <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
2026-07-31 14:09:16 -04:00
parent 305627e32b
commit 555e4c0709

View File

@@ -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.