diff --git a/litellm/litellm_core_utils/json_validation_rule.py b/litellm/litellm_core_utils/json_validation_rule.py index 12f952d1d69..36ab0a188b8 100644 --- a/litellm/litellm_core_utils/json_validation_rule.py +++ b/litellm/litellm_core_utils/json_validation_rule.py @@ -52,6 +52,14 @@ def normalize_json_schema_types( for key, value in schema.items(): if key == "type" and isinstance(value, str) and value in type_mapping: normalized_schema[key] = type_mapping[value] + elif key == "type" and isinstance(value, list): + # JSON Schema also allows a list of types, which is the usual way + # to mark a field nullable (e.g. ["STRING", "NULL"]). Without this + # branch those entries fall through to the generic list recursion, + # which leaves the bare strings uppercase. + normalized_schema[key] = [ + type_mapping.get(entry, entry) for entry in value + ] elif key == "properties" and isinstance(value, dict): # Recursively normalize properties normalized_schema[key] = { diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 9a98bdbb6b1..eb49afe63f9 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -2056,8 +2056,8 @@ async def get_users( - internal_user_viewer user_ids: Optional[str] Get list of users by user_ids. Comma separated list of user_ids. - sso_ids: Optional[str] - Get list of users by sso_ids. Comma separated list of sso_ids. + sso_user_ids: Optional[str] + Get list of users by sso_user_ids. Comma separated list of sso_user_ids. user_email: Optional[str] Filter users by partial email match team: Optional[str] diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 97999cbb6d7..d94a8a6cb07 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -4179,7 +4179,9 @@ async def delete_verification_tokens( Args: tokens: List of tokens to delete - user_id: Optional user_id to filter by + user_api_key_cache: In-memory key cache to invalidate deleted tokens from + user_api_key_dict: User authentication information + litellm_changed_by: Optional username of the admin performing the change, for audit logs Returns: Tuple[Optional[Dict], List[LiteLLM_VerificationToken]]: diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index c6d7975b75e..22b4555e49e 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -5416,14 +5416,14 @@ async def ui_view_teams( [PROXY-ADMIN ONLY] Filter teams based on partial match of team_id or team_alias with pagination. Args: - user_id (Optional[str]): Partial user ID to search for - user_email (Optional[str]): Partial email to search for + team_id (Optional[str]): Partial team ID to search for + team_alias (Optional[str]): Partial team alias to search for page (int): Page number for pagination (starts at 1) page_size (int): Number of items per page (max 100) user_api_key_dict (UserAPIKeyAuth): User authentication information Returns: - List[LiteLLM_SpendLogs]: Paginated list of matching user records + List[LiteLLM_TeamTable]: Paginated list of matching team records """ from litellm.proxy.proxy_server import prisma_client diff --git a/tests/test_litellm/litellm_core_utils/test_json_validation_rule.py b/tests/test_litellm/litellm_core_utils/test_json_validation_rule.py new file mode 100644 index 00000000000..5ff6bee986e --- /dev/null +++ b/tests/test_litellm/litellm_core_utils/test_json_validation_rule.py @@ -0,0 +1,67 @@ +from litellm.litellm_core_utils.json_validation_rule import ( + normalize_json_schema_types, + normalize_tool_schema, +) + + +def test_normalizes_a_plain_string_type(): + assert normalize_json_schema_types({"type": "STRING"}) == {"type": "string"} + + +def test_normalizes_a_list_of_types(): + """A list of types is how a nullable field is expressed. + + Regression: these entries fell through to the generic list recursion, which + returns bare strings untouched, so they stayed uppercase. + """ + assert normalize_json_schema_types({"type": ["STRING", "NULL"]}) == { + "type": ["string", "null"] + } + + +def test_normalizes_a_list_of_types_when_nested(): + schema = {"properties": {"a": {"type": ["INTEGER", "NULL"]}}} + + assert normalize_json_schema_types(schema) == { + "properties": {"a": {"type": ["integer", "null"]}} + } + + +def test_leaves_unknown_type_entries_alone(): + assert normalize_json_schema_types({"type": ["STRING", "custom"]}) == { + "type": ["string", "custom"] + } + + +def test_still_normalizes_properties_items_and_anyof(): + schema = { + "type": "OBJECT", + "properties": {"xs": {"type": "ARRAY", "items": {"type": "INTEGER"}}}, + "anyOf": [{"type": "STRING"}], + } + + assert normalize_json_schema_types(schema) == { + "type": "object", + "properties": {"xs": {"type": "array", "items": {"type": "integer"}}}, + "anyOf": [{"type": "string"}], + } + + +def test_tool_schema_normalizes_a_nullable_parameter(): + tool = { + "function": { + "parameters": { + "type": "OBJECT", + "properties": {"x": {"type": ["STRING", "NULL"]}}, + } + } + } + + assert normalize_tool_schema(tool) == { + "function": { + "parameters": { + "type": "object", + "properties": {"x": {"type": ["string", "null"]}}, + } + } + } diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 405ec9a01bf..8cb22adecf1 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -14659,14 +14659,14 @@ export interface paths { * @description [PROXY-ADMIN ONLY] Filter teams based on partial match of team_id or team_alias with pagination. * * Args: - * user_id (Optional[str]): Partial user ID to search for - * user_email (Optional[str]): Partial email to search for + * team_id (Optional[str]): Partial team ID to search for + * team_alias (Optional[str]): Partial team alias to search for * page (int): Page number for pagination (starts at 1) * page_size (int): Number of items per page (max 100) * user_api_key_dict (UserAPIKeyAuth): User authentication information * * Returns: - * List[LiteLLM_SpendLogs]: Paginated list of matching user records + * List[LiteLLM_TeamTable]: Paginated list of matching team records */ get: operations["ui_view_teams_team_filter_ui_get"]; put?: never; @@ -16154,8 +16154,8 @@ export interface paths { * - internal_user_viewer * user_ids: Optional[str] * Get list of users by user_ids. Comma separated list of user_ids. - * sso_ids: Optional[str] - * Get list of users by sso_ids. Comma separated list of sso_ids. + * sso_user_ids: Optional[str] + * Get list of users by sso_user_ids. Comma separated list of sso_user_ids. * user_email: Optional[str] * Filter users by partial email match * team: Optional[str]