From 8673f2541e88331ff576df36b21cb55ceecd0330 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 26 Nov 2024 14:19:24 +0530 Subject: [PATCH 01/25] fix(key_management_endpoints.py): fix user-membership check when creating team key (#6890) * fix(key_management_endpoints.py): fix user-membership check when creating team key * docs: add deprecation notice on original `/v1/messages` endpoint + add better swagger tags on pass-through endpoints * fix(gemini/): fix image_url handling for gemini Fixes https://github.com/BerriAI/litellm/issues/6897 * fix(teams.tsx): fix member add when role is 'user' * fix(team_endpoints.py): /team/member_add fix adding several new members to team * test(test_vertex.py): remove redundant test * test(test_proxy_server.py): fix team member add tests --- litellm/llms/prompt_templates/factory.py | 23 +++ .../gemini/transformation.py | 7 +- .../vertex_and_google_ai_studio_gemini.py | 37 +++++ litellm/proxy/_new_secret_config.yaml | 20 +++ litellm/proxy/_types.py | 1 - litellm/proxy/auth/auth_checks.py | 153 ++++++++++++------ .../key_management_endpoints.py | 61 +++++-- .../management_endpoints/team_endpoints.py | 1 + .../llm_passthrough_endpoints.py | 45 +++++- litellm/proxy/proxy_server.py | 6 +- .../vertex_ai_endpoints/langfuse_endpoints.py | 11 +- .../vertex_ai_endpoints/vertex_endpoints.py | 8 +- tests/llm_translation/base_llm_unit_tests.py | 29 ++++ tests/llm_translation/test_gemini.py | 15 ++ tests/llm_translation/test_prompt_factory.py | 13 ++ tests/llm_translation/test_vertex.py | 91 ----------- .../test_key_management.py | 29 +++- tests/proxy_unit_tests/test_proxy_server.py | 11 +- ui/litellm-dashboard/src/components/teams.tsx | 7 +- 19 files changed, 399 insertions(+), 169 deletions(-) create mode 100644 tests/llm_translation/test_gemini.py diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 45b7a6c5b..cb79a81b7 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -33,6 +33,7 @@ from litellm.types.llms.openai import ( ChatCompletionAssistantToolCall, ChatCompletionFunctionMessage, ChatCompletionImageObject, + ChatCompletionImageUrlObject, ChatCompletionTextObject, ChatCompletionToolCallFunctionChunk, ChatCompletionToolMessage, @@ -681,6 +682,27 @@ def construct_tool_use_system_prompt( return tool_use_system_prompt +def convert_generic_image_chunk_to_openai_image_obj( + image_chunk: GenericImageParsingChunk, +) -> str: + """ + Convert a generic image chunk to an OpenAI image object. + + Input: + GenericImageParsingChunk( + type="base64", + media_type="image/jpeg", + data="...", + ) + + Return: + "data:image/jpeg;base64,{base64_image}" + """ + return "data:{};{},{}".format( + image_chunk["media_type"], image_chunk["type"], image_chunk["data"] + ) + + def convert_to_anthropic_image_obj(openai_image_url: str) -> GenericImageParsingChunk: """ Input: @@ -706,6 +728,7 @@ def convert_to_anthropic_image_obj(openai_image_url: str) -> GenericImageParsing data=base64_data, ) except Exception as e: + traceback.print_exc() if "Error: Unable to fetch image from URL" in str(e): raise e raise Exception( diff --git a/litellm/llms/vertex_ai_and_google_ai_studio/gemini/transformation.py b/litellm/llms/vertex_ai_and_google_ai_studio/gemini/transformation.py index f828d93c8..4b5b7281b 100644 --- a/litellm/llms/vertex_ai_and_google_ai_studio/gemini/transformation.py +++ b/litellm/llms/vertex_ai_and_google_ai_studio/gemini/transformation.py @@ -294,7 +294,12 @@ def _transform_request_body( optional_params = {k: v for k, v in optional_params.items() if k not in remove_keys} try: - content = _gemini_convert_messages_with_history(messages=messages) + if custom_llm_provider == "gemini": + content = litellm.GoogleAIStudioGeminiConfig._transform_messages( + messages=messages + ) + else: + content = litellm.VertexGeminiConfig._transform_messages(messages=messages) tools: Optional[Tools] = optional_params.pop("tools", None) tool_choice: Optional[ToolConfig] = optional_params.pop("tool_choice", None) safety_settings: Optional[List[SafetSettingsConfig]] = optional_params.pop( diff --git a/litellm/llms/vertex_ai_and_google_ai_studio/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai_and_google_ai_studio/gemini/vertex_and_google_ai_studio_gemini.py index f2fc599ed..4287ed1bc 100644 --- a/litellm/llms/vertex_ai_and_google_ai_studio/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai_and_google_ai_studio/gemini/vertex_and_google_ai_studio_gemini.py @@ -35,7 +35,12 @@ from litellm.llms.custom_httpx.http_handler import ( HTTPHandler, get_async_httpx_client, ) +from litellm.llms.prompt_templates.factory import ( + convert_generic_image_chunk_to_openai_image_obj, + convert_to_anthropic_image_obj, +) from litellm.types.llms.openai import ( + AllMessageValues, ChatCompletionResponseMessage, ChatCompletionToolCallChunk, ChatCompletionToolCallFunctionChunk, @@ -78,6 +83,8 @@ from ..common_utils import ( ) from ..vertex_llm_base import VertexBase from .transformation import ( + _gemini_convert_messages_with_history, + _process_gemini_image, async_transform_request_body, set_headers, sync_transform_request_body, @@ -912,6 +919,10 @@ class VertexGeminiConfig: return model_response + @staticmethod + def _transform_messages(messages: List[AllMessageValues]) -> List[ContentType]: + return _gemini_convert_messages_with_history(messages=messages) + class GoogleAIStudioGeminiConfig( VertexGeminiConfig @@ -1015,6 +1026,32 @@ class GoogleAIStudioGeminiConfig( model, non_default_params, optional_params, drop_params ) + @staticmethod + def _transform_messages(messages: List[AllMessageValues]) -> List[ContentType]: + """ + Google AI Studio Gemini does not support image urls in messages. + """ + for message in messages: + _message_content = message.get("content") + if _message_content is not None and isinstance(_message_content, list): + _parts: List[PartType] = [] + for element in _message_content: + if element.get("type") == "image_url": + img_element = element + _image_url: Optional[str] = None + if isinstance(img_element.get("image_url"), dict): + _image_url = img_element["image_url"].get("url") # type: ignore + else: + _image_url = img_element.get("image_url") # type: ignore + if _image_url and "https://" in _image_url: + image_obj = convert_to_anthropic_image_obj(_image_url) + img_element["image_url"] = ( # type: ignore + convert_generic_image_chunk_to_openai_image_obj( + image_obj + ) + ) + return _gemini_convert_messages_with_history(messages=messages) + async def make_call( client: Optional[AsyncHTTPHandler], diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 7ff209094..86ece3788 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -12,3 +12,23 @@ model_list: vertex_ai_project: "adroit-crow-413218" vertex_ai_location: "us-east5" +router_settings: + routing_strategy: usage-based-routing-v2 + #redis_url: "os.environ/REDIS_URL" + redis_host: "os.environ/REDIS_HOST" + redis_port: "os.environ/REDIS_PORT" + +litellm_settings: + cache: true + cache_params: + type: redis + host: "os.environ/REDIS_HOST" + port: "os.environ/REDIS_PORT" + namespace: "litellm.caching" + ttl: 600 +# key_generation_settings: +# team_key_generation: +# allowed_team_member_roles: ["admin"] +# required_params: ["tags"] # require team admins to set tags for cost-tracking when generating a team key +# personal_key_generation: # maps to 'Default Team' on UI +# allowed_user_roles: ["proxy_admin"] \ No newline at end of file diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 72d6c84c9..965b72642 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1982,7 +1982,6 @@ class MemberAddRequest(LiteLLMBase): # Replace member_data with the single Member object data["member"] = member # Call the superclass __init__ method to initialize the object - traceback.print_stack() super().__init__(**data) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 7d29032c6..5d789436a 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -523,6 +523,10 @@ async def _cache_management_object( proxy_logging_obj: Optional[ProxyLogging], ): await user_api_key_cache.async_set_cache(key=key, value=value) + if proxy_logging_obj is not None: + await proxy_logging_obj.internal_usage_cache.dual_cache.async_set_cache( + key=key, value=value + ) async def _cache_team_object( @@ -586,26 +590,63 @@ async def _get_team_db_check(team_id: str, prisma_client: PrismaClient): ) -async def get_team_object( - team_id: str, - prisma_client: Optional[PrismaClient], - user_api_key_cache: DualCache, - parent_otel_span: Optional[Span] = None, - proxy_logging_obj: Optional[ProxyLogging] = None, - check_cache_only: Optional[bool] = None, -) -> LiteLLM_TeamTableCachedObj: - """ - - Check if team id in proxy Team Table - - if valid, return LiteLLM_TeamTable object with defined limits - - if not, then raise an error - """ - if prisma_client is None: - raise Exception( - "No DB Connected. See - https://docs.litellm.ai/docs/proxy/virtual_keys" - ) +async def _get_team_object_from_db(team_id: str, prisma_client: PrismaClient): + return await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": team_id} + ) - # check if in cache - key = "team_id:{}".format(team_id) + +async def _get_team_object_from_user_api_key_cache( + team_id: str, + prisma_client: PrismaClient, + user_api_key_cache: DualCache, + last_db_access_time: LimitedSizeOrderedDict, + db_cache_expiry: int, + proxy_logging_obj: Optional[ProxyLogging], + key: str, +) -> LiteLLM_TeamTableCachedObj: + db_access_time_key = key + should_check_db = _should_check_db( + key=db_access_time_key, + last_db_access_time=last_db_access_time, + db_cache_expiry=db_cache_expiry, + ) + if should_check_db: + response = await _get_team_db_check( + team_id=team_id, prisma_client=prisma_client + ) + else: + response = None + + if response is None: + raise Exception + + _response = LiteLLM_TeamTableCachedObj(**response.dict()) + # save the team object to cache + await _cache_team_object( + team_id=team_id, + team_table=_response, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + # save to db access time + # save to db access time + _update_last_db_access_time( + key=db_access_time_key, + value=_response, + last_db_access_time=last_db_access_time, + ) + + return _response + + +async def _get_team_object_from_cache( + key: str, + proxy_logging_obj: Optional[ProxyLogging], + user_api_key_cache: DualCache, + parent_otel_span: Optional[Span], +) -> Optional[LiteLLM_TeamTableCachedObj]: cached_team_obj: Optional[LiteLLM_TeamTableCachedObj] = None ## CHECK REDIS CACHE ## @@ -613,6 +654,7 @@ async def get_team_object( proxy_logging_obj is not None and proxy_logging_obj.internal_usage_cache.dual_cache ): + cached_team_obj = ( await proxy_logging_obj.internal_usage_cache.dual_cache.async_get_cache( key=key, parent_otel_span=parent_otel_span @@ -628,47 +670,58 @@ async def get_team_object( elif isinstance(cached_team_obj, LiteLLM_TeamTableCachedObj): return cached_team_obj - if check_cache_only: + return None + + +async def get_team_object( + team_id: str, + prisma_client: Optional[PrismaClient], + user_api_key_cache: DualCache, + parent_otel_span: Optional[Span] = None, + proxy_logging_obj: Optional[ProxyLogging] = None, + check_cache_only: Optional[bool] = None, + check_db_only: Optional[bool] = None, +) -> LiteLLM_TeamTableCachedObj: + """ + - Check if team id in proxy Team Table + - if valid, return LiteLLM_TeamTable object with defined limits + - if not, then raise an error + """ + if prisma_client is None: raise Exception( - f"Team doesn't exist in cache + check_cache_only=True. Team={team_id}." + "No DB Connected. See - https://docs.litellm.ai/docs/proxy/virtual_keys" ) + # check if in cache + key = "team_id:{}".format(team_id) + + if not check_db_only: + cached_team_obj = await _get_team_object_from_cache( + key=key, + proxy_logging_obj=proxy_logging_obj, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + ) + + if cached_team_obj is not None: + return cached_team_obj + + if check_cache_only: + raise Exception( + f"Team doesn't exist in cache + check_cache_only=True. Team={team_id}." + ) + # else, check db try: - db_access_time_key = "team_id:{}".format(team_id) - should_check_db = _should_check_db( - key=db_access_time_key, - last_db_access_time=last_db_access_time, - db_cache_expiry=db_cache_expiry, - ) - if should_check_db: - response = await _get_team_db_check( - team_id=team_id, prisma_client=prisma_client - ) - else: - response = None - - if response is None: - raise Exception - - _response = LiteLLM_TeamTableCachedObj(**response.dict()) - # save the team object to cache - await _cache_team_object( + return await _get_team_object_from_user_api_key_cache( team_id=team_id, - team_table=_response, + prisma_client=prisma_client, user_api_key_cache=user_api_key_cache, proxy_logging_obj=proxy_logging_obj, - ) - - # save to db access time - # save to db access time - _update_last_db_access_time( - key=db_access_time_key, - value=_response, last_db_access_time=last_db_access_time, + db_cache_expiry=db_cache_expiry, + key=key, ) - - return _response except Exception: raise Exception( f"Team doesn't exist in db. Team={team_id}. Create team via `/team/new` call." diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 8353b5d91..ba6df9a2e 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -29,6 +29,7 @@ from litellm.proxy.auth.auth_checks import ( _cache_key_object, _delete_cache_key_object, get_key_object, + get_team_object, ) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks @@ -46,7 +47,19 @@ def _is_team_key(data: GenerateKeyRequest): return data.team_id is not None +def _get_user_in_team( + team_table: LiteLLM_TeamTableCachedObj, user_id: Optional[str] +) -> Optional[Member]: + if user_id is None: + return None + for member in team_table.members_with_roles: + if member.user_id is not None and member.user_id == user_id: + return member + return None + + def _team_key_generation_team_member_check( + team_table: LiteLLM_TeamTableCachedObj, user_api_key_dict: UserAPIKeyAuth, team_key_generation: Optional[TeamUIKeyGenerationConfig], ): @@ -56,17 +69,19 @@ def _team_key_generation_team_member_check( ): return True - if user_api_key_dict.team_member is None: + user_in_team = _get_user_in_team( + team_table=team_table, user_id=user_api_key_dict.user_id + ) + if user_in_team is None: raise HTTPException( status_code=400, - detail=f"User not assigned to team. Got team_member={user_api_key_dict.team_member}", + detail=f"User={user_api_key_dict.user_id} not assigned to team={team_table.team_id}", ) - team_member_role = user_api_key_dict.team_member.role - if team_member_role not in team_key_generation["allowed_team_member_roles"]: + if user_in_team.role not in team_key_generation["allowed_team_member_roles"]: raise HTTPException( status_code=400, - detail=f"Team member role {team_member_role} not in allowed_team_member_roles={litellm.key_generation_settings['team_key_generation']['allowed_team_member_roles']}", # type: ignore + detail=f"Team member role {user_in_team.role} not in allowed_team_member_roles={team_key_generation['allowed_team_member_roles']}", ) return True @@ -88,7 +103,9 @@ def _key_generation_required_param_check( def _team_key_generation_check( - user_api_key_dict: UserAPIKeyAuth, data: GenerateKeyRequest + team_table: LiteLLM_TeamTableCachedObj, + user_api_key_dict: UserAPIKeyAuth, + data: GenerateKeyRequest, ): if ( litellm.key_generation_settings is None @@ -99,7 +116,8 @@ def _team_key_generation_check( _team_key_generation = litellm.key_generation_settings["team_key_generation"] # type: ignore _team_key_generation_team_member_check( - user_api_key_dict, + team_table=team_table, + user_api_key_dict=user_api_key_dict, team_key_generation=_team_key_generation, ) _key_generation_required_param_check( @@ -155,7 +173,9 @@ def _personal_key_generation_check( def key_generation_check( - user_api_key_dict: UserAPIKeyAuth, data: GenerateKeyRequest + team_table: Optional[LiteLLM_TeamTableCachedObj], + user_api_key_dict: UserAPIKeyAuth, + data: GenerateKeyRequest, ) -> bool: """ Check if admin has restricted key creation to certain roles for teams or individuals @@ -170,8 +190,15 @@ def key_generation_check( is_team_key = _is_team_key(data=data) if is_team_key: + if team_table is None: + raise HTTPException( + status_code=400, + detail=f"Unable to find team object in database. Team ID: {data.team_id}", + ) return _team_key_generation_check( - user_api_key_dict=user_api_key_dict, data=data + team_table=team_table, + user_api_key_dict=user_api_key_dict, + data=data, ) else: return _personal_key_generation_check( @@ -254,6 +281,7 @@ async def generate_key_fn( # noqa: PLR0915 litellm_proxy_admin_name, prisma_client, proxy_logging_obj, + user_api_key_cache, user_custom_key_generate, ) @@ -271,7 +299,20 @@ async def generate_key_fn( # noqa: PLR0915 status_code=status.HTTP_403_FORBIDDEN, detail=message ) elif litellm.key_generation_settings is not None: - key_generation_check(user_api_key_dict=user_api_key_dict, data=data) + if data.team_id is None: + team_table: Optional[LiteLLM_TeamTableCachedObj] = None + else: + team_table = await get_team_object( + team_id=data.team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=user_api_key_dict.parent_otel_span, + ) + key_generation_check( + team_table=team_table, + user_api_key_dict=user_api_key_dict, + data=data, + ) # check if user set default key/generate params on config.yaml if litellm.default_key_generate_params is not None: for elem in data: diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 575e71119..7b4f4a526 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -547,6 +547,7 @@ async def team_member_add( parent_otel_span=None, proxy_logging_obj=proxy_logging_obj, check_cache_only=False, + check_db_only=True, ) if existing_team_row is None: raise HTTPException( diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 274ffda5b..8eb8290f4 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -54,12 +54,19 @@ def create_request_copy(request: Request): } -@router.api_route("/gemini/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +@router.api_route( + "/gemini/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Google AI Studio Pass-through", "pass-through"], +) async def gemini_proxy_route( endpoint: str, request: Request, fastapi_response: Response, ): + """ + [Docs](https://docs.litellm.ai/docs/pass_through/google_ai_studio) + """ ## CHECK FOR LITELLM API KEY IN THE QUERY PARAMS - ?..key=LITELLM_API_KEY google_ai_studio_api_key = request.query_params.get("key") or request.headers.get( "x-goog-api-key" @@ -113,13 +120,20 @@ async def gemini_proxy_route( return received_value -@router.api_route("/cohere/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +@router.api_route( + "/cohere/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Cohere Pass-through", "pass-through"], +) async def cohere_proxy_route( endpoint: str, request: Request, fastapi_response: Response, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): + """ + [Docs](https://docs.litellm.ai/docs/pass_through/cohere) + """ base_target_url = "https://api.cohere.com" encoded_endpoint = httpx.URL(endpoint).path @@ -156,7 +170,9 @@ async def cohere_proxy_route( @router.api_route( - "/anthropic/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"] + "/anthropic/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Anthropic Pass-through", "pass-through"], ) async def anthropic_proxy_route( endpoint: str, @@ -164,6 +180,9 @@ async def anthropic_proxy_route( fastapi_response: Response, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): + """ + [Docs](https://docs.litellm.ai/docs/anthropic_completion) + """ base_target_url = "https://api.anthropic.com" encoded_endpoint = httpx.URL(endpoint).path @@ -203,13 +222,20 @@ async def anthropic_proxy_route( return received_value -@router.api_route("/bedrock/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +@router.api_route( + "/bedrock/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Bedrock Pass-through", "pass-through"], +) async def bedrock_proxy_route( endpoint: str, request: Request, fastapi_response: Response, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): + """ + [Docs](https://docs.litellm.ai/docs/pass_through/bedrock) + """ create_request_copy(request) try: @@ -277,13 +303,22 @@ async def bedrock_proxy_route( return received_value -@router.api_route("/azure/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +@router.api_route( + "/azure/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Azure Pass-through", "pass-through"], +) async def azure_proxy_route( endpoint: str, request: Request, fastapi_response: Response, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): + """ + Call any azure endpoint using the proxy. + + Just use `{PROXY_BASE_URL}/azure/{endpoint:path}` + """ base_target_url = get_secret_str(secret_name="AZURE_API_BASE") if base_target_url is None: raise Exception( diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4a867f46a..afb83aa37 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5663,11 +5663,11 @@ async def anthropic_response( # noqa: PLR0915 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ - This is a BETA endpoint that calls 100+ LLMs in the anthropic format. + 🚨 DEPRECATED ENDPOINT🚨 - To do a simple pass-through for anthropic, do `{PROXY_BASE_URL}/anthropic/v1/messages` + Use `{PROXY_BASE_URL}/anthropic/v1/messages` instead - [Docs](https://docs.litellm.ai/docs/anthropic_completion). - Docs - https://docs.litellm.ai/docs/anthropic_completion + This was a BETA endpoint that calls 100+ LLMs in the anthropic format. """ from litellm import adapter_completion from litellm.adapters.anthropic_adapter import anthropic_adapter diff --git a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py index ba8653d82..6ce9d5dd8 100644 --- a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py @@ -58,12 +58,21 @@ def create_request_copy(request: Request): } -@router.api_route("/langfuse/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +@router.api_route( + "/langfuse/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + tags=["Langfuse Pass-through", "pass-through"], +) async def langfuse_proxy_route( endpoint: str, request: Request, fastapi_response: Response, ): + """ + Call Langfuse via LiteLLM proxy. Works with Langfuse SDK. + + [Docs](https://docs.litellm.ai/docs/pass_through/langfuse) + """ ## CHECK FOR LITELLM API KEY IN THE QUERY PARAMS - ?..key=LITELLM_API_KEY api_key = request.headers.get("Authorization") or "" diff --git a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py index 271e8992c..92463b2bd 100644 --- a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py @@ -144,16 +144,22 @@ def construct_target_url( @router.api_route( "/vertex-ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"], + tags=["Vertex AI Pass-through", "pass-through"], include_in_schema=False, ) @router.api_route( - "/vertex_ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"] + "/vertex_ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"], tags=["Vertex AI Pass-through", "pass-through"] ) async def vertex_proxy_route( endpoint: str, request: Request, fastapi_response: Response, ): + """ + Call LiteLLM proxy via Vertex AI SDK. + + [Docs](https://docs.litellm.ai/docs/pass_through/vertex_ai) + """ encoded_endpoint = httpx.URL(endpoint).path import re diff --git a/tests/llm_translation/base_llm_unit_tests.py b/tests/llm_translation/base_llm_unit_tests.py index 88fce6dac..24a972e20 100644 --- a/tests/llm_translation/base_llm_unit_tests.py +++ b/tests/llm_translation/base_llm_unit_tests.py @@ -190,6 +190,35 @@ class BaseLLMChatTest(ABC): """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" pass + def test_image_url(self): + litellm.set_verbose = True + from litellm.utils import supports_vision + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + base_completion_call_args = self.get_base_completion_call_args() + if not supports_vision(base_completion_call_args["model"], None): + pytest.skip("Model does not support image input") + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "What's in this image?"}, + { + "type": "image_url", + "image_url": { + "url": "https://i.pinimg.com/736x/b4/b1/be/b4b1becad04d03a9071db2817fc9fe77.jpg" + }, + }, + ], + } + ] + + response = litellm.completion(**base_completion_call_args, messages=messages) + assert response is not None + @pytest.fixture def pdf_messages(self): import base64 diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py new file mode 100644 index 000000000..4e6c5118d --- /dev/null +++ b/tests/llm_translation/test_gemini.py @@ -0,0 +1,15 @@ +from base_llm_unit_tests import BaseLLMChatTest + + +class TestGoogleAIStudioGemini(BaseLLMChatTest): + def get_base_completion_call_args(self) -> dict: + return {"model": "gemini/gemini-1.5-flash"} + + def test_tool_call_no_arguments(self, tool_call_no_arguments): + """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" + from litellm.llms.prompt_templates.factory import ( + convert_to_gemini_tool_call_invoke, + ) + + result = convert_to_gemini_tool_call_invoke(tool_call_no_arguments) + print(result) diff --git a/tests/llm_translation/test_prompt_factory.py b/tests/llm_translation/test_prompt_factory.py index 104997563..d8cf191f6 100644 --- a/tests/llm_translation/test_prompt_factory.py +++ b/tests/llm_translation/test_prompt_factory.py @@ -687,3 +687,16 @@ def test_just_system_message(): llm_provider="bedrock", ) assert "bedrock requires at least one non-system message" in str(e.value) + + +def test_convert_generic_image_chunk_to_openai_image_obj(): + from litellm.llms.prompt_templates.factory import ( + convert_generic_image_chunk_to_openai_image_obj, + convert_to_anthropic_image_obj, + ) + + url = "https://i.pinimg.com/736x/b4/b1/be/b4b1becad04d03a9071db2817fc9fe77.jpg" + image_obj = convert_to_anthropic_image_obj(url) + url_str = convert_generic_image_chunk_to_openai_image_obj(image_obj) + image_obj = convert_to_anthropic_image_obj(url_str) + print(image_obj) diff --git a/tests/llm_translation/test_vertex.py b/tests/llm_translation/test_vertex.py index 3e1087536..1ea2514c9 100644 --- a/tests/llm_translation/test_vertex.py +++ b/tests/llm_translation/test_vertex.py @@ -1190,80 +1190,6 @@ def test_get_image_mime_type_from_url(): assert _get_image_mime_type_from_url("invalid_url") is None -@pytest.mark.parametrize( - "image_url", ["https://example.com/image.jpg", "https://example.com/image.png"] -) -def test_image_completion_request(image_url): - """https:// .jpg, .png images are passed directly to the model""" - from unittest.mock import patch, Mock - import litellm - from litellm.llms.vertex_ai_and_google_ai_studio.gemini.transformation import ( - _get_image_mime_type_from_url, - ) - - # Mock response data - mock_response = Mock() - mock_response.json.return_value = { - "candidates": [{"content": {"parts": [{"text": "This is a sunflower"}]}}], - "usageMetadata": { - "promptTokenCount": 11, - "candidatesTokenCount": 50, - "totalTokenCount": 61, - }, - "modelVersion": "gemini-1.5-pro", - } - mock_response.raise_for_status = MagicMock() - mock_response.status_code = 200 - - # Expected request body - expected_request_body = { - "contents": [ - { - "role": "user", - "parts": [ - {"text": "Whats in this image?"}, - { - "file_data": { - "file_uri": image_url, - "mime_type": _get_image_mime_type_from_url(image_url), - } - }, - ], - } - ], - "system_instruction": {"parts": [{"text": "Be a good bot"}]}, - "generationConfig": {}, - } - - messages = [ - {"role": "system", "content": "Be a good bot"}, - { - "role": "user", - "content": [ - {"type": "text", "text": "Whats in this image?"}, - {"type": "image_url", "image_url": {"url": image_url}}, - ], - }, - ] - - client = HTTPHandler() - with patch.object(client, "post", new=MagicMock()) as mock_post: - mock_post.return_value = mock_response - try: - litellm.completion( - model="gemini/gemini-1.5-pro", - messages=messages, - client=client, - ) - except Exception as e: - print(e) - - # Assert the request body matches expected - mock_post.assert_called_once() - print("mock_post.call_args.kwargs['json']", mock_post.call_args.kwargs["json"]) - assert mock_post.call_args.kwargs["json"] == expected_request_body - - @pytest.mark.parametrize( "model, expected_url", [ @@ -1298,20 +1224,3 @@ def test_vertex_embedding_url(model, expected_url): assert url == expected_url assert endpoint == "predict" - - -from base_llm_unit_tests import BaseLLMChatTest - - -class TestVertexGemini(BaseLLMChatTest): - def get_base_completion_call_args(self) -> dict: - return {"model": "gemini/gemini-1.5-flash"} - - def test_tool_call_no_arguments(self, tool_call_no_arguments): - """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" - from litellm.llms.prompt_templates.factory import ( - convert_to_gemini_tool_call_invoke, - ) - - result = convert_to_gemini_tool_call_invoke(tool_call_no_arguments) - print(result) diff --git a/tests/proxy_admin_ui_tests/test_key_management.py b/tests/proxy_admin_ui_tests/test_key_management.py index 0b392a268..d0b1ab294 100644 --- a/tests/proxy_admin_ui_tests/test_key_management.py +++ b/tests/proxy_admin_ui_tests/test_key_management.py @@ -556,13 +556,22 @@ def test_team_key_generation_team_member_check(): _team_key_generation_check, ) from fastapi import HTTPException + from litellm.proxy._types import LiteLLM_TeamTableCachedObj litellm.key_generation_settings = { "team_key_generation": {"allowed_team_member_roles": ["admin"]} } + team_table = LiteLLM_TeamTableCachedObj( + team_id="test_team_id", + team_alias="test_team_alias", + members_with_roles=[Member(role="admin", user_id="test_user_id")], + ) + assert _team_key_generation_check( + team_table=team_table, user_api_key_dict=UserAPIKeyAuth( + user_id="test_user_id", user_role=LitellmUserRoles.INTERNAL_USER, api_key="sk-1234", team_member=Member(role="admin", user_id="test_user_id"), @@ -570,8 +579,15 @@ def test_team_key_generation_team_member_check(): data=GenerateKeyRequest(), ) + team_table = LiteLLM_TeamTableCachedObj( + team_id="test_team_id", + team_alias="test_team_alias", + members_with_roles=[Member(role="user", user_id="test_user_id")], + ) + with pytest.raises(HTTPException): _team_key_generation_check( + team_table=team_table, user_api_key_dict=UserAPIKeyAuth( user_role=LitellmUserRoles.INTERNAL_USER, api_key="sk-1234", @@ -607,6 +623,7 @@ def test_key_generation_required_params_check( StandardKeyGenerationConfig, PersonalUIKeyGenerationConfig, ) + from litellm.proxy._types import LiteLLM_TeamTableCachedObj from fastapi import HTTPException user_api_key_dict = UserAPIKeyAuth( @@ -614,7 +631,13 @@ def test_key_generation_required_params_check( api_key="sk-1234", user_id="test_user_id", team_id="test_team_id", - team_member=Member(role="admin", user_id="test_user_id"), + team_member=None, + ) + + team_table = LiteLLM_TeamTableCachedObj( + team_id="test_team_id", + team_alias="test_team_alias", + members_with_roles=[Member(role="admin", user_id="test_user_id")], ) if key_type == "team_key": @@ -632,13 +655,13 @@ def test_key_generation_required_params_check( if expected_result: if key_type == "team_key": - assert _team_key_generation_check(user_api_key_dict, input_data) + assert _team_key_generation_check(team_table, user_api_key_dict, input_data) elif key_type == "personal_key": assert _personal_key_generation_check(user_api_key_dict, input_data) else: if key_type == "team_key": with pytest.raises(HTTPException): - _team_key_generation_check(user_api_key_dict, input_data) + _team_key_generation_check(team_table, user_api_key_dict, input_data) elif key_type == "personal_key": with pytest.raises(HTTPException): _personal_key_generation_check(user_api_key_dict, input_data) diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index d70962858..64bb67b58 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -1014,7 +1014,11 @@ async def test_create_team_member_add(prisma_client, new_member_method): with patch( "litellm.proxy.proxy_server.prisma_client.db.litellm_usertable", new_callable=AsyncMock, - ) as mock_litellm_usertable: + ) as mock_litellm_usertable, patch( + "litellm.proxy.auth.auth_checks._get_team_object_from_user_api_key_cache", + new=AsyncMock(return_value=team_obj), + ) as mock_team_obj: + mock_client = AsyncMock( return_value=LiteLLM_UserTable( user_id="1234", max_budget=100, user_email="1234" @@ -1193,7 +1197,10 @@ async def test_create_team_member_add_team_admin( with patch( "litellm.proxy.proxy_server.prisma_client.db.litellm_usertable", new_callable=AsyncMock, - ) as mock_litellm_usertable: + ) as mock_litellm_usertable, patch( + "litellm.proxy.auth.auth_checks._get_team_object_from_user_api_key_cache", + new=AsyncMock(return_value=team_obj), + ) as mock_team_obj: mock_client = AsyncMock( return_value=LiteLLM_UserTable( user_id="1234", max_budget=100, user_email="1234" diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx index 0364245be..db83fd532 100644 --- a/ui/litellm-dashboard/src/components/teams.tsx +++ b/ui/litellm-dashboard/src/components/teams.tsx @@ -413,6 +413,7 @@ const Team: React.FC = ({ selectedTeam["team_id"], user_role ); + message.success("Member added"); console.log(`response for team create call: ${response["data"]}`); // Checking if the team exists in the list and updating or adding accordingly const foundIndex = teams.findIndex((team) => { @@ -430,6 +431,7 @@ const Team: React.FC = ({ setSelectedTeam(response.data); } setIsAddMemberModalVisible(false); + } } catch (error) { console.error("Error creating the team:", error); @@ -825,6 +827,9 @@ const Team: React.FC = ({ labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left" + initialValues={{ + role: "user", + }} > <> @@ -842,8 +847,8 @@ const Team: React.FC = ({ - user admin + user From 8fd3bf34d82696131cd22b766db7fbdc2c61ccea Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 14:39:13 -0800 Subject: [PATCH 02/25] (feat) pass through llm endpoints - add `PATCH` support (vertex context caching requires for update ops) (#6924) * add PATCH for pass through endpoints * test_pass_through_routes_support_all_methods --- .../llm_passthrough_endpoints.py | 10 +++--- .../vertex_ai_endpoints/langfuse_endpoints.py | 2 +- .../vertex_ai_endpoints/vertex_endpoints.py | 6 ++-- .../test_pass_through_unit_tests.py | 35 +++++++++++++++++++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 8eb8290f4..cae211da7 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -56,7 +56,7 @@ def create_request_copy(request: Request): @router.api_route( "/gemini/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Google AI Studio Pass-through", "pass-through"], ) async def gemini_proxy_route( @@ -122,7 +122,7 @@ async def gemini_proxy_route( @router.api_route( "/cohere/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Cohere Pass-through", "pass-through"], ) async def cohere_proxy_route( @@ -171,7 +171,7 @@ async def cohere_proxy_route( @router.api_route( "/anthropic/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Anthropic Pass-through", "pass-through"], ) async def anthropic_proxy_route( @@ -224,7 +224,7 @@ async def anthropic_proxy_route( @router.api_route( "/bedrock/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Bedrock Pass-through", "pass-through"], ) async def bedrock_proxy_route( @@ -305,7 +305,7 @@ async def bedrock_proxy_route( @router.api_route( "/azure/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Azure Pass-through", "pass-through"], ) async def azure_proxy_route( diff --git a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py index 6ce9d5dd8..8992a7330 100644 --- a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py @@ -60,7 +60,7 @@ def create_request_copy(request: Request): @router.api_route( "/langfuse/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Langfuse Pass-through", "pass-through"], ) async def langfuse_proxy_route( diff --git a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py index 92463b2bd..03f4ac9cd 100644 --- a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py @@ -143,12 +143,14 @@ def construct_target_url( @router.api_route( "/vertex-ai/{endpoint:path}", - methods=["GET", "POST", "PUT", "DELETE"], + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Vertex AI Pass-through", "pass-through"], include_in_schema=False, ) @router.api_route( - "/vertex_ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"], tags=["Vertex AI Pass-through", "pass-through"] + "/vertex_ai/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], + tags=["Vertex AI Pass-through", "pass-through"], ) async def vertex_proxy_route( endpoint: str, diff --git a/tests/pass_through_unit_tests/test_pass_through_unit_tests.py b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py index c564c14d2..d5b6b1c9a 100644 --- a/tests/pass_through_unit_tests/test_pass_through_unit_tests.py +++ b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py @@ -10,6 +10,8 @@ sys.path.insert( ) # Adds the parent directory to the system path import fastapi +from fastapi import FastAPI +from fastapi.routing import APIRoute import httpx import pytest import litellm @@ -320,3 +322,36 @@ async def test_pass_through_request_logging_failure_with_stream( assert content is not None if isinstance(content, bytes): assert len(content) > 0 + + +def test_pass_through_routes_support_all_methods(): + """ + Test that all pass-through routes support GET, POST, PUT, DELETE, PATCH methods + """ + # Import the routers + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + router as llm_router, + ) + from litellm.proxy.vertex_ai_endpoints.vertex_endpoints import ( + router as vertex_router, + ) + + # Expected HTTP methods + expected_methods = {"GET", "POST", "PUT", "DELETE", "PATCH"} + + # Function to check routes in a router + def check_router_methods(router): + for route in router.routes: + if isinstance(route, APIRoute): + # Get path and methods for this route + path = route.path + methods = set(route.methods) + print("supported methods for route", path, "are", methods) + # Assert all expected methods are supported + assert ( + methods == expected_methods + ), f"Route {path} does not support all methods. Supported: {methods}, Expected: {expected_methods}" + + # Check both routers + check_router_methods(llm_router) + check_router_methods(vertex_router) From d84e355eabe956627fee0a7c39698d58f872d4eb Mon Sep 17 00:00:00 2001 From: paul-gauthier <69695708+paul-gauthier@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:06:17 -0800 Subject: [PATCH 03/25] sonnet supports pdf, haiku does not (#6928) --- model_prices_and_context_window.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index a56472f7f..b0d0e7d37 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -2032,7 +2032,6 @@ "tool_use_system_prompt_tokens": 264, "supports_assistant_prefill": true, "supports_prompt_caching": true, - "supports_pdf_input": true, "supports_response_schema": true }, "claude-3-opus-20240229": { @@ -2098,6 +2097,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159, "supports_assistant_prefill": true, + "supports_pdf_input": true, "supports_prompt_caching": true, "supports_response_schema": true }, From aea68cbeb652e52b09e3da9f195728144d6db664 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 19:27:06 -0800 Subject: [PATCH 04/25] (feat) DataDog Logger - Add Failure logging + use Standard Logging payload (#6929) * add async_log_failure_event for dd * use standard logging payload for DD logging * use standard logging payload for DD * fix use SLP status * allow opting into _create_v0_logging_payload * add unit tests for DD logging payload * fix dd logging tests --- litellm/__init__.py | 1 + litellm/integrations/datadog/datadog.py | 210 ++++++++++++------ .../test_datadog.py | 136 ++++++++++-- 3 files changed, 257 insertions(+), 90 deletions(-) rename tests/{local_testing => logging_callback_tests}/test_datadog.py (65%) diff --git a/litellm/__init__.py b/litellm/__init__.py index 65b1b3465..43f91fe58 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -68,6 +68,7 @@ callbacks: List[Union[Callable, _custom_logger_compatible_callbacks_literal]] = langfuse_default_tags: Optional[List[str]] = None langsmith_batch_size: Optional[int] = None argilla_batch_size: Optional[int] = None +datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload argilla_transformation_object: Optional[Dict[str, Any]] = None _async_input_callback: List[Callable] = ( [] diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 40044ce9f..527b6f87d 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -33,6 +33,7 @@ from litellm.llms.custom_httpx.http_handler import ( httpxSpecialProvider, ) from litellm.types.services import ServiceLoggerPayload +from litellm.types.utils import StandardLoggingPayload from .types import DD_ERRORS, DatadogPayload, DataDogStatus from .utils import make_json_serializable @@ -106,20 +107,20 @@ class DataDogLogger(CustomBatchLogger): verbose_logger.debug( "Datadog: Logging - Enters logging function for model %s", kwargs ) - dd_payload = self.create_datadog_logging_payload( - kwargs=kwargs, - response_obj=response_obj, - start_time=start_time, - end_time=end_time, - ) + await self._log_async_event(kwargs, response_obj, start_time, end_time) - self.log_queue.append(dd_payload) + except Exception as e: + verbose_logger.exception( + f"Datadog Layer Error - {str(e)}\n{traceback.format_exc()}" + ) + pass + + async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time): + try: verbose_logger.debug( - f"Datadog, event added to queue. Will flush in {self.flush_interval} seconds..." + "Datadog: Logging - Enters logging function for model %s", kwargs ) - - if len(self.log_queue) >= self.batch_size: - await self.async_send_batch() + await self._log_async_event(kwargs, response_obj, start_time, end_time) except Exception as e: verbose_logger.exception( @@ -181,12 +182,20 @@ class DataDogLogger(CustomBatchLogger): verbose_logger.debug( "Datadog: Logging - Enters logging function for model %s", kwargs ) - dd_payload = self.create_datadog_logging_payload( - kwargs=kwargs, - response_obj=response_obj, - start_time=start_time, - end_time=end_time, - ) + if litellm.datadog_use_v1 is True: + dd_payload = self._create_v0_logging_payload( + kwargs=kwargs, + response_obj=response_obj, + start_time=start_time, + end_time=end_time, + ) + else: + dd_payload = self.create_datadog_logging_payload( + kwargs=kwargs, + response_obj=response_obj, + start_time=start_time, + end_time=end_time, + ) response = self.sync_client.post( url=self.intake_url, @@ -215,6 +224,22 @@ class DataDogLogger(CustomBatchLogger): pass pass + async def _log_async_event(self, kwargs, response_obj, start_time, end_time): + dd_payload = self.create_datadog_logging_payload( + kwargs=kwargs, + response_obj=response_obj, + start_time=start_time, + end_time=end_time, + ) + + self.log_queue.append(dd_payload) + verbose_logger.debug( + f"Datadog, event added to queue. Will flush in {self.flush_interval} seconds..." + ) + + if len(self.log_queue) >= self.batch_size: + await self.async_send_batch() + def create_datadog_logging_payload( self, kwargs: Union[dict, Any], @@ -236,63 +261,19 @@ class DataDogLogger(CustomBatchLogger): """ import json - litellm_params = kwargs.get("litellm_params", {}) - metadata = ( - litellm_params.get("metadata", {}) or {} - ) # if litellm_params['metadata'] == None - messages = kwargs.get("messages") - optional_params = kwargs.get("optional_params", {}) - call_type = kwargs.get("call_type", "litellm.completion") - cache_hit = kwargs.get("cache_hit", False) - usage = response_obj["usage"] - id = response_obj.get("id", str(uuid.uuid4())) - usage = dict(usage) - try: - response_time = (end_time - start_time).total_seconds() * 1000 - except Exception: - response_time = None + standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get( + "standard_logging_object", None + ) + if standard_logging_object is None: + raise ValueError("standard_logging_object not found in kwargs") - try: - response_obj = dict(response_obj) - except Exception: - response_obj = response_obj - - # Clean Metadata before logging - never log raw metadata - # the raw metadata can contain circular references which leads to infinite recursion - # we clean out all extra litellm metadata params before logging - clean_metadata = {} - if isinstance(metadata, dict): - for key, value in metadata.items(): - # clean litellm metadata before logging - if key in [ - "endpoint", - "caching_groups", - "previous_models", - ]: - continue - else: - clean_metadata[key] = value + status = DataDogStatus.INFO + if standard_logging_object.get("status") == "failure": + status = DataDogStatus.ERROR # Build the initial payload - payload = { - "id": id, - "call_type": call_type, - "cache_hit": cache_hit, - "start_time": start_time, - "end_time": end_time, - "response_time": response_time, - "model": kwargs.get("model", ""), - "user": kwargs.get("user", ""), - "model_parameters": optional_params, - "spend": kwargs.get("response_cost", 0), - "messages": messages, - "response": response_obj, - "usage": usage, - "metadata": clean_metadata, - } - - make_json_serializable(payload) - json_payload = json.dumps(payload) + make_json_serializable(standard_logging_object) + json_payload = json.dumps(standard_logging_object) verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) @@ -302,7 +283,7 @@ class DataDogLogger(CustomBatchLogger): hostname="", message=json_payload, service="litellm-server", - status=DataDogStatus.INFO, + status=status, ) return dd_payload @@ -382,3 +363,88 @@ class DataDogLogger(CustomBatchLogger): No user has asked for this so far, this might be spammy on datatdog. If need arises we can implement this """ return + + def _create_v0_logging_payload( + self, + kwargs: Union[dict, Any], + response_obj: Any, + start_time: datetime.datetime, + end_time: datetime.datetime, + ) -> DatadogPayload: + """ + Note: This is our V1 Version of DataDog Logging Payload + + + (Not Recommended) If you want this to get logged set `litellm.datadog_use_v1 = True` + """ + import json + + litellm_params = kwargs.get("litellm_params", {}) + metadata = ( + litellm_params.get("metadata", {}) or {} + ) # if litellm_params['metadata'] == None + messages = kwargs.get("messages") + optional_params = kwargs.get("optional_params", {}) + call_type = kwargs.get("call_type", "litellm.completion") + cache_hit = kwargs.get("cache_hit", False) + usage = response_obj["usage"] + id = response_obj.get("id", str(uuid.uuid4())) + usage = dict(usage) + try: + response_time = (end_time - start_time).total_seconds() * 1000 + except Exception: + response_time = None + + try: + response_obj = dict(response_obj) + except Exception: + response_obj = response_obj + + # Clean Metadata before logging - never log raw metadata + # the raw metadata can contain circular references which leads to infinite recursion + # we clean out all extra litellm metadata params before logging + clean_metadata = {} + if isinstance(metadata, dict): + for key, value in metadata.items(): + # clean litellm metadata before logging + if key in [ + "endpoint", + "caching_groups", + "previous_models", + ]: + continue + else: + clean_metadata[key] = value + + # Build the initial payload + payload = { + "id": id, + "call_type": call_type, + "cache_hit": cache_hit, + "start_time": start_time, + "end_time": end_time, + "response_time": response_time, + "model": kwargs.get("model", ""), + "user": kwargs.get("user", ""), + "model_parameters": optional_params, + "spend": kwargs.get("response_cost", 0), + "messages": messages, + "response": response_obj, + "usage": usage, + "metadata": clean_metadata, + } + + make_json_serializable(payload) + json_payload = json.dumps(payload) + + verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) + + dd_payload = DatadogPayload( + ddsource=os.getenv("DD_SOURCE", "litellm"), + ddtags="", + hostname="", + message=json_payload, + service="litellm-server", + status=DataDogStatus.INFO, + ) + return dd_payload diff --git a/tests/local_testing/test_datadog.py b/tests/logging_callback_tests/test_datadog.py similarity index 65% rename from tests/local_testing/test_datadog.py rename to tests/logging_callback_tests/test_datadog.py index 990a5b76c..a93156226 100644 --- a/tests/local_testing/test_datadog.py +++ b/tests/logging_callback_tests/test_datadog.py @@ -2,6 +2,7 @@ import io import os import sys + sys.path.insert(0, os.path.abspath("../..")) import asyncio @@ -16,11 +17,126 @@ import pytest import litellm from litellm import completion from litellm._logging import verbose_logger -from litellm.integrations.datadog.types import DatadogPayload +from litellm.integrations.datadog.datadog import * +from datetime import datetime, timedelta +from litellm.types.utils import ( + StandardLoggingPayload, + StandardLoggingModelInformation, + StandardLoggingMetadata, + StandardLoggingHiddenParams, +) verbose_logger.setLevel(logging.DEBUG) +def create_standard_logging_payload() -> StandardLoggingPayload: + return StandardLoggingPayload( + id="test_id", + call_type="completion", + response_cost=0.1, + response_cost_failure_debug_info=None, + status="success", + total_tokens=30, + prompt_tokens=20, + completion_tokens=10, + startTime=1234567890.0, + endTime=1234567891.0, + completionStartTime=1234567890.5, + model_map_information=StandardLoggingModelInformation( + model_map_key="gpt-3.5-turbo", model_map_value=None + ), + model="gpt-3.5-turbo", + model_id="model-123", + model_group="openai-gpt", + api_base="https://api.openai.com", + metadata=StandardLoggingMetadata( + user_api_key_hash="test_hash", + user_api_key_org_id=None, + user_api_key_alias="test_alias", + user_api_key_team_id="test_team", + user_api_key_user_id="test_user", + user_api_key_team_alias="test_team_alias", + spend_logs_metadata=None, + requester_ip_address="127.0.0.1", + requester_metadata=None, + ), + cache_hit=False, + cache_key=None, + saved_cache_cost=0.0, + request_tags=[], + end_user=None, + requester_ip_address="127.0.0.1", + messages=[{"role": "user", "content": "Hello, world!"}], + response={"choices": [{"message": {"content": "Hi there!"}}]}, + error_str=None, + model_parameters={"stream": True}, + hidden_params=StandardLoggingHiddenParams( + model_id="model-123", + cache_key=None, + api_base="https://api.openai.com", + response_cost="0.1", + additional_headers=None, + ), + ) + + +@pytest.mark.asyncio +async def test_create_datadog_logging_payload(): + """Test creating a DataDog logging payload from a standard logging object""" + dd_logger = DataDogLogger() + standard_payload = create_standard_logging_payload() + + # Create mock kwargs with the standard logging object + kwargs = {"standard_logging_object": standard_payload} + + # Test payload creation + dd_payload = dd_logger.create_datadog_logging_payload( + kwargs=kwargs, + response_obj=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + # Verify payload structure + assert dd_payload["ddsource"] == os.getenv("DD_SOURCE", "litellm") + assert dd_payload["service"] == "litellm-server" + assert dd_payload["status"] == DataDogStatus.INFO + + # verify the message field == standard_payload + dict_payload = json.loads(dd_payload["message"]) + assert dict_payload == standard_payload + + +@pytest.mark.asyncio +async def test_datadog_failure_logging(): + """Test logging a failure event to DataDog""" + dd_logger = DataDogLogger() + standard_payload = create_standard_logging_payload() + standard_payload["status"] = "failure" # Set status to failure + standard_payload["error_str"] = "Test error" + + kwargs = {"standard_logging_object": standard_payload} + + dd_payload = dd_logger.create_datadog_logging_payload( + kwargs=kwargs, + response_obj=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + assert ( + dd_payload["status"] == DataDogStatus.ERROR + ) # Verify failure maps to warning status + + # verify the message field == standard_payload + dict_payload = json.loads(dd_payload["message"]) + assert dict_payload == standard_payload + + # verify error_str is in the message field + assert "error_str" in dict_payload + assert dict_payload["error_str"] == "Test error" + + @pytest.mark.asyncio async def test_datadog_logging_http_request(): """ @@ -111,22 +227,7 @@ async def test_datadog_logging_http_request(): # Parse the 'message' field as JSON and check its structure message = json.loads(body[0]["message"]) - expected_message_fields = [ - "id", - "call_type", - "cache_hit", - "start_time", - "end_time", - "response_time", - "model", - "user", - "model_parameters", - "spend", - "messages", - "response", - "usage", - "metadata", - ] + expected_message_fields = StandardLoggingPayload.__annotations__.keys() for field in expected_message_fields: assert field in message, f"Field '{field}' is missing from the message" @@ -138,7 +239,6 @@ async def test_datadog_logging_http_request(): assert "temperature" in message["model_parameters"] assert "max_tokens" in message["model_parameters"] assert isinstance(message["response"], dict) - assert isinstance(message["usage"], dict) assert isinstance(message["metadata"], dict) except Exception as e: From 4bc06392db3d34d67a80eb931ee4f1737c984f6e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 20:26:57 -0800 Subject: [PATCH 05/25] (feat) log proxy auth errors on datadog (#6931) * add new dd type for auth errors * add async_log_proxy_authentication_errors * fix comment * use async_log_proxy_authentication_errors * test_datadog_post_call_failure_hook * test_async_log_proxy_authentication_errors --- litellm/integrations/datadog/datadog.py | 35 ++++++++- ...odel_prices_and_context_window_backup.json | 2 +- litellm/proxy/auth/user_api_key_auth.py | 12 +-- litellm/proxy/utils.py | 43 +++++++++- .../integrations/datadog.py} | 10 ++- tests/logging_callback_tests/test_datadog.py | 78 +++++++++++++++++++ tests/proxy_unit_tests/test_proxy_server.py | 70 +++++++++++++++++ 7 files changed, 241 insertions(+), 9 deletions(-) rename litellm/{integrations/datadog/types.py => types/integrations/datadog.py} (68%) diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 527b6f87d..42d9a38d6 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -32,10 +32,11 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, ) +from litellm.proxy._types import UserAPIKeyAuth +from litellm.types.integrations.datadog import * from litellm.types.services import ServiceLoggerPayload from litellm.types.utils import StandardLoggingPayload -from .types import DD_ERRORS, DatadogPayload, DataDogStatus from .utils import make_json_serializable DD_MAX_BATCH_SIZE = 1000 # max number of logs DD API can accept @@ -364,6 +365,38 @@ class DataDogLogger(CustomBatchLogger): """ return + async def async_post_call_failure_hook( + self, + request_data: dict, + original_exception: Exception, + user_api_key_dict: UserAPIKeyAuth, + ): + """ + Handles Proxy Errors (not-related to LLM API), ex: Authentication Errors + """ + import json + + _exception_payload = DatadogProxyFailureHookJsonMessage( + exception=str(original_exception), + error_class=str(original_exception.__class__.__name__), + status_code=getattr(original_exception, "status_code", None), + traceback=traceback.format_exc(), + user_api_key_dict=user_api_key_dict.model_dump(), + ) + + json_payload = json.dumps(_exception_payload) + verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) + dd_payload = DatadogPayload( + ddsource=os.getenv("DD_SOURCE", "litellm"), + ddtags="", + hostname="", + message=json_payload, + service="litellm-server", + status=DataDogStatus.ERROR, + ) + + self.log_queue.append(dd_payload) + def _create_v0_logging_payload( self, kwargs: Union[dict, Any], diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index a56472f7f..b0d0e7d37 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -2032,7 +2032,6 @@ "tool_use_system_prompt_tokens": 264, "supports_assistant_prefill": true, "supports_prompt_caching": true, - "supports_pdf_input": true, "supports_response_schema": true }, "claude-3-opus-20240229": { @@ -2098,6 +2097,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159, "supports_assistant_prefill": true, + "supports_pdf_input": true, "supports_prompt_caching": true, "supports_response_schema": true }, diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index d19215245..32f0c95db 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -1197,13 +1197,15 @@ async def user_api_key_auth( # noqa: PLR0915 extra={"requester_ip": requester_ip}, ) - # Log this exception to OTEL - if open_telemetry_logger is not None: - await open_telemetry_logger.async_post_call_failure_hook( # type: ignore + # Log this exception to OTEL, Datadog etc + asyncio.create_task( + proxy_logging_obj.async_log_proxy_authentication_errors( original_exception=e, - request_data={}, - user_api_key_dict=UserAPIKeyAuth(parent_otel_span=parent_otel_span), + request=request, + parent_otel_span=parent_otel_span, + api_key=api_key, ) + ) if isinstance(e, litellm.BudgetExceededError): raise ProxyException( diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 2a298af21..46d554190 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -854,6 +854,20 @@ class ProxyLogging: ), ).start() + await self._run_post_call_failure_hook_custom_loggers( + original_exception=original_exception, + request_data=request_data, + user_api_key_dict=user_api_key_dict, + ) + + return + + async def _run_post_call_failure_hook_custom_loggers( + self, + original_exception: Exception, + request_data: dict, + user_api_key_dict: UserAPIKeyAuth, + ): for callback in litellm.callbacks: try: _callback: Optional[CustomLogger] = None @@ -872,7 +886,34 @@ class ProxyLogging: except Exception as e: raise e - return + async def async_log_proxy_authentication_errors( + self, + original_exception: Exception, + request: Request, + parent_otel_span: Optional[Any], + api_key: str, + ): + """ + Handler for Logging Authentication Errors on LiteLLM Proxy + Why not use post_call_failure_hook? + - `post_call_failure_hook` calls `litellm_logging_obj.async_failure_handler`. This led to the Exception being logged twice + + What does this handler do? + - Logs Authentication Errors (like invalid API Key passed) to CustomLogger compatible classes (OTEL, Datadog etc) + - calls CustomLogger.async_post_call_failure_hook + """ + + user_api_key_dict = UserAPIKeyAuth( + parent_otel_span=parent_otel_span, + token=_hash_token_if_needed(token=api_key), + ) + request_data = await request.json() + await self._run_post_call_failure_hook_custom_loggers( + original_exception=original_exception, + request_data=request_data, + user_api_key_dict=user_api_key_dict, + ) + pass async def post_call_success_hook( self, diff --git a/litellm/integrations/datadog/types.py b/litellm/types/integrations/datadog.py similarity index 68% rename from litellm/integrations/datadog/types.py rename to litellm/types/integrations/datadog.py index 87aa3ce17..79d4eded4 100644 --- a/litellm/integrations/datadog/types.py +++ b/litellm/types/integrations/datadog.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import TypedDict +from typing import Optional, TypedDict class DataDogStatus(str, Enum): @@ -19,3 +19,11 @@ class DatadogPayload(TypedDict, total=False): class DD_ERRORS(Enum): DATADOG_413_ERROR = "Datadog API Error - Payload too large (batch is above 5MB uncompressed). If you want this logged either disable request/response logging or set `DD_BATCH_SIZE=50`" + + +class DatadogProxyFailureHookJsonMessage(TypedDict, total=False): + exception: str + error_class: str + status_code: Optional[int] + traceback: str + user_api_key_dict: dict diff --git a/tests/logging_callback_tests/test_datadog.py b/tests/logging_callback_tests/test_datadog.py index a93156226..43af1b53b 100644 --- a/tests/logging_callback_tests/test_datadog.py +++ b/tests/logging_callback_tests/test_datadog.py @@ -344,3 +344,81 @@ async def test_datadog_logging(): await asyncio.sleep(5) except Exception as e: print(e) + + +@pytest.mark.asyncio +async def test_datadog_post_call_failure_hook(): + """Test logging proxy failures (e.g., authentication errors) to DataDog""" + try: + from litellm.integrations.datadog.datadog import DataDogLogger + + os.environ["DD_SITE"] = "https://fake.datadoghq.com" + os.environ["DD_API_KEY"] = "anything" + dd_logger = DataDogLogger() + + # Create a mock for the async_client's post method + mock_post = AsyncMock() + mock_post.return_value.status_code = 202 + mock_post.return_value.text = "Accepted" + dd_logger.async_client.post = mock_post + + # Create a test exception + class AuthenticationError(Exception): + def __init__(self): + self.status_code = 401 + super().__init__("Invalid API key") + + test_exception = AuthenticationError() + + # Create test request data and user API key dict + request_data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}], + } + + user_api_key_dict = UserAPIKeyAuth( + api_key="fake_key", user_id="test_user", team_id="test_team" + ) + + # Call the failure hook + await dd_logger.async_post_call_failure_hook( + request_data=request_data, + original_exception=test_exception, + user_api_key_dict=user_api_key_dict, + ) + + # Wait for the periodic flush + await asyncio.sleep(6) + + # Assert that the mock was called + assert mock_post.called, "HTTP request was not made" + + # Get the arguments of the last call + args, kwargs = mock_post.call_args + + # Verify endpoint + assert kwargs["url"].endswith("/api/v2/logs"), "Incorrect DataDog endpoint" + + # Decode and verify payload + body = kwargs["data"] + with gzip.open(io.BytesIO(body), "rb") as f: + body = f.read().decode("utf-8") + + body = json.loads(body) + assert len(body) == 1, "Expected one log entry" + + log_entry = body[0] + assert log_entry["status"] == "error", "Expected error status" + assert log_entry["service"] == "litellm-server" + + # Verify message content + message = json.loads(log_entry["message"]) + print("logged message", json.dumps(message, indent=2)) + assert message["exception"] == "Invalid API key" + assert message["error_class"] == "AuthenticationError" + assert message["status_code"] == 401 + assert "traceback" in message + assert message["user_api_key_dict"]["api_key"] == "fake_key" + + except Exception as e: + pytest.fail(f"Test failed with exception: {str(e)}") diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index 64bb67b58..f0bcc7190 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -2125,3 +2125,73 @@ async def test_proxy_server_prisma_setup_invalid_db(): if _old_db_url: os.environ["DATABASE_URL"] = _old_db_url + + +@pytest.mark.asyncio +async def test_async_log_proxy_authentication_errors(): + """ + Test if async_log_proxy_authentication_errors correctly logs authentication errors through custom loggers + """ + import json + from fastapi import Request + from litellm.proxy.utils import ProxyLogging + from litellm.caching import DualCache + from litellm.integrations.custom_logger import CustomLogger + + # Create a mock custom logger to verify it's called + class MockCustomLogger(CustomLogger): + def __init__(self): + self.called = False + self.exception_logged = None + self.request_data_logged = None + self.user_api_key_dict_logged = None + + async def async_post_call_failure_hook( + self, + request_data: dict, + original_exception: Exception, + user_api_key_dict: UserAPIKeyAuth, + ): + self.called = True + self.exception_logged = original_exception + self.request_data_logged = request_data + print("logged request_data", request_data) + if isinstance(request_data, AsyncMock): + self.request_data_logged = ( + await request_data() + ) # get the actual value from AsyncMock + else: + self.request_data_logged = request_data + self.user_api_key_dict_logged = user_api_key_dict + + # Create test data + test_data = {"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]} + + # Create a mock request + request = Request(scope={"type": "http", "method": "POST"}) + request._json = AsyncMock(return_value=test_data) + + # Create a test exception + test_exception = Exception("Invalid API Key") + + # Initialize ProxyLogging + mock_logger = MockCustomLogger() + litellm.callbacks = [mock_logger] + proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache()) + + # Call the method + await proxy_logging_obj.async_log_proxy_authentication_errors( + original_exception=test_exception, + request=request, + parent_otel_span=None, + api_key="test-key", + ) + + # Verify the mock logger was called with correct parameters + assert mock_logger.called == True + assert mock_logger.exception_logged == test_exception + assert mock_logger.request_data_logged == test_data + assert mock_logger.user_api_key_dict_logged is not None + assert ( + mock_logger.user_api_key_dict_logged.token is not None + ) # token should be hashed From 68e59824a37b42fc95e04f3e046175e0a060b180 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 20:27:12 -0800 Subject: [PATCH 06/25] (feat) Allow using include to include external YAML files in a config.yaml (#6922) * add helper to process inlcudes directive on yaml * add doc on config management * unit tests for `include` on config.yaml --- .../docs/proxy/config_management.md | 59 ++++++++++++++++ docs/my-website/docs/proxy/configs.md | 2 +- docs/my-website/sidebars.js | 2 +- litellm/proxy/model_config.yaml | 10 +++ litellm/proxy/proxy_config.yaml | 26 +------ litellm/proxy/proxy_server.py | 55 +++++++++++++++ .../config_with_include.yaml | 5 ++ .../config_with_missing_include.yaml | 5 ++ .../config_with_multiple_includes.yaml | 6 ++ .../example_config_yaml/included_models.yaml | 4 ++ .../example_config_yaml/models_file_1.yaml | 4 ++ .../example_config_yaml/models_file_2.yaml | 4 ++ .../test_proxy_config_unit_test.py | 69 +++++++++++++++++++ 13 files changed, 226 insertions(+), 25 deletions(-) create mode 100644 docs/my-website/docs/proxy/config_management.md create mode 100644 litellm/proxy/model_config.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/config_with_include.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/config_with_missing_include.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/config_with_multiple_includes.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/included_models.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/models_file_1.yaml create mode 100644 tests/proxy_unit_tests/example_config_yaml/models_file_2.yaml diff --git a/docs/my-website/docs/proxy/config_management.md b/docs/my-website/docs/proxy/config_management.md new file mode 100644 index 000000000..4f7c5775b --- /dev/null +++ b/docs/my-website/docs/proxy/config_management.md @@ -0,0 +1,59 @@ +# File Management + +## `include` external YAML files in a config.yaml + +You can use `include` to include external YAML files in a config.yaml. + +**Quick Start Usage:** + +To include a config file, use `include` with either a single file or a list of files. + +Contents of `parent_config.yaml`: +```yaml +include: + - model_config.yaml # 👈 Key change, will include the contents of model_config.yaml + +litellm_settings: + callbacks: ["prometheus"] +``` + + +Contents of `model_config.yaml`: +```yaml +model_list: + - model_name: gpt-4o + litellm_params: + model: openai/gpt-4o + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + - model_name: fake-anthropic-endpoint + litellm_params: + model: anthropic/fake + api_base: https://exampleanthropicendpoint-production.up.railway.app/ + +``` + +Start proxy server + +This will start the proxy server with config `parent_config.yaml`. Since the `include` directive is used, the server will also include the contents of `model_config.yaml`. +``` +litellm --config parent_config.yaml --detailed_debug +``` + + + + + +## Examples using `include` + +Include a single file: +```yaml +include: + - model_config.yaml +``` + +Include multiple files: +```yaml +include: + - model_config.yaml + - another_config.yaml +``` \ No newline at end of file diff --git a/docs/my-website/docs/proxy/configs.md b/docs/my-website/docs/proxy/configs.md index 4bd4d2666..ccb9872d6 100644 --- a/docs/my-website/docs/proxy/configs.md +++ b/docs/my-website/docs/proxy/configs.md @@ -2,7 +2,7 @@ import Image from '@theme/IdealImage'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Proxy Config.yaml +# Overview Set model list, `api_base`, `api_key`, `temperature` & proxy server settings (`master-key`) on the config.yaml. | Param Name | Description | diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 879175db6..1deb0dd75 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -32,7 +32,7 @@ const sidebars = { { "type": "category", "label": "Config.yaml", - "items": ["proxy/configs", "proxy/config_settings"] + "items": ["proxy/configs", "proxy/config_management", "proxy/config_settings"] }, { type: "category", diff --git a/litellm/proxy/model_config.yaml b/litellm/proxy/model_config.yaml new file mode 100644 index 000000000..a0399c095 --- /dev/null +++ b/litellm/proxy/model_config.yaml @@ -0,0 +1,10 @@ +model_list: + - model_name: gpt-4o + litellm_params: + model: openai/gpt-4o + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + - model_name: fake-anthropic-endpoint + litellm_params: + model: anthropic/fake + api_base: https://exampleanthropicendpoint-production.up.railway.app/ + diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 40cd86c5c..aad190fb7 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,25 +1,5 @@ -model_list: - - model_name: gpt-4o - litellm_params: - model: openai/gpt-4o - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - - model_name: fake-anthropic-endpoint - litellm_params: - model: anthropic/fake - api_base: https://exampleanthropicendpoint-production.up.railway.app/ - -router_settings: - provider_budget_config: - openai: - budget_limit: 0.3 # float of $ value budget for time period - time_period: 1d # can be 1d, 2d, 30d - anthropic: - budget_limit: 5 - time_period: 1d - redis_host: os.environ/REDIS_HOST - redis_port: os.environ/REDIS_PORT - redis_password: os.environ/REDIS_PASSWORD +include: + - model_config.yaml litellm_settings: - callbacks: ["prometheus"] - success_callback: ["langfuse"] \ No newline at end of file + callbacks: ["prometheus"] \ No newline at end of file diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index afb83aa37..15971263a 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1377,6 +1377,16 @@ class ProxyConfig: _, file_extension = os.path.splitext(config_file_path) return file_extension.lower() == ".yaml" or file_extension.lower() == ".yml" + def _load_yaml_file(self, file_path: str) -> dict: + """ + Load and parse a YAML file + """ + try: + with open(file_path, "r") as file: + return yaml.safe_load(file) or {} + except Exception as e: + raise Exception(f"Error loading yaml file {file_path}: {str(e)}") + async def _get_config_from_file( self, config_file_path: Optional[str] = None ) -> dict: @@ -1407,6 +1417,51 @@ class ProxyConfig: "litellm_settings": {}, } + # Process includes + config = self._process_includes( + config=config, base_dir=os.path.dirname(os.path.abspath(file_path or "")) + ) + + verbose_proxy_logger.debug(f"loaded config={json.dumps(config, indent=4)}") + return config + + def _process_includes(self, config: dict, base_dir: str) -> dict: + """ + Process includes by appending their contents to the main config + + Handles nested config.yamls with `include` section + + Example config: This will get the contents from files in `include` and append it + ```yaml + include: + - model_config.yaml + + litellm_settings: + callbacks: ["prometheus"] + ``` + """ + if "include" not in config: + return config + + if not isinstance(config["include"], list): + raise ValueError("'include' must be a list of file paths") + + # Load and append all included files + for include_file in config["include"]: + file_path = os.path.join(base_dir, include_file) + if not os.path.exists(file_path): + raise FileNotFoundError(f"Included file not found: {file_path}") + + included_config = self._load_yaml_file(file_path) + # Simply update/extend the main config with included config + for key, value in included_config.items(): + if isinstance(value, list) and key in config: + config[key].extend(value) + else: + config[key] = value + + # Remove the include directive + del config["include"] return config async def save_config(self, new_config: dict): diff --git a/tests/proxy_unit_tests/example_config_yaml/config_with_include.yaml b/tests/proxy_unit_tests/example_config_yaml/config_with_include.yaml new file mode 100644 index 000000000..0a0c9434b --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/config_with_include.yaml @@ -0,0 +1,5 @@ +include: + - included_models.yaml + +litellm_settings: + callbacks: ["prometheus"] \ No newline at end of file diff --git a/tests/proxy_unit_tests/example_config_yaml/config_with_missing_include.yaml b/tests/proxy_unit_tests/example_config_yaml/config_with_missing_include.yaml new file mode 100644 index 000000000..40d3e9e7f --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/config_with_missing_include.yaml @@ -0,0 +1,5 @@ +include: + - non-existent-file.yaml + +litellm_settings: + callbacks: ["prometheus"] \ No newline at end of file diff --git a/tests/proxy_unit_tests/example_config_yaml/config_with_multiple_includes.yaml b/tests/proxy_unit_tests/example_config_yaml/config_with_multiple_includes.yaml new file mode 100644 index 000000000..c46adacd7 --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/config_with_multiple_includes.yaml @@ -0,0 +1,6 @@ +include: + - models_file_1.yaml + - models_file_2.yaml + +litellm_settings: + callbacks: ["prometheus"] \ No newline at end of file diff --git a/tests/proxy_unit_tests/example_config_yaml/included_models.yaml b/tests/proxy_unit_tests/example_config_yaml/included_models.yaml new file mode 100644 index 000000000..c1526b203 --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/included_models.yaml @@ -0,0 +1,4 @@ +model_list: + - model_name: included-model + litellm_params: + model: gpt-4 \ No newline at end of file diff --git a/tests/proxy_unit_tests/example_config_yaml/models_file_1.yaml b/tests/proxy_unit_tests/example_config_yaml/models_file_1.yaml new file mode 100644 index 000000000..344f67128 --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/models_file_1.yaml @@ -0,0 +1,4 @@ +model_list: + - model_name: included-model-1 + litellm_params: + model: gpt-4 \ No newline at end of file diff --git a/tests/proxy_unit_tests/example_config_yaml/models_file_2.yaml b/tests/proxy_unit_tests/example_config_yaml/models_file_2.yaml new file mode 100644 index 000000000..56bc3b1aa --- /dev/null +++ b/tests/proxy_unit_tests/example_config_yaml/models_file_2.yaml @@ -0,0 +1,4 @@ +model_list: + - model_name: included-model-2 + litellm_params: + model: gpt-3.5-turbo \ No newline at end of file diff --git a/tests/proxy_unit_tests/test_proxy_config_unit_test.py b/tests/proxy_unit_tests/test_proxy_config_unit_test.py index bb51ce726..e9923e89d 100644 --- a/tests/proxy_unit_tests/test_proxy_config_unit_test.py +++ b/tests/proxy_unit_tests/test_proxy_config_unit_test.py @@ -23,6 +23,8 @@ import logging from litellm.proxy.proxy_server import ProxyConfig +INVALID_FILES = ["config_with_missing_include.yaml"] + @pytest.mark.asyncio async def test_basic_reading_configs_from_files(): @@ -38,6 +40,9 @@ async def test_basic_reading_configs_from_files(): print(files) for file in files: + if file in INVALID_FILES: # these are intentionally invalid files + continue + print("reading file=", file) config_path = os.path.join(example_config_yaml_path, file) config = await proxy_config_instance.get_config(config_file_path=config_path) print(config) @@ -115,3 +120,67 @@ async def test_read_config_file_with_os_environ_vars(): os.environ[key] = _old_env_vars[key] else: del os.environ[key] + + +@pytest.mark.asyncio +async def test_basic_include_directive(): + """ + Test that the include directive correctly loads and merges configs + """ + proxy_config_instance = ProxyConfig() + current_path = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join( + current_path, "example_config_yaml", "config_with_include.yaml" + ) + + config = await proxy_config_instance.get_config(config_file_path=config_path) + + # Verify the included model list was merged + assert len(config["model_list"]) > 0 + assert any( + model["model_name"] == "included-model" for model in config["model_list"] + ) + + # Verify original config settings remain + assert config["litellm_settings"]["callbacks"] == ["prometheus"] + + +@pytest.mark.asyncio +async def test_missing_include_file(): + """ + Test that a missing included file raises FileNotFoundError + """ + proxy_config_instance = ProxyConfig() + current_path = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join( + current_path, "example_config_yaml", "config_with_missing_include.yaml" + ) + + with pytest.raises(FileNotFoundError): + await proxy_config_instance.get_config(config_file_path=config_path) + + +@pytest.mark.asyncio +async def test_multiple_includes(): + """ + Test that multiple files in the include list are all processed correctly + """ + proxy_config_instance = ProxyConfig() + current_path = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join( + current_path, "example_config_yaml", "config_with_multiple_includes.yaml" + ) + + config = await proxy_config_instance.get_config(config_file_path=config_path) + + # Verify models from both included files are present + assert len(config["model_list"]) == 2 + assert any( + model["model_name"] == "included-model-1" for model in config["model_list"] + ) + assert any( + model["model_name"] == "included-model-2" for model in config["model_list"] + ) + + # Verify original config settings remain + assert config["litellm_settings"]["callbacks"] == ["prometheus"] From fe151db27c4642bfcc6e3409f588496bedaeb9f8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 20:27:50 -0800 Subject: [PATCH 07/25] =?UTF-8?q?bump:=20version=201.52.16=20=E2=86=92=201?= =?UTF-8?q?.53.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5096cc60..11a4880d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.52.16" +version = "1.53.0" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -91,7 +91,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.52.16" +version = "1.53.0" version_files = [ "pyproject.toml:^version" ] From a6da3dea03fb0f57c1923a2639ae3515a123fb7a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 26 Nov 2024 22:08:04 -0800 Subject: [PATCH 08/25] (feat) dd logger - set tags according to the values set by those env vars (#6933) * dd logger, inherit from .envs * test_datadog_payload_environment_variables * fix _get_datadog_service --- litellm/integrations/datadog/datadog.py | 44 ++++++++++++++------ litellm/proxy/proxy_config.yaml | 2 +- tests/logging_callback_tests/test_datadog.py | 44 ++++++++++++++++++++ 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 42d9a38d6..482c2bc10 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -279,11 +279,11 @@ class DataDogLogger(CustomBatchLogger): verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) dd_payload = DatadogPayload( - ddsource=os.getenv("DD_SOURCE", "litellm"), - ddtags="", - hostname="", + ddsource=self._get_datadog_source(), + ddtags=self._get_datadog_tags(), + hostname=self._get_datadog_hostname(), message=json_payload, - service="litellm-server", + service=self._get_datadog_service(), status=status, ) return dd_payload @@ -387,11 +387,11 @@ class DataDogLogger(CustomBatchLogger): json_payload = json.dumps(_exception_payload) verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) dd_payload = DatadogPayload( - ddsource=os.getenv("DD_SOURCE", "litellm"), - ddtags="", - hostname="", + ddsource=self._get_datadog_source(), + ddtags=self._get_datadog_tags(), + hostname=self._get_datadog_hostname(), message=json_payload, - service="litellm-server", + service=self._get_datadog_service(), status=DataDogStatus.ERROR, ) @@ -473,11 +473,31 @@ class DataDogLogger(CustomBatchLogger): verbose_logger.debug("Datadog: Logger - Logging payload = %s", json_payload) dd_payload = DatadogPayload( - ddsource=os.getenv("DD_SOURCE", "litellm"), - ddtags="", - hostname="", + ddsource=self._get_datadog_source(), + ddtags=self._get_datadog_tags(), + hostname=self._get_datadog_hostname(), message=json_payload, - service="litellm-server", + service=self._get_datadog_service(), status=DataDogStatus.INFO, ) return dd_payload + + @staticmethod + def _get_datadog_tags(): + return f"env:{os.getenv('DD_ENV', 'unknown')},service:{os.getenv('DD_SERVICE', 'litellm')},version:{os.getenv('DD_VERSION', 'unknown')}" + + @staticmethod + def _get_datadog_source(): + return os.getenv("DD_SOURCE", "litellm") + + @staticmethod + def _get_datadog_service(): + return os.getenv("DD_SERVICE", "litellm-server") + + @staticmethod + def _get_datadog_hostname(): + return "" + + @staticmethod + def _get_datadog_env(): + return os.getenv("DD_ENV", "unknown") diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index aad190fb7..968cb8b39 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -2,4 +2,4 @@ include: - model_config.yaml litellm_settings: - callbacks: ["prometheus"] \ No newline at end of file + callbacks: ["datadog"] diff --git a/tests/logging_callback_tests/test_datadog.py b/tests/logging_callback_tests/test_datadog.py index 43af1b53b..53667460e 100644 --- a/tests/logging_callback_tests/test_datadog.py +++ b/tests/logging_callback_tests/test_datadog.py @@ -422,3 +422,47 @@ async def test_datadog_post_call_failure_hook(): except Exception as e: pytest.fail(f"Test failed with exception: {str(e)}") + + +@pytest.mark.asyncio +async def test_datadog_payload_environment_variables(): + """Test that DataDog payload correctly includes environment variables in the payload structure""" + try: + # Set test environment variables + test_env = { + "DD_ENV": "test-env", + "DD_SERVICE": "test-service", + "DD_VERSION": "1.0.0", + "DD_SOURCE": "test-source", + "DD_API_KEY": "fake-key", + "DD_SITE": "datadoghq.com", + } + + with patch.dict(os.environ, test_env): + dd_logger = DataDogLogger() + standard_payload = create_standard_logging_payload() + + # Create the payload + dd_payload = dd_logger.create_datadog_logging_payload( + kwargs={"standard_logging_object": standard_payload}, + response_obj=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + print("dd payload=", json.dumps(dd_payload, indent=2)) + + # Verify payload structure and environment variables + assert ( + dd_payload["ddsource"] == "test-source" + ), "Incorrect source in payload" + assert ( + dd_payload["service"] == "test-service" + ), "Incorrect service in payload" + assert ( + dd_payload["ddtags"] + == "env:test-env,service:test-service,version:1.0.0" + ), "Incorrect tags in payload" + + except Exception as e: + pytest.fail(f"Test failed with exception: {str(e)}") From 562e7defe688f6ab1ce54536d995def9fd2b1ca7 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 27 Nov 2024 12:53:19 +0530 Subject: [PATCH 09/25] build(ui/): update ui build --- litellm/proxy/_experimental/out/404.html | 1 - .../static/chunks/131-3d2257b0ff5aadb2.js | 8 ----- .../static/chunks/131-4ee1d633e8928742.js | 8 +++++ .../static/chunks/626-0c564a21577c9c53.js | 13 +++++++ .../static/chunks/626-4e8df4039ecf4386.js | 13 ------- ...3027703b2e8.js => 902-292bb6a83427dbc7.js} | 0 .../chunks/app/layout-05e5448bd170dbcb.js | 1 + .../chunks/app/layout-61827b157521da1b.js | 1 - ...a6b5e5b14c.js => page-748a83a8e772a56b.js} | 2 +- ...be58b9d19c.js => page-884a15d08f8be397.js} | 2 +- ...d7217f38ce.js => page-a952da77e0730c7c.js} | 2 +- ...b53edf.js => main-app-096338c8e1915716.js} | 2 +- ...0c46e0b.js => webpack-b9c71b6f9761a436.js} | 2 +- ...56a1984d35914.css => ea3759ed931c00b2.css} | 2 +- .../static/media/05a31a2ca4975f99-s.woff2 | Bin 0 -> 10496 bytes .../static/media/26a46d62cd723877-s.woff2 | Bin 18820 -> 0 bytes .../static/media/513657b02c5c193f-s.woff2 | Bin 0 -> 17612 bytes .../static/media/51ed15f9841b9f9d-s.woff2 | Bin 0 -> 22524 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 25908 -> 0 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 19072 -> 0 bytes .../static/media/6d93bde91c0c2823-s.woff2 | Bin 74316 -> 0 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 11220 -> 0 bytes .../static/media/a34f9d1faa5f3315-s.p.woff2 | Bin 48556 -> 0 bytes .../static/media/c9a5bc6a7c948fb0-s.p.woff2 | Bin 0 -> 46552 bytes .../static/media/d6b16ce4a6175f26-s.woff2 | Bin 0 -> 80044 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 10280 -> 0 bytes .../static/media/ec159349637c90ad-s.woff2 | Bin 0 -> 27316 bytes .../static/media/fd4db3eb5472fc27-s.woff2 | Bin 0 -> 12768 bytes .../_buildManifest.js | 0 .../_ssgManifest.js | 0 litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 4 +-- .../proxy/_experimental/out/model_hub.html | 1 - litellm/proxy/_experimental/out/model_hub.txt | 4 +-- .../proxy/_experimental/out/onboarding.html | 1 - .../proxy/_experimental/out/onboarding.txt | 4 +-- ui/litellm-dashboard/out/404.html | 1 - .../WeMIGILYzOYN-R9DXbvCD/_buildManifest.js | 1 - .../WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js | 1 - .../static/chunks/131-3d2257b0ff5aadb2.js | 8 ----- .../chunks/2f6dbc85-cac2949a76539886.js | 1 - .../chunks/3014691f-b24e8254c7593934.js | 1 - .../static/chunks/626-4e8df4039ecf4386.js | 13 ------- .../static/chunks/684-16b194c83a169f6d.js | 1 - .../static/chunks/69-8316d07d1f41e39f.js | 1 - .../static/chunks/777-9d9df0b75010dbf9.js | 1 - .../static/chunks/902-58bf23027703b2e8.js | 13 ------- .../chunks/app/_not-found-4163791cb6a88df1.js | 1 - .../chunks/app/layout-61827b157521da1b.js | 1 - .../app/model_hub/page-104cada6b5e5b14c.js | 1 - .../app/onboarding/page-bad6cfbe58b9d19c.js | 1 - .../chunks/app/page-68b04cd7217f38ce.js | 1 - .../chunks/fd9d1056-f593049e31b05aeb.js | 1 - .../chunks/framework-b370f160bb96059c.js | 33 ------------------ .../static/chunks/main-a61244f130fbf565.js | 1 - .../chunks/main-app-9b4fb13a7db53edf.js | 1 - .../chunks/pages/_app-d21e88acd55d90f1.js | 1 - .../chunks/pages/_error-d6107f1aac0c574c.js | 1 - .../chunks/polyfills-c67a75d1b6f99dc8.js | 1 - .../static/chunks/webpack-e8ad0a25b0c46e0b.js | 1 - .../out/_next/static/css/00256a1984d35914.css | 5 --- .../static/media/26a46d62cd723877-s.woff2 | Bin 18820 -> 0 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 25908 -> 0 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 19072 -> 0 bytes .../static/media/6d93bde91c0c2823-s.woff2 | Bin 74316 -> 0 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 11220 -> 0 bytes .../static/media/a34f9d1faa5f3315-s.p.woff2 | Bin 48556 -> 0 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 10280 -> 0 bytes ui/litellm-dashboard/out/favicon.ico | Bin 15406 -> 0 bytes ui/litellm-dashboard/out/index.html | 1 - ui/litellm-dashboard/out/index.txt | 7 ---- ui/litellm-dashboard/out/model_hub.html | 1 - ui/litellm-dashboard/out/model_hub.txt | 7 ---- ui/litellm-dashboard/out/next.svg | 1 - ui/litellm-dashboard/out/onboarding.html | 1 - ui/litellm-dashboard/out/onboarding.txt | 7 ---- ui/litellm-dashboard/out/vercel.svg | 1 - 77 files changed, 35 insertions(+), 156 deletions(-) delete mode 100644 litellm/proxy/_experimental/out/404.html delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/131-3d2257b0ff5aadb2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/131-4ee1d633e8928742.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/626-0c564a21577c9c53.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/626-4e8df4039ecf4386.js rename litellm/proxy/_experimental/out/_next/static/chunks/{902-58bf23027703b2e8.js => 902-292bb6a83427dbc7.js} (100%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-05e5448bd170dbcb.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-61827b157521da1b.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/{page-104cada6b5e5b14c.js => page-748a83a8e772a56b.js} (97%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/{page-bad6cfbe58b9d19c.js => page-884a15d08f8be397.js} (94%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/{page-68b04cd7217f38ce.js => page-a952da77e0730c7c.js} (51%) rename litellm/proxy/_experimental/out/_next/static/chunks/{main-app-9b4fb13a7db53edf.js => main-app-096338c8e1915716.js} (54%) rename litellm/proxy/_experimental/out/_next/static/chunks/{webpack-e8ad0a25b0c46e0b.js => webpack-b9c71b6f9761a436.js} (98%) rename litellm/proxy/_experimental/out/_next/static/css/{00256a1984d35914.css => ea3759ed931c00b2.css} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/media/05a31a2ca4975f99-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/26a46d62cd723877-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/513657b02c5c193f-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/51ed15f9841b9f9d-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/55c55f0601d81cf3-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/581909926a08bbc8-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/6d93bde91c0c2823-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/a34f9d1faa5f3315-s.p.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/d6b16ce4a6175f26-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/df0a9ae256c0569c-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/ec159349637c90ad-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/fd4db3eb5472fc27-s.woff2 rename litellm/proxy/_experimental/out/_next/static/{WeMIGILYzOYN-R9DXbvCD => pDx3dChtj-paUmJExuV6u}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{WeMIGILYzOYN-R9DXbvCD => pDx3dChtj-paUmJExuV6u}/_ssgManifest.js (100%) delete mode 100644 litellm/proxy/_experimental/out/model_hub.html delete mode 100644 litellm/proxy/_experimental/out/onboarding.html delete mode 100644 ui/litellm-dashboard/out/404.html delete mode 100644 ui/litellm-dashboard/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_buildManifest.js delete mode 100644 ui/litellm-dashboard/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/131-3d2257b0ff5aadb2.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/2f6dbc85-cac2949a76539886.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/3014691f-b24e8254c7593934.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/626-4e8df4039ecf4386.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/684-16b194c83a169f6d.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/69-8316d07d1f41e39f.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/777-9d9df0b75010dbf9.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/902-58bf23027703b2e8.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/_not-found-4163791cb6a88df1.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/layout-61827b157521da1b.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/model_hub/page-104cada6b5e5b14c.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/onboarding/page-bad6cfbe58b9d19c.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-68b04cd7217f38ce.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/fd9d1056-f593049e31b05aeb.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/framework-b370f160bb96059c.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/main-a61244f130fbf565.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/main-app-9b4fb13a7db53edf.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/pages/_app-d21e88acd55d90f1.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/pages/_error-d6107f1aac0c574c.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/webpack-e8ad0a25b0c46e0b.js delete mode 100644 ui/litellm-dashboard/out/_next/static/css/00256a1984d35914.css delete mode 100644 ui/litellm-dashboard/out/_next/static/media/26a46d62cd723877-s.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/55c55f0601d81cf3-s.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/581909926a08bbc8-s.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/6d93bde91c0c2823-s.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/a34f9d1faa5f3315-s.p.woff2 delete mode 100644 ui/litellm-dashboard/out/_next/static/media/df0a9ae256c0569c-s.woff2 delete mode 100644 ui/litellm-dashboard/out/favicon.ico delete mode 100644 ui/litellm-dashboard/out/index.html delete mode 100644 ui/litellm-dashboard/out/index.txt delete mode 100644 ui/litellm-dashboard/out/model_hub.html delete mode 100644 ui/litellm-dashboard/out/model_hub.txt delete mode 100644 ui/litellm-dashboard/out/next.svg delete mode 100644 ui/litellm-dashboard/out/onboarding.html delete mode 100644 ui/litellm-dashboard/out/onboarding.txt delete mode 100644 ui/litellm-dashboard/out/vercel.svg diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html deleted file mode 100644 index 223f5d80e..000000000 --- a/litellm/proxy/_experimental/out/404.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/131-3d2257b0ff5aadb2.js b/litellm/proxy/_experimental/out/_next/static/chunks/131-3d2257b0ff5aadb2.js deleted file mode 100644 index 51181e75a..000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/131-3d2257b0ff5aadb2.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[131],{84174:function(e,t,n){n.d(t,{Z:function(){return s}});var a=n(14749),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"}}]},name:"copy",theme:"outlined"},o=n(60688),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},50459:function(e,t,n){n.d(t,{Z:function(){return s}});var a=n(14749),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"},o=n(60688),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},92836:function(e,t,n){n.d(t,{Z:function(){return p}});var a=n(69703),r=n(80991),i=n(2898),o=n(99250),s=n(65492),l=n(2265),c=n(41608),d=n(50027);n(18174),n(21871),n(41213);let u=(0,s.fn)("Tab"),p=l.forwardRef((e,t)=>{let{icon:n,className:p,children:g}=e,m=(0,a._T)(e,["icon","className","children"]),b=(0,l.useContext)(c.O),f=(0,l.useContext)(d.Z);return l.createElement(r.O,Object.assign({ref:t,className:(0,o.q)(u("root"),"flex whitespace-nowrap truncate max-w-xs outline-none focus:ring-0 text-tremor-default transition duration-100",f?(0,s.bM)(f,i.K.text).selectTextColor:"solid"===b?"ui-selected:text-tremor-content-emphasis dark:ui-selected:text-dark-tremor-content-emphasis":"ui-selected:text-tremor-brand dark:ui-selected:text-dark-tremor-brand",function(e,t){switch(e){case"line":return(0,o.q)("ui-selected:border-b-2 hover:border-b-2 border-transparent transition duration-100 -mb-px px-2 py-2","hover:border-tremor-content hover:text-tremor-content-emphasis text-tremor-content","dark:hover:border-dark-tremor-content-emphasis dark:hover:text-dark-tremor-content-emphasis dark:text-dark-tremor-content",t?(0,s.bM)(t,i.K.border).selectBorderColor:"ui-selected:border-tremor-brand dark:ui-selected:border-dark-tremor-brand");case"solid":return(0,o.q)("border-transparent border rounded-tremor-small px-2.5 py-1","ui-selected:border-tremor-border ui-selected:bg-tremor-background ui-selected:shadow-tremor-input hover:text-tremor-content-emphasis ui-selected:text-tremor-brand","dark:ui-selected:border-dark-tremor-border dark:ui-selected:bg-dark-tremor-background dark:ui-selected:shadow-dark-tremor-input dark:hover:text-dark-tremor-content-emphasis dark:ui-selected:text-dark-tremor-brand",t?(0,s.bM)(t,i.K.text).selectTextColor:"text-tremor-content dark:text-dark-tremor-content")}}(b,f),p)},m),n?l.createElement(n,{className:(0,o.q)(u("icon"),"flex-none h-5 w-5",g?"mr-2":"")}):null,g?l.createElement("span",null,g):null)});p.displayName="Tab"},26734:function(e,t,n){n.d(t,{Z:function(){return c}});var a=n(69703),r=n(80991),i=n(99250),o=n(65492),s=n(2265);let l=(0,o.fn)("TabGroup"),c=s.forwardRef((e,t)=>{let{defaultIndex:n,index:o,onIndexChange:c,children:d,className:u}=e,p=(0,a._T)(e,["defaultIndex","index","onIndexChange","children","className"]);return s.createElement(r.O.Group,Object.assign({as:"div",ref:t,defaultIndex:n,selectedIndex:o,onChange:c,className:(0,i.q)(l("root"),"w-full",u)},p),d)});c.displayName="TabGroup"},41608:function(e,t,n){n.d(t,{O:function(){return c},Z:function(){return u}});var a=n(69703),r=n(2265),i=n(50027);n(18174),n(21871),n(41213);var o=n(80991),s=n(99250);let l=(0,n(65492).fn)("TabList"),c=(0,r.createContext)("line"),d={line:(0,s.q)("flex border-b space-x-4","border-tremor-border","dark:border-dark-tremor-border"),solid:(0,s.q)("inline-flex p-0.5 rounded-tremor-default space-x-1.5","bg-tremor-background-subtle","dark:bg-dark-tremor-background-subtle")},u=r.forwardRef((e,t)=>{let{color:n,variant:u="line",children:p,className:g}=e,m=(0,a._T)(e,["color","variant","children","className"]);return r.createElement(o.O.List,Object.assign({ref:t,className:(0,s.q)(l("root"),"justify-start overflow-x-clip",d[u],g)},m),r.createElement(c.Provider,{value:u},r.createElement(i.Z.Provider,{value:n},p)))});u.displayName="TabList"},32126:function(e,t,n){n.d(t,{Z:function(){return d}});var a=n(69703);n(50027);var r=n(18174);n(21871);var i=n(41213),o=n(99250),s=n(65492),l=n(2265);let c=(0,s.fn)("TabPanel"),d=l.forwardRef((e,t)=>{let{children:n,className:s}=e,d=(0,a._T)(e,["children","className"]),{selectedValue:u}=(0,l.useContext)(i.Z),p=u===(0,l.useContext)(r.Z);return l.createElement("div",Object.assign({ref:t,className:(0,o.q)(c("root"),"w-full mt-2",p?"":"hidden",s),"aria-selected":p?"true":"false"},d),n)});d.displayName="TabPanel"},23682:function(e,t,n){n.d(t,{Z:function(){return u}});var a=n(69703),r=n(80991);n(50027);var i=n(18174);n(21871);var o=n(41213),s=n(99250),l=n(65492),c=n(2265);let d=(0,l.fn)("TabPanels"),u=c.forwardRef((e,t)=>{let{children:n,className:l}=e,u=(0,a._T)(e,["children","className"]);return c.createElement(r.O.Panels,Object.assign({as:"div",ref:t,className:(0,s.q)(d("root"),"w-full",l)},u),e=>{let{selectedIndex:t}=e;return c.createElement(o.Z.Provider,{value:{selectedValue:t}},c.Children.map(n,(e,t)=>c.createElement(i.Z.Provider,{value:t},e)))})});u.displayName="TabPanels"},50027:function(e,t,n){n.d(t,{Z:function(){return i}});var a=n(2265),r=n(54942);n(99250);let i=(0,a.createContext)(r.fr.Blue)},18174:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(0)},21871:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(void 0)},41213:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},21467:function(e,t,n){n.d(t,{i:function(){return s}});var a=n(2265),r=n(44329),i=n(54165),o=n(57499);function s(e){return t=>a.createElement(i.ZP,{theme:{token:{motion:!1,zIndexPopupBase:0}}},a.createElement(e,Object.assign({},t)))}t.Z=(e,t,n,i)=>s(s=>{let{prefixCls:l,style:c}=s,d=a.useRef(null),[u,p]=a.useState(0),[g,m]=a.useState(0),[b,f]=(0,r.Z)(!1,{value:s.open}),{getPrefixCls:E}=a.useContext(o.E_),h=E(t||"select",l);a.useEffect(()=>{if(f(!0),"undefined"!=typeof ResizeObserver){let e=new ResizeObserver(e=>{let t=e[0].target;p(t.offsetHeight+8),m(t.offsetWidth)}),t=setInterval(()=>{var a;let r=n?".".concat(n(h)):".".concat(h,"-dropdown"),i=null===(a=d.current)||void 0===a?void 0:a.querySelector(r);i&&(clearInterval(t),e.observe(i))},10);return()=>{clearInterval(t),e.disconnect()}}},[]);let S=Object.assign(Object.assign({},s),{style:Object.assign(Object.assign({},c),{margin:0}),open:b,visible:b,getPopupContainer:()=>d.current});return i&&(S=i(S)),a.createElement("div",{ref:d,style:{paddingBottom:u,position:"relative",minWidth:g}},a.createElement(e,Object.assign({},S)))})},99129:function(e,t,n){let a;n.d(t,{Z:function(){return eY}});var r=n(63787),i=n(2265),o=n(37274),s=n(57499),l=n(54165),c=n(99537),d=n(77136),u=n(20653),p=n(40388),g=n(16480),m=n.n(g),b=n(51761),f=n(47387),E=n(70595),h=n(24750),S=n(89211),y=n(13565),T=n(51350),A=e=>{let{type:t,children:n,prefixCls:a,buttonProps:r,close:o,autoFocus:s,emitEvent:l,isSilent:c,quitOnNullishReturnValue:d,actionFn:u}=e,p=i.useRef(!1),g=i.useRef(null),[m,b]=(0,S.Z)(!1),f=function(){null==o||o.apply(void 0,arguments)};i.useEffect(()=>{let e=null;return s&&(e=setTimeout(()=>{var e;null===(e=g.current)||void 0===e||e.focus()})),()=>{e&&clearTimeout(e)}},[]);let E=e=>{e&&e.then&&(b(!0),e.then(function(){b(!1,!0),f.apply(void 0,arguments),p.current=!1},e=>{if(b(!1,!0),p.current=!1,null==c||!c())return Promise.reject(e)}))};return i.createElement(y.ZP,Object.assign({},(0,T.nx)(t),{onClick:e=>{let t;if(!p.current){if(p.current=!0,!u){f();return}if(l){var n;if(t=u(e),d&&!((n=t)&&n.then)){p.current=!1,f(e);return}}else if(u.length)t=u(o),p.current=!1;else if(!(t=u())){f();return}E(t)}},loading:m,prefixCls:a},r,{ref:g}),n)};let R=i.createContext({}),{Provider:I}=R;var N=()=>{let{autoFocusButton:e,cancelButtonProps:t,cancelTextLocale:n,isSilent:a,mergedOkCancel:r,rootPrefixCls:o,close:s,onCancel:l,onConfirm:c}=(0,i.useContext)(R);return r?i.createElement(A,{isSilent:a,actionFn:l,close:function(){null==s||s.apply(void 0,arguments),null==c||c(!1)},autoFocus:"cancel"===e,buttonProps:t,prefixCls:"".concat(o,"-btn")},n):null},_=()=>{let{autoFocusButton:e,close:t,isSilent:n,okButtonProps:a,rootPrefixCls:r,okTextLocale:o,okType:s,onConfirm:l,onOk:c}=(0,i.useContext)(R);return i.createElement(A,{isSilent:n,type:s||"primary",actionFn:c,close:function(){null==t||t.apply(void 0,arguments),null==l||l(!0)},autoFocus:"ok"===e,buttonProps:a,prefixCls:"".concat(r,"-btn")},o)},v=n(81303),w=n(14749),k=n(80406),C=n(88804),O=i.createContext({}),x=n(5239),L=n(31506),D=n(91010),P=n(4295),M=n(72480);function F(e,t,n){var a=t;return!a&&n&&(a="".concat(e,"-").concat(n)),a}function U(e,t){var n=e["page".concat(t?"Y":"X","Offset")],a="scroll".concat(t?"Top":"Left");if("number"!=typeof n){var r=e.document;"number"!=typeof(n=r.documentElement[a])&&(n=r.body[a])}return n}var B=n(49367),G=n(74084),$=i.memo(function(e){return e.children},function(e,t){return!t.shouldUpdate}),H={width:0,height:0,overflow:"hidden",outline:"none"},z=i.forwardRef(function(e,t){var n,a,r,o=e.prefixCls,s=e.className,l=e.style,c=e.title,d=e.ariaId,u=e.footer,p=e.closable,g=e.closeIcon,b=e.onClose,f=e.children,E=e.bodyStyle,h=e.bodyProps,S=e.modalRender,y=e.onMouseDown,T=e.onMouseUp,A=e.holderRef,R=e.visible,I=e.forceRender,N=e.width,_=e.height,v=e.classNames,k=e.styles,C=i.useContext(O).panel,L=(0,G.x1)(A,C),D=(0,i.useRef)(),P=(0,i.useRef)();i.useImperativeHandle(t,function(){return{focus:function(){var e;null===(e=D.current)||void 0===e||e.focus()},changeActive:function(e){var t=document.activeElement;e&&t===P.current?D.current.focus():e||t!==D.current||P.current.focus()}}});var M={};void 0!==N&&(M.width=N),void 0!==_&&(M.height=_),u&&(n=i.createElement("div",{className:m()("".concat(o,"-footer"),null==v?void 0:v.footer),style:(0,x.Z)({},null==k?void 0:k.footer)},u)),c&&(a=i.createElement("div",{className:m()("".concat(o,"-header"),null==v?void 0:v.header),style:(0,x.Z)({},null==k?void 0:k.header)},i.createElement("div",{className:"".concat(o,"-title"),id:d},c))),p&&(r=i.createElement("button",{type:"button",onClick:b,"aria-label":"Close",className:"".concat(o,"-close")},g||i.createElement("span",{className:"".concat(o,"-close-x")})));var F=i.createElement("div",{className:m()("".concat(o,"-content"),null==v?void 0:v.content),style:null==k?void 0:k.content},r,a,i.createElement("div",(0,w.Z)({className:m()("".concat(o,"-body"),null==v?void 0:v.body),style:(0,x.Z)((0,x.Z)({},E),null==k?void 0:k.body)},h),f),n);return i.createElement("div",{key:"dialog-element",role:"dialog","aria-labelledby":c?d:null,"aria-modal":"true",ref:L,style:(0,x.Z)((0,x.Z)({},l),M),className:m()(o,s),onMouseDown:y,onMouseUp:T},i.createElement("div",{tabIndex:0,ref:D,style:H,"aria-hidden":"true"}),i.createElement($,{shouldUpdate:R||I},S?S(F):F),i.createElement("div",{tabIndex:0,ref:P,style:H,"aria-hidden":"true"}))}),j=i.forwardRef(function(e,t){var n=e.prefixCls,a=e.title,r=e.style,o=e.className,s=e.visible,l=e.forceRender,c=e.destroyOnClose,d=e.motionName,u=e.ariaId,p=e.onVisibleChanged,g=e.mousePosition,b=(0,i.useRef)(),f=i.useState(),E=(0,k.Z)(f,2),h=E[0],S=E[1],y={};function T(){var e,t,n,a,r,i=(n={left:(t=(e=b.current).getBoundingClientRect()).left,top:t.top},r=(a=e.ownerDocument).defaultView||a.parentWindow,n.left+=U(r),n.top+=U(r,!0),n);S(g?"".concat(g.x-i.left,"px ").concat(g.y-i.top,"px"):"")}return h&&(y.transformOrigin=h),i.createElement(B.ZP,{visible:s,onVisibleChanged:p,onAppearPrepare:T,onEnterPrepare:T,forceRender:l,motionName:d,removeOnLeave:c,ref:b},function(s,l){var c=s.className,d=s.style;return i.createElement(z,(0,w.Z)({},e,{ref:t,title:a,ariaId:u,prefixCls:n,holderRef:l,style:(0,x.Z)((0,x.Z)((0,x.Z)({},d),r),y),className:m()(o,c)}))})});function V(e){var t=e.prefixCls,n=e.style,a=e.visible,r=e.maskProps,o=e.motionName,s=e.className;return i.createElement(B.ZP,{key:"mask",visible:a,motionName:o,leavedClassName:"".concat(t,"-mask-hidden")},function(e,a){var o=e.className,l=e.style;return i.createElement("div",(0,w.Z)({ref:a,style:(0,x.Z)((0,x.Z)({},l),n),className:m()("".concat(t,"-mask"),o,s)},r))})}function W(e){var t=e.prefixCls,n=void 0===t?"rc-dialog":t,a=e.zIndex,r=e.visible,o=void 0!==r&&r,s=e.keyboard,l=void 0===s||s,c=e.focusTriggerAfterClose,d=void 0===c||c,u=e.wrapStyle,p=e.wrapClassName,g=e.wrapProps,b=e.onClose,f=e.afterOpenChange,E=e.afterClose,h=e.transitionName,S=e.animation,y=e.closable,T=e.mask,A=void 0===T||T,R=e.maskTransitionName,I=e.maskAnimation,N=e.maskClosable,_=e.maskStyle,v=e.maskProps,C=e.rootClassName,O=e.classNames,U=e.styles,B=(0,i.useRef)(),G=(0,i.useRef)(),$=(0,i.useRef)(),H=i.useState(o),z=(0,k.Z)(H,2),W=z[0],q=z[1],Y=(0,D.Z)();function K(e){null==b||b(e)}var Z=(0,i.useRef)(!1),X=(0,i.useRef)(),Q=null;return(void 0===N||N)&&(Q=function(e){Z.current?Z.current=!1:G.current===e.target&&K(e)}),(0,i.useEffect)(function(){o&&(q(!0),(0,L.Z)(G.current,document.activeElement)||(B.current=document.activeElement))},[o]),(0,i.useEffect)(function(){return function(){clearTimeout(X.current)}},[]),i.createElement("div",(0,w.Z)({className:m()("".concat(n,"-root"),C)},(0,M.Z)(e,{data:!0})),i.createElement(V,{prefixCls:n,visible:A&&o,motionName:F(n,R,I),style:(0,x.Z)((0,x.Z)({zIndex:a},_),null==U?void 0:U.mask),maskProps:v,className:null==O?void 0:O.mask}),i.createElement("div",(0,w.Z)({tabIndex:-1,onKeyDown:function(e){if(l&&e.keyCode===P.Z.ESC){e.stopPropagation(),K(e);return}o&&e.keyCode===P.Z.TAB&&$.current.changeActive(!e.shiftKey)},className:m()("".concat(n,"-wrap"),p,null==O?void 0:O.wrapper),ref:G,onClick:Q,style:(0,x.Z)((0,x.Z)((0,x.Z)({zIndex:a},u),null==U?void 0:U.wrapper),{},{display:W?null:"none"})},g),i.createElement(j,(0,w.Z)({},e,{onMouseDown:function(){clearTimeout(X.current),Z.current=!0},onMouseUp:function(){X.current=setTimeout(function(){Z.current=!1})},ref:$,closable:void 0===y||y,ariaId:Y,prefixCls:n,visible:o&&W,onClose:K,onVisibleChanged:function(e){if(e)!function(){if(!(0,L.Z)(G.current,document.activeElement)){var e;null===(e=$.current)||void 0===e||e.focus()}}();else{if(q(!1),A&&B.current&&d){try{B.current.focus({preventScroll:!0})}catch(e){}B.current=null}W&&(null==E||E())}null==f||f(e)},motionName:F(n,h,S)}))))}j.displayName="Content",n(53850);var q=function(e){var t=e.visible,n=e.getContainer,a=e.forceRender,r=e.destroyOnClose,o=void 0!==r&&r,s=e.afterClose,l=e.panelRef,c=i.useState(t),d=(0,k.Z)(c,2),u=d[0],p=d[1],g=i.useMemo(function(){return{panel:l}},[l]);return(i.useEffect(function(){t&&p(!0)},[t]),a||!o||u)?i.createElement(O.Provider,{value:g},i.createElement(C.Z,{open:t||a||u,autoDestroy:!1,getContainer:n,autoLock:t||u},i.createElement(W,(0,w.Z)({},e,{destroyOnClose:o,afterClose:function(){null==s||s(),p(!1)}})))):null};q.displayName="Dialog";var Y=function(e,t,n){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i.createElement(v.Z,null),r=arguments.length>4&&void 0!==arguments[4]&&arguments[4];if("boolean"==typeof e?!e:void 0===t?!r:!1===t||null===t)return[!1,null];let o="boolean"==typeof t||null==t?a:t;return[!0,n?n(o):o]},K=n(22127),Z=n(86718),X=n(47137),Q=n(92801),J=n(48563);function ee(){}let et=i.createContext({add:ee,remove:ee});var en=n(17094),ea=()=>{let{cancelButtonProps:e,cancelTextLocale:t,onCancel:n}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({onClick:n},e),t)},er=()=>{let{confirmLoading:e,okButtonProps:t,okType:n,okTextLocale:a,onOk:r}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({},(0,T.nx)(n),{loading:e,onClick:r},t),a)},ei=n(4678);function eo(e,t){return i.createElement("span",{className:"".concat(e,"-close-x")},t||i.createElement(v.Z,{className:"".concat(e,"-close-icon")}))}let es=e=>{let t;let{okText:n,okType:a="primary",cancelText:o,confirmLoading:s,onOk:l,onCancel:c,okButtonProps:d,cancelButtonProps:u,footer:p}=e,[g]=(0,E.Z)("Modal",(0,ei.A)()),m={confirmLoading:s,okButtonProps:d,cancelButtonProps:u,okTextLocale:n||(null==g?void 0:g.okText),cancelTextLocale:o||(null==g?void 0:g.cancelText),okType:a,onOk:l,onCancel:c},b=i.useMemo(()=>m,(0,r.Z)(Object.values(m)));return"function"==typeof p||void 0===p?(t=i.createElement(i.Fragment,null,i.createElement(ea,null),i.createElement(er,null)),"function"==typeof p&&(t=p(t,{OkBtn:er,CancelBtn:ea})),t=i.createElement(I,{value:b},t)):t=p,i.createElement(en.n,{disabled:!1},t)};var el=n(11303),ec=n(13703),ed=n(58854),eu=n(80316),ep=n(76585),eg=n(8985);function em(e){return{position:e,inset:0}}let eb=e=>{let{componentCls:t,antCls:n}=e;return[{["".concat(t,"-root")]:{["".concat(t).concat(n,"-zoom-enter, ").concat(t).concat(n,"-zoom-appear")]:{transform:"none",opacity:0,animationDuration:e.motionDurationSlow,userSelect:"none"},["".concat(t).concat(n,"-zoom-leave ").concat(t,"-content")]:{pointerEvents:"none"},["".concat(t,"-mask")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,height:"100%",backgroundColor:e.colorBgMask,pointerEvents:"none",["".concat(t,"-hidden")]:{display:"none"}}),["".concat(t,"-wrap")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,overflow:"auto",outline:0,WebkitOverflowScrolling:"touch",["&:has(".concat(t).concat(n,"-zoom-enter), &:has(").concat(t).concat(n,"-zoom-appear)")]:{pointerEvents:"none"}})}},{["".concat(t,"-root")]:(0,ec.J$)(e)}]},ef=e=>{let{componentCls:t}=e;return[{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl"},["".concat(t,"-centered")]:{textAlign:"center","&::before":{display:"inline-block",width:0,height:"100%",verticalAlign:"middle",content:'""'},[t]:{top:0,display:"inline-block",paddingBottom:0,textAlign:"start",verticalAlign:"middle"}},["@media (max-width: ".concat(e.screenSMMax,"px)")]:{[t]:{maxWidth:"calc(100vw - 16px)",margin:"".concat((0,eg.bf)(e.marginXS)," auto")},["".concat(t,"-centered")]:{[t]:{flex:1}}}}},{[t]:Object.assign(Object.assign({},(0,el.Wf)(e)),{pointerEvents:"none",position:"relative",top:100,width:"auto",maxWidth:"calc(100vw - ".concat((0,eg.bf)(e.calc(e.margin).mul(2).equal()),")"),margin:"0 auto",paddingBottom:e.paddingLG,["".concat(t,"-title")]:{margin:0,color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.titleFontSize,lineHeight:e.titleLineHeight,wordWrap:"break-word"},["".concat(t,"-content")]:{position:"relative",backgroundColor:e.contentBg,backgroundClip:"padding-box",border:0,borderRadius:e.borderRadiusLG,boxShadow:e.boxShadow,pointerEvents:"auto",padding:e.contentPadding},["".concat(t,"-close")]:Object.assign({position:"absolute",top:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),insetInlineEnd:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),zIndex:e.calc(e.zIndexPopupBase).add(10).equal(),padding:0,color:e.modalCloseIconColor,fontWeight:e.fontWeightStrong,lineHeight:1,textDecoration:"none",background:"transparent",borderRadius:e.borderRadiusSM,width:e.modalCloseBtnSize,height:e.modalCloseBtnSize,border:0,outline:0,cursor:"pointer",transition:"color ".concat(e.motionDurationMid,", background-color ").concat(e.motionDurationMid),"&-x":{display:"flex",fontSize:e.fontSizeLG,fontStyle:"normal",lineHeight:"".concat((0,eg.bf)(e.modalCloseBtnSize)),justifyContent:"center",textTransform:"none",textRendering:"auto"},"&:hover":{color:e.modalIconHoverColor,backgroundColor:e.closeBtnHoverBg,textDecoration:"none"},"&:active":{backgroundColor:e.closeBtnActiveBg}},(0,el.Qy)(e)),["".concat(t,"-header")]:{color:e.colorText,background:e.headerBg,borderRadius:"".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)," 0 0"),marginBottom:e.headerMarginBottom,padding:e.headerPadding,borderBottom:e.headerBorderBottom},["".concat(t,"-body")]:{fontSize:e.fontSize,lineHeight:e.lineHeight,wordWrap:"break-word",padding:e.bodyPadding},["".concat(t,"-footer")]:{textAlign:"end",background:e.footerBg,marginTop:e.footerMarginTop,padding:e.footerPadding,borderTop:e.footerBorderTop,borderRadius:e.footerBorderRadius,["> ".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginInlineStart:e.marginXS}},["".concat(t,"-open")]:{overflow:"hidden"}})},{["".concat(t,"-pure-panel")]:{top:"auto",padding:0,display:"flex",flexDirection:"column",["".concat(t,"-content,\n ").concat(t,"-body,\n ").concat(t,"-confirm-body-wrapper")]:{display:"flex",flexDirection:"column",flex:"auto"},["".concat(t,"-confirm-body")]:{marginBottom:"auto"}}}]},eE=e=>{let{componentCls:t}=e;return{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl",["".concat(t,"-confirm-body")]:{direction:"rtl"}}}}},eh=e=>{let t=e.padding,n=e.fontSizeHeading5,a=e.lineHeightHeading5;return(0,eu.TS)(e,{modalHeaderHeight:e.calc(e.calc(a).mul(n).equal()).add(e.calc(t).mul(2).equal()).equal(),modalFooterBorderColorSplit:e.colorSplit,modalFooterBorderStyle:e.lineType,modalFooterBorderWidth:e.lineWidth,modalIconHoverColor:e.colorIconHover,modalCloseIconColor:e.colorIcon,modalCloseBtnSize:e.fontHeight,modalConfirmIconSize:e.fontHeight,modalTitleHeight:e.calc(e.titleFontSize).mul(e.titleLineHeight).equal()})},eS=e=>({footerBg:"transparent",headerBg:e.colorBgElevated,titleLineHeight:e.lineHeightHeading5,titleFontSize:e.fontSizeHeading5,contentBg:e.colorBgElevated,titleColor:e.colorTextHeading,closeBtnHoverBg:e.wireframe?"transparent":e.colorFillContent,closeBtnActiveBg:e.wireframe?"transparent":e.colorFillContentHover,contentPadding:e.wireframe?0:"".concat((0,eg.bf)(e.paddingMD)," ").concat((0,eg.bf)(e.paddingContentHorizontalLG)),headerPadding:e.wireframe?"".concat((0,eg.bf)(e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,headerBorderBottom:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",headerMarginBottom:e.wireframe?0:e.marginXS,bodyPadding:e.wireframe?e.paddingLG:0,footerPadding:e.wireframe?"".concat((0,eg.bf)(e.paddingXS)," ").concat((0,eg.bf)(e.padding)):0,footerBorderTop:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",footerBorderRadius:e.wireframe?"0 0 ".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)):0,footerMarginTop:e.wireframe?0:e.marginSM,confirmBodyPadding:e.wireframe?"".concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,confirmIconMarginInlineEnd:e.wireframe?e.margin:e.marginSM,confirmBtnsMarginTop:e.wireframe?e.marginLG:e.marginSM});var ey=(0,ep.I$)("Modal",e=>{let t=eh(e);return[ef(t),eE(t),eb(t),(0,ed._y)(t,"zoom")]},eS,{unitless:{titleLineHeight:!0}}),eT=n(92935),eA=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};(0,K.Z)()&&window.document.documentElement&&document.documentElement.addEventListener("click",e=>{a={x:e.pageX,y:e.pageY},setTimeout(()=>{a=null},100)},!0);var eR=e=>{var t;let{getPopupContainer:n,getPrefixCls:r,direction:o,modal:l}=i.useContext(s.E_),c=t=>{let{onCancel:n}=e;null==n||n(t)},{prefixCls:d,className:u,rootClassName:p,open:g,wrapClassName:E,centered:h,getContainer:S,closeIcon:y,closable:T,focusTriggerAfterClose:A=!0,style:R,visible:I,width:N=520,footer:_,classNames:w,styles:k}=e,C=eA(e,["prefixCls","className","rootClassName","open","wrapClassName","centered","getContainer","closeIcon","closable","focusTriggerAfterClose","style","visible","width","footer","classNames","styles"]),O=r("modal",d),x=r(),L=(0,eT.Z)(O),[D,P,M]=ey(O,L),F=m()(E,{["".concat(O,"-centered")]:!!h,["".concat(O,"-wrap-rtl")]:"rtl"===o}),U=null!==_&&i.createElement(es,Object.assign({},e,{onOk:t=>{let{onOk:n}=e;null==n||n(t)},onCancel:c})),[B,G]=Y(T,y,e=>eo(O,e),i.createElement(v.Z,{className:"".concat(O,"-close-icon")}),!0),$=function(e){let t=i.useContext(et),n=i.useRef();return(0,J.zX)(a=>{if(a){let r=e?a.querySelector(e):a;t.add(r),n.current=r}else t.remove(n.current)})}(".".concat(O,"-content")),[H,z]=(0,b.Cn)("Modal",C.zIndex);return D(i.createElement(Q.BR,null,i.createElement(X.Ux,{status:!0,override:!0},i.createElement(Z.Z.Provider,{value:z},i.createElement(q,Object.assign({width:N},C,{zIndex:H,getContainer:void 0===S?n:S,prefixCls:O,rootClassName:m()(P,p,M,L),footer:U,visible:null!=g?g:I,mousePosition:null!==(t=C.mousePosition)&&void 0!==t?t:a,onClose:c,closable:B,closeIcon:G,focusTriggerAfterClose:A,transitionName:(0,f.m)(x,"zoom",e.transitionName),maskTransitionName:(0,f.m)(x,"fade",e.maskTransitionName),className:m()(P,u,null==l?void 0:l.className),style:Object.assign(Object.assign({},null==l?void 0:l.style),R),classNames:Object.assign(Object.assign({wrapper:F},null==l?void 0:l.classNames),w),styles:Object.assign(Object.assign({},null==l?void 0:l.styles),k),panelRef:$}))))))};let eI=e=>{let{componentCls:t,titleFontSize:n,titleLineHeight:a,modalConfirmIconSize:r,fontSize:i,lineHeight:o,modalTitleHeight:s,fontHeight:l,confirmBodyPadding:c}=e,d="".concat(t,"-confirm");return{[d]:{"&-rtl":{direction:"rtl"},["".concat(e.antCls,"-modal-header")]:{display:"none"},["".concat(d,"-body-wrapper")]:Object.assign({},(0,el.dF)()),["&".concat(t," ").concat(t,"-body")]:{padding:c},["".concat(d,"-body")]:{display:"flex",flexWrap:"nowrap",alignItems:"start",["> ".concat(e.iconCls)]:{flex:"none",fontSize:r,marginInlineEnd:e.confirmIconMarginInlineEnd,marginTop:e.calc(e.calc(l).sub(r).equal()).div(2).equal()},["&-has-title > ".concat(e.iconCls)]:{marginTop:e.calc(e.calc(s).sub(r).equal()).div(2).equal()}},["".concat(d,"-paragraph")]:{display:"flex",flexDirection:"column",flex:"auto",rowGap:e.marginXS,maxWidth:"calc(100% - ".concat((0,eg.bf)(e.calc(e.modalConfirmIconSize).add(e.marginSM).equal()),")")},["".concat(d,"-title")]:{color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:n,lineHeight:a},["".concat(d,"-content")]:{color:e.colorText,fontSize:i,lineHeight:o},["".concat(d,"-btns")]:{textAlign:"end",marginTop:e.confirmBtnsMarginTop,["".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginBottom:0,marginInlineStart:e.marginXS}}},["".concat(d,"-error ").concat(d,"-body > ").concat(e.iconCls)]:{color:e.colorError},["".concat(d,"-warning ").concat(d,"-body > ").concat(e.iconCls,",\n ").concat(d,"-confirm ").concat(d,"-body > ").concat(e.iconCls)]:{color:e.colorWarning},["".concat(d,"-info ").concat(d,"-body > ").concat(e.iconCls)]:{color:e.colorInfo},["".concat(d,"-success ").concat(d,"-body > ").concat(e.iconCls)]:{color:e.colorSuccess}}};var eN=(0,ep.bk)(["Modal","confirm"],e=>[eI(eh(e))],eS,{order:-1e3}),e_=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};function ev(e){let{prefixCls:t,icon:n,okText:a,cancelText:o,confirmPrefixCls:s,type:l,okCancel:g,footer:b,locale:f}=e,h=e_(e,["prefixCls","icon","okText","cancelText","confirmPrefixCls","type","okCancel","footer","locale"]),S=n;if(!n&&null!==n)switch(l){case"info":S=i.createElement(p.Z,null);break;case"success":S=i.createElement(c.Z,null);break;case"error":S=i.createElement(d.Z,null);break;default:S=i.createElement(u.Z,null)}let y=null!=g?g:"confirm"===l,T=null!==e.autoFocusButton&&(e.autoFocusButton||"ok"),[A]=(0,E.Z)("Modal"),R=f||A,v=a||(y?null==R?void 0:R.okText:null==R?void 0:R.justOkText),w=Object.assign({autoFocusButton:T,cancelTextLocale:o||(null==R?void 0:R.cancelText),okTextLocale:v,mergedOkCancel:y},h),k=i.useMemo(()=>w,(0,r.Z)(Object.values(w))),C=i.createElement(i.Fragment,null,i.createElement(N,null),i.createElement(_,null)),O=void 0!==e.title&&null!==e.title,x="".concat(s,"-body");return i.createElement("div",{className:"".concat(s,"-body-wrapper")},i.createElement("div",{className:m()(x,{["".concat(x,"-has-title")]:O})},S,i.createElement("div",{className:"".concat(s,"-paragraph")},O&&i.createElement("span",{className:"".concat(s,"-title")},e.title),i.createElement("div",{className:"".concat(s,"-content")},e.content))),void 0===b||"function"==typeof b?i.createElement(I,{value:k},i.createElement("div",{className:"".concat(s,"-btns")},"function"==typeof b?b(C,{OkBtn:_,CancelBtn:N}):C)):b,i.createElement(eN,{prefixCls:t}))}let ew=e=>{let{close:t,zIndex:n,afterClose:a,open:r,keyboard:o,centered:s,getContainer:l,maskStyle:c,direction:d,prefixCls:u,wrapClassName:p,rootPrefixCls:g,bodyStyle:E,closable:S=!1,closeIcon:y,modalRender:T,focusTriggerAfterClose:A,onConfirm:R,styles:I}=e,N="".concat(u,"-confirm"),_=e.width||416,v=e.style||{},w=void 0===e.mask||e.mask,k=void 0!==e.maskClosable&&e.maskClosable,C=m()(N,"".concat(N,"-").concat(e.type),{["".concat(N,"-rtl")]:"rtl"===d},e.className),[,O]=(0,h.ZP)(),x=i.useMemo(()=>void 0!==n?n:O.zIndexPopupBase+b.u6,[n,O]);return i.createElement(eR,{prefixCls:u,className:C,wrapClassName:m()({["".concat(N,"-centered")]:!!e.centered},p),onCancel:()=>{null==t||t({triggerCancel:!0}),null==R||R(!1)},open:r,title:"",footer:null,transitionName:(0,f.m)(g||"","zoom",e.transitionName),maskTransitionName:(0,f.m)(g||"","fade",e.maskTransitionName),mask:w,maskClosable:k,style:v,styles:Object.assign({body:E,mask:c},I),width:_,zIndex:x,afterClose:a,keyboard:o,centered:s,getContainer:l,closable:S,closeIcon:y,modalRender:T,focusTriggerAfterClose:A},i.createElement(ev,Object.assign({},e,{confirmPrefixCls:N})))};var ek=e=>{let{rootPrefixCls:t,iconPrefixCls:n,direction:a,theme:r}=e;return i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:n,direction:a,theme:r},i.createElement(ew,Object.assign({},e)))},eC=[];let eO="",ex=e=>{var t,n;let{prefixCls:a,getContainer:r,direction:o}=e,l=(0,ei.A)(),c=(0,i.useContext)(s.E_),d=eO||c.getPrefixCls(),u=a||"".concat(d,"-modal"),p=r;return!1===p&&(p=void 0),i.createElement(ek,Object.assign({},e,{rootPrefixCls:d,prefixCls:u,iconPrefixCls:c.iconPrefixCls,theme:c.theme,direction:null!=o?o:c.direction,locale:null!==(n=null===(t=c.locale)||void 0===t?void 0:t.Modal)&&void 0!==n?n:l,getContainer:p}))};function eL(e){let t;let n=(0,l.w6)(),a=document.createDocumentFragment(),s=Object.assign(Object.assign({},e),{close:u,open:!0});function c(){for(var t=arguments.length,n=Array(t),i=0;ie&&e.triggerCancel);e.onCancel&&s&&e.onCancel.apply(e,[()=>{}].concat((0,r.Z)(n.slice(1))));for(let e=0;e{let t=n.getPrefixCls(void 0,eO),r=n.getIconPrefixCls(),s=n.getTheme(),c=i.createElement(ex,Object.assign({},e));(0,o.s)(i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:r,theme:s},n.holderRender?n.holderRender(c):c),a)})}function u(){for(var t=arguments.length,n=Array(t),a=0;a{"function"==typeof e.afterClose&&e.afterClose(),c.apply(this,n)}})).visible&&delete s.visible,d(s)}return d(s),eC.push(u),{destroy:u,update:function(e){d(s="function"==typeof e?e(s):Object.assign(Object.assign({},s),e))}}}function eD(e){return Object.assign(Object.assign({},e),{type:"warning"})}function eP(e){return Object.assign(Object.assign({},e),{type:"info"})}function eM(e){return Object.assign(Object.assign({},e),{type:"success"})}function eF(e){return Object.assign(Object.assign({},e),{type:"error"})}function eU(e){return Object.assign(Object.assign({},e),{type:"confirm"})}var eB=n(21467),eG=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},e$=(0,eB.i)(e=>{let{prefixCls:t,className:n,closeIcon:a,closable:r,type:o,title:l,children:c,footer:d}=e,u=eG(e,["prefixCls","className","closeIcon","closable","type","title","children","footer"]),{getPrefixCls:p}=i.useContext(s.E_),g=p(),b=t||p("modal"),f=(0,eT.Z)(g),[E,h,S]=ey(b,f),y="".concat(b,"-confirm"),T={};return T=o?{closable:null!=r&&r,title:"",footer:"",children:i.createElement(ev,Object.assign({},e,{prefixCls:b,confirmPrefixCls:y,rootPrefixCls:g,content:c}))}:{closable:null==r||r,title:l,footer:null!==d&&i.createElement(es,Object.assign({},e)),children:c},E(i.createElement(z,Object.assign({prefixCls:b,className:m()(h,"".concat(b,"-pure-panel"),o&&y,o&&"".concat(y,"-").concat(o),n,S,f)},u,{closeIcon:eo(b,a),closable:r},T)))}),eH=n(79474),ez=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},ej=i.forwardRef((e,t)=>{var n,{afterClose:a,config:o}=e,l=ez(e,["afterClose","config"]);let[c,d]=i.useState(!0),[u,p]=i.useState(o),{direction:g,getPrefixCls:m}=i.useContext(s.E_),b=m("modal"),f=m(),h=function(){d(!1);for(var e=arguments.length,t=Array(e),n=0;ne&&e.triggerCancel);u.onCancel&&a&&u.onCancel.apply(u,[()=>{}].concat((0,r.Z)(t.slice(1))))};i.useImperativeHandle(t,()=>({destroy:h,update:e=>{p(t=>Object.assign(Object.assign({},t),e))}}));let S=null!==(n=u.okCancel)&&void 0!==n?n:"confirm"===u.type,[y]=(0,E.Z)("Modal",eH.Z.Modal);return i.createElement(ek,Object.assign({prefixCls:b,rootPrefixCls:f},u,{close:h,open:c,afterClose:()=>{var e;a(),null===(e=u.afterClose)||void 0===e||e.call(u)},okText:u.okText||(S?null==y?void 0:y.okText:null==y?void 0:y.justOkText),direction:u.direction||g,cancelText:u.cancelText||(null==y?void 0:y.cancelText)},l))});let eV=0,eW=i.memo(i.forwardRef((e,t)=>{let[n,a]=function(){let[e,t]=i.useState([]);return[e,i.useCallback(e=>(t(t=>[].concat((0,r.Z)(t),[e])),()=>{t(t=>t.filter(t=>t!==e))}),[])]}();return i.useImperativeHandle(t,()=>({patchElement:a}),[]),i.createElement(i.Fragment,null,n)}));function eq(e){return eL(eD(e))}eR.useModal=function(){let e=i.useRef(null),[t,n]=i.useState([]);i.useEffect(()=>{t.length&&((0,r.Z)(t).forEach(e=>{e()}),n([]))},[t]);let a=i.useCallback(t=>function(a){var o;let s,l;eV+=1;let c=i.createRef(),d=new Promise(e=>{s=e}),u=!1,p=i.createElement(ej,{key:"modal-".concat(eV),config:t(a),ref:c,afterClose:()=>{null==l||l()},isSilent:()=>u,onConfirm:e=>{s(e)}});return(l=null===(o=e.current)||void 0===o?void 0:o.patchElement(p))&&eC.push(l),{destroy:()=>{function e(){var e;null===(e=c.current)||void 0===e||e.destroy()}c.current?e():n(t=>[].concat((0,r.Z)(t),[e]))},update:e=>{function t(){var t;null===(t=c.current)||void 0===t||t.update(e)}c.current?t():n(e=>[].concat((0,r.Z)(e),[t]))},then:e=>(u=!0,d.then(e))}},[]);return[i.useMemo(()=>({info:a(eP),success:a(eM),error:a(eF),warning:a(eD),confirm:a(eU)}),[]),i.createElement(eW,{key:"modal-holder",ref:e})]},eR.info=function(e){return eL(eP(e))},eR.success=function(e){return eL(eM(e))},eR.error=function(e){return eL(eF(e))},eR.warning=eq,eR.warn=eq,eR.confirm=function(e){return eL(eU(e))},eR.destroyAll=function(){for(;eC.length;){let e=eC.pop();e&&e()}},eR.config=function(e){let{rootPrefixCls:t}=e;eO=t},eR._InternalPanelDoNotUseOrYouWillBeFired=e$;var eY=eR},13703:function(e,t,n){n.d(t,{J$:function(){return s}});var a=n(8985),r=n(59353);let i=new a.E4("antFadeIn",{"0%":{opacity:0},"100%":{opacity:1}}),o=new a.E4("antFadeOut",{"0%":{opacity:1},"100%":{opacity:0}}),s=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],{antCls:n}=e,a="".concat(n,"-fade"),s=t?"&":"";return[(0,r.R)(a,i,o,e.motionDurationMid,t),{["\n ".concat(s).concat(a,"-enter,\n ").concat(s).concat(a,"-appear\n ")]:{opacity:0,animationTimingFunction:"linear"},["".concat(s).concat(a,"-leave")]:{animationTimingFunction:"linear"}}]}},44056:function(e){e.exports=function(e,n){for(var a,r,i,o=e||"",s=n||"div",l={},c=0;c4&&m.slice(0,4)===o&&s.test(t)&&("-"===t.charAt(4)?b=o+(n=t.slice(5).replace(l,u)).charAt(0).toUpperCase()+n.slice(1):(g=(p=t).slice(4),t=l.test(g)?p:("-"!==(g=g.replace(c,d)).charAt(0)&&(g="-"+g),o+g)),f=r),new f(b,t))};var s=/^data[-\w.:]+$/i,l=/-[a-z]/g,c=/[A-Z]/g;function d(e){return"-"+e.toLowerCase()}function u(e){return e.charAt(1).toUpperCase()}},31872:function(e,t,n){var a=n(96130),r=n(64730),i=n(61861),o=n(46982),s=n(83671),l=n(53618);e.exports=a([i,r,o,s,l])},83671:function(e,t,n){var a=n(7667),r=n(13585),i=a.booleanish,o=a.number,s=a.spaceSeparated;e.exports=r({transform:function(e,t){return"role"===t?t:"aria-"+t.slice(4).toLowerCase()},properties:{ariaActiveDescendant:null,ariaAtomic:i,ariaAutoComplete:null,ariaBusy:i,ariaChecked:i,ariaColCount:o,ariaColIndex:o,ariaColSpan:o,ariaControls:s,ariaCurrent:null,ariaDescribedBy:s,ariaDetails:null,ariaDisabled:i,ariaDropEffect:s,ariaErrorMessage:null,ariaExpanded:i,ariaFlowTo:s,ariaGrabbed:i,ariaHasPopup:null,ariaHidden:i,ariaInvalid:null,ariaKeyShortcuts:null,ariaLabel:null,ariaLabelledBy:s,ariaLevel:o,ariaLive:null,ariaModal:i,ariaMultiLine:i,ariaMultiSelectable:i,ariaOrientation:null,ariaOwns:s,ariaPlaceholder:null,ariaPosInSet:o,ariaPressed:i,ariaReadOnly:i,ariaRelevant:null,ariaRequired:i,ariaRoleDescription:s,ariaRowCount:o,ariaRowIndex:o,ariaRowSpan:o,ariaSelected:i,ariaSetSize:o,ariaSort:null,ariaValueMax:o,ariaValueMin:o,ariaValueNow:o,ariaValueText:null,role:null}})},53618:function(e,t,n){var a=n(7667),r=n(13585),i=n(46640),o=a.boolean,s=a.overloadedBoolean,l=a.booleanish,c=a.number,d=a.spaceSeparated,u=a.commaSeparated;e.exports=r({space:"html",attributes:{acceptcharset:"accept-charset",classname:"class",htmlfor:"for",httpequiv:"http-equiv"},transform:i,mustUseProperty:["checked","multiple","muted","selected"],properties:{abbr:null,accept:u,acceptCharset:d,accessKey:d,action:null,allow:null,allowFullScreen:o,allowPaymentRequest:o,allowUserMedia:o,alt:null,as:null,async:o,autoCapitalize:null,autoComplete:d,autoFocus:o,autoPlay:o,capture:o,charSet:null,checked:o,cite:null,className:d,cols:c,colSpan:null,content:null,contentEditable:l,controls:o,controlsList:d,coords:c|u,crossOrigin:null,data:null,dateTime:null,decoding:null,default:o,defer:o,dir:null,dirName:null,disabled:o,download:s,draggable:l,encType:null,enterKeyHint:null,form:null,formAction:null,formEncType:null,formMethod:null,formNoValidate:o,formTarget:null,headers:d,height:c,hidden:o,high:c,href:null,hrefLang:null,htmlFor:d,httpEquiv:d,id:null,imageSizes:null,imageSrcSet:u,inputMode:null,integrity:null,is:null,isMap:o,itemId:null,itemProp:d,itemRef:d,itemScope:o,itemType:d,kind:null,label:null,lang:null,language:null,list:null,loading:null,loop:o,low:c,manifest:null,max:null,maxLength:c,media:null,method:null,min:null,minLength:c,multiple:o,muted:o,name:null,nonce:null,noModule:o,noValidate:o,onAbort:null,onAfterPrint:null,onAuxClick:null,onBeforePrint:null,onBeforeUnload:null,onBlur:null,onCancel:null,onCanPlay:null,onCanPlayThrough:null,onChange:null,onClick:null,onClose:null,onContextMenu:null,onCopy:null,onCueChange:null,onCut:null,onDblClick:null,onDrag:null,onDragEnd:null,onDragEnter:null,onDragExit:null,onDragLeave:null,onDragOver:null,onDragStart:null,onDrop:null,onDurationChange:null,onEmptied:null,onEnded:null,onError:null,onFocus:null,onFormData:null,onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null,onKeyUp:null,onLanguageChange:null,onLoad:null,onLoadedData:null,onLoadedMetadata:null,onLoadEnd:null,onLoadStart:null,onMessage:null,onMessageError:null,onMouseDown:null,onMouseEnter:null,onMouseLeave:null,onMouseMove:null,onMouseOut:null,onMouseOver:null,onMouseUp:null,onOffline:null,onOnline:null,onPageHide:null,onPageShow:null,onPaste:null,onPause:null,onPlay:null,onPlaying:null,onPopState:null,onProgress:null,onRateChange:null,onRejectionHandled:null,onReset:null,onResize:null,onScroll:null,onSecurityPolicyViolation:null,onSeeked:null,onSeeking:null,onSelect:null,onSlotChange:null,onStalled:null,onStorage:null,onSubmit:null,onSuspend:null,onTimeUpdate:null,onToggle:null,onUnhandledRejection:null,onUnload:null,onVolumeChange:null,onWaiting:null,onWheel:null,open:o,optimum:c,pattern:null,ping:d,placeholder:null,playsInline:o,poster:null,preload:null,readOnly:o,referrerPolicy:null,rel:d,required:o,reversed:o,rows:c,rowSpan:c,sandbox:d,scope:null,scoped:o,seamless:o,selected:o,shape:null,size:c,sizes:null,slot:null,span:c,spellCheck:l,src:null,srcDoc:null,srcLang:null,srcSet:u,start:c,step:null,style:null,tabIndex:c,target:null,title:null,translate:null,type:null,typeMustMatch:o,useMap:null,value:l,width:c,wrap:null,align:null,aLink:null,archive:d,axis:null,background:null,bgColor:null,border:c,borderColor:null,bottomMargin:c,cellPadding:null,cellSpacing:null,char:null,charOff:null,classId:null,clear:null,code:null,codeBase:null,codeType:null,color:null,compact:o,declare:o,event:null,face:null,frame:null,frameBorder:null,hSpace:c,leftMargin:c,link:null,longDesc:null,lowSrc:null,marginHeight:c,marginWidth:c,noResize:o,noHref:o,noShade:o,noWrap:o,object:null,profile:null,prompt:null,rev:null,rightMargin:c,rules:null,scheme:null,scrolling:l,standby:null,summary:null,text:null,topMargin:c,valueType:null,version:null,vAlign:null,vLink:null,vSpace:c,allowTransparency:null,autoCorrect:null,autoSave:null,disablePictureInPicture:o,disableRemotePlayback:o,prefix:null,property:null,results:c,security:null,unselectable:null}})},46640:function(e,t,n){var a=n(25852);e.exports=function(e,t){return a(e,t.toLowerCase())}},25852:function(e){e.exports=function(e,t){return t in e?e[t]:t}},13585:function(e,t,n){var a=n(39900),r=n(94949),i=n(7478);e.exports=function(e){var t,n,o=e.space,s=e.mustUseProperty||[],l=e.attributes||{},c=e.properties,d=e.transform,u={},p={};for(t in c)n=new i(t,d(l,t),c[t],o),-1!==s.indexOf(t)&&(n.mustUseProperty=!0),u[t]=n,p[a(t)]=t,p[a(n.attribute)]=t;return new r(u,p,o)}},7478:function(e,t,n){var a=n(74108),r=n(7667);e.exports=s,s.prototype=new a,s.prototype.defined=!0;var i=["boolean","booleanish","overloadedBoolean","number","commaSeparated","spaceSeparated","commaOrSpaceSeparated"],o=i.length;function s(e,t,n,s){var l,c,d,u=-1;for(s&&(this.space=s),a.call(this,e,t);++u