diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index d8a1075a16..721a8c2a1c 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -521,6 +521,16 @@ async def _update_existing_team_model_assignment( and _get_team_public_model_name(d.model_info) == old_public_name ] + # Add new name first, then delete old name to prevent access loss on partial failure + await team_model_add( + data=TeamModelAddRequest( + team_id=team_id, + models=[public_model_name], + ), + http_request=Request(scope={"type": "http"}), + user_api_key_dict=user_api_key_dict, + ) + if not other_deployments_with_old_name: await team_model_delete( data=TeamModelDeleteRequest( @@ -531,15 +541,6 @@ async def _update_existing_team_model_assignment( user_api_key_dict=user_api_key_dict, ) - await team_model_add( - data=TeamModelAddRequest( - team_id=team_id, - models=[public_model_name], - ), - http_request=Request(scope={"type": "http"}), - user_api_key_dict=user_api_key_dict, - ) - patch_data.model_name = None diff --git a/litellm/router.py b/litellm/router.py index 76c13443e7..d7f5d42eac 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -8233,9 +8233,11 @@ class Router: ): return True elif model_name is not None and model["model_name"] == model_name: + model_team_id = (model.get("model_info") or {}).get("team_id") if ( team_id is None - or (model.get("model_info") or {}).get("team_id") == team_id + or model_team_id is None # global deployment - accessible to all teams + or model_team_id == team_id ): return True return False diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index 83e6b0c93a..2dd29fd5c9 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -712,6 +712,15 @@ class TestTeamModelSiblingRouting: "team_public_model_name": public_name, }, }, + { + "model_name": "global-gpt-4o", + "litellm_params": { + "model": "azure/gpt-4o", + "api_key": "global-key", + "api_base": "https://global.openai.azure.com", + }, + "model_info": {}, # No team_id - global deployment + }, ], ) @@ -732,6 +741,38 @@ class TestTeamModelSiblingRouting: "https://westus.openai.azure.com", } + def test_global_deployments_accessible_to_teams(self): + """Test that global deployments (no team_id) are accessible to all teams""" + import litellm + + router = litellm.Router( + model_list=[ + { + "model_name": "global-gpt-4o", + "litellm_params": { + "model": "azure/gpt-4o", + "api_key": "global-key", + "api_base": "https://global.openai.azure.com", + }, + "model_info": {}, # No team_id - global deployment + }, + ], + ) + + # Global deployment should be accessible when team_id is provided + deployments = router._get_all_deployments( + model_name="global-gpt-4o", team_id="teamA" + ) + assert len(deployments) == 1 + assert deployments[0]["model_name"] == "global-gpt-4o" + + # should_include_deployment should return True for global deployments + assert router.should_include_deployment( + model_name="global-gpt-4o", + model={"model_name": "global-gpt-4o", "model_info": {}}, + team_id="teamA", + ) + class TestTeamModelUpdate: """Test team model update handles team_id consistently with model creation"""