diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index 401963c301b..71ae1348f39 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -175,22 +175,27 @@ def get_complete_model_list( If list contains wildcard -> return known provider models """ - unique_models: Set[str] = set() + unique_models = [] + def append_unique(models): + for model in models: + if model not in unique_models: + unique_models.append(model) + if key_models: - unique_models.update(key_models) + append_unique(key_models) elif team_models: - unique_models.update(team_models) + append_unique(team_models) else: - unique_models.update(proxy_model_list) + append_unique(proxy_model_list) if include_model_access_groups: - unique_models.update(model_access_groups.keys()) + append_unique(list(model_access_groups.keys())) # TODO: keys order if user_model: - unique_models.add(user_model) + append_unique([user_model]) if infer_model_from_keys: valid_models = get_valid_models() - unique_models.update(valid_models) + append_unique(valid_models) if only_model_access_groups: model_access_groups_to_return: List[str] = [] @@ -205,7 +210,7 @@ def get_complete_model_list( llm_router=llm_router, ) - complete_model_list = list(unique_models) + all_wildcard_models + complete_model_list = unique_models + all_wildcard_models return complete_model_list @@ -261,7 +266,7 @@ def get_known_models_from_wildcard( def _get_wildcard_models( - unique_models: Set[str], + unique_models: List[str], return_wildcard_routes: Optional[bool] = False, llm_router: Optional[Router] = None, ) -> List[str]: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 98355afdfd6..c6ba2a99d52 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6469,18 +6469,18 @@ async def model_metrics_exceptions( """ sql_query = """ WITH cte AS ( - SELECT + SELECT CASE WHEN api_base = '' THEN litellm_model_name ELSE CONCAT(litellm_model_name, '-', api_base) END AS combined_model_api_base, exception_type, COUNT(*) AS num_rate_limit_exceptions FROM "LiteLLM_ErrorLogs" - WHERE - "startTime" >= $1::timestamp - AND "endTime" <= $2::timestamp + WHERE + "startTime" >= $1::timestamp + AND "endTime" <= $2::timestamp AND model_group = $3 GROUP BY combined_model_api_base, exception_type ) - SELECT + SELECT combined_model_api_base, COUNT(*) AS total_exceptions, json_object_agg(exception_type, num_rate_limit_exceptions) AS exception_counts @@ -6711,10 +6711,13 @@ def _get_model_group_info( llm_router: Router, all_models_str: List[str], model_group: Optional[str] ) -> List[ModelGroupInfoProxy]: model_groups: List[ModelGroupInfoProxy] = [] - # ensure all_models_str is a set - all_models_str_set = set(all_models_str) - for model in all_models_str_set: + unique_models = [] + for model in all_models_str: + if model not in unique_models: + unique_models.append(model) + + for model in unique_models: if model_group is not None and model_group != model: continue @@ -7304,7 +7307,7 @@ async def login(request: Request): # noqa: PLR0915 get_disabled_non_admin_personal_key_creation() ) """ - To login to Admin UI, we support the following + To login to Admin UI, we support the following - Login with UI_USERNAME and UI_PASSWORD - Login with Invite Link `user_email` and `password` combination """ @@ -8131,8 +8134,8 @@ async def update_config_general_settings( """ - Check if prisma_client is None - Check if user allowed to call this endpoint (admin-only) - - Check if param in general settings - - Check if config value is valid type + - Check if param in general settings + - Check if config value is valid type """ if prisma_client is None: @@ -8208,7 +8211,7 @@ async def get_config_general_settings( """ - Check if prisma_client is None - Check if user allowed to call this endpoint (admin-only) - - Check if param in general settings + - Check if param in general settings """ if prisma_client is None: raise HTTPException( @@ -8272,7 +8275,7 @@ async def get_config_list( """ - Check if prisma_client is None - Check if user allowed to call this endpoint (admin-only) - - Check if param in general settings + - Check if param in general settings """ if prisma_client is None: raise HTTPException( @@ -8408,7 +8411,7 @@ async def delete_config_general_settings( """ - Check if prisma_client is None - Check if user allowed to call this endpoint (admin-only) - - Check if param in general settings + - Check if param in general settings """ if prisma_client is None: raise HTTPException( @@ -8592,7 +8595,7 @@ async def get_config(): # noqa: PLR0915 }, } ] - + """ for _callback in _success_callbacks: if _callback != "langfuse": diff --git a/tests/test_litellm/proxy/auth/test_model_checks.py b/tests/test_litellm/proxy/auth/test_model_checks.py index ccd85a7148f..789af480e72 100644 --- a/tests/test_litellm/proxy/auth/test_model_checks.py +++ b/tests/test_litellm/proxy/auth/test_model_checks.py @@ -19,3 +19,46 @@ def test_get_team_models_for_all_models_and_team_only_models(): ) combined_models = team_models + proxy_model_list assert set(result) == set(combined_models) + + +@pytest.mark.parametrize( + "key_models,team_models,proxy_model_list,model_list,expected", + [ + ( + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"], + [], + [], + [{"model_name": "anthropic/*", "litellm_params": {"model": "anthropic/*"}}], + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"] + ), + ( + [], + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"], + [], + [{"model_name": "anthropic/*", "litellm_params": {"model": "anthropic/*"}}], + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"] + ), + ( + [], + [], + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"], + [{"model_name": "anthropic/*", "litellm_params": {"model": "anthropic/*"}}], + ["anthropic/claude-3-haiku-20240307", "anthropic/claude-3-5-haiku-20241022"] + ), + ], +) +def test_get_complete_model_list_order(key_models, team_models, proxy_model_list, model_list, expected): + """ + Test that get_complete_model_list preserves order + """ + from litellm.proxy.auth.model_checks import get_complete_model_list + from litellm import Router + + assert get_complete_model_list( + proxy_model_list=proxy_model_list, + key_models=key_models, + team_models=team_models, + user_model=None, + infer_model_from_keys=False, + llm_router=Router(model_list=model_list), + ) == expected diff --git a/tests/test_litellm/proxy/test_proxy_utils.py b/tests/test_litellm/proxy/test_proxy_utils.py index eb29ed51a97..6997ac65275 100644 --- a/tests/test_litellm/proxy/test_proxy_utils.py +++ b/tests/test_litellm/proxy/test_proxy_utils.py @@ -57,3 +57,35 @@ def test_proxy_only_error_false_for_other_error_type(): ) is False ) + + +def test_get_model_group_info_order(): + from litellm.proxy.proxy_server import _get_model_group_info + from litellm import Router + + router = Router( + model_list=[ + { + "model_name": "openai/tts-1", + "litellm_params": { + "model": "openai/tts-1", + "api_key": "sk-1234", + }, + }, + { + "model_name": "openai/gpt-3.5-turbo", + "litellm_params": { + "model": "openai/gpt-3.5-turbo", + "api_key": "sk-1234", + }, + }, + ] + ) + model_list = _get_model_group_info( + llm_router=router, + all_models_str=["openai/tts-1", "openai/gpt-3.5-turbo"], + model_group=None, + ) + + model_groups = [m.model_group for m in model_list] + assert model_groups == ["openai/tts-1", "openai/gpt-3.5-turbo"]