diff --git a/litellm/main.py b/litellm/main.py index 7d457d9cdd11..a95978570b0b 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1006,7 +1006,8 @@ def responses_api_bridge_check( # ``reasoningSummary`` in ``extra_body``) must be bridged; Chat Completions rejects # those keys. # - # - gpt-5.4+: tools + reasoning_effort (original) or any reasoning-summary alias. + # - gpt-5.4+: tools alone (OpenAI applies reasoning_effort server-side, making + # /v1/chat/completions reject tool calls for this family) or reasoning-summary alias. # - Older GPT-5 names (e.g. ``gpt-5``, ``gpt-5.1``): bridge only when a reasoning # summary alias is present with ``reasoning_effort`` (tools alone stay on chat). if ( @@ -1014,8 +1015,10 @@ def responses_api_bridge_check( and model_info.get("mode") != "responses" and OpenAIGPT5Config.is_model_gpt_5_model(model) and not OpenAIGPT5Config.is_model_gpt_5_search_model(model) - and reasoning_effort is not None - and (reasoning_summary is not None or (OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model) and tools)) + and ( + (reasoning_effort is not None and reasoning_summary is not None) + or (OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model) and tools) + ) ): model_info["mode"] = "responses" model = model.replace("responses/", "") diff --git a/litellm/router_strategy/budget_limiter.py b/litellm/router_strategy/budget_limiter.py index 067f38ab11c8..3514fae90463 100644 --- a/litellm/router_strategy/budget_limiter.py +++ b/litellm/router_strategy/budget_limiter.py @@ -214,6 +214,7 @@ def _filter_out_deployments_above_budget( for idx, deployment in enumerate(healthy_deployments): is_within_budget = True + # Check provider budget # Check provider budget if self.provider_budget_config: if idx < len(deployment_providers): @@ -222,21 +223,18 @@ def _filter_out_deployments_above_budget( provider = self._get_llm_provider_for_deployment(deployment) if provider in provider_configs: config = provider_configs[provider] - if config.max_budget is None: - continue - current_spend = spend_map.get(f"provider_spend:{provider}:{config.budget_duration}", 0.0) - self._track_provider_remaining_budget_prometheus( - provider=provider, - spend=current_spend, - budget_limit=config.max_budget, - ) - - if config.max_budget and current_spend >= config.max_budget: - debug_msg = f"Exceeded budget for provider {provider}: {current_spend} >= {config.max_budget}" - deployment_above_budget_info += f"{debug_msg}\n" - is_within_budget = False - continue - + if config.max_budget is not None: + current_spend = spend_map.get(f"provider_spend:{provider}:{config.budget_duration}", 0.0) + self._track_provider_remaining_budget_prometheus( + provider=provider, + spend=current_spend, + budget_limit=config.max_budget, + ) + if current_spend >= config.max_budget: + debug_msg = f"Exceeded budget for provider {provider}: {current_spend} >= {config.max_budget}" + deployment_above_budget_info += f"{debug_msg}\n" + is_within_budget = False + # Check deployment budget if self.deployment_budget_config and is_within_budget: _model_name = deployment.get("model_name") diff --git a/tests/test_litellm/test_gpt56_bridge.py b/tests/test_litellm/test_gpt56_bridge.py new file mode 100644 index 000000000000..42eb2dacb8a6 --- /dev/null +++ b/tests/test_litellm/test_gpt56_bridge.py @@ -0,0 +1,16 @@ +"""Regression test for https://github.com/BerriAI/litellm/issues/33221""" +from litellm.main import responses_api_bridge_check + + +def test_gpt56_tools_bridged_to_responses_without_reasoning_effort(): + tools = [{"type": "function", "function": {"name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}] + for model in ["gpt-5.6-sol", "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6"]: + model_info, _ = responses_api_bridge_check(model=model, custom_llm_provider="openai", tools=tools, reasoning_effort=None) + assert model_info.get("mode") == "responses", f"{model} with tools should bridge to responses even without reasoning_effort" + +def test_older_gpt5_with_tools_not_bridged_without_reasoning_effort(): + """gpt-5, gpt-5.1, gpt-5.3 with tools should NOT bridge without reasoning_effort.""" + tools = [{"type": "function", "function": {"name": "get_weather", "description": "test", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}] + for model in ["gpt-5", "gpt-5.1", "gpt-5.3"]: + model_info, _ = responses_api_bridge_check(model=model, custom_llm_provider="openai", tools=tools, reasoning_effort=None) + assert model_info.get("mode") != "responses", f"{model} with tools but no reasoning_effort should NOT bridge" \ No newline at end of file diff --git a/tests/test_provider_budget_fix.py b/tests/test_provider_budget_fix.py new file mode 100644 index 000000000000..e15184181632 --- /dev/null +++ b/tests/test_provider_budget_fix.py @@ -0,0 +1,40 @@ +"""Regression test for https://github.com/BerriAI/litellm/issues/33327""" +from litellm.router_strategy.budget_limiter import RouterBudgetLimiting +from litellm.types.utils import BudgetConfig + + +def test_duration_only_provider_config_keeps_deployment(): + """ + A provider budget entry with duration but no max_budget should NOT + remove deployments from the healthy set. + """ + limiter = RouterBudgetLimiting.__new__(RouterBudgetLimiting) + limiter.provider_budget_config = {"openai": BudgetConfig(budget_duration="1d", max_budget=None)} + limiter.deployment_budget_config = None + limiter.tag_budget_config = None + + healthy_deployments = [{ + "model_name": "chat", + "litellm_params": { + "model": "openai/gpt-4o-mini", + "custom_llm_provider": "openai", + }, + "model_info": {"id": "deployment-1"}, + }] + + provider_configs = {"openai": limiter.provider_budget_config["openai"]} + + result, _ = limiter._filter_out_deployments_above_budget( + potential_deployments=healthy_deployments, + healthy_deployments=healthy_deployments, + provider_configs=provider_configs, + deployment_configs={}, + deployment_providers=["openai"], + spend_map={}, + request_tags=[], + ) + + assert len(result) == 1, ( + f"Expected 1 deployment, got {len(result)}. " + "duration-only provider config should not remove deployments." + )