diff --git a/litellm/router_utils/reasoning_effort_capability.py b/litellm/router_utils/reasoning_effort_capability.py index 2a4fae4109f..399f97a9864 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 ff5660288f5..fd5926b75f0 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 @@ 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, + }, + 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):