[Docs] Tutorial using MSFT auto team assignment with LiteLLM (#9898)
* add default_team_params as a config.yaml setting * create_litellm_team_from_sso_group * test_default_team_params * test_create_team_without_default_params * docs default team settings * docs msft entra id tutorial * commit litellm docs msft group assignment * litellm MSFT sso * member, team assignment on litellm * docs msft auto assignment * bug fix default team setting * docs litellm default team settings * test_default_team_params
142
docs/my-website/docs/tutorials/msft_sso.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
import Image from '@theme/IdealImage';
|
||||
|
||||
# Microsoft SSO: Sync Groups, Members with LiteLLM
|
||||
|
||||
Sync Microsoft SSO Groups, Members with LiteLLM Teams.
|
||||
|
||||
<Image img={require('../../img/litellm_entra_id.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## Overview of this tutorial
|
||||
|
||||
1. Auto-Create Entra ID Groups on LiteLLM Teams
|
||||
2. Sync Entra ID Team Memberships
|
||||
3. Set default params for new teams and users auto-created on LiteLLM
|
||||
|
||||
## 1. Auto-Create Entra ID Groups on LiteLLM Teams
|
||||
|
||||
In this step, our goal is to have LiteLLM automatically create a new team on the LiteLLM DB when there is a new Group Added to the LiteLLM Enterprise App on Azure Entra ID.
|
||||
|
||||
### 1.1 Create a new group in Entra ID
|
||||
|
||||
|
||||
Navigate to [your Azure Portal](https://portal.azure.com/) > Groups > New Group. Create a new group.
|
||||
|
||||
<Image img={require('../../img/entra_create_team.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
### 1.2 Assign the group to your LiteLLM Enterprise App
|
||||
|
||||
On your Azure Portal, navigate to `Enterprise Applications` > Select your litellm app
|
||||
|
||||
<Image img={require('../../img/msft_enterprise_app.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Once you've selected your litellm app, click on `Users and Groups` > `Add user/group`
|
||||
|
||||
<Image img={require('../../img/msft_enterprise_assign_group.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
<br />
|
||||
|
||||
Now select the group you created in step 1.1. And add it to the LiteLLM Enterprise App. At this point we have added `Production LLM Evals Group` to the LiteLLM Enterprise App. The next steps is having LiteLLM automatically create the `Production LLM Evals Group` on the LiteLLM DB when a new user signs in.
|
||||
|
||||
<Image img={require('../../img/msft_enterprise_select_group.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
|
||||
### 1.3 Sign in to LiteLLM UI via SSO
|
||||
|
||||
Sign into the LiteLLM UI via SSO. You should be redirected to the Entra ID SSO page. This SSO sign in flow will trigger LiteLLM to fetch the latest Groups and Members from Azure Entra ID.
|
||||
|
||||
<Image img={require('../../img/msft_sso_sign_in.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
### 1.4 Check the new team on LiteLLM UI
|
||||
|
||||
On the LiteLLM UI, Navigate to `Teams`, You should see the new team `Production LLM Evals Group` auto-created on LiteLLM.
|
||||
|
||||
<Image img={require('../../img/msft_auto_team.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
#### How this works
|
||||
|
||||
When a SSO user signs in to LiteLLM, LiteLLM automatically fetches the Groups under the LiteLLM Enterprise App, and found that the `Production LLM Evals Group` was assigned to the LiteLLM Enterprise App. Since the id associated with `Production LLM Evals Group` did not exist in the LiteLLM Teams Table, LiteLLM automatically created a new team with the name `Production LLM Evals Group` and id == id of `Production LLM Evals Group` in Entra ID.
|
||||
|
||||
|
||||
## 2. Sync Entra ID Team Memberships
|
||||
|
||||
In this step, we will have LiteLLM automatically add a user to the `Production LLM Evals` Team on the LiteLLM DB when a new user is added to the `Production LLM Evals` Group in Entra ID.
|
||||
|
||||
### 2.1 Navigate to the `Production LLM Evals` Group in Entra ID
|
||||
|
||||
Navigate to the `Production LLM Evals` Group in Entra ID.
|
||||
|
||||
<Image img={require('../../img/msft_member_1.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
|
||||
### 2.2 Add a member to the group in Entra ID
|
||||
|
||||
Select `Members` > `Add members`
|
||||
|
||||
In this stage you should add the user you want to add to the `Production LLM Evals` Team.
|
||||
|
||||
<Image img={require('../../img/msft_member_2.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
|
||||
|
||||
### 2.3 Sign in as the new user on LiteLLM UI
|
||||
|
||||
Sign in as the new user on LiteLLM UI. You should be redirected to the Entra ID SSO page. This SSO sign in flow will trigger LiteLLM to fetch the latest Groups and Members from Azure Entra ID. During this step LiteLLM sync it's teams, team members with what is available from Entra ID
|
||||
|
||||
<Image img={require('../../img/msft_sso_sign_in.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
|
||||
|
||||
### 2.4 Check the team membership on LiteLLM UI
|
||||
|
||||
On the LiteLLM UI, Navigate to `Teams`, You should see the new team `Production LLM Evals Group`. Since your are now a member of the `Production LLM Evals Group` in Entra ID, you should see the new team `Production LLM Evals Group` on the LiteLLM UI.
|
||||
|
||||
<Image img={require('../../img/msft_member_3.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
## 3. Set default params for new teams auto-created on LiteLLM
|
||||
|
||||
Since litellm auto creates a new team on the LiteLLM DB when there is a new Group Added to the LiteLLM Enterprise App on Azure Entra ID, we can set default params for new teams created.
|
||||
|
||||
This allows you to set a default budget, models, etc for new teams created.
|
||||
|
||||
### 3.1 Set `default_team_params` on litellm
|
||||
|
||||
Navigate to your litellm config file and set the following params
|
||||
|
||||
```yaml showLineNumbers title="litellm config with default_team_params"
|
||||
litellm_settings:
|
||||
default_team_params: # Default Params to apply when litellm auto creates a team from SSO IDP provider
|
||||
max_budget: 100 # Optional[float], optional): $100 budget for the team
|
||||
budget_duration: 30d # Optional[str], optional): 30 days budget_duration for the team
|
||||
models: ["gpt-3.5-turbo"] # Optional[List[str]], optional): models to be used by the team
|
||||
```
|
||||
|
||||
### 3.2 Auto-create a new team on LiteLLM
|
||||
|
||||
- In this step you should add a new group to the LiteLLM Enterprise App on Azure Entra ID (like we did in step 1.1). We will call this group `Default LiteLLM Prod Team` on Azure Entra ID.
|
||||
- Start litellm proxy server with your config
|
||||
- Sign into LiteLLM UI via SSO
|
||||
- Navigate to `Teams` and you should see the new team `Default LiteLLM Prod Team` auto-created on LiteLLM
|
||||
- Note LiteLLM will set the default params for this new team.
|
||||
|
||||
<Image img={require('../../img/msft_default_settings.png')} style={{ width: '800px', height: 'auto' }} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
docs/my-website/img/entra_create_team.png
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
docs/my-website/img/litellm_entra_id.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
docs/my-website/img/msft_auto_team.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
docs/my-website/img/msft_default_settings.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
docs/my-website/img/msft_enterprise_app.png
Normal file
After Width: | Height: | Size: 292 KiB |
BIN
docs/my-website/img/msft_enterprise_assign_group.png
Normal file
After Width: | Height: | Size: 277 KiB |
BIN
docs/my-website/img/msft_enterprise_select_group.png
Normal file
After Width: | Height: | Size: 245 KiB |
BIN
docs/my-website/img/msft_member_1.png
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
docs/my-website/img/msft_member_2.png
Normal file
After Width: | Height: | Size: 274 KiB |
BIN
docs/my-website/img/msft_member_3.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
docs/my-website/img/msft_sso_sign_in.png
Normal file
After Width: | Height: | Size: 818 KiB |
|
@ -435,6 +435,7 @@ const sidebars = {
|
|||
label: "Tutorials",
|
||||
items: [
|
||||
"tutorials/openweb_ui",
|
||||
"tutorials/msft_sso",
|
||||
'tutorials/litellm_proxy_aporia',
|
||||
{
|
||||
type: "category",
|
||||
|
|
|
@ -277,7 +277,7 @@ default_key_generate_params: Optional[Dict] = None
|
|||
upperbound_key_generate_params: Optional[LiteLLM_UpperboundKeyGenerateParams] = None
|
||||
key_generation_settings: Optional[StandardKeyGenerationConfig] = None
|
||||
default_internal_user_params: Optional[Dict] = None
|
||||
default_team_params: Optional[NewTeamRequest] = None
|
||||
default_team_params: Optional[Union[NewTeamRequest, Dict]] = None
|
||||
default_team_settings: Optional[List] = None
|
||||
max_user_budget: Optional[float] = None
|
||||
default_max_internal_user_budget: Optional[float] = None
|
||||
|
|
|
@ -11,6 +11,7 @@ Has all /sso/* routes
|
|||
import asyncio
|
||||
import os
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
|
@ -919,13 +920,13 @@ class SSOAuthenticationHandler:
|
|||
team_alias=litellm_team_name,
|
||||
)
|
||||
if litellm.default_team_params:
|
||||
team_request = litellm.default_team_params.model_copy(
|
||||
deep=True,
|
||||
update={
|
||||
"team_id": litellm_team_id,
|
||||
"team_alias": litellm_team_name,
|
||||
},
|
||||
team_request = SSOAuthenticationHandler._cast_and_deepcopy_litellm_default_team_params(
|
||||
default_team_params=litellm.default_team_params,
|
||||
litellm_team_id=litellm_team_id,
|
||||
litellm_team_name=litellm_team_name,
|
||||
team_request=team_request,
|
||||
)
|
||||
|
||||
await new_team(
|
||||
data=team_request,
|
||||
# params used for Audit Logging
|
||||
|
@ -938,6 +939,35 @@ class SSOAuthenticationHandler:
|
|||
except Exception as e:
|
||||
verbose_proxy_logger.exception(f"Error creating Litellm Team: {e}")
|
||||
|
||||
@staticmethod
|
||||
def _cast_and_deepcopy_litellm_default_team_params(
|
||||
default_team_params: Union[NewTeamRequest, Dict],
|
||||
team_request: NewTeamRequest,
|
||||
litellm_team_id: str,
|
||||
litellm_team_name: Optional[str] = None,
|
||||
) -> NewTeamRequest:
|
||||
"""
|
||||
Casts and deepcopies the litellm.default_team_params to a NewTeamRequest object
|
||||
|
||||
- Ensures we create a new NewTeamRequest object
|
||||
- Handle the case where litellm.default_team_params is a dict or a NewTeamRequest object
|
||||
- Adds the litellm_team_id and litellm_team_name to the NewTeamRequest object
|
||||
"""
|
||||
if isinstance(default_team_params, dict):
|
||||
_team_request = deepcopy(default_team_params)
|
||||
_team_request["team_id"] = litellm_team_id
|
||||
_team_request["team_alias"] = litellm_team_name
|
||||
team_request = NewTeamRequest(**_team_request)
|
||||
elif isinstance(litellm.default_team_params, NewTeamRequest):
|
||||
team_request = litellm.default_team_params.model_copy(
|
||||
deep=True,
|
||||
update={
|
||||
"team_id": litellm_team_id,
|
||||
"team_alias": litellm_team_name,
|
||||
},
|
||||
)
|
||||
return team_request
|
||||
|
||||
|
||||
class MicrosoftSSOHandler:
|
||||
"""
|
||||
|
|
|
@ -418,62 +418,21 @@ def test_get_group_ids_from_graph_api_response():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upsert_sso_user_existing_user():
|
||||
"""
|
||||
If a user_id is already in the LiteLLM DB and the user signed in with SSO. Ensure that the user_id is updated with the SSO user_email
|
||||
|
||||
SSO Test
|
||||
"""
|
||||
# Arrange
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db = MagicMock()
|
||||
mock_prisma.db.litellm_usertable = MagicMock()
|
||||
mock_prisma.db.litellm_usertable.update_many = AsyncMock()
|
||||
|
||||
# Create a mock existing user
|
||||
mock_user = MagicMock()
|
||||
mock_user.user_id = "existing_user_123"
|
||||
mock_user.user_email = "old_email@example.com"
|
||||
|
||||
# Create mock SSO response
|
||||
mock_sso_response = CustomOpenID(
|
||||
email="new_email@example.com",
|
||||
display_name="Test User",
|
||||
provider="microsoft",
|
||||
id="existing_user_123",
|
||||
first_name="Test",
|
||||
last_name="User",
|
||||
team_ids=[],
|
||||
)
|
||||
|
||||
# Create mock user defined values
|
||||
mock_user_defined_values = MagicMock()
|
||||
|
||||
# Act
|
||||
result = await SSOAuthenticationHandler.upsert_sso_user(
|
||||
result=mock_sso_response,
|
||||
user_info=mock_user,
|
||||
user_email="new_email@example.com",
|
||||
user_defined_values=mock_user_defined_values,
|
||||
prisma_client=mock_prisma,
|
||||
)
|
||||
|
||||
# Assert
|
||||
mock_prisma.db.litellm_usertable.update_many.assert_called_once_with(
|
||||
where={"user_id": "existing_user_123"},
|
||||
data={"user_email": "new_email@example.com"},
|
||||
)
|
||||
assert result == mock_user
|
||||
|
||||
|
||||
async def test_default_team_params():
|
||||
@pytest.mark.parametrize(
|
||||
"team_params",
|
||||
[
|
||||
# Test case 1: Using NewTeamRequest
|
||||
NewTeamRequest(max_budget=10, budget_duration="1d", models=["special-gpt-5"]),
|
||||
# Test case 2: Using Dict
|
||||
{"max_budget": 10, "budget_duration": "1d", "models": ["special-gpt-5"]},
|
||||
],
|
||||
)
|
||||
async def test_default_team_params(team_params):
|
||||
"""
|
||||
When litellm.default_team_params is set, it should be used to create a new team
|
||||
"""
|
||||
# Arrange
|
||||
litellm.default_team_params = NewTeamRequest(
|
||||
max_budget=10, budget_duration="1d", models=["special-gpt-5"]
|
||||
)
|
||||
litellm.default_team_params = team_params
|
||||
|
||||
def mock_jsonify_team_object(db_data):
|
||||
return db_data
|
||||
|
|