diff --git a/litellm/router_utils/reasoning_effort_capability.py b/litellm/router_utils/reasoning_effort_capability.py index 399f97a9864..9185d901a28 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 fd5926b75f0..a0dbf3b6637 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 @@ class TestResolveSupportedReasoningEfforts: 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 @@ class TestResolveSupportedReasoningEfforts: ) 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):