Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions litellm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,16 +1006,19 @@ 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 (
custom_llm_provider in ("openai", "azure")
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/", "")
Expand Down
28 changes: 13 additions & 15 deletions litellm/router_strategy/budget_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Comment on lines +217 to 219

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicate # Check provider budget comment was introduced by the diff. One of them should be removed.

Suggested change
# Check provider budget
# Check provider budget
if self.provider_budget_config:
# Check provider budget
if self.provider_budget_config:

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

if idx < len(deployment_providers):
Expand All @@ -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")
Expand Down
16 changes: 16 additions & 0 deletions tests/test_litellm/test_gpt56_bridge.py
Original file line number Diff line number Diff line change
@@ -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"
40 changes: 40 additions & 0 deletions tests/test_provider_budget_fix.py
Original file line number Diff line number Diff line change
@@ -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=[],
)
Comment on lines +27 to +35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Same list passed to both potential_deployments and healthy_deployments causes an infinite loop

potential_deployments=healthy_deployments passes the same Python list object for both parameters. Inside _filter_out_deployments_above_budget, the method appends each eligible deployment to potential_deployments while iterating over healthy_deployments. Because both refer to the same list, the first eligible deployment is appended mid-iteration, the iterator sees the new element, processes it again, appends again, and so on indefinitely. In production, potential_deployments is always initialized as a separate empty list (potential_deployments: List[Dict] = [] at line 141 of budget_limiter.py). The test should pass potential_deployments=[] to match the production call pattern and avoid this hang.


assert len(result) == 1, (
f"Expected 1 deployment, got {len(result)}. "
"duration-only provider config should not remove deployments."
)
Loading