From 5e651d80527de58512103f8fe32cc013edd8e577 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Thu, 27 Aug 2026 20:49:58 -0700 Subject: [PATCH] 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 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):