From 5e651d80527de58512103f8fe32cc013edd8e577 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Thu, 27 Aug 2026 20:49:58 -0700 Subject: [PATCH 1/2] fix(anthropic): handle per-level reasoning_effort flags without supports_reasoning When a model has only per-level flags (e.g. supports_minimal_reasoning_effort: true) but no explicit supports_reasoning flag, treat it as implicitly reasoning-capable. This fixes gpt-5-search-api which declares minimal support but was incorrectly degraded to low/minimal floor due to missing explicit supports_reasoning flag. Test: verify per-level flag enables resolution path even without supports_reasoning. Note: This change indirectly causes 20 azure deployments to forward max/xhigh instead of degrading to high when requested, as these models now correctly resolve their supported efforts through declared capability flags. This is intended behavior (avoiding unnecessary degradation) but silent; operators seeing increased latency/cost should check reasoning effort changes in logs. Co-Authored-By: Claude --- litellm/router_utils/reasoning_effort_capability.py | 11 +++++++++-- .../router_utils/test_reasoning_effort_capability.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/litellm/router_utils/reasoning_effort_capability.py b/litellm/router_utils/reasoning_effort_capability.py index 2a4fae4109f4..399f97a98641 100644 --- a/litellm/router_utils/reasoning_effort_capability.py +++ b/litellm/router_utils/reasoning_effort_capability.py @@ -148,16 +148,23 @@ def resolve_supported_reasoning_efforts( unset flag as () would let one custom deployment empty every level its mapped siblings agree on. deployment_is_mapped is that provenance, and an operator who wants either answer for an off-map deployment gets it by setting supports_reasoning explicitly. + + If no explicit supports_reasoning flag is set but at least one per-level flag (e.g. + supports_minimal_reasoning_effort) is present, treat supports_reasoning as implicitly True, + since the per-level flags are evidence the model supports reasoning. """ supports_reasoning: Final = model_info.get("supports_reasoning") + flags: Final = _declared_effort_flags(model_info) + has_per_level_flag: Final = any(value is not None for value in flags.values()) + if supports_reasoning is not True: - return () if supports_reasoning is False or deployment_is_mapped else None + if not has_per_level_flag: + return () if supports_reasoning is False or deployment_is_mapped else None declared: Final = declared_reasoning_efforts(model_info) if declared is not None: return declared - flags: Final = _declared_effort_flags(model_info) if all(value is None for value in flags.values()): return None diff --git a/tests/test_litellm/router_utils/test_reasoning_effort_capability.py b/tests/test_litellm/router_utils/test_reasoning_effort_capability.py index ff5660288f58..fd5926b75f07 100644 --- a/tests/test_litellm/router_utils/test_reasoning_effort_capability.py +++ b/tests/test_litellm/router_utils/test_reasoning_effort_capability.py @@ -85,6 +85,18 @@ def test_opt_in_flag_set_false_stays_excluded(self): ) assert resolved == ("none", "minimal", "low", "medium", "high") + def test_per_level_flag_without_supports_reasoning_treats_as_implicit_true(self): + # A model with only a per-level flag (e.g. supports_minimal_reasoning_effort) but no + # explicit supports_reasoning should be treated as reasoning-capable, since the per-level + # flag is evidence of reasoning support. + resolved = resolve_supported_reasoning_efforts( + { + "supports_minimal_reasoning_effort": True, + }, + deployment_is_mapped=True, + ) + assert resolved == ("none", "minimal", "low", "medium", "high") + class TestBareModelNameFallback: def test_a_prefixed_entry_inherits_the_flags_of_its_unprefixed_twin(self): From 460361923cdcf385171aab24b4c06325be1690bf Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Thu, 27 Aug 2026 20:58:24 -0700 Subject: [PATCH 2/2] fix(anthropic): explicit supports_reasoning=False wins over per-level flags Greptile P1: the implicit-True branch bypassed the operator's explicit supports_reasoning: false escape hatch when per-level flags were present or inherited through the bare-twin lookup. Return () first on explicit False, then apply the per-level implication only when the flag is unset. Also drops a test comment that restated the test name (P2). Co-Authored-By: Claude --- .../router_utils/reasoning_effort_capability.py | 16 +++++++++------- .../test_reasoning_effort_capability.py | 13 ++++++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/litellm/router_utils/reasoning_effort_capability.py b/litellm/router_utils/reasoning_effort_capability.py index 399f97a98641..9185d901a284 100644 --- a/litellm/router_utils/reasoning_effort_capability.py +++ b/litellm/router_utils/reasoning_effort_capability.py @@ -149,17 +149,19 @@ def resolve_supported_reasoning_efforts( on. deployment_is_mapped is that provenance, and an operator who wants either answer for an off-map deployment gets it by setting supports_reasoning explicitly. - If no explicit supports_reasoning flag is set but at least one per-level flag (e.g. - supports_minimal_reasoning_effort) is present, treat supports_reasoning as implicitly True, - since the per-level flags are evidence the model supports reasoning. + If supports_reasoning is unset but at least one per-level flag (e.g. + supports_minimal_reasoning_effort) is present, treat it as implicitly True, since the + per-level flags are evidence the model supports reasoning. An explicit False always wins: + it is the operator's escape hatch and must not be overridden by inherited per-level flags. """ supports_reasoning: Final = model_info.get("supports_reasoning") + if supports_reasoning is False: + return () + flags: Final = _declared_effort_flags(model_info) has_per_level_flag: Final = any(value is not None for value in flags.values()) - - if supports_reasoning is not True: - if not has_per_level_flag: - return () if supports_reasoning is False or deployment_is_mapped else None + if supports_reasoning is not True and not has_per_level_flag: + return () if deployment_is_mapped else None declared: Final = declared_reasoning_efforts(model_info) if declared is not None: diff --git a/tests/test_litellm/router_utils/test_reasoning_effort_capability.py b/tests/test_litellm/router_utils/test_reasoning_effort_capability.py index fd5926b75f07..a0dbf3b66375 100644 --- a/tests/test_litellm/router_utils/test_reasoning_effort_capability.py +++ b/tests/test_litellm/router_utils/test_reasoning_effort_capability.py @@ -86,9 +86,6 @@ def test_opt_in_flag_set_false_stays_excluded(self): assert resolved == ("none", "minimal", "low", "medium", "high") def test_per_level_flag_without_supports_reasoning_treats_as_implicit_true(self): - # A model with only a per-level flag (e.g. supports_minimal_reasoning_effort) but no - # explicit supports_reasoning should be treated as reasoning-capable, since the per-level - # flag is evidence of reasoning support. resolved = resolve_supported_reasoning_efforts( { "supports_minimal_reasoning_effort": True, @@ -97,6 +94,16 @@ def test_per_level_flag_without_supports_reasoning_treats_as_implicit_true(self) ) assert resolved == ("none", "minimal", "low", "medium", "high") + def test_explicit_supports_reasoning_false_wins_over_per_level_flags(self): + resolved = resolve_supported_reasoning_efforts( + { + "supports_reasoning": False, + "supports_minimal_reasoning_effort": True, + }, + deployment_is_mapped=True, + ) + assert resolved == () + class TestBareModelNameFallback: def test_a_prefixed_entry_inherits_the_flags_of_its_unprefixed_twin(self):