Skip to content
Open
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
10 changes: 10 additions & 0 deletions litellm/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6339,6 +6339,16 @@ async def async_function_with_fallbacks_common_utils( # noqa: PLR0915
if disable_fallbacks is True or original_model_group is None:
raise e

# If the caller routed to a `model_group_alias`, fallbacks may be
# declared against the underlying real model group (see #10317).
# Resolve the alias here so the downstream exact-match lookup in
# `get_fallback_model_group` works in both cases. `original_model_group`
# intentionally keeps the alias name for logging / fallback headers.
if model_group is not None:
aliased_model_group = self._get_model_from_alias(model=model_group)

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.

Low: Alias-specific fallback policy is bypassed

Rewriting model_group before lookup means a configuration containing both {"public-alias": ["safe-fallback"]} and {"real-group": ["other-fallback"]} will always select the latter for alias requests. A caller can deliberately trigger a content-policy or provider failure and route their request to a backend that was not configured for the public alias. Preserve alias-keyed policies by trying the original alias first, and only retry the lookup with the resolved model group when no alias-specific entry matches; apply the same precedence to general, context-window, and content-policy fallbacks.

if aliased_model_group is not None:
model_group = aliased_model_group

input_kwargs = {
"litellm_router": self,
"original_exception": original_exception,
Expand Down
54 changes: 54 additions & 0 deletions tests/local_testing/test_router_fallbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1626,3 +1626,57 @@ async def test_router_attempted_fallbacks_in_response(expected_attempted_fallbac
resp._hidden_params["additional_headers"]["x-litellm-attempted-fallbacks"]
== expected_attempted_fallbacks
)


@pytest.mark.parametrize("sync_mode", [True, False])
@pytest.mark.asyncio
async def test_model_group_alias_respects_fallbacks(sync_mode):
"""
Related issue - https://github.com/BerriAI/litellm/issues/10317

When a request targets a model via ``model_group_alias``, fallbacks
declared against the underlying real model group should still fire.
Previously the fallback lookup was performed against the alias name
(e.g. ``"alias-model"``) which never matched a fallback keyed by the
real model (``"real-bad-model"``), so the router raised the original
error with ``No fallback model group found for original
model_group=alias-model``.
"""
router = Router(
model_list=[
{
"model_name": "real-bad-model",
"litellm_params": {
"model": "openai/my-bad-model",
"api_key": "my-bad-api-key",
},
},
{
"model_name": "my-good-model",
"litellm_params": {
"model": "gpt-4o",
"api_key": os.getenv("OPENAI_API_KEY"),
},
},
],
model_group_alias={"alias-model": "real-bad-model"},
fallbacks=[{"real-bad-model": ["my-good-model"]}],
)

if sync_mode:
response = router.completion(
model="alias-model",
messages=[{"role": "user", "content": "Hey, how's it going?"}],
mock_testing_fallbacks=True,
mock_response="Hey! nice day",
)
else:
response = await router.acompletion(
model="alias-model",
messages=[{"role": "user", "content": "Hey, how's it going?"}],
mock_testing_fallbacks=True,
mock_response="Hey! nice day",
)

assert isinstance(response, litellm.ModelResponse)
assert response.model == "gpt-4o"
Loading