From 94da5c076c3b27b4341f5cb82fc2e5011ef0c5d6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 12 Nov 2025 18:22:26 -0800 Subject: [PATCH 001/349] Add Daily Org Spend Table, read path, and write path --- .../litellm_proxy_extras/schema.prisma | 29 +++ litellm/constants.py | 1 + litellm/proxy/_types.py | 4 + litellm/proxy/db/db_spend_update_writer.py | 104 ++++++++++- .../redis_update_buffer.py | 36 ++++ .../organization_endpoints.py | 99 ++++++++++ litellm/proxy/schema.prisma | 29 +++ schema.prisma | 29 +++ .../proxy/db/test_db_spend_update_writer.py | 84 ++++++++- .../test_organization_endpoints.py | 175 ++++++++++++++++++ 10 files changed, 585 insertions(+), 5 deletions(-) diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index 88904561129..739ae8cc60f 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -419,6 +419,35 @@ model LiteLLM_DailyUserSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily organization spend metrics per model and key +model LiteLLM_DailyOrganizationSpend { + id String @id @default(uuid()) + organization_id String? + date String + api_key String + model String? + model_group String? + custom_llm_provider String? + mcp_namespaced_tool_name String? + prompt_tokens BigInt @default(0) + completion_tokens BigInt @default(0) + cache_read_input_tokens BigInt @default(0) + cache_creation_input_tokens BigInt @default(0) + spend Float @default(0.0) + api_requests BigInt @default(0) + successful_requests BigInt @default(0) + failed_requests BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([organization_id]) + @@index([api_key]) + @@index([model]) + @@index([mcp_namespaced_tool_name]) +} + // Track daily team spend metrics per model and key model LiteLLM_DailyTeamSpend { id String @id @default(uuid()) diff --git a/litellm/constants.py b/litellm/constants.py index 220e425068b..48be1c1fbf2 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -131,6 +131,7 @@ DEFAULT_SSL_CIPHERS = os.getenv( REDIS_UPDATE_BUFFER_KEY = "litellm_spend_update_buffer" REDIS_DAILY_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_spend_update_buffer" REDIS_DAILY_TEAM_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_team_spend_update_buffer" +REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_org_spend_update_buffer" REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_tag_spend_update_buffer" MAX_REDIS_BUFFER_DEQUEUE_COUNT = int(os.getenv("MAX_REDIS_BUFFER_DEQUEUE_COUNT", 100)) MAX_SIZE_IN_MEMORY_QUEUE = int(os.getenv("MAX_SIZE_IN_MEMORY_QUEUE", 10000)) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index a212eab076c..48307fc6a2e 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3536,6 +3536,10 @@ class DailyTeamSpendTransaction(BaseDailySpendTransaction): team_id: str +class DailyOrganizationSpendTransaction(BaseDailySpendTransaction): + organization_id: str + + class DailyUserSpendTransaction(BaseDailySpendTransaction): user_id: str diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 06b5301424e..8b276c583dd 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -23,6 +23,7 @@ from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, BaseDailySpendTransaction, DailyTagSpendTransaction, + DailyOrganizationSpendTransaction, DailyTeamSpendTransaction, DailyUserSpendTransaction, DBSpendUpdateTransactions, @@ -64,6 +65,7 @@ class DBSpendUpdateWriter: self.spend_update_queue = SpendUpdateQueue() self.daily_spend_update_queue = DailySpendUpdateQueue() self.daily_team_spend_update_queue = DailySpendUpdateQueue() + self.daily_org_spend_update_queue = DailySpendUpdateQueue() self.daily_tag_spend_update_queue = DailySpendUpdateQueue() async def update_database( @@ -180,7 +182,13 @@ class DBSpendUpdateWriter: prisma_client=prisma_client, ) ) - + asyncio.create_task( + self.add_spend_log_transaction_to_daily_org_transaction( + payload=payload, + org_id=org_id, + prisma_client=prisma_client, + ) + ) asyncio.create_task( self.add_spend_log_transaction_to_daily_tag_transaction( payload=payload, @@ -460,6 +468,7 @@ class DBSpendUpdateWriter: spend_update_queue=self.spend_update_queue, daily_spend_update_queue=self.daily_spend_update_queue, daily_team_spend_update_queue=self.daily_team_spend_update_queue, + daily_org_spend_update_queue=self.daily_org_spend_update_queue, daily_tag_spend_update_queue=self.daily_tag_spend_update_queue, ) @@ -502,6 +511,17 @@ class DBSpendUpdateWriter: daily_spend_transactions=daily_team_spend_update_transactions, ) + daily_org_spend_update_transactions = ( + await self.redis_update_buffer.get_all_daily_org_spend_update_transactions_from_redis_buffer() + ) + if daily_org_spend_update_transactions is not None: + await DBSpendUpdateWriter.update_daily_org_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + daily_spend_transactions=daily_org_spend_update_transactions, + ) + daily_tag_spend_update_transactions = ( await self.redis_update_buffer.get_all_daily_tag_spend_update_transactions_from_redis_buffer() ) @@ -573,6 +593,20 @@ class DBSpendUpdateWriter: daily_spend_transactions=daily_team_spend_update_transactions, ) + ################## Daily Organization Spend Update Transactions ################## + # Aggregate all in memory daily org spend transactions and commit to db + daily_org_spend_update_transactions = cast( + Dict[str, DailyOrganizationSpendTransaction], + await self.daily_org_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions(), + ) + + await DBSpendUpdateWriter.update_daily_org_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + daily_spend_transactions=daily_org_spend_update_transactions, + ) + ################## Daily Tag Spend Update Transactions ################## # Aggregate all in memory daily tag spend transactions and commit to db daily_tag_spend_update_transactions = cast( @@ -962,14 +996,15 @@ class DBSpendUpdateWriter: Dict[str, DailyUserSpendTransaction], Dict[str, DailyTeamSpendTransaction], Dict[str, DailyTagSpendTransaction], + Dict[str, DailyOrganizationSpendTransaction], ], - entity_type: Literal["user", "team", "tag"], + entity_type: Literal["user", "team", "org", "tag"], entity_id_field: str, table_name: str, unique_constraint_name: str, ) -> None: """ - Generic function to update daily spend for any entity type (user, team, tag) + Generic function to update daily spend for any entity type (user, team, org, tag) """ from litellm.proxy.utils import _raise_failed_update_spend_exception @@ -1191,6 +1226,27 @@ class DBSpendUpdateWriter: unique_constraint_name="team_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", ) + @staticmethod + async def update_daily_org_spend( + n_retry_times: int, + prisma_client: PrismaClient, + proxy_logging_obj: ProxyLogging, + daily_spend_transactions: Dict[str, DailyOrganizationSpendTransaction], + ): + """ + Batch job to update LiteLLM_DailyOrganizationSpend table using in-memory daily_spend_transactions + """ + await DBSpendUpdateWriter._update_daily_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + daily_spend_transactions=daily_spend_transactions, + entity_type="org", + entity_id_field="organization_id", + table_name="litellm_dailyorganizationspend", + unique_constraint_name="organization_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + ) + @staticmethod async def update_daily_tag_spend( n_retry_times: int, @@ -1216,13 +1272,15 @@ class DBSpendUpdateWriter: self, payload: Union[dict, SpendLogsPayload], prisma_client: PrismaClient, - type: Literal["user", "team", "request_tags"] = "user", + type: Literal["user", "team", "org", "request_tags"] = "user", ) -> Optional[BaseDailySpendTransaction]: common_expected_keys = ["startTime", "api_key"] if type == "user": expected_keys = ["user", *common_expected_keys] elif type == "team": expected_keys = ["team_id", *common_expected_keys] + elif type == "org": + expected_keys = ["organization_id", *common_expected_keys] elif type == "request_tags": expected_keys = ["request_tags", *common_expected_keys] else: @@ -1354,6 +1412,44 @@ class DBSpendUpdateWriter: update={daily_transaction_key: daily_transaction} ) + async def add_spend_log_transaction_to_daily_org_transaction( + self, + payload: SpendLogsPayload, + prisma_client: Optional[PrismaClient] = None, + org_id: Optional[str] = None, + ) -> None: + if prisma_client is None: + verbose_proxy_logger.debug( + "prisma_client is None. Skipping writing spend logs to db." + ) + return + + if org_id is None: + verbose_proxy_logger.debug( + "organization_id is None for request. Skipping incrementing organization spend." + ) + return + + # Inject org_id for daily aggregation check + payload_with_org: SpendLogsPayload = dict(payload) + payload_with_org["organization_id"] = org_id + + base_daily_transaction = ( + await self._common_add_spend_log_transaction_to_daily_transaction( + payload_with_org, prisma_client, "org" + ) + ) + if base_daily_transaction is None: + return + + daily_transaction_key = f"{org_id}_{base_daily_transaction['date']}_{payload_with_org['api_key']}_{payload_with_org['model']}_{payload_with_org['custom_llm_provider']}" + daily_transaction = DailyOrganizationSpendTransaction( + organization_id=org_id, **base_daily_transaction + ) + await self.daily_org_spend_update_queue.add_update( + update={daily_transaction_key: daily_transaction} + ) + async def add_spend_log_transaction_to_daily_tag_transaction( self, payload: SpendLogsPayload, diff --git a/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py b/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py index 91d0bee1d3a..921fd9701bd 100644 --- a/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py +++ b/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py @@ -15,6 +15,7 @@ from litellm.constants import ( REDIS_DAILY_SPEND_UPDATE_BUFFER_KEY, REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY, REDIS_DAILY_TEAM_SPEND_UPDATE_BUFFER_KEY, + REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY, REDIS_UPDATE_BUFFER_KEY, ) from litellm.litellm_core_utils.safe_json_dumps import safe_dumps @@ -22,6 +23,7 @@ from litellm.proxy._types import ( DailyTagSpendTransaction, DailyTeamSpendTransaction, DailyUserSpendTransaction, + DailyOrganizationSpendTransaction, DBSpendUpdateTransactions, ) from litellm.proxy.db.db_transaction_queue.base_update_queue import service_logger_obj @@ -104,6 +106,7 @@ class RedisUpdateBuffer: spend_update_queue: SpendUpdateQueue, daily_spend_update_queue: DailySpendUpdateQueue, daily_team_spend_update_queue: DailySpendUpdateQueue, + daily_org_spend_update_queue: DailySpendUpdateQueue, daily_tag_spend_update_queue: DailySpendUpdateQueue, ): """ @@ -166,6 +169,9 @@ class RedisUpdateBuffer: daily_team_spend_update_transactions = ( await daily_team_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions() ) + daily_org_spend_update_transactions = ( + await daily_org_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions() + ) daily_tag_spend_update_transactions = ( await daily_tag_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions() ) @@ -195,6 +201,12 @@ class RedisUpdateBuffer: service_type=ServiceTypes.REDIS_DAILY_TEAM_SPEND_UPDATE_QUEUE, ) + await self._store_transactions_in_redis( + transactions=daily_org_spend_update_transactions, + redis_key=REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY, + service_type=ServiceTypes.REDIS_DAILY_SPEND_UPDATE_QUEUE, + ) + await self._store_transactions_in_redis( transactions=daily_tag_spend_update_transactions, redis_key=REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY, @@ -329,6 +341,30 @@ class RedisUpdateBuffer: ), ) + async def get_all_daily_org_spend_update_transactions_from_redis_buffer( + self, + ) -> Optional[Dict[str, DailyOrganizationSpendTransaction]]: + """ + Gets all the daily organization spend update transactions from Redis + """ + if self.redis_cache is None: + return None + list_of_transactions = await self.redis_cache.async_lpop( + key=REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY, + count=MAX_REDIS_BUFFER_DEQUEUE_COUNT, + ) + if list_of_transactions is None: + return None + list_of_daily_spend_update_transactions = [ + json.loads(transaction) for transaction in list_of_transactions + ] + return cast( + Dict[str, DailyOrganizationSpendTransaction], + DailySpendUpdateQueue.get_aggregated_daily_spend_update_transactions( + list_of_daily_spend_update_transactions + ), + ) + async def get_all_daily_tag_spend_update_transactions_from_redis_buffer( self, ) -> Optional[Dict[str, DailyTagSpendTransaction]]: diff --git a/litellm/proxy/management_endpoints/organization_endpoints.py b/litellm/proxy/management_endpoints/organization_endpoints.py index 402dffaaa7d..65764b3b957 100644 --- a/litellm/proxy/management_endpoints/organization_endpoints.py +++ b/litellm/proxy/management_endpoints/organization_endpoints.py @@ -25,6 +25,7 @@ from litellm.proxy.management_endpoints.budget_management_endpoints import ( update_budget, ) from litellm.proxy.management_endpoints.common_utils import _set_object_metadata_field +from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view from litellm.proxy.management_helpers.object_permission_utils import ( handle_update_object_permission_common, ) @@ -34,6 +35,10 @@ from litellm.proxy.management_helpers.utils import ( ) from litellm.proxy.utils import PrismaClient from litellm.utils import _update_dictionary +from litellm.types.proxy.management_endpoints.common_daily_activity import ( + SpendAnalyticsPaginatedResponse, +) +from litellm.proxy.management_endpoints.common_daily_activity import get_daily_activity router = APIRouter() @@ -255,6 +260,100 @@ async def new_organization( return response +@router.get( + "/organization/daily/activity", + response_model=SpendAnalyticsPaginatedResponse, + tags=["organization management"], +) +async def get_organization_daily_activity( + organization_ids: Optional[str] = None, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + model: Optional[str] = None, + api_key: Optional[str] = None, + page: int = 1, + page_size: int = 10, + exclude_organization_ids: Optional[str] = None, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Get daily activity for specific organizations or all accessible organizations. + """ + from litellm.proxy.proxy_server import ( + prisma_client, + proxy_logging_obj, + user_api_key_cache, + ) + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, + ) + + # Parse comma-separated ids + org_ids_list = organization_ids.split(",") if organization_ids else None + exclude_org_ids_list: Optional[List[str]] = None + if exclude_organization_ids: + exclude_org_ids_list = ( + exclude_organization_ids.split(",") if exclude_organization_ids else None + ) + + # Restrict non-proxy-admins to only organizations where they are org_admin + if not _user_has_admin_view(user_api_key_dict): + memberships = await prisma_client.db.litellm_organizationmembership.find_many( + where={"user_id": user_api_key_dict.user_id} + ) + admin_org_ids = [ + m.organization_id + for m in memberships + if m.user_role == LitellmUserRoles.ORG_ADMIN.value + ] + if org_ids_list is None: + # Default to orgs where user is org_admin + org_ids_list = admin_org_ids + else: + # Ensure user is org_admin for all requested orgs + for org_id in org_ids_list: + if org_id not in admin_org_ids: + raise HTTPException( + status_code=403, + detail={ + "error": "User is not org_admin for Organization= {}.".format( + org_id + ) + }, + ) + + # Fetch organization aliases for metadata + where_condition = {} + if org_ids_list: + where_condition["organization_id"] = {"in": list(org_ids_list)} + org_aliases = await prisma_client.db.litellm_organizationtable.find_many( + where=where_condition + ) + org_alias_metadata = { + o.organization_id: {"organization_alias": o.organization_alias} + for o in org_aliases + } + + # Query daily activity for organizations + return await get_daily_activity( + prisma_client=prisma_client, + table_name="litellm_dailyorganizationspend", + entity_id_field="organization_id", + entity_id=org_ids_list, + entity_metadata_field=org_alias_metadata, + exclude_entity_ids=exclude_org_ids_list, + start_date=start_date, + end_date=end_date, + model=model, + api_key=api_key, + page=page, + page_size=page_size, + ) + + async def _set_object_permission( data: NewOrganizationRequest, prisma_client: Optional[PrismaClient], diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 51e6ea94540..4ec9662c325 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -419,6 +419,35 @@ model LiteLLM_DailyUserSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily organization spend metrics per model and key +model LiteLLM_DailyOrganizationSpend { + id String @id @default(uuid()) + organization_id String? + date String + api_key String + model String? + model_group String? + custom_llm_provider String? + mcp_namespaced_tool_name String? + prompt_tokens BigInt @default(0) + completion_tokens BigInt @default(0) + cache_read_input_tokens BigInt @default(0) + cache_creation_input_tokens BigInt @default(0) + spend Float @default(0.0) + api_requests BigInt @default(0) + successful_requests BigInt @default(0) + failed_requests BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([organization_id]) + @@index([api_key]) + @@index([model]) + @@index([mcp_namespaced_tool_name]) +} + // Track daily team spend metrics per model and key model LiteLLM_DailyTeamSpend { id String @id @default(uuid()) diff --git a/schema.prisma b/schema.prisma index 51e6ea94540..4ec9662c325 100644 --- a/schema.prisma +++ b/schema.prisma @@ -419,6 +419,35 @@ model LiteLLM_DailyUserSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily organization spend metrics per model and key +model LiteLLM_DailyOrganizationSpend { + id String @id @default(uuid()) + organization_id String? + date String + api_key String + model String? + model_group String? + custom_llm_provider String? + mcp_namespaced_tool_name String? + prompt_tokens BigInt @default(0) + completion_tokens BigInt @default(0) + cache_read_input_tokens BigInt @default(0) + cache_creation_input_tokens BigInt @default(0) + spend Float @default(0.0) + api_requests BigInt @default(0) + successful_requests BigInt @default(0) + failed_requests BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([organization_id]) + @@index([api_key]) + @@index([model]) + @@index([mcp_namespaced_tool_name]) +} + // Track daily team spend metrics per model and key model LiteLLM_DailyTeamSpend { id String @id @default(uuid()) diff --git a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py index 6dbbbdd7442..d29ddffa36e 100644 --- a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py +++ b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py @@ -387,4 +387,86 @@ async def test_add_spend_log_transaction_to_daily_tag_transaction_with_request_i transaction_dict = call[1]["update"] # Each transaction should have one key with the format tag_date_api_key_model_provider for key, transaction in transaction_dict.items(): - assert transaction["request_id"] == request_id, f"request_id should be {request_id} but got {transaction.get('request_id')}" \ No newline at end of file + assert transaction["request_id"] == request_id, f"request_id should be {request_id} but got {transaction.get('request_id')}" + + +@pytest.mark.asyncio +async def test_add_spend_log_transaction_to_daily_org_transaction_injects_org_id_and_queues_update(): + """ + Verify org_id is injected into payload for daily aggregation and the update is queued. + """ + writer = DBSpendUpdateWriter() + mock_prisma = MagicMock() + mock_prisma.get_request_status = MagicMock(return_value="success") + + org_id = "org-xyz" + payload = { + "request_id": "req-1", + "user": "test-user", + "startTime": "2024-01-01T12:00:00", + "api_key": "test-key", + "model": "gpt-4", + "custom_llm_provider": "openai", + "model_group": "gpt-4-group", + "prompt_tokens": 10, + "completion_tokens": 5, + "spend": 0.2, + "metadata": '{"usage_object": {}}', + } + + writer.daily_org_spend_update_queue.add_update = AsyncMock() + + await writer.add_spend_log_transaction_to_daily_org_transaction( + payload=payload, + prisma_client=mock_prisma, + org_id=org_id, + ) + + # Should enqueue one org spend update + writer.daily_org_spend_update_queue.add_update.assert_called_once() + + # Validate key and injected fields + call_args = writer.daily_org_spend_update_queue.add_update.call_args[1] + update_dict = call_args["update"] + assert len(update_dict) == 1 + for key, transaction in update_dict.items(): + assert key == f"{org_id}_2024-01-01_test-key_gpt-4_openai" + assert transaction["organization_id"] == org_id + assert transaction["date"] == "2024-01-01" + assert transaction["api_key"] == "test-key" + assert transaction["model"] == "gpt-4" + assert transaction["custom_llm_provider"] == "openai" + + +@pytest.mark.asyncio +async def test_add_spend_log_transaction_to_daily_org_transaction_skips_when_org_id_missing(): + """ + Ensure no update is queued when org_id is None. + """ + writer = DBSpendUpdateWriter() + mock_prisma = MagicMock() + mock_prisma.get_request_status = MagicMock(return_value="success") + + payload = { + "request_id": "req-2", + "user": "test-user", + "startTime": "2024-01-01T12:00:00", + "api_key": "test-key", + "model": "gpt-4", + "custom_llm_provider": "openai", + "model_group": "gpt-4-group", + "prompt_tokens": 10, + "completion_tokens": 5, + "spend": 0.2, + "metadata": '{"usage_object": {}}', + } + + writer.daily_org_spend_update_queue.add_update = AsyncMock() + + await writer.add_spend_log_transaction_to_daily_org_transaction( + payload=payload, + prisma_client=mock_prisma, + org_id=None, + ) + + writer.daily_org_spend_update_queue.add_update.assert_not_called() \ No newline at end of file diff --git a/tests/test_litellm/proxy/management_endpoints/test_organization_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_organization_endpoints.py index 981c26d6db9..c02db727bf0 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_organization_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_organization_endpoints.py @@ -94,6 +94,181 @@ async def test_organization_update_object_permissions_existing_permission(monkey mock_prisma_client.db.litellm_objectpermissiontable.upsert.assert_called_once() +@pytest.mark.asyncio +async def test_get_organization_daily_activity_admin_param_passing(monkeypatch): + """ + As admin, ensure parsed params are forwarded to get_daily_activity with correct values. + """ + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints import organization_endpoints + from litellm.proxy.management_endpoints.organization_endpoints import ( + get_organization_daily_activity, + ) + + # Mock prisma client + mock_prisma_client = AsyncMock() + mock_prisma_client.db.litellm_organizationtable.find_many = AsyncMock( + return_value=[] + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Admin view -> skip membership restriction + monkeypatch.setattr( + "litellm.proxy.management_endpoints.organization_endpoints._user_has_admin_view", + lambda _: True, + ) + + # Patch downstream common function and verify call args + mocked_response = MagicMock(name="SpendAnalyticsPaginatedResponse") + get_daily_activity_mock = AsyncMock(return_value=mocked_response) + monkeypatch.setattr( + organization_endpoints, "get_daily_activity", get_daily_activity_mock + ) + + auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin1") + result = await get_organization_daily_activity( + organization_ids="org1,org2", + start_date="2024-01-01", + end_date="2024-01-31", + model="gpt-4", + api_key="test-key", + page=2, + page_size=5, + exclude_organization_ids="org3", + user_api_key_dict=auth, + ) + + # Ensure passthrough to common method with correct args + get_daily_activity_mock.assert_awaited_once() + kwargs = get_daily_activity_mock.call_args.kwargs + assert kwargs["table_name"] == "litellm_dailyorganizationspend" + assert kwargs["entity_id_field"] == "organization_id" + assert kwargs["entity_id"] == ["org1", "org2"] + assert kwargs["exclude_entity_ids"] == ["org3"] + assert kwargs["start_date"] == "2024-01-01" + assert kwargs["end_date"] == "2024-01-31" + assert kwargs["model"] == "gpt-4" + assert kwargs["api_key"] == "test-key" + assert kwargs["page"] == 2 + assert kwargs["page_size"] == 5 + + assert result is mocked_response + + +@pytest.mark.asyncio +async def test_get_organization_daily_activity_non_admin_defaults_to_admin_orgs(monkeypatch): + """ + Non-admin with no explicit organization_ids should default to orgs they are ORG_ADMIN of. + """ + from types import SimpleNamespace + + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints import organization_endpoints + from litellm.proxy.management_endpoints.organization_endpoints import ( + get_organization_daily_activity, + ) + + # Mock prisma client and memberships + mock_prisma_client = AsyncMock() + mock_prisma_client.db.litellm_organizationtable.find_many = AsyncMock( + return_value=[] + ) + mock_prisma_client.db.litellm_organizationmembership.find_many = AsyncMock( + return_value=[ + SimpleNamespace( + organization_id="orgA", user_role=LitellmUserRoles.ORG_ADMIN.value + ), + SimpleNamespace( + organization_id="orgB", user_role=LitellmUserRoles.ORG_ADMIN.value + ), + ] + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Non-admin view + monkeypatch.setattr( + "litellm.proxy.management_endpoints.organization_endpoints._user_has_admin_view", + lambda _: False, + ) + + # Patch downstream aggregator + mocked_response = MagicMock(name="SpendAnalyticsPaginatedResponse") + get_daily_activity_mock = AsyncMock(return_value=mocked_response) + monkeypatch.setattr( + organization_endpoints, "get_daily_activity", get_daily_activity_mock + ) + + auth = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, user_id="regular-user" + ) + await get_organization_daily_activity( + organization_ids=None, + start_date="2024-02-01", + end_date="2024-02-28", + model=None, + api_key=None, + page=1, + page_size=10, + exclude_organization_ids=None, + user_api_key_dict=auth, + ) + + kwargs = get_daily_activity_mock.call_args.kwargs + assert kwargs["entity_id"] == ["orgA", "orgB"] + assert kwargs["start_date"] == "2024-02-01" + assert kwargs["end_date"] == "2024-02-28" + + +@pytest.mark.asyncio +async def test_get_organization_daily_activity_non_admin_unauthorized_org_raises(monkeypatch): + """ + Non-admin requesting an org they aren't ORG_ADMIN for should raise 403. + """ + from types import SimpleNamespace + + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints.organization_endpoints import ( + get_organization_daily_activity, + ) + + # Mock prisma client and memberships (only orgA is admin) + mock_prisma_client = AsyncMock() + mock_prisma_client.db.litellm_organizationmembership.find_many = AsyncMock( + return_value=[ + SimpleNamespace( + organization_id="orgA", user_role=LitellmUserRoles.ORG_ADMIN.value + ) + ] + ) + mock_prisma_client.db.litellm_organizationtable.find_many = AsyncMock( + return_value=[] + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Non-admin view + monkeypatch.setattr( + "litellm.proxy.management_endpoints.organization_endpoints._user_has_admin_view", + lambda _: False, + ) + + auth = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, user_id="regular-user" + ) + + with pytest.raises(HTTPException) as exc: + await get_organization_daily_activity( + organization_ids="orgA,orgX", # orgX is unauthorized + start_date="2024-03-01", + end_date="2024-03-31", + model=None, + api_key=None, + page=1, + page_size=10, + exclude_organization_ids=None, + user_api_key_dict=auth, + ) + assert exc.value.status_code == 403 + @pytest.mark.asyncio async def test_organization_update_object_permissions_no_existing_permission( monkeypatch, From 09afe14a5f104cbef09c7f29142c630bdd506ffa Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 13 Nov 2025 15:49:35 -0800 Subject: [PATCH 002/349] Addressing linting issues --- litellm/proxy/_types.py | 1 + litellm/proxy/db/db_spend_update_writer.py | 27 ++++++++++++++++--- .../organization_endpoints.py | 4 +-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 48307fc6a2e..81c5a2e920e 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2600,6 +2600,7 @@ class SpendLogsPayload(TypedDict): cache_key: str request_tags: str # json str team_id: Optional[str] + organization_id: Optional[str] end_user: Optional[str] requester_ip_address: Optional[str] custom_llm_provider: Optional[str] diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 8b276c583dd..647a815071e 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -972,6 +972,20 @@ class DBSpendUpdateWriter: ) -> None: ... + @overload + @staticmethod + async def _update_daily_spend( + n_retry_times: int, + prisma_client: PrismaClient, + proxy_logging_obj: ProxyLogging, + daily_spend_transactions: Dict[str, DailyOrganizationSpendTransaction], + entity_type: Literal["org"], + entity_id_field: str, + table_name: str, + unique_constraint_name: str, + ) -> None: + ... + @overload @staticmethod async def _update_daily_spend( @@ -1430,9 +1444,16 @@ class DBSpendUpdateWriter: ) return - # Inject org_id for daily aggregation check - payload_with_org: SpendLogsPayload = dict(payload) - payload_with_org["organization_id"] = org_id + print("org_id", org_id) + print("payload", payload) + + payload_with_org = cast( + SpendLogsPayload, + { + **payload, + "organization_id": org_id, + }, + ) base_daily_transaction = ( await self._common_add_spend_log_transaction_to_daily_transaction( diff --git a/litellm/proxy/management_endpoints/organization_endpoints.py b/litellm/proxy/management_endpoints/organization_endpoints.py index 65764b3b957..99b37c765a8 100644 --- a/litellm/proxy/management_endpoints/organization_endpoints.py +++ b/litellm/proxy/management_endpoints/organization_endpoints.py @@ -281,10 +281,8 @@ async def get_organization_daily_activity( """ from litellm.proxy.proxy_server import ( prisma_client, - proxy_logging_obj, - user_api_key_cache, ) - + if prisma_client is None: raise HTTPException( status_code=500, From cfc44ea2794b9634bd7533d565fdde9fec46d568 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 13 Nov 2025 15:51:49 -0800 Subject: [PATCH 003/349] Remove debug statements --- litellm/proxy/db/db_spend_update_writer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 647a815071e..1d998f457b5 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -1444,9 +1444,6 @@ class DBSpendUpdateWriter: ) return - print("org_id", org_id) - print("payload", payload) - payload_with_org = cast( SpendLogsPayload, { From ae4dfb53dd4f03658d0850fb258590477685c1d3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 14 Nov 2025 18:06:49 -0800 Subject: [PATCH 004/349] Add migration --- .../migration.sql | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20251114180624_Add_org_usage_table/migration.sql diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114180624_Add_org_usage_table/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114180624_Add_org_usage_table/migration.sql new file mode 100644 index 00000000000..74e0eea3134 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114180624_Add_org_usage_table/migration.sql @@ -0,0 +1,42 @@ +-- CreateTable +CREATE TABLE "LiteLLM_DailyOrganizationSpend" ( + "id" TEXT NOT NULL, + "organization_id" TEXT, + "date" TEXT NOT NULL, + "api_key" TEXT NOT NULL, + "model" TEXT, + "model_group" TEXT, + "custom_llm_provider" TEXT, + "mcp_namespaced_tool_name" TEXT, + "prompt_tokens" BIGINT NOT NULL DEFAULT 0, + "completion_tokens" BIGINT NOT NULL DEFAULT 0, + "cache_read_input_tokens" BIGINT NOT NULL DEFAULT 0, + "cache_creation_input_tokens" BIGINT NOT NULL DEFAULT 0, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "api_requests" BIGINT NOT NULL DEFAULT 0, + "successful_requests" BIGINT NOT NULL DEFAULT 0, + "failed_requests" BIGINT NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_DailyOrganizationSpend_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_date_idx" ON "LiteLLM_DailyOrganizationSpend"("date"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_organization_id_idx" ON "LiteLLM_DailyOrganizationSpend"("organization_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_api_key_idx" ON "LiteLLM_DailyOrganizationSpend"("api_key"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_model_idx" ON "LiteLLM_DailyOrganizationSpend"("model"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_mcp_namespaced_tool_name_idx" ON "LiteLLM_DailyOrganizationSpend"("mcp_namespaced_tool_name"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyOrganizationSpend_organization_id_date_api_key_key" ON "LiteLLM_DailyOrganizationSpend"("organization_id", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name"); + From 24c23c62ba0f3e9caca2986fd4d8fc3a8864ae47 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 14 Nov 2025 18:14:22 -0800 Subject: [PATCH 005/349] Linting --- litellm/proxy/spend_tracking/spend_tracking_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 32d9c4b1f21..10dae1deed7 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -381,6 +381,7 @@ def get_logging_payload( # noqa: PLR0915 model=kwargs.get("model", "") or "", user=metadata.get("user_api_key_user_id", "") or "", team_id=metadata.get("user_api_key_team_id", "") or "", + organization_id=metadata.get("user_api_key_org_id") or "", metadata=safe_dumps(clean_metadata), cache_key=cache_key, spend=kwargs.get("response_cost", 0), From d01efcb3084a0a3bd7302294d1072ab8ab247ecd Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Sat, 15 Nov 2025 15:58:41 -0800 Subject: [PATCH 006/349] speech set up --- no_cache_hits.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ speech.mp3 | Bin 0 -> 104 bytes speech_config.yaml | 9 +++++++++ 3 files changed, 57 insertions(+) create mode 100644 no_cache_hits.py create mode 100644 speech.mp3 create mode 100644 speech_config.yaml diff --git a/no_cache_hits.py b/no_cache_hits.py new file mode 100644 index 00000000000..1b3bf895f77 --- /dev/null +++ b/no_cache_hits.py @@ -0,0 +1,48 @@ +from locust import HttpUser, between, task + + +class MyUser(HttpUser): + """ + Minimal Locust user for repeatedly hitting `/v1/audio/speech`. + The goal is to measure server-side performance, so we avoid any extra work + (file writes, random generation, manual timing, custom event hooks, etc.) + that could inflate client-side latency. + """ + + wait_time = between(0.5, 1) + host = "http://0.0.0.0:8090" + + def on_start(self): + self.api_key = "sk-1234" + self.model_name = "fake-openai-speech" + self.headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json", + } + self.prompt_counter = 0 + + @task + def audio_speech_request(self): + self.prompt_counter += 1 + # Ensure prompts differ slightly so the backend can't reuse cached audio. + prompt = ( + "Generate a short spoken status update mentioning counter " + f"{self.prompt_counter}." + ) + + response = self.client.post( + "v1/audio/speech", + json={ + "model": self.model_name, + "input": prompt, + "voice": "alloy", + "format": "mp3", + }, + headers=self.headers, + name="audio_speech", + ) + + if response.status_code != 200: + # log the errors in error.txt + with open("error.txt", "a") as error_log: + error_log.write(response.text + "\n") \ No newline at end of file diff --git a/speech.mp3 b/speech.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f4f854d9bd215e2493d48b4bc4d39804bd79c038 GIT binary patch literal 104 NcmezWdjbPJ000JT0*e3u literal 0 HcmV?d00001 diff --git a/speech_config.yaml b/speech_config.yaml new file mode 100644 index 00000000000..ad9920a2793 --- /dev/null +++ b/speech_config.yaml @@ -0,0 +1,9 @@ +model_list: + - model_name: fake-openai-speech + litellm_params: + model: openai/gpt-4o-mini-tts + api_base: http://0.0.0.0:8090/ + api_key: sk-1234 + model_info: + mode: audio_speech + \ No newline at end of file From 3d2552be9fd87b7cf57cb3065affe7977e6f1715 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 15 Nov 2025 16:31:37 -0800 Subject: [PATCH 007/349] Fixed failing tests --- .../proxy/spend_tracking/test_spend_management_endpoints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 65b245b9e7e..33715eb461a 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -196,6 +196,7 @@ ignored_keys = [ "endTime", "completionStartTime", "endTime", + "organization_id", "metadata.model_map_information", "metadata.usage_object", "metadata.cold_storage_object_key", From 44f2013495c6987bef3918ca045f942777b21c34 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Sat, 15 Nov 2025 17:02:15 -0800 Subject: [PATCH 008/349] fix: change chunk_size for aiter_bytes 1KB is too small for audio and is lowering the RPS when testing with medium to large files --- litellm/proxy/proxy_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a6e73199f0e..36178652a7d 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5305,7 +5305,7 @@ async def audio_speech( # Printing each chunk size async def generate(_response: HttpxBinaryResponseContent): - _generator = await _response.aiter_bytes(chunk_size=1024) + _generator = await _response.aiter_bytes(chunk_size=4096) async for chunk in _generator: yield chunk From 348d28d871a8c8d00ec1263d6d61caff4843cd7b Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Sat, 15 Nov 2025 17:15:26 -0800 Subject: [PATCH 009/349] fix: remove function definition from every request --- litellm/proxy/proxy_server.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 36178652a7d..c65c7f77f0d 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -14,6 +14,7 @@ from datetime import datetime, timedelta from typing import ( TYPE_CHECKING, Any, + AsyncGenerator, List, Literal, Optional, @@ -5231,6 +5232,14 @@ async def moderations( ) +async def _audio_speech_chunk_generator( + _response: HttpxBinaryResponseContent, +) -> AsyncGenerator[bytes, None]: + _generator = await _response.aiter_bytes(chunk_size=4096) + async for chunk in _generator: + yield chunk + + @router.post( "/v1/audio/speech", dependencies=[Depends(user_api_key_auth)], @@ -5303,12 +5312,6 @@ async def audio_speech( response_cost = hidden_params.get("response_cost", None) or "" litellm_call_id = hidden_params.get("litellm_call_id", None) or "" - # Printing each chunk size - async def generate(_response: HttpxBinaryResponseContent): - _generator = await _response.aiter_bytes(chunk_size=4096) - async for chunk in _generator: - yield chunk - custom_headers = ProxyBaseLLMRequestProcessing.get_custom_headers( user_api_key_dict=user_api_key_dict, model_id=model_id, @@ -5337,7 +5340,9 @@ async def audio_speech( media_type = "audio/wav" # Gemini TTS returns WAV format after conversion return StreamingResponse( - generate(response), media_type=media_type, headers=custom_headers # type: ignore + _audio_speech_chunk_generator(response), # type: ignore[arg-type] + media_type=media_type, + headers=custom_headers, # type: ignore ) except Exception as e: From 8ea0e31678863d2d700bf857bedac4d25338e008 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Sat, 15 Nov 2025 17:30:58 -0800 Subject: [PATCH 010/349] Optimize streaming response accumulation Refactor async_data_generator to build streamed text via list accumulation and ''.join() instead of repeated string concatenation. This improves performance for long responses without changing streaming behavior. --- litellm/proxy/proxy_server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c65c7f77f0d..b67de4a87a7 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4017,7 +4017,8 @@ async def async_data_generator( ): verbose_proxy_logger.debug("inside generator") try: - str_so_far = "" + # Use a list to accumulate response segments to avoid O(n^2) string concatenation + str_so_far_parts: list[str] = [] error_message: Optional[str] = None async for chunk in proxy_logging_obj.async_post_call_streaming_iterator_hook( user_api_key_dict=user_api_key_dict, @@ -4033,12 +4034,12 @@ async def async_data_generator( user_api_key_dict=user_api_key_dict, response=chunk, data=request_data, - str_so_far=str_so_far, + str_so_far="".join(str_so_far_parts), ) if isinstance(chunk, (ModelResponse, ModelResponseStream)): response_str = litellm.get_response_string(response_obj=chunk) - str_so_far += response_str + str_so_far_parts.append(response_str) if isinstance(chunk, BaseModel): chunk = chunk.model_dump_json(exclude_none=True, exclude_unset=True) From 98e2b64040f6e5f882648b433a23799582d710a1 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Sat, 15 Nov 2025 17:37:01 -0800 Subject: [PATCH 011/349] Optimize response string construction Use list accumulation and join in get_response_string to avoid O(n^2) string concatenation and add a brief comment explaining the performance rationale. --- litellm/utils.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 783d462a7af..201e8145254 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -4437,17 +4437,20 @@ def get_response_string(response_obj: Union[ModelResponse, ModelResponseStream]) responses_api_response = getattr(response_obj, "response", None) if responses_api_response and hasattr(responses_api_response, "output"): output_list = responses_api_response.output - response_str = "" + # Use list accumulation to avoid O(n^2) string concatenation: + # repeatedly doing `response_str += part` copies the full string each time + # because Python strings are immutable, so total work grows with n^2. + response_output_parts: List[str] = [] for output_item in output_list: # Handle output items with content array if hasattr(output_item, "content"): for content_part in output_item.content: if hasattr(content_part, "text"): - response_str += content_part.text + response_output_parts.append(content_part.text) # Handle output items with direct text field elif hasattr(output_item, "text"): - response_str += output_item.text - return response_str + response_output_parts.append(output_item.text) + return "".join(response_output_parts) # Handle Responses API text delta events if hasattr(response_obj, "type") and hasattr(response_obj, "delta"): @@ -4461,16 +4464,17 @@ def get_response_string(response_obj: Union[ModelResponse, ModelResponseStream]) response_obj.choices ) - response_str = "" + # Use list accumulation to avoid O(n^2) string concatenation across choices + response_parts: List[str] = [] for choice in _choices: if isinstance(choice, Choices): if choice.message.content is not None: - response_str += choice.message.content + response_parts.append(str(choice.message.content)) elif isinstance(choice, StreamingChoices): if choice.delta.content is not None: - response_str += choice.delta.content + response_parts.append(str(choice.delta.content)) - return response_str + return "".join(response_parts) def get_api_key(llm_provider: str, dynamic_api_key: Optional[str]): From 4614e528dc4b3385582810f3c565d62668373d3c Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 17 Nov 2025 10:11:55 -0800 Subject: [PATCH 012/349] fix: remove deadcode The optimizations related to `select_data_generator` had no effect because its output which is the generator wasn't being used. --- litellm/proxy/proxy_server.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index b67de4a87a7..d6a98a1ca5b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5327,11 +5327,6 @@ async def audio_speech( hidden_params=hidden_params, ) - select_data_generator( - response=response, - user_api_key_dict=user_api_key_dict, - request_data=data, - ) # Determine media type based on model type media_type = "audio/mpeg" # Default for OpenAI TTS request_model = data.get("model", "") From c8c12298590885bc845088d4982c7059996f04a9 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 17 Nov 2025 10:24:34 -0800 Subject: [PATCH 013/349] fix: call_type mistake & remove repetitive .lower() calls --- litellm/proxy/proxy_server.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index d6a98a1ca5b..cb43ef0cecf 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5286,7 +5286,7 @@ async def audio_speech( ### CALL HOOKS ### - modify incoming data / reject request before calling the model data = await proxy_logging_obj.pre_call_hook( - user_api_key_dict=user_api_key_dict, data=data, call_type="image_generation" + user_api_key_dict=user_api_key_dict, data=data, call_type="aspeech" ) ## ROUTE TO CORRECT ENDPOINT ## @@ -5330,10 +5330,12 @@ async def audio_speech( # Determine media type based on model type media_type = "audio/mpeg" # Default for OpenAI TTS request_model = data.get("model", "") - if "gemini" in request_model.lower() and ( - "tts" in request_model.lower() or "preview-tts" in request_model.lower() - ): - media_type = "audio/wav" # Gemini TTS returns WAV format after conversion + if request_model: + request_model_lower = request_model.lower() + if "gemini" in request_model_lower and ( + "tts" in request_model_lower or "preview-tts" in request_model_lower + ): + media_type = "audio/wav" # Gemini TTS returns WAV format after conversion return StreamingResponse( _audio_speech_chunk_generator(response), # type: ignore[arg-type] From 697cb0906011cb84f2cc8c34b4ff7a2d7402dc13 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 17 Nov 2025 10:51:18 -0800 Subject: [PATCH 014/349] fix: shared_sessions not being used --- litellm/llms/openai/openai.py | 5 +++++ litellm/main.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 2949e35e5e7..3282b7665c0 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -1414,6 +1414,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): timeout: Union[float, httpx.Timeout], aspeech: Optional[bool] = None, client=None, + shared_session: Optional["ClientSession"] = None, ) -> HttpxBinaryResponseContent: if aspeech is not None and aspeech is True: return self.async_audio_speech( @@ -1428,6 +1429,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): max_retries=max_retries, timeout=timeout, client=client, + shared_session=shared_session, ) # type: ignore openai_client = self._get_openai_client( @@ -1437,6 +1439,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): timeout=timeout, max_retries=max_retries, client=client, + shared_session=shared_session, ) response = cast(OpenAI, openai_client).audio.speech.create( @@ -1460,6 +1463,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): max_retries: int, timeout: Union[float, httpx.Timeout], client=None, + shared_session: Optional["ClientSession"] = None, ) -> HttpxBinaryResponseContent: openai_client = cast( AsyncOpenAI, @@ -1470,6 +1474,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): timeout=timeout, max_retries=max_retries, client=client, + shared_session=shared_session, ), ) diff --git a/litellm/main.py b/litellm/main.py index 14d0b04b7b5..412d7f1c38e 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -5747,6 +5747,7 @@ def speech( # noqa: PLR0915 proxy_server_request = kwargs.get("proxy_server_request", None) extra_headers = kwargs.get("extra_headers", None) model_info = kwargs.get("model_info", None) + shared_session = kwargs.get("shared_session", None) model, custom_llm_provider, dynamic_api_key, api_base = get_llm_provider( model=model, custom_llm_provider=custom_llm_provider, api_base=api_base ) # type: ignore @@ -5856,6 +5857,7 @@ def speech( # noqa: PLR0915 timeout=timeout, client=client, # pass AsyncOpenAI, OpenAI client aspeech=aspeech, + shared_session=shared_session, ) elif custom_llm_provider == "azure": # Check if this is Azure Speech Service (Cognitive Services TTS) From f1895265e6b5643ef0e5be97b6f4cd65fcc2e78f Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 17 Nov 2025 12:55:58 -0800 Subject: [PATCH 015/349] fix: increase chunk_size to 8 KB for optimal latency --- litellm/proxy/proxy_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index cb43ef0cecf..16988205530 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5236,7 +5236,7 @@ async def moderations( async def _audio_speech_chunk_generator( _response: HttpxBinaryResponseContent, ) -> AsyncGenerator[bytes, None]: - _generator = await _response.aiter_bytes(chunk_size=4096) + _generator = await _response.aiter_bytes(chunk_size=8192) async for chunk in _generator: yield chunk From 7241b4e9b505ea433563a88916cece2d062cd063 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 17 Nov 2025 12:59:48 -0800 Subject: [PATCH 016/349] add: comment above optimization For anybody that would change this value for whatever reason, the comment makes the tradeoff clear. --- litellm/proxy/proxy_server.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 16988205530..da592c90720 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5236,6 +5236,10 @@ async def moderations( async def _audio_speech_chunk_generator( _response: HttpxBinaryResponseContent, ) -> AsyncGenerator[bytes, None]: + # chunk_size has a big impact on latency, it can't be too small or too large + # too small: latency is high + # too large: latency is low, but memory usage is high + # 8192 is a good compromise _generator = await _response.aiter_bytes(chunk_size=8192) async for chunk in _generator: yield chunk From b4e25a68a4690e93110fb4ca2c4aa56f1c08a25a Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 18 Nov 2025 09:40:26 -0800 Subject: [PATCH 017/349] fix: remove test files --- no_cache_hits.py | 48 --------------------------------------------- speech.mp3 | Bin 104 -> 0 bytes speech_config.yaml | 9 --------- 3 files changed, 57 deletions(-) delete mode 100644 no_cache_hits.py delete mode 100644 speech.mp3 delete mode 100644 speech_config.yaml diff --git a/no_cache_hits.py b/no_cache_hits.py deleted file mode 100644 index 1b3bf895f77..00000000000 --- a/no_cache_hits.py +++ /dev/null @@ -1,48 +0,0 @@ -from locust import HttpUser, between, task - - -class MyUser(HttpUser): - """ - Minimal Locust user for repeatedly hitting `/v1/audio/speech`. - The goal is to measure server-side performance, so we avoid any extra work - (file writes, random generation, manual timing, custom event hooks, etc.) - that could inflate client-side latency. - """ - - wait_time = between(0.5, 1) - host = "http://0.0.0.0:8090" - - def on_start(self): - self.api_key = "sk-1234" - self.model_name = "fake-openai-speech" - self.headers = { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json", - } - self.prompt_counter = 0 - - @task - def audio_speech_request(self): - self.prompt_counter += 1 - # Ensure prompts differ slightly so the backend can't reuse cached audio. - prompt = ( - "Generate a short spoken status update mentioning counter " - f"{self.prompt_counter}." - ) - - response = self.client.post( - "v1/audio/speech", - json={ - "model": self.model_name, - "input": prompt, - "voice": "alloy", - "format": "mp3", - }, - headers=self.headers, - name="audio_speech", - ) - - if response.status_code != 200: - # log the errors in error.txt - with open("error.txt", "a") as error_log: - error_log.write(response.text + "\n") \ No newline at end of file diff --git a/speech.mp3 b/speech.mp3 deleted file mode 100644 index f4f854d9bd215e2493d48b4bc4d39804bd79c038..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104 NcmezWdjbPJ000JT0*e3u diff --git a/speech_config.yaml b/speech_config.yaml deleted file mode 100644 index ad9920a2793..00000000000 --- a/speech_config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -model_list: - - model_name: fake-openai-speech - litellm_params: - model: openai/gpt-4o-mini-tts - api_base: http://0.0.0.0:8090/ - api_key: sk-1234 - model_info: - mode: audio_speech - \ No newline at end of file From 29057ba6add3dd17288e06d90424f9f2d71f4a2c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 19 Nov 2025 11:55:38 +0530 Subject: [PATCH 018/349] Added support for azure anthopic models via chat completion --- docs/my-website/docs/providers/anthropic.md | 26 +- docs/my-website/docs/providers/azure/azure.md | 14 +- .../docs/providers/azure/azure_anthropic.md | 378 ++++++++++++++++++ litellm/__init__.py | 1 + .../get_llm_provider_logic.py | 18 + litellm/llms/azure/anthropic/__init__.py | 8 + litellm/llms/azure/anthropic/handler.py | 236 +++++++++++ .../llms/azure/anthropic/transformation.py | 96 +++++ litellm/main.py | 58 +++ litellm/types/utils.py | 1 + litellm/utils.py | 2 + .../anthropic/test_azure_anthropic_handler.py | 216 ++++++++++ .../test_azure_anthropic_provider_routing.py | 82 ++++ .../test_azure_anthropic_transformation.py | 191 +++++++++ 14 files changed, 1318 insertions(+), 9 deletions(-) create mode 100644 docs/my-website/docs/providers/azure/azure_anthropic.md create mode 100644 litellm/llms/azure/anthropic/__init__.py create mode 100644 litellm/llms/azure/anthropic/handler.py create mode 100644 litellm/llms/azure/anthropic/transformation.py create mode 100644 tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py create mode 100644 tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_routing.py create mode 100644 tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py diff --git a/docs/my-website/docs/providers/anthropic.md b/docs/my-website/docs/providers/anthropic.md index afcb6a34d9c..08b52066e16 100644 --- a/docs/my-website/docs/providers/anthropic.md +++ b/docs/my-website/docs/providers/anthropic.md @@ -17,11 +17,11 @@ LiteLLM supports all anthropic models. | Property | Details | |-------|-------| -| Description | Claude is a highly performant, trustworthy, and intelligent AI platform built by Anthropic. Claude excels at tasks involving language, reasoning, analysis, coding, and more. | -| Provider Route on LiteLLM | `anthropic/` (add this prefix to the model name, to route any requests to Anthropic - e.g. `anthropic/claude-3-5-sonnet-20240620`) | -| Provider Doc | [Anthropic ↗](https://docs.anthropic.com/en/docs/build-with-claude/overview) | -| API Endpoint for Provider | https://api.anthropic.com | -| Supported Endpoints | `/chat/completions` | +| Description | Claude is a highly performant, trustworthy, and intelligent AI platform built by Anthropic. Claude excels at tasks involving language, reasoning, analysis, coding, and more. Also available via Azure Foundry. | +| Provider Route on LiteLLM | `anthropic/` (add this prefix to the model name, to route any requests to Anthropic - e.g. `anthropic/claude-3-5-sonnet-20240620`). For Azure Foundry deployments, use `azure/claude-*` (see [Azure Anthropic documentation](../providers/azure/azure_anthropic)) | +| Provider Doc | [Anthropic ↗](https://docs.anthropic.com/en/docs/build-with-claude/overview), [Azure Foundry Claude ↗](https://learn.microsoft.com/en-us/azure/ai-services/foundry-models/claude) | +| API Endpoint for Provider | https://api.anthropic.com (or Azure Foundry endpoint: `https://.services.ai.azure.com/anthropic`) | +| Supported Endpoints | `/chat/completions`, `/v1/messages` (passthrough) | ## Supported OpenAI Parameters @@ -59,6 +59,22 @@ os.environ["ANTHROPIC_API_KEY"] = "your-api-key" # os.environ["LITELLM_ANTHROPIC_DISABLE_URL_SUFFIX"] = "true" # [OPTIONAL] Disable automatic URL suffix appending ``` +:::tip Azure Foundry Support + +Claude models are also available via Microsoft Azure Foundry. Use the `azure/` prefix instead of `anthropic/` and configure Azure authentication. See the [Azure Anthropic documentation](../providers/azure/azure_anthropic) for details. + +Example: +```python +response = completion( + model="azure/claude-sonnet-4-5", + api_base="https://.services.ai.azure.com/anthropic", + api_key="your-azure-api-key", + messages=[{"role": "user", "content": "Hello!"}] +) +``` + +::: + ### Custom API Base When using a custom API base for Anthropic (e.g., a proxy or custom endpoint), LiteLLM automatically appends the appropriate suffix (`/v1/messages` or `/v1/complete`) to your base URL. diff --git a/docs/my-website/docs/providers/azure/azure.md b/docs/my-website/docs/providers/azure/azure.md index 2f845357328..d338a0287e0 100644 --- a/docs/my-website/docs/providers/azure/azure.md +++ b/docs/my-website/docs/providers/azure/azure.md @@ -9,10 +9,10 @@ import TabItem from '@theme/TabItem'; | Property | Details | |-------|-------| -| Description | Azure OpenAI Service provides REST API access to OpenAI's powerful language models including o1, o1-mini, GPT-5, GPT-4o, GPT-4o mini, GPT-4 Turbo with Vision, GPT-4, GPT-3.5-Turbo, and Embeddings model series | -| Provider Route on LiteLLM | `azure/`, [`azure/o_series/`](#o-series-models), [`azure/gpt5_series/`](#gpt-5-models) | -| Supported Operations | [`/chat/completions`](#azure-openai-chat-completion-models), [`/responses`](./azure_responses), [`/completions`](#azure-instruct-models), [`/embeddings`](./azure_embedding), [`/audio/speech`](azure_speech), [`/audio/transcriptions`](../audio_transcription), `/fine_tuning`, [`/batches`](#azure-batches-api), `/files`, [`/images`](../image_generation#azure-openai-image-generation-models) | -| Link to Provider Doc | [Azure OpenAI ↗](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) +| Description | Azure OpenAI Service provides REST API access to OpenAI's powerful language models including o1, o1-mini, GPT-5, GPT-4o, GPT-4o mini, GPT-4 Turbo with Vision, GPT-4, GPT-3.5-Turbo, and Embeddings model series. Also supports Claude models via Azure Foundry. | +| Provider Route on LiteLLM | `azure/`, [`azure/o_series/`](#o-series-models), [`azure/gpt5_series/`](#gpt-5-models), [`azure/claude-*`](./azure_anthropic) (Claude models via Azure Foundry) | +| Supported Operations | [`/chat/completions`](#azure-openai-chat-completion-models), [`/responses`](./azure_responses), [`/completions`](#azure-instruct-models), [`/embeddings`](./azure_embedding), [`/audio/speech`](azure_speech), [`/audio/transcriptions`](../audio_transcription), `/fine_tuning`, [`/batches`](#azure-batches-api), `/files`, [`/images`](../image_generation#azure-openai-image-generation-models), [`/anthropic/v1/messages`](./azure_anthropic) (Claude passthrough) | +| Link to Provider Doc | [Azure OpenAI ↗](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview), [Azure Foundry Claude ↗](https://learn.microsoft.com/en-us/azure/ai-services/foundry-models/claude) ## API Keys, Params api_key, api_base, api_version etc can be passed directly to `litellm.completion` - see here or set as `litellm.api_key` params see here @@ -27,6 +27,12 @@ os.environ["AZURE_AD_TOKEN"] = "" os.environ["AZURE_API_TYPE"] = "" ``` +:::info Azure Foundry Claude Models + +Azure also supports Claude models via Azure Foundry. Use `azure/claude-*` model names (e.g., `azure/claude-sonnet-4-5`) with Azure authentication. See the [Azure Anthropic documentation](./azure_anthropic) for details. + +::: + ## **Usage - LiteLLM Python SDK** Open In Colab diff --git a/docs/my-website/docs/providers/azure/azure_anthropic.md b/docs/my-website/docs/providers/azure/azure_anthropic.md new file mode 100644 index 00000000000..9a45e1db599 --- /dev/null +++ b/docs/my-website/docs/providers/azure/azure_anthropic.md @@ -0,0 +1,378 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Azure Anthropic (Claude via Azure Foundry) + +LiteLLM supports Claude models deployed via Microsoft Azure Foundry, including Claude Sonnet 4.5, Claude Haiku 4.5, and Claude Opus 4.1. + +## Available Models + +Azure Foundry supports the following Claude models: + +- `claude-sonnet-4-5` - Anthropic's most capable model for building real-world agents and handling complex, long-horizon tasks +- `claude-haiku-4-5` - Near-frontier performance with the right speed and cost for high-volume use cases +- `claude-opus-4-1` - Industry leader for coding, delivering sustained performance on long-running tasks + +| Property | Details | +|-------|-------| +| Description | Claude models deployed via Microsoft Azure Foundry. Uses the same API as Anthropic's Messages API but with Azure authentication. | +| Provider Route on LiteLLM | `azure/` (add this prefix to Claude model names - e.g. `azure/claude-sonnet-4-5`) | +| Provider Doc | [Azure Foundry Claude Models ↗](https://learn.microsoft.com/en-us/azure/ai-services/foundry-models/claude) | +| API Endpoint | `https://.services.ai.azure.com/anthropic/v1/messages` | +| Supported Endpoints | `/chat/completions`, `/anthropic/v1/messages` (passthrough) | + +## Key Features + +- **Extended thinking**: Enhanced reasoning capabilities for complex tasks +- **Image and text input**: Strong vision capabilities for analyzing charts, graphs, technical diagrams, and reports +- **Code generation**: Advanced thinking with code generation, analysis, and debugging (Claude Sonnet 4.5 and Claude Opus 4.1) +- **Same API as Anthropic**: All request/response transformations are identical to the main Anthropic provider + +## Authentication + +Azure Anthropic supports two authentication methods: + +1. **API Key**: Use the `api-key` header +2. **Azure AD Token**: Use `Authorization: Bearer ` header (Microsoft Entra ID) + +## API Keys and Configuration + +```python +import os + +# Option 1: API Key authentication +os.environ["AZURE_API_KEY"] = "your-azure-api-key" +os.environ["AZURE_API_BASE"] = "https://.services.ai.azure.com/anthropic" + +# Option 2: Azure AD Token authentication +os.environ["AZURE_AD_TOKEN"] = "your-azure-ad-token" +os.environ["AZURE_API_BASE"] = "https://.services.ai.azure.com/anthropic" + +# Optional: Azure AD Token Provider (for automatic token refresh) +os.environ["AZURE_TENANT_ID"] = "your-tenant-id" +os.environ["AZURE_CLIENT_ID"] = "your-client-id" +os.environ["AZURE_CLIENT_SECRET"] = "your-client-secret" +os.environ["AZURE_SCOPE"] = "https://cognitiveservices.azure.com/.default" +``` + +## Usage - LiteLLM Python SDK + +### Basic Completion + +```python +from litellm import completion + +# Set environment variables +os.environ["AZURE_API_KEY"] = "your-azure-api-key" +os.environ["AZURE_API_BASE"] = "https://.services.ai.azure.com/anthropic" + +# Make a completion request +response = completion( + model="azure/claude-sonnet-4-5", + messages=[ + {"role": "user", "content": "What are 3 things to visit in Seattle?"} + ], + max_tokens=1000, + temperature=0.7, +) + +print(response) +``` + +### Completion with API Key Parameter + +```python +import litellm + +response = litellm.completion( + model="azure/claude-sonnet-4-5", + api_base="https://.services.ai.azure.com/anthropic", + api_key="your-azure-api-key", + messages=[ + {"role": "user", "content": "Hello!"} + ], + max_tokens=1000, +) +``` + +### Completion with Azure AD Token + +```python +import litellm + +response = litellm.completion( + model="azure/claude-sonnet-4-5", + api_base="https://.services.ai.azure.com/anthropic", + azure_ad_token="your-azure-ad-token", + messages=[ + {"role": "user", "content": "Hello!"} + ], + max_tokens=1000, +) +``` + +### Streaming + +```python +from litellm import completion + +response = completion( + model="azure/claude-sonnet-4-5", + messages=[ + {"role": "user", "content": "Write a short story"} + ], + stream=True, + max_tokens=1000, +) + +for chunk in response: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="", flush=True) +``` + +### Tool Calling + +```python +from litellm import completion + +response = completion( + model="azure/claude-sonnet-4-5", + messages=[ + {"role": "user", "content": "What's the weather in Seattle?"} + ], + tools=[ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + } + }, + "required": ["location"] + } + } + } + ], + tool_choice="auto", + max_tokens=1000, +) + +print(response) +``` + +## Usage - LiteLLM Proxy Server + +### 1. Save key in your environment + +```bash +export AZURE_API_KEY="your-azure-api-key" +export AZURE_API_BASE="https://.services.ai.azure.com/anthropic" +``` + +### 2. Configure the proxy + +```yaml +model_list: + - model_name: claude-sonnet-4-5 + litellm_params: + model: azure/claude-sonnet-4-5 + api_base: https://.services.ai.azure.com/anthropic + api_key: os.environ/AZURE_API_KEY +``` + +### 3. Test it + + + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--data '{ + "model": "claude-sonnet-4-5", + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ], + "max_tokens": 1000 +}' +``` + + + + +```python +from openai import OpenAI + +client = OpenAI( + api_key="anything", + base_url="http://0.0.0.0:4000" +) + +response = client.chat.completions.create( + model="claude-sonnet-4-5", + messages=[ + {"role": "user", "content": "Hello!"} + ], + max_tokens=1000 +) + +print(response) +``` + + + + +## Messages API Passthrough + +Azure Anthropic also supports the native Anthropic Messages API via passthrough. The endpoint structure is the same as Anthropic's `/v1/messages` API. + +### Using Anthropic SDK + +```python +from anthropic import Anthropic + +client = Anthropic( + api_key="your-azure-api-key", + base_url="https://.services.ai.azure.com/anthropic" +) + +response = client.messages.create( + model="claude-sonnet-4-5", + max_tokens=1000, + messages=[ + {"role": "user", "content": "Hello, world"} + ] +) + +print(response) +``` + +### Using LiteLLM Proxy Passthrough + +```bash +curl --request POST \ + --url http://0.0.0.0:4000/anthropic/v1/messages \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header "Authorization: bearer sk-anything" \ + --data '{ + "model": "claude-sonnet-4-5", + "max_tokens": 1024, + "messages": [ + {"role": "user", "content": "Hello, world"} + ] +}' +``` + +## Supported OpenAI Parameters + +Azure Anthropic supports the same parameters as the main Anthropic provider: + +``` +"stream", +"stop", +"temperature", +"top_p", +"max_tokens", +"max_completion_tokens", +"tools", +"tool_choice", +"extra_headers", +"parallel_tool_calls", +"response_format", +"user", +"thinking", +"reasoning_effort" +``` + +:::info + +Azure Anthropic API requires `max_tokens` to be passed. LiteLLM automatically passes `max_tokens=4096` when no `max_tokens` are provided. + +::: + +## Differences from Standard Anthropic Provider + +The only difference between Azure Anthropic and the standard Anthropic provider is authentication: + +- **Standard Anthropic**: Uses `x-api-key` header +- **Azure Anthropic**: Uses `api-key` header or `Authorization: Bearer ` for Azure AD authentication + +All other request/response transformations, tool calling, streaming, and feature support are identical. + +## API Base URL Format + +The API base URL should follow this format: + +``` +https://.services.ai.azure.com/anthropic +``` + +LiteLLM will automatically append `/v1/messages` if not already present in the URL. + +## Example: Full Configuration + +```python +import os +from litellm import completion + +# Configure Azure Anthropic +os.environ["AZURE_API_KEY"] = "your-azure-api-key" +os.environ["AZURE_API_BASE"] = "https://my-resource.services.ai.azure.com/anthropic" + +# Make a request +response = completion( + model="azure/claude-sonnet-4-5", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Explain quantum computing in simple terms."} + ], + max_tokens=1000, + temperature=0.7, + stream=False, +) + +print(response.choices[0].message.content) +``` + +## Troubleshooting + +### Missing API Base Error + +If you see an error about missing API base, ensure you've set: + +```python +os.environ["AZURE_API_BASE"] = "https://.services.ai.azure.com/anthropic" +``` + +Or pass it directly: + +```python +response = completion( + model="azure/claude-sonnet-4-5", + api_base="https://.services.ai.azure.com/anthropic", + # ... +) +``` + +### Authentication Errors + +- **API Key**: Ensure `AZURE_API_KEY` is set or passed as `api_key` parameter +- **Azure AD Token**: Ensure `AZURE_AD_TOKEN` is set or passed as `azure_ad_token` parameter +- **Token Provider**: For automatic token refresh, configure `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, and `AZURE_CLIENT_SECRET` + +## Related Documentation + +- [Anthropic Provider Documentation](./anthropic.md) - For standard Anthropic API usage +- [Azure OpenAI Documentation](./azure.md) - For Azure OpenAI models +- [Azure Authentication Guide](../secret_managers/azure_key_vault.md) - For Azure AD token setup + diff --git a/litellm/__init__.py b/litellm/__init__.py index c86768490f3..929835c547e 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1108,6 +1108,7 @@ from .llms.openrouter.chat.transformation import OpenrouterConfig from .llms.datarobot.chat.transformation import DataRobotConfig from .llms.anthropic.chat.transformation import AnthropicConfig from .llms.anthropic.common_utils import AnthropicModelInfo +from .llms.azure.anthropic.transformation import AzureAnthropicConfig from .llms.groq.stt.transformation import GroqSTTConfig from .llms.anthropic.completion.transformation import AnthropicTextConfig from .llms.triton.completion.transformation import TritonConfig diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index ef0ebe074d7..de616a332d7 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -22,6 +22,19 @@ def _is_non_openai_azure_model(model: str) -> bool: return False +def _is_azure_anthropic_model(model: str) -> Optional[str]: + try: + model_parts = model.split("/", 1) + if len(model_parts) > 1: + model_name = model_parts[1].lower() + # Check if model name contains claude + if "claude" in model_name or model_name.startswith("claude"): + return model_parts[1] # Return model name without "azure/" prefix + except Exception: + pass + return None + + def handle_cohere_chat_model_custom_llm_provider( model: str, custom_llm_provider: Optional[str] = None ) -> Tuple[str, Optional[str]]: @@ -123,6 +136,11 @@ def get_llm_provider( # noqa: PLR0915 # AZURE AI-Studio Logic - Azure AI Studio supports AZURE/Cohere # If User passes azure/command-r-plus -> we should send it to cohere_chat/command-r-plus if model.split("/", 1)[0] == "azure": + # Check if it's an Azure Anthropic model (claude models) + azure_anthropic_model = _is_azure_anthropic_model(model) + if azure_anthropic_model: + custom_llm_provider = "azure_anthropic" + return azure_anthropic_model, custom_llm_provider, dynamic_api_key, api_base if _is_non_openai_azure_model(model): custom_llm_provider = "openai" return model, custom_llm_provider, dynamic_api_key, api_base diff --git a/litellm/llms/azure/anthropic/__init__.py b/litellm/llms/azure/anthropic/__init__.py new file mode 100644 index 00000000000..b40c4dfa9b3 --- /dev/null +++ b/litellm/llms/azure/anthropic/__init__.py @@ -0,0 +1,8 @@ +""" +Azure Anthropic provider - supports Claude models via Azure Foundry +""" +from .handler import AzureAnthropicChatCompletion +from .transformation import AzureAnthropicConfig + +__all__ = ["AzureAnthropicChatCompletion", "AzureAnthropicConfig"] + diff --git a/litellm/llms/azure/anthropic/handler.py b/litellm/llms/azure/anthropic/handler.py new file mode 100644 index 00000000000..cfa5eeddaa9 --- /dev/null +++ b/litellm/llms/azure/anthropic/handler.py @@ -0,0 +1,236 @@ +""" +Azure Anthropic handler - reuses AnthropicChatCompletion logic with Azure authentication +""" +import copy +import json +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union + +import httpx + +import litellm +from litellm.llms.anthropic.chat.handler import AnthropicChatCompletion +from litellm.llms.custom_httpx.http_handler import ( + AsyncHTTPHandler, + HTTPHandler, + get_async_httpx_client, +) +from litellm.types.utils import ModelResponse +from litellm.utils import CustomStreamWrapper + +from .transformation import AzureAnthropicConfig + +if TYPE_CHECKING: + from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper as CustomStreamWrapperType + from litellm.llms.base_llm.chat.transformation import BaseConfig + + +class AzureAnthropicChatCompletion(AnthropicChatCompletion): + """ + Azure Anthropic chat completion handler. + Reuses all Anthropic logic but with Azure authentication. + """ + + def __init__(self) -> None: + super().__init__() + + def completion( + self, + model: str, + messages: list, + api_base: str, + custom_llm_provider: str, + custom_prompt_dict: dict, + model_response: ModelResponse, + print_verbose: Callable, + encoding, + api_key, + logging_obj, + optional_params: dict, + timeout: Union[float, httpx.Timeout], + litellm_params: dict, + acompletion=None, + logger_fn=None, + headers={}, + client=None, + ): + """ + Completion method that uses Azure authentication instead of Anthropic's x-api-key. + All other logic is the same as AnthropicChatCompletion. + """ + from litellm.utils import ProviderConfigManager + + optional_params = copy.deepcopy(optional_params) + stream = optional_params.pop("stream", None) + json_mode: bool = optional_params.pop("json_mode", False) + is_vertex_request: bool = optional_params.pop("is_vertex_request", False) + _is_function_call = False + messages = copy.deepcopy(messages) + + # Use AzureAnthropicConfig instead of AnthropicConfig + headers = AzureAnthropicConfig().validate_environment( + api_key=api_key, + headers=headers, + model=model, + messages=messages, + optional_params={**optional_params, "is_vertex_request": is_vertex_request}, + litellm_params=litellm_params, + ) + + config = ProviderConfigManager.get_provider_chat_config( + model=model, + provider=litellm.types.utils.LlmProviders(custom_llm_provider), + ) + if config is None: + raise ValueError( + f"Provider config not found for model: {model} and provider: {custom_llm_provider}" + ) + + data = config.transform_request( + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input=messages, + api_key=api_key, + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + print_verbose(f"_is_function_call: {_is_function_call}") + if acompletion is True: + if ( + stream is True + ): # if function call - fake the streaming (need complete blocks for output parsing in openai format) + print_verbose("makes async azure anthropic streaming POST request") + data["stream"] = stream + return self.acompletion_stream_function( + model=model, + messages=messages, + data=data, + api_base=api_base, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + encoding=encoding, + api_key=api_key, + logging_obj=logging_obj, + optional_params=optional_params, + stream=stream, + _is_function_call=_is_function_call, + json_mode=json_mode, + litellm_params=litellm_params, + logger_fn=logger_fn, + headers=headers, + timeout=timeout, + client=( + client + if client is not None and isinstance(client, AsyncHTTPHandler) + else None + ), + ) + else: + return self.acompletion_function( + model=model, + messages=messages, + data=data, + api_base=api_base, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + encoding=encoding, + api_key=api_key, + provider_config=config, + logging_obj=logging_obj, + optional_params=optional_params, + stream=stream, + _is_function_call=_is_function_call, + litellm_params=litellm_params, + logger_fn=logger_fn, + headers=headers, + client=client, + json_mode=json_mode, + timeout=timeout, + ) + else: + ## COMPLETION CALL + if ( + stream is True + ): # if function call - fake the streaming (need complete blocks for output parsing in openai format) + data["stream"] = stream + # Import the make_sync_call from parent + from litellm.llms.anthropic.chat.handler import make_sync_call + + completion_stream, response_headers = make_sync_call( + client=client, + api_base=api_base, + headers=headers, # type: ignore + data=json.dumps(data), + model=model, + messages=messages, + logging_obj=logging_obj, + timeout=timeout, + json_mode=json_mode, + ) + from litellm.llms.anthropic.common_utils import process_anthropic_headers + + return CustomStreamWrapper( + completion_stream=completion_stream, + model=model, + custom_llm_provider="azure_anthropic", + logging_obj=logging_obj, + _response_headers=process_anthropic_headers(response_headers), + ) + + else: + if client is None or not isinstance(client, HTTPHandler): + from litellm.llms.custom_httpx.http_handler import _get_httpx_client + + client = _get_httpx_client(params={"timeout": timeout}) + else: + client = client + + try: + response = client.post( + api_base, + headers=headers, + data=json.dumps(data), + timeout=timeout, + ) + except Exception as e: + from litellm.llms.anthropic.common_utils import AnthropicError + + status_code = getattr(e, "status_code", 500) + error_headers = getattr(e, "headers", None) + error_text = getattr(e, "text", str(e)) + error_response = getattr(e, "response", None) + if error_headers is None and error_response: + error_headers = getattr(error_response, "headers", None) + if error_response and hasattr(error_response, "text"): + error_text = getattr(error_response, "text", error_text) + raise AnthropicError( + message=error_text, + status_code=status_code, + headers=error_headers, + ) + + return config.transform_response( + model=model, + raw_response=response, + model_response=model_response, + logging_obj=logging_obj, + api_key=api_key, + request_data=data, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + encoding=encoding, + json_mode=json_mode, + ) + diff --git a/litellm/llms/azure/anthropic/transformation.py b/litellm/llms/azure/anthropic/transformation.py new file mode 100644 index 00000000000..81beeb74ae1 --- /dev/null +++ b/litellm/llms/azure/anthropic/transformation.py @@ -0,0 +1,96 @@ +""" +Azure Anthropic transformation config - extends AnthropicConfig with Azure authentication +""" +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union + +import litellm +from litellm.llms.anthropic.chat.transformation import AnthropicConfig +from litellm.llms.azure.common_utils import BaseAzureLLM, get_azure_ad_token +from litellm.types.llms.openai import AllMessageValues +from litellm.types.router import GenericLiteLLMParams + +if TYPE_CHECKING: + pass + + +class AzureAnthropicConfig(AnthropicConfig): + """ + Azure Anthropic configuration that extends AnthropicConfig. + The only difference is authentication - Azure uses api-key header or Azure AD token + instead of x-api-key header. + """ + + @property + def custom_llm_provider(self) -> Optional[str]: + return "azure_anthropic" + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: Union[dict, GenericLiteLLMParams], + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> Dict: + """ + Validate environment and set up Azure authentication headers. + Azure supports: + 1. API key via 'api-key' header + 2. Azure AD token via 'Authorization: Bearer ' header + """ + # Convert dict to GenericLiteLLMParams if needed + if isinstance(litellm_params, dict): + # Ensure api_key is included if provided + if api_key and "api_key" not in litellm_params: + litellm_params = {**litellm_params, "api_key": api_key} + litellm_params_obj = GenericLiteLLMParams(**litellm_params) + else: + litellm_params_obj = litellm_params or GenericLiteLLMParams() + # Set api_key if provided and not already set + if api_key and not litellm_params_obj.api_key: + litellm_params_obj.api_key = api_key + + # Use Azure authentication logic + headers = BaseAzureLLM._base_validate_azure_environment( + headers=headers, litellm_params=litellm_params_obj + ) + + # Get tools and other anthropic-specific setup + tools = optional_params.get("tools") + prompt_caching_set = self.is_cache_control_set(messages=messages) + computer_tool_used = self.is_computer_tool_used(tools=tools) + mcp_server_used = self.is_mcp_server_used( + mcp_servers=optional_params.get("mcp_servers") + ) + pdf_used = self.is_pdf_used(messages=messages) + file_id_used = self.is_file_id_used(messages=messages) + user_anthropic_beta_headers = self._get_user_anthropic_beta_headers( + anthropic_beta_header=headers.get("anthropic-beta") + ) + + # Get anthropic headers (but we'll replace x-api-key with Azure auth) + anthropic_headers = self.get_anthropic_headers( + computer_tool_used=computer_tool_used, + prompt_caching_set=prompt_caching_set, + pdf_used=pdf_used, + api_key=api_key or "", # Azure auth is already in headers + file_id_used=file_id_used, + is_vertex_request=optional_params.get("is_vertex_request", False), + user_anthropic_beta_headers=user_anthropic_beta_headers, + mcp_server_used=mcp_server_used, + ) + + # Remove x-api-key from anthropic headers since Azure uses different auth + anthropic_headers.pop("x-api-key", None) + + # Merge headers - Azure auth (api-key or Authorization) takes precedence + headers = {**anthropic_headers, **headers} + + # Ensure anthropic-version header is set + if "anthropic-version" not in headers: + headers["anthropic-version"] = "2023-06-01" + + return headers + diff --git a/litellm/main.py b/litellm/main.py index 88c3f7bc55b..4857f1b9754 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -152,6 +152,7 @@ from .litellm_core_utils.prompt_templates.factory import ( ) from .litellm_core_utils.streaming_chunk_builder_utils import ChunkProcessor from .llms.anthropic.chat import AnthropicChatCompletion +from .llms.azure.anthropic.handler import AzureAnthropicChatCompletion from .llms.azure.audio_transcriptions import AzureAudioTranscription from .llms.azure.azure import AzureChatCompletion, _check_dynamic_azure_params from .llms.azure.chat.o_series_handler import AzureOpenAIO1ChatCompletion @@ -253,6 +254,7 @@ openai_image_variations = OpenAIImageVariationsHandler() groq_chat_completions = GroqChatCompletion() azure_ai_embedding = AzureAIEmbedding() anthropic_chat_completions = AnthropicChatCompletion() +azure_anthropic_chat_completions = AzureAnthropicChatCompletion() azure_chat_completions = AzureChatCompletion() azure_o1_chat_completions = AzureOpenAIO1ChatCompletion() azure_text_completions = AzureTextCompletion() @@ -2353,6 +2355,62 @@ def completion( # type: ignore # noqa: PLR0915 original_response=response, ) response = response + elif custom_llm_provider == "azure_anthropic": + # Azure Anthropic uses same API as Anthropic but with Azure authentication + api_key = ( + api_key + or litellm.azure_key + or litellm.api_key + or get_secret("AZURE_API_KEY") + or get_secret("AZURE_OPENAI_API_KEY") + ) + custom_prompt_dict = custom_prompt_dict or litellm.custom_prompt_dict + # Azure Foundry endpoint format: https://.services.ai.azure.com/anthropic/v1/messages + api_base = ( + api_base + or litellm.api_base + or get_secret("AZURE_API_BASE") + ) + + if api_base is None: + raise ValueError( + "Missing Azure API Base - Please set `api_base` or `AZURE_API_BASE` environment variable. " + "Expected format: https://.services.ai.azure.com/anthropic" + ) + + # Ensure the URL ends with /v1/messages + if not api_base.endswith("/v1/messages"): + if not api_base.endswith("/anthropic"): + api_base = api_base.rstrip("/") + "/anthropic" + api_base = api_base.rstrip("/") + "/v1/messages" + + response = azure_anthropic_chat_completions.completion( + model=model, + messages=messages, + api_base=api_base, + acompletion=acompletion, + custom_prompt_dict=litellm.custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + optional_params=optional_params, + litellm_params=litellm_params, + logger_fn=logger_fn, + encoding=encoding, # for calculating input/output tokens + api_key=api_key, + logging_obj=logging, + headers=headers, + timeout=timeout, + client=client, + custom_llm_provider=custom_llm_provider, + ) + if optional_params.get("stream", False) or acompletion is True: + ## LOGGING + logging.post_call( + input=messages, + api_key=api_key, + original_response=response, + ) + response = response elif custom_llm_provider == "nlp_cloud": nlp_cloud_key = ( api_key diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 9bef6ebef7d..3c8fa885f67 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2545,6 +2545,7 @@ class LlmProviders(str, Enum): AZURE = "azure" AZURE_TEXT = "azure_text" AZURE_AI = "azure_ai" + AZURE_ANTHROPIC = "azure_anthropic" SAGEMAKER = "sagemaker" SAGEMAKER_CHAT = "sagemaker_chat" BEDROCK = "bedrock" diff --git a/litellm/utils.py b/litellm/utils.py index 47fd06a9f27..e114e4cc051 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7148,6 +7148,8 @@ class ProviderConfigManager: return litellm.AzureAIStudioConfig() elif litellm.LlmProviders.AZURE_TEXT == provider: return litellm.AzureOpenAITextConfig() + elif litellm.LlmProviders.AZURE_ANTHROPIC == provider: + return litellm.AzureAnthropicConfig() elif litellm.LlmProviders.HOSTED_VLLM == provider: return litellm.HostedVLLMChatConfig() elif litellm.LlmProviders.NLP_CLOUD == provider: diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py new file mode 100644 index 00000000000..34aed42478d --- /dev/null +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py @@ -0,0 +1,216 @@ +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) +) + +import json +from unittest.mock import MagicMock, patch + +import pytest + +from litellm.llms.azure.anthropic.handler import AzureAnthropicChatCompletion +from litellm.types.utils import ModelResponse + + +class TestAzureAnthropicChatCompletion: + def test_inherits_from_anthropic_chat_completion(self): + """Test that AzureAnthropicChatCompletion inherits from AnthropicChatCompletion""" + handler = AzureAnthropicChatCompletion() + assert isinstance(handler, AzureAnthropicChatCompletion) + # Check that it has methods from parent class + assert hasattr(handler, "acompletion_function") + assert hasattr(handler, "acompletion_stream_function") + + @patch("litellm.utils.ProviderConfigManager") + @patch("litellm.llms.azure.anthropic.handler.AzureAnthropicConfig") + def test_completion_uses_azure_anthropic_config(self, mock_azure_config, mock_provider_manager): + """Test that completion method uses AzureAnthropicConfig""" + handler = AzureAnthropicChatCompletion() + mock_config = MagicMock() + mock_config.transform_request.return_value = {"model": "claude-sonnet-4-5", "messages": []} + mock_config.transform_response.return_value = ModelResponse() + mock_config_instance = MagicMock() + mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_azure_config.return_value = mock_config_instance + mock_provider_manager.get_provider_chat_config.return_value = mock_config + + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + api_base = "https://test.services.ai.azure.com/anthropic/v1/messages" + custom_llm_provider = "azure_anthropic" + custom_prompt_dict = {} + model_response = ModelResponse() + print_verbose = MagicMock() + encoding = MagicMock() + api_key = "test-api-key" + logging_obj = MagicMock() + optional_params = {} + timeout = 60.0 + litellm_params = {"api_key": "test-api-key"} + headers = {} + + with patch.object( + handler, "acompletion_function", return_value=ModelResponse() + ) as mock_acompletion: + handler.completion( + model=model, + messages=messages, + api_base=api_base, + custom_llm_provider=custom_llm_provider, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + encoding=encoding, + api_key=api_key, + logging_obj=logging_obj, + optional_params=optional_params, + timeout=timeout, + litellm_params=litellm_params, + headers=headers, + acompletion=True, + ) + + # Verify AzureAnthropicConfig was used + mock_azure_config.assert_called_once() + mock_config_instance.validate_environment.assert_called_once() + + @patch("litellm.llms.anthropic.chat.handler.make_sync_call") + @patch("litellm.utils.ProviderConfigManager") + @patch("litellm.llms.azure.anthropic.handler.AzureAnthropicConfig") + def test_completion_streaming(self, mock_azure_config, mock_provider_manager, mock_make_sync_call): + # Note: decorators are applied in reverse order + """Test completion with streaming""" + handler = AzureAnthropicChatCompletion() + mock_config = MagicMock() + mock_config.transform_request.return_value = { + "model": "claude-sonnet-4-5", + "messages": [], + "stream": True, + } + mock_config_instance = MagicMock() + mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_azure_config.return_value = mock_config_instance + mock_provider_manager.get_provider_chat_config.return_value = mock_config + + # Mock streaming response + mock_stream = MagicMock() + mock_headers = MagicMock() + mock_make_sync_call.return_value = (mock_stream, mock_headers) + + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + api_base = "https://test.services.ai.azure.com/anthropic/v1/messages" + custom_llm_provider = "azure_anthropic" + custom_prompt_dict = {} + model_response = ModelResponse() + print_verbose = MagicMock() + encoding = MagicMock() + api_key = "test-api-key" + logging_obj = MagicMock() + optional_params = {"stream": True} + timeout = 60.0 + litellm_params = {"api_key": "test-api-key"} + headers = {} + + result = handler.completion( + model=model, + messages=messages, + api_base=api_base, + custom_llm_provider=custom_llm_provider, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + encoding=encoding, + api_key=api_key, + logging_obj=logging_obj, + optional_params=optional_params, + timeout=timeout, + litellm_params=litellm_params, + headers=headers, + acompletion=False, + ) + + # Verify streaming was handled + mock_make_sync_call.assert_called_once() + assert result is not None + + @patch("litellm.llms.custom_httpx.http_handler._get_httpx_client") + @patch("litellm.utils.ProviderConfigManager") + @patch("litellm.llms.azure.anthropic.handler.AzureAnthropicConfig") + def test_completion_non_streaming(self, mock_azure_config, mock_provider_manager, mock_get_client): + # Note: decorators are applied in reverse order + """Test completion without streaming""" + handler = AzureAnthropicChatCompletion() + mock_config = MagicMock() + mock_config.transform_request.return_value = { + "model": "claude-sonnet-4-5", + "messages": [], + } + mock_response = ModelResponse() + mock_config.transform_response.return_value = mock_response + mock_config_instance = MagicMock() + mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_azure_config.return_value = mock_config_instance + mock_provider_manager.get_provider_chat_config.return_value = mock_config + + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + api_base = "https://test.services.ai.azure.com/anthropic/v1/messages" + custom_llm_provider = "azure_anthropic" + custom_prompt_dict = {} + model_response = ModelResponse() + print_verbose = MagicMock() + encoding = MagicMock() + api_key = "test-api-key" + logging_obj = MagicMock() + optional_params = {} + timeout = 60.0 + litellm_params = {"api_key": "test-api-key"} + headers = {} + + # Mock HTTP client + mock_client = MagicMock() + mock_response_obj = MagicMock() + mock_response_obj.status_code = 200 + mock_response_obj.text = json.dumps({ + "id": "test-id", + "model": "claude-sonnet-4-5", + "content": [{"type": "text", "text": "Hello!"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5}, + }) + mock_response_obj.json.return_value = { + "id": "test-id", + "model": "claude-sonnet-4-5", + "content": [{"type": "text", "text": "Hello!"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5}, + } + mock_client.post.return_value = mock_response_obj + mock_get_client.return_value = mock_client + + result = handler.completion( + model=model, + messages=messages, + api_base=api_base, + custom_llm_provider=custom_llm_provider, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + encoding=encoding, + api_key=api_key, + logging_obj=logging_obj, + optional_params=optional_params, + timeout=timeout, + litellm_params=litellm_params, + headers=headers, + client=None, # Let it create the client + acompletion=False, + ) + + # Verify non-streaming was handled + mock_client.post.assert_called_once() + assert result is not None + diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_routing.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_routing.py new file mode 100644 index 00000000000..a7aa2983175 --- /dev/null +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_routing.py @@ -0,0 +1,82 @@ +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) +) + +import pytest + +from litellm.litellm_core_utils.get_llm_provider_logic import _is_azure_anthropic_model, get_llm_provider + + +class TestAzureAnthropicProviderRouting: + def test_is_azure_anthropic_model_with_claude(self): + """Test _is_azure_anthropic_model detects Claude models""" + # Test various Claude model names + assert _is_azure_anthropic_model("azure/claude-sonnet-4-5") == "claude-sonnet-4-5" + assert _is_azure_anthropic_model("azure/claude-opus-4-1") == "claude-opus-4-1" + assert _is_azure_anthropic_model("azure/claude-haiku-4-5") == "claude-haiku-4-5" + assert _is_azure_anthropic_model("azure/claude-3-5-sonnet") == "claude-3-5-sonnet" + assert _is_azure_anthropic_model("azure/claude-3-opus") == "claude-3-opus" + + def test_is_azure_anthropic_model_case_insensitive(self): + """Test _is_azure_anthropic_model is case insensitive""" + assert _is_azure_anthropic_model("azure/CLAUDE-sonnet-4-5") == "CLAUDE-sonnet-4-5" + assert _is_azure_anthropic_model("azure/Claude-Sonnet-4-5") == "Claude-Sonnet-4-5" + + def test_is_azure_anthropic_model_with_non_claude(self): + """Test _is_azure_anthropic_model returns None for non-Claude models""" + assert _is_azure_anthropic_model("azure/gpt-4") is None + assert _is_azure_anthropic_model("azure/gpt-35-turbo") is None + assert _is_azure_anthropic_model("azure/command-r-plus") is None + + def test_is_azure_anthropic_model_with_invalid_format(self): + """Test _is_azure_anthropic_model handles invalid formats""" + assert _is_azure_anthropic_model("azure") is None + assert _is_azure_anthropic_model("claude-sonnet-4-5") is None + assert _is_azure_anthropic_model("") is None + + def test_get_llm_provider_routes_azure_claude_to_azure_anthropic(self): + """Test that get_llm_provider routes azure/claude-* models to azure_anthropic""" + model, provider, dynamic_api_key, api_base = get_llm_provider( + model="azure/claude-sonnet-4-5" + ) + assert provider == "azure_anthropic" + assert model == "claude-sonnet-4-5" # Should strip "azure/" prefix + + def test_get_llm_provider_routes_azure_claude_opus(self): + """Test routing for Claude Opus models""" + model, provider, dynamic_api_key, api_base = get_llm_provider( + model="azure/claude-opus-4-1" + ) + assert provider == "azure_anthropic" + assert model == "claude-opus-4-1" + + def test_get_llm_provider_routes_azure_claude_haiku(self): + """Test routing for Claude Haiku models""" + model, provider, dynamic_api_key, api_base = get_llm_provider( + model="azure/claude-haiku-4-5" + ) + assert provider == "azure_anthropic" + assert model == "claude-haiku-4-5" + + def test_get_llm_provider_does_not_route_non_claude_azure_models(self): + """Test that non-Claude Azure models are not routed to azure_anthropic""" + model, provider, dynamic_api_key, api_base = get_llm_provider( + model="azure/gpt-4" + ) + assert provider != "azure_anthropic" + # Should be routed to regular azure provider + assert provider == "azure" or provider == "openai" + + def test_get_llm_provider_with_custom_llm_provider_override(self): + """Test that custom_llm_provider parameter can override routing""" + model, provider, dynamic_api_key, api_base = get_llm_provider( + model="azure/claude-sonnet-4-5", custom_llm_provider="azure" + ) + # When custom_llm_provider is explicitly set, it should be respected + # But the routing logic should still detect it as azure_anthropic + # This depends on the order of checks in get_llm_provider + assert provider in ["azure_anthropic", "azure"] + diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py new file mode 100644 index 00000000000..43f0b5c439d --- /dev/null +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py @@ -0,0 +1,191 @@ +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) +) + +import pytest +from unittest.mock import MagicMock, patch + +from litellm.llms.azure.anthropic.transformation import AzureAnthropicConfig +from litellm.types.router import GenericLiteLLMParams + + +class TestAzureAnthropicConfig: + def test_custom_llm_provider(self): + """Test that custom_llm_provider returns 'azure_anthropic'""" + config = AzureAnthropicConfig() + assert config.custom_llm_provider == "azure_anthropic" + + def test_validate_environment_with_dict_litellm_params(self): + """Test validate_environment with dict litellm_params""" + config = AzureAnthropicConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + api_key = "test-api-key" + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + result = config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + api_key=api_key, + ) + + # Verify that dict was converted to GenericLiteLLMParams + call_args = mock_validate.call_args + assert isinstance(call_args[1]["litellm_params"], GenericLiteLLMParams) + assert call_args[1]["litellm_params"].api_key == "test-api-key" + assert "anthropic-version" in result + + def test_validate_environment_with_generic_litellm_params(self): + """Test validate_environment with GenericLiteLLMParams object""" + config = AzureAnthropicConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = GenericLiteLLMParams(api_key="test-api-key") + api_key = "test-api-key" + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + result = config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + api_key=api_key, + ) + + # Verify that GenericLiteLLMParams was passed through + call_args = mock_validate.call_args + assert isinstance(call_args[1]["litellm_params"], GenericLiteLLMParams) + assert "anthropic-version" in result + + def test_validate_environment_sets_api_key_in_litellm_params(self): + """Test that api_key parameter is set in litellm_params if provided""" + config = AzureAnthropicConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {} # Empty dict, no api_key + api_key = "provided-api-key" + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "provided-api-key"} + config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + api_key=api_key, + ) + + # Verify that api_key was set in litellm_params + call_args = mock_validate.call_args + assert call_args[1]["litellm_params"].api_key == "provided-api-key" + + def test_validate_environment_removes_x_api_key(self): + """Test that x-api-key header is removed (Azure uses api-key instead)""" + config = AzureAnthropicConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + with patch.object( + config, "get_anthropic_headers", return_value={"x-api-key": "should-be-removed"} + ): + result = config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + # Verify x-api-key was removed + assert "x-api-key" not in result + assert "api-key" in result + + def test_validate_environment_sets_anthropic_version(self): + """Test that anthropic-version header is set""" + config = AzureAnthropicConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + with patch.object(config, "get_anthropic_headers", return_value={}): + result = config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert result["anthropic-version"] == "2023-06-01" + + def test_validate_environment_preserves_existing_anthropic_version(self): + """Test that existing anthropic-version header is preserved""" + config = AzureAnthropicConfig() + headers = {"anthropic-version": "2024-01-01"} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key", "anthropic-version": "2024-01-01"} + with patch.object(config, "get_anthropic_headers", return_value={"anthropic-version": "2024-01-01"}): + result = config.validate_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert result["anthropic-version"] == "2024-01-01" + + def test_inherits_anthropic_config_methods(self): + """Test that AzureAnthropicConfig inherits methods from AnthropicConfig""" + config = AzureAnthropicConfig() + + # Test that it has AnthropicConfig methods + assert hasattr(config, "get_anthropic_headers") + assert hasattr(config, "is_cache_control_set") + assert hasattr(config, "is_computer_tool_used") + assert hasattr(config, "transform_request") + assert hasattr(config, "transform_response") + From 3e58fe42b7a4e9ddf334525e18677714d68d3af2 Mon Sep 17 00:00:00 2001 From: Chesars Date: Wed, 19 Nov 2025 17:07:46 -0300 Subject: [PATCH 019/349] fix: Support response_format parameter in completion -> responses bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #16810 ## Problem When using completion() with models that have mode: "responses" (like o3-pro, gpt-5-codex), the response_format parameter with JSON schemas was being ignored or incorrectly handled, causing: - Large schemas (>512 chars) to fail with "metadata.schema_dict_json: string too long" error - Structured outputs to be silently dropped - Users' code to break unexpectedly ## Root Cause The completion -> responses bridge in litellm/completion_extras/litellm_responses_transformation/transformation.py was missing the conversion of response_format (Chat Completion format) to text.format (Responses API format). The inverse bridge (responses -> completion) already had this conversion implemented in commit 29f0ed223a, but the completion -> responses direction was incomplete. ## Solution Added _transform_response_format_to_text_format() method that converts: - response_format with json_schema → text.format with json_schema - response_format with json_object → text.format with json_object - response_format with text → text.format with text Updated transform_request() to detect and convert response_format parameter before sending to litellm.responses(). ## Changes - Added _transform_response_format_to_text_format() method (lines 592-647) - Modified transform_request() to handle response_format (lines 199-203) - Added comprehensive tests to validate the conversion ## Testing - 5 new unit tests covering all conversion scenarios - Real API test with OpenAI confirming large schemas (>512 chars) work - No more metadata.schema_dict_json errors ## Impact Users can now use completion() with models that have mode: "responses" and: - Use large JSON schemas without hitting metadata 512 char limit - Get proper structured outputs - Have their existing code continue working --- .../transformation.py | 62 ++++++++ ...responses_transformation_transformation.py | 136 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 tests/test_litellm/completion_extras/test_litellm_responses_transformation_transformation.py diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 54d32fe3466..a6dbd5d0dcd 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -196,6 +196,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): cast(List[Dict[str, Any]], value) ) ) + elif key == "response_format": + # Convert response_format to text.format + text_format = self._transform_response_format_to_text_format(value) + if text_format: + responses_api_request["text"] = text_format # type: ignore elif key in ResponsesAPIOptionalRequestParams.__annotations__.keys(): responses_api_request[key] = value # type: ignore elif key in ("metadata"): @@ -589,6 +594,63 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return Reasoning(effort="minimal") return None + def _transform_response_format_to_text_format( + self, response_format: Union[Dict[str, Any], Any] + ) -> Optional[Dict[str, Any]]: + """ + Transform Chat Completion response_format parameter to Responses API text.format parameter. + + Chat Completion response_format structure: + { + "type": "json_schema", + "json_schema": { + "name": "schema_name", + "schema": {...}, + "strict": True + } + } + + Responses API text parameter structure: + { + "format": { + "type": "json_schema", + "name": "schema_name", + "schema": {...}, + "strict": True + } + } + """ + if not response_format: + return None + + if isinstance(response_format, dict): + format_type = response_format.get("type") + + if format_type == "json_schema": + json_schema = response_format.get("json_schema", {}) + return { + "format": { + "type": "json_schema", + "name": json_schema.get("name", "response_schema"), + "schema": json_schema.get("schema", {}), + "strict": json_schema.get("strict", False), + } + } + elif format_type == "json_object": + return { + "format": { + "type": "json_object" + } + } + elif format_type == "text": + return { + "format": { + "type": "text" + } + } + + return None + def _map_responses_status_to_finish_reason(self, status: Optional[str]) -> str: """Map responses API status to chat completion finish_reason""" if not status: diff --git a/tests/test_litellm/completion_extras/test_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/test_litellm_responses_transformation_transformation.py new file mode 100644 index 00000000000..adbaf219079 --- /dev/null +++ b/tests/test_litellm/completion_extras/test_litellm_responses_transformation_transformation.py @@ -0,0 +1,136 @@ +""" +Test for response_format to text.format conversion in completion -> responses bridge +""" +import pytest +from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, +) + + +def test_transform_response_format_to_text_format_json_schema(): + """Test conversion of response_format with json_schema to text.format""" + handler = LiteLLMResponsesTransformationHandler() + + # Chat Completion format + response_format = { + "type": "json_schema", + "json_schema": { + "name": "person_schema", + "schema": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + }, + "required": ["name", "age"], + "additionalProperties": False + }, + "strict": True + } + } + + # Convert to Responses API format + result = handler._transform_response_format_to_text_format(response_format) + + # Verify conversion + assert result is not None + assert "format" in result + assert result["format"]["type"] == "json_schema" + assert result["format"]["name"] == "person_schema" + assert result["format"]["strict"] is True + assert "schema" in result["format"] + assert result["format"]["schema"]["type"] == "object" + assert "properties" in result["format"]["schema"] + + +def test_transform_response_format_to_text_format_json_object(): + """Test conversion of response_format with json_object to text.format""" + handler = LiteLLMResponsesTransformationHandler() + + response_format = { + "type": "json_object" + } + + result = handler._transform_response_format_to_text_format(response_format) + + assert result is not None + assert "format" in result + assert result["format"]["type"] == "json_object" + + +def test_transform_response_format_to_text_format_text(): + """Test conversion of response_format with text to text.format""" + handler = LiteLLMResponsesTransformationHandler() + + response_format = { + "type": "text" + } + + result = handler._transform_response_format_to_text_format(response_format) + + assert result is not None + assert "format" in result + assert result["format"]["type"] == "text" + + +def test_transform_response_format_to_text_format_none(): + """Test that None input returns None""" + handler = LiteLLMResponsesTransformationHandler() + + result = handler._transform_response_format_to_text_format(None) + + assert result is None + + +def test_transform_request_with_response_format(): + """Test that transform_request correctly handles response_format parameter""" + handler = LiteLLMResponsesTransformationHandler() + + messages = [ + {"role": "user", "content": "Extract person info: John Doe, 30 years old"} + ] + + optional_params = { + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "person_schema", + "schema": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + }, + "required": ["name", "age"], + "additionalProperties": False + }, + "strict": True + } + } + } + + litellm_params = {} + headers = {} + + # Mock logging object + class MockLoggingObj: + pass + + litellm_logging_obj = MockLoggingObj() + + result = handler.transform_request( + model="o3-pro", + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + headers=headers, + litellm_logging_obj=litellm_logging_obj, + ) + + # Verify that text parameter was set with converted format + assert "text" in result + assert result["text"] is not None + assert "format" in result["text"] + assert result["text"]["format"]["type"] == "json_schema" + assert result["text"]["format"]["name"] == "person_schema" + assert "schema" in result["text"]["format"] From 4b80813fc20dbab597a312c8f6e5e3b29a1ecaaa Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 19 Nov 2025 13:37:51 -0800 Subject: [PATCH 020/349] Return 404 when a user is not found --- .../internal_user_endpoints.py | 9 +- .../test_internal_user_endpoints.py | 100 ++++++++++++++++-- 2 files changed, 96 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 66085b69b3d..ba71dbdc8ff 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -563,10 +563,15 @@ async def user_info( user_id = user_api_key_dict.user_id ## GET USER ROW ## + user_info = None if user_id is not None: user_info = await prisma_client.get_data(user_id=user_id) - else: - user_info = None + + if user_info is None: + raise HTTPException( + status_code=404, + detail=f"User {user_id} not found", + ) ## GET ALL TEAMS ## team_list = [] diff --git a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py index 266056bcdd2..ebce0040684 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py @@ -315,7 +315,32 @@ async def test_user_info_url_encoding_plus_character(mocker): # Mock the prisma client mock_prisma_client = mocker.MagicMock() - mock_prisma_client.get_data = mocker.AsyncMock() + + # Create a real LiteLLM_UserTable instance (BaseModel) so isinstance check passes + mock_user = LiteLLM_UserTable( + user_id="machine-user+alp-air-admin-b58-b@tempus.com", + user_email="machine-user+alp-air-admin-b58-b@tempus.com", + teams=[], + ) + + # Mock get_data to return user when called with user_id, empty list for keys + async def mock_get_data(*args, **kwargs): + if kwargs.get("table_name") == "key": + return [] + elif kwargs.get("table_name") == "team": + return [] + elif kwargs.get("user_id") is not None: + return mock_user + return None + + mock_prisma_client.get_data = mocker.AsyncMock(side_effect=mock_get_data) + + # Mock list_team to return None (patch it from where it's imported) + mock_list_team = mocker.AsyncMock(return_value=None) + mocker.patch( + "litellm.proxy.management_endpoints.team_endpoints.list_team", + mock_list_team, + ) # Patch the prisma client import in the endpoint mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) @@ -335,20 +360,73 @@ async def test_user_info_url_encoding_plus_character(mocker): "machine-user alp-air-admin-b58-b@tempus.com" # What FastAPI gives us ) expected_user_id = "machine-user+alp-air-admin-b58-b@tempus.com" - try: - response = await user_info( - user_id=decoded_user_id, + + response = await user_info( + user_id=decoded_user_id, + user_api_key_dict=mock_user_api_key_dict, + request=mock_request, + ) + + # Verify that the response contains the correct user data + # Check that get_data was called with the correct user_id (first call should be for user) + user_call = None + for call in mock_prisma_client.get_data.call_args_list: + if call.kwargs.get("user_id") and not call.kwargs.get("table_name"): + user_call = call + break + + assert user_call is not None, "get_data should be called with user_id" + assert user_call.kwargs["user_id"] == expected_user_id + + +@pytest.mark.asyncio +async def test_user_info_nonexistent_user(mocker): + """ + Test that /user/info endpoint returns 404 when a non-existent user_id is provided. + """ + from fastapi import Request + + from litellm.proxy._types import ProxyException, UserAPIKeyAuth + from litellm.proxy.management_endpoints.internal_user_endpoints import user_info + + # Mock the prisma client + mock_prisma_client = mocker.MagicMock() + + # Mock get_data to return None (user doesn't exist) + async def mock_get_data(*args, **kwargs): + if kwargs.get("table_name") == "key": + return [] + elif kwargs.get("user_id") is not None: + return None # User not found + return None + + mock_prisma_client.get_data = mocker.AsyncMock(side_effect=mock_get_data) + + # Patch the prisma client import in the endpoint + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Create a mock request + mock_request = mocker.MagicMock(spec=Request) + + # Mock user_api_key_dict + mock_user_api_key_dict = UserAPIKeyAuth( + user_id="test_admin", user_role="proxy_admin" + ) + + # Call user_info function with a non-existent user_id + nonexistent_user_id = "nonexistent-user@example.com" + + # Should raise ProxyException with 404 status code (HTTPException is converted by decorator) + with pytest.raises(ProxyException) as exc_info: + await user_info( + user_id=nonexistent_user_id, user_api_key_dict=mock_user_api_key_dict, request=mock_request, ) - except Exception as e: - print(f"Error in user_info: {e}") - # Verify that the response contains the correct user data - print( - f"mock_prisma_client.get_data.call_args: {mock_prisma_client.get_data.call_args.kwargs}" - ) - assert mock_prisma_client.get_data.call_args.kwargs["user_id"] == expected_user_id + # Verify the exception details + assert exc_info.value.code == "404" # ProxyException.code is a string + assert f"User {nonexistent_user_id} not found" in str(exc_info.value.message) @pytest.mark.asyncio From 8fd0c81e5bf2096c0c3f7b98ff0b3315f1213c94 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 20 Nov 2025 15:08:38 +0530 Subject: [PATCH 021/349] Add cost tracking for streaming in vertex ai --- .../pass_through_endpoints.py | 74 ++++- .../streaming_handler.py | 56 ++++ ...x_ai_anthropic_streaming_cost_injection.py | 279 ++++++++++++++++++ 3 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 3eee47f201b..a7df3b8cfd6 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -746,7 +746,79 @@ async def pass_through_request( # noqa: PLR0915 headers=headers, ) - response = await async_client.send(req, stream=stream) + + async def mock_vertex_anthropic_streaming_response(): + import json + async def sse_event(event, data): + return f"event: {event}\ndata: {json.dumps(data) if not isinstance(data, str) else data}\n\n" + + # Claude Sonnet 4 - public Vertex AI "Anthropic" style events + events = [ + ( + "message_start", + { + "type": "message_start", + "message": { + "model": "claude-sonnet-4-20250514", + "id": "msg_vrtx_01Dj", + "type": "message", + "role": "assistant", + "content": [], + "stop_reason": None, + "stop_sequence": None, + "usage": { + "input_tokens": 13735, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 1, + }, + }, + }, + ), + ("ping", {"type": "ping"}), + ( + "content_block_start", + { + "type": "content_block_start", + "index": 0, + "content_block": {"type": "text", "text": ""}, + }, + ), + ( + "message_delta", + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None}, + "usage": {"output_tokens": 89}, + }, + ), + ( + "message_stop", + { + "type": "message_stop" + }, + ), + ] + for event, data in events: + await asyncio.sleep(0.1) + yield await sse_event(event, data) + + class MockAsyncResponse: + # Minimal mimic of httpx.Response for streaming purposes + status_code = 200 + headers = {} + + async def aiter_bytes(self): + async for s in mock_vertex_anthropic_streaming_response(): + # Each event is a string -> bytes + yield s.encode("utf-8") + + def raise_for_status(self): + return + + response = MockAsyncResponse() + # else: + # response = await async_client.send(req, stream=stream) try: response.raise_for_status() diff --git a/litellm/proxy/pass_through_endpoints/streaming_handler.py b/litellm/proxy/pass_through_endpoints/streaming_handler.py index 2d5b0a686ce..d1b7c8962ee 100644 --- a/litellm/proxy/pass_through_endpoints/streaming_handler.py +++ b/litellm/proxy/pass_through_endpoints/streaming_handler.py @@ -4,10 +4,12 @@ from typing import List, Optional import httpx +import litellm from litellm._logging import verbose_proxy_logger from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.thread_pool_executor import executor from litellm.proxy._types import PassThroughEndpointLoggingResultValues +from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType from litellm.types.utils import StandardPassThroughResponseObject @@ -37,11 +39,35 @@ class PassThroughStreamingHandler: """ - Yields chunks from the response - Collect non-empty chunks for post-processing (logging) + - Inject cost into chunks if include_cost_in_streaming_usage is enabled """ try: raw_bytes: List[bytes] = [] + # Extract model name for cost injection + model_name = PassThroughStreamingHandler._extract_model_for_cost_injection( + request_body=request_body, + url_route=url_route, + endpoint_type=endpoint_type, + litellm_logging_obj=litellm_logging_obj, + ) + async for chunk in response.aiter_bytes(): raw_bytes.append(chunk) + if ( + getattr(litellm, "include_cost_in_streaming_usage", False) + and model_name + ): + if endpoint_type == EndpointType.VERTEX_AI: + # Only handle streamRawPredict (uses Anthropic format) + if "streamRawPredict" in url_route or "rawPredict" in url_route: + modified_chunk = ( + ProxyBaseLLMRequestProcessing._process_chunk_with_cost_injection( + chunk, model_name + ) + ) + if modified_chunk is not None: + chunk = modified_chunk + yield chunk # After all chunks are processed, handle post-processing @@ -164,6 +190,36 @@ class PassThroughStreamingHandler: **kwargs, ) + @staticmethod + def _extract_model_for_cost_injection( + request_body: Optional[dict], + url_route: str, + endpoint_type: EndpointType, + litellm_logging_obj: LiteLLMLoggingObj, + ) -> Optional[str]: + """ + Extract model name for cost injection from various sources. + """ + # Try to get model from request body + if request_body: + model = request_body.get("model") + if model: + return model + + # Try to get model from logging object + if hasattr(litellm_logging_obj, "model_call_details"): + model = litellm_logging_obj.model_call_details.get("model") + if model: + return model + + # For Vertex AI, try to extract from URL + if endpoint_type == EndpointType.VERTEX_AI: + model = VertexPassthroughLoggingHandler.extract_model_from_url(url_route) + if model and model != "unknown": + return model + + return None + @staticmethod def _convert_raw_bytes_to_str_lines(raw_bytes: List[bytes]) -> List[str]: """ diff --git a/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py b/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py new file mode 100644 index 00000000000..b9293f730d9 --- /dev/null +++ b/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py @@ -0,0 +1,279 @@ +""" +Test cost injection for Vertex AI Anthropic (streamRawPredict) passthrough streaming. + +This test verifies that cost is correctly injected into streaming chunks +for Vertex AI streamRawPredict endpoints when include_cost_in_streaming_usage is enabled. +""" + +import json +import os +import sys +from datetime import datetime +from unittest.mock import AsyncMock, MagicMock, patch + +sys.path.insert(0, os.path.abspath("../..")) + +import httpx +import pytest +import litellm +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType +from litellm.proxy.pass_through_endpoints.success_handler import ( + PassThroughEndpointLogging, +) +from litellm.proxy.pass_through_endpoints.streaming_handler import ( + PassThroughStreamingHandler, +) + + +@pytest.mark.asyncio +async def test_vertex_ai_anthropic_streaming_cost_injection_enabled(): + """ + Test that cost is injected into Vertex AI streamRawPredict streaming chunks + when include_cost_in_streaming_usage is enabled. + """ + # Enable cost injection + original_value = getattr(litellm, "include_cost_in_streaming_usage", False) + litellm.include_cost_in_streaming_usage = True + + try: + # Mock response with Anthropic SSE format chunks + response = AsyncMock(spec=httpx.Response) + + # Create chunks with message_delta event containing usage + chunks_with_usage = [ + b'data: {"type": "content_block_delta", "delta": {"text": "Hello"}}\n\n', + b'data: {"type": "message_delta", "usage": {"input_tokens": 10, "output_tokens": 5}}\n\n', + b'data: {"type": "content_block_delta", "delta": {"text": " world"}}\n\n', + ] + + async def mock_aiter_bytes(): + for chunk in chunks_with_usage: + yield chunk + + response.aiter_bytes = mock_aiter_bytes + + # Setup logging object with model info + litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) + litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.async_success_handler = AsyncMock() + + request_body = {"model": "claude-sonnet-4@20250514"} + start_time = datetime.now() + passthrough_success_handler_obj = MagicMock(spec=PassThroughEndpointLogging) + + url_route = "v1/projects/test-project/locations/us-east5/publishers/anthropic/models/claude-sonnet-4@20250514:streamRawPredict" + + # Mock completion_cost to return a test cost value + with patch("litellm.completion_cost", return_value=0.00015): + received_chunks = [] + async for chunk in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body=request_body, + litellm_logging_obj=litellm_logging_obj, + endpoint_type=EndpointType.VERTEX_AI, + start_time=start_time, + passthrough_success_handler_obj=passthrough_success_handler_obj, + url_route=url_route, + ): + received_chunks.append(chunk) + + # Verify that cost was injected into the message_delta chunk + cost_injected = False + for chunk in received_chunks: + if isinstance(chunk, bytes): + chunk_str = chunk.decode("utf-8", errors="ignore") + if "message_delta" in chunk_str and "cost" in chunk_str: + # Parse the chunk to verify cost was added + for line in chunk_str.split("\n"): + if line.startswith("data:") and "message_delta" in line: + json_part = line.split("data:", 1)[1].strip() + if json_part and json_part != "[DONE]": + try: + obj = json.loads(json_part) + if ( + obj.get("type") == "message_delta" + and "usage" in obj + and "cost" in obj["usage"] + ): + assert obj["usage"]["cost"] == 0.00015 + cost_injected = True + except json.JSONDecodeError: + pass + + assert cost_injected, "Cost was not injected into message_delta chunk" + + finally: + # Restore original value + litellm.include_cost_in_streaming_usage = original_value + + +@pytest.mark.asyncio +async def test_vertex_ai_anthropic_streaming_cost_injection_disabled(): + """ + Test that cost is NOT injected when include_cost_in_streaming_usage is disabled. + """ + # Disable cost injection + original_value = getattr(litellm, "include_cost_in_streaming_usage", False) + litellm.include_cost_in_streaming_usage = False + + try: + # Mock response with Anthropic SSE format chunks + response = AsyncMock(spec=httpx.Response) + + chunks_with_usage = [ + b'data: {"type": "message_delta", "usage": {"input_tokens": 10, "output_tokens": 5}}\n\n', + ] + + async def mock_aiter_bytes(): + for chunk in chunks_with_usage: + yield chunk + + response.aiter_bytes = mock_aiter_bytes + + litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) + litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.async_success_handler = AsyncMock() + + request_body = {"model": "claude-sonnet-4@20250514"} + start_time = datetime.now() + passthrough_success_handler_obj = MagicMock(spec=PassThroughEndpointLogging) + + url_route = "v1/projects/test-project/locations/us-east5/publishers/anthropic/models/claude-sonnet-4@20250514:streamRawPredict" + + received_chunks = [] + async for chunk in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body=request_body, + litellm_logging_obj=litellm_logging_obj, + endpoint_type=EndpointType.VERTEX_AI, + start_time=start_time, + passthrough_success_handler_obj=passthrough_success_handler_obj, + url_route=url_route, + ): + received_chunks.append(chunk) + + # Verify that cost was NOT injected + cost_found = False + for chunk in received_chunks: + if isinstance(chunk, bytes): + chunk_str = chunk.decode("utf-8", errors="ignore") + if "cost" in chunk_str: + cost_found = True + + assert not cost_found, "Cost should not be injected when feature is disabled" + + finally: + # Restore original value + litellm.include_cost_in_streaming_usage = original_value + + +@pytest.mark.asyncio +async def test_vertex_ai_anthropic_streaming_cost_injection_no_usage_chunk(): + """ + Test that chunks without usage are not modified. + """ + original_value = getattr(litellm, "include_cost_in_streaming_usage", False) + litellm.include_cost_in_streaming_usage = True + + try: + response = AsyncMock(spec=httpx.Response) + + # Chunks without usage (should not be modified) + chunks_without_usage = [ + b'data: {"type": "content_block_delta", "delta": {"text": "Hello"}}\n\n', + b'data: {"type": "content_block_start", "index": 0}\n\n', + ] + + async def mock_aiter_bytes(): + for chunk in chunks_without_usage: + yield chunk + + response.aiter_bytes = mock_aiter_bytes + + litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) + litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.async_success_handler = AsyncMock() + + request_body = {"model": "claude-sonnet-4@20250514"} + start_time = datetime.now() + passthrough_success_handler_obj = MagicMock(spec=PassThroughEndpointLogging) + + url_route = "v1/projects/test-project/locations/us-east5/publishers/anthropic/models/claude-sonnet-4@20250514:streamRawPredict" + + received_chunks = [] + async for chunk in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body=request_body, + litellm_logging_obj=litellm_logging_obj, + endpoint_type=EndpointType.VERTEX_AI, + start_time=start_time, + passthrough_success_handler_obj=passthrough_success_handler_obj, + url_route=url_route, + ): + received_chunks.append(chunk) + + # Verify chunks remain unchanged (no cost injection attempted) + assert len(received_chunks) == len(chunks_without_usage) + # Chunks should be exactly as input since they don't contain usage + for i, chunk in enumerate(received_chunks): + assert chunk == chunks_without_usage[i] + + finally: + litellm.include_cost_in_streaming_usage = original_value + + +@pytest.mark.asyncio +async def test_vertex_ai_anthropic_streaming_model_extraction(): + """ + Test that model name is correctly extracted for cost calculation. + """ + original_value = getattr(litellm, "include_cost_in_streaming_usage", False) + litellm.include_cost_in_streaming_usage = True + + try: + response = AsyncMock(spec=httpx.Response) + + chunks = [ + b'data: {"type": "message_delta", "usage": {"input_tokens": 10, "output_tokens": 5}}\n\n', + ] + + async def mock_aiter_bytes(): + for chunk in chunks: + yield chunk + + response.aiter_bytes = mock_aiter_bytes + + litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) + litellm_logging_obj.model_call_details = {} + litellm_logging_obj.async_success_handler = AsyncMock() + + # Test model extraction from request body + request_body = {"model": "claude-sonnet-4@20250514"} + start_time = datetime.now() + passthrough_success_handler_obj = MagicMock(spec=PassThroughEndpointLogging) + + url_route = "v1/projects/test-project/locations/us-east5/publishers/anthropic/models/claude-sonnet-4@20250514:streamRawPredict" + + with patch("litellm.completion_cost") as mock_cost: + mock_cost.return_value = 0.0001 + received_chunks = [] + async for chunk in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body=request_body, + litellm_logging_obj=litellm_logging_obj, + endpoint_type=EndpointType.VERTEX_AI, + start_time=start_time, + passthrough_success_handler_obj=passthrough_success_handler_obj, + url_route=url_route, + ): + received_chunks.append(chunk) + + # Verify completion_cost was called with the correct model + assert mock_cost.called + call_args = mock_cost.call_args + assert call_args[1]["model"] == "claude-sonnet-4@20250514" + + finally: + litellm.include_cost_in_streaming_usage = original_value + From 5c8b5b1b5ed446a47cf92d90ff8a7b43f2bad64d Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 20 Nov 2025 16:33:27 +0530 Subject: [PATCH 022/349] feat: Add header passing support for MCP tools in Responses API - Extract headers from secret_fields.raw_headers and tools[].headers - Merge headers from both sources before passing to MCP server - Add extract_mcp_headers_from_request utility method - Fix tool name prefix handling in _execute_tool_calls - Add get_mcp_servers_from_ids method to MCPServerManager - Ensure raw_headers is properly converted to dict before use --- .../mcp_server/mcp_server_manager.py | 21 ++++++- litellm/responses/main.py | 17 ++++++ .../mcp/litellm_proxy_mcp_handler.py | 12 +++- .../responses/mcp/mcp_streaming_iterator.py | 57 ++++++++++++++++++ litellm/responses/utils.py | 60 +++++++++++++++++++ 5 files changed, 165 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 23658b38210..aef46c82ea8 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -1412,7 +1412,7 @@ class MCPServerManager: if extra_headers is None: extra_headers = {} for header in mcp_server.extra_headers: - if header in raw_headers: + if isinstance(header, str) and header in raw_headers: extra_headers[header] = raw_headers[header] if mcp_server.static_headers: @@ -1692,6 +1692,25 @@ class MCPServerManager: return server return None + def get_mcp_servers_from_ids( + self, server_ids: List[str] + ) -> List[MCPServer]: + """ + Get MCP servers from a list of server IDs. + + Args: + server_ids: List of server IDs to retrieve + + Returns: + List of MCPServer objects corresponding to the provided IDs + """ + servers: List[MCPServer] = [] + for server_id in server_ids: + server = self.get_mcp_server_by_id(server_id) + if server: + servers.append(server) + return servers + def _generate_stable_server_id( self, server_name: str, diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 013b50aa8a9..29815330d22 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -279,10 +279,27 @@ async def aresponses_api_with_mcp( user_api_key_auth = kwargs.get("litellm_metadata", {}).get( "user_api_key_auth" ) + + # Extract MCP auth headers from the request to pass to MCP server + secret_fields = kwargs.get("secret_fields") + ( + mcp_auth_header, + mcp_server_auth_headers, + oauth2_headers, + raw_headers_from_request, + ) = ResponsesAPIRequestUtils.extract_mcp_headers_from_request( + secret_fields=secret_fields, + tools=tools, + ) + tool_results = await LiteLLM_Proxy_MCP_Handler._execute_tool_calls( tool_server_map=tool_server_map, tool_calls=tool_calls, user_api_key_auth=user_api_key_auth, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers_from_request, ) if tool_results: diff --git a/litellm/responses/mcp/litellm_proxy_mcp_handler.py b/litellm/responses/mcp/litellm_proxy_mcp_handler.py index c7322cab092..ad6ab95342c 100644 --- a/litellm/responses/mcp/litellm_proxy_mcp_handler.py +++ b/litellm/responses/mcp/litellm_proxy_mcp_handler.py @@ -397,7 +397,13 @@ class LiteLLM_Proxy_MCP_Handler: @staticmethod async def _execute_tool_calls( - tool_server_map: dict[str, str], tool_calls: List[Any], user_api_key_auth: Any + tool_server_map: dict[str, str], + tool_calls: List[Any], + user_api_key_auth: Any, + mcp_auth_header: Optional[str] = None, + mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]] = None, + oauth2_headers: Optional[Dict[str, str]] = None, + raw_headers: Optional[Dict[str, str]] = None, ) -> List[Dict[str, Any]]: """Execute tool calls and return results.""" from fastapi import HTTPException @@ -435,6 +441,10 @@ class LiteLLM_Proxy_MCP_Handler: name=tool_name, arguments=parsed_arguments, user_api_key_auth=user_api_key_auth, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers, proxy_logging_obj=proxy_logging_obj, ) diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py index ea31f0f7f1d..c00c2a2f3b2 100644 --- a/litellm/responses/mcp/mcp_streaming_iterator.py +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -299,8 +299,61 @@ class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator): "custom_llm_provider", None ) + self._extract_mcp_headers_from_params() + # Mark as async iterator self.is_async = True + + def _extract_mcp_headers_from_params(self) -> None: + """Extract MCP headers from original request params to pass to tool calls""" + from typing import Dict, Optional + from starlette.datastructures import Headers + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( + MCPRequestHandler, + ) + + # Extract headers from secret_fields in original_request_params + raw_headers_from_request: Optional[Dict[str, str]] = None + secret_fields = self.original_request_params.get("secret_fields") + if secret_fields and isinstance(secret_fields, dict): + raw_headers_from_request = secret_fields.get("raw_headers") + + # Extract MCP-specific headers + self.mcp_auth_header: Optional[str] = None + self.mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]] = None + self.oauth2_headers: Optional[Dict[str, str]] = None + self.raw_headers: Optional[Dict[str, str]] = raw_headers_from_request + + if raw_headers_from_request: + headers_obj = Headers(raw_headers_from_request) + self.mcp_auth_header = MCPRequestHandler._get_mcp_auth_header_from_headers(headers_obj) + self.mcp_server_auth_headers = MCPRequestHandler._get_mcp_server_auth_headers_from_headers(headers_obj) + self.oauth2_headers = MCPRequestHandler._get_oauth2_headers_from_headers(headers_obj) + + # Also check if headers are provided in tools array (from request body) + tools = self.original_request_params.get("tools") + if tools: + for tool in tools: + if isinstance(tool, dict) and tool.get("type") == "mcp": + tool_headers = tool.get("headers", {}) + if tool_headers and isinstance(tool_headers, dict): + # Merge tool headers into mcp_server_auth_headers + headers_obj_from_tool = Headers(tool_headers) + tool_mcp_server_auth_headers = MCPRequestHandler._get_mcp_server_auth_headers_from_headers(headers_obj_from_tool) + + if tool_mcp_server_auth_headers: + if self.mcp_server_auth_headers is None: + self.mcp_server_auth_headers = {} + # Merge the headers from tool into existing headers + for server_alias, headers_dict in tool_mcp_server_auth_headers.items(): + if server_alias not in self.mcp_server_auth_headers: + self.mcp_server_auth_headers[server_alias] = {} + self.mcp_server_auth_headers[server_alias].update(headers_dict) + + # Also merge raw headers + if self.raw_headers is None: + self.raw_headers = {} + self.raw_headers.update(tool_headers) def _should_auto_execute_tools(self) -> bool: """Check if tools should be auto-executed""" @@ -511,6 +564,10 @@ class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator): tool_server_map=self.tool_server_map, tool_calls=tool_calls, user_api_key_auth=self.user_api_key_auth, + mcp_auth_header=self.mcp_auth_header, + mcp_server_auth_headers=self.mcp_server_auth_headers, + oauth2_headers=self.oauth2_headers, + raw_headers=self.raw_headers, ) # Create completion events and output_item.done events for tool execution diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 7798a7573d1..198182cf118 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -2,6 +2,7 @@ import base64 from typing import ( Any, Dict, + Iterable, List, Optional, Type, @@ -350,6 +351,65 @@ class ResponsesAPIRequestUtils: return text return text + @staticmethod + def extract_mcp_headers_from_request( + secret_fields: Optional[Dict[str, Any]], + tools: Optional[Iterable[Any]], + ) -> tuple[ + Optional[str], + Optional[Dict[str, Dict[str, str]]], + Optional[Dict[str, str]], + Optional[Dict[str, str]], + ]: + """ + Extract MCP auth headers from the request to pass to MCP server. + Headers from tools.headers in request body should be passed to MCP server. + """ + from starlette.datastructures import Headers + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( + MCPRequestHandler, + ) + + # Extract headers from secret_fields which contains the original request headers + raw_headers_from_request: Optional[Dict[str, str]] = None + if secret_fields and isinstance(secret_fields, dict): + raw_headers_from_request = secret_fields.get("raw_headers") + + # Extract MCP-specific headers using MCPRequestHandler methods + mcp_auth_header: Optional[str] = None + mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]] = None + oauth2_headers: Optional[Dict[str, str]] = None + + if raw_headers_from_request: + headers_obj = Headers(raw_headers_from_request) + mcp_auth_header = MCPRequestHandler._get_mcp_auth_header_from_headers(headers_obj) + mcp_server_auth_headers = MCPRequestHandler._get_mcp_server_auth_headers_from_headers(headers_obj) + oauth2_headers = MCPRequestHandler._get_oauth2_headers_from_headers(headers_obj) + + if tools: + for tool in tools: + if isinstance(tool, dict) and tool.get("type") == "mcp": + tool_headers = tool.get("headers", {}) + if tool_headers and isinstance(tool_headers, dict): + # Merge tool headers into mcp_server_auth_headers + # Extract server-specific headers from tool.headers + headers_obj_from_tool = Headers(tool_headers) + tool_mcp_server_auth_headers = MCPRequestHandler._get_mcp_server_auth_headers_from_headers(headers_obj_from_tool) + if tool_mcp_server_auth_headers: + if mcp_server_auth_headers is None: + mcp_server_auth_headers = {} + # Merge the headers from tool into existing headers + for server_alias, headers_dict in tool_mcp_server_auth_headers.items(): + if server_alias not in mcp_server_auth_headers: + mcp_server_auth_headers[server_alias] = {} + mcp_server_auth_headers[server_alias].update(headers_dict) + # Also merge raw headers (non-prefixed headers from tool.headers) + if raw_headers_from_request is None: + raw_headers_from_request = {} + raw_headers_from_request.update(tool_headers) + + return mcp_auth_header, mcp_server_auth_headers, oauth2_headers, raw_headers_from_request + class ResponseAPILoggingUtils: @staticmethod From abcc2f0e6462f2144d5ddf5347115d377ba876ba Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 20 Nov 2025 21:25:22 +0530 Subject: [PATCH 023/349] remove mock response --- .../pass_through_endpoints.py | 74 +------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index a7df3b8cfd6..3eee47f201b 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -746,79 +746,7 @@ async def pass_through_request( # noqa: PLR0915 headers=headers, ) - - async def mock_vertex_anthropic_streaming_response(): - import json - async def sse_event(event, data): - return f"event: {event}\ndata: {json.dumps(data) if not isinstance(data, str) else data}\n\n" - - # Claude Sonnet 4 - public Vertex AI "Anthropic" style events - events = [ - ( - "message_start", - { - "type": "message_start", - "message": { - "model": "claude-sonnet-4-20250514", - "id": "msg_vrtx_01Dj", - "type": "message", - "role": "assistant", - "content": [], - "stop_reason": None, - "stop_sequence": None, - "usage": { - "input_tokens": 13735, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0, - "output_tokens": 1, - }, - }, - }, - ), - ("ping", {"type": "ping"}), - ( - "content_block_start", - { - "type": "content_block_start", - "index": 0, - "content_block": {"type": "text", "text": ""}, - }, - ), - ( - "message_delta", - { - "type": "message_delta", - "delta": {"stop_reason": "end_turn", "stop_sequence": None}, - "usage": {"output_tokens": 89}, - }, - ), - ( - "message_stop", - { - "type": "message_stop" - }, - ), - ] - for event, data in events: - await asyncio.sleep(0.1) - yield await sse_event(event, data) - - class MockAsyncResponse: - # Minimal mimic of httpx.Response for streaming purposes - status_code = 200 - headers = {} - - async def aiter_bytes(self): - async for s in mock_vertex_anthropic_streaming_response(): - # Each event is a string -> bytes - yield s.encode("utf-8") - - def raise_for_status(self): - return - - response = MockAsyncResponse() - # else: - # response = await async_client.send(req, stream=stream) + response = await async_client.send(req, stream=stream) try: response.raise_for_status() From 1c67b7e1daf28a9d9afc24d647adfb35c71d322b Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Thu, 20 Nov 2025 17:48:40 -0800 Subject: [PATCH 024/349] fix: place hardcoded value on constants.py --- litellm/constants.py | 1 + litellm/proxy/proxy_server.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/constants.py b/litellm/constants.py index 3f763cad926..fc26e1cf817 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -246,6 +246,7 @@ TOGETHER_AI_EMBEDDING_350_M = int(os.getenv("TOGETHER_AI_EMBEDDING_350_M", 350)) QDRANT_SCALAR_QUANTILE = float(os.getenv("QDRANT_SCALAR_QUANTILE", 0.99)) QDRANT_VECTOR_SIZE = int(os.getenv("QDRANT_VECTOR_SIZE", 1536)) CACHED_STREAMING_CHUNK_DELAY = float(os.getenv("CACHED_STREAMING_CHUNK_DELAY", 0.02)) +AUDIO_SPEECH_CHUNK_SIZE = 8192 # chunk_size for audio speech streaming. Balance between latency and memory usage MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB = int( os.getenv("MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB", 512) ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index da592c90720..47c30e9ce84 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -32,6 +32,7 @@ from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, AIOHTTP_KEEPALIVE_TIMEOUT, AIOHTTP_TTL_DNS_CACHE, + AUDIO_SPEECH_CHUNK_SIZE, BASE_MCP_ROUTE, DEFAULT_MAX_RECURSE_DEPTH, DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL, @@ -5240,7 +5241,7 @@ async def _audio_speech_chunk_generator( # too small: latency is high # too large: latency is low, but memory usage is high # 8192 is a good compromise - _generator = await _response.aiter_bytes(chunk_size=8192) + _generator = await _response.aiter_bytes(chunk_size=AUDIO_SPEECH_CHUNK_SIZE) async for chunk in _generator: yield chunk From 24f90679f8bf765e8eec9878dd6a22c7e2750b9e Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 22 Nov 2025 10:21:52 -0800 Subject: [PATCH 025/349] Changes for CI/CD Tests --- .../gcs_pub_sub_body/spend_logs_payload.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json b/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json index b25080df0d0..8b2941672b3 100644 --- a/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json +++ b/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json @@ -10,6 +10,7 @@ "model": "gpt-4o", "user": "", "team_id": "", + "organization_id": "", "metadata": "{\"applied_guardrails\": [], \"batch_models\": null, \"mcp_tool_call_metadata\": null, \"vector_store_request_metadata\": null, \"guardrail_information\": null, \"usage_object\": {\"completion_tokens\": 20, \"prompt_tokens\": 10, \"total_tokens\": 30, \"completion_tokens_details\": null, \"prompt_tokens_details\": null}, \"model_map_information\": {\"model_map_key\": \"gpt-4o\", \"model_map_value\": {\"key\": \"gpt-4o\", \"max_tokens\": 16384, \"max_input_tokens\": 128000, \"max_output_tokens\": 16384, \"input_cost_per_token\": 2.5e-06, \"cache_creation_input_token_cost\": null, \"cache_read_input_token_cost\": 1.25e-06, \"input_cost_per_character\": null, \"input_cost_per_token_above_128k_tokens\": null, \"input_cost_per_token_above_200k_tokens\": null, \"input_cost_per_query\": null, \"input_cost_per_second\": null, \"input_cost_per_audio_token\": null, \"input_cost_per_token_batches\": 1.25e-06, \"output_cost_per_token_batches\": 5e-06, \"output_cost_per_token\": 1e-05, \"output_cost_per_audio_token\": null, \"output_cost_per_character\": null, \"output_cost_per_token_above_128k_tokens\": null, \"output_cost_per_character_above_128k_tokens\": null, \"output_cost_per_token_above_200k_tokens\": null, \"output_cost_per_second\": null, \"output_cost_per_image\": null, \"output_vector_size\": null, \"litellm_provider\": \"openai\", \"mode\": \"chat\", \"supports_system_messages\": true, \"supports_response_schema\": true, \"supports_vision\": true, \"supports_function_calling\": true, \"supports_tool_choice\": true, \"supports_assistant_prefill\": false, \"supports_prompt_caching\": true, \"supports_audio_input\": false, \"supports_audio_output\": false, \"supports_pdf_input\": false, \"supports_embedding_image_input\": false, \"supports_native_streaming\": null, \"supports_web_search\": true, \"supports_reasoning\": false, \"search_context_cost_per_query\": {\"search_context_size_low\": 0.03, \"search_context_size_medium\": 0.035, \"search_context_size_high\": 0.05}, \"tpm\": null, \"rpm\": null, \"supported_openai_params\": [\"frequency_penalty\", \"logit_bias\", \"logprobs\", \"top_logprobs\", \"max_tokens\", \"max_completion_tokens\", \"modalities\", \"prediction\", \"n\", \"presence_penalty\", \"seed\", \"stop\", \"stream\", \"stream_options\", \"temperature\", \"top_p\", \"tools\", \"tool_choice\", \"function_call\", \"functions\", \"max_retries\", \"extra_headers\", \"parallel_tool_calls\", \"audio\", \"response_format\", \"user\"]}}, \"additional_usage_values\": {\"completion_tokens_details\": null, \"prompt_tokens_details\": null}}", "cache_key": "Cache OFF", "spend": 0.00022500000000000002, From dd72ff3abf5b080907103435f4b52dbbc0d57e96 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 22 Nov 2025 12:54:49 -0800 Subject: [PATCH 026/349] Add organization_id to spend logs table --- .../migration.sql | 3 +++ litellm-proxy-extras/litellm_proxy_extras/schema.prisma | 1 + litellm/proxy/schema.prisma | 1 + schema.prisma | 1 + 4 files changed, 6 insertions(+) create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20251122125322_Add organization_id to spend logs/migration.sql diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251122125322_Add organization_id to spend logs/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251122125322_Add organization_id to spend logs/migration.sql new file mode 100644 index 00000000000..4ea082f2750 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251122125322_Add organization_id to spend logs/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "LiteLLM_SpendLogs" ADD COLUMN "organization_id" TEXT; + diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index e11dca1531c..2883dfc4b82 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -304,6 +304,7 @@ model LiteLLM_SpendLogs { cache_key String? @default("") request_tags Json? @default("[]") team_id String? + organization_id String? end_user String? requester_ip_address String? messages Json? @default("{}") diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index e11dca1531c..2883dfc4b82 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -304,6 +304,7 @@ model LiteLLM_SpendLogs { cache_key String? @default("") request_tags Json? @default("[]") team_id String? + organization_id String? end_user String? requester_ip_address String? messages Json? @default("{}") diff --git a/schema.prisma b/schema.prisma index e11dca1531c..2883dfc4b82 100644 --- a/schema.prisma +++ b/schema.prisma @@ -304,6 +304,7 @@ model LiteLLM_SpendLogs { cache_key String? @default("") request_tags Json? @default("[]") team_id String? + organization_id String? end_user String? requester_ip_address String? messages Json? @default("{}") From b4b8133d47e25e8d2557de10e4a0f5a881803f9a Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 18 Nov 2025 14:47:08 -0800 Subject: [PATCH 027/349] refactor: litellm init file #1 --- litellm/__init__.py | 60 ++++++++++++++++++- litellm/images/main.py | 5 +- litellm/integrations/prometheus.py | 24 +++++++- litellm/litellm_core_utils/litellm_logging.py | 23 ++++++- litellm/main.py | 8 ++- 5 files changed, 109 insertions(+), 11 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index d93f44c37e0..739cef04e2d 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1039,8 +1039,6 @@ openai_image_generation_models = ["dall-e-2", "dall-e-3"] openai_video_generation_models = ["sora-2"] from .timeout import timeout -from .cost_calculator import completion_cost -from litellm.litellm_core_utils.litellm_logging import Logging, modify_integration from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.litellm_core_utils.core_helpers import remove_index_from_tool_calls from litellm.litellm_core_utils.token_counter import get_modified_max_tokens @@ -1449,7 +1447,6 @@ from .vector_store_files.main import ( update as vector_store_file_update, ) from .scheduler import * -from .cost_calculator import response_cost_calculator, cost_per_token ### ADAPTERS ### from .types.adapter import AdapterItem @@ -1504,3 +1501,60 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None: """Set global BitBucket configuration for prompt management.""" global global_gitlab_config global_gitlab_config = config + + +# Lazy import for cost_calculator functions to avoid loading the module at import time +# This significantly reduces memory usage when importing litellm +def _lazy_import_cost_calculator(name: str) -> Any: + """Lazy import for cost_calculator functions.""" + from .cost_calculator import ( + completion_cost as _completion_cost, + cost_per_token as _cost_per_token, + response_cost_calculator as _response_cost_calculator, + ) + + # Map names to imported functions + _cost_functions = { + "completion_cost": _completion_cost, + "cost_per_token": _cost_per_token, + "response_cost_calculator": _response_cost_calculator, + } + + # Cache the imported function in the module namespace + func = _cost_functions[name] + globals()[name] = func + + return func + + +# Lazy import for litellm_logging to avoid loading the module at import time +# This significantly reduces memory usage when importing litellm +def _lazy_import_litellm_logging(name: str) -> Any: + """Lazy import for litellm_logging module.""" + from litellm.litellm_core_utils.litellm_logging import ( + Logging as _Logging, + modify_integration as _modify_integration, + ) + + # Map names to imported objects + _logging_objects = { + "Logging": _Logging, + "modify_integration": _modify_integration, + } + + # Cache the imported object in the module namespace + obj = _logging_objects[name] + globals()[name] = obj + + return obj + + +def __getattr__(name: str) -> Any: + """Lazy import for cost_calculator and litellm_logging functions.""" + if name in ("completion_cost", "response_cost_calculator", "cost_per_token"): + return _lazy_import_cost_calculator(name) + + if name in ("Logging", "modify_integration"): + return _lazy_import_litellm_logging(name) + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/litellm/images/main.py b/litellm/images/main.py index 333a751b045..2e93765c8fb 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -6,11 +6,12 @@ from typing import Any, Coroutine, Dict, List, Literal, Optional, Union, cast, o import httpx import litellm -from litellm import Logging, client, exception_type, get_litellm_params +from litellm import client, exception_type, get_litellm_params from litellm.constants import DEFAULT_IMAGE_ENDPOINT_MODEL from litellm.constants import request_timeout as DEFAULT_REQUEST_TIMEOUT from litellm.exceptions import LiteLLMUnknownProvider -from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +# Logging is imported at module level since litellm_logging is already loaded via main.py imports +from litellm.litellm_core_utils.litellm_logging import Logging, Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.mock_functions import mock_image_generation from litellm.llms.base_llm import BaseImageEditConfig, BaseImageGenerationConfig from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 8186006f8c8..4ce818f0cef 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -24,13 +24,29 @@ from litellm.proxy._types import LiteLLM_TeamTable, UserAPIKeyAuth from litellm.types.integrations.prometheus import * from litellm.types.integrations.prometheus import _sanitize_prometheus_label_name from litellm.types.utils import StandardLoggingPayload -from litellm.utils import get_end_user_id_for_cost_tracking if TYPE_CHECKING: from apscheduler.schedulers.asyncio import AsyncIOScheduler else: AsyncIOScheduler = Any +# Cached lazy import for get_end_user_id_for_cost_tracking +# Module-level cache to avoid repeated imports while preserving memory benefits +_get_end_user_id_for_cost_tracking = None + + +def _get_cached_end_user_id_for_cost_tracking(): + """ + Get cached get_end_user_id_for_cost_tracking function. + Lazy imports on first call to avoid loading utils.py at import time (60MB saved). + Subsequent calls use cached function for better performance. + """ + global _get_end_user_id_for_cost_tracking + if _get_end_user_id_for_cost_tracking is None: + from litellm.utils import get_end_user_id_for_cost_tracking + _get_end_user_id_for_cost_tracking = get_end_user_id_for_cost_tracking + return _get_end_user_id_for_cost_tracking + class PrometheusLogger(CustomLogger): # Class variables or attributes @@ -778,6 +794,8 @@ class PrometheusLogger(CustomLogger): model = kwargs.get("model", "") litellm_params = kwargs.get("litellm_params", {}) or {} _metadata = litellm_params.get("metadata", {}) + get_end_user_id_for_cost_tracking = _get_cached_end_user_id_for_cost_tracking() + end_user_id = get_end_user_id_for_cost_tracking( litellm_params, service_type="prometheus" ) @@ -1164,6 +1182,8 @@ class PrometheusLogger(CustomLogger): "standard_logging_object", {} ) litellm_params = kwargs.get("litellm_params", {}) or {} + get_end_user_id_for_cost_tracking = _get_cached_end_user_id_for_cost_tracking() + end_user_id = get_end_user_id_for_cost_tracking( litellm_params, service_type="prometheus" ) @@ -2249,6 +2269,8 @@ def prometheus_label_factory( } if UserAPIKeyLabelNames.END_USER.value in filtered_labels: + get_end_user_id_for_cost_tracking = _get_cached_end_user_id_for_cost_tracking() + filtered_labels["end_user"] = get_end_user_id_for_cost_tracking( litellm_params={"user_api_key_end_user_id": enum_values.end_user}, service_type="prometheus", diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 9c4a7e38768..67407ff4c7a 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -58,7 +58,6 @@ from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.integrations.custom_logger import CustomLogger from litellm.integrations.deepeval.deepeval import DeepEvalLogger from litellm.integrations.mlflow import MlflowLogger -from litellm.integrations.prometheus import PrometheusLogger from litellm.integrations.sqs import SQSLogger from litellm.litellm_core_utils.get_litellm_params import get_litellm_params from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import ( @@ -247,6 +246,23 @@ class ServiceTraceIDCache: in_memory_trace_id_cache = ServiceTraceIDCache() in_memory_dynamic_logger_cache = DynamicLoggingCache() +# Cached lazy import for PrometheusLogger +# Module-level cache to avoid repeated imports while preserving memory benefits +_PrometheusLogger = None + + +def _get_cached_prometheus_logger(): + """ + Get cached PrometheusLogger class. + Lazy imports on first call to avoid loading prometheus.py and utils.py at import time (60MB saved). + Subsequent calls use cached class for better performance. + """ + global _PrometheusLogger + if _PrometheusLogger is None: + from litellm.integrations.prometheus import PrometheusLogger + _PrometheusLogger = PrometheusLogger + return _PrometheusLogger + class Logging(LiteLLMLoggingBaseClass): global supabaseClient, promptLayerLogger, weightsBiasesLogger, logfireLogger, capture_exception, add_breadcrumb, lunaryLogger, logfireLogger, prometheusLogger, slack_app @@ -3457,6 +3473,8 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 _in_memory_loggers.append(_literalai_logger) return _literalai_logger # type: ignore elif logging_integration == "prometheus": + PrometheusLogger = _get_cached_prometheus_logger() + for callback in _in_memory_loggers: if isinstance(callback, PrometheusLogger): return callback # type: ignore @@ -3934,7 +3952,8 @@ def get_custom_logger_compatible_class( # noqa: PLR0915 for callback in _in_memory_loggers: if isinstance(callback, LiteralAILogger): return callback - elif logging_integration == "prometheus" and PrometheusLogger is not None: + elif logging_integration == "prometheus": + PrometheusLogger = _get_cached_prometheus_logger() for callback in _in_memory_loggers: if isinstance(callback, PrometheusLogger): return callback diff --git a/litellm/main.py b/litellm/main.py index b082b491f24..57f8256ec35 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -53,12 +53,14 @@ from typing_extensions import overload import litellm from litellm import ( # type: ignore - Logging, client, exception_type, get_litellm_params, get_optional_params, ) +# Logging is imported lazily when needed to avoid loading litellm_logging at import time +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging from litellm.constants import ( DEFAULT_MOCK_RESPONSE_COMPLETION_TOKEN_COUNT, DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT, @@ -77,7 +79,7 @@ from litellm.litellm_core_utils.health_check_utils import ( _create_health_check_response, _filter_model_params, ) -from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj, Logging from litellm.litellm_core_utils.mock_functions import ( mock_embedding, mock_image_generation, @@ -6295,7 +6297,7 @@ def stream_chunk_builder( # noqa: PLR0915 messages: Optional[list] = None, start_time=None, end_time=None, - logging_obj: Optional[Logging] = None, + logging_obj: Optional["Logging"] = None, ) -> Optional[Union[ModelResponse, TextCompletionResponse]]: try: if chunks is None: From 863b2267f2d1df1783b9047f7eeae2cab48980fe Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 24 Nov 2025 17:01:58 -0800 Subject: [PATCH 028/349] remove comment --- litellm/images/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/images/main.py b/litellm/images/main.py index 2e93765c8fb..5b6cc995ec8 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -10,7 +10,6 @@ from litellm import client, exception_type, get_litellm_params from litellm.constants import DEFAULT_IMAGE_ENDPOINT_MODEL from litellm.constants import request_timeout as DEFAULT_REQUEST_TIMEOUT from litellm.exceptions import LiteLLMUnknownProvider -# Logging is imported at module level since litellm_logging is already loaded via main.py imports from litellm.litellm_core_utils.litellm_logging import Logging, Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.mock_functions import mock_image_generation from litellm.llms.base_llm import BaseImageEditConfig, BaseImageGenerationConfig From 8df9ff39f7d866a39cb421b4a625b6cfd119d7bd Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 24 Nov 2025 17:31:17 -0800 Subject: [PATCH 029/349] remove reduntant logging --- litellm/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/main.py b/litellm/main.py index 57f8256ec35..b3ce7d7c73b 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -79,7 +79,7 @@ from litellm.litellm_core_utils.health_check_utils import ( _create_health_check_response, _filter_model_params, ) -from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj, Logging +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.mock_functions import ( mock_embedding, mock_image_generation, From 06e302d257cdb1f91f637c2b0a88df8c9aabb3b6 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Mon, 24 Nov 2025 17:53:57 -0800 Subject: [PATCH 030/349] fix: use LiteLLMLoggingObj instead of Logging in runtime type annotations - Replace Logging type annotations with LiteLLMLoggingObj in main.py (lines 1157, 4097, 5811) - Fixes NameError: name 'Logging' is not defined errors - Maintains lazy loading benefits - Logging only loaded when accessed via litellm.Logging - Add error handling to lazy import functions for better debugging --- litellm/__init__.py | 45 ++++++++++++++++++++++++++++----------------- litellm/main.py | 6 +++--- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 739cef04e2d..8e8815b1257 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1531,26 +1531,37 @@ def _lazy_import_cost_calculator(name: str) -> Any: # This significantly reduces memory usage when importing litellm def _lazy_import_litellm_logging(name: str) -> Any: """Lazy import for litellm_logging module.""" - from litellm.litellm_core_utils.litellm_logging import ( - Logging as _Logging, - modify_integration as _modify_integration, - ) - - # Map names to imported objects - _logging_objects = { - "Logging": _Logging, - "modify_integration": _modify_integration, - } - - # Cache the imported object in the module namespace - obj = _logging_objects[name] - globals()[name] = obj - - return obj + try: + from litellm.litellm_core_utils.litellm_logging import ( + Logging as _Logging, + modify_integration as _modify_integration, + ) + + # Map names to imported objects + _logging_objects = { + "Logging": _Logging, + "modify_integration": _modify_integration, + } + + # Cache the imported object in the module namespace + obj = _logging_objects[name] + globals()[name] = obj + + return obj + except Exception as e: + # If lazy import fails, raise a more informative error + raise AttributeError( + f"module {__name__!r} has no attribute {name!r}. " + f"Lazy import failed: {e}" + ) from e def __getattr__(name: str) -> Any: - """Lazy import for cost_calculator and litellm_logging functions.""" + """Lazy import for cost_calculator and litellm_logging functions. + + This allows these heavy modules to be loaded only when accessed, + reducing initial import time and memory usage. + """ if name in ("completion_cost", "response_cost_calculator", "cost_per_token"): return _lazy_import_cost_calculator(name) diff --git a/litellm/main.py b/litellm/main.py index b3ce7d7c73b..88bf0b72a0b 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1154,7 +1154,7 @@ def completion( # type: ignore # noqa: PLR0915 api_base = base_url if num_retries is not None: max_retries = num_retries - logging: Logging = cast(Logging, litellm_logging_obj) + logging: LiteLLMLoggingObj = cast(LiteLLMLoggingObj, litellm_logging_obj) fallbacks = fallbacks or litellm.model_fallbacks if fallbacks is not None: return completion_with_fallbacks(**args) @@ -4094,7 +4094,7 @@ def embedding( # noqa: PLR0915 litellm_params_dict = get_litellm_params(**kwargs) - logging: Logging = litellm_logging_obj # type: ignore + logging: LiteLLMLoggingObj = litellm_logging_obj # type: ignore logging.update_environment_variables( model=model, user=user, @@ -5808,7 +5808,7 @@ def speech( # noqa: PLR0915 kwargs=kwargs, ) - logging_obj: Logging = cast(Logging, kwargs.get("litellm_logging_obj")) + logging_obj: LiteLLMLoggingObj = cast(LiteLLMLoggingObj, kwargs.get("litellm_logging_obj")) logging_obj.update_environment_variables( model=model, user=user, From 031bb3c5d90e429bf65210302ecae80e46cc505c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 24 Nov 2025 22:40:46 -0800 Subject: [PATCH 031/349] Test prisma changes for Gemini tests --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index a518628afb9..d756755c552 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3047,6 +3047,7 @@ jobs: docker run -d \ -p 4000:4000 \ -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \ + -e USE_PRISMA_MIGRATE=True \ -e LITELLM_MASTER_KEY="sk-1234" \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ -e GEMINI_API_KEY=$GEMINI_API_KEY \ From 29ab291cf5c6e4895e587b982b26792d730da34b Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:31:16 +0530 Subject: [PATCH 032/349] Add vertex ai image support --- litellm/images/main.py | 45 +-- .../vertex_ai/image_generation/__init__.py | 43 +++ .../vertex_gemini_transformation.py | 266 ++++++++++++++++++ .../vertex_imagen_transformation.py | 231 +++++++++++++++ litellm/utils.py | 8 +- 5 files changed, 550 insertions(+), 43 deletions(-) create mode 100644 litellm/llms/vertex_ai/image_generation/__init__.py create mode 100644 litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py create mode 100644 litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py diff --git a/litellm/images/main.py b/litellm/images/main.py index 333a751b045..786136e6699 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -19,6 +19,8 @@ from litellm.llms.custom_llm import CustomLLM #################### Initialize provider clients #################### llm_http_handler: BaseLLMHTTPHandler = BaseLLMHTTPHandler() +from openai.types.audio.transcription_create_params import FileTypes # type: ignore + from litellm.main import ( azure_chat_completions, base_llm_aiohttp_handler, @@ -26,7 +28,6 @@ from litellm.main import ( bedrock_image_generation, openai_chat_completions, openai_image_variations, - vertex_image_generation, ) ########################################### @@ -36,7 +37,6 @@ from litellm.types.llms.openai import ImageGenerationRequestQuality from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import ( LITELLM_IMAGE_VARIATION_PROVIDERS, - FileTypes, LlmProviders, all_litellm_params, ) @@ -344,6 +344,7 @@ def image_generation( # noqa: PLR0915 litellm.LlmProviders.GEMINI, litellm.LlmProviders.FAL_AI, litellm.LlmProviders.RUNWAYML, + litellm.LlmProviders.VERTEX_AI, ): if image_generation_config is None: raise ValueError( @@ -430,46 +431,6 @@ def image_generation( # noqa: PLR0915 api_base=api_base, api_key=api_key, ) - elif custom_llm_provider == "vertex_ai": - vertex_ai_project = ( - optional_params.pop("vertex_project", None) - or optional_params.pop("vertex_ai_project", None) - or litellm.vertex_project - or get_secret_str("VERTEXAI_PROJECT") - ) - vertex_ai_location = ( - optional_params.pop("vertex_location", None) - or optional_params.pop("vertex_ai_location", None) - or litellm.vertex_location - or get_secret_str("VERTEXAI_LOCATION") - ) - vertex_credentials = ( - optional_params.pop("vertex_credentials", None) - or optional_params.pop("vertex_ai_credentials", None) - or get_secret_str("VERTEXAI_CREDENTIALS") - ) - - api_base = ( - api_base - or litellm.api_base - or get_secret_str("VERTEXAI_API_BASE") - or get_secret_str("VERTEX_API_BASE") - ) - - model_response = vertex_image_generation.image_generation( - model=model, - prompt=prompt, - timeout=timeout, - logging_obj=litellm_logging_obj, - optional_params=optional_params, - model_response=model_response, - vertex_project=vertex_ai_project, - vertex_location=vertex_ai_location, - vertex_credentials=vertex_credentials, - aimg_generation=aimg_generation, - api_base=api_base, - client=client, - ) elif ( custom_llm_provider in litellm._custom_providers ): # Assume custom LLM provider diff --git a/litellm/llms/vertex_ai/image_generation/__init__.py b/litellm/llms/vertex_ai/image_generation/__init__.py new file mode 100644 index 00000000000..a6f6156167a --- /dev/null +++ b/litellm/llms/vertex_ai/image_generation/__init__.py @@ -0,0 +1,43 @@ +from litellm.llms.base_llm.image_generation.transformation import ( + BaseImageGenerationConfig, +) +from litellm.llms.vertex_ai.common_utils import ( + VertexAIModelRoute, + get_vertex_ai_model_route, +) + +from .vertex_gemini_transformation import VertexAIGeminiImageGenerationConfig +from .vertex_imagen_transformation import VertexAIImagenImageGenerationConfig + +__all__ = [ + "VertexAIGeminiImageGenerationConfig", + "VertexAIImagenImageGenerationConfig", + "get_vertex_ai_image_generation_config", +] + + +def get_vertex_ai_image_generation_config(model: str) -> BaseImageGenerationConfig: + """ + Get the appropriate image generation config for a Vertex AI model. + + Routes to the correct transformation class based on the model type: + - Gemini image generation models use generateContent API (VertexAIGeminiImageGenerationConfig) + - Imagen models use predict API (VertexAIImagenImageGenerationConfig) + + Args: + model: The model name (e.g., "gemini-2.5-flash-image", "imagegeneration@006") + + Returns: + BaseImageGenerationConfig: The appropriate configuration class + """ + # Determine the model route + model_route = get_vertex_ai_model_route(model) + + if model_route == VertexAIModelRoute.GEMINI: + # Gemini models use generateContent API + return VertexAIGeminiImageGenerationConfig() + else: + # Default to Imagen for other models (imagegeneration, etc.) + # This includes NON_GEMINI models like imagegeneration@006 + return VertexAIImagenImageGenerationConfig() + diff --git a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py new file mode 100644 index 00000000000..0a87dea997a --- /dev/null +++ b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py @@ -0,0 +1,266 @@ +import json +import os +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import httpx + +import litellm +from litellm.llms.base_llm.image_generation.transformation import ( + BaseImageGenerationConfig, +) +from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ( + AllMessageValues, + OpenAIImageGenerationOptionalParams, +) +from litellm.types.utils import ImageObject, ImageResponse +from litellm.utils import ModelResponse + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): + """ + Vertex AI Gemini Image Generation Configuration + + Uses generateContent API for Gemini image generation models on Vertex AI + Supports models like gemini-2.5-flash-image, gemini-3-pro-image-preview, etc. + """ + + def __init__(self) -> None: + BaseImageGenerationConfig.__init__(self) + VertexLLM.__init__(self) + + def get_supported_openai_params( + self, model: str + ) -> List[OpenAIImageGenerationOptionalParams]: + """ + Gemini image generation supported parameters + """ + return [ + "n", + "size", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_params = self.get_supported_openai_params(model) + mapped_params = {} + + for k, v in non_default_params.items(): + if k not in optional_params.keys(): + if k in supported_params: + # Map OpenAI parameters to Gemini format + if k == "n": + mapped_params["candidate_count"] = v + elif k == "size": + # Map OpenAI size format to Gemini aspectRatio + mapped_params["aspectRatio"] = self._map_size_to_aspect_ratio(v) + else: + mapped_params[k] = v + + return mapped_params + + def _map_size_to_aspect_ratio(self, size: str) -> str: + """ + Map OpenAI size format to Gemini aspect ratio format + """ + aspect_ratio_map = { + "1024x1024": "1:1", + "1792x1024": "16:9", + "1024x1792": "9:16", + "1280x896": "4:3", + "896x1280": "3:4" + } + return aspect_ratio_map.get(size, "1:1") + + def _resolve_vertex_project(self) -> Optional[str]: + return ( + getattr(self, "_vertex_project", None) + or os.environ.get("VERTEXAI_PROJECT") + or getattr(litellm, "vertex_project", None) + or get_secret_str("VERTEXAI_PROJECT") + ) + + def _resolve_vertex_location(self) -> Optional[str]: + return ( + getattr(self, "_vertex_location", None) + or os.environ.get("VERTEXAI_LOCATION") + or os.environ.get("VERTEX_LOCATION") + or getattr(litellm, "vertex_location", None) + or get_secret_str("VERTEXAI_LOCATION") + or get_secret_str("VERTEX_LOCATION") + ) + + def _resolve_vertex_credentials(self) -> Optional[str]: + return ( + getattr(self, "_vertex_credentials", None) + or os.environ.get("VERTEXAI_CREDENTIALS") + or getattr(litellm, "vertex_credentials", None) + or os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") + or get_secret_str("VERTEXAI_CREDENTIALS") + ) + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for Vertex AI Gemini generateContent API + """ + vertex_project = self._resolve_vertex_project() + vertex_location = self._resolve_vertex_location() + + if not vertex_project or not vertex_location: + raise ValueError("vertex_project and vertex_location are required for Vertex AI") + + # Use the model name as provided, handling vertex_ai prefix + model_name = model + if model.startswith("vertex_ai/"): + model_name = model.replace("vertex_ai/", "") + + if api_base: + base_url = api_base.rstrip("/") + else: + base_url = f"https://{vertex_location}-aiplatform.googleapis.com" + + return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:generateContent" + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + headers = headers or {} + vertex_project = self._resolve_vertex_project() + vertex_credentials = self._resolve_vertex_credentials() + access_token, _ = self._ensure_access_token( + credentials=vertex_credentials, + project_id=vertex_project, + custom_llm_provider="vertex_ai", + ) + return self.set_headers(access_token, headers) + + def transform_image_generation_request( + self, + model: str, + prompt: str, + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + """ + Transform the image generation request to Gemini format + + Uses generateContent API with responseModalities: ["IMAGE"] + """ + # Prepare messages with the prompt + contents = [ + { + "role": "user", + "parts": [{"text": prompt}] + } + ] + + # Prepare generation config + generation_config: Dict[str, Any] = { + "responseModalities": ["IMAGE"] + } + + # Handle image-specific config parameters + image_config: Dict[str, Any] = {} + + # Map aspectRatio + if "aspectRatio" in optional_params: + image_config["aspectRatio"] = optional_params["aspectRatio"] + elif "aspect_ratio" in optional_params: + image_config["aspectRatio"] = optional_params["aspect_ratio"] + + # Map imageSize (for Gemini 3 Pro) + if "imageSize" in optional_params: + image_config["imageSize"] = optional_params["imageSize"] + elif "image_size" in optional_params: + image_config["imageSize"] = optional_params["image_size"] + + if image_config: + generation_config["imageConfig"] = image_config + + # Handle candidate_count (n parameter) + if "candidate_count" in optional_params: + generation_config["candidateCount"] = optional_params["candidate_count"] + elif "n" in optional_params: + generation_config["candidateCount"] = optional_params["n"] + + request_body: Dict[str, Any] = { + "contents": contents, + "generationConfig": generation_config + } + + return request_body + + def transform_image_generation_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ImageResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ImageResponse: + """ + Transform Gemini image generation response to litellm ImageResponse format + """ + try: + response_data = raw_response.json() + except Exception as e: + raise self.get_error_class( + error_message=f"Error transforming image generation response: {e}", + status_code=raw_response.status_code, + headers=raw_response.headers, + ) + + if not model_response.data: + model_response.data = [] + + # Gemini image generation models return in candidates format + candidates = response_data.get("candidates", []) + for candidate in candidates: + content = candidate.get("content", {}) + parts = content.get("parts", []) + for part in parts: + # Look for inlineData with image + if "inlineData" in part: + inline_data = part["inlineData"] + if "data" in inline_data: + model_response.data.append(ImageObject( + b64_json=inline_data["data"], + url=None, + )) + + return model_response + diff --git a/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py new file mode 100644 index 00000000000..275c547c8dd --- /dev/null +++ b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py @@ -0,0 +1,231 @@ +import json +import os +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import httpx + +import litellm +from litellm.llms.base_llm.image_generation.transformation import ( + BaseImageGenerationConfig, +) +from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ( + AllMessageValues, + OpenAIImageGenerationOptionalParams, +) +from litellm.types.utils import ImageObject, ImageResponse + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class VertexAIImagenImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): + """ + Vertex AI Imagen Image Generation Configuration + + Uses predict API for Imagen models on Vertex AI + Supports models like imagegeneration@006 + """ + + def __init__(self) -> None: + BaseImageGenerationConfig.__init__(self) + VertexLLM.__init__(self) + + def get_supported_openai_params( + self, model: str + ) -> List[OpenAIImageGenerationOptionalParams]: + """ + Imagen API supported parameters + """ + return [ + "n", + "size" + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_params = self.get_supported_openai_params(model) + mapped_params = {} + + for k, v in non_default_params.items(): + if k not in optional_params.keys(): + if k in supported_params: + # Map OpenAI parameters to Imagen format + if k == "n": + mapped_params["sampleCount"] = v + elif k == "size": + # Map OpenAI size format to Imagen aspectRatio + mapped_params["aspectRatio"] = self._map_size_to_aspect_ratio(v) + else: + mapped_params[k] = v + + return mapped_params + + def _map_size_to_aspect_ratio(self, size: str) -> str: + """ + Map OpenAI size format to Imagen aspect ratio format + """ + aspect_ratio_map = { + "1024x1024": "1:1", + "1792x1024": "16:9", + "1024x1792": "9:16", + "1280x896": "4:3", + "896x1280": "3:4" + } + return aspect_ratio_map.get(size, "1:1") + + def _resolve_vertex_project(self) -> Optional[str]: + return ( + getattr(self, "_vertex_project", None) + or os.environ.get("VERTEXAI_PROJECT") + or getattr(litellm, "vertex_project", None) + or get_secret_str("VERTEXAI_PROJECT") + ) + + def _resolve_vertex_location(self) -> Optional[str]: + return ( + getattr(self, "_vertex_location", None) + or os.environ.get("VERTEXAI_LOCATION") + or os.environ.get("VERTEX_LOCATION") + or getattr(litellm, "vertex_location", None) + or get_secret_str("VERTEXAI_LOCATION") + or get_secret_str("VERTEX_LOCATION") + ) + + def _resolve_vertex_credentials(self) -> Optional[str]: + return ( + getattr(self, "_vertex_credentials", None) + or os.environ.get("VERTEXAI_CREDENTIALS") + or getattr(litellm, "vertex_credentials", None) + or os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") + or get_secret_str("VERTEXAI_CREDENTIALS") + ) + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for Vertex AI Imagen predict API + """ + vertex_project = self._resolve_vertex_project() + vertex_location = self._resolve_vertex_location() + + if not vertex_project or not vertex_location: + raise ValueError("vertex_project and vertex_location are required for Vertex AI") + + # Use the model name as provided, handling vertex_ai prefix + model_name = model + if model.startswith("vertex_ai/"): + model_name = model.replace("vertex_ai/", "") + + if api_base: + base_url = api_base.rstrip("/") + else: + base_url = f"https://{vertex_location}-aiplatform.googleapis.com" + + return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:predict" + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + headers = headers or {} + vertex_project = self._resolve_vertex_project() + vertex_credentials = self._resolve_vertex_credentials() + access_token, _ = self._ensure_access_token( + credentials=vertex_credentials, + project_id=vertex_project, + custom_llm_provider="vertex_ai", + ) + return self.set_headers(access_token, headers) + + def transform_image_generation_request( + self, + model: str, + prompt: str, + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + """ + Transform the image generation request to Imagen format + + Uses predict API with instances and parameters + """ + # Default parameters + default_params = { + "sampleCount": 1, + } + + # Merge with optional params + parameters = {**default_params, **optional_params} + + request_body = { + "instances": [{"prompt": prompt}], + "parameters": parameters, + } + + return request_body + + def transform_image_generation_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ImageResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ImageResponse: + """ + Transform Imagen image generation response to litellm ImageResponse format + """ + try: + response_data = raw_response.json() + except Exception as e: + raise self.get_error_class( + error_message=f"Error transforming image generation response: {e}", + status_code=raw_response.status_code, + headers=raw_response.headers, + ) + + if not model_response.data: + model_response.data = [] + + # Imagen format - predictions with generated images + predictions = response_data.get("predictions", []) + for prediction in predictions: + # Imagen returns images as bytesBase64Encoded + if "bytesBase64Encoded" in prediction: + model_response.data.append(ImageObject( + b64_json=prediction["bytesBase64Encoded"], + url=None, + )) + + return model_response + diff --git a/litellm/utils.py b/litellm/utils.py index 1ec2576d356..3bc570bd700 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6866,7 +6866,7 @@ def convert_to_dict(message: Union[BaseModel, dict]) -> dict: dict: The converted message. """ if isinstance(message, BaseModel): - return message.model_dump(exclude_none=True) + return message.model_dump(exclude_none=True) # type: ignore elif isinstance(message, dict): return message else: @@ -7671,6 +7671,12 @@ class ProviderConfigManager: ) return get_runwayml_image_generation_config(model) + elif LlmProviders.VERTEX_AI == provider: + from litellm.llms.vertex_ai.image_generation import ( + get_vertex_ai_image_generation_config, + ) + + return get_vertex_ai_image_generation_config(model) return None @staticmethod From f52f05748dc688895c7a26a4b65d8ea8d8c0b59c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:31:50 +0530 Subject: [PATCH 033/349] Update docs related to vertex ai image gen --- .../my-website/docs/providers/vertex_image.md | 73 +++++++++++++++++-- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/docs/my-website/docs/providers/vertex_image.md b/docs/my-website/docs/providers/vertex_image.md index 27e584cb222..c4d5d554088 100644 --- a/docs/my-website/docs/providers/vertex_image.md +++ b/docs/my-website/docs/providers/vertex_image.md @@ -1,18 +1,65 @@ # Vertex AI Image Generation -Vertex AI Image Generation uses Google's Imagen models to generate high-quality images from text descriptions. +Vertex AI supports two types of image generation: + +1. **Gemini Image Generation Models** (Nano Banana 🍌) - Conversational image generation using `generateContent` API +2. **Imagen Models** - Traditional image generation using `predict` API | Property | Details | |----------|---------| -| Description | Vertex AI Image Generation uses Google's Imagen models to generate high-quality images from text descriptions. | +| Description | Vertex AI Image Generation supports both Gemini image generation models | | Provider Route on LiteLLM | `vertex_ai/` | | Provider Doc | [Google Cloud Vertex AI Image Generation ↗](https://cloud.google.com/vertex-ai/docs/generative-ai/image/generate-images) | +| Gemini Image Generation Docs | [Gemini Image Generation ↗](https://ai.google.dev/gemini-api/docs/image-generation) | ## Quick Start -### LiteLLM Python SDK +### Gemini Image Generation Models -```python showLineNumbers title="Basic Image Generation" +Gemini image generation models support conversational image creation with features like: +- Text-to-Image generation +- Image editing (text + image → image) +- Multi-turn image refinement +- High-fidelity text rendering +- Up to 4K resolution (Gemini 3 Pro) + +```python showLineNumbers title="Gemini 2.5 Flash Image" +import litellm + +# Generate a single image +response = await litellm.aimage_generation( + prompt="A nano banana dish in a fancy restaurant with a Gemini theme", + model="vertex_ai/gemini-2.5-flash-image", + vertex_ai_project="your-project-id", + vertex_ai_location="us-central1", + n=1, + size="1024x1024", +) + +print(response.data[0].b64_json) # Gemini returns base64 images +``` + +```python showLineNumbers title="Gemini 3 Pro Image Preview (4K output)" +import litellm + +# Generate high-resolution image +response = await litellm.aimage_generation( + prompt="Da Vinci style anatomical sketch of a dissected Monarch butterfly", + model="vertex_ai/gemini-3-pro-image-preview", + vertex_ai_project="your-project-id", + vertex_ai_location="us-central1", + n=1, + size="1024x1024", + # Optional: specify image size for Gemini 3 Pro + # imageSize="4K", # Options: "1K", "2K", "4K" +) + +print(response.data[0].b64_json) +``` + +### Imagen Models + +```python showLineNumbers title="Imagen Image Generation" import litellm # Generate a single image @@ -21,9 +68,11 @@ response = await litellm.aimage_generation( model="vertex_ai/imagen-4.0-generate-001", vertex_ai_project="your-project-id", vertex_ai_location="us-central1", + n=1, + size="1024x1024", ) -print(response.data[0].url) +print(response.data[0].b64_json) # Imagen also returns base64 images ``` ### LiteLLM Proxy @@ -70,6 +119,18 @@ print(response.data[0].url) ## Supported Models +### Gemini Image Generation Models + +- `vertex_ai/gemini-2.5-flash-image` - Fast, efficient image generation (1024px resolution) +- `vertex_ai/gemini-3-pro-image-preview` - Advanced model with 4K output, Google Search grounding, and thinking mode +- `vertex_ai/gemini-2.0-flash-preview-image` - Preview model +- `vertex_ai/gemini-2.5-flash-image-preview` - Preview model + +### Imagen Models + +- `vertex_ai/imagegeneration@006` - Legacy Imagen model +- `vertex_ai/imagen-4.0-generate-001` - Latest Imagen model +- `vertex_ai/imagen-3.0-generate-001` - Imagen 3.0 model :::tip @@ -77,7 +138,5 @@ print(response.data[0].url) ::: -LiteLLM supports all Vertex AI Imagen models available through Google Cloud. - For the complete and up-to-date list of supported models, visit: [https://models.litellm.ai/](https://models.litellm.ai/) From 883cfaeeafa2ecb16f91a540be63dfbfa644001c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:32:13 +0530 Subject: [PATCH 034/349] Add tests --- .../image_gen_tests/test_image_generation.py | 32 ++ ...rtex_ai_image_generation_transformation.py | 457 ++++++++++++++++++ 2 files changed, 489 insertions(+) create mode 100644 tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py diff --git a/tests/image_gen_tests/test_image_generation.py b/tests/image_gen_tests/test_image_generation.py index 24d5843293b..add60c755be 100644 --- a/tests/image_gen_tests/test_image_generation.py +++ b/tests/image_gen_tests/test_image_generation.py @@ -119,6 +119,38 @@ class TestVertexImageGeneration(BaseImageGenTest): } +class TestVertexAIGeminiImageGeneration(BaseImageGenTest): + """Test Gemini image generation models (Nano Banana)""" + def get_base_image_generation_call_args(self) -> dict: + # comment this when running locally + load_vertex_ai_credentials() + + litellm.in_memory_llm_clients_cache = InMemoryCache() + return { + "model": "vertex_ai/gemini-2.5-flash-image", + "vertex_ai_project": "pathrise-convert-1606954137718", + "vertex_ai_location": "us-central1", + "n": 1, + "size": "1024x1024", + } + + +class TestVertexAIGemini3ProImageGeneration(BaseImageGenTest): + """Test Gemini 3 Pro image generation model""" + def get_base_image_generation_call_args(self) -> dict: + # comment this when running locally + load_vertex_ai_credentials() + + litellm.in_memory_llm_clients_cache = InMemoryCache() + return { + "model": "vertex_ai/gemini-3-pro-image-preview", + "vertex_ai_project": "pathrise-convert-1606954137718", + "vertex_ai_location": "us-central1", + "n": 1, + "size": "1024x1024", + } + + class TestBedrockNovaCanvasTextToImage(BaseImageGenTest): def get_base_image_generation_call_args(self) -> dict: litellm.in_memory_llm_clients_cache = InMemoryCache() diff --git a/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py b/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py new file mode 100644 index 00000000000..9f33400594b --- /dev/null +++ b/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py @@ -0,0 +1,457 @@ +import os +import sys +from unittest.mock import MagicMock, patch + +import httpx +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + +from litellm.llms.vertex_ai.image_generation import ( + get_vertex_ai_image_generation_config, +) +from litellm.llms.vertex_ai.image_generation.vertex_gemini_transformation import ( + VertexAIGeminiImageGenerationConfig, +) +from litellm.llms.vertex_ai.image_generation.vertex_imagen_transformation import ( + VertexAIImagenImageGenerationConfig, +) + + +class TestVertexAIGeminiImageGenerationConfig: + def setup_method(self): + """Set up test fixtures""" + self.config = VertexAIGeminiImageGenerationConfig() + + def test_get_supported_openai_params(self): + """Test get_supported_openai_params returns correct params""" + supported = self.config.get_supported_openai_params("gemini-2.5-flash-image") + assert "n" in supported + assert "size" in supported + + def test_map_openai_params_n(self): + """Test mapping n parameter to candidate_count""" + non_default_params = {"n": 3} + optional_params = {} + result = self.config.map_openai_params( + non_default_params, optional_params, "gemini-2.5-flash-image", False + ) + assert result.get("candidate_count") == 3 + + def test_map_openai_params_size(self): + """Test mapping size parameter to aspectRatio""" + non_default_params = {"size": "1024x1024"} + optional_params = {} + result = self.config.map_openai_params( + non_default_params, optional_params, "gemini-2.5-flash-image", False + ) + assert result.get("aspectRatio") == "1:1" + + def test_map_openai_params_size_16_9(self): + """Test mapping 16:9 size""" + non_default_params = {"size": "1792x1024"} + optional_params = {} + result = self.config.map_openai_params( + non_default_params, optional_params, "gemini-2.5-flash-image", False + ) + assert result.get("aspectRatio") == "16:9" + + def test_map_size_to_aspect_ratio(self): + """Test size to aspect ratio mapping""" + assert self.config._map_size_to_aspect_ratio("1024x1024") == "1:1" + assert self.config._map_size_to_aspect_ratio("1792x1024") == "16:9" + assert self.config._map_size_to_aspect_ratio("1024x1792") == "9:16" + assert self.config._map_size_to_aspect_ratio("1280x896") == "4:3" + assert self.config._map_size_to_aspect_ratio("896x1280") == "3:4" + assert self.config._map_size_to_aspect_ratio("unknown") == "1:1" # default + + def test_transform_image_generation_request_basic(self): + """Test basic request transformation""" + request = self.config.transform_image_generation_request( + model="gemini-2.5-flash-image", + prompt="A nano banana", + optional_params={}, + litellm_params={}, + headers={}, + ) + assert "contents" in request + assert "generationConfig" in request + assert request["generationConfig"]["responseModalities"] == ["IMAGE"] + assert request["contents"][0]["parts"][0]["text"] == "A nano banana" + + def test_transform_image_generation_request_with_aspect_ratio(self): + """Test request transformation with aspectRatio""" + request = self.config.transform_image_generation_request( + model="gemini-2.5-flash-image", + prompt="A nano banana", + optional_params={"aspectRatio": "16:9"}, + litellm_params={}, + headers={}, + ) + assert request["generationConfig"]["imageConfig"]["aspectRatio"] == "16:9" + + def test_transform_image_generation_request_with_image_size(self): + """Test request transformation with imageSize (Gemini 3 Pro)""" + request = self.config.transform_image_generation_request( + model="gemini-3-pro-image-preview", + prompt="A nano banana", + optional_params={"imageSize": "4K"}, + litellm_params={}, + headers={}, + ) + assert request["generationConfig"]["imageConfig"]["imageSize"] == "4K" + + def test_transform_image_generation_request_with_candidate_count(self): + """Test request transformation with candidate_count""" + request = self.config.transform_image_generation_request( + model="gemini-2.5-flash-image", + prompt="A nano banana", + optional_params={"candidate_count": 2}, + litellm_params={}, + headers={}, + ) + assert request["generationConfig"]["candidateCount"] == 2 + + def test_transform_image_generation_request_with_n(self): + """Test request transformation with n parameter""" + request = self.config.transform_image_generation_request( + model="gemini-2.5-flash-image", + prompt="A nano banana", + optional_params={"n": 2}, + litellm_params={}, + headers={}, + ) + assert request["generationConfig"]["candidateCount"] == 2 + + def test_transform_image_generation_response(self): + """Test response transformation""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "candidates": [ + { + "content": { + "parts": [ + { + "inlineData": { + "mimeType": "image/png", + "data": "base64_encoded_image_data", + } + } + ] + } + } + ] + } + mock_response.headers = {} + + from litellm.types.utils import ImageResponse + + model_response = ImageResponse() + result = self.config.transform_image_generation_response( + model="gemini-2.5-flash-image", + raw_response=mock_response, + model_response=model_response, + logging_obj=MagicMock(), + request_data={}, + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert len(result.data) == 1 + assert result.data[0].b64_json == "base64_encoded_image_data" + assert result.data[0].url is None + + def test_transform_image_generation_response_multiple_images(self): + """Test response transformation with multiple images""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "candidates": [ + { + "content": { + "parts": [ + { + "inlineData": { + "mimeType": "image/png", + "data": "image1", + } + }, + { + "inlineData": { + "mimeType": "image/png", + "data": "image2", + } + }, + ] + } + } + ] + } + mock_response.headers = {} + + from litellm.types.utils import ImageResponse + + model_response = ImageResponse() + result = self.config.transform_image_generation_response( + model="gemini-2.5-flash-image", + raw_response=mock_response, + model_response=model_response, + logging_obj=MagicMock(), + request_data={}, + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert len(result.data) == 2 + assert result.data[0].b64_json == "image1" + assert result.data[1].b64_json == "image2" + + +class TestVertexAIImagenImageGenerationConfig: + def setup_method(self): + """Set up test fixtures""" + self.config = VertexAIImagenImageGenerationConfig() + + def test_get_supported_openai_params(self): + """Test get_supported_openai_params returns correct params""" + supported = self.config.get_supported_openai_params("imagegeneration@006") + assert "n" in supported + assert "size" in supported + + def test_map_openai_params_n(self): + """Test mapping n parameter to sampleCount""" + non_default_params = {"n": 3} + optional_params = {} + result = self.config.map_openai_params( + non_default_params, optional_params, "imagegeneration@006", False + ) + assert result.get("sampleCount") == 3 + + def test_map_openai_params_size(self): + """Test mapping size parameter to aspectRatio""" + non_default_params = {"size": "1024x1024"} + optional_params = {} + result = self.config.map_openai_params( + non_default_params, optional_params, "imagegeneration@006", False + ) + assert result.get("aspectRatio") == "1:1" + + def test_map_size_to_aspect_ratio(self): + """Test size to aspect ratio mapping""" + assert self.config._map_size_to_aspect_ratio("1024x1024") == "1:1" + assert self.config._map_size_to_aspect_ratio("1792x1024") == "16:9" + assert self.config._map_size_to_aspect_ratio("unknown") == "1:1" # default + + def test_transform_image_generation_request_basic(self): + """Test basic request transformation""" + request = self.config.transform_image_generation_request( + model="imagegeneration@006", + prompt="A cat", + optional_params={}, + litellm_params={}, + headers={}, + ) + assert "instances" in request + assert "parameters" in request + assert request["instances"][0]["prompt"] == "A cat" + assert request["parameters"]["sampleCount"] == 1 + + def test_transform_image_generation_request_with_params(self): + """Test request transformation with parameters""" + request = self.config.transform_image_generation_request( + model="imagegeneration@006", + prompt="A cat", + optional_params={"sampleCount": 2, "aspectRatio": "16:9"}, + litellm_params={}, + headers={}, + ) + assert request["parameters"]["sampleCount"] == 2 + assert request["parameters"]["aspectRatio"] == "16:9" + + def test_transform_image_generation_response(self): + """Test response transformation""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "predictions": [ + {"bytesBase64Encoded": "base64_encoded_image_data"} + ] + } + mock_response.headers = {} + + from litellm.types.utils import ImageResponse + + model_response = ImageResponse() + result = self.config.transform_image_generation_response( + model="imagegeneration@006", + raw_response=mock_response, + model_response=model_response, + logging_obj=MagicMock(), + request_data={}, + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert len(result.data) == 1 + assert result.data[0].b64_json == "base64_encoded_image_data" + assert result.data[0].url is None + + def test_transform_image_generation_response_multiple_images(self): + """Test response transformation with multiple images""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "predictions": [ + {"bytesBase64Encoded": "image1"}, + {"bytesBase64Encoded": "image2"}, + ] + } + mock_response.headers = {} + + from litellm.types.utils import ImageResponse + + model_response = ImageResponse() + result = self.config.transform_image_generation_response( + model="imagegeneration@006", + raw_response=mock_response, + model_response=model_response, + logging_obj=MagicMock(), + request_data={}, + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert len(result.data) == 2 + assert result.data[0].b64_json == "image1" + assert result.data[1].b64_json == "image2" + + +class TestGetVertexAIImageGenerationConfig: + """Test the router function that selects the correct config""" + + def test_get_gemini_model_config(self): + """Test that Gemini models return Gemini config""" + config = get_vertex_ai_image_generation_config("gemini-2.5-flash-image") + assert isinstance(config, VertexAIGeminiImageGenerationConfig) + + config = get_vertex_ai_image_generation_config("gemini-3-pro-image-preview") + assert isinstance(config, VertexAIGeminiImageGenerationConfig) + + config = get_vertex_ai_image_generation_config( + "vertex_ai/gemini-2.5-flash-image" + ) + assert isinstance(config, VertexAIGeminiImageGenerationConfig) + + def test_get_imagen_model_config(self): + """Test that Imagen models return Imagen config""" + config = get_vertex_ai_image_generation_config("imagegeneration@006") + assert isinstance(config, VertexAIImagenImageGenerationConfig) + + config = get_vertex_ai_image_generation_config("imagen-4.0-generate-001") + assert isinstance(config, VertexAIImagenImageGenerationConfig) + + config = get_vertex_ai_image_generation_config( + "vertex_ai/imagegeneration@006" + ) + assert isinstance(config, VertexAIImagenImageGenerationConfig) + + def test_get_non_gemini_model_config(self): + """Test that non-Gemini models default to Imagen config""" + config = get_vertex_ai_image_generation_config("some-other-model") + assert isinstance(config, VertexAIImagenImageGenerationConfig) + + +class TestVertexAIImageGenerationIntegration: + """Integration tests for Vertex AI image generation""" + + @pytest.mark.skipif( + not os.getenv("VERTEXAI_PROJECT"), + reason="Vertex AI credentials not set", + ) + def test_gemini_image_generation_config_validation(self): + """Test that Gemini config can validate environment""" + config = VertexAIGeminiImageGenerationConfig() + with patch.object( + config, "_resolve_vertex_project", return_value="test-project" + ), patch.object( + config, "_resolve_vertex_location", return_value="us-central1" + ), patch.object( + config, "_ensure_access_token", return_value=("token", None) + ): + headers = config.validate_environment( + headers={}, + model="gemini-2.5-flash-image", + messages=[], + optional_params={}, + litellm_params={}, + ) + assert "Authorization" in headers + + @pytest.mark.skipif( + not os.getenv("VERTEXAI_PROJECT"), + reason="Vertex AI credentials not set", + ) + def test_imagen_image_generation_config_validation(self): + """Test that Imagen config can validate environment""" + config = VertexAIImagenImageGenerationConfig() + with patch.object( + config, "_resolve_vertex_project", return_value="test-project" + ), patch.object( + config, "_resolve_vertex_location", return_value="us-central1" + ), patch.object( + config, "_ensure_access_token", return_value=("token", None) + ): + headers = config.validate_environment( + headers={}, + model="imagegeneration@006", + messages=[], + optional_params={}, + litellm_params={}, + ) + assert "Authorization" in headers + + def test_gemini_get_complete_url(self): + """Test Gemini config URL generation""" + config = VertexAIGeminiImageGenerationConfig() + with patch.object( + config, "_resolve_vertex_project", return_value="test-project" + ), patch.object( + config, "_resolve_vertex_location", return_value="us-central1" + ): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="gemini-2.5-flash-image", + optional_params={}, + litellm_params={}, + ) + assert "test-project" in url + assert "us-central1" in url + assert "gemini-2.5-flash-image" in url + assert "generateContent" in url + + def test_imagen_get_complete_url(self): + """Test Imagen config URL generation""" + config = VertexAIImagenImageGenerationConfig() + with patch.object( + config, "_resolve_vertex_project", return_value="test-project" + ), patch.object( + config, "_resolve_vertex_location", return_value="us-central1" + ): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="imagegeneration@006", + optional_params={}, + litellm_params={}, + ) + assert "test-project" in url + assert "us-central1" in url + assert "imagegeneration@006" in url + assert "predict" in url + From b0d511143c952d523ab5302ac6031618af1d69d9 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:36:20 +0530 Subject: [PATCH 035/349] remove unsused imports --- .../vertex_ai/image_generation/vertex_gemini_transformation.py | 2 -- .../vertex_ai/image_generation/vertex_imagen_transformation.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py index 0a87dea997a..149e0850bf0 100644 --- a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py +++ b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py @@ -1,4 +1,3 @@ -import json import os from typing import TYPE_CHECKING, Any, Dict, List, Optional @@ -15,7 +14,6 @@ from litellm.types.llms.openai import ( OpenAIImageGenerationOptionalParams, ) from litellm.types.utils import ImageObject, ImageResponse -from litellm.utils import ModelResponse if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj diff --git a/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py index 275c547c8dd..8c4ad5dd423 100644 --- a/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py +++ b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py @@ -1,6 +1,5 @@ -import json import os -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional import httpx From a50083a87b024887327e1e67e516f2fe641d5a34 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:56:30 +0530 Subject: [PATCH 036/349] Remove none support from reasoning param --- .../llms/azure/chat/gpt_5_transformation.py | 36 ++++++++++++++++++- .../chat/test_azure_gpt5_transformation.py | 10 ++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/litellm/llms/azure/chat/gpt_5_transformation.py b/litellm/llms/azure/chat/gpt_5_transformation.py index d563a2889ca..209475730f8 100644 --- a/litellm/llms/azure/chat/gpt_5_transformation.py +++ b/litellm/llms/azure/chat/gpt_5_transformation.py @@ -2,6 +2,8 @@ from typing import List +import litellm +from litellm.exceptions import UnsupportedParamsError from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config from litellm.types.llms.openai import AllMessageValues @@ -33,7 +35,34 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): drop_params: bool, api_version: str = "", ) -> dict: - return OpenAIGPT5Config.map_openai_params( + reasoning_effort_value = ( + non_default_params.get("reasoning_effort") + or optional_params.get("reasoning_effort") + ) + + if reasoning_effort_value == "none": + if litellm.drop_params is True or ( + drop_params is not None and drop_params is True + ): + non_default_params = non_default_params.copy() + optional_params = optional_params.copy() + if non_default_params.get("reasoning_effort") == "none": + non_default_params.pop("reasoning_effort") + if optional_params.get("reasoning_effort") == "none": + optional_params.pop("reasoning_effort") + else: + raise UnsupportedParamsError( + status_code=400, + message=( + "Azure OpenAI does not support reasoning_effort='none'. " + "Supported values are: 'low', 'medium', and 'high'. " + "To drop this parameter, set `litellm.drop_params=True` or for proxy:\n\n" + "`litellm_settings:\n drop_params: true`\n" + "Issue: https://github.com/BerriAI/litellm/issues/16704" + ), + ) + + result = OpenAIGPT5Config.map_openai_params( self, non_default_params=non_default_params, optional_params=optional_params, @@ -41,6 +70,11 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): drop_params=drop_params, ) + if result.get("reasoning_effort") == "none": + result.pop("reasoning_effort") + + return result + def transform_request( self, model: str, diff --git a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py index 76d069733be..f48d1a1e93e 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py @@ -104,7 +104,12 @@ def test_azure_gpt5_codex_series_transform_request(config: AzureOpenAIGPT5Config # GPT-5.1 temperature handling tests for Azure def test_azure_gpt5_1_temperature_with_reasoning_effort_none(config: AzureOpenAIGPT5Config): - """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none'.""" + """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none'. + + Note: Azure OpenAI doesn't support reasoning_effort='none', so it's dropped from the params. + However, the temperature logic still works correctly because the parent treats missing + reasoning_effort the same as 'none' for gpt-5.1. + """ params = config.map_openai_params( non_default_params={"temperature": 0.5, "reasoning_effort": "none"}, optional_params={}, @@ -113,7 +118,8 @@ def test_azure_gpt5_1_temperature_with_reasoning_effort_none(config: AzureOpenAI api_version="2024-05-01-preview", ) assert params["temperature"] == 0.5 - assert params["reasoning_effort"] == "none" + # Azure doesn't support reasoning_effort="none", so it should be dropped + assert "reasoning_effort" not in params or params.get("reasoning_effort") != "none" def test_azure_gpt5_1_temperature_without_reasoning_effort(config: AzureOpenAIGPT5Config): From c149ade6a8de9c4ab69b905c4d75b136cfa686c3 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 13:57:15 +0530 Subject: [PATCH 037/349] Add tests related to reasoning param none --- .../chat/test_azure_gpt5_transformation.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py index f48d1a1e93e..3095ff87f5a 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py @@ -104,17 +104,17 @@ def test_azure_gpt5_codex_series_transform_request(config: AzureOpenAIGPT5Config # GPT-5.1 temperature handling tests for Azure def test_azure_gpt5_1_temperature_with_reasoning_effort_none(config: AzureOpenAIGPT5Config): - """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none'. + """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none' and drop_params=True. - Note: Azure OpenAI doesn't support reasoning_effort='none', so it's dropped from the params. - However, the temperature logic still works correctly because the parent treats missing - reasoning_effort the same as 'none' for gpt-5.1. + Note: Azure OpenAI doesn't support reasoning_effort='none', so it's dropped from the params + when drop_params=True. The temperature logic still works correctly because the parent treats + missing reasoning_effort the same as 'none' for gpt-5.1. """ params = config.map_openai_params( non_default_params={"temperature": 0.5, "reasoning_effort": "none"}, optional_params={}, model="azure/gpt-5.1", - drop_params=False, + drop_params=True, api_version="2024-05-01-preview", ) assert params["temperature"] == 0.5 @@ -122,6 +122,18 @@ def test_azure_gpt5_1_temperature_with_reasoning_effort_none(config: AzureOpenAI assert "reasoning_effort" not in params or params.get("reasoning_effort") != "none" +def test_azure_gpt5_1_reasoning_effort_none_error_when_drop_params_false(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5.1 raises error for reasoning_effort='none' when drop_params=False.""" + with pytest.raises(litellm.utils.UnsupportedParamsError): + config.map_openai_params( + non_default_params={"reasoning_effort": "none"}, + optional_params={}, + model="azure/gpt-5.1", + drop_params=False, + api_version="2024-05-01-preview", + ) + + def test_azure_gpt5_1_temperature_without_reasoning_effort(config: AzureOpenAIGPT5Config): """Test that Azure GPT-5.1 supports any temperature when reasoning_effort is not specified.""" params = config.map_openai_params( From 67d69d12b059777701d96e4ad60592f333e0ffc2 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 17:14:59 +0530 Subject: [PATCH 038/349] Add cost tracking and logging support --- litellm/cost_calculator.py | 51 +++++ litellm/litellm_core_utils/litellm_logging.py | 5 + litellm/proxy/_types.py | 2 + litellm/proxy/proxy_server.py | 36 +++- litellm/proxy/search_endpoints/endpoints.py | 21 ++ .../spend_tracking/spend_tracking_utils.py | 3 +- .../test_search_api_logging.py | 202 ++++++++++++++++++ 7 files changed, 310 insertions(+), 10 deletions(-) create mode 100644 tests/proxy_unit_tests/test_search_api_logging.py diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 0f5195e31af..9ef26d23ce2 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -1031,6 +1031,57 @@ def completion_cost( # noqa: PLR0915 billed_units.get("search_units") or 1 ) # cohere charges per request by default. completion_tokens = search_units + elif ( + call_type == CallTypes.search.value + or call_type == CallTypes.asearch.value + ): + from litellm.search import search_provider_cost_per_query + + # Extract number_of_queries from optional_params or default to 1 + number_of_queries = 1 + if optional_params is not None: + # Check if query is a list (multiple queries) + query = optional_params.get("query") + if isinstance(query, list): + number_of_queries = len(query) + elif query is not None: + number_of_queries = 1 + + search_model = model or "" + if custom_llm_provider and "/" not in search_model: + # If model is like "tavily-search", construct "tavily/search" for cost lookup + search_model = f"{custom_llm_provider}/search" + + prompt_cost, completion_cost_result = search_provider_cost_per_query( + model=search_model, + custom_llm_provider=custom_llm_provider, + number_of_queries=number_of_queries, + optional_params=optional_params, + ) + + # Return the total cost (prompt_cost + completion_cost, but for search it's just prompt_cost) + _final_cost = prompt_cost + completion_cost_result + + # Apply discount + original_cost = _final_cost + _final_cost, discount_percent, discount_amount = _apply_cost_discount( + base_cost=_final_cost, + custom_llm_provider=custom_llm_provider, + ) + + # Store cost breakdown in logging object if available + _store_cost_breakdown_in_logging_obj( + litellm_logging_obj=litellm_logging_obj, + prompt_tokens_cost_usd_dollar=prompt_cost, + completion_tokens_cost_usd_dollar=completion_cost_result, + cost_for_built_in_tools_cost_usd_dollar=0.0, + total_cost_usd_dollar=_final_cost, + original_cost=original_cost, + discount_percent=discount_percent, + discount_amount=discount_amount, + ) + + return _final_cost elif call_type == CallTypes.arealtime.value and isinstance( completion_response, LiteLLMRealtimeStreamLoggingObject ): diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 9c4a7e38768..5b4fa6b7e24 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -70,6 +70,7 @@ from litellm.litellm_core_utils.redact_messages import ( redact_message_input_output_from_logging, ) from litellm.llms.base_llm.ocr.transformation import OCRResponse +from litellm.llms.base_llm.search.transformation import SearchResponse from litellm.responses.utils import ResponseAPILoggingUtils from litellm.types.containers.main import ContainerObject from litellm.types.llms.openai import ( @@ -1298,6 +1299,7 @@ class Logging(LiteLLMLoggingBaseClass): OpenAIFileObject, LiteLLMRealtimeStreamLoggingObject, OpenAIModerationResponse, + "SearchResponse", ], cache_hit: Optional[bool] = None, litellm_model_name: Optional[str] = None, @@ -1710,8 +1712,11 @@ class Logging(LiteLLMLoggingBaseClass): or isinstance(logging_result, LiteLLMRealtimeStreamLoggingObject) or isinstance(logging_result, OpenAIModerationResponse) or isinstance(logging_result, OCRResponse) # OCR + or isinstance(logging_result, SearchResponse) # Search API or isinstance(logging_result, dict) and logging_result.get("object") == "vector_store.search_results.page" + or isinstance(logging_result, dict) + and logging_result.get("object") == "search" # Search API (dict format) or isinstance(logging_result, VideoObject) or isinstance(logging_result, ContainerObject) or (self.call_type == CallTypes.call_mcp_tool.value) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 8a8bbdfe2ef..9bfd118aba2 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -348,6 +348,8 @@ class LiteLLMRoutes(enum.Enum): # search "/search", "/v1/search", + "/search/{search_tool_name}", + "/v1/search/{search_tool_name}", # OCR "/ocr", "/v1/ocr", diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 0c0ced82ba1..13307742860 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2010,6 +2010,16 @@ class ProxyConfig: ) print(f"\033[32m {search_tool_name} ({search_provider})\033[0m") # noqa + # Handle os.environ/ variables in litellm_params + litellm_params = search_tool.get("litellm_params", {}) + if litellm_params: + for k, v in litellm_params.items(): + if isinstance(v, str) and v.startswith("os.environ/"): + _v = v.replace("os.environ/", "") + v = get_secret(_v) + litellm_params[k] = v + search_tool["litellm_params"] = litellm_params + # Cast to SearchToolTypedDict for type safety try: search_tool_typed: SearchToolTypedDict = SearchToolTypedDict(**search_tool) # type: ignore @@ -3761,6 +3771,7 @@ class ProxyConfig: async def _init_search_tools_in_db(self, prisma_client: PrismaClient): """ Initialize search tools from database into the router on startup. + Only updates router if there are tools in the database, otherwise preserves config-loaded tools. """ global llm_router @@ -3778,17 +3789,24 @@ class ProxyConfig: f"Loading {len(search_tools)} search tool(s) from database into router" ) - if llm_router is not None: - # Add search tools to the router - await SearchAPIRouter.update_router_search_tools( - router_instance=llm_router, search_tools=search_tools - ) - verbose_proxy_logger.info( - f"Successfully loaded {len(search_tools)} search tool(s) into router" - ) + # Only update router if there are tools in the database + # This prevents overwriting config-loaded tools with an empty list + if len(search_tools) > 0: + if llm_router is not None: + # Add search tools to the router + await SearchAPIRouter.update_router_search_tools( + router_instance=llm_router, search_tools=search_tools + ) + verbose_proxy_logger.info( + f"Successfully loaded {len(search_tools)} search tool(s) into router" + ) + else: + verbose_proxy_logger.debug( + "Router not initialized yet, search tools will be added when router is created" + ) else: verbose_proxy_logger.debug( - "Router not initialized yet, search tools will be added when router is created" + "No search tools found in database, keeping config-loaded search tools (if any)" ) except Exception as e: diff --git a/litellm/proxy/search_endpoints/endpoints.py b/litellm/proxy/search_endpoints/endpoints.py index da5388f389d..8cfc8bb5106 100644 --- a/litellm/proxy/search_endpoints/endpoints.py +++ b/litellm/proxy/search_endpoints/endpoints.py @@ -131,6 +131,27 @@ async def search( if search_tool_name is not None: data["search_tool_name"] = search_tool_name + if "search_tool_name" in data and data["search_tool_name"]: + data["model"] = data["search_tool_name"] + + if llm_router is not None and hasattr(llm_router, "search_tools"): + search_tool_name_value = data["search_tool_name"] + matching_tools = [ + tool for tool in llm_router.search_tools + if tool.get("search_tool_name") == search_tool_name_value + ] + + if matching_tools: + search_tool = matching_tools[0] + search_provider = search_tool.get("litellm_params", {}).get("search_provider") + + if search_provider: + data["custom_llm_provider"] = search_provider + + if "metadata" not in data: + data["metadata"] = {} + data["metadata"]["model_group"] = search_tool_name_value + # Process request using ProxyBaseLLMRequestProcessing processor = ProxyBaseLLMRequestProcessing(data=data) try: diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 08e56248853..59f712e5b6f 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -228,7 +228,8 @@ def get_logging_payload( # noqa: PLR0915 if call_type in ["ocr", "aocr"]: usage = _extract_usage_for_ocr_call(response_obj, response_obj_dict) else: - usage = cast(dict, response_obj).get("usage", None) or {} + # Use response_obj_dict instead of response_obj to avoid calling .get() on Pydantic models + usage = response_obj_dict.get("usage", None) or {} if isinstance(usage, litellm.Usage): usage = dict(usage) diff --git a/tests/proxy_unit_tests/test_search_api_logging.py b/tests/proxy_unit_tests/test_search_api_logging.py new file mode 100644 index 00000000000..7ac22e51ef2 --- /dev/null +++ b/tests/proxy_unit_tests/test_search_api_logging.py @@ -0,0 +1,202 @@ +""" +Test search API logging and cost tracking in proxy. + +Tests that search API requests are properly logged to LiteLLM_SpendLogs +with correct fields populated (call_type, model, custom_llm_provider, +model_group, spend, etc.) +""" +import asyncio +import os +import sys +import time +from datetime import datetime +from unittest.mock import AsyncMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) +import litellm +from litellm import Router +from litellm.caching import DualCache +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.hooks.proxy_track_cost_callback import _ProxyDBLogger +from litellm.proxy.spend_tracking.spend_management_endpoints import view_spend_logs +from litellm.proxy.utils import ProxyLogging, hash_token, update_spend +from litellm.llms.base_llm.search.transformation import SearchResponse, SearchResult + + +@pytest.fixture +def prisma_client(): + from litellm.proxy import proxy_server + from litellm.proxy.proxy_cli import append_query_params + from litellm.proxy.utils import PrismaClient + + params = {"connection_limit": 100, "pool_timeout": 60} + database_url = os.getenv("DATABASE_URL") + if database_url is None: + pytest.skip("DATABASE_URL not set") + + modified_url = append_query_params(database_url, params) + os.environ["DATABASE_URL"] = modified_url + + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + + prisma_client = PrismaClient( + database_url=os.environ["DATABASE_URL"], proxy_logging_obj=proxy_logging_obj + ) + + proxy_server.litellm_proxy_budget_name = ( + f"litellm-proxy-budget-{time.time()}" + ) + proxy_server.user_custom_key_generate = None + + return prisma_client + + +@pytest.mark.asyncio +async def test_search_api_logging_and_cost_tracking(prisma_client): + """ + Test that search API requests are logged with correct fields and cost tracking. + + Verifies: + 1. Search request creates a spend log entry + 2. call_type is set to "asearch" + 3. model is set to search_tool_name + 4. custom_llm_provider is set correctly + 5. model_group is set to search_tool_name + 6. spend is calculated and logged + """ + setattr(litellm.proxy.proxy_server, "prisma_client", prisma_client) + setattr(litellm.proxy.proxy_server, "master_key", "sk-1234") + await litellm.proxy.proxy_server.prisma_client.connect() + + # Setup router with search tool + search_tool_name = "tavily-search" + search_provider = "tavily" + + router = Router(model_list=[]) + router.search_tools = [ + { + "search_tool_name": search_tool_name, + "litellm_params": { + "search_provider": search_provider, + }, + } + ] + + setattr(litellm.proxy.proxy_server, "llm_router", router) + + # Generate a test API key + from litellm.proxy.management_endpoints.key_management_endpoints import generate_key_fn + from litellm.proxy._types import GenerateKeyRequest + + from litellm.proxy._types import LitellmUserRoles + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-1234", + user_id="test_user", + ) + + key_request = GenerateKeyRequest(models=[], duration=None) + key_response = await generate_key_fn( + data=key_request, user_api_key_dict=user_api_key_dict + ) + generated_key = key_response.key + user_id = key_response.user_id + + # Create mock search response + mock_search_result = SearchResult( + title="Test Result", + url="https://example.com", + snippet="Test snippet", + ) + + mock_search_response = SearchResponse( + object="search", + results=[mock_search_result], + ) + + # Mock the search function to return our mock response + with patch("litellm.search.main.asearch", new_callable=AsyncMock) as mock_asearch: + mock_asearch.return_value = mock_search_response + + # Setup proxy logging + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + setattr(litellm.proxy.proxy_server, "proxy_logging_obj", proxy_logging_obj) + + # Call the track_cost_callback directly to simulate what happens after a search + proxy_db_logger = _ProxyDBLogger() + + # Simulate the kwargs that would be passed from the search endpoint + request_id = "search_test_123" + kwargs = { + "call_type": "asearch", + "model": search_tool_name, + "custom_llm_provider": search_provider, + "litellm_call_id": request_id, # Set request_id in kwargs + "litellm_params": { + "metadata": { + "user_api_key": hash_token(generated_key), + "user_api_key_user_id": user_id, + "model_group": search_tool_name, + } + }, + "metadata": { + "user_api_key": hash_token(generated_key), + "user_api_key_user_id": user_id, + "model_group": search_tool_name, + }, + "response_cost": 0.008, # Mock cost for tavily search + } + + # Set id on the response object + mock_search_response.id = request_id + + await proxy_db_logger._PROXY_track_cost_callback( + kwargs=kwargs, + completion_response=mock_search_response, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + # Wait for async operations + await asyncio.sleep(2) + await update_spend( + prisma_client=prisma_client, + db_writer_client=None, + proxy_logging_obj=proxy_logging_obj, + ) + + # Query spend logs + spend_logs = await view_spend_logs( + request_id=request_id, + user_api_key_dict=UserAPIKeyAuth(api_key=generated_key), + ) + + # Verify spend log was created + assert len(spend_logs) == 1, f"Expected 1 spend log, got {len(spend_logs)}" + + spend_log = spend_logs[0] + + # Verify all fields are populated correctly + assert spend_log.request_id == request_id + assert spend_log.call_type == "asearch" + assert spend_log.model == search_tool_name + assert spend_log.custom_llm_provider == search_provider + assert spend_log.model_group == search_tool_name + assert spend_log.spend == 0.008 + # API key should be hashed (either the generated key or the one from metadata) + assert spend_log.api_key != "" # Should be populated + # Note: user field may be empty if not set in the request, but user_id should be in metadata + assert spend_log.metadata.get("user_api_key_user_id") == user_id or spend_log.user == user_id + + print(f"✅ Search API logging test passed!") + print(f" - call_type: {spend_log.call_type}") + print(f" - model: {spend_log.model}") + print(f" - custom_llm_provider: {spend_log.custom_llm_provider}") + print(f" - model_group: {spend_log.model_group}") + print(f" - spend: {spend_log.spend}") + From c54986c3c9839701ddde3808ec0959376c4ea8ed Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 25 Nov 2025 17:26:53 +0530 Subject: [PATCH 039/349] list path now routes to Vertex --- litellm/batches/main.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/litellm/batches/main.py b/litellm/batches/main.py index 838ee95b2b5..995c45b925a 100644 --- a/litellm/batches/main.py +++ b/litellm/batches/main.py @@ -644,7 +644,7 @@ def retrieve_batch( async def alist_batches( after: Optional[str] = None, limit: Optional[int] = None, - custom_llm_provider: Literal["openai", "azure"] = "openai", + custom_llm_provider: Literal["openai", "azure", "vertex_ai"] = "openai", metadata: Optional[Dict[str, str]] = None, extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, @@ -687,7 +687,7 @@ async def alist_batches( def list_batches( after: Optional[str] = None, limit: Optional[int] = None, - custom_llm_provider: Literal["openai", "azure"] = "openai", + custom_llm_provider: Literal["openai", "azure", "vertex_ai"] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -784,9 +784,36 @@ def list_batches( max_retries=optional_params.max_retries, litellm_params=litellm_params, ) + elif custom_llm_provider == "vertex_ai": + api_base = optional_params.api_base or "" + vertex_ai_project = ( + optional_params.vertex_project + or litellm.vertex_project + or get_secret_str("VERTEXAI_PROJECT") + ) + vertex_ai_location = ( + optional_params.vertex_location + or litellm.vertex_location + or get_secret_str("VERTEXAI_LOCATION") + ) + vertex_credentials = optional_params.vertex_credentials or get_secret_str( + "VERTEXAI_CREDENTIALS" + ) + + response = vertex_ai_batches_instance.list_batches( + _is_async=_is_async, + after=after, + limit=limit, + api_base=api_base, + vertex_project=vertex_ai_project, + vertex_location=vertex_ai_location, + vertex_credentials=vertex_credentials, + timeout=timeout, + max_retries=optional_params.max_retries, + ) else: raise litellm.exceptions.BadRequestError( - message="LiteLLM doesn't support {} for 'list_batch'. Only 'openai' is supported.".format( + message="LiteLLM doesn't support {} for 'list_batch'. Supported providers: openai, azure, vertex_ai.".format( custom_llm_provider ), model="n/a", From 2cc2f67a7600a2c7bdc91846bfad4dc578bc79dd Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 25 Nov 2025 17:27:12 +0530 Subject: [PATCH 040/349] added the minimal GET logic --- litellm/llms/vertex_ai/batches/handler.py | 96 +++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/litellm/llms/vertex_ai/batches/handler.py b/litellm/llms/vertex_ai/batches/handler.py index 7932881f482..864cc190312 100644 --- a/litellm/llms/vertex_ai/batches/handler.py +++ b/litellm/llms/vertex_ai/batches/handler.py @@ -213,3 +213,99 @@ class VertexAIBatchPrediction(VertexLLM): response=_json_response ) return vertex_batch_response + + def list_batches( + self, + _is_async: bool, + after: Optional[str], + limit: Optional[int], + api_base: Optional[str], + vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], + vertex_project: Optional[str], + vertex_location: Optional[str], + timeout: Union[float, httpx.Timeout], + max_retries: Optional[int], + ): + sync_handler = _get_httpx_client() + + access_token, project_id = self._ensure_access_token( + credentials=vertex_credentials, + project_id=vertex_project, + custom_llm_provider="vertex_ai", + ) + + default_api_base = self.create_vertex_batch_url( + vertex_location=vertex_location or "us-central1", + vertex_project=vertex_project or project_id, + ) + + if len(default_api_base.split(":")) > 1: + endpoint = default_api_base.split(":")[-1] + else: + endpoint = "" + + _, api_base = self._check_custom_proxy( + api_base=api_base, + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint=endpoint, + stream=None, + auth_header=None, + url=default_api_base, + ) + + headers = { + "Content-Type": "application/json; charset=utf-8", + "Authorization": f"Bearer {access_token}", + } + + params: Dict[str, Any] = {} + if limit is not None: + params["pageSize"] = str(limit) + if after is not None: + params["pageToken"] = after + + if _is_async is True: + return self._async_list_batches( + api_base=api_base, + headers=headers, + params=params, + ) + + response = sync_handler.get( + url=api_base, + headers=headers, + params=params, + ) + + if response.status_code != 200: + raise Exception(f"Error: {response.status_code} {response.text}") + + _json_response = response.json() + vertex_batch_response = VertexAIBatchTransformation.transform_vertex_ai_batch_list_response_to_openai_list_response( + response=_json_response + ) + return vertex_batch_response + + async def _async_list_batches( + self, + api_base: str, + headers: Dict[str, str], + params: Dict[str, Any], + ): + client = get_async_httpx_client( + llm_provider=litellm.LlmProviders.VERTEX_AI, + ) + response = await client.get( + url=api_base, + headers=headers, + params=params, + ) + if response.status_code != 200: + raise Exception(f"Error: {response.status_code} {response.text}") + + _json_response = response.json() + vertex_batch_response = VertexAIBatchTransformation.transform_vertex_ai_batch_list_response_to_openai_list_response( + response=_json_response + ) + return vertex_batch_response From a24b43cbdfbe52c02dec8f6d61c005991db0c968 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 25 Nov 2025 17:27:39 +0530 Subject: [PATCH 041/349] only added the helper that converts --- .../llms/vertex_ai/batches/transformation.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/litellm/llms/vertex_ai/batches/transformation.py b/litellm/llms/vertex_ai/batches/transformation.py index 22cd0bd402a..a0adb3e55a8 100644 --- a/litellm/llms/vertex_ai/batches/transformation.py +++ b/litellm/llms/vertex_ai/batches/transformation.py @@ -1,5 +1,5 @@ from litellm._uuid import uuid -from typing import Dict +from typing import Any, Dict from litellm.llms.vertex_ai.common_utils import ( _convert_vertex_datetime_to_openai_datetime, @@ -67,6 +67,33 @@ class VertexAIBatchTransformation: ), ) + @classmethod + def transform_vertex_ai_batch_list_response_to_openai_list_response( + cls, response: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Transforms Vertex AI batch list response into OpenAI-compatible list response. + """ + + batch_jobs = response.get("batchPredictionJobs", []) or [] + data = [ + cls.transform_vertex_ai_batch_response_to_openai_batch_response(job) + for job in batch_jobs + ] + + first_id = data[0].id if len(data) > 0 else None + last_id = data[-1].id if len(data) > 0 else None + next_page_token = response.get("nextPageToken") + + return { + "object": "list", + "data": data, + "first_id": first_id, + "last_id": last_id, + "has_more": bool(next_page_token), + "next_page_token": next_page_token, + } + @classmethod def _get_batch_id_from_vertex_ai_batch_response( cls, response: VertexBatchPredictionResponse From 2cf86e8ef83eb02941a1f31f099ad033e8ddee08 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 25 Nov 2025 17:28:02 +0530 Subject: [PATCH 042/349] new mocked --- .../test_openai_batches_and_files.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/batches_tests/test_openai_batches_and_files.py b/tests/batches_tests/test_openai_batches_and_files.py index 67ce2f7249a..2f4f9bbcda1 100644 --- a/tests/batches_tests/test_openai_batches_and_files.py +++ b/tests/batches_tests/test_openai_batches_and_files.py @@ -447,6 +447,18 @@ mock_vertex_batch_response = { "completionStats": {"successfulCount": 0, "failedCount": 0, "remainingCount": 100}, } +mock_vertex_list_response = { + "batchPredictionJobs": [ + mock_vertex_batch_response, + { + **mock_vertex_batch_response, + "name": "projects/123456789/locations/us-central1/batchPredictionJobs/test-batch-id-789", + "state": "JOB_STATE_SUCCEEDED", + }, + ], + "nextPageToken": "", +} + @pytest.mark.asyncio async def test_avertex_batch_prediction(monkeypatch): @@ -533,3 +545,35 @@ async def test_avertex_batch_prediction(monkeypatch): print("retrieved_batch=", retrieved_batch) assert retrieved_batch.id == "test-batch-id-456" + + +@pytest.mark.asyncio +async def test_vertex_list_batches(monkeypatch): + monkeypatch.setenv("GCS_BUCKET_NAME", "litellm-local") + monkeypatch.setenv("VERTEXAI_PROJECT", "litellm-test-project") + monkeypatch.setenv("VERTEXAI_LOCATION", "us-central1") + + monkeypatch.setattr( + "litellm.llms.vertex_ai.batches.handler.VertexAIBatchPrediction._ensure_access_token", + lambda self, credentials, project_id, custom_llm_provider: ("mock-token", "litellm-test-project"), + ) + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.get" + ) as mock_get: + mock_get_response = MagicMock() + mock_get_response.json.return_value = mock_vertex_list_response + mock_get_response.status_code = 200 + mock_get_response.raise_for_status.return_value = None + mock_get.return_value = mock_get_response + + list_response = await litellm.alist_batches( + custom_llm_provider="vertex_ai", + limit=2, + ) + + assert list_response["object"] == "list" + assert list_response["has_more"] is False + assert len(list_response["data"]) == 2 + assert list_response["data"][0].id == "test-batch-id-456" + assert list_response["data"][1].id == "test-batch-id-789" From afe540e88d7681e91036c716ad389b259247d4d6 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 18:26:25 +0530 Subject: [PATCH 043/349] Fix auth issue --- .../llms/azure/anthropic/transformation.py | 14 ++--- litellm/main.py | 16 ++++-- ...odel_prices_and_context_window_backup.json | 54 +++++++++++++++++++ model_prices_and_context_window.json | 54 +++++++++++++++++++ 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/litellm/llms/azure/anthropic/transformation.py b/litellm/llms/azure/anthropic/transformation.py index 81beeb74ae1..9bc4f130563 100644 --- a/litellm/llms/azure/anthropic/transformation.py +++ b/litellm/llms/azure/anthropic/transformation.py @@ -1,11 +1,10 @@ """ Azure Anthropic transformation config - extends AnthropicConfig with Azure authentication """ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union -import litellm from litellm.llms.anthropic.chat.transformation import AnthropicConfig -from litellm.llms.azure.common_utils import BaseAzureLLM, get_azure_ad_token +from litellm.llms.azure.common_utils import BaseAzureLLM from litellm.types.llms.openai import AllMessageValues from litellm.types.router import GenericLiteLLMParams @@ -56,6 +55,11 @@ class AzureAnthropicConfig(AnthropicConfig): headers = BaseAzureLLM._base_validate_azure_environment( headers=headers, litellm_params=litellm_params_obj ) + + # Azure Anthropic uses x-api-key header (not api-key) + # Convert api-key to x-api-key if present + if "api-key" in headers and "x-api-key" not in headers: + headers["x-api-key"] = headers.pop("api-key") # Get tools and other anthropic-specific setup tools = optional_params.get("tools") @@ -81,10 +85,6 @@ class AzureAnthropicConfig(AnthropicConfig): user_anthropic_beta_headers=user_anthropic_beta_headers, mcp_server_used=mcp_server_used, ) - - # Remove x-api-key from anthropic headers since Azure uses different auth - anthropic_headers.pop("x-api-key", None) - # Merge headers - Azure auth (api-key or Authorization) takes precedence headers = {**anthropic_headers, **headers} diff --git a/litellm/main.py b/litellm/main.py index 4857f1b9754..c155dc31db3 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2379,10 +2379,18 @@ def completion( # type: ignore # noqa: PLR0915 ) # Ensure the URL ends with /v1/messages - if not api_base.endswith("/v1/messages"): - if not api_base.endswith("/anthropic"): - api_base = api_base.rstrip("/") + "/anthropic" - api_base = api_base.rstrip("/") + "/v1/messages" + api_base = api_base.rstrip("/") + if api_base.endswith("/v1/messages"): + pass + elif api_base.endswith("/anthropic/v1/messages"): + pass + else: + if "/anthropic" in api_base: + parts = api_base.split("/anthropic", 1) + api_base = parts[0] + "/anthropic" + else: + api_base = api_base + "/anthropic" + api_base = api_base + "/v1/messages" response = azure_anthropic_chat_completions.completion( model=model, diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 351e01bc45e..bc1a1950664 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1087,6 +1087,60 @@ "output_cost_per_token": 1.5e-05, "supports_function_calling": true }, + "azure/claude-haiku-4-5": { + "input_cost_per_token": 1e-06, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 5e-06, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/claude-opus-4-1": { + "input_cost_per_token": 1.5e-05, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 32000, + "max_tokens": 32000, + "mode": "chat", + "output_cost_per_token": 7.5e-05, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/claude-sonnet-4-5": { + "input_cost_per_token": 3e-06, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/computer-use-preview": { "input_cost_per_token": 3e-06, "litellm_provider": "azure", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 351e01bc45e..bc1a1950664 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1087,6 +1087,60 @@ "output_cost_per_token": 1.5e-05, "supports_function_calling": true }, + "azure/claude-haiku-4-5": { + "input_cost_per_token": 1e-06, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 5e-06, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/claude-opus-4-1": { + "input_cost_per_token": 1.5e-05, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 32000, + "max_tokens": 32000, + "mode": "chat", + "output_cost_per_token": 7.5e-05, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/claude-sonnet-4-5": { + "input_cost_per_token": 3e-06, + "litellm_provider": "azure_anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/computer-use-preview": { "input_cost_per_token": 3e-06, "litellm_provider": "azure", From dd4c8ecbef4ea0e5dda8925553261d5978de9172 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 18:36:39 +0530 Subject: [PATCH 044/349] Add v1/messages support for azure anthropic models --- .../docs/providers/azure/azure_anthropic.md | 2 +- litellm/llms/azure/anthropic/__init__.py | 6 +- .../anthropic/messages_transformation.py | 125 ++++++++++++++++++ litellm/utils.py | 6 + 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 litellm/llms/azure/anthropic/messages_transformation.py diff --git a/docs/my-website/docs/providers/azure/azure_anthropic.md b/docs/my-website/docs/providers/azure/azure_anthropic.md index 9a45e1db599..6e0f2b60ef2 100644 --- a/docs/my-website/docs/providers/azure/azure_anthropic.md +++ b/docs/my-website/docs/providers/azure/azure_anthropic.md @@ -19,7 +19,7 @@ Azure Foundry supports the following Claude models: | Provider Route on LiteLLM | `azure/` (add this prefix to Claude model names - e.g. `azure/claude-sonnet-4-5`) | | Provider Doc | [Azure Foundry Claude Models ↗](https://learn.microsoft.com/en-us/azure/ai-services/foundry-models/claude) | | API Endpoint | `https://.services.ai.azure.com/anthropic/v1/messages` | -| Supported Endpoints | `/chat/completions`, `/anthropic/v1/messages` (passthrough) | +| Supported Endpoints | `/chat/completions`, `/anthropic/v1/messages`| ## Key Features diff --git a/litellm/llms/azure/anthropic/__init__.py b/litellm/llms/azure/anthropic/__init__.py index b40c4dfa9b3..233f22999f0 100644 --- a/litellm/llms/azure/anthropic/__init__.py +++ b/litellm/llms/azure/anthropic/__init__.py @@ -4,5 +4,9 @@ Azure Anthropic provider - supports Claude models via Azure Foundry from .handler import AzureAnthropicChatCompletion from .transformation import AzureAnthropicConfig -__all__ = ["AzureAnthropicChatCompletion", "AzureAnthropicConfig"] +try: + from .messages_transformation import AzureAnthropicMessagesConfig + __all__ = ["AzureAnthropicChatCompletion", "AzureAnthropicConfig", "AzureAnthropicMessagesConfig"] +except ImportError: + __all__ = ["AzureAnthropicChatCompletion", "AzureAnthropicConfig"] diff --git a/litellm/llms/azure/anthropic/messages_transformation.py b/litellm/llms/azure/anthropic/messages_transformation.py new file mode 100644 index 00000000000..dd6aae4a769 --- /dev/null +++ b/litellm/llms/azure/anthropic/messages_transformation.py @@ -0,0 +1,125 @@ +""" +Azure Anthropic messages transformation config - extends AnthropicMessagesConfig with Azure authentication +""" +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +import httpx + +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj, verbose_logger +from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, +) +from litellm.llms.azure.common_utils import BaseAzureLLM +from litellm.types.llms.anthropic_messages.anthropic_response import ( + AnthropicMessagesResponse, +) +from litellm.types.router import GenericLiteLLMParams + +if TYPE_CHECKING: + pass + + +class AzureAnthropicMessagesConfig(AnthropicMessagesConfig): + """ + Azure Anthropic messages configuration that extends AnthropicMessagesConfig. + The only difference is authentication - Azure uses x-api-key header (not api-key) + and Azure endpoint format. + """ + + def validate_anthropic_messages_environment( + self, + headers: dict, + model: str, + messages: List[Any], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> Tuple[dict, Optional[str]]: + """ + Validate environment and set up Azure authentication headers for /v1/messages endpoint. + Azure Anthropic uses x-api-key header (not api-key). + """ + from litellm.secret_managers.main import get_secret_str + + # Convert dict to GenericLiteLLMParams if needed + if isinstance(litellm_params, dict): + if api_key and "api_key" not in litellm_params: + litellm_params = {**litellm_params, "api_key": api_key} + litellm_params_obj = GenericLiteLLMParams(**litellm_params) + else: + litellm_params_obj = litellm_params or GenericLiteLLMParams() + if api_key and not litellm_params_obj.api_key: + litellm_params_obj.api_key = api_key + + # Use Azure authentication logic + headers = BaseAzureLLM._base_validate_azure_environment( + headers=headers, litellm_params=litellm_params_obj + ) + + # Azure Anthropic uses x-api-key header (not api-key) + # Convert api-key to x-api-key if present + if "api-key" in headers and "x-api-key" not in headers: + headers["x-api-key"] = headers.pop("api-key") + + # Set anthropic-version header + if "anthropic-version" not in headers: + headers["anthropic-version"] = "2023-06-01" + + # Set content-type header + if "content-type" not in headers: + headers["content-type"] = "application/json" + + # Update headers with optional anthropic beta features + headers = self._update_headers_with_optional_anthropic_beta( + headers=headers, + context_management=optional_params.get("context_management"), + ) + + return headers, api_base + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for Azure Anthropic /v1/messages endpoint. + Azure Foundry endpoint format: https://.services.ai.azure.com/anthropic/v1/messages + """ + from litellm.secret_managers.main import get_secret_str + + api_base = api_base or get_secret_str("AZURE_API_BASE") + if api_base is None: + raise ValueError( + "Missing Azure API Base - Please set `api_base` or `AZURE_API_BASE` environment variable. " + "Expected format: https://.services.ai.azure.com/anthropic" + ) + + # Ensure the URL ends with /v1/messages + api_base = api_base.rstrip("/") + if api_base.endswith("/v1/messages"): + # Already correct + pass + elif api_base.endswith("/anthropic/v1/messages"): + # Already correct + pass + else: + # Check if /anthropic is already in the path + if "/anthropic" in api_base: + # /anthropic exists, ensure we end with /anthropic/v1/messages + # Extract the base URL up to and including /anthropic + parts = api_base.split("/anthropic", 1) + api_base = parts[0] + "/anthropic" + else: + # /anthropic not in path, add it + api_base = api_base + "/anthropic" + # Add /v1/messages + api_base = api_base + "/v1/messages" + + return api_base + diff --git a/litellm/utils.py b/litellm/utils.py index e114e4cc051..2245487c6d5 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7341,6 +7341,12 @@ class ProviderConfigManager: ) return VertexAIPartnerModelsAnthropicMessagesConfig() + elif litellm.LlmProviders.AZURE_ANTHROPIC == provider: + from litellm.llms.azure.anthropic.messages_transformation import ( + AzureAnthropicMessagesConfig, + ) + + return AzureAnthropicMessagesConfig() return None @staticmethod From e2f2ccd913954cdca833b4be2d890b54c8bb1f28 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 18:45:51 +0530 Subject: [PATCH 045/349] Add tests related messages api --- .../anthropic/messages_transformation.py | 3 +- .../anthropic/test_azure_anthropic_handler.py | 6 +- ...azure_anthropic_messages_transformation.py | 241 ++++++++++++++++++ .../test_azure_anthropic_provider_config.py | 59 +++++ .../test_azure_anthropic_transformation.py | 16 +- 5 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_messages_transformation.py create mode 100644 tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_config.py diff --git a/litellm/llms/azure/anthropic/messages_transformation.py b/litellm/llms/azure/anthropic/messages_transformation.py index dd6aae4a769..4640f68a3e0 100644 --- a/litellm/llms/azure/anthropic/messages_transformation.py +++ b/litellm/llms/azure/anthropic/messages_transformation.py @@ -5,7 +5,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple import httpx -from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj, verbose_logger +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.litellm_core_utils.litellm_logging import verbose_logger from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( AnthropicMessagesConfig, ) diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py index 34aed42478d..bb5d1f9933d 100644 --- a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_handler.py @@ -32,7 +32,7 @@ class TestAzureAnthropicChatCompletion: mock_config.transform_request.return_value = {"model": "claude-sonnet-4-5", "messages": []} mock_config.transform_response.return_value = ModelResponse() mock_config_instance = MagicMock() - mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_config_instance.validate_environment.return_value = {"x-api-key": "test-api-key", "anthropic-version": "2023-06-01"} mock_azure_config.return_value = mock_config_instance mock_provider_manager.get_provider_chat_config.return_value = mock_config @@ -90,7 +90,7 @@ class TestAzureAnthropicChatCompletion: "stream": True, } mock_config_instance = MagicMock() - mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_config_instance.validate_environment.return_value = {"x-api-key": "test-api-key", "anthropic-version": "2023-06-01"} mock_azure_config.return_value = mock_config_instance mock_provider_manager.get_provider_chat_config.return_value = mock_config @@ -151,7 +151,7 @@ class TestAzureAnthropicChatCompletion: mock_response = ModelResponse() mock_config.transform_response.return_value = mock_response mock_config_instance = MagicMock() - mock_config_instance.validate_environment.return_value = {"api-key": "test-api-key", "anthropic-version": "2023-06-01"} + mock_config_instance.validate_environment.return_value = {"x-api-key": "test-api-key", "anthropic-version": "2023-06-01"} mock_azure_config.return_value = mock_config_instance mock_provider_manager.get_provider_chat_config.return_value = mock_config diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_messages_transformation.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_messages_transformation.py new file mode 100644 index 00000000000..abed1a7852e --- /dev/null +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_messages_transformation.py @@ -0,0 +1,241 @@ +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) +) + +from unittest.mock import MagicMock, patch + +import pytest + +from litellm.llms.azure.anthropic.messages_transformation import ( + AzureAnthropicMessagesConfig, +) +from litellm.types.router import GenericLiteLLMParams + + +class TestAzureAnthropicMessagesConfig: + def test_inherits_from_anthropic_messages_config(self): + """Test that AzureAnthropicMessagesConfig inherits from AnthropicMessagesConfig""" + config = AzureAnthropicMessagesConfig() + assert isinstance(config, AzureAnthropicMessagesConfig) + # Check that it has methods from parent class + assert hasattr(config, "get_supported_anthropic_messages_params") + assert hasattr(config, "get_complete_url") + assert hasattr(config, "validate_anthropic_messages_environment") + assert hasattr(config, "transform_anthropic_messages_request") + assert hasattr(config, "transform_anthropic_messages_response") + + def test_validate_anthropic_messages_environment_with_dict_litellm_params(self): + """Test validate_anthropic_messages_environment with dict litellm_params""" + config = AzureAnthropicMessagesConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + api_key = "test-api-key" + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + result, api_base = config.validate_anthropic_messages_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + api_key=api_key, + ) + + # Verify that dict was converted to GenericLiteLLMParams + call_args = mock_validate.call_args + assert isinstance(call_args[1]["litellm_params"], GenericLiteLLMParams) + assert call_args[1]["litellm_params"].api_key == "test-api-key" + assert "anthropic-version" in result + assert "x-api-key" in result + assert result["x-api-key"] == "test-api-key" + assert "api-key" not in result + + def test_validate_anthropic_messages_environment_converts_api_key_to_x_api_key(self): + """Test that api-key header is converted to x-api-key""" + config = AzureAnthropicMessagesConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + result, api_base = config.validate_anthropic_messages_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + # Verify api-key was converted to x-api-key + assert "x-api-key" in result + assert result["x-api-key"] == "test-api-key" + assert "api-key" not in result + + def test_validate_anthropic_messages_environment_sets_headers(self): + """Test that required headers are set""" + config = AzureAnthropicMessagesConfig() + headers = {} + model = "claude-sonnet-4-5" + messages = [{"role": "user", "content": "Hello"}] + optional_params = {} + litellm_params = {"api_key": "test-api-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-api-key"} + result, api_base = config.validate_anthropic_messages_environment( + headers=headers, + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert "anthropic-version" in result + assert result["anthropic-version"] == "2023-06-01" + assert "content-type" in result + assert result["content-type"] == "application/json" + assert "x-api-key" in result + + def test_get_complete_url_with_base_url(self): + """Test get_complete_url with base URL""" + config = AzureAnthropicMessagesConfig() + api_base = "https://test.services.ai.azure.com/anthropic" + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + url = config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert url == "https://test.services.ai.azure.com/anthropic/v1/messages" + + def test_get_complete_url_with_base_url_ending_with_slash(self): + """Test get_complete_url with base URL ending with slash""" + config = AzureAnthropicMessagesConfig() + api_base = "https://test.services.ai.azure.com/anthropic/" + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + url = config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert url == "https://test.services.ai.azure.com/anthropic/v1/messages" + + def test_get_complete_url_with_base_url_already_containing_v1_messages(self): + """Test get_complete_url with base URL already containing /v1/messages""" + config = AzureAnthropicMessagesConfig() + api_base = "https://test.services.ai.azure.com/anthropic/v1/messages" + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + url = config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert url == "https://test.services.ai.azure.com/anthropic/v1/messages" + + def test_get_complete_url_with_base_url_containing_anthropic(self): + """Test get_complete_url with base URL already containing /anthropic""" + config = AzureAnthropicMessagesConfig() + api_base = "https://test.services.ai.azure.com/anthropic" + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + url = config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert url == "https://test.services.ai.azure.com/anthropic/v1/messages" + + def test_get_complete_url_with_base_url_without_anthropic(self): + """Test get_complete_url with base URL without /anthropic""" + config = AzureAnthropicMessagesConfig() + api_base = "https://test.services.ai.azure.com" + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + url = config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + assert url == "https://test.services.ai.azure.com/anthropic/v1/messages" + + def test_get_complete_url_raises_error_when_api_base_missing(self): + """Test get_complete_url raises error when api_base is None""" + config = AzureAnthropicMessagesConfig() + api_base = None + api_key = "test-api-key" + model = "claude-sonnet-4-5" + optional_params = {} + litellm_params = {} + + with patch("litellm.secret_managers.main.get_secret_str", return_value=None): + with pytest.raises(ValueError, match="Missing Azure API Base"): + config.get_complete_url( + api_base=api_base, + api_key=api_key, + model=model, + optional_params=optional_params, + litellm_params=litellm_params, + ) + + def test_get_supported_anthropic_messages_params(self): + """Test get_supported_anthropic_messages_params returns correct params""" + config = AzureAnthropicMessagesConfig() + model = "claude-sonnet-4-5" + params = config.get_supported_anthropic_messages_params(model) + + assert "messages" in params + assert "model" in params + assert "max_tokens" in params + assert "temperature" in params + assert "tools" in params + assert "tool_choice" in params + diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_config.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_config.py new file mode 100644 index 00000000000..db118154eee --- /dev/null +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_provider_config.py @@ -0,0 +1,59 @@ +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) +) + +from unittest.mock import patch + +import pytest + +import litellm +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager + + +class TestAzureAnthropicProviderConfig: + def test_get_provider_anthropic_messages_config_returns_azure_config(self): + """Test that get_provider_anthropic_messages_config returns AzureAnthropicMessagesConfig for azure_anthropic provider""" + from litellm.llms.azure.anthropic.messages_transformation import ( + AzureAnthropicMessagesConfig, + ) + + config = ProviderConfigManager.get_provider_anthropic_messages_config( + model="claude-sonnet-4-5", + provider=LlmProviders.AZURE_ANTHROPIC, + ) + + assert config is not None + assert isinstance(config, AzureAnthropicMessagesConfig) + + def test_get_provider_anthropic_messages_config_returns_anthropic_config_for_anthropic_provider(self): + """Test that get_provider_anthropic_messages_config returns AnthropicMessagesConfig for anthropic provider""" + from litellm.llms.azure.anthropic.messages_transformation import ( + AzureAnthropicMessagesConfig, + ) + + config = ProviderConfigManager.get_provider_anthropic_messages_config( + model="claude-sonnet-4-5", + provider=LlmProviders.ANTHROPIC, + ) + + # Should return AnthropicMessagesConfig, not AzureAnthropicMessagesConfig + assert config is not None + assert not isinstance(config, AzureAnthropicMessagesConfig) + assert isinstance(config, litellm.AnthropicMessagesConfig) + + def test_get_provider_chat_config_returns_azure_anthropic_config(self): + """Test that get_provider_chat_config returns AzureAnthropicConfig for azure_anthropic provider""" + from litellm.llms.azure.anthropic.transformation import AzureAnthropicConfig + + config = ProviderConfigManager.get_provider_chat_config( + model="claude-sonnet-4-5", + provider=LlmProviders.AZURE_ANTHROPIC, + ) + + assert config is not None + assert isinstance(config, AzureAnthropicConfig) + diff --git a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py index 43f0b5c439d..f26831e9195 100644 --- a/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py +++ b/tests/test_litellm/llms/azure/anthropic/test_azure_anthropic_transformation.py @@ -5,9 +5,10 @@ sys.path.insert( 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) ) -import pytest from unittest.mock import MagicMock, patch +import pytest + from litellm.llms.azure.anthropic.transformation import AzureAnthropicConfig from litellm.types.router import GenericLiteLLMParams @@ -102,8 +103,8 @@ class TestAzureAnthropicConfig: call_args = mock_validate.call_args assert call_args[1]["litellm_params"].api_key == "provided-api-key" - def test_validate_environment_removes_x_api_key(self): - """Test that x-api-key header is removed (Azure uses api-key instead)""" + def test_validate_environment_converts_api_key_to_x_api_key(self): + """Test that api-key header is converted to x-api-key (Azure Anthropic uses x-api-key)""" config = AzureAnthropicConfig() headers = {} model = "claude-sonnet-4-5" @@ -116,7 +117,7 @@ class TestAzureAnthropicConfig: ) as mock_validate: mock_validate.return_value = {"api-key": "test-api-key"} with patch.object( - config, "get_anthropic_headers", return_value={"x-api-key": "should-be-removed"} + config, "get_anthropic_headers", return_value={} ): result = config.validate_environment( headers=headers, @@ -126,9 +127,10 @@ class TestAzureAnthropicConfig: litellm_params=litellm_params, ) - # Verify x-api-key was removed - assert "x-api-key" not in result - assert "api-key" in result + # Verify api-key was converted to x-api-key + assert "x-api-key" in result + assert result["x-api-key"] == "test-api-key" + assert "api-key" not in result def test_validate_environment_sets_anthropic_version(self): """Test that anthropic-version header is set""" From 255d1bc2398646a10be00a9979992070f48c4d07 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 18:53:50 +0530 Subject: [PATCH 046/349] fix lint errors --- litellm/llms/azure/anthropic/handler.py | 6 ++---- .../llms/azure/anthropic/messages_transformation.py | 11 +---------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/litellm/llms/azure/anthropic/handler.py b/litellm/llms/azure/anthropic/handler.py index cfa5eeddaa9..e0aec942502 100644 --- a/litellm/llms/azure/anthropic/handler.py +++ b/litellm/llms/azure/anthropic/handler.py @@ -3,7 +3,7 @@ Azure Anthropic handler - reuses AnthropicChatCompletion logic with Azure authen """ import copy import json -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Callable, Union import httpx @@ -12,7 +12,6 @@ from litellm.llms.anthropic.chat.handler import AnthropicChatCompletion from litellm.llms.custom_httpx.http_handler import ( AsyncHTTPHandler, HTTPHandler, - get_async_httpx_client, ) from litellm.types.utils import ModelResponse from litellm.utils import CustomStreamWrapper @@ -20,8 +19,7 @@ from litellm.utils import CustomStreamWrapper from .transformation import AzureAnthropicConfig if TYPE_CHECKING: - from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper as CustomStreamWrapperType - from litellm.llms.base_llm.chat.transformation import BaseConfig + pass class AzureAnthropicChatCompletion(AnthropicChatCompletion): diff --git a/litellm/llms/azure/anthropic/messages_transformation.py b/litellm/llms/azure/anthropic/messages_transformation.py index 4640f68a3e0..55818cc07d6 100644 --- a/litellm/llms/azure/anthropic/messages_transformation.py +++ b/litellm/llms/azure/anthropic/messages_transformation.py @@ -1,19 +1,12 @@ """ Azure Anthropic messages transformation config - extends AnthropicMessagesConfig with Azure authentication """ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, List, Optional, Tuple -import httpx - -from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.litellm_core_utils.litellm_logging import verbose_logger from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( AnthropicMessagesConfig, ) from litellm.llms.azure.common_utils import BaseAzureLLM -from litellm.types.llms.anthropic_messages.anthropic_response import ( - AnthropicMessagesResponse, -) from litellm.types.router import GenericLiteLLMParams if TYPE_CHECKING: @@ -41,8 +34,6 @@ class AzureAnthropicMessagesConfig(AnthropicMessagesConfig): Validate environment and set up Azure authentication headers for /v1/messages endpoint. Azure Anthropic uses x-api-key header (not api-key). """ - from litellm.secret_managers.main import get_secret_str - # Convert dict to GenericLiteLLMParams if needed if isinstance(litellm_params, dict): if api_key and "api_key" not in litellm_params: From 1c612288bc4d69880e3c93fea63f579368374754 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 25 Nov 2025 20:19:55 +0530 Subject: [PATCH 047/349] fix lint errors --- litellm/llms/azure/anthropic/handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/llms/azure/anthropic/handler.py b/litellm/llms/azure/anthropic/handler.py index e0aec942502..cf4765190c2 100644 --- a/litellm/llms/azure/anthropic/handler.py +++ b/litellm/llms/azure/anthropic/handler.py @@ -176,7 +176,9 @@ class AzureAnthropicChatCompletion(AnthropicChatCompletion): timeout=timeout, json_mode=json_mode, ) - from litellm.llms.anthropic.common_utils import process_anthropic_headers + from litellm.llms.anthropic.common_utils import ( + process_anthropic_headers, + ) return CustomStreamWrapper( completion_stream=completion_stream, From 00e17c81a1f3d47b2b44ce1ffa350ef3bcbdc7ee Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Nov 2025 09:36:24 -0800 Subject: [PATCH 048/349] Add enforce user param functionality (#17088) * feat: Add reject_metadata_tags to proxy config Co-authored-by: krrishdholakia * Refactor: Rename reject_metadata_tags to reject_clientside_metadata_tags Co-authored-by: krrishdholakia --------- Co-authored-by: Cursor Agent --- docs/my-website/docs/proxy/config_settings.md | 2 + .../proxy/reject_clientside_metadata_tags.md | 120 ++++++++++++ litellm/proxy/_types.py | 4 + litellm/proxy/auth/auth_checks.py | 18 ++ ...eject_clientside_metadata_tags_config.yaml | 14 ++ .../proxy/auth/test_auth_checks.py | 179 ++++++++++++++++++ 6 files changed, 337 insertions(+) create mode 100644 docs/my-website/docs/proxy/reject_clientside_metadata_tags.md create mode 100644 litellm/proxy/example_config_yaml/reject_clientside_metadata_tags_config.yaml diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 4d1bc549e05..5a586035bcf 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -104,6 +104,7 @@ general_settings: disable_responses_id_security: boolean # turn off response ID security checks that prevent users from accessing other users' responses enable_jwt_auth: boolean # allow proxy admin to auth in via jwt tokens with 'litellm_proxy_admin' in claims enforce_user_param: boolean # requires all openai endpoint requests to have a 'user' param + reject_clientside_metadata_tags: boolean # if true, rejects requests with client-side 'metadata.tags' to prevent users from influencing budgets allowed_routes: ["route1", "route2"] # list of allowed proxy API routes - a user can access. (currently JWT-Auth only) key_management_system: google_kms # either google_kms or azure_kms master_key: string @@ -201,6 +202,7 @@ router_settings: | disable_responses_id_security | boolean | If true, disables response ID security checks that prevent users from accessing response IDs from other users. When false (default), response IDs are encrypted with user information to ensure users can only access their own responses. Applies to /v1/responses endpoints | | enable_jwt_auth | boolean | allow proxy admin to auth in via jwt tokens with 'litellm_proxy_admin' in claims. [Doc on JWT Tokens](token_auth) | | enforce_user_param | boolean | If true, requires all OpenAI endpoint requests to have a 'user' param. [Doc on call hooks](call_hooks)| +| reject_clientside_metadata_tags | boolean | If true, rejects requests that contain client-side 'metadata.tags' to prevent users from influencing budgets by sending different tags. Tags can only be inherited from the API key metadata. | | allowed_routes | array of strings | List of allowed proxy API routes a user can access [Doc on controlling allowed routes](enterprise#control-available-public-private-routes)| | key_management_system | string | Specifies the key management system. [Doc Secret Managers](../secret) | | master_key | string | The master key for the proxy [Set up Virtual Keys](virtual_keys) | diff --git a/docs/my-website/docs/proxy/reject_clientside_metadata_tags.md b/docs/my-website/docs/proxy/reject_clientside_metadata_tags.md new file mode 100644 index 00000000000..534c65939eb --- /dev/null +++ b/docs/my-website/docs/proxy/reject_clientside_metadata_tags.md @@ -0,0 +1,120 @@ +# Reject Client-Side Metadata Tags + +## Overview + +The `reject_clientside_metadata_tags` setting allows you to prevent users from passing client-side `metadata.tags` in their API requests. This ensures that tags are only inherited from the API key metadata and cannot be overridden by users to potentially influence budget tracking or routing decisions. + +## Use Case + +This feature is particularly useful in multi-tenant scenarios where: +- You want to enforce strict budget tracking based on API key tags +- You want to prevent users from manipulating routing decisions by sending custom client-side tags +- You need to ensure consistent tag-based filtering and reporting + +## Configuration + +Add the following to your `config.yaml`: + +```yaml +general_settings: + reject_clientside_metadata_tags: true # Default is false/null +``` + +## Behavior + +### When `reject_clientside_metadata_tags: true` + +**Rejected Request Example:** +```bash +curl -X POST http://localhost:4000/chat/completions \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}], + "metadata": { + "tags": ["custom-tag"] # This will be rejected + } + }' +``` + +**Error Response:** +```json +{ + "error": { + "message": "Client-side 'metadata.tags' not allowed in request. 'reject_clientside_metadata_tags'=True. Tags can only be set via API key metadata.", + "type": "bad_request_error", + "param": "metadata.tags", + "code": 400 + } +} +``` + +**Allowed Request Example:** +```bash +curl -X POST http://localhost:4000/chat/completions \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}], + "metadata": { + "custom_field": "value" # Other metadata fields are allowed + } + }' +``` + +### When `reject_clientside_metadata_tags: false` or not set + +All requests are allowed, including those with client-side `metadata.tags`. + +## Setting Tags via API Key + +When `reject_clientside_metadata_tags` is enabled, tags should be set on the API key metadata: + +```bash +curl -X POST http://localhost:4000/key/generate \ + -H "Authorization: Bearer sk-master-key" \ + -H "Content-Type: application/json" \ + -d '{ + "metadata": { + "tags": ["team-a", "production"] + } + }' +``` + +These tags will be automatically inherited by all requests made with that API key. + +## Complete Example Configuration + +```yaml +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + +general_settings: + master_key: sk-1234 + database_url: "postgresql://user:password@localhost:5432/litellm" + + # Reject client-side tags + reject_clientside_metadata_tags: true + + # Optional: Also enforce user parameter + enforce_user_param: true +``` + +## Similar Features + +- `enforce_user_param` - Requires all requests to include a 'user' parameter +- Tag-based routing - Use tags for intelligent request routing +- Budget tracking - Track spending per tag + +## Notes + +- This check only applies to LLM API routes (e.g., `/chat/completions`, `/embeddings`) +- Management endpoints (e.g., `/key/generate`) are not affected +- The check validates that client-side `metadata.tags` is not present in the request body +- Other metadata fields can still be passed in requests +- Tags set on API keys will still be applied to all requests diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 8a8bbdfe2ef..d209ac52ae9 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1889,6 +1889,10 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase): allowed_routes: Optional[List] = Field( None, description="Proxy API Endpoints you want users to be able to access" ) + reject_clientside_metadata_tags: Optional[bool] = Field( + None, + description="When set to True, rejects requests that contain client-side 'metadata.tags' to prevent users from influencing budgets by sending different tags. Tags can only be inherited from the API key metadata.", + ) enable_public_model_hub: bool = Field( default=False, description="Public model hub for users to see what models they have access to, supported openai params, etc.", diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 32795a1874d..c9774b18b88 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -186,6 +186,24 @@ async def common_checks( raise Exception( f"'user' param not passed in. 'enforce_user_param'={general_settings['enforce_user_param']}" ) + + # 6.1 [OPTIONAL] If 'reject_clientside_metadata_tags' enabled - reject request if it has client-side 'metadata.tags' + if ( + general_settings.get("reject_clientside_metadata_tags", None) is not None + and general_settings["reject_clientside_metadata_tags"] is True + ): + if ( + RouteChecks.is_llm_api_route(route=route) + and "metadata" in request_body + and isinstance(request_body["metadata"], dict) + and "tags" in request_body["metadata"] + ): + raise ProxyException( + message=f"Client-side 'metadata.tags' not allowed in request. 'reject_clientside_metadata_tags'={general_settings['reject_clientside_metadata_tags']}. Tags can only be set via API key metadata.", + type=ProxyErrorTypes.bad_request_error, + param="metadata.tags", + code=status.HTTP_400_BAD_REQUEST, + ) # 7. [OPTIONAL] If 'litellm.max_budget' is set (>0), is proxy under budget if ( litellm.max_budget > 0 diff --git a/litellm/proxy/example_config_yaml/reject_clientside_metadata_tags_config.yaml b/litellm/proxy/example_config_yaml/reject_clientside_metadata_tags_config.yaml new file mode 100644 index 00000000000..3c43c3c5374 --- /dev/null +++ b/litellm/proxy/example_config_yaml/reject_clientside_metadata_tags_config.yaml @@ -0,0 +1,14 @@ +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + +general_settings: + master_key: sk-1234 + database_url: "postgresql://user:password@localhost:5432/litellm" + + # Reject requests that contain client-side metadata.tags + # This prevents users from influencing budgets by sending different tags + # Tags can only be inherited from the API key metadata + reject_clientside_metadata_tags: true diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index 057b56ce317..6cd4bec3f18 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -732,3 +732,182 @@ async def test_get_team_object_raises_404_when_not_found(): assert exc_info.value.status_code == 404 assert "Team doesn't exist in db" in str(exc_info.value.detail) + + +# Reject Client-Side Metadata Tags Tests + + +@pytest.mark.asyncio +async def test_reject_clientside_metadata_tags_enabled_with_tags(): + """Test that common_checks rejects request when reject_clientside_metadata_tags is True and metadata.tags is present""" + from litellm.proxy.auth.auth_checks import common_checks + from fastapi import Request + + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "metadata": {"tags": ["custom-tag"]}, + } + + general_settings = {"reject_clientside_metadata_tags": True} + + # Create a mock request object + mock_request = MagicMock(spec=Request) + + with pytest.raises(ProxyException) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=None, + request=mock_request, + ) + + assert exc_info.value.type == ProxyErrorTypes.bad_request_error + assert "metadata.tags" in exc_info.value.message + assert exc_info.value.param == "metadata.tags" + assert exc_info.value.code == 400 + + +@pytest.mark.asyncio +async def test_reject_clientside_metadata_tags_enabled_without_tags(): + """Test that common_checks allows request when reject_clientside_metadata_tags is True but no metadata.tags is present""" + from litellm.proxy.auth.auth_checks import common_checks + from fastapi import Request + + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "metadata": {"custom_field": "value"}, # No tags field + } + + general_settings = {"reject_clientside_metadata_tags": True} + + # Create a mock request object + mock_request = MagicMock(spec=Request) + + # Should not raise an exception + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=None, + request=mock_request, + ) + + assert result is True + + +@pytest.mark.asyncio +async def test_reject_clientside_metadata_tags_disabled_with_tags(): + """Test that common_checks allows request with metadata.tags when reject_clientside_metadata_tags is False""" + from litellm.proxy.auth.auth_checks import common_checks + from fastapi import Request + + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "metadata": {"tags": ["custom-tag"]}, + } + + general_settings = {"reject_clientside_metadata_tags": False} + + # Create a mock request object + mock_request = MagicMock(spec=Request) + + # Should not raise an exception + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=None, + request=mock_request, + ) + + assert result is True + + +@pytest.mark.asyncio +async def test_reject_clientside_metadata_tags_not_set_with_tags(): + """Test that common_checks allows request with metadata.tags when reject_clientside_metadata_tags is not set""" + from litellm.proxy.auth.auth_checks import common_checks + from fastapi import Request + + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "metadata": {"tags": ["custom-tag"]}, + } + + general_settings = {} # No reject_clientside_metadata_tags setting + + # Create a mock request object + mock_request = MagicMock(spec=Request) + + # Should not raise an exception + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=None, + request=mock_request, + ) + + assert result is True + + +@pytest.mark.asyncio +async def test_reject_clientside_metadata_tags_non_llm_route(): + """Test that reject_clientside_metadata_tags check only applies to LLM API routes""" + from litellm.proxy.auth.auth_checks import common_checks + from fastapi import Request + + request_body = { + "metadata": {"tags": ["custom-tag"]}, + } + + general_settings = {"reject_clientside_metadata_tags": True} + + # Create a mock request object + mock_request = MagicMock(spec=Request) + + # Should not raise an exception for non-LLM route + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/key/generate", # Management route, not LLM route + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=None, + request=mock_request, + ) + + assert result is True From 59b4b9a07cc3e9f9f9619534d619b44597177d62 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 26 Nov 2025 00:02:48 +0530 Subject: [PATCH 049/349] fix documentation of anthropic azure --- docs/my-website/docs/providers/azure/azure.md | 2 +- docs/my-website/docs/providers/azure/azure_anthropic.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/providers/azure/azure.md b/docs/my-website/docs/providers/azure/azure.md index 971cd9fd492..0b9fd29e680 100644 --- a/docs/my-website/docs/providers/azure/azure.md +++ b/docs/my-website/docs/providers/azure/azure.md @@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem'; |-------|-------| | Description | Azure OpenAI Service provides REST API access to OpenAI's powerful language models including o1, o1-mini, GPT-5, GPT-4o, GPT-4o mini, GPT-4 Turbo with Vision, GPT-4, GPT-3.5-Turbo, and Embeddings model series. Also supports Claude models via Azure Foundry. | | Provider Route on LiteLLM | `azure/`, [`azure/o_series/`](#o-series-models), [`azure/gpt5_series/`](#gpt-5-models), [`azure/claude-*`](./azure_anthropic) (Claude models via Azure Foundry) | -| Supported Operations | [`/chat/completions`](#azure-openai-chat-completion-models), [`/responses`](./azure_responses), [`/completions`](#azure-instruct-models), [`/embeddings`](./azure_embedding), [`/audio/speech`](azure_speech), [`/audio/transcriptions`](../audio_transcription), `/fine_tuning`, [`/batches`](#azure-batches-api), `/files`, [`/images`](../image_generation#azure-openai-image-generation-models), [`/anthropic/v1/messages`](./azure_anthropic) (Claude passthrough) | +| Supported Operations | [`/chat/completions`](#azure-openai-chat-completion-models), [`/responses`](./azure_responses), [`/completions`](#azure-instruct-models), [`/embeddings`](./azure_embedding), [`/audio/speech`](azure_speech), [`/audio/transcriptions`](../audio_transcription), `/fine_tuning`, [`/batches`](#azure-batches-api), `/files`, [`/images`](../image_generation#azure-openai-image-generation-models), [`/anthropic/v1/messages`](./azure_anthropic) | | Link to Provider Doc | [Azure OpenAI ↗](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview), [Azure Foundry Claude ↗](https://learn.microsoft.com/en-us/azure/ai-services/foundry-models/claude) ## API Keys, Params diff --git a/docs/my-website/docs/providers/azure/azure_anthropic.md b/docs/my-website/docs/providers/azure/azure_anthropic.md index 6e0f2b60ef2..771912646b5 100644 --- a/docs/my-website/docs/providers/azure/azure_anthropic.md +++ b/docs/my-website/docs/providers/azure/azure_anthropic.md @@ -231,9 +231,9 @@ print(response) -## Messages API Passthrough +## Messages API -Azure Anthropic also supports the native Anthropic Messages API via passthrough. The endpoint structure is the same as Anthropic's `/v1/messages` API. +Azure Anthropic also supports the native Anthropic Messages API. The endpoint structure is the same as Anthropic's `/v1/messages` API. ### Using Anthropic SDK @@ -256,7 +256,7 @@ response = client.messages.create( print(response) ``` -### Using LiteLLM Proxy Passthrough +### Using LiteLLM Proxy ```bash curl --request POST \ From 42fa19a152c40bc6faf8bcc07dd82a404029f604 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 11:02:45 -0800 Subject: [PATCH 050/349] Revert "Test prisma changes for Gemini tests" This reverts commit 031bb3c5d90e429bf65210302ecae80e46cc505c. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d756755c552..a518628afb9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3047,7 +3047,6 @@ jobs: docker run -d \ -p 4000:4000 \ -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \ - -e USE_PRISMA_MIGRATE=True \ -e LITELLM_MASTER_KEY="sk-1234" \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ -e GEMINI_API_KEY=$GEMINI_API_KEY \ From 359025554ec8265b9531fe75347352e4493eb658 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 11:05:49 -0800 Subject: [PATCH 051/349] =?UTF-8?q?bump:=20version=200.4.6=20=E2=86=92=200?= =?UTF-8?q?.4.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- litellm-proxy-extras/pyproject.toml | 4 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index 78e34ccd01a..a22e3ca330c 100644 --- a/litellm-proxy-extras/pyproject.toml +++ b/litellm-proxy-extras/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-proxy-extras" -version = "0.4.6" +version = "0.4.7" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.4.6" +version = "0.4.7" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/pyproject.toml b/pyproject.toml index d485772b36e..15094667a53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.36.0", optional = true} redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = "^1.21.2", optional = true, python = ">=3.10"} -litellm-proxy-extras = {version = "0.4.6", optional = true} +litellm-proxy-extras = {version = "0.4.7", optional = true} rich = {version = "13.7.1", optional = true} litellm-enterprise = {version = "0.1.22", optional = true} diskcache = {version = "^5.6.1", optional = true} diff --git a/requirements.txt b/requirements.txt index 3a426d83e31..25b15b9b0fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,7 @@ sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests cryptography==44.0.1 tzdata==2025.1 # IANA time zone database -litellm-proxy-extras==0.4.6 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.7 # for proxy extras - e.g. prisma migrations ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.1 # for env tiktoken==0.8.0 # for calculating usage From aec0ab777bde6f59cd7fc4be153caad247e973de Mon Sep 17 00:00:00 2001 From: abi_jey Date: Tue, 25 Nov 2025 19:18:41 +0000 Subject: [PATCH 052/349] feat: add GA protocol as litellm_params for realtime api on azure provider --- litellm/llms/azure/realtime/handler.py | 54 ++++++++++++++++-- .../realtime/test_azure_realtime_handler.py | 56 +++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/litellm/llms/azure/realtime/handler.py b/litellm/llms/azure/realtime/handler.py index 8e5581206de..14f772ac074 100644 --- a/litellm/llms/azure/realtime/handler.py +++ b/litellm/llms/azure/realtime/handler.py @@ -28,16 +28,60 @@ async def forward_messages(client_ws: Any, backend_ws: Any): class AzureOpenAIRealtime(AzureChatCompletion): - def _construct_url(self, api_base: str, model: str, api_version: str) -> str: + def _get_realtime_protocol(self) -> str: + """Return the configured realtime protocol. + + Supported values (case-insensitive): + - "beta" -> use legacy `/openai/realtime` (current default) + - "v1" -> use `/openai/v1/realtime` + - "ga" -> alias for "v1" (GA path is v1) + + If the parameter is missing or invalid, we fall back to the current + behavior for full backwards compatibility. """ - Example output: + + # `litellm_params` is the standard place to configure provider-specific + # behavior. We keep this defensive in case the attribute isn't set. + params: Any = getattr(self, "litellm_params", None) + if not isinstance(params, dict): + return "beta" + + value = params.get("realtime_protocol") + if not isinstance(value, str): + return "beta" + + value_normalized = value.lower() + if value_normalized in {"v1", "ga"}: + return "v1" + + # Treat anything else (including explicit "beta") as current default + return "beta" + + def _construct_url( + self, + api_base: str, + model: str, + api_version: str, + ) -> str: + """Construct the websocket URL for Azure OpenAI realtime. + + Example default output (beta / legacy behavior): "wss://my-endpoint-sweden-berri992.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview"; + When `realtime_protocol` is set to "v1" or "GA" via `litellm_params`, + this switches to `/openai/v1/realtime`. """ + api_base = api_base.replace("https://", "wss://") - return ( - f"{api_base}/openai/realtime?api-version={api_version}&deployment={model}" - ) + + protocol = self._get_realtime_protocol() + if protocol == "v1": + path = "/openai/v1/realtime" + else: + # default / beta behavior + path = "/openai/realtime" + + return f"{api_base}{path}?api-version={api_version}&deployment={model}" async def async_realtime( self, diff --git a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py index 7bcbe371568..1446bc3df79 100644 --- a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py +++ b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py @@ -71,3 +71,59 @@ async def test_async_realtime_uses_max_size_parameter(): mock_realtime_streaming.assert_called_once() mock_streaming_instance.bidirectional_forward.assert_awaited_once() + +@pytest.mark.asyncio +async def test_construct_url_uses_legacy_realtime_by_default(): + """By default we should keep using `/openai/realtime` (beta behavior).""" + + from litellm.llms.azure.realtime.handler import AzureOpenAIRealtime + + handler = AzureOpenAIRealtime() + api_base = "https://my-endpoint.openai.azure.com" + api_version = "2024-10-01-preview" + model = "gpt-4o-realtime-preview" + + url = handler._construct_url(api_base=api_base, model=model, api_version=api_version) + + assert url.startswith("wss://my-endpoint.openai.azure.com") + assert "/openai/realtime" in url + assert "/openai/v1/realtime" not in url + + +@pytest.mark.asyncio +async def test_construct_url_uses_v1_when_realtime_protocol_v1_or_ga(): + """Setting `realtime_protocol` to v1/GA should switch to `/openai/v1/realtime`.""" + + from litellm.llms.azure.realtime.handler import AzureOpenAIRealtime + + api_base = "https://my-endpoint.openai.azure.com" + api_version = "2024-10-01-preview" + model = "gpt-4o-realtime-preview" + + # Helper to construct handler URL with a specific realtime_protocol. + # We avoid mutating handler attributes directly since type checkers don't + # know about `litellm_params` on this class. Instead, we patch the + # `_get_realtime_protocol` helper which is what `_construct_url` uses. + + # v1 -> /openai/v1/realtime + handler_v1 = AzureOpenAIRealtime() + with patch.object(handler_v1, "_get_realtime_protocol", return_value="v1"): + url_v1 = handler_v1._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/v1/realtime" in url_v1 + assert "/openai/realtime" not in url_v1 + + # GA (case-insensitive) -> /openai/v1/realtime + handler_ga = AzureOpenAIRealtime() + with patch.object(handler_ga, "_get_realtime_protocol", return_value="v1"): + url_ga = handler_ga._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/v1/realtime" in url_ga + assert "/openai/realtime" not in url_ga + + # beta or any other value keeps legacy path + handler_beta = AzureOpenAIRealtime() + with patch.object(handler_beta, "_get_realtime_protocol", return_value="beta"): + url_beta = handler_beta._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/realtime" in url_beta + assert "/openai/v1/realtime" not in url_beta + + From 67622fb0404877bf07865bc1be60932adc315376 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 26 Nov 2025 00:58:47 +0530 Subject: [PATCH 053/349] Add day 0 support for anthropic new feat (#17091) * Added tool search support for anthropic * Add programtic tool calling support * Add tool use input examples support * Add anthropic effort param support * Add anthropic effort param support * Add blog for new features * fix mypy and lint errors * fix mypy and lint errors * fix mypy and lint errors * fix mypy and lint errors * Add better handling * Add better handling --- .../blog/anthropic_advanced_features/index.md | 655 ++++++++++++++++ .../docs/providers/anthropic_effort.md | 276 +++++++ .../anthropic_programmatic_tool_calling.md | 430 ++++++++++ .../anthropic_tool_input_examples.md | 438 +++++++++++ .../docs/providers/anthropic_tool_search.md | 397 ++++++++++ litellm/llms/anthropic/chat/handler.py | 81 +- litellm/llms/anthropic/chat/transformation.py | 266 ++++++- litellm/llms/anthropic/common_utils.py | 101 +++ .../adapters/transformation.py | 2 +- litellm/types/llms/anthropic.py | 74 ++ litellm/types/llms/openai.py | 13 +- litellm/types/utils.py | 3 +- .../test_anthropic_chat_transformation.py | 741 ++++++++++++++++++ 13 files changed, 3420 insertions(+), 57 deletions(-) create mode 100644 docs/my-website/blog/anthropic_advanced_features/index.md create mode 100644 docs/my-website/docs/providers/anthropic_effort.md create mode 100644 docs/my-website/docs/providers/anthropic_programmatic_tool_calling.md create mode 100644 docs/my-website/docs/providers/anthropic_tool_input_examples.md create mode 100644 docs/my-website/docs/providers/anthropic_tool_search.md diff --git a/docs/my-website/blog/anthropic_advanced_features/index.md b/docs/my-website/blog/anthropic_advanced_features/index.md new file mode 100644 index 00000000000..71e5d9b1925 --- /dev/null +++ b/docs/my-website/blog/anthropic_advanced_features/index.md @@ -0,0 +1,655 @@ +--- +slug: anthropic_advanced_features +title: "Advanced Anthropic Features in LiteLLM: Tool Search, Programmatic Tool Calling, Input Examples, and Effort Control" +date: 2025-01-25T10:00:00 +authors: + - name: Sameer Kankute + title: SWE @ LiteLLM (LLM Translation) + url: https://www.linkedin.com/in/sameer-kankute/ + image_url: https://media.licdn.com/dms/image/v2/D4D03AQHB_loQYd5gjg/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1719137160975?e=1765411200&v=beta&t=c8396f--_lH6Fb_pVvx_jGholPfcl0bvwmNynbNdnII + - name: Krrish Dholakia + title: "CEO, LiteLLM" + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: "CTO, LiteLLM" + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +tags: [anthropic, claude, tool search, programmatic tool calling, effort, advanced features] +hide_table_of_contents: false +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +:::info + +This guide covers Anthropic's latest advanced features now available in LiteLLM: Tool Search, Programmatic Tool Calling, Tool Input Examples, and the Effort Parameter. + +::: + +We're excited to announce support for Anthropic's latest advanced features in LiteLLM! These powerful capabilities enable you to build more efficient, scalable, and cost-effective AI applications with Claude. + +## Table of Contents + +1. [Tool Search](#tool-search) +2. [Programmatic Tool Calling](#programmatic-tool-calling) +3. [Tool Input Examples](#tool-input-examples) +4. [Effort Parameter: Control Token Usage](#effort-parameter) +5. [Cost Tracking: Monitor Tool Search Usage](#cost-tracking) +6. [Combining Features](#combining-features) + +--- + +## Tool Search {#tool-search} + +### Usage Example + +```python +import litellm +import os + +# Configure your API key +os.environ["ANTHROPIC_API_KEY"] = "your-api-key" + +# Define your tools with defer_loading +tools = [ + # Tool search tool (regex variant) + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # Deferred tools - loaded on-demand + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location. Returns temperature and conditions.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + "description": "Temperature unit" + } + }, + "required": ["location"] + } + }, + "defer_loading": True # Load on-demand + }, + { + "type": "function", + "function": { + "name": "search_files", + "description": "Search through files in the workspace using keywords", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "file_types": { + "type": "array", + "items": {"type": "string"} + } + }, + "required": ["query"] + } + }, + "defer_loading": True + }, + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute SQL queries against the database", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string"} + }, + "required": ["sql"] + } + }, + "defer_loading": True + } +] + +# Make a request - Claude will search for and use relevant tools +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{ + "role": "user", + "content": "What's the weather like in San Francisco?" + }], + tools=tools +) + +print("Claude's response:", response.choices[0].message.content) +print("Tool calls:", response.choices[0].message.tool_calls) + +# Check tool search usage +if hasattr(response.usage, 'server_tool_use'): + print(f"Tool searches performed: {response.usage.server_tool_use.tool_search_requests}") +``` + +### BM25 Variant (Natural Language Search) + +For natural language queries instead of regex patterns: + +```python +tools = [ + { + "type": "tool_search_tool_bm25_20251119", # Natural language variant + "name": "tool_search_tool_bm25" + }, + # ... your deferred tools +] +``` + +--- + +## Programmatic Tool Calling {#programmatic-tool-calling} + +### Usage Example + +```python +import litellm +import json + +# Define tools that can be called programmatically +tools = [ + # Code execution tool (required for programmatic calling) + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + # Tool that can be called from code + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.", + "parameters": { + "type": "object", + "properties": { + "sql": { + "type": "string", + "description": "SQL query to execute" + } + }, + "required": ["sql"] + } + }, + "allowed_callers": ["code_execution_20250825"] # Enable programmatic calling + } +] + +# First request +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{ + "role": "user", + "content": "Query sales data for West, East, and Central regions, then tell me which had the highest revenue" + }], + tools=tools +) + +print("Claude's response:", response.choices[0].message) + +# Handle tool calls +messages = [ + {"role": "user", "content": "Query sales data for West, East, and Central regions, then tell me which had the highest revenue"}, + {"role": "assistant", "content": response.choices[0].message.content, "tool_calls": response.choices[0].message.tool_calls} +] + +# Process each tool call +for tool_call in response.choices[0].message.tool_calls: + # Check if it's a programmatic call + if hasattr(tool_call, 'caller') and tool_call.caller: + print(f"Programmatic call to {tool_call.function.name}") + print(f"Called from: {tool_call.caller}") + + # Simulate tool execution + if tool_call.function.name == "query_database": + args = json.loads(tool_call.function.arguments) + # Simulate database query + result = json.dumps([ + {"region": "West", "revenue": 150000}, + {"region": "East", "revenue": 180000}, + {"region": "Central", "revenue": 120000} + ]) + + messages.append({ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": tool_call.id, + "content": result + }] + }) + +# Get final response +final_response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=messages, + tools=tools +) + +print("\nFinal answer:", final_response.choices[0].message.content) +``` + +--- + +## Tool Input Examples {#tool-input-examples} + +### Usage Example + +```python +import litellm + +tools = [ + { + "type": "function", + "function": { + "name": "create_calendar_event", + "description": "Create a new calendar event with attendees and reminders", + "parameters": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "start_time": { + "type": "string", + "description": "ISO 8601 format: YYYY-MM-DDTHH:MM:SS" + }, + "duration_minutes": {"type": "integer"}, + "attendees": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": {"type": "string"}, + "optional": {"type": "boolean"} + } + } + }, + "reminders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "minutes_before": {"type": "integer"}, + "method": {"type": "string", "enum": ["email", "popup"]} + } + } + } + }, + "required": ["title", "start_time", "duration_minutes"] + } + }, + # Provide concrete examples + "input_examples": [ + { + "title": "Team Standup", + "start_time": "2025-01-15T09:00:00", + "duration_minutes": 30, + "attendees": [ + {"email": "alice@company.com", "optional": False}, + {"email": "bob@company.com", "optional": False} + ], + "reminders": [ + {"minutes_before": 15, "method": "popup"} + ] + }, + { + "title": "Lunch Break", + "start_time": "2025-01-15T12:00:00", + "duration_minutes": 60 + # Demonstrates optional fields can be omitted + } + ] + } +] + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{ + "role": "user", + "content": "Schedule a team meeting for tomorrow at 2pm for 45 minutes with john@company.com and sarah@company.com" + }], + tools=tools +) + +print("Tool call:", response.choices[0].message.tool_calls[0].function.arguments) +``` + +--- + +## Effort Parameter: Control Token Usage {#effort-parameter} + +### Usage Example + +```python +import litellm + +message = "Analyze the trade-offs between microservices and monolithic architectures" + +# High effort (default) - Maximum capability +response_high = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{"role": "user", "content": message}], + output_config={"effort": "high"} +) + +print("High effort response:") +print(response_high.choices[0].message.content) +print(f"Tokens used: {response_high.usage.completion_tokens}\n") + +# Medium effort - Balanced approach +response_medium = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{"role": "user", "content": message}], + output_config={"effort": "medium"} +) + +print("Medium effort response:") +print(response_medium.choices[0].message.content) +print(f"Tokens used: {response_medium.usage.completion_tokens}\n") + +# Low effort - Maximum efficiency +response_low = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{"role": "user", "content": message}], + output_config={"effort": "low"} +) + +print("Low effort response:") +print(response_low.choices[0].message.content) +print(f"Tokens used: {response_low.usage.completion_tokens}\n") + +# Compare token usage +print("Token Comparison:") +print(f"High: {response_high.usage.completion_tokens} tokens") +print(f"Medium: {response_medium.usage.completion_tokens} tokens") +print(f"Low: {response_low.usage.completion_tokens} tokens") +``` + +### Effort with Tool Use + +Lower effort affects both explanations and tool calls: + +```python +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + } + } +] + +# Low effort = fewer tool calls, more direct +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{ + "role": "user", + "content": "Check weather in San Francisco, New York, and London" + }], + tools=tools, + output_config={"effort": "low"} # May combine into fewer calls +) +``` + +--- + +## Cost Tracking: Monitor Tool Search Usage {#cost-tracking} + +### Understanding Tool Search Costs + +Tool search operations are tracked separately in the usage object, allowing you to monitor and optimize costs. + +### Tracking Example + +```python +import litellm + +tools = [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # ... 100 deferred tools +] + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{ + "role": "user", + "content": "Find and use the weather tool for San Francisco" + }], + tools=tools +) + +# Standard token usage +print("Token Usage:") +print(f" Input tokens: {response.usage.prompt_tokens}") +print(f" Output tokens: {response.usage.completion_tokens}") +print(f" Total tokens: {response.usage.total_tokens}") + +# Tool search specific usage +if hasattr(response.usage, 'server_tool_use') and response.usage.server_tool_use: + print(f"\nTool Search Usage:") + print(f" Search requests: {response.usage.server_tool_use.tool_search_requests}") + + # Calculate cost (example pricing) + input_cost = response.usage.prompt_tokens * 0.000003 # $3 per 1M tokens + output_cost = response.usage.completion_tokens * 0.000015 # $15 per 1M tokens + search_cost = response.usage.server_tool_use.tool_search_requests * 0.0001 # Example + + total_cost = input_cost + output_cost + search_cost + + print(f"\nCost Breakdown:") + print(f" Input tokens: ${input_cost:.6f}") + print(f" Output tokens: ${output_cost:.6f}") + print(f" Tool searches: ${search_cost:.6f}") + print(f" Total: ${total_cost:.6f}") +``` + +### Cost Optimization Tips + +1. **Keep frequently used tools non-deferred** (3-5 tools) +2. **Use tool search for large catalogs** (10+ tools) +3. **Monitor search requests** to identify optimization opportunities +4. **Combine with effort parameter** for maximum efficiency + +```python +# Optimized for cost +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{"role": "user", "content": "Simple query"}], + tools=tools_with_search, + output_config={"effort": "low"} # Reduce output tokens +) +``` + +--- + +## Combining Features {#combining-features} + +### The Power of Integration + +These features work together seamlessly. Here's a real-world example combining all of them: + +```python +import litellm +import json + +# Large tool catalog with search, programmatic calling, and examples +tools = [ + # Enable tool search + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # Enable programmatic calling + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + # Database tool with all features + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute SQL queries against the analytics database. Returns JSON array of results.", + "parameters": { + "type": "object", + "properties": { + "sql": { + "type": "string", + "description": "SQL SELECT statement" + }, + "limit": { + "type": "integer", + "description": "Maximum rows to return" + } + }, + "required": ["sql"] + } + }, + "defer_loading": True, # Tool search + "allowed_callers": ["code_execution_20250825"], # Programmatic calling + "input_examples": [ # Input examples + { + "sql": "SELECT region, SUM(revenue) as total FROM sales GROUP BY region", + "limit": 100 + } + ] + }, + # ... 50 more tools with defer_loading +] + +# Make request with effort control +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{ + "role": "user", + "content": "Analyze sales by region for the last quarter and identify top performers" + }], + tools=tools, + output_config={"effort": "medium"} # Balanced efficiency +) + +# Track comprehensive usage +print("Complete Usage Metrics:") +print(f" Input tokens: {response.usage.prompt_tokens}") +print(f" Output tokens: {response.usage.completion_tokens}") +print(f" Total tokens: {response.usage.total_tokens}") + +if hasattr(response.usage, 'server_tool_use') and response.usage.server_tool_use: + print(f" Tool searches: {response.usage.server_tool_use.tool_search_requests}") + +print(f"\nResponse: {response.choices[0].message.content}") +``` + +### Real-World Benefits + +This combination enables: + +1. **Massive scale** - Handle 1000+ tools efficiently +2. **Low latency** - Programmatic calling reduces round trips +3. **High accuracy** - Input examples ensure correct tool usage +4. **Cost control** - Effort parameter optimizes token spend +5. **Full visibility** - Track all usage metrics + +--- + +## Getting Started + +### Installation + +```bash +pip install litellm --upgrade +``` + +### Configuration + +```python +import os +import litellm + +# Set your API key +os.environ["ANTHROPIC_API_KEY"] = "your-api-key" + +# LiteLLM automatically handles beta headers for all features +``` + +### Supported Models + +| Feature | Supported Models | +|---------|-----------------| +| Tool Search | Claude Opus 4.5, Sonnet 4.5 | +| Programmatic Tool Calling | Claude Opus 4.5, Sonnet 4.5 | +| Input Examples | Claude Opus 4.5, Sonnet 4.5 | +| Effort Parameter | Claude Opus 4.5 only | + +### Supported Endpoints + +**Note**: All features are supported on the `/chat/completions` endpoint only. + +| Feature | Supported Models | +|---------|-----------------| +| Tool Search | Claude Opus 4.5, Sonnet 4.5 | +| Programmatic Tool Calling | Claude Opus 4.5, Sonnet 4.5 | +| Input Examples | Claude Opus 4.5, Sonnet 4.5 | +| Effort Parameter | Claude Opus 4.5 only | + +### Provider Support + +All features work across: +- ✅ Standard Anthropic API +- ✅ Azure Anthropic +- ✅ Vertex AI Anthropic +- ✅ LiteLLM Proxy + +--- + +## Conclusion + +These advanced Anthropic features in LiteLLM enable you to build more sophisticated, efficient, and cost-effective AI applications: + +- **Tool Search** scales to thousands of tools +- **Programmatic Tool Calling** reduces latency and tokens +- **Input Examples** improve accuracy +- **Effort Parameter** controls costs + +All features work seamlessly together and are supported across all Anthropic providers through LiteLLM's unified interface. + +### Resources + +- [LiteLLM Documentation](https://docs.litellm.ai/) +- [Anthropic Tool Search Docs](https://docs.litellm.ai/docs/providers/anthropic_tool_search) +- [Anthropic Programmatic Tool Calling Docs](https://docs.litellm.ai/docs/providers/anthropic_programmatic_tool_calling) +- [Anthropic Input Examples Docs](https://docs.litellm.ai/docs/providers/anthropic_tool_input_examples) +- [Anthropic Effort Parameter Docs](https://docs.litellm.ai/docs/providers/anthropic_effort) + +### Get Started Today + +```bash +pip install litellm --upgrade +``` + +Happy building! 🚀 + diff --git a/docs/my-website/docs/providers/anthropic_effort.md b/docs/my-website/docs/providers/anthropic_effort.md new file mode 100644 index 00000000000..d1116ad5be4 --- /dev/null +++ b/docs/my-website/docs/providers/anthropic_effort.md @@ -0,0 +1,276 @@ +# Anthropic Effort Parameter + +Control how many tokens Claude uses when responding with the `effort` parameter, trading off between response thoroughness and token efficiency. + +## Overview + +The `effort` parameter allows you to control how eager Claude is about spending tokens when responding to requests. This gives you the ability to trade off between response thoroughness and token efficiency, all with a single model. + +**Note**: The effort parameter is currently in beta and only supported by Claude Opus 4.5. You must include the beta header `effort-2025-11-24` when using this feature (LiteLLM automatically adds this header when `output_config` with `effort` is detected). + +## How Effort Works + +By default, Claude uses maximum effort—spending as many tokens as needed for the best possible outcome. By lowering the effort level, you can instruct Claude to be more conservative with token usage, optimizing for speed and cost while accepting some reduction in capability. + +**Tip**: Setting `effort` to `"high"` produces exactly the same behavior as omitting the `effort` parameter entirely. + +The effort parameter affects **all tokens** in the response, including: +- Text responses and explanations +- Tool calls and function arguments +- Extended thinking (when enabled) + +This approach has two major advantages: +1. It doesn't require thinking to be enabled in order to use it. +2. It can affect all token spend including tool calls. For example, lower effort would mean Claude makes fewer tool calls. + +This gives a much greater degree of control over efficiency. + +## Effort Levels + +| Level | Description | Typical use case | +|-------|-------------|------------------| +| `high` | Maximum capability—Claude uses as many tokens as needed for the best possible outcome. Equivalent to not setting the parameter. | Complex reasoning, difficult coding problems, agentic tasks | +| `medium` | Balanced approach with moderate token savings. | Agentic tasks that require a balance of speed, cost, and performance | +| `low` | Most efficient—significant token savings with some capability reduction. | Simpler tasks that need the best speed and lowest costs, such as subagents | + +## Quick Start + +### Using LiteLLM SDK + + + + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{ + "role": "user", + "content": "Analyze the trade-offs between microservices and monolithic architectures" + }], + output_config={ + "effort": "medium" + } +) + +print(response.choices[0].message.content) +``` + + + + +```typescript +import Anthropic from "@anthropic-ai/sdk"; + +const client = new Anthropic({ + apiKey: process.env.ANTHROPIC_API_KEY, +}); + +const response = await client.messages.create({ + model: "claude-opus-4-5-20251101", + max_tokens: 4096, + messages: [{ + role: "user", + content: "Analyze the trade-offs between microservices and monolithic architectures" + }], + output_config: { + effort: "medium" + } +}); + +console.log(response.content[0].text); +``` + + + + +### Using LiteLLM Proxy + +```bash +curl http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $LITELLM_API_KEY" \ + -d '{ + "model": "anthropic/claude-opus-4-5-20251101", + "messages": [{ + "role": "user", + "content": "Analyze the trade-offs between microservices and monolithic architectures" + }], + "output_config": { + "effort": "medium" + } + }' +``` + +### Direct Anthropic API Call + +```bash +curl https://api.anthropic.com/v1/messages \ + --header "x-api-key: $ANTHROPIC_API_KEY" \ + --header "anthropic-version: 2023-06-01" \ + --header "anthropic-beta: effort-2025-11-24" \ + --header "content-type: application/json" \ + --data '{ + "model": "claude-opus-4-5-20251101", + "max_tokens": 4096, + "messages": [{ + "role": "user", + "content": "Analyze the trade-offs between microservices and monolithic architectures" + }], + "output_config": { + "effort": "medium" + } + }' +``` + +## Model Compatibility + +The effort parameter is currently only supported by: +- **Claude Opus 4.5** (`claude-opus-4-5-20251101`) + +## When Should I Adjust the Effort Parameter? + +- Use **high effort** (the default) when you need Claude's best work—complex reasoning, nuanced analysis, difficult coding problems, or any task where quality is the top priority. + +- Use **medium effort** as a balanced option when you want solid performance without the full token expenditure of high effort. + +- Use **low effort** when you're optimizing for speed (because Claude answers with fewer tokens) or cost—for example, simple classification tasks, quick lookups, or high-volume use cases where marginal quality improvements don't justify additional latency or spend. + +## Effort with Tool Use + +When using tools, the effort parameter affects both the explanations around tool calls and the tool calls themselves. Lower effort levels tend to: +- Combine multiple operations into fewer tool calls +- Make fewer tool calls +- Proceed directly to action + +Example with tools: + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{ + "role": "user", + "content": "Check the weather in multiple cities" + }], + tools=[{ + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + } + }], + output_config={ + "effort": "low" # Will make fewer tool calls + } +) +``` + +## Effort with Extended Thinking + +The effort parameter works seamlessly with extended thinking. When both are enabled, effort controls the token budget across all response types: + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{ + "role": "user", + "content": "Solve this complex problem" + }], + thinking={ + "type": "enabled", + "budget_tokens": 5000 + }, + output_config={ + "effort": "medium" # Affects both thinking and response tokens + } +) +``` + +## Best Practices + +1. **Start with the default (high)** for new tasks, then experiment with lower effort levels if you're looking to optimize costs. + +2. **Use medium effort for production agentic workflows** where you need a balance of quality and efficiency. + +3. **Reserve low effort for high-volume, simple tasks** like classification, routing, or data extraction where speed matters more than nuanced responses. + +4. **Monitor token usage** to understand the actual savings from different effort levels for your specific use cases. + +5. **Test with your specific prompts** as the impact of effort levels can vary based on task complexity. + +## Provider Support + +The effort parameter is supported across all Anthropic-compatible providers: + +- **Standard Anthropic**: ✅ Supported (Claude Opus 4.5) +- **Azure Anthropic**: ✅ Supported (Claude Opus 4.5) +- **Vertex AI Anthropic**: ✅ Supported (Claude Opus 4.5) + +LiteLLM automatically handles the beta header injection for all providers. + +## Usage and Pricing + +Token usage with different effort levels is tracked in the standard usage object. Lower effort levels result in fewer output tokens, which directly reduces costs: + +```python +response = litellm.completion( + model="anthropic/claude-opus-4-5-20251101", + messages=[{"role": "user", "content": "Analyze this"}], + output_config={"effort": "low"} +) + +print(f"Output tokens: {response.usage.completion_tokens}") +print(f"Total tokens: {response.usage.total_tokens}") +``` + +## Troubleshooting + +### Beta header not being added + +LiteLLM automatically adds the `effort-2025-11-24` beta header when `output_config` with `effort` is detected. If you're not seeing the header: + +1. Ensure you're using `output_config` with an `effort` field +2. Verify the model is Claude Opus 4.5 +3. Check that LiteLLM version supports this feature + +### Invalid effort value error + +Only three values are accepted: `"high"`, `"medium"`, `"low"`. Any other value will raise a validation error: + +```python +# ❌ This will raise an error +output_config={"effort": "very_low"} + +# ✅ Use one of the valid values +output_config={"effort": "low"} +``` + +### Model not supported + +Currently, only Claude Opus 4.5 supports the effort parameter. Using it with other models may result in the parameter being ignored or an error. + +## Related Features + +- [Extended Thinking](/docs/providers/anthropic_extended_thinking) - Control Claude's reasoning process +- [Tool Use](/docs/providers/anthropic_tools) - Enable Claude to use tools and functions +- [Programmatic Tool Calling](/docs/providers/anthropic_programmatic_tool_calling) - Let Claude write code that calls tools +- [Prompt Caching](/docs/providers/anthropic_prompt_caching) - Cache prompts to reduce costs + +## Additional Resources + +- [Anthropic Effort Documentation](https://docs.anthropic.com/en/docs/build-with-claude/effort) +- [LiteLLM Anthropic Provider Guide](/docs/providers/anthropic) +- [Cost Optimization Best Practices](/docs/guides/cost_optimization) + diff --git a/docs/my-website/docs/providers/anthropic_programmatic_tool_calling.md b/docs/my-website/docs/providers/anthropic_programmatic_tool_calling.md new file mode 100644 index 00000000000..6d3e15785e5 --- /dev/null +++ b/docs/my-website/docs/providers/anthropic_programmatic_tool_calling.md @@ -0,0 +1,430 @@ +# Anthropic Programmatic Tool Calling + +Programmatic tool calling allows Claude to write code that calls your tools programmatically within a code execution container, rather than requiring round trips through the model for each tool invocation. This reduces latency for multi-tool workflows and decreases token consumption by allowing Claude to filter or process data before it reaches the model's context window. + +:::info +Programmatic tool calling is currently in public beta. LiteLLM automatically adds the required `advanced-tool-use-2025-11-20` beta header when it detects tools with the `allowed_callers` field. + +This feature requires the code execution tool to be enabled. +::: + +## Model Compatibility + +Programmatic tool calling is available on the following models: + +| Model | Tool Version | +|-------|--------------| +| Claude Opus 4.5 (`claude-opus-4-5-20251101`) | `code_execution_20250825` | +| Claude Sonnet 4.5 (`claude-sonnet-4-5-20250929`) | `code_execution_20250825` | + +## Quick Start + +Here's a simple example where Claude programmatically queries a database multiple times and aggregates results: + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[ + { + "role": "user", + "content": "Query sales data for the West, East, and Central regions, then tell me which region had the highest revenue" + } + ], + tools=[ + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.", + "parameters": { + "type": "object", + "properties": { + "sql": { + "type": "string", + "description": "SQL query to execute" + } + }, + "required": ["sql"] + } + }, + "allowed_callers": ["code_execution_20250825"] + } + ] +) + +print(response) +``` + +## How It Works + +When you configure a tool to be callable from code execution and Claude decides to use that tool: + +1. Claude writes Python code that invokes the tool as a function, potentially including multiple tool calls and pre/post-processing logic +2. Claude runs this code in a sandboxed container via code execution +3. When a tool function is called, code execution pauses and the API returns a `tool_use` block with a `caller` field +4. You provide the tool result, and code execution continues (intermediate results are not loaded into Claude's context window) +5. Once all code execution completes, Claude receives the final output and continues working on the task + +This approach is particularly useful for: + +- **Large data processing**: Filter or aggregate tool results before they reach Claude's context +- **Multi-step workflows**: Save tokens and latency by calling tools serially or in a loop without sampling Claude in-between tool calls +- **Conditional logic**: Make decisions based on intermediate tool results + +## The `allowed_callers` Field + +The `allowed_callers` field specifies which contexts can invoke a tool: + +```python +{ + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query against the database", + "parameters": {...} + }, + "allowed_callers": ["code_execution_20250825"] +} +``` + +**Possible values:** + +- `["direct"]` - Only Claude can call this tool directly (default if omitted) +- `["code_execution_20250825"]` - Only callable from within code execution +- `["direct", "code_execution_20250825"]` - Callable both directly and from code execution + +:::tip +We recommend choosing either `["direct"]` or `["code_execution_20250825"]` for each tool rather than enabling both, as this provides clearer guidance to Claude for how best to use the tool. +::: + +## The `caller` Field in Responses + +Every tool use block includes a `caller` field indicating how it was invoked: + +**Direct invocation (traditional tool use):** + +```python +{ + "type": "tool_use", + "id": "toolu_abc123", + "name": "query_database", + "input": {"sql": ""}, + "caller": {"type": "direct"} +} +``` + +**Programmatic invocation:** + +```python +{ + "type": "tool_use", + "id": "toolu_xyz789", + "name": "query_database", + "input": {"sql": ""}, + "caller": { + "type": "code_execution_20250825", + "tool_id": "srvtoolu_abc123" + } +} +``` + +The `tool_id` references the code execution tool that made the programmatic call. + +## Container Lifecycle + +Programmatic tool calling uses code execution containers: + +- **Container creation**: A new container is created for each session unless you reuse an existing one +- **Expiration**: Containers expire after approximately 4.5 minutes of inactivity (subject to change) +- **Container ID**: Pass the `container` parameter to reuse an existing container +- **Reuse**: Pass the container ID to maintain state across requests + +```python +# First request - creates a new container +response1 = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{"role": "user", "content": "Query the database"}], + tools=[...] +) + +# Get container ID from response (if available in response metadata) +container_id = response1.get("container", {}).get("id") + +# Second request - reuse the same container +response2 = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[...], + tools=[...], + container=container_id # Reuse container +) +``` + +:::warning +When a tool is called programmatically and the container is waiting for your tool result, you must respond before the container expires. Monitor the `expires_at` field. If the container expires, Claude may treat the tool call as timed out and retry it. +::: + +## Example Workflow + +### Step 1: Initial Request + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{ + "role": "user", + "content": "Query customer purchase history from the last quarter and identify our top 5 customers by revenue" + }], + tools=[ + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string", "description": "SQL query to execute"} + }, + "required": ["sql"] + } + }, + "allowed_callers": ["code_execution_20250825"] + } + ] +) +``` + +### Step 2: API Response with Tool Call + +Claude writes code that calls your tool. The response includes: + +```python +{ + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I'll query the purchase history and analyze the results." + }, + { + "type": "server_tool_use", + "id": "srvtoolu_abc123", + "name": "code_execution", + "input": { + "code": "results = await query_database('')\ntop_customers = sorted(results, key=lambda x: x['revenue'], reverse=True)[:5]" + } + }, + { + "type": "tool_use", + "id": "toolu_def456", + "name": "query_database", + "input": {"sql": ""}, + "caller": { + "type": "code_execution_20250825", + "tool_id": "srvtoolu_abc123" + } + } + ], + "stop_reason": "tool_use" +} +``` + +### Step 3: Provide Tool Result + +```python +# Add assistant's response and tool result to conversation +messages = [ + {"role": "user", "content": "Query customer purchase history..."}, + { + "role": "assistant", + "content": response.choices[0].message.content, + "tool_calls": response.choices[0].message.tool_calls + }, + { + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": "toolu_def456", + "content": '[{"customer_id": "C1", "revenue": 45000}, ...]' + } + ] + } +] + +# Continue the conversation +response2 = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=messages, + tools=[...] +) +``` + +### Step 4: Final Response + +Once code execution completes, Claude provides the final response: + +```python +{ + "content": [ + { + "type": "code_execution_tool_result", + "tool_use_id": "srvtoolu_abc123", + "content": { + "type": "code_execution_result", + "stdout": "Top 5 customers by revenue:\n1. Customer C1: $45,000\n...", + "stderr": "", + "return_code": 0 + } + }, + { + "type": "text", + "text": "I've analyzed the purchase history from last quarter. Your top 5 customers generated $167,500 in total revenue..." + } + ], + "stop_reason": "end_turn" +} +``` + +## Advanced Patterns + +### Batch Processing with Loops + +Claude can write code that processes multiple items efficiently: + +```python +# Claude writes code like this: +regions = ["West", "East", "Central", "North", "South"] +results = {} +for region in regions: + data = await query_database(f"SELECT SUM(revenue) FROM sales WHERE region='{region}'") + results[region] = data[0]["total"] + +top_region = max(results.items(), key=lambda x: x[1]) +print(f"Top region: {top_region[0]} with ${top_region[1]:,}") +``` + +This pattern: +- Reduces model round-trips from N (one per region) to 1 +- Processes large result sets programmatically before returning to Claude +- Saves tokens by only returning aggregated conclusions + +### Early Termination + +Claude can stop processing as soon as success criteria are met: + +```python +endpoints = ["us-east", "eu-west", "apac"] +for endpoint in endpoints: + status = await check_health(endpoint) + if status == "healthy": + print(f"Found healthy endpoint: {endpoint}") + break # Stop early +``` + +### Data Filtering + +```python +logs = await fetch_logs(server_id) +errors = [log for log in logs if "ERROR" in log] +print(f"Found {len(errors)} errors") +for error in errors[-10:]: # Only return last 10 errors + print(error) +``` + +## Best Practices + +### Tool Design + +- **Provide detailed output descriptions**: Since Claude deserializes tool results in code, clearly document the format (JSON structure, field types, etc.) +- **Return structured data**: JSON or other easily parseable formats work best for programmatic processing +- **Keep responses concise**: Return only necessary data to minimize processing overhead + +### When to Use Programmatic Calling + +**Good use cases:** + +- Processing large datasets where you only need aggregates or summaries +- Multi-step workflows with 3+ dependent tool calls +- Operations requiring filtering, sorting, or transformation of tool results +- Tasks where intermediate data shouldn't influence Claude's reasoning +- Parallel operations across many items (e.g., checking 50 endpoints) + +**Less ideal use cases:** + +- Single tool calls with simple responses +- Tools that need immediate user feedback +- Very fast operations where code execution overhead would outweigh the benefit + +## Token Efficiency + +Programmatic tool calling can significantly reduce token consumption: + +- **Tool results from programmatic calls are not added to Claude's context** - only the final code output is +- **Intermediate processing happens in code** - filtering, aggregation, etc. don't consume model tokens +- **Multiple tool calls in one code execution** - reduces overhead compared to separate model turns + +For example, calling 10 tools directly uses ~10x the tokens of calling them programmatically and returning a summary. + +## Provider Support + +LiteLLM supports programmatic tool calling across all Anthropic-compatible providers: + +- **Standard Anthropic API** (`anthropic/claude-sonnet-4-5-20250929`) +- **Azure Anthropic** (`azure/claude-sonnet-4-5-20250929`) +- **Vertex AI Anthropic** (`vertex_ai/claude-sonnet-4-5-20250929`) + +The beta header is automatically added when LiteLLM detects tools with `allowed_callers` field. + +## Limitations + +### Feature Incompatibilities + +- **Structured outputs**: Tools with `strict: true` are not supported with programmatic calling +- **Tool choice**: You cannot force programmatic calling of a specific tool via `tool_choice` +- **Parallel tool use**: `disable_parallel_tool_use: true` is not supported with programmatic calling + +### Tool Restrictions + +The following tools cannot currently be called programmatically: + +- Web search +- Web fetch +- Tools provided by an MCP connector + +## Troubleshooting + +### Common Issues + +**"Tool not allowed" error** + +- Verify your tool definition includes `"allowed_callers": ["code_execution_20250825"]` +- Check that you're using a compatible model (Claude Sonnet 4.5 or Opus 4.5) + +**Container expiration** + +- Ensure you respond to tool calls within the container's lifetime (~4.5 minutes) +- Consider implementing faster tool execution + +**Beta header not added** + +- LiteLLM automatically adds the beta header when it detects `allowed_callers` +- If you're manually setting headers, ensure you include `advanced-tool-use-2025-11-20` + +## Related Features + +- [Anthropic Tool Search](./anthropic_tool_search.md) - Dynamically discover and load tools on-demand +- [Anthropic Provider](./anthropic.md) - General Anthropic provider documentation + diff --git a/docs/my-website/docs/providers/anthropic_tool_input_examples.md b/docs/my-website/docs/providers/anthropic_tool_input_examples.md new file mode 100644 index 00000000000..d0b7cc1762c --- /dev/null +++ b/docs/my-website/docs/providers/anthropic_tool_input_examples.md @@ -0,0 +1,438 @@ +# Anthropic Tool Input Examples + +Provide concrete examples of valid tool inputs to help Claude understand how to use your tools more effectively. This is particularly useful for complex tools with nested objects, optional parameters, or format-sensitive inputs. + +:::info +Tool input examples is a beta feature. LiteLLM automatically adds the required `advanced-tool-use-2025-11-20` beta header when it detects tools with the `input_examples` field. +::: + +## When to Use Input Examples + +Input examples are most helpful for: + +- **Complex nested objects**: Tools with deeply nested parameter structures +- **Optional parameters**: Showing when optional parameters should be included +- **Format-sensitive inputs**: Demonstrating expected formats (dates, addresses, etc.) +- **Enum values**: Illustrating valid enum choices in context +- **Edge cases**: Showing how to handle special cases + +:::tip +**Prioritize descriptions first!** Clear, detailed tool descriptions are more important than examples. Use `input_examples` as a supplement for complex tools where descriptions alone may not be sufficient. +::: + +## Quick Start + +Add an `input_examples` field to your tool definition with an array of example input objects: + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[ + {"role": "user", "content": "What's the weather like in San Francisco?"} + ], + tools=[ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + "description": "The unit of temperature" + } + }, + "required": ["location"] + } + }, + "input_examples": [ + { + "location": "San Francisco, CA", + "unit": "fahrenheit" + }, + { + "location": "Tokyo, Japan", + "unit": "celsius" + }, + { + "location": "New York, NY" # 'unit' is optional + } + ] + } + ] +) + +print(response) +``` + +## How It Works + +When you provide `input_examples`: + +1. **LiteLLM detects** the `input_examples` field in your tool definition +2. **Beta header added automatically**: The `advanced-tool-use-2025-11-20` header is injected +3. **Examples included in prompt**: Anthropic includes the examples alongside your tool schema +4. **Claude learns patterns**: The model uses examples to understand proper tool usage +5. **Better tool calls**: Claude makes more accurate tool calls with correct parameter formats + +## Example Formats + +### Simple Tool with Examples + +```python +{ + "type": "function", + "function": { + "name": "send_email", + "description": "Send an email to a recipient", + "parameters": { + "type": "object", + "properties": { + "to": {"type": "string", "description": "Email address"}, + "subject": {"type": "string"}, + "body": {"type": "string"} + }, + "required": ["to", "subject", "body"] + } + }, + "input_examples": [ + { + "to": "user@example.com", + "subject": "Meeting Reminder", + "body": "Don't forget our meeting tomorrow at 2 PM." + }, + { + "to": "team@company.com", + "subject": "Weekly Update", + "body": "Here's this week's progress report..." + } + ] +} +``` + +### Complex Nested Objects + +```python +{ + "type": "function", + "function": { + "name": "create_calendar_event", + "description": "Create a new calendar event", + "parameters": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "start": { + "type": "object", + "properties": { + "date": {"type": "string"}, + "time": {"type": "string"} + } + }, + "attendees": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": {"type": "string"}, + "optional": {"type": "boolean"} + } + } + } + }, + "required": ["title", "start"] + } + }, + "input_examples": [ + { + "title": "Team Standup", + "start": { + "date": "2025-01-15", + "time": "09:00" + }, + "attendees": [ + {"email": "alice@example.com", "optional": False}, + {"email": "bob@example.com", "optional": True} + ] + }, + { + "title": "Lunch Break", + "start": { + "date": "2025-01-15", + "time": "12:00" + } + # No attendees - showing optional field + } + ] +} +``` + +### Format-Sensitive Parameters + +```python +{ + "type": "function", + "function": { + "name": "search_flights", + "description": "Search for available flights", + "parameters": { + "type": "object", + "properties": { + "origin": {"type": "string", "description": "Airport code"}, + "destination": {"type": "string", "description": "Airport code"}, + "date": {"type": "string", "description": "Date in YYYY-MM-DD format"}, + "passengers": {"type": "integer"} + }, + "required": ["origin", "destination", "date"] + } + }, + "input_examples": [ + { + "origin": "SFO", + "destination": "JFK", + "date": "2025-03-15", + "passengers": 2 + }, + { + "origin": "LAX", + "destination": "ORD", + "date": "2025-04-20", + "passengers": 1 + } + ] +} +``` + +## Requirements and Limitations + +### Schema Validation + +- Each example **must be valid** according to the tool's `input_schema` +- Invalid examples will return a **400 error** from Anthropic +- Validation happens server-side (LiteLLM passes examples through) + +### Server-Side Tools Not Supported + +Input examples are **only supported for user-defined tools**. The following server-side tools do NOT support `input_examples`: + +- `web_search` (web search tool) +- `code_execution` (code execution tool) +- `computer_use` (computer use tool) +- `bash_tool` (bash execution tool) +- `text_editor` (text editor tool) + +### Token Costs + +Examples add to your prompt tokens: + +- **Simple examples**: ~20-50 tokens per example +- **Complex nested objects**: ~100-200 tokens per example +- **Trade-off**: Higher token cost for better tool call accuracy + +### Model Compatibility + +Input examples work with all Claude models that support the `advanced-tool-use-2025-11-20` beta header: + +- Claude Opus 4.5 (`claude-opus-4-5-20251101`) +- Claude Sonnet 4.5 (`claude-sonnet-4-5-20250929`) +- Claude Opus 4.1 (`claude-opus-4-1-20250805`) + +:::note +On Google Cloud's Vertex AI and Amazon Bedrock, only Claude Opus 4.5 supports tool input examples. +::: + +## Best Practices + +### 1. Show Diverse Examples + +Include examples that demonstrate different use cases: + +```python +"input_examples": [ + {"location": "San Francisco, CA", "unit": "fahrenheit"}, # US city + {"location": "Tokyo, Japan", "unit": "celsius"}, # International + {"location": "New York, NY"} # Optional param omitted +] +``` + +### 2. Demonstrate Optional Parameters + +Show when optional parameters should and shouldn't be included: + +```python +"input_examples": [ + { + "query": "machine learning", + "filters": {"year": 2024, "category": "research"} # With optional filters + }, + { + "query": "artificial intelligence" # Without optional filters + } +] +``` + +### 3. Illustrate Format Requirements + +Make format expectations clear through examples: + +```python +"input_examples": [ + { + "phone": "+1-555-123-4567", # Shows expected phone format + "date": "2025-01-15", # Shows date format (YYYY-MM-DD) + "time": "14:30" # Shows time format (HH:MM) + } +] +``` + +### 4. Keep Examples Realistic + +Use realistic, production-like examples rather than placeholder data: + +```python +# ✅ Good - realistic examples +"input_examples": [ + {"email": "alice@company.com", "role": "admin"}, + {"email": "bob@company.com", "role": "user"} +] + +# ❌ Bad - placeholder examples +"input_examples": [ + {"email": "test@test.com", "role": "role1"}, + {"email": "example@example.com", "role": "role2"} +] +``` + +### 5. Limit Example Count + +Provide 2-5 examples per tool: + +- **Too few** (1): May not show enough variation +- **Just right** (2-5): Demonstrates patterns without bloating tokens +- **Too many** (10+): Wastes tokens, diminishing returns + +## Integration with Other Features + +Input examples work seamlessly with other Anthropic tool features: + +### With Tool Search + +```python +{ + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query", + "parameters": {...} + }, + "defer_loading": True, # Tool search + "input_examples": [ # Input examples + {"sql": "SELECT * FROM users WHERE id = 1"} + ] +} +``` + +### With Programmatic Tool Calling + +```python +{ + "type": "function", + "function": { + "name": "fetch_data", + "description": "Fetch data from API", + "parameters": {...} + }, + "allowed_callers": ["code_execution_20250825"], # Programmatic calling + "input_examples": [ # Input examples + {"endpoint": "/api/users", "method": "GET"} + ] +} +``` + +### All Features Combined + +```python +{ + "type": "function", + "function": { + "name": "advanced_tool", + "description": "A complex tool", + "parameters": {...} + }, + "defer_loading": True, # Tool search + "allowed_callers": ["code_execution_20250825"], # Programmatic calling + "input_examples": [ # Input examples + {"param1": "value1", "param2": "value2"} + ] +} +``` + +## Provider Support + +LiteLLM supports input examples across all Anthropic-compatible providers: + +- **Standard Anthropic API** (`anthropic/claude-sonnet-4-5-20250929`) +- **Azure Anthropic** (`azure/claude-sonnet-4-5-20250929`) +- **Vertex AI Anthropic** (`vertex_ai/claude-sonnet-4-5-20250929`) + +The beta header is automatically added when LiteLLM detects tools with `input_examples` field. + +## Troubleshooting + +### "Invalid request" error with examples + +**Problem**: Receiving 400 error when using input examples + +**Solution**: Ensure each example is valid according to your `input_schema`: + +```python +# Check that: +# 1. All required fields are present in examples +# 2. Field types match the schema +# 3. Enum values are valid +# 4. Nested objects follow the schema structure +``` + +### Examples not improving tool calls + +**Problem**: Adding examples doesn't seem to help + +**Solution**: +1. **Check descriptions first**: Ensure tool descriptions are detailed and clear +2. **Review example quality**: Make sure examples are realistic and diverse +3. **Verify schema**: Confirm examples actually match your schema +4. **Add more variation**: Include examples showing different use cases + +### Token usage too high + +**Problem**: Input examples consuming too many tokens + +**Solution**: +1. **Reduce example count**: Use 2-3 examples instead of 5+ +2. **Simplify examples**: Remove unnecessary fields from examples +3. **Consider descriptions**: If descriptions are clear, examples may not be needed + +## When NOT to Use Input Examples + +Skip input examples if: + +- **Tool is simple**: Single parameter tools with clear descriptions +- **Schema is self-explanatory**: Well-structured schema with good descriptions +- **Token budget is tight**: Examples add 20-200 tokens each +- **Server-side tools**: web_search, code_execution, etc. don't support examples + +## Related Features + +- [Anthropic Tool Search](./anthropic_tool_search.md) - Dynamically discover and load tools on-demand +- [Anthropic Programmatic Tool Calling](./anthropic_programmatic_tool_calling.md) - Call tools from code execution +- [Anthropic Provider](./anthropic.md) - General Anthropic provider documentation + diff --git a/docs/my-website/docs/providers/anthropic_tool_search.md b/docs/my-website/docs/providers/anthropic_tool_search.md new file mode 100644 index 00000000000..3d61022b26f --- /dev/null +++ b/docs/my-website/docs/providers/anthropic_tool_search.md @@ -0,0 +1,397 @@ +# Anthropic Tool Search + +Tool search enables Claude to dynamically discover and load tools on-demand from large tool catalogs (10,000+ tools). Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog and loads only the tools it needs. + +## Benefits + +- **Context efficiency**: Avoid consuming massive portions of your context window with tool definitions +- **Better tool selection**: Claude's tool selection accuracy degrades with more than 30-50 tools. Tool search maintains accuracy even with thousands of tools +- **On-demand loading**: Tools are only loaded when Claude needs them + +## Supported Models + +Tool search is available on: +- Claude Opus 4.5 +- Claude Sonnet 4.5 + +## Supported Platforms + +- Anthropic API (direct) +- Azure Anthropic (Microsoft Foundry) +- Google Cloud Vertex AI +- Amazon Bedrock (invoke API only, not converse API) + +## Tool Search Variants + +LiteLLM supports both tool search variants: + +### 1. Regex Tool Search (`tool_search_tool_regex_20251119`) + +Claude constructs regex patterns to search for tools. + +### 2. BM25 Tool Search (`tool_search_tool_bm25_20251119`) + +Claude uses natural language queries to search for tools using the BM25 algorithm. + +## Quick Start + +### Basic Example with Regex Tool Search + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[ + {"role": "user", "content": "What is the weather in San Francisco?"} + ], + tools=[ + # Tool search tool (regex variant) + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # Deferred tool - will be loaded on-demand + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather at a specific location", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"}, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"] + } + }, + "required": ["location"] + } + }, + "defer_loading": True # Mark for deferred loading + }, + # Another deferred tool + { + "type": "function", + "function": { + "name": "search_files", + "description": "Search through files in the workspace", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "file_types": { + "type": "array", + "items": {"type": "string"} + } + }, + "required": ["query"] + } + }, + "defer_loading": True + } + ] +) + +print(response.choices[0].message.content) +``` + +### BM25 Tool Search Example + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[ + {"role": "user", "content": "Search for Python files containing 'authentication'"} + ], + tools=[ + # Tool search tool (BM25 variant) + { + "type": "tool_search_tool_bm25_20251119", + "name": "tool_search_tool_bm25" + }, + # Deferred tools... + { + "type": "function", + "function": { + "name": "search_codebase", + "description": "Search through codebase files by content and filename", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "file_pattern": {"type": "string"} + }, + "required": ["query"] + } + }, + "defer_loading": True + } + ] +) +``` + +## Using with Azure Anthropic + +```python +import litellm + +response = litellm.completion( + model="azure_anthropic/claude-sonnet-4-5", + api_base="https://.services.ai.azure.com/anthropic", + api_key="your-azure-api-key", + messages=[ + {"role": "user", "content": "What's the weather like?"} + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + }, + "defer_loading": True + } + ] +) +``` + +## Using with Vertex AI + +```python +import litellm + +response = litellm.completion( + model="vertex_ai/claude-sonnet-4-5", + vertex_project="your-project-id", + vertex_location="us-central1", + messages=[ + {"role": "user", "content": "Search my documents"} + ], + tools=[ + { + "type": "tool_search_tool_bm25_20251119", + "name": "tool_search_tool_bm25" + }, + # Your deferred tools... + ] +) +``` + +## Streaming Support + +Tool search works with streaming: + +```python +import litellm + +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[ + {"role": "user", "content": "Get the weather"} + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + }, + "defer_loading": True + } + ], + stream=True +) + +for chunk in response: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="") +``` + +## LiteLLM Proxy + +Tool search works automatically through the LiteLLM proxy: + +### Proxy Config + +```yaml +model_list: + - model_name: claude-sonnet + litellm_params: + model: anthropic/claude-sonnet-4-5-20250929 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +### Client Request + +```python +import openai + +client = openai.OpenAI( + api_key="your-litellm-proxy-key", + base_url="http://0.0.0.0:4000" +) + +response = client.chat.completions.create( + model="claude-sonnet", + messages=[ + {"role": "user", "content": "What's the weather?"} + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + }, + "defer_loading": True + } + ] +) +``` + +## Important Notes + +### Beta Header + +LiteLLM automatically adds the `advanced-tool-use-2025-11-20` beta header when tool search tools are detected. You don't need to manually specify it. + +### Deferred Loading + +- Tools with `defer_loading: true` are only loaded when Claude discovers them via search +- At least one tool must be non-deferred (the tool search tool itself) +- Keep your 3-5 most frequently used tools as non-deferred for optimal performance + +### Tool Descriptions + +Write clear, descriptive tool names and descriptions that match how users describe tasks. The search algorithm uses: +- Tool names +- Tool descriptions +- Argument names +- Argument descriptions + +### Usage Tracking + +Tool search requests are tracked in the usage object: + +```python +response = litellm.completion( + model="anthropic/claude-sonnet-4-5-20250929", + messages=[{"role": "user", "content": "Search for tools"}], + tools=[...] +) + +# Check tool search usage +if response.usage.server_tool_use: + print(f"Tool search requests: {response.usage.server_tool_use.tool_search_requests}") +``` + +## Error Handling + +### All Tools Deferred + +```python +# ❌ This will fail - at least one tool must be non-deferred +tools = [ + { + "type": "function", + "function": {...}, + "defer_loading": True + } +] + +# ✅ Correct - tool search tool is non-deferred +tools = [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "type": "function", + "function": {...}, + "defer_loading": True + } +] +``` + +### Missing Tool Definition + +If Claude references a tool that isn't in your deferred tools list, you'll get an error. Make sure all tools that might be discovered are included in the tools parameter with `defer_loading: true`. + +## Best Practices + +1. **Keep frequently used tools non-deferred**: Your 3-5 most common tools should not have `defer_loading: true` + +2. **Use semantic descriptions**: Tool descriptions should use natural language that matches user queries + +3. **Choose the right variant**: + - Use **regex** for exact pattern matching (faster) + - Use **BM25** for natural language semantic search + +4. **Monitor usage**: Track `tool_search_requests` in the usage object to understand search patterns + +5. **Optimize tool catalog**: Remove unused tools and consolidate similar functionality + +## When to Use Tool Search + +**Good use cases:** +- 10+ tools available in your system +- Tool definitions consuming >10K tokens +- Experiencing tool selection accuracy issues +- Building systems with multiple tool categories +- Tool library growing over time + +**When traditional tool calling is better:** +- Less than 10 tools total +- All tools are frequently used +- Very small tool definitions (<100 tokens total) + +## Limitations + +- Not compatible with tool use examples +- Requires Claude Opus 4.5 or Sonnet 4.5 +- On Bedrock, only available via invoke API (not converse API) +- Maximum 10,000 tools in catalog +- Returns 3-5 most relevant tools per search + +## Additional Resources + +- [Anthropic Tool Search Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/tool-search) +- [LiteLLM Tool Calling Guide](https://docs.litellm.ai/docs/completion/function_call) + diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index b7b39f10395..b363b747de5 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -42,6 +42,7 @@ from litellm.types.llms.openai import ( ChatCompletionRedactedThinkingBlock, ChatCompletionThinkingBlock, ChatCompletionToolCallChunk, + ChatCompletionToolCallFunctionChunk, ) from litellm.types.utils import ( Delta, @@ -550,15 +551,18 @@ class ModelResponseIterator: if "text" in content_block["delta"]: text = content_block["delta"]["text"] elif "partial_json" in content_block["delta"]: - tool_use = { - "id": None, - "type": "function", - "function": { - "name": None, - "arguments": content_block["delta"]["partial_json"], + tool_use = cast( + ChatCompletionToolCallChunk, + { + "id": None, + "type": "function", + "function": { + "name": None, + "arguments": content_block["delta"]["partial_json"], + }, + "index": self.tool_index, }, - "index": self.tool_index, - } + ) elif "citation" in content_block["delta"]: provider_specific_fields["citation"] = content_block["delta"]["citation"] elif ( @@ -569,7 +573,7 @@ class ModelResponseIterator: ChatCompletionThinkingBlock( type="thinking", thinking=content_block["delta"].get("thinking") or "", - signature=content_block["delta"].get("signature"), + signature=str(content_block["delta"].get("signature") or ""), ) ] provider_specific_fields["thinking_blocks"] = thinking_blocks @@ -625,7 +629,7 @@ class ModelResponseIterator: return content_block_start - def chunk_parser(self, chunk: dict) -> ModelResponseStream: + def chunk_parser(self, chunk: dict) -> ModelResponseStream: # noqa: PLR0915 try: type_chunk = chunk.get("type", "") or "" @@ -672,15 +676,32 @@ class ModelResponseIterator: text = content_block_start["content_block"]["text"] elif content_block_start["content_block"]["type"] == "tool_use": self.tool_index += 1 - tool_use = { - "id": content_block_start["content_block"]["id"], - "type": "function", - "function": { - "name": content_block_start["content_block"]["name"], - "arguments": "", - }, - "index": self.tool_index, - } + tool_use = ChatCompletionToolCallChunk( + id=content_block_start["content_block"]["id"], + type="function", + function=ChatCompletionToolCallFunctionChunk( + name=content_block_start["content_block"]["name"], + arguments="", + ), + index=self.tool_index, + ) + # Include caller information if present (for programmatic tool calling) + if "caller" in content_block_start["content_block"]: + caller_data = content_block_start["content_block"]["caller"] + if caller_data: + tool_use["caller"] = cast(Dict[str, Any], caller_data) # type: ignore[typeddict-item] + elif content_block_start["content_block"]["type"] == "server_tool_use": + # Handle server tool use (for tool search) + self.tool_index += 1 + tool_use = ChatCompletionToolCallChunk( + id=content_block_start["content_block"]["id"], + type="function", + function=ChatCompletionToolCallFunctionChunk( + name=content_block_start["content_block"]["name"], + arguments="", + ), + index=self.tool_index, + ) elif ( content_block_start["content_block"]["type"] == "redacted_thinking" ): @@ -696,17 +717,21 @@ class ModelResponseIterator: # check if tool call content block is_empty = self.check_empty_tool_call_args() if is_empty: - tool_use = { - "id": None, - "type": "function", - "function": { - "name": None, - "arguments": "{}", - }, - "index": self.tool_index, - } + tool_use = ChatCompletionToolCallChunk( + id=None, # type: ignore[typeddict-item] + type="function", + function=ChatCompletionToolCallFunctionChunk( + name=None, # type: ignore[typeddict-item] + arguments="{}", + ), + index=self.tool_index, + ) # Reset response_format tool tracking when block stops self.is_response_format_tool = False + elif type_chunk == "tool_result": + # Handle tool_result blocks (for tool search results with tool_reference) + # These are automatically handled by Anthropic API, we just pass them through + pass elif type_chunk == "message_delta": finish_reason, usage = self._handle_message_delta(chunk) elif type_chunk == "message_start": diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 623a98c132f..ac1c9b1e000 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -54,7 +54,10 @@ from litellm.types.utils import ( CompletionTokensDetailsWrapper, ) from litellm.types.utils import Message as LitellmMessage -from litellm.types.utils import PromptTokensDetailsWrapper, ServerToolUse +from litellm.types.utils import ( + PromptTokensDetailsWrapper, + ServerToolUse, +) from litellm.utils import ( ModelResponse, Usage, @@ -187,7 +190,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) return _tool_choice - def _map_tool_helper( + def _map_tool_helper( # noqa: PLR0915 self, tool: ChatCompletionToolParam ) -> Tuple[Optional[AllAnthropicToolsValues], Optional[AnthropicMcpServerTool]]: returned_tool: Optional[AllAnthropicToolsValues] = None @@ -250,9 +253,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): returned_tool = _computer_tool elif any(tool["type"].startswith(t) for t in ANTHROPIC_HOSTED_TOOLS): - function_name = tool.get("name", tool.get("function", {}).get("name")) - if function_name is None or not isinstance(function_name, str): + function_name_obj = tool.get("name", tool.get("function", {}).get("name")) + if function_name_obj is None or not isinstance(function_name_obj, str): raise ValueError("Missing required parameter: name") + function_name = function_name_obj additional_tool_params = {} for k, v in tool.items(): @@ -268,6 +272,30 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): mcp_server = self._map_openai_mcp_server_tool( cast(OpenAIMcpServerTool, tool) ) + elif tool["type"] == "tool_search_tool_regex_20251119": + # Tool search tool using regex + from litellm.types.llms.anthropic import AnthropicToolSearchToolRegex + + tool_name_obj = tool.get("name", "tool_search_tool_regex") + if not isinstance(tool_name_obj, str): + raise ValueError("Tool search tool must have a valid name") + tool_name = tool_name_obj + returned_tool = AnthropicToolSearchToolRegex( + type="tool_search_tool_regex_20251119", + name=tool_name, + ) + elif tool["type"] == "tool_search_tool_bm25_20251119": + # Tool search tool using BM25 + from litellm.types.llms.anthropic import AnthropicToolSearchToolBM25 + + tool_name_obj = tool.get("name", "tool_search_tool_bm25") + if not isinstance(tool_name_obj, str): + raise ValueError("Tool search tool must have a valid name") + tool_name = tool_name_obj + returned_tool = AnthropicToolSearchToolBM25( + type="tool_search_tool_bm25_20251119", + name=tool_name, + ) if returned_tool is None and mcp_server is None: raise ValueError(f"Unsupported tool type: {tool['type']}") @@ -275,14 +303,67 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): _cache_control = tool.get("cache_control", None) _cache_control_function = tool.get("function", {}).get("cache_control", None) if returned_tool is not None: - if _cache_control is not None: - returned_tool["cache_control"] = _cache_control - elif _cache_control_function is not None and isinstance( - _cache_control_function, dict - ): - returned_tool["cache_control"] = ChatCompletionCachedContent( - **_cache_control_function # type: ignore - ) + # Only set cache_control on tools that support it (not tool search tools) + tool_type = returned_tool.get("type", "") + if tool_type not in ("tool_search_tool_regex_20251119", "tool_search_tool_bm25_20251119"): + if _cache_control is not None: + returned_tool["cache_control"] = _cache_control # type: ignore[typeddict-item] + elif _cache_control_function is not None and isinstance( + _cache_control_function, dict + ): + returned_tool["cache_control"] = ChatCompletionCachedContent( # type: ignore[typeddict-item] + **_cache_control_function # type: ignore + ) + + ## check if defer_loading is set in the tool + _defer_loading = tool.get("defer_loading", None) + _defer_loading_function = tool.get("function", {}).get("defer_loading", None) + if returned_tool is not None: + # Only set defer_loading on tools that support it (not tool search tools or computer tools) + tool_type = returned_tool.get("type", "") + if tool_type not in ("tool_search_tool_regex_20251119", "tool_search_tool_bm25_20251119", "computer_20241022", "computer_20250124"): + if _defer_loading is not None: + if not isinstance(_defer_loading, bool): + raise ValueError("defer_loading must be a boolean") + returned_tool["defer_loading"] = _defer_loading # type: ignore[typeddict-item] + elif _defer_loading_function is not None: + if not isinstance(_defer_loading_function, bool): + raise ValueError("defer_loading must be a boolean") + returned_tool["defer_loading"] = _defer_loading_function # type: ignore[typeddict-item] + + ## check if allowed_callers is set in the tool + _allowed_callers = tool.get("allowed_callers", None) + _allowed_callers_function = tool.get("function", {}).get("allowed_callers", None) + if returned_tool is not None: + # Only set allowed_callers on tools that support it (not tool search tools or computer tools) + tool_type = returned_tool.get("type", "") + if tool_type not in ("tool_search_tool_regex_20251119", "tool_search_tool_bm25_20251119", "computer_20241022", "computer_20250124"): + if _allowed_callers is not None: + if not isinstance(_allowed_callers, list) or not all( + isinstance(item, str) for item in _allowed_callers + ): + raise ValueError("allowed_callers must be a list of strings") + returned_tool["allowed_callers"] = _allowed_callers # type: ignore[typeddict-item] + elif _allowed_callers_function is not None: + if not isinstance(_allowed_callers_function, list) or not all( + isinstance(item, str) for item in _allowed_callers_function + ): + raise ValueError("allowed_callers must be a list of strings") + returned_tool["allowed_callers"] = _allowed_callers_function # type: ignore[typeddict-item] + + ## check if input_examples is set in the tool + _input_examples = tool.get("input_examples", None) + _input_examples_function = tool.get("function", {}).get("input_examples", None) + if returned_tool is not None: + # Only set input_examples on user-defined tools (type "custom" or no type) + tool_type = returned_tool.get("type", "") + if tool_type == "custom" or (tool_type == "" and "name" in returned_tool): + if _input_examples is not None and isinstance(_input_examples, list): + returned_tool["input_examples"] = _input_examples # type: ignore[typeddict-item] + elif _input_examples_function is not None and isinstance( + _input_examples_function, list + ): + returned_tool["input_examples"] = _input_examples_function # type: ignore[typeddict-item] return returned_tool, mcp_server @@ -334,6 +415,82 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): mcp_servers.append(mcp_server_tool) return anthropic_tools, mcp_servers + def _detect_tool_search_tools(self, tools: Optional[List]) -> bool: + """Check if tool search tools are present in the tools list.""" + if not tools: + return False + + for tool in tools: + tool_type = tool.get("type", "") + if tool_type in ["tool_search_tool_regex_20251119", "tool_search_tool_bm25_20251119"]: + return True + return False + + def _separate_deferred_tools( + self, tools: List + ) -> Tuple[List, List]: + """ + Separate tools into deferred and non-deferred lists. + + Returns: + Tuple of (non_deferred_tools, deferred_tools) + """ + non_deferred = [] + deferred = [] + + for tool in tools: + if tool.get("defer_loading", False): + deferred.append(tool) + else: + non_deferred.append(tool) + + return non_deferred, deferred + + def _expand_tool_references( + self, + content: List, + deferred_tools: List, + ) -> List: + """ + Expand tool_reference blocks to full tool definitions. + + When Anthropic's tool search returns results, it includes tool_reference blocks + that reference tools by name. This method expands those references to full + tool definitions from the deferred_tools catalog. + + Args: + content: Response content that may contain tool_reference blocks + deferred_tools: List of deferred tools that can be referenced + + Returns: + Content with tool_reference blocks expanded to full tool definitions + """ + if not deferred_tools: + return content + + # Create a mapping of tool names to tool definitions + tool_map = {} + for tool in deferred_tools: + tool_name = tool.get("name") or tool.get("function", {}).get("name") + if tool_name: + tool_map[tool_name] = tool + + # Expand tool references in content + expanded_content = [] + for item in content: + if isinstance(item, dict) and item.get("type") == "tool_reference": + tool_name = item.get("tool_name") + if tool_name and tool_name in tool_map: + # Replace reference with full tool definition + expanded_content.append(tool_map[tool_name]) + else: + # Keep the reference if we can't find the tool + expanded_content.append(item) + else: + expanded_content.append(item) + + return expanded_content + def _map_stop_sequences( self, stop: Optional[Union[str, List[str]]] ) -> Optional[List[str]]: @@ -822,6 +979,17 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): "messages": anthropic_messages, **optional_params, } + + ## Handle output_config (Anthropic-specific parameter) + if "output_config" in optional_params: + output_config = optional_params.get("output_config") + if output_config and isinstance(output_config, dict): + effort = output_config.get("effort") + if effort and effort not in ["high", "medium", "low"]: + raise ValueError( + f"Invalid effort value: {effort}. Must be one of: 'high', 'medium', 'low'" + ) + data["output_config"] = output_config return data @@ -870,18 +1038,40 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): text_content += content["text"] ## TOOL CALLING elif content["type"] == "tool_use": - tool_calls.append( - ChatCompletionToolCallChunk( - id=content["id"], - type="function", - function=ChatCompletionToolCallFunctionChunk( - name=content["name"], - arguments=json.dumps(content["input"]), - ), - index=idx, - ) + tool_call = ChatCompletionToolCallChunk( + id=content["id"], + type="function", + function=ChatCompletionToolCallFunctionChunk( + name=content["name"], + arguments=json.dumps(content["input"]), + ), + index=idx, ) - + # Include caller information if present (for programmatic tool calling) + if "caller" in content: + tool_call["caller"] = cast(Dict[str, Any], content["caller"]) # type: ignore[typeddict-item] + tool_calls.append(tool_call) + ## SERVER TOOL USE (for tool search) + elif content["type"] == "server_tool_use": + # Server tool use blocks are for tool search - treat as tool calls + tool_call = ChatCompletionToolCallChunk( + id=content["id"], + type="function", + function=ChatCompletionToolCallFunctionChunk( + name=content["name"], + arguments=json.dumps(content.get("input", {})), + ), + index=idx, + ) + # Include caller information if present (for programmatic tool calling) + if "caller" in content: + tool_call["caller"] = cast(Dict[str, Any], content["caller"]) # type: ignore[typeddict-item] + tool_calls.append(tool_call) + ## TOOL SEARCH TOOL RESULT (skip - this is metadata about tool discovery) + elif content["type"] == "tool_search_tool_result": + # This block contains tool_references that were discovered + # We don't need to include this in the response as it's internal metadata + pass elif content.get("thinking", None) is not None: if thinking_blocks is None: thinking_blocks = [] @@ -916,7 +1106,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): return text_content, citations, thinking_blocks, reasoning_content, tool_calls def calculate_usage( - self, usage_object: dict, reasoning_content: Optional[str] + self, usage_object: dict, reasoning_content: Optional[str], completion_response: Optional[dict] = None ) -> Usage: # NOTE: Sometimes the usage object has None set explicitly for token counts, meaning .get() & key access returns None, and we need to account for this prompt_tokens = usage_object.get("input_tokens", 0) or 0 @@ -926,6 +1116,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): cache_read_input_tokens: int = 0 cache_creation_token_details: Optional[CacheCreationTokenDetails] = None web_search_requests: Optional[int] = None + tool_search_requests: Optional[int] = None if ( "cache_creation_input_tokens" in _usage and _usage["cache_creation_input_tokens"] is not None @@ -946,6 +1137,25 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): web_search_requests = cast( int, _usage["server_tool_use"]["web_search_requests"] ) + if ( + "tool_search_requests" in _usage["server_tool_use"] + and _usage["server_tool_use"]["tool_search_requests"] is not None + ): + tool_search_requests = cast( + int, _usage["server_tool_use"]["tool_search_requests"] + ) + + # Count tool_search_requests from content blocks if not in usage + # Anthropic doesn't always include tool_search_requests in the usage object + if tool_search_requests is None and completion_response is not None: + tool_search_count = 0 + for content in completion_response.get("content", []): + if content.get("type") == "server_tool_use": + tool_name = content.get("name", "") + if "tool_search" in tool_name: + tool_search_count += 1 + if tool_search_count > 0: + tool_search_requests = tool_search_count if "cache_creation" in _usage and _usage["cache_creation"] is not None: cache_creation_token_details = CacheCreationTokenDetails( @@ -982,8 +1192,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): cache_read_input_tokens=cache_read_input_tokens, completion_tokens_details=completion_token_details, server_tool_use=( - ServerToolUse(web_search_requests=web_search_requests) - if web_search_requests is not None + ServerToolUse( + web_search_requests=web_search_requests, + tool_search_requests=tool_search_requests, + ) + if (web_search_requests is not None or tool_search_requests is not None) else None ), ) @@ -1077,6 +1290,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): usage = self.calculate_usage( usage_object=completion_response["usage"], reasoning_content=reasoning_content, + completion_response=completion_response, ) setattr(model_response, "usage", usage) # type: ignore diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 0d00a3b4632..9f5688f9e01 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -88,6 +88,86 @@ class AnthropicModelInfo(BaseLLMModelInfo): return True return False + def is_tool_search_used(self, tools: Optional[List]) -> bool: + """ + Check if tool search tools are present in the tools list. + """ + if not tools: + return False + + for tool in tools: + tool_type = tool.get("type", "") + if tool_type in ["tool_search_tool_regex_20251119", "tool_search_tool_bm25_20251119"]: + return True + return False + + def is_programmatic_tool_calling_used(self, tools: Optional[List]) -> bool: + """ + Check if programmatic tool calling is being used (tools with allowed_callers field). + + Returns True if any tool has allowed_callers containing 'code_execution_20250825'. + """ + if not tools: + return False + + for tool in tools: + # Check top-level allowed_callers + allowed_callers = tool.get("allowed_callers", None) + if allowed_callers and isinstance(allowed_callers, list): + if "code_execution_20250825" in allowed_callers: + return True + + # Check function.allowed_callers for OpenAI format tools + function = tool.get("function", {}) + if isinstance(function, dict): + function_allowed_callers = function.get("allowed_callers", None) + if function_allowed_callers and isinstance(function_allowed_callers, list): + if "code_execution_20250825" in function_allowed_callers: + return True + + return False + + def is_input_examples_used(self, tools: Optional[List]) -> bool: + """ + Check if input_examples is being used in any tools. + + Returns True if any tool has input_examples field. + """ + if not tools: + return False + + for tool in tools: + # Check top-level input_examples + input_examples = tool.get("input_examples", None) + if input_examples and isinstance(input_examples, list) and len(input_examples) > 0: + return True + + # Check function.input_examples for OpenAI format tools + function = tool.get("function", {}) + if isinstance(function, dict): + function_input_examples = function.get("input_examples", None) + if function_input_examples and isinstance(function_input_examples, list) and len(function_input_examples) > 0: + return True + + return False + + def is_effort_used(self, optional_params: Optional[dict]) -> bool: + """ + Check if effort parameter is being used via output_config. + + Returns True if output_config with effort field is present. + """ + if not optional_params: + return False + + output_config = optional_params.get("output_config") + if output_config and isinstance(output_config, dict): + effort = output_config.get("effort") + if effort and isinstance(effort, str): + return True + + return False + def _get_user_anthropic_beta_headers( self, anthropic_beta_header: Optional[str] ) -> Optional[List[str]]: @@ -122,6 +202,10 @@ class AnthropicModelInfo(BaseLLMModelInfo): pdf_used: bool = False, file_id_used: bool = False, mcp_server_used: bool = False, + tool_search_used: bool = False, + programmatic_tool_calling_used: bool = False, + input_examples_used: bool = False, + effort_used: bool = False, is_vertex_request: bool = False, user_anthropic_beta_headers: Optional[List[str]] = None, ) -> dict: @@ -138,6 +222,15 @@ class AnthropicModelInfo(BaseLLMModelInfo): betas.add("code-execution-2025-05-22") if mcp_server_used: betas.add("mcp-client-2025-04-04") + # Tool search, programmatic tool calling, and input_examples all use the same beta header + if tool_search_used or programmatic_tool_calling_used or input_examples_used: + from litellm.types.llms.anthropic import ANTHROPIC_TOOL_SEARCH_BETA_HEADER + betas.add(ANTHROPIC_TOOL_SEARCH_BETA_HEADER) + + # Effort parameter uses a separate beta header + if effort_used: + from litellm.types.llms.anthropic import ANTHROPIC_EFFORT_BETA_HEADER + betas.add(ANTHROPIC_EFFORT_BETA_HEADER) headers = { "anthropic-version": anthropic_version or "2023-06-01", @@ -182,6 +275,10 @@ class AnthropicModelInfo(BaseLLMModelInfo): ) pdf_used = self.is_pdf_used(messages=messages) file_id_used = self.is_file_id_used(messages=messages) + tool_search_used = self.is_tool_search_used(tools=tools) + programmatic_tool_calling_used = self.is_programmatic_tool_calling_used(tools=tools) + input_examples_used = self.is_input_examples_used(tools=tools) + effort_used = self.is_effort_used(optional_params=optional_params) user_anthropic_beta_headers = self._get_user_anthropic_beta_headers( anthropic_beta_header=headers.get("anthropic-beta") ) @@ -194,6 +291,10 @@ class AnthropicModelInfo(BaseLLMModelInfo): is_vertex_request=optional_params.get("is_vertex_request", False), user_anthropic_beta_headers=user_anthropic_beta_headers, mcp_server_used=mcp_server_used, + tool_search_used=tool_search_used, + programmatic_tool_calling_used=programmatic_tool_calling_used, + input_examples_used=input_examples_used, + effort_used=effort_used, ) headers = {**headers, **anthropic_headers} diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 0e905014fe2..98e57f279cf 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -645,7 +645,7 @@ class LiteLLMAnthropicMessagesAdapter: type="tool_use", id=choice.delta.tool_calls[0].id or str(uuid.uuid4()), name=choice.delta.tool_calls[0].function.name or "", - input={}, + input={}, # type: ignore[typeddict-item] ) elif isinstance(choice, StreamingChoices) and hasattr( choice.delta, "thinking_blocks" diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index fc210a7d084..507b382f785 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -36,12 +36,20 @@ class AnthropicOutputSchema(TypedDict, total=False): schema: Required[dict] +class AnthropicOutputConfig(TypedDict, total=False): + """Configuration for controlling Claude's output behavior.""" + effort: Literal["high", "medium", "low"] + + class AnthropicMessagesTool(TypedDict, total=False): name: Required[str] description: str input_schema: Optional[AnthropicInputSchema] type: Literal["custom"] cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + defer_loading: bool + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] class AnthropicComputerTool(TypedDict, total=False): @@ -67,24 +75,78 @@ class AnthropicWebSearchTool(TypedDict, total=False): cache_control: Optional[Union[dict, ChatCompletionCachedContent]] max_uses: Optional[int] user_location: Optional[AnthropicWebSearchUserLocation] + defer_loading: Optional[bool] + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] class AnthropicHostedTools(TypedDict, total=False): # for bash_tool and text_editor type: Required[str] name: Required[str] cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + defer_loading: Optional[bool] + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] class AnthropicCodeExecutionTool(TypedDict, total=False): type: Required[str] name: Required[Literal["code_execution"]] cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + defer_loading: Optional[bool] + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] class AnthropicMemoryTool(TypedDict, total=False): type: Required[str] name: Required[Literal["memory"]] cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + defer_loading: Optional[bool] + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] + + +class AnthropicToolSearchToolRegex(TypedDict, total=False): + """Tool search tool using regex patterns for tool discovery.""" + type: Required[Literal["tool_search_tool_regex_20251119"]] + name: Required[str] + + +class AnthropicToolSearchToolBM25(TypedDict, total=False): + """Tool search tool using BM25 algorithm for tool discovery.""" + type: Required[Literal["tool_search_tool_bm25_20251119"]] + name: Required[str] + cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + defer_loading: Optional[bool] + allowed_callers: Optional[List[str]] + input_examples: Optional[List[Dict[str, Any]]] + + +class ToolReference(TypedDict, total=False): + """Reference to a tool that should be expanded from deferred tools.""" + type: Required[Literal["tool_reference"]] + tool_name: Required[str] + + +class DirectToolCaller(TypedDict, total=False): + """Indicates a tool was called directly by Claude.""" + type: Required[Literal["direct"]] + + +class CodeExecutionToolCaller(TypedDict, total=False): + """Indicates a tool was called programmatically from code execution.""" + type: Required[Literal["code_execution_20250825"]] + tool_id: Required[str] # ID of the code execution tool that made the call + + +ToolCaller = Union[DirectToolCaller, CodeExecutionToolCaller] + + +class AnthropicContainer(TypedDict, total=False): + """Container metadata for code execution.""" + id: Required[str] + expires_at: Optional[str] # ISO 8601 timestamp AllAnthropicToolsValues = Union[ @@ -94,6 +156,8 @@ AllAnthropicToolsValues = Union[ AnthropicWebSearchTool, AnthropicCodeExecutionTool, AnthropicMemoryTool, + AnthropicToolSearchToolRegex, + AnthropicToolSearchToolBM25, ] @@ -121,6 +185,7 @@ class AnthropicMessagesToolUseParam(TypedDict, total=False): name: str input: dict cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + caller: Optional[ToolCaller] AnthropicMessagesAssistantMessageValues = Union[ @@ -372,6 +437,7 @@ class ToolUseBlock(TypedDict): name: str type: Literal["tool_use"] + caller: Optional[ToolCaller] class TextBlock(TypedDict): @@ -565,3 +631,11 @@ class ANTHROPIC_BETA_HEADER_VALUES(str, Enum): WEB_FETCH_2025_09_10 = "web-fetch-2025-09-10" CONTEXT_MANAGEMENT_2025_06_27 = "context-management-2025-06-27" STRUCTURED_OUTPUT_2025_09_25 = "structured-outputs-2025-11-13" + ADVANCED_TOOL_USE_2025_11_20 = "advanced-tool-use-2025-11-20" + + +# Tool search beta header constant +ANTHROPIC_TOOL_SEARCH_BETA_HEADER = "advanced-tool-use-2025-11-20" + +# Effort beta header constant +ANTHROPIC_EFFORT_BETA_HEADER = "effort-2025-11-24" diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index c29b2f32ea5..61d58e4c86d 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -1,6 +1,17 @@ from enum import Enum from os import PathLike -from typing import IO, Any, Dict, Iterable, List, Literal, Mapping, Optional, Tuple, Union +from typing import ( + IO, + Any, + Dict, + Iterable, + List, + Literal, + Mapping, + Optional, + Tuple, + Union, +) import httpx from openai._legacy_response import ( diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 0f73407610b..b0d081d8f87 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -999,7 +999,8 @@ class PromptTokensDetailsWrapper( class ServerToolUse(BaseModel): - web_search_requests: Optional[int] + web_search_requests: Optional[int] = None + tool_search_requests: Optional[int] = None class Usage(CompletionUsage): diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 8ff2f0a4474..09c16add77a 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -556,3 +556,744 @@ def test_anthropic_structured_output_beta_header(): "structured-outputs-2025-11-13" in response["raw_request_headers"]["anthropic-beta"] ) + + +# ============ Tool Search Tests ============ + + +def test_tool_search_regex_detection(): + """Test that tool search regex tools are properly detected""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + config = AnthropicModelInfo() + + # Test with tool search regex tool + tools = [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + } + ] + assert config.is_tool_search_used(tools) is True + + # Test without tool search + tools = [ + { + "type": "function", + "function": {"name": "get_weather"} + } + ] + assert config.is_tool_search_used(tools) is False + + +def test_tool_search_bm25_detection(): + """Test that tool search BM25 tools are properly detected""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + config = AnthropicModelInfo() + + # Test with tool search BM25 tool + tools = [ + { + "type": "tool_search_tool_bm25_20251119", + "name": "tool_search_tool_bm25" + } + ] + assert config.is_tool_search_used(tools) is True + + +def test_tool_search_beta_header(): + """Test that tool search beta header is automatically added""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + config = AnthropicModelInfo() + + headers = config.get_anthropic_headers( + api_key="test-key", + tool_search_used=True, + ) + + assert "anthropic-beta" in headers + assert "advanced-tool-use-2025-11-20" in headers["anthropic-beta"] + + +def test_tool_search_regex_mapping(): + """Test that tool search regex tools are properly mapped""" + config = AnthropicConfig() + + tool = { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + } + + mapped_tool, mcp_server = config._map_tool_helper(tool) + + assert mapped_tool is not None + assert mapped_tool["type"] == "tool_search_tool_regex_20251119" + assert mapped_tool["name"] == "tool_search_tool_regex" + assert mcp_server is None + + +def test_tool_search_bm25_mapping(): + """Test that tool search BM25 tools are properly mapped""" + config = AnthropicConfig() + + tool = { + "type": "tool_search_tool_bm25_20251119", + "name": "tool_search_tool_bm25" + } + + mapped_tool, mcp_server = config._map_tool_helper(tool) + + assert mapped_tool is not None + assert mapped_tool["type"] == "tool_search_tool_bm25_20251119" + assert mapped_tool["name"] == "tool_search_tool_bm25" + assert mcp_server is None + + +def test_deferred_tools_separation(): + """Test that deferred and non-deferred tools are properly separated""" + config = AnthropicConfig() + + tools = [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "type": "function", + "function": {"name": "get_weather"}, + "defer_loading": True + }, + { + "type": "function", + "function": {"name": "search_files"}, + "defer_loading": False + } + ] + + non_deferred, deferred = config._separate_deferred_tools(tools) + + assert len(non_deferred) == 2 # tool_search and search_files + assert len(deferred) == 1 # get_weather + + +def test_server_tool_use_in_response(): + """Test that server_tool_use blocks are parsed correctly""" + config = AnthropicConfig() + + completion_response = { + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_01ABC123", + "name": "tool_search_tool_regex", + "input": {"query": "weather"} + } + ] + } + + text, citations, thinking_blocks, reasoning_content, tool_calls = config.extract_response_content( + completion_response + ) + + assert len(tool_calls) == 1 + assert tool_calls[0]["id"] == "srvtoolu_01ABC123" + assert tool_calls[0]["function"]["name"] == "tool_search_tool_regex" + + +def test_tool_search_usage_tracking(): + """Test that tool_search_requests are tracked in usage""" + config = AnthropicConfig() + + usage_object = { + "input_tokens": 100, + "output_tokens": 50, + "server_tool_use": { + "tool_search_requests": 2 + } + } + + usage = config.calculate_usage(usage_object=usage_object, reasoning_content=None) + + assert usage.server_tool_use is not None + assert usage.server_tool_use.tool_search_requests == 2 + + +def test_tool_reference_expansion(): + """Test that tool_reference blocks are expanded correctly""" + config = AnthropicConfig() + + deferred_tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather" + } + } + ] + + content = [ + {"type": "text", "text": "I'll search for tools"}, + {"type": "tool_reference", "tool_name": "get_weather"} + ] + + expanded = config._expand_tool_references(content, deferred_tools) + + assert len(expanded) == 2 + assert expanded[0]["type"] == "text" + assert expanded[1]["type"] == "function" + assert expanded[1]["function"]["name"] == "get_weather" + + +def test_defer_loading_preserved_in_transformation(): + """Test that defer_loading parameter is preserved when transforming tools""" + config = AnthropicConfig() + + tool = { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + }, + "defer_loading": True + } + + mapped_tool, mcp_server = config._map_tool_helper(tool) + + assert mapped_tool is not None + assert mapped_tool.get("defer_loading") is True + assert mapped_tool["name"] == "get_weather" + assert mcp_server is None + + +def test_tool_search_complete_response_parsing(): + """Test parsing a complete tool search response with server_tool_use and tool_search_tool_result blocks""" + config = AnthropicConfig() + + # Simulating actual Anthropic API response with tool search + completion_response = { + "content": [ + { + "type": "text", + "text": "I'll search for weather-related tools that can help you." + }, + { + "type": "server_tool_use", + "id": "srvtoolu_015i6aVA2niwzv4RG4DtnxDJ", + "name": "tool_search_tool_regex", + "input": {"pattern": "weather", "limit": 5}, + "caller": {"type": "direct"} + }, + { + "type": "tool_search_tool_result", + "tool_use_id": "srvtoolu_015i6aVA2niwzv4RG4DtnxDJ", + "content": { + "type": "tool_search_tool_search_result", + "tool_references": [{"type": "tool_reference", "tool_name": "get_weather"}] + } + }, + { + "type": "text", + "text": "Great! I found a weather tool." + }, + { + "type": "tool_use", + "id": "toolu_01CrCNx4ntSaeeV9iArT4JfQ", + "name": "get_weather", + "input": {"location": "San Francisco"} + } + ], + "usage": { + "input_tokens": 1639, + "output_tokens": 170, + "server_tool_use": {"web_search_requests": 0} + } + } + + # Extract content + text, citations, thinking_blocks, reasoning_content, tool_calls = config.extract_response_content( + completion_response + ) + + # Verify text extraction (should concatenate both text blocks) + assert "I'll search for weather-related tools" in text + assert "Great! I found a weather tool" in text + + # Verify tool calls (should have both server_tool_use and tool_use) + assert len(tool_calls) == 2 + assert tool_calls[0]["function"]["name"] == "tool_search_tool_regex" + assert tool_calls[1]["function"]["name"] == "get_weather" + + # Verify usage calculation counts tool_search_requests from content + usage = config.calculate_usage( + usage_object=completion_response["usage"], + reasoning_content=None, + completion_response=completion_response + ) + + assert usage.server_tool_use is not None + assert usage.server_tool_use.web_search_requests == 0 + assert usage.server_tool_use.tool_search_requests == 1 # Counted from server_tool_use blocks + + +def test_allowed_callers_field_preservation(): + """Test that allowed_callers field is preserved during tool transformation.""" + config = AnthropicConfig() + + # Test with top-level allowed_callers + tool_with_allowed_callers = { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string"} + }, + "required": ["sql"] + } + }, + "allowed_callers": ["code_execution_20250825"] + } + + transformed_tool, _ = config._map_tool_helper(tool_with_allowed_callers) + assert transformed_tool is not None + assert "allowed_callers" in transformed_tool + assert transformed_tool["allowed_callers"] == ["code_execution_20250825"] + + +def test_programmatic_tool_calling_beta_header(): + """Test that beta header is automatically added when programmatic tool calling is detected.""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + model_info = AnthropicModelInfo() + + # Test detection with allowed_callers + tools = [ + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query", + "parameters": {"type": "object", "properties": {}} + }, + "allowed_callers": ["code_execution_20250825"] + } + ] + + is_programmatic = model_info.is_programmatic_tool_calling_used(tools) + assert is_programmatic is True + + # Test header generation + headers = model_info.get_anthropic_headers( + api_key="test-key", + programmatic_tool_calling_used=True + ) + + assert "anthropic-beta" in headers + assert "advanced-tool-use-2025-11-20" in headers["anthropic-beta"] + + +def test_caller_field_in_response(): + """Test that caller field is correctly parsed from tool_use blocks.""" + config = AnthropicConfig() + + # Mock response with programmatic tool call + completion_response = { + "id": "msg_test", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I'll query the database." + }, + { + "type": "tool_use", + "id": "toolu_123", + "name": "query_database", + "input": {"sql": "SELECT * FROM users"}, + "caller": { + "type": "code_execution_20250825", + "tool_id": "srvtoolu_abc" + } + } + ], + "stop_reason": "tool_use", + "usage": {"input_tokens": 100, "output_tokens": 50} + } + + text, citations, thinking, reasoning, tool_calls = config.extract_response_content(completion_response) + + assert len(tool_calls) == 1 + assert tool_calls[0]["id"] == "toolu_123" + assert tool_calls[0]["function"]["name"] == "query_database" + assert "caller" in tool_calls[0] + assert tool_calls[0]["caller"]["type"] == "code_execution_20250825" + assert tool_calls[0]["caller"]["tool_id"] == "srvtoolu_abc" + + +def test_code_execution_20250825_tool_type(): + """Test that code_execution_20250825 tool type is handled correctly.""" + config = AnthropicConfig() + + tool = { + "type": "code_execution_20250825", + "name": "code_execution" + } + + transformed_tool, _ = config._map_tool_helper(tool) + assert transformed_tool is not None + assert transformed_tool["type"] == "code_execution_20250825" + assert transformed_tool["name"] == "code_execution" + + +def test_allowed_callers_in_function_field(): + """Test that allowed_callers in function field is also preserved.""" + config = AnthropicConfig() + + # Test with function.allowed_callers + tool = { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string"} + }, + "required": ["sql"] + }, + "allowed_callers": ["code_execution_20250825"] + } + } + + transformed_tool, _ = config._map_tool_helper(tool) + assert transformed_tool is not None + assert "allowed_callers" in transformed_tool + assert transformed_tool["allowed_callers"] == ["code_execution_20250825"] + + +def test_input_examples_field_preservation(): + """Test that input_examples field is preserved during tool transformation.""" + config = AnthropicConfig() + + # Test with top-level input_examples + tool_with_examples = { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"}, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} + }, + "required": ["location"] + } + }, + "input_examples": [ + {"location": "San Francisco, CA", "unit": "fahrenheit"}, + {"location": "Tokyo, Japan", "unit": "celsius"} + ] + } + + transformed_tool, _ = config._map_tool_helper(tool_with_examples) + assert transformed_tool is not None + assert "input_examples" in transformed_tool + assert len(transformed_tool["input_examples"]) == 2 + assert transformed_tool["input_examples"][0]["location"] == "San Francisco, CA" + + +def test_input_examples_beta_header(): + """Test that beta header is automatically added when input_examples is detected.""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + model_info = AnthropicModelInfo() + + # Test detection with input_examples + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": {"type": "object", "properties": {}} + }, + "input_examples": [ + {"location": "San Francisco, CA"} + ] + } + ] + + is_examples_used = model_info.is_input_examples_used(tools) + assert is_examples_used is True + + # Test header generation + headers = model_info.get_anthropic_headers( + api_key="test-key", + input_examples_used=True + ) + + assert "anthropic-beta" in headers + assert "advanced-tool-use-2025-11-20" in headers["anthropic-beta"] + + +def test_input_examples_in_function_field(): + """Test that input_examples in function field is also preserved.""" + config = AnthropicConfig() + + # Test with function.input_examples + tool = { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + }, + "input_examples": [ + {"location": "Paris, France"}, + {"location": "London, UK"} + ] + } + } + + transformed_tool, _ = config._map_tool_helper(tool) + assert transformed_tool is not None + assert "input_examples" in transformed_tool + assert len(transformed_tool["input_examples"]) == 2 + + +def test_input_examples_with_other_features(): + """Test that input_examples works alongside other tool features.""" + config = AnthropicConfig() + + # Tool with input_examples, defer_loading, and allowed_callers + tool = { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string"} + }, + "required": ["sql"] + } + }, + "input_examples": [ + {"sql": "SELECT * FROM users WHERE id = 1"} + ], + "defer_loading": True, + "allowed_callers": ["code_execution_20250825"] + } + + transformed_tool, _ = config._map_tool_helper(tool) + assert transformed_tool is not None + assert "input_examples" in transformed_tool + assert "defer_loading" in transformed_tool + assert "allowed_callers" in transformed_tool + assert transformed_tool["defer_loading"] is True + assert transformed_tool["allowed_callers"] == ["code_execution_20250825"] + + +def test_input_examples_empty_list_not_added(): + """Test that empty input_examples list is not added to transformed tool.""" + config = AnthropicConfig() + + # Tool with empty input_examples + tool = { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + }, + "input_examples": [] + } + + transformed_tool, _ = config._map_tool_helper(tool) + assert transformed_tool is not None + # Empty list should not be added + assert "input_examples" not in transformed_tool or len(transformed_tool.get("input_examples", [])) == 0 + + +# ============ Effort Parameter Tests ============ + + +def test_effort_output_config_preservation(): + """Test that output_config with effort is preserved in transformation.""" + config = AnthropicConfig() + + messages = [{"role": "user", "content": "Analyze this code"}] + optional_params = { + "output_config": { + "effort": "medium" + } + } + + result = config.transform_request( + model="claude-opus-4-5-20251101", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + assert "output_config" in result + assert result["output_config"]["effort"] == "medium" + + +def test_effort_beta_header_injection(): + """Test that effort beta header is automatically added when output_config is detected.""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + model_info = AnthropicModelInfo() + + # Test with effort parameter + optional_params = { + "output_config": { + "effort": "low" + } + } + + effort_used = model_info.is_effort_used(optional_params=optional_params) + assert effort_used is True + + headers = model_info.get_anthropic_headers( + api_key="test-key", + effort_used=effort_used + ) + + assert "anthropic-beta" in headers + assert "effort-2025-11-24" in headers["anthropic-beta"] + + +def test_effort_validation(): + """Test that only valid effort values are accepted.""" + config = AnthropicConfig() + + messages = [{"role": "user", "content": "Test"}] + + # Valid values should work + for effort in ["high", "medium", "low"]: + optional_params = {"output_config": {"effort": effort}} + result = config.transform_request( + model="claude-opus-4-5-20251101", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + assert result["output_config"]["effort"] == effort + + # Invalid value should raise error + with pytest.raises(ValueError, match="Invalid effort value"): + optional_params = {"output_config": {"effort": "invalid"}} + config.transform_request( + model="claude-opus-4-5-20251101", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + +def test_effort_with_claude_opus_45(): + """Test effort parameter works with Claude Opus 4.5 model.""" + config = AnthropicConfig() + + messages = [{"role": "user", "content": "Complex analysis task"}] + optional_params = { + "output_config": { + "effort": "high" + } + } + + result = config.transform_request( + model="claude-opus-4-5-20251101", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + assert "output_config" in result + assert result["output_config"]["effort"] == "high" + assert result["model"] == "claude-opus-4-5-20251101" + + +def test_effort_with_other_features(): + """Test effort works alongside other features (thinking, tools).""" + config = AnthropicConfig() + + messages = [{"role": "user", "content": "Use tools efficiently"}] + tools = [ + { + "type": "function", + "function": { + "name": "get_data", + "description": "Get data", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"} + }, + "required": ["query"] + } + } + } + ] + optional_params = { + "output_config": { + "effort": "low" + }, + "tools": tools, + "thinking": { + "type": "enabled", + "budget_tokens": 1000 + } + } + + result = config.transform_request( + model="claude-opus-4-5-20251101", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + # Verify all features are present + assert "output_config" in result + assert result["output_config"]["effort"] == "low" + assert "tools" in result + assert len(result["tools"]) > 0 + assert "thinking" in result From db2c8e363175b6ca155fd159f166e2a7f17a0565 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Nov 2025 11:57:51 -0800 Subject: [PATCH 054/349] docs: initial doc cleanup --- .../index.md | 205 +++++++++++++++++- .../docs/providers/anthropic_tool_search.md | 2 +- 2 files changed, 194 insertions(+), 13 deletions(-) rename docs/my-website/blog/{anthropic_advanced_features => anthropic_opus_4_5_and_advanced_features}/index.md (79%) diff --git a/docs/my-website/blog/anthropic_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md similarity index 79% rename from docs/my-website/blog/anthropic_advanced_features/index.md rename to docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index 71e5d9b1925..0b0f4a54167 100644 --- a/docs/my-website/blog/anthropic_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -1,7 +1,7 @@ --- slug: anthropic_advanced_features -title: "Advanced Anthropic Features in LiteLLM: Tool Search, Programmatic Tool Calling, Input Examples, and Effort Control" -date: 2025-01-25T10:00:00 +title: "Day 0 Support: Claude 4.5 Opus (+Advanced Features)" +date: 2025-11-25T10:00:00 authors: - name: Sameer Kankute title: SWE @ LiteLLM (LLM Translation) @@ -22,24 +22,205 @@ hide_table_of_contents: false import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +This guide covers Anthropic's latest model (Claude Opus 4.5) and its advanced features now available in LiteLLM: Tool Search, Programmatic Tool Calling, Tool Input Examples, and the Effort Parameter. + +--- + +## Usage + + + + + +```python +import os +from litellm import completion + +# set env - [OPTIONAL] replace with your anthropic key +os.environ["ANTHROPIC_API_KEY"] = "your-api-key" + +messages = [{"role": "user", "content": "Hey! how's it going?"}] + +## OPENAI /chat/completions API format +response = completion(model="claude-opus-4-5-20251101", messages=messages) +print(response) + +``` + + + + +**1. Setup config.yaml** + +```yaml +model_list: + - model_name: claude-4 ### RECEIVED MODEL NAME ### + litellm_params: # all params accepted by litellm.completion() - https://docs.litellm.ai/docs/completion/input + model: claude-opus-4-5-20251101 ### MODEL NAME sent to `litellm.completion()` ### + api_key: "os.environ/ANTHROPIC_API_KEY" # does os.getenv("ANTHROPIC_API_KEY") +``` + +**2. Start the proxy** + +```bash +litellm --config /path/to/config.yaml +``` + +**3. Test it!** + + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + +```bash +curl --location 'http://0.0.0.0:4000/v1/messages' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "max_tokens": 1024, + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + + + + +## Usage - Bedrock + :::info -This guide covers Anthropic's latest advanced features now available in LiteLLM: Tool Search, Programmatic Tool Calling, Tool Input Examples, and the Effort Parameter. +LiteLLM uses the boto3 library to authenticate with Bedrock. + +For more ways to authenticate with Bedrock, see the [Bedrock documentation](../../docs/providers/bedrock#authentication). ::: -We're excited to announce support for Anthropic's latest advanced features in LiteLLM! These powerful capabilities enable you to build more efficient, scalable, and cost-effective AI applications with Claude. + + -## Table of Contents -1. [Tool Search](#tool-search) -2. [Programmatic Tool Calling](#programmatic-tool-calling) -3. [Tool Input Examples](#tool-input-examples) -4. [Effort Parameter: Control Token Usage](#effort-parameter) -5. [Cost Tracking: Monitor Tool Search Usage](#cost-tracking) -6. [Combining Features](#combining-features) +```python +import os +from litellm import completion + +os.environ["AWS_ACCESS_KEY_ID"] = "" +os.environ["AWS_SECRET_ACCESS_KEY"] = "" +os.environ["AWS_REGION_NAME"] = "" + +## OPENAI /chat/completions API format +response = completion( + model="bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0", + messages=[{ "content": "Hello, how are you?","role": "user"}] +) +``` + + + + +**1. Setup config.yaml** + +```yaml +model_list: + - model_name: claude-4 ### RECEIVED MODEL NAME ### + litellm_params: # all params accepted by litellm.completion() - https://docs.litellm.ai/docs/completion/input + model: bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0 ### MODEL NAME sent to `litellm.completion()` ### + aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID + aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY + aws_region_name: os.environ/AWS_REGION_NAME +``` + +**2. Start the proxy** + +```bash +litellm --config /path/to/config.yaml +``` + +**3. Test it!** + + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + +```bash +curl --location 'http://0.0.0.0:4000/v1/messages' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "max_tokens": 1024, + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + +```bash +curl --location 'http://0.0.0.0:4000/bedrock/model/claude-4/invoke' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Hello, how are you?"}] + }' +``` + + +```bash +curl --location 'http://0.0.0.0:4000/bedrock/model/claude-4/converse' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "messages": [{"role": "user", "content": "Hello, how are you?"}] + }' +``` + + + + ---- ## Tool Search {#tool-search} diff --git a/docs/my-website/docs/providers/anthropic_tool_search.md b/docs/my-website/docs/providers/anthropic_tool_search.md index 3d61022b26f..7b9e7cfaa72 100644 --- a/docs/my-website/docs/providers/anthropic_tool_search.md +++ b/docs/my-website/docs/providers/anthropic_tool_search.md @@ -380,7 +380,7 @@ If Claude references a tool that isn't in your deferred tools list, you'll get a **When traditional tool calling is better:** - Less than 10 tools total - All tools are frequently used -- Very small tool definitions (<100 tokens total) +- Very small tool definitions (\<100 tokens total) ## Limitations From 44cde2e48fe6d2365f8cf3c972d1be8de7bbceec Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 12:03:01 -0800 Subject: [PATCH 055/349] Disable edit, delete, info, for dynamically generated spend tags --- .../tag_management/TagTable.test.tsx | 101 ++++++++++++++++++ .../components/tag_management/TagTable.tsx | 86 +++++++++++---- .../components/CreateTagModal.test.tsx | 64 +++++++++++ .../components/CreateTagModal.tsx | 44 ++------ .../components/tag_management/tag_info.tsx | 61 ++++++----- 5 files changed, 279 insertions(+), 77 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/tag_management/TagTable.test.tsx create mode 100644 ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.test.tsx diff --git a/ui/litellm-dashboard/src/components/tag_management/TagTable.test.tsx b/ui/litellm-dashboard/src/components/tag_management/TagTable.test.tsx new file mode 100644 index 00000000000..a56721787d5 --- /dev/null +++ b/ui/litellm-dashboard/src/components/tag_management/TagTable.test.tsx @@ -0,0 +1,101 @@ +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import TagTable from "./TagTable"; +import { Tag } from "./types"; + +describe("TagTable", () => { + const mockOnEdit = vi.fn(); + const mockOnDelete = vi.fn(); + const mockOnSelectTag = vi.fn(); + + const mockTag: Tag = { + name: "test-tag", + description: "Test description", + models: ["model-1", "model-2"], + model_info: { + "model-1": "GPT-4", + "model-2": "Claude-3", + }, + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + }; + + const mockDynamicSpendTag: Tag = { + name: "dynamic-spend-tag", + description: + "This is just a spend tag that was passed dynamically in a request. It does not control any LLM models.", + models: [], + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + }; + + const defaultProps = { + data: [], + onEdit: mockOnEdit, + onDelete: mockOnDelete, + onSelectTag: mockOnSelectTag, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render", () => { + render(); + expect(screen.getByText("Tag Name")).toBeInTheDocument(); + expect(screen.getByText("Description")).toBeInTheDocument(); + expect(screen.getByText("Allowed Models")).toBeInTheDocument(); + expect(screen.getByText("Created")).toBeInTheDocument(); + expect(screen.getByText("Actions")).toBeInTheDocument(); + }); + + it("should display no tags found message when data is empty", () => { + render(); + expect(screen.getByText("No tags found")).toBeInTheDocument(); + }); + + it("should display tag name", () => { + render(); + expect(screen.getByText("test-tag")).toBeInTheDocument(); + }); + + it("should display tag description", () => { + render(); + expect(screen.getByText("Test description")).toBeInTheDocument(); + }); + + it("should display All Models badge when models array is empty", () => { + const tagWithNoModels: Tag = { + ...mockTag, + models: [], + }; + render(); + expect(screen.getByText("All Models")).toBeInTheDocument(); + }); + + it("should display formatted created date", () => { + render(); + const formattedDate = new Date(mockTag.created_at).toLocaleDateString(); + expect(screen.getByText(formattedDate)).toBeInTheDocument(); + }); + + it("should disable tag name button for dynamic spend tags", () => { + render(); + const tagButton = screen.getByRole("button", { name: "dynamic-spend-tag" }); + expect(tagButton).toBeDisabled(); + }); + + it("should disable edit icon for dynamic spend tags", () => { + render(); + const editIcon = screen.getByLabelText("Edit tag (disabled)"); + expect(editIcon).toBeInTheDocument(); + expect(editIcon).toHaveClass("cursor-not-allowed"); + }); + + it("should disable delete icon for dynamic spend tags", () => { + render(); + const deleteIcon = screen.getByLabelText("Delete tag (disabled)"); + expect(deleteIcon).toBeInTheDocument(); + expect(deleteIcon).toHaveClass("cursor-not-allowed"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/tag_management/TagTable.tsx b/ui/litellm-dashboard/src/components/tag_management/TagTable.tsx index aa43388893e..ce28ac6e6f2 100644 --- a/ui/litellm-dashboard/src/components/tag_management/TagTable.tsx +++ b/ui/litellm-dashboard/src/components/tag_management/TagTable.tsx @@ -1,18 +1,4 @@ -import React from "react"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeaderCell, - TableRow, - Icon, - Button, - Badge, - Text, -} from "@tremor/react"; -import { PencilAltIcon, TrashIcon, SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline"; -import { Tooltip } from "antd"; +import { ChevronDownIcon, ChevronUpIcon, PencilAltIcon, SwitchVerticalIcon, TrashIcon } from "@heroicons/react/outline"; import { ColumnDef, flexRender, @@ -21,6 +7,20 @@ import { SortingState, useReactTable, } from "@tanstack/react-table"; +import { + Badge, + Button, + Icon, + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + Text, +} from "@tremor/react"; +import { Tooltip } from "antd"; +import React from "react"; import { Tag } from "./types"; interface TagTableProps { @@ -30,6 +30,9 @@ interface TagTableProps { onSelectTag: (tagName: string) => void; } +const DYNAMIC_SPEND_TAG_DESCRIPTION = + "This is just a spend tag that was passed dynamically in a request. It does not control any LLM models."; + const TagTable: React.FC = ({ data, onEdit, onDelete, onSelectTag }) => { const [sorting, setSorting] = React.useState([{ id: "created_at", desc: true }]); @@ -39,14 +42,20 @@ const TagTable: React.FC = ({ data, onEdit, onDelete, onSelectTag accessorKey: "name", cell: ({ row }) => { const tag = row.original; + const isDynamicSpendTag = tag.description === DYNAMIC_SPEND_TAG_DESCRIPTION; return (
- + @@ -68,7 +77,7 @@ const TagTable: React.FC = ({ data, onEdit, onDelete, onSelectTag }, }, { - header: "Allowed LLMs", + header: "Allowed Models", accessorKey: "models", cell: ({ row }) => { const tag = row.original; @@ -102,13 +111,50 @@ const TagTable: React.FC = ({ data, onEdit, onDelete, onSelectTag }, { id: "actions", - header: "", + header: "Actions", cell: ({ row }) => { const tag = row.original; + const isDynamicSpendTag = tag.description === DYNAMIC_SPEND_TAG_DESCRIPTION; return (
- onEdit(tag)} className="cursor-pointer" /> - onDelete(tag.name)} className="cursor-pointer" /> + {isDynamicSpendTag ? ( + + + + ) : ( + + onEdit(tag)} + className="cursor-pointer hover:text-blue-500" + /> + + )} + {isDynamicSpendTag ? ( + + + + ) : ( + + onDelete(tag.name)} + className="cursor-pointer hover:text-red-500" + /> + + )}
); }, diff --git a/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.test.tsx b/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.test.tsx new file mode 100644 index 00000000000..997faf4a004 --- /dev/null +++ b/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.test.tsx @@ -0,0 +1,64 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import CreateTagModal from "./CreateTagModal"; + +describe("CreateTagModal", () => { + const mockOnCancel = vi.fn(); + const mockOnSubmit = vi.fn(); + const mockAvailableModels = [ + { + model_name: "GPT-4", + litellm_params: { model: "gpt-4" }, + model_info: { id: "model-1" }, + }, + { + model_name: "Claude-3", + litellm_params: { model: "claude-3" }, + model_info: { id: "model-2" }, + }, + ]; + + const defaultProps = { + visible: true, + onCancel: mockOnCancel, + onSubmit: mockOnSubmit, + availableModels: mockAvailableModels, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render the modal", () => { + render(); + expect(screen.getByRole("dialog")).toBeInTheDocument(); + expect(screen.getByText("Create New Tag")).toBeInTheDocument(); + }); + + it("should submit form with required tag name", async () => { + const user = userEvent.setup(); + render(); + + const tagNameInput = screen.getByLabelText("Tag Name"); + await user.type(tagNameInput, "test-tag"); + + const submitButton = screen.getByRole("button", { name: /Create Tag/i }); + await user.click(submitButton); + + expect(mockOnSubmit).toHaveBeenCalledWith({ + tag_name: "test-tag", + }); + }); + + it("should not submit form when tag name is missing", async () => { + const user = userEvent.setup(); + render(); + + const submitButton = screen.getByRole("button", { name: /Create Tag/i }); + await user.click(submitButton); + + // Form validation should prevent submission + expect(mockOnSubmit).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.tsx b/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.tsx index 4d1909abd93..3412d684524 100644 --- a/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.tsx +++ b/ui/litellm-dashboard/src/components/tag_management/components/CreateTagModal.tsx @@ -1,9 +1,9 @@ -import React from "react"; -import { Button, TextInput, Accordion, AccordionHeader, AccordionBody, Title } from "@tremor/react"; -import { Modal, Form, Select as Select2, Tooltip, Input } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; -import NumericalInput from "../../shared/numerical_input"; +import { Accordion, AccordionBody, AccordionHeader, Button, TextInput, Title } from "@tremor/react"; +import { Form, Input, Modal, Select as Select2, Tooltip } from "antd"; +import React from "react"; import BudgetDurationDropdown from "../../common_components/budget_duration_dropdown"; +import NumericalInput from "../../shared/numerical_input"; interface ModelInfo { model_name: string; @@ -22,12 +22,7 @@ interface CreateTagModalProps { availableModels: ModelInfo[]; } -const CreateTagModal: React.FC = ({ - visible, - onCancel, - onSubmit, - availableModels, -}) => { +const CreateTagModal: React.FC = ({ visible, onCancel, onSubmit, availableModels }) => { const [form] = Form.useForm(); const handleFinish = (values: any) => { @@ -41,25 +36,9 @@ const CreateTagModal: React.FC = ({ }; return ( - -
- + + + @@ -70,15 +49,15 @@ const CreateTagModal: React.FC = ({ - Allowed Models{" "} - + Allowed Models + } name="allowed_llms" > - + {availableModels.map((model) => (
@@ -150,4 +129,3 @@ const CreateTagModal: React.FC = ({ }; export default CreateTagModal; - diff --git a/ui/litellm-dashboard/src/components/tag_management/tag_info.tsx b/ui/litellm-dashboard/src/components/tag_management/tag_info.tsx index 60cde134e60..1c66a107db7 100644 --- a/ui/litellm-dashboard/src/components/tag_management/tag_info.tsx +++ b/ui/litellm-dashboard/src/components/tag_management/tag_info.tsx @@ -1,5 +1,15 @@ import React, { useState, useEffect } from "react"; -import { Card, Text, Title, Button, Badge, Accordion, AccordionHeader, AccordionBody, Title as TremorTitle } from "@tremor/react"; +import { + Card, + Text, + Title, + Button, + Badge, + Accordion, + AccordionHeader, + AccordionBody, + Title as TremorTitle, +} from "@tremor/react"; import { Form, Input, Select as Select2, Tooltip } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { fetchUserModels } from "../organisms/create_key_button"; @@ -131,7 +141,7 @@ const TagInfoView: React.FC = ({ tagId, onClose, accessToken, - + @@ -141,15 +151,15 @@ const TagInfoView: React.FC = ({ tagId, onClose, accessToken, - Allowed LLMs{" "} - + Allowed Models + } name="models" > - + {userModels.map((modelId) => ( {getModelDisplayName(modelId)} @@ -228,7 +238,7 @@ const TagInfoView: React.FC = ({ tagId, onClose, accessToken, {tagDetails.description || "-"}
- Allowed LLMs + Allowed Models
{!tagDetails.models || tagDetails.models.length === 0 ? ( All Models @@ -256,30 +266,33 @@ const TagInfoView: React.FC = ({ tagId, onClose, accessToken, Budget & Rate Limits
- {tagDetails.litellm_budget_table.max_budget !== undefined && tagDetails.litellm_budget_table.max_budget !== null && ( -
- Max Budget - ${tagDetails.litellm_budget_table.max_budget} -
- )} + {tagDetails.litellm_budget_table.max_budget !== undefined && + tagDetails.litellm_budget_table.max_budget !== null && ( +
+ Max Budget + ${tagDetails.litellm_budget_table.max_budget} +
+ )} {tagDetails.litellm_budget_table.budget_duration && (
Budget Duration {tagDetails.litellm_budget_table.budget_duration}
)} - {tagDetails.litellm_budget_table.tpm_limit !== undefined && tagDetails.litellm_budget_table.tpm_limit !== null && ( -
- TPM Limit - {tagDetails.litellm_budget_table.tpm_limit.toLocaleString()} -
- )} - {tagDetails.litellm_budget_table.rpm_limit !== undefined && tagDetails.litellm_budget_table.rpm_limit !== null && ( -
- RPM Limit - {tagDetails.litellm_budget_table.rpm_limit.toLocaleString()} -
- )} + {tagDetails.litellm_budget_table.tpm_limit !== undefined && + tagDetails.litellm_budget_table.tpm_limit !== null && ( +
+ TPM Limit + {tagDetails.litellm_budget_table.tpm_limit.toLocaleString()} +
+ )} + {tagDetails.litellm_budget_table.rpm_limit !== undefined && + tagDetails.litellm_budget_table.rpm_limit !== null && ( +
+ RPM Limit + {tagDetails.litellm_budget_table.rpm_limit.toLocaleString()} +
+ )}
)} From 36f8c9463f3a447b35bcbd4e8588b9c883ef73fe Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 12:13:19 -0800 Subject: [PATCH 056/349] fix: resolve type checking errors for lazy-loaded functions in budget_manager - Add type stubs and @overload decorators for cost_per_token and completion_cost - Refactor lazy loading system with centralized registry for better maintainability - Add comprehensive documentation for adding new lazy-loaded functions - Fixes 'Any? not callable' errors at lines 111, 139, and 146 in budget_manager.py --- litellm/__init__.py | 82 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 8e8815b1257..a513619c4dd 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -20,6 +20,8 @@ from typing import ( Literal, get_args, TYPE_CHECKING, + Tuple, + overload, ) from litellm.types.integrations.datadog_llm_obs import DatadogLLMObsInitParams from litellm.types.integrations.datadog import DatadogInitParams @@ -1503,8 +1505,20 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None: global_gitlab_config = config -# Lazy import for cost_calculator functions to avoid loading the module at import time -# This significantly reduces memory usage when importing litellm +# ============================================================================ +# LAZY LOADING SYSTEM +# ============================================================================ +# This system allows heavy modules to be loaded only when accessed, +# significantly reducing initial import time and memory usage. +# +# To add a new lazy-loaded function/class: +# 1. Add the import handler function (e.g., _lazy_import_xxx) +# 2. Add entries to _LAZY_LOAD_REGISTRY below +# 3. Add type stubs in the TYPE_CHECKING block +# 4. Add @overload decorators for type checking +# ============================================================================ + + def _lazy_import_cost_calculator(name: str) -> Any: """Lazy import for cost_calculator functions.""" from .cost_calculator import ( @@ -1513,22 +1527,17 @@ def _lazy_import_cost_calculator(name: str) -> Any: response_cost_calculator as _response_cost_calculator, ) - # Map names to imported functions _cost_functions = { "completion_cost": _completion_cost, "cost_per_token": _cost_per_token, "response_cost_calculator": _response_cost_calculator, } - # Cache the imported function in the module namespace func = _cost_functions[name] - globals()[name] = func - + globals()[name] = func # Cache for future access return func -# Lazy import for litellm_logging to avoid loading the module at import time -# This significantly reduces memory usage when importing litellm def _lazy_import_litellm_logging(name: str) -> Any: """Lazy import for litellm_logging module.""" try: @@ -1537,35 +1546,70 @@ def _lazy_import_litellm_logging(name: str) -> Any: modify_integration as _modify_integration, ) - # Map names to imported objects _logging_objects = { "Logging": _Logging, "modify_integration": _modify_integration, } - # Cache the imported object in the module namespace obj = _logging_objects[name] - globals()[name] = obj - + globals()[name] = obj # Cache for future access return obj except Exception as e: - # If lazy import fails, raise a more informative error raise AttributeError( f"module {__name__!r} has no attribute {name!r}. " f"Lazy import failed: {e}" ) from e +# Registry mapping lazy-loaded names to their import handlers +# Add new lazy-loaded items here for easy maintenance +_LAZY_LOAD_REGISTRY: Dict[str, Callable[[str], Any]] = { + # Cost calculator functions + "completion_cost": _lazy_import_cost_calculator, + "cost_per_token": _lazy_import_cost_calculator, + "response_cost_calculator": _lazy_import_cost_calculator, + # Logging objects + "Logging": _lazy_import_litellm_logging, + "modify_integration": _lazy_import_litellm_logging, +} + + +# Type stubs for lazy-loaded functions/classes to help type checkers +# Add type annotations here for new lazy-loaded items +if TYPE_CHECKING: + # Cost calculator functions + cost_per_token: Callable[..., Tuple[float, float]] + completion_cost: Callable[..., float] + response_cost_calculator: Any + # Logging objects + Logging: Any + modify_integration: Any + + +# Type overloads for __getattr__ to provide proper type hints +# Add @overload decorators here for new lazy-loaded items with specific types +@overload +def __getattr__(name: Literal["cost_per_token"]) -> Callable[..., Tuple[float, float]]: + ... + + +@overload +def __getattr__(name: Literal["completion_cost"]) -> Callable[..., float]: + ... + + +@overload +def __getattr__(name: Literal["response_cost_calculator", "Logging", "modify_integration"]) -> Any: + ... + + def __getattr__(name: str) -> Any: - """Lazy import for cost_calculator and litellm_logging functions. + """Lazy import handler for cost_calculator and litellm_logging functions. This allows these heavy modules to be loaded only when accessed, reducing initial import time and memory usage. """ - if name in ("completion_cost", "response_cost_calculator", "cost_per_token"): - return _lazy_import_cost_calculator(name) - - if name in ("Logging", "modify_integration"): - return _lazy_import_litellm_logging(name) + if name in _LAZY_LOAD_REGISTRY: + return _LAZY_LOAD_REGISTRY[name](name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") From be712908a3ea1f625826b261e5d55ecd51890ee8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Nov 2025 12:20:39 -0800 Subject: [PATCH 057/349] [Feat] Add OpenAI compatible bedrock imported models. - qwen etc (#17097) * test_bedrock_openai_imported_model * AmazonBedrockOpenAIConfig * add openai route for bedrock * docs fix * fix code qa check --- docs/my-website/docs/providers/bedrock.md | 202 +--------- .../docs/providers/bedrock_imported.md | 369 ++++++++++++++++++ docs/my-website/sidebars.js | 1 + litellm/__init__.py | 3 + .../amazon_openai_transformation.py | 186 +++++++++ litellm/llms/bedrock/common_utils.py | 18 +- .../prompt_security/prompt_security.py | 19 +- litellm/proxy/proxy_config.yaml | 19 +- litellm/utils.py | 12 +- .../test_bedrock_completion.py | 97 +++++ 10 files changed, 699 insertions(+), 227 deletions(-) create mode 100644 docs/my-website/docs/providers/bedrock_imported.md create mode 100644 litellm/llms/bedrock/chat/invoke_transformations/amazon_openai_transformation.py diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index f0b89615a0d..9e22f67527e 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -7,7 +7,7 @@ ALL Bedrock models (Anthropic, Meta, Deepseek, Mistral, Amazon, etc.) are Suppor | Property | Details | |-------|-------| | Description | Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs). | -| Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models) | +| Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models), [`bedrock/openai/`](./bedrock_imported.md#openai-compatible-imported-models-qwen-25-vl-etc) | | Provider Doc | [Amazon Bedrock ↗](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) | | Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations` | | Rerank Endpoint | `/rerank` | @@ -1598,206 +1598,6 @@ curl -X POST 'http://0.0.0.0:4000/chat/completions' \ -## Bedrock Imported Models (Deepseek, Deepseek R1) - -### Deepseek R1 - -This is a separate route, as the chat template is different. - -| Property | Details | -|----------|---------| -| Provider Route | `bedrock/deepseek_r1/{model_arn}` | -| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Deepseek Bedrock Imported Model](https://aws.amazon.com/blogs/machine-learning/deploy-deepseek-r1-distilled-llama-models-with-amazon-bedrock-custom-model-import/) | - - - - -```python -from litellm import completion -import os - -response = completion( - model="bedrock/deepseek_r1/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n", # bedrock/deepseek_r1/{your-model-arn} - messages=[{"role": "user", "content": "Tell me a joke"}], -) -``` - - - - - - -**1. Add to config** - -```yaml -model_list: - - model_name: DeepSeek-R1-Distill-Llama-70B - litellm_params: - model: bedrock/deepseek_r1/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n - -``` - -**2. Start proxy** - -```bash -litellm --config /path/to/config.yaml - -# RUNNING at http://0.0.0.0:4000 -``` - -**3. Test it!** - -```bash -curl --location 'http://0.0.0.0:4000/chat/completions' \ - --header 'Authorization: Bearer sk-1234' \ - --header 'Content-Type: application/json' \ - --data '{ - "model": "DeepSeek-R1-Distill-Llama-70B", # 👈 the 'model_name' in config - "messages": [ - { - "role": "user", - "content": "what llm are you" - } - ], - }' -``` - - - - - -### Deepseek (not R1) - -| Property | Details | -|----------|---------| -| Provider Route | `bedrock/llama/{model_arn}` | -| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Deepseek Bedrock Imported Model](https://aws.amazon.com/blogs/machine-learning/deploy-deepseek-r1-distilled-llama-models-with-amazon-bedrock-custom-model-import/) | - - - -Use this route to call Bedrock Imported Models that follow the `llama` Invoke Request / Response spec - - - - - -```python -from litellm import completion -import os - -response = completion( - model="bedrock/llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n", # bedrock/llama/{your-model-arn} - messages=[{"role": "user", "content": "Tell me a joke"}], -) -``` - - - - - - -**1. Add to config** - -```yaml -model_list: - - model_name: DeepSeek-R1-Distill-Llama-70B - litellm_params: - model: bedrock/llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n - -``` - -**2. Start proxy** - -```bash -litellm --config /path/to/config.yaml - -# RUNNING at http://0.0.0.0:4000 -``` - -**3. Test it!** - -```bash -curl --location 'http://0.0.0.0:4000/chat/completions' \ - --header 'Authorization: Bearer sk-1234' \ - --header 'Content-Type: application/json' \ - --data '{ - "model": "DeepSeek-R1-Distill-Llama-70B", # 👈 the 'model_name' in config - "messages": [ - { - "role": "user", - "content": "what llm are you" - } - ], - }' -``` - - - - -### Qwen3 Imported Models - -| Property | Details | -|----------|---------| -| Provider Route | `bedrock/qwen3/{model_arn}` | -| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Qwen3 Models](https://aws.amazon.com/about-aws/whats-new/2025/09/qwen3-models-fully-managed-amazon-bedrock/) | - - - - -```python -from litellm import completion -import os - -response = completion( - model="bedrock/qwen3/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen3-model", # bedrock/qwen3/{your-model-arn} - messages=[{"role": "user", "content": "Tell me a joke"}], - max_tokens=100, - temperature=0.7 -) -``` - - - - - -**1. Add to config** - -```yaml -model_list: - - model_name: Qwen3-32B - litellm_params: - model: bedrock/qwen3/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen3-model - -``` - -**2. Start proxy** - -```bash -litellm --config /path/to/config.yaml - -# RUNNING at http://0.0.0.0:4000 -``` - -**3. Test it!** - -```bash -curl --location 'http://0.0.0.0:4000/chat/completions' \ - --header 'Authorization: Bearer sk-1234' \ - --header 'Content-Type: application/json' \ - --data '{ - "model": "Qwen3-32B", # 👈 the 'model_name' in config - "messages": [ - { - "role": "user", - "content": "what llm are you" - } - ], - }' -``` - - - - ### OpenAI GPT OSS | Property | Details | diff --git a/docs/my-website/docs/providers/bedrock_imported.md b/docs/my-website/docs/providers/bedrock_imported.md new file mode 100644 index 00000000000..8b0dd721c3c --- /dev/null +++ b/docs/my-website/docs/providers/bedrock_imported.md @@ -0,0 +1,369 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Bedrock Imported Models + +Bedrock Imported Models (Deepseek, Deepseek R1, Qwen, OpenAI-compatible models) + +### Deepseek R1 + +This is a separate route, as the chat template is different. + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/deepseek_r1/{model_arn}` | +| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Deepseek Bedrock Imported Model](https://aws.amazon.com/blogs/machine-learning/deploy-deepseek-r1-distilled-llama-models-with-amazon-bedrock-custom-model-import/) | + + + + +```python +from litellm import completion +import os + +response = completion( + model="bedrock/deepseek_r1/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n", # bedrock/deepseek_r1/{your-model-arn} + messages=[{"role": "user", "content": "Tell me a joke"}], +) +``` + + + + + + +**1. Add to config** + +```yaml +model_list: + - model_name: DeepSeek-R1-Distill-Llama-70B + litellm_params: + model: bedrock/deepseek_r1/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n + +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "DeepSeek-R1-Distill-Llama-70B", # 👈 the 'model_name' in config + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ], + }' +``` + + + + + +### Deepseek (not R1) + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/llama/{model_arn}` | +| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Deepseek Bedrock Imported Model](https://aws.amazon.com/blogs/machine-learning/deploy-deepseek-r1-distilled-llama-models-with-amazon-bedrock-custom-model-import/) | + + + +Use this route to call Bedrock Imported Models that follow the `llama` Invoke Request / Response spec + + + + + +```python +from litellm import completion +import os + +response = completion( + model="bedrock/llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n", # bedrock/llama/{your-model-arn} + messages=[{"role": "user", "content": "Tell me a joke"}], +) +``` + + + + + + +**1. Add to config** + +```yaml +model_list: + - model_name: DeepSeek-R1-Distill-Llama-70B + litellm_params: + model: bedrock/llama/arn:aws:bedrock:us-east-1:086734376398:imported-model/r4c4kewx2s0n + +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "DeepSeek-R1-Distill-Llama-70B", # 👈 the 'model_name' in config + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ], + }' +``` + + + + +### Qwen3 Imported Models + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/qwen3/{model_arn}` | +| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html), [Qwen3 Models](https://aws.amazon.com/about-aws/whats-new/2025/09/qwen3-models-fully-managed-amazon-bedrock/) | + + + + +```python +from litellm import completion +import os + +response = completion( + model="bedrock/qwen3/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen3-model", # bedrock/qwen3/{your-model-arn} + messages=[{"role": "user", "content": "Tell me a joke"}], + max_tokens=100, + temperature=0.7 +) +``` + + + + + +**1. Add to config** + +```yaml +model_list: + - model_name: Qwen3-32B + litellm_params: + model: bedrock/qwen3/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen3-model + +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "Qwen3-32B", # 👈 the 'model_name' in config + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ], + }' +``` + + + + +### OpenAI-Compatible Imported Models (Qwen 2.5 VL, etc.) + +Use this route for Bedrock imported models that follow the **OpenAI Chat Completions API spec**. This includes models like Qwen 2.5 VL that accept OpenAI-formatted messages with support for vision (images), tool calling, and other OpenAI features. + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/openai/{model_arn}` | +| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html) | +| Supported Features | Vision (images), tool calling, streaming, system messages | + +#### LiteLLMSDK Usage + +**Basic Usage** + +```python +from litellm import completion + +response = completion( + model="bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z", # bedrock/openai/{your-model-arn} + messages=[{"role": "user", "content": "Tell me a joke"}], + max_tokens=300, + temperature=0.5 +) +``` + +**With Vision (Images)** + +```python +import base64 +from litellm import completion + +# Load and encode image +with open("image.jpg", "rb") as f: + image_base64 = base64.b64encode(f.read()).decode("utf-8") + +response = completion( + model="bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z", + messages=[ + { + "role": "system", + "content": "You are a helpful assistant that can analyze images." + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "What's in this image?"}, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"} + } + ] + } + ], + max_tokens=300, + temperature=0.5 +) +``` + +**Comparing Multiple Images** + +```python +import base64 +from litellm import completion + +# Load images +with open("image1.jpg", "rb") as f: + image1_base64 = base64.b64encode(f.read()).decode("utf-8") +with open("image2.jpg", "rb") as f: + image2_base64 = base64.b64encode(f.read()).decode("utf-8") + +response = completion( + model="bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z", + messages=[ + { + "role": "system", + "content": "You are a helpful assistant that can analyze images." + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "Spot the difference between these two images?"}, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image1_base64}"} + }, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image2_base64}"} + } + ] + } + ], + max_tokens=300, + temperature=0.5 +) +``` + +#### LiteLLM Proxy Usage (AI Gateway) + +**1. Add to config** + +```yaml +model_list: + - model_name: qwen-25vl-72b + litellm_params: + model: bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +Basic text request: + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "qwen-25vl-72b", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ], + "max_tokens": 300 + }' +``` + +With vision (image): + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "qwen-25vl-72b", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant that can analyze images." + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "What is in this image?"}, + { + "type": "image_url", + "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZ..."} + } + ] + } + ], + "max_tokens": 300, + "temperature": 0.5 + }' +``` \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 6fa5fdeced0..104c7541d67 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -530,6 +530,7 @@ const sidebars = { items: [ "providers/bedrock", "providers/bedrock_embedding", + "providers/bedrock_imported", "providers/bedrock_image_gen", "providers/bedrock_rerank", "providers/bedrock_agentcore", diff --git a/litellm/__init__.py b/litellm/__init__.py index 768ba39a47a..0048f4c29cf 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1225,6 +1225,9 @@ from .llms.bedrock.chat.invoke_transformations.amazon_titan_transformation impor from .llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( AmazonInvokeConfig, ) +from .llms.bedrock.chat.invoke_transformations.amazon_openai_transformation import ( + AmazonBedrockOpenAIConfig, +) from .llms.bedrock.image.amazon_stability1_transformation import AmazonStabilityConfig from .llms.bedrock.image.amazon_stability3_transformation import AmazonStability3Config diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_openai_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_openai_transformation.py new file mode 100644 index 00000000000..ee07b71ef15 --- /dev/null +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_openai_transformation.py @@ -0,0 +1,186 @@ +""" +Transformation for Bedrock imported models that use OpenAI Chat Completions format. + +Use this for models imported into Bedrock that accept the OpenAI API format. +Model format: bedrock/openai/ + +Example: bedrock/openai/arn:aws:bedrock:us-east-1:123456789012:imported-model/abc123 +""" + +from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union + +import httpx + +from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM +from litellm.llms.bedrock.common_utils import BedrockError +from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.types.llms.openai import AllMessageValues + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class AmazonBedrockOpenAIConfig(OpenAIGPTConfig, BaseAWSLLM): + """ + Configuration for Bedrock imported models that use OpenAI Chat Completions format. + + This class handles the transformation of requests and responses for Bedrock + imported models that accept the OpenAI API format directly. + + Inherits from OpenAIGPTConfig to leverage standard OpenAI parameter handling + and response transformation, while adding Bedrock-specific URL generation + and AWS request signing. + + Usage: + model = "bedrock/openai/arn:aws:bedrock:us-east-1:123456789012:imported-model/abc123" + """ + + def __init__(self, **kwargs): + OpenAIGPTConfig.__init__(self, **kwargs) + BaseAWSLLM.__init__(self, **kwargs) + + @property + def custom_llm_provider(self) -> Optional[str]: + return "bedrock" + + def _get_openai_model_id(self, model: str) -> str: + """ + Extract the actual model ID from the LiteLLM model name. + + Input format: bedrock/openai/ + Returns: + """ + # Remove bedrock/ prefix if present + if model.startswith("bedrock/"): + model = model[8:] + + # Remove openai/ prefix + if model.startswith("openai/"): + model = model[7:] + + return model + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for the Bedrock invoke endpoint. + + Uses the standard Bedrock invoke endpoint format. + """ + model_id = self._get_openai_model_id(model) + + # Get AWS region + aws_region_name = self._get_aws_region_name( + optional_params=optional_params, model=model + ) + + # Get runtime endpoint + aws_bedrock_runtime_endpoint = optional_params.get( + "aws_bedrock_runtime_endpoint", None + ) + endpoint_url, proxy_endpoint_url = self.get_runtime_endpoint( + api_base=api_base, + aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, + aws_region_name=aws_region_name, + ) + + # Build the invoke URL + if stream: + endpoint_url = f"{endpoint_url}/model/{model_id}/invoke-with-response-stream" + else: + endpoint_url = f"{endpoint_url}/model/{model_id}/invoke" + + return endpoint_url + + def sign_request( + self, + headers: dict, + optional_params: dict, + request_data: dict, + api_base: str, + api_key: Optional[str] = None, + model: Optional[str] = None, + stream: Optional[bool] = None, + fake_stream: Optional[bool] = None, + ) -> Tuple[dict, Optional[bytes]]: + """ + Sign the request using AWS Signature Version 4. + """ + return self._sign_request( + service_name="bedrock", + headers=headers, + optional_params=optional_params, + request_data=request_data, + api_base=api_base, + api_key=api_key, + model=model, + stream=stream, + fake_stream=fake_stream, + ) + + def transform_request( + self, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + """ + Transform the request to OpenAI Chat Completions format for Bedrock imported models. + + Removes AWS-specific params and stream param (handled separately in URL), + then delegates to parent class for standard OpenAI request transformation. + """ + # Remove stream from optional_params as it's handled separately in URL + optional_params.pop("stream", None) + + # Remove AWS-specific params that shouldn't be in the request body + inference_params = { + k: v + for k, v in optional_params.items() + if k not in self.aws_authentication_params + } + + # Use parent class transform_request for OpenAI format + return super().transform_request( + model=self._get_openai_model_id(model), + messages=messages, + optional_params=inference_params, + litellm_params=litellm_params, + headers=headers, + ) + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + """ + Validate the environment and return headers. + + For Bedrock, we don't need Bearer token auth since we use AWS SigV4. + """ + return headers + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] + ) -> BedrockError: + """Return the appropriate error class for Bedrock.""" + return BedrockError(status_code=status_code, message=error_message) diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index baaec996535..35d3d736a1c 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -403,6 +403,9 @@ class BedrockModelInfo(BaseLLMModelInfo): if model.startswith("invoke/"): model = model.split("/", 1)[1] + if model.startswith("openai/"): + model = model.split("/", 1)[1] + return model @staticmethod @@ -446,12 +449,12 @@ class BedrockModelInfo(BaseLLMModelInfo): @staticmethod def get_bedrock_route( model: str, - ) -> Literal["converse", "invoke", "converse_like", "agent", "agentcore", "async_invoke"]: + ) -> Literal["converse", "invoke", "converse_like", "agent", "agentcore", "async_invoke", "openai"]: """ Get the bedrock route for the given model. """ route_mappings: Dict[ - str, Literal["invoke", "converse_like", "converse", "agent", "agentcore", "async_invoke"] + str, Literal["invoke", "converse_like", "converse", "agent", "agentcore", "async_invoke", "openai"] ] = { "invoke/": "invoke", "converse_like/": "converse_like", @@ -459,6 +462,7 @@ class BedrockModelInfo(BaseLLMModelInfo): "agent/": "agent", "agentcore/": "agentcore", "async_invoke/": "async_invoke", + "openai/": "openai", } # Check explicit routes first @@ -517,6 +521,14 @@ class BedrockModelInfo(BaseLLMModelInfo): """ return "async_invoke/" in model + @staticmethod + def _explicit_openai_route(model: str) -> bool: + """ + Check if the model is an explicit openai route. + Used for Bedrock imported models that use OpenAI Chat Completions format. + """ + return "openai/" in model + @staticmethod def get_bedrock_provider_config_for_messages_api( model: str, @@ -566,6 +578,8 @@ def get_bedrock_chat_config(model: str): # Handle explicit routes first if bedrock_route == "converse" or bedrock_route == "converse_like": return litellm.AmazonConverseConfig() + elif bedrock_route == "openai": + return litellm.AmazonBedrockOpenAIConfig() elif bedrock_route == "agent": from litellm.llms.bedrock.chat.invoke_agent.transformation import ( AmazonInvokeAgentConfig, diff --git a/litellm/proxy/guardrails/guardrail_hooks/prompt_security/prompt_security.py b/litellm/proxy/guardrails/guardrail_hooks/prompt_security/prompt_security.py index daee50f30cc..23b9da4714c 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/prompt_security/prompt_security.py +++ b/litellm/proxy/guardrails/guardrail_hooks/prompt_security/prompt_security.py @@ -1,13 +1,18 @@ -import os -import re import asyncio import base64 +import os +import re from typing import TYPE_CHECKING, Any, AsyncGenerator, Optional, Type, Union + from fastapi import HTTPException + from litellm import DualCache from litellm._logging import verbose_proxy_logger from litellm.integrations.custom_guardrail import CustomGuardrail -from litellm.llms.custom_httpx.http_handler import get_async_httpx_client, httpxSpecialProvider +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) from litellm.proxy._types import UserAPIKeyAuth from litellm.types.utils import ( Choices, @@ -15,7 +20,7 @@ from litellm.types.utils import ( EmbeddingResponse, ImageResponse, ModelResponse, - ModelResponseStream + ModelResponseStream, ) if TYPE_CHECKING: @@ -267,8 +272,10 @@ class PromptSecurityGuardrail(CustomGuardrail): content = msg.get('content', '') # Handle both string and list content types if isinstance(content, str): - if content.startswith('### '): return False - if '"follow_ups": [' in content: return False + if content.startswith('### '): + return False + if '"follow_ups": [' in content: + return False return True messages = list(filter(lambda msg: good_msg(msg), messages)) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 014bcdc1670..26e867dc33e 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,22 +1,7 @@ model_list: - - model_name: aws/anthropic/bedrock-claude-3-5-sonnet-v1 + - model_name: qwen-25vl-72b litellm_params: - model: bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0 - aws_region_name: us-east-1 - custom_llm_provider: bedrock - - model_name: aws/anthropic/bedrock-claude-3-5-sonnet-v1 - litellm_params: - model: bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0 - aws_region_name: us-west-2 - custom_llm_provider: bedrock - - model_name: bedrock/* - litellm_params: - model: bedrock/* - custom_llm_provider: bedrock - aws_region_name: us-west-2 - - model_name: runwayml/* - litellm_params: - model: runwayml/* + model: bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z diff --git a/litellm/utils.py b/litellm/utils.py index f683a59f501..1b4df689959 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3719,7 +3719,17 @@ def get_optional_params( # noqa: PLR0915 else False ), ) - + elif bedrock_route == "openai": + optional_params = litellm.AmazonBedrockOpenAIConfig().map_openai_params( + model=model, + non_default_params=non_default_params, + optional_params=optional_params, + drop_params=( + drop_params + if drop_params is not None and isinstance(drop_params, bool) + else False + ), + ) elif "anthropic" in bedrock_base_model and bedrock_route == "invoke": if bedrock_base_model.startswith("anthropic.claude-3"): optional_params = ( diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index 9242950daac..f43e939c681 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -3434,3 +3434,100 @@ async def test_bedrock_streaming_passthrough_test1(monkeypatch): print(mock_callback.call_args.kwargs.keys()) assert "standard_logging_object" in mock_callback.call_args.kwargs["kwargs"] assert "response_cost" in mock_callback.call_args.kwargs["kwargs"] + + +def test_bedrock_openai_imported_model(): + """ + Test that Bedrock imported models using OpenAI format work correctly. + + This test validates: + 1. The request body follows OpenAI Chat Completions format + 2. The URL is correctly constructed for Bedrock invoke endpoint + 3. Messages with system, user roles and image_url content are preserved + """ + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + + # Sample base64 image data (truncated for test) + sample_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + + messages = [ + { + "role": "system", + "content": "You are a helpful assistant that can analyze images.", + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Spot the difference between the two images?", + }, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{sample_base64}"}, + }, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{sample_base64}"}, + }, + ], + }, + ] + + with patch.object(client, "post") as mock_post: + try: + response = completion( + model="bedrock/openai/arn:aws:bedrock:us-east-1:117159858402:imported-model/m4gc1mrfuddy", + messages=messages, + max_tokens=300, + temperature=0.5, + client=client, + ) + except Exception as e: + print(f"Exception (expected during mock): {e}") + + mock_post.assert_called_once() + + # Validate URL + url = mock_post.call_args.kwargs["url"] + print(f"URL: {url}") + assert "bedrock-runtime.us-east-1.amazonaws.com" in url + assert "arn:aws:bedrock:us-east-1:117159858402:imported-model/m4gc1mrfuddy" in url + assert "/invoke" in url + + # Validate request body follows OpenAI format + request_body = json.loads(mock_post.call_args.kwargs["data"]) + print(f"Request body: {json.dumps(request_body, indent=2)}") + + # Check messages structure + assert "messages" in request_body + assert len(request_body["messages"]) == 2 + + # Check system message + system_msg = request_body["messages"][0] + assert system_msg["role"] == "system" + assert "helpful assistant" in system_msg["content"] + + # Check user message with image content + user_msg = request_body["messages"][1] + assert user_msg["role"] == "user" + assert isinstance(user_msg["content"], list) + assert len(user_msg["content"]) == 3 + + # Check text content + assert user_msg["content"][0]["type"] == "text" + assert "Spot the difference" in user_msg["content"][0]["text"] + + # Check image_url content + assert user_msg["content"][1]["type"] == "image_url" + assert "image_url" in user_msg["content"][1] + assert user_msg["content"][1]["image_url"]["url"].startswith("data:image/jpeg;base64,") + + assert user_msg["content"][2]["type"] == "image_url" + assert "image_url" in user_msg["content"][2] + + # Check max_tokens and temperature + assert request_body["max_tokens"] == 300 + assert request_body["temperature"] == 0.5 From 7f991fe89782031beefb959b76f5a642619a841f Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 12:37:54 -0800 Subject: [PATCH 058/349] refactor: clean up lazy loading system and remove type ignore comments - Remove excessive comments and simplify documentation - Remove @overload decorators (type stubs are sufficient) - Remove Logging type stub to avoid redefinition errors - Keep only essential type stubs for cost_per_token and completion_cost - Fixes type checking errors without using type: ignore comments --- litellm/__init__.py | 52 +++++---------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index a513619c4dd..5cb135269bd 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -22,6 +22,7 @@ from typing import ( TYPE_CHECKING, Tuple, overload, + Type, ) from litellm.types.integrations.datadog_llm_obs import DatadogLLMObsInitParams from litellm.types.integrations.datadog import DatadogInitParams @@ -1505,20 +1506,7 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None: global_gitlab_config = config -# ============================================================================ -# LAZY LOADING SYSTEM -# ============================================================================ -# This system allows heavy modules to be loaded only when accessed, -# significantly reducing initial import time and memory usage. -# -# To add a new lazy-loaded function/class: -# 1. Add the import handler function (e.g., _lazy_import_xxx) -# 2. Add entries to _LAZY_LOAD_REGISTRY below -# 3. Add type stubs in the TYPE_CHECKING block -# 4. Add @overload decorators for type checking -# ============================================================================ - - +# Lazy loading system for heavy modules to reduce initial import time and memory usage def _lazy_import_cost_calculator(name: str) -> Any: """Lazy import for cost_calculator functions.""" from .cost_calculator import ( @@ -1534,7 +1522,7 @@ def _lazy_import_cost_calculator(name: str) -> Any: } func = _cost_functions[name] - globals()[name] = func # Cache for future access + globals()[name] = func return func @@ -1552,7 +1540,7 @@ def _lazy_import_litellm_logging(name: str) -> Any: } obj = _logging_objects[name] - globals()[name] = obj # Cache for future access + globals()[name] = obj return obj except Exception as e: raise AttributeError( @@ -1561,54 +1549,24 @@ def _lazy_import_litellm_logging(name: str) -> Any: ) from e -# Registry mapping lazy-loaded names to their import handlers -# Add new lazy-loaded items here for easy maintenance _LAZY_LOAD_REGISTRY: Dict[str, Callable[[str], Any]] = { - # Cost calculator functions "completion_cost": _lazy_import_cost_calculator, "cost_per_token": _lazy_import_cost_calculator, "response_cost_calculator": _lazy_import_cost_calculator, - # Logging objects "Logging": _lazy_import_litellm_logging, "modify_integration": _lazy_import_litellm_logging, } -# Type stubs for lazy-loaded functions/classes to help type checkers -# Add type annotations here for new lazy-loaded items if TYPE_CHECKING: - # Cost calculator functions cost_per_token: Callable[..., Tuple[float, float]] completion_cost: Callable[..., float] response_cost_calculator: Any - # Logging objects - Logging: Any modify_integration: Any -# Type overloads for __getattr__ to provide proper type hints -# Add @overload decorators here for new lazy-loaded items with specific types -@overload -def __getattr__(name: Literal["cost_per_token"]) -> Callable[..., Tuple[float, float]]: - ... - - -@overload -def __getattr__(name: Literal["completion_cost"]) -> Callable[..., float]: - ... - - -@overload -def __getattr__(name: Literal["response_cost_calculator", "Logging", "modify_integration"]) -> Any: - ... - - def __getattr__(name: str) -> Any: - """Lazy import handler for cost_calculator and litellm_logging functions. - - This allows these heavy modules to be loaded only when accessed, - reducing initial import time and memory usage. - """ + """Lazy import handler for cost_calculator and litellm_logging functions.""" if name in _LAZY_LOAD_REGISTRY: return _LAZY_LOAD_REGISTRY[name](name) From 2ef5a41a24b7f0ff83a467b834d747ce3baba1d1 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 12:48:24 -0800 Subject: [PATCH 059/349] fix: resolve type checking errors in vertex_ai and mcp_server_manager - Fix type incompatibility in vertex_ai/videos/transformation.py by casting litellm_params to Dict[str, Any] - Add await to async add_update_server call in mcp_server_manager.py - Resolves 4 type checking errors across 2 files --- litellm/llms/vertex_ai/videos/transformation.py | 9 +++++---- .../proxy/_experimental/mcp_server/mcp_server_manager.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/litellm/llms/vertex_ai/videos/transformation.py b/litellm/llms/vertex_ai/videos/transformation.py index 2b6d43dd708..1f657f63bf1 100644 --- a/litellm/llms/vertex_ai/videos/transformation.py +++ b/litellm/llms/vertex_ai/videos/transformation.py @@ -7,7 +7,7 @@ Based on: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-refer import base64 import time -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union, cast import httpx from httpx._types import RequestFiles @@ -174,10 +174,11 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase): """ # Extract Vertex AI parameters using safe helpers from VertexBase # Use safe_get_* methods that don't mutate litellm_params dict - litellm_params = litellm_params or {} + # Ensure litellm_params is a dict for type checking + params_dict: Dict[str, Any] = cast(Dict[str, Any], litellm_params) if litellm_params is not None else {} - vertex_project = VertexBase.safe_get_vertex_ai_project(litellm_params=litellm_params) - vertex_credentials = VertexBase.safe_get_vertex_ai_credentials(litellm_params=litellm_params) + vertex_project = VertexBase.safe_get_vertex_ai_project(litellm_params=params_dict) + vertex_credentials = VertexBase.safe_get_vertex_ai_credentials(litellm_params=params_dict) # Get access token from Vertex credentials access_token, project_id = self.get_access_token( diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 54c79fc696c..4a0d25e24f3 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -2270,7 +2270,7 @@ class MCPServerManager: server.status = "unhealthy" ## try adding server to registry to get error try: - self.add_update_server(server) + await self.add_update_server(server) except Exception as e: server.health_check_error = str(e) server.health_check_error = "Server is not in in memory registry yet. This could be a temporary sync issue." From ab877d9551e76da2d3208c6daba1b71238d2d43b Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 13:07:09 -0800 Subject: [PATCH 060/349] fix: convert MCP TextContent objects to JSON-serializable format in logging - Convert Pydantic BaseModel objects (TextContent, ImageContent, etc.) to dicts in get_final_response_obj - Fixes TypeError: Object of type TextContent is not JSON serializable - Resolves test failures in test_mcp_tool_call_hook and test_mcp_cost_tracking --- litellm/litellm_core_utils/litellm_logging.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 67407ff4c7a..eb596d8203c 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4380,7 +4380,23 @@ class StandardLoggingPayloadSetup: if response_obj: final_response_obj: Optional[Union[dict, str, list]] = response_obj elif isinstance(init_response_obj, list) or isinstance(init_response_obj, str): - final_response_obj = init_response_obj + # Convert MCP content objects (TextContent, ImageContent, etc.) to JSON-serializable format + if isinstance(init_response_obj, list): + serialized_list = [] + for item in init_response_obj: + # Check if item is a Pydantic BaseModel (MCP content types are Pydantic models) + if isinstance(item, BaseModel): + # Convert Pydantic model to dict for JSON serialization + serialized_list.append(item.model_dump()) + elif hasattr(item, "__dict__") and not isinstance(item, (str, int, float, bool, type(None))): + # Fallback: convert object to dict (but skip primitive types) + serialized_list.append(item.__dict__) + else: + # Already serializable (str, dict, int, float, bool, None, etc.) + serialized_list.append(item) + final_response_obj = serialized_list + else: + final_response_obj = init_response_obj else: final_response_obj = {} From 48810e6bcb9fc8974e2eb2c7b7bf87571f45fc6b Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 13:14:25 -0800 Subject: [PATCH 061/349] refactor: improve MCP TextContent serialization to follow existing patterns - Remove risky __dict__ fallback for non-BaseModel objects - Only convert BaseModel objects to dicts using model_dump() (consistent with line 4745-4746) - Keep other objects unchanged to maintain backward compatibility - Follows existing codebase patterns for Pydantic model serialization --- litellm/litellm_core_utils/litellm_logging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index eb596d8203c..cf8d5f2cc2d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4385,14 +4385,14 @@ class StandardLoggingPayloadSetup: serialized_list = [] for item in init_response_obj: # Check if item is a Pydantic BaseModel (MCP content types are Pydantic models) + # This follows the same pattern used at line 4745-4746 for BaseModel objects if isinstance(item, BaseModel): # Convert Pydantic model to dict for JSON serialization serialized_list.append(item.model_dump()) - elif hasattr(item, "__dict__") and not isinstance(item, (str, int, float, bool, type(None))): - # Fallback: convert object to dict (but skip primitive types) - serialized_list.append(item.__dict__) else: # Already serializable (str, dict, int, float, bool, None, etc.) + # Non-BaseModel objects are kept as-is - they should already be serializable + # or will be handled by json.dumps with default=str if needed serialized_list.append(item) final_response_obj = serialized_list else: From b3348c665ac6b89ebd50eedd7ee0b64b2b0d600c Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Tue, 25 Nov 2025 14:22:48 -0800 Subject: [PATCH 062/349] fix: use cached import helper for Logging in ahealth_check to preserve lazy loading - Use get_litellm_logging_class() from cached_imports instead of direct import - Preserves lazy loading benefit (only loads when function is called) - Follows existing codebase pattern for cached imports - Fixes NameError: name 'Logging' is not defined in test_ahealth_check_ocr --- litellm/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/main.py b/litellm/main.py index 88bf0b72a0b..4482cf5d123 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -6109,6 +6109,10 @@ async def ahealth_check( } """ from litellm.litellm_core_utils.health_check_helpers import HealthCheckHelpers + from litellm.litellm_core_utils.cached_imports import get_litellm_logging_class + + # Use cached import helper to lazy-load Logging class (only loads when function is called) + Logging = get_litellm_logging_class() # Map modes to their corresponding health check calls ######################################################### From 52f1bf1a800bf76d3896fdf9d714f160f45d408b Mon Sep 17 00:00:00 2001 From: YutaSaito <36355491+uc4w6c@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:33:38 +0900 Subject: [PATCH 063/349] fix: missing await (#17103) --- litellm/proxy/_experimental/mcp_server/mcp_server_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 54c79fc696c..25b5211464c 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -1976,7 +1976,7 @@ class MCPServerManager: verbose_logger.debug( f"Adding server to registry: {server.server_id} ({server.server_name})" ) - self.add_update_server(server) + await self.add_update_server(server) verbose_logger.debug( f"Registry now contains {len(self.get_registry())} servers" @@ -2270,7 +2270,7 @@ class MCPServerManager: server.status = "unhealthy" ## try adding server to registry to get error try: - self.add_update_server(server) + await self.add_update_server(server) except Exception as e: server.health_check_error = str(e) server.health_check_error = "Server is not in in memory registry yet. This could be a temporary sync issue." From f3d577592023d374ce532058c1634e63a69f5009 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Nov 2025 14:40:17 -0800 Subject: [PATCH 064/349] fix: fix doc load issue --- docs/my-website/docs/providers/anthropic_effort.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/my-website/docs/providers/anthropic_effort.md b/docs/my-website/docs/providers/anthropic_effort.md index d1116ad5be4..0015162a95b 100644 --- a/docs/my-website/docs/providers/anthropic_effort.md +++ b/docs/my-website/docs/providers/anthropic_effort.md @@ -1,3 +1,6 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # Anthropic Effort Parameter Control how many tokens Claude uses when responding with the `effort` parameter, trading off between response thoroughness and token efficiency. From db587926a473f51a21bc96935d10495ae7fdab7e Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 14:46:46 -0800 Subject: [PATCH 065/349] Sorting changes, pending tests and loading state --- .../src/components/view_users/columns.tsx | 14 +++++++-- .../src/components/view_users/table.tsx | 31 +++++++++---------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_users/columns.tsx b/ui/litellm-dashboard/src/components/view_users/columns.tsx index 32bfa0ed6d2..20df4fc246e 100644 --- a/ui/litellm-dashboard/src/components/view_users/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_users/columns.tsx @@ -22,10 +22,12 @@ export const columns = ( handleUserClick: (userId: string, openInEditMode?: boolean) => void, selectionOptions?: SelectionOptions, ): ColumnDef[] => { + // Backend sortable columns: user_id, user_email, created_at, spend, user_alias, user_role const baseColumns: ColumnDef[] = [ { header: "User ID", accessorKey: "user_id", + enableSorting: true, cell: ({ row }) => ( {row.original.user_id ? `${row.original.user_id.slice(0, 7)}...` : "-"} @@ -35,16 +37,19 @@ export const columns = ( { header: "Email", accessorKey: "user_email", + enableSorting: true, cell: ({ row }) => {row.original.user_email || "-"}, }, { header: "Global Proxy Role", accessorKey: "user_role", + enableSorting: true, cell: ({ row }) => {possibleUIRoles?.[row.original.user_role]?.ui_label || "-"}, }, { header: "Spend (USD)", accessorKey: "spend", + enableSorting: true, cell: ({ row }) => ( {row.original.spend ? formatNumberWithCommas(row.original.spend, 4) : "-"} ), @@ -52,6 +57,7 @@ export const columns = ( { header: "Budget (USD)", accessorKey: "max_budget", + enableSorting: false, cell: ({ row }) => ( {row.original.max_budget !== null ? row.original.max_budget : "Unlimited"} ), @@ -66,6 +72,7 @@ export const columns = (
), accessorKey: "sso_user_id", + enableSorting: false, cell: ({ row }) => ( {row.original.sso_user_id !== null ? row.original.sso_user_id : "-"} ), @@ -73,6 +80,7 @@ export const columns = ( { header: "API Keys", accessorKey: "key_count", + enableSorting: false, cell: ({ row }) => ( {row.original.key_count > 0 ? ( @@ -90,7 +98,7 @@ export const columns = ( { header: "Created At", accessorKey: "created_at", - sortingFn: "datetime", + enableSorting: true, cell: ({ row }) => ( {row.original.created_at ? new Date(row.original.created_at).toLocaleDateString() : "-"} @@ -100,7 +108,7 @@ export const columns = ( { header: "Updated At", accessorKey: "updated_at", - sortingFn: "datetime", + enableSorting: false, cell: ({ row }) => ( {row.original.updated_at ? new Date(row.original.updated_at).toLocaleDateString() : "-"} @@ -110,6 +118,7 @@ export const columns = ( { id: "actions", header: "Actions", + enableSorting: false, cell: ({ row }) => (
@@ -148,6 +157,7 @@ export const columns = ( return [ { id: "select", + enableSorting: false, header: () => ( { + onSortingChange: (updaterOrValue: any) => { + const newSorting = typeof updaterOrValue === "function" ? updaterOrValue(sorting) : updaterOrValue; setSorting(newSorting); - if (newSorting.length > 0) { + if (newSorting && Array.isArray(newSorting) && newSorting.length > 0 && newSorting[0]) { const sortState = newSorting[0]; - const sortBy = sortState.id; - const sortOrder = sortState.desc ? "desc" : "asc"; - onSortChange?.(sortBy, sortOrder); + if (sortState.id) { + const sortBy = sortState.id; + const sortOrder = sortState.desc ? "desc" : "asc"; + onSortChange?.(sortBy, sortOrder); + } + } else { + // Reset to default sort when no sorting is selected + onSortChange?.("created_at", "desc"); } }, getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), + manualSorting: true, enableSorting: true, }); @@ -403,7 +402,7 @@ export function UserDataTable({ header.id === "actions" ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]" : "" - }`} + } ${header.column.getCanSort() ? "cursor-pointer hover:bg-gray-50" : ""}`} onClick={header.column.getToggleSortingHandler()} >
@@ -412,7 +411,7 @@ export function UserDataTable({ ? null : flexRender(header.column.columnDef.header, header.getContext())}
- {header.id !== "actions" && ( + {header.id !== "actions" && header.column.getCanSort() && (
{header.column.getIsSorted() ? ( { From c0288d81aa4ef31b9e1f529ce032c827c3087c9f Mon Sep 17 00:00:00 2001 From: Sam Chou Date: Tue, 25 Nov 2025 14:49:12 -0800 Subject: [PATCH 066/349] Fix bedrock claude opus 4.5 inference profile - only global currently (#17101) --- .../model_prices_and_context_window_backup.json | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b3dc11e206c..f9cc8bfa067 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -2687,7 +2687,7 @@ "/v1/audio/transcriptions" ] }, - "azure/gpt-5.1-2025-11-13": { + "azure/gpt-5.1-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -2723,7 +2723,7 @@ "supports_service_tier": true, "supports_vision": true }, - "azure/gpt-5.1-chat-2025-11-13": { + "azure/gpt-5.1-chat-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -2758,7 +2758,7 @@ "supports_tool_choice": false, "supports_vision": true }, - "azure/gpt-5.1-codex-2025-11-13": { + "azure/gpt-5.1-codex-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -19521,7 +19521,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/deepseek-v3.1:671b-cloud" : { + "ollama/deepseek-v3.1:671b-cloud": { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 163840, @@ -19531,7 +19531,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/gpt-oss:120b-cloud" : { + "ollama/gpt-oss:120b-cloud": { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 131072, @@ -19541,7 +19541,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/gpt-oss:20b-cloud" : { + "ollama/gpt-oss:20b-cloud": { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 131072, @@ -22037,7 +22037,6 @@ "supports_reasoning": true, "source": "https://cloud.sambanova.ai/plans/pricing" }, - "snowflake/claude-3-5-sonnet": { "litellm_provider": "snowflake", "max_input_tokens": 18000, @@ -23232,7 +23231,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, - "us.anthropic.claude-opus-4-5-20251101-v1:0": { + "global.anthropic.claude-opus-4-5-20251101-v1:0": { "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, From 8637d74e170b52c23af6d780f9d7d20eea167069 Mon Sep 17 00:00:00 2001 From: Kerem Turgutlu Date: Wed, 26 Nov 2025 01:50:17 +0300 Subject: [PATCH 067/349] include `server_tool_use` in streaming usage (#16826) * include server_tool_use in streaming usage * add test --- .../streaming_chunk_builder_utils.py | 12 ++- .../streaming_chunk_builder_utils.py | 3 +- .../test_streaming_chunk_builder_utils.py | 81 +++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py index ddcf81b5ba5..c332e5f88f7 100644 --- a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py @@ -18,6 +18,7 @@ from litellm.types.utils import ( ModelResponseStream, PromptTokensDetailsWrapper, Usage, + ServerToolUse ) from litellm.utils import print_verbose, token_counter @@ -418,7 +419,8 @@ class ChunkProcessor: ## anthropic prompt caching information ## cache_creation_input_tokens: Optional[int] = None cache_read_input_tokens: Optional[int] = None - + + server_tool_use: Optional[ServerToolUse] = None web_search_requests: Optional[int] = None completion_tokens_details: Optional[CompletionTokensDetails] = None prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None @@ -462,6 +464,8 @@ class ChunkProcessor: completion_tokens_details = usage_chunk_dict[ "completion_tokens_details" ] + if hasattr(usage_chunk, 'server_tool_use') and usage_chunk.server_tool_use is not None: + server_tool_use = usage_chunk.server_tool_use if ( usage_chunk_dict["prompt_tokens_details"] is not None and getattr( @@ -483,6 +487,7 @@ class ChunkProcessor: completion_tokens=completion_tokens, cache_creation_input_tokens=cache_creation_input_tokens, cache_read_input_tokens=cache_read_input_tokens, + server_tool_use=server_tool_use, web_search_requests=web_search_requests, completion_tokens_details=completion_tokens_details, prompt_tokens_details=prompt_tokens_details, @@ -513,6 +518,9 @@ class ChunkProcessor: "cache_read_input_tokens" ] + server_tool_use: Optional[ServerToolUse] = calculated_usage_per_chunk[ + "server_tool_use" + ] web_search_requests: Optional[int] = calculated_usage_per_chunk[ "web_search_requests" ] @@ -576,6 +584,8 @@ class ChunkProcessor: if prompt_tokens_details is not None: returned_usage.prompt_tokens_details = prompt_tokens_details + if server_tool_use is not None: + returned_usage.server_tool_use = server_tool_use if web_search_requests is not None: if returned_usage.prompt_tokens_details is None: returned_usage.prompt_tokens_details = PromptTokensDetailsWrapper( diff --git a/litellm/types/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/types/litellm_core_utils/streaming_chunk_builder_utils.py index aa879e14c34..a1f89dac5cf 100644 --- a/litellm/types/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/types/litellm_core_utils/streaming_chunk_builder_utils.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Optional from typing_extensions import TypedDict -from ..utils import CompletionTokensDetails, PromptTokensDetailsWrapper +from ..utils import CompletionTokensDetails, PromptTokensDetailsWrapper, ServerToolUse class UsagePerChunk(TypedDict): @@ -10,6 +10,7 @@ class UsagePerChunk(TypedDict): completion_tokens: int cache_creation_input_tokens: Optional[int] cache_read_input_tokens: Optional[int] + server_tool_use: Optional[ServerToolUse] web_search_requests: Optional[int] completion_tokens_details: Optional[CompletionTokensDetails] prompt_tokens_details: Optional[PromptTokensDetailsWrapper] diff --git a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py index f6636874336..2164a3b82e3 100644 --- a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py @@ -16,6 +16,7 @@ from litellm.types.utils import ( Function, ModelResponseStream, PromptTokensDetails, + ServerToolUse, StreamingChoices, Usage, ) @@ -325,3 +326,83 @@ def test_stream_chunk_builder_litellm_usage_chunks(): assert usage.prompt_tokens == 50 assert usage.completion_tokens == 27 assert usage.total_tokens == 77 + + +def test_stream_chunk_builder_anthropic_web_search(): + # Prepare two mocked streaming chunks with usage split across them + chunk1 = ModelResponseStream( + id="chatcmpl-mocked-usage-1", + created=1745513206, + model="claude-sonnet-4-5-20250929", + object="chat.completion.chunk", + system_fingerprint=None, + choices=[ + StreamingChoices( + finish_reason=None, + index=0, + delta=Delta( + provider_specific_fields=None, + content="", + role=None, + function_call=None, + tool_calls=None, + audio=None, + ), + logprobs=None, + ) + ], + provider_specific_fields=None, + stream_options={"include_usage": True}, + usage=Usage( + completion_tokens=0, + prompt_tokens=50, + total_tokens=50, + completion_tokens_details=None, + server_tool_use=ServerToolUse(web_search_requests=2), + prompt_tokens_details=None, + ), + ) + + chunk2 = ModelResponseStream( + id="chatcmpl-mocked-usage-1", + created=1745513207, + model="claude-sonnet-4-5-20250929", + object="chat.completion.chunk", + system_fingerprint=None, + choices=[ + StreamingChoices( + finish_reason="stop", + index=0, + delta=Delta( + provider_specific_fields=None, + content=None, + role=None, + function_call=None, + tool_calls=None, + audio=None, + ), + logprobs=None, + ) + ], + provider_specific_fields=None, + stream_options={"include_usage": True}, + usage=Usage( + completion_tokens=27, + prompt_tokens=0, + total_tokens=27, + completion_tokens_details=None, + prompt_tokens_details=None, + ), + ) + + chunks = [chunk1, chunk2] + processor = ChunkProcessor(chunks=chunks) + + usage = processor.calculate_usage( + chunks=chunks, model="claude-sonnet-4-5-20250929", completion_output="" + ) + + assert usage.prompt_tokens == 50 + assert usage.completion_tokens == 27 + assert usage.total_tokens == 77 + assert usage.server_tool_use['web_search_requests'] == 2 \ No newline at end of file From 70a13258477f2868cbae00919205af433fa68625 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Nov 2025 15:01:15 -0800 Subject: [PATCH 068/349] docs: more doc cleanup --- .../index.md | 188 ++++++++++-------- ...odel_prices_and_context_window_backup.json | 52 +++++ model_prices_and_context_window.json | 52 +++++ 3 files changed, 209 insertions(+), 83 deletions(-) diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index 0b0f4a54167..051235bc74d 100644 --- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -26,6 +26,13 @@ This guide covers Anthropic's latest model (Claude Opus 4.5) and its advanced fe --- +| Feature | Supported Models | +|---------|-----------------| +| Tool Search | Claude Opus 4.5, Sonnet 4.5 | +| Programmatic Tool Calling | Claude Opus 4.5, Sonnet 4.5 | +| Input Examples | Claude Opus 4.5, Sonnet 4.5 | +| Effort Parameter | Claude Opus 4.5 only | + ## Usage @@ -222,6 +229,104 @@ curl --location 'http://0.0.0.0:4000/bedrock/model/claude-4/converse' \ +## Usage - Vertex AI + + + + + +```python +from litellm import completion +import json + +## GET CREDENTIALS +## RUN ## +# !gcloud auth application-default login - run this to add vertex credentials to your env +## OR ## +file_path = 'path/to/vertex_ai_service_account.json' + +# Load the JSON file +with open(file_path, 'r') as file: + vertex_credentials = json.load(file) + +# Convert to JSON string +vertex_credentials_json = json.dumps(vertex_credentials) + +## COMPLETION CALL +response = completion( + model="vertex_ai/claude-opus-4-5@20251101", + messages=[{ "content": "Hello, how are you?","role": "user"}], + vertex_credentials=vertex_credentials_json, + vertex_project="your-project-id", + vertex_location="us-east5" +) +``` + + + + +**1. Setup config.yaml** + +```yaml +model_list: + - model_name: claude-4 ### RECEIVED MODEL NAME ### + litellm_params: + model: vertex_ai/claude-opus-4-5@20251101 + vertex_credentials: "/path/to/service_account.json" + vertex_project: "your-project-id" + vertex_location: "us-east5" +``` + +**2. Start the proxy** + +```bash +litellm --config /path/to/config.yaml +``` + +**3. Test it!** + + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + +```bash +curl --location 'http://0.0.0.0:4000/v1/messages' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "max_tokens": 1024, + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + } +' +``` + + + + + + + ## Tool Search {#tool-search} ### Usage Example @@ -336,8 +441,6 @@ tools = [ ## Programmatic Tool Calling {#programmatic-tool-calling} -### Usage Example - ```python import litellm import json @@ -428,8 +531,6 @@ print("\nFinal answer:", final_response.choices[0].message.content) ## Tool Input Examples {#tool-input-examples} -### Usage Example - ```python import litellm @@ -755,82 +856,3 @@ This combination enables: 4. **Cost control** - Effort parameter optimizes token spend 5. **Full visibility** - Track all usage metrics ---- - -## Getting Started - -### Installation - -```bash -pip install litellm --upgrade -``` - -### Configuration - -```python -import os -import litellm - -# Set your API key -os.environ["ANTHROPIC_API_KEY"] = "your-api-key" - -# LiteLLM automatically handles beta headers for all features -``` - -### Supported Models - -| Feature | Supported Models | -|---------|-----------------| -| Tool Search | Claude Opus 4.5, Sonnet 4.5 | -| Programmatic Tool Calling | Claude Opus 4.5, Sonnet 4.5 | -| Input Examples | Claude Opus 4.5, Sonnet 4.5 | -| Effort Parameter | Claude Opus 4.5 only | - -### Supported Endpoints - -**Note**: All features are supported on the `/chat/completions` endpoint only. - -| Feature | Supported Models | -|---------|-----------------| -| Tool Search | Claude Opus 4.5, Sonnet 4.5 | -| Programmatic Tool Calling | Claude Opus 4.5, Sonnet 4.5 | -| Input Examples | Claude Opus 4.5, Sonnet 4.5 | -| Effort Parameter | Claude Opus 4.5 only | - -### Provider Support - -All features work across: -- ✅ Standard Anthropic API -- ✅ Azure Anthropic -- ✅ Vertex AI Anthropic -- ✅ LiteLLM Proxy - ---- - -## Conclusion - -These advanced Anthropic features in LiteLLM enable you to build more sophisticated, efficient, and cost-effective AI applications: - -- **Tool Search** scales to thousands of tools -- **Programmatic Tool Calling** reduces latency and tokens -- **Input Examples** improve accuracy -- **Effort Parameter** controls costs - -All features work seamlessly together and are supported across all Anthropic providers through LiteLLM's unified interface. - -### Resources - -- [LiteLLM Documentation](https://docs.litellm.ai/) -- [Anthropic Tool Search Docs](https://docs.litellm.ai/docs/providers/anthropic_tool_search) -- [Anthropic Programmatic Tool Calling Docs](https://docs.litellm.ai/docs/providers/anthropic_programmatic_tool_calling) -- [Anthropic Input Examples Docs](https://docs.litellm.ai/docs/providers/anthropic_tool_input_examples) -- [Anthropic Effort Parameter Docs](https://docs.litellm.ai/docs/providers/anthropic_effort) - -### Get Started Today - -```bash -pip install litellm --upgrade -``` - -Happy building! 🚀 - diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f9cc8bfa067..e51e5bb4b2b 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -24604,6 +24604,58 @@ "supports_tool_choice": true, "supports_vision": true }, + "vertex_ai/claude-opus-4-5": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, + "vertex_ai/claude-opus-4-5@20251101": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "vertex_ai/claude-sonnet-4-5": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b3dc11e206c..ff8766003c4 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -24605,6 +24605,58 @@ "supports_tool_choice": true, "supports_vision": true }, + "vertex_ai/claude-opus-4-5": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, + "vertex_ai/claude-opus-4-5@20251101": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "vertex_ai/claude-sonnet-4-5": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, From 3da9974a8770a5d05f839d78a7e15739b0664217 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 15:54:55 -0800 Subject: [PATCH 069/349] Tests --- .../src/components/view_users/table.test.tsx | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_users/table.test.tsx b/ui/litellm-dashboard/src/components/view_users/table.test.tsx index 5b612b27329..278a42e8963 100644 --- a/ui/litellm-dashboard/src/components/view_users/table.test.tsx +++ b/ui/litellm-dashboard/src/components/view_users/table.test.tsx @@ -1,6 +1,5 @@ -import { render } from "@testing-library/react"; +import { act, fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; -import React from "react"; import { UserDataTable } from "./table"; @@ -21,7 +20,7 @@ describe("UserDataTable", () => { const updateFilters = vi.fn(); - const { getByText } = render( + render( { />, ); - expect(getByText("Filters")).toBeInTheDocument(); + expect(screen.getByText("Filters")).toBeInTheDocument(); + }); + + it("should call onSortChange when clicking a sortable header", () => { + const filters = { + email: "", + user_id: "", + user_role: "", + sso_user_id: "", + team: "", + model: "", + min_spend: null, + max_spend: null, + sort_by: "created_at", + sort_order: "desc" as const, + }; + + const updateFilters = vi.fn(); + const onSortChange = vi.fn(); + + const possibleUIRoles = { + admin: { ui_label: "Admin" }, + user: { ui_label: "User" }, + }; + + render( + , + ); + + const emailHeader = screen.getByRole("columnheader", { name: /email/i }); + act(() => { + fireEvent.click(emailHeader); + }); + + expect(onSortChange).toHaveBeenCalledWith("user_email", "desc"); }); }); From 8ee6812edff5d1e79d5efbabd5c801a45439b1cf Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Nov 2025 15:58:51 -0800 Subject: [PATCH 070/349] docs: cleanup launch post --- .../index.md | 503 ++++++++++++++++-- ...odel_prices_and_context_window_backup.json | 15 +- 2 files changed, 470 insertions(+), 48 deletions(-) diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index 051235bc74d..9753529f572 100644 --- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -329,8 +329,13 @@ curl --location 'http://0.0.0.0:4000/v1/messages' \ ## Tool Search {#tool-search} +This lets Claude work with thousands of tools, by dynamically loading tools on-demand, instead of loading all tools into the context window upfront. + ### Usage Example + + + ```python import litellm import os @@ -407,7 +412,7 @@ tools = [ # Make a request - Claude will search for and use relevant tools response = litellm.completion( - model="anthropic/claude-sonnet-4-5-20250929", + model="anthropic/claude-opus-4-5-20251101", messages=[{ "role": "user", "content": "What's the weather like in San Francisco?" @@ -422,6 +427,108 @@ print("Tool calls:", response.choices[0].message.tool_calls) if hasattr(response.usage, 'server_tool_use'): print(f"Tool searches performed: {response.usage.server_tool_use.tool_search_requests}") ``` + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "What's the weather like in San Francisco?" + }], + "tools": [ + # Tool search tool (regex variant) + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # Deferred tools - loaded on-demand + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location. Returns temperature and conditions.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + "description": "Temperature unit" + } + }, + "required": ["location"] + } + }, + "defer_loading": True # Load on-demand + }, + { + "type": "function", + "function": { + "name": "search_files", + "description": "Search through files in the workspace using keywords", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "file_types": { + "type": "array", + "items": {"type": "string"} + } + }, + "required": ["query"] + } + }, + "defer_loading": True + }, + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute SQL queries against the database", + "parameters": { + "type": "object", + "properties": { + "sql": {"type": "string"} + }, + "required": ["sql"] + } + }, + "defer_loading": True + } + ] +} +' +``` + + ### BM25 Variant (Natural Language Search) @@ -441,6 +548,11 @@ tools = [ ## Programmatic Tool Calling {#programmatic-tool-calling} +Programmatic tool calling allows Claude to write code that calls your tools programmatically. [Learn more](https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling) + + + + ```python import litellm import json @@ -527,10 +639,80 @@ final_response = litellm.completion( print("\nFinal answer:", final_response.choices[0].message.content) ``` + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "Query sales data for West, East, and Central regions, then tell me which had the highest revenue" + }], + "tools": [ + # Code execution tool (required for programmatic calling) + { + "type": "code_execution_20250825", + "name": "code_execution" + }, + # Tool that can be called from code + { + "type": "function", + "function": { + "name": "query_database", + "description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.", + "parameters": { + "type": "object", + "properties": { + "sql": { + "type": "string", + "description": "SQL query to execute" + } + }, + "required": ["sql"] + } + }, + "allowed_callers": ["code_execution_20250825"] # Enable programmatic calling + } + ] +} +' +``` + + + --- ## Tool Input Examples {#tool-input-examples} +You can now provide Claude with examples of how to use your tools. [Learn more](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-input-examples) + + + + + ```python import litellm @@ -609,12 +791,124 @@ response = litellm.completion( print("Tool call:", response.choices[0].message.tool_calls[0].function.arguments) ``` + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "Schedule a team meeting for tomorrow at 2pm for 45 minutes with john@company.com and sarah@company.com" + }], + "tools": [ + { + "type": "function", + "function": { + "name": "create_calendar_event", + "description": "Create a new calendar event with attendees and reminders", + "parameters": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "start_time": { + "type": "string", + "description": "ISO 8601 format: YYYY-MM-DDTHH:MM:SS" + }, + "duration_minutes": {"type": "integer"}, + "attendees": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": {"type": "string"}, + "optional": {"type": "boolean"} + } + } + }, + "reminders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "minutes_before": {"type": "integer"}, + "method": {"type": "string", "enum": ["email", "popup"]} + } + } + } + }, + "required": ["title", "start_time", "duration_minutes"] + } + }, + # Provide concrete examples + "input_examples": [ + { + "title": "Team Standup", + "start_time": "2025-01-15T09:00:00", + "duration_minutes": 30, + "attendees": [ + {"email": "alice@company.com", "optional": False}, + {"email": "bob@company.com", "optional": False} + ], + "reminders": [ + {"minutes_before": 15, "method": "popup"} + ] + }, + { + "title": "Lunch Break", + "start_time": "2025-01-15T12:00:00", + "duration_minutes": 60 + # Demonstrates optional fields can be omitted + } + ] + } +] +} +' +``` + + + --- ## Effort Parameter: Control Token Usage {#effort-parameter} +Controls aspects like how much effort the model puts into its response, via `output_config={"effort": ..}`. + +:::info + +Soon, we will map OpenAI's `reasoning_effort` parameter to this. +::: + +Potential Values for `effort` parameter: `"high"`, `"medium"`, `"low"`. + ### Usage Example + + + ```python import litellm @@ -660,41 +954,46 @@ print(f"Medium: {response_medium.usage.completion_tokens} tokens") print(f"Low: {response_low.usage.completion_tokens} tokens") ``` -### Effort with Tool Use + + -Lower effort affects both explanations and tool calls: +1. Setup config.yaml -```python -tools = [ - { - "type": "function", - "function": { - "name": "get_weather", - "description": "Get weather for a location", - "parameters": { - "type": "object", - "properties": { - "location": {"type": "string"} - }, - "required": ["location"] - } - } - } -] - -# Low effort = fewer tool calls, more direct -response = litellm.completion( - model="anthropic/claude-opus-4-5-20251101", - messages=[{ - "role": "user", - "content": "Check weather in San Francisco, New York, and London" - }], - tools=tools, - output_config={"effort": "low"} # May combine into fewer calls -) +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY ``` ---- +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "Analyze the trade-offs between microservices and monolithic architectures" + }], + "output_config": { + "effort": "high" + } + } +' +``` + + + ## Cost Tracking: Monitor Tool Search Usage {#cost-tracking} @@ -702,8 +1001,15 @@ response = litellm.completion( Tool search operations are tracked separately in the usage object, allowing you to monitor and optimize costs. +It is available in the `usage` object, under `server_tool_use.tool_search_requests`. + +Anthropic charges $0.0001 per tool search request. + ### Tracking Example + + + ```python import litellm @@ -749,6 +1055,65 @@ if hasattr(response.usage, 'server_tool_use') and response.usage.server_tool_use print(f" Total: ${total_cost:.6f}") ``` + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "Find and use the weather tool for San Francisco" + }], + "tools": [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # ... 100 deferred tools + ] + } +' +``` + +Expected Response: + +```json +{ + ..., + "usage": { + ..., + "server_tool_use": { + "tool_search_requests": 1 + } + } +} +``` + + + + ### Cost Optimization Tips 1. **Keep frequently used tools non-deferred** (3-5 tools) @@ -756,15 +1121,6 @@ if hasattr(response.usage, 'server_tool_use') and response.usage.server_tool_use 3. **Monitor search requests** to identify optimization opportunities 4. **Combine with effort parameter** for maximum efficiency -```python -# Optimized for cost -response = litellm.completion( - model="anthropic/claude-sonnet-4-5-20250929", - messages=[{"role": "user", "content": "Simple query"}], - tools=tools_with_search, - output_config={"effort": "low"} # Reduce output tokens -) -``` --- @@ -774,6 +1130,9 @@ response = litellm.completion( These features work together seamlessly. Here's a real-world example combining all of them: + + + ```python import litellm import json @@ -846,6 +1205,68 @@ if hasattr(response.usage, 'server_tool_use') and response.usage.server_tool_use print(f"\nResponse: {response.choices[0].message.content}") ``` + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: claude-4 + litellm_params: + model: anthropic/claude-opus-4-5-20251101 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +2. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data ' { + "model": "claude-4", + "messages": [{ + "role": "user", + "content": "Analyze sales by region for the last quarter and identify top performers" + }], + "tools": [ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + # ... 100 deferred tools + ], + "output_config": { + "effort": "medium" + } + } +' +``` + +Expected Response: + +```json +{ + ..., + "usage": { + ..., + "server_tool_use": { + "tool_search_requests": 1 + } + } +} +``` + + + + ### Real-World Benefits This combination enables: diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index e51e5bb4b2b..ff8766003c4 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -2687,7 +2687,7 @@ "/v1/audio/transcriptions" ] }, - "azure/gpt-5.1-2025-11-13": { + "azure/gpt-5.1-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -2723,7 +2723,7 @@ "supports_service_tier": true, "supports_vision": true }, - "azure/gpt-5.1-chat-2025-11-13": { + "azure/gpt-5.1-chat-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -2758,7 +2758,7 @@ "supports_tool_choice": false, "supports_vision": true }, - "azure/gpt-5.1-codex-2025-11-13": { + "azure/gpt-5.1-codex-2025-11-13": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, "input_cost_per_token": 1.25e-06, @@ -19521,7 +19521,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/deepseek-v3.1:671b-cloud": { + "ollama/deepseek-v3.1:671b-cloud" : { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 163840, @@ -19531,7 +19531,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/gpt-oss:120b-cloud": { + "ollama/gpt-oss:120b-cloud" : { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 131072, @@ -19541,7 +19541,7 @@ "output_cost_per_token": 0.0, "supports_function_calling": true }, - "ollama/gpt-oss:20b-cloud": { + "ollama/gpt-oss:20b-cloud" : { "input_cost_per_token": 0.0, "litellm_provider": "ollama", "max_input_tokens": 131072, @@ -22037,6 +22037,7 @@ "supports_reasoning": true, "source": "https://cloud.sambanova.ai/plans/pricing" }, + "snowflake/claude-3-5-sonnet": { "litellm_provider": "snowflake", "max_input_tokens": 18000, @@ -23231,7 +23232,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, - "global.anthropic.claude-opus-4-5-20251101-v1:0": { + "us.anthropic.claude-opus-4-5-20251101-v1:0": { "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, From 5cb5c2a7b7fcd80de1caa803701af2d596965fbb Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Nov 2025 16:04:27 -0800 Subject: [PATCH 071/349] docs: more doc cleanup --- .../blog/anthropic_opus_4_5_and_advanced_features/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index 9753529f572..b545e936186 100644 --- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -33,6 +33,8 @@ This guide covers Anthropic's latest model (Claude Opus 4.5) and its advanced fe | Input Examples | Claude Opus 4.5, Sonnet 4.5 | | Effort Parameter | Claude Opus 4.5 only | +Supported Providers: [Anthropic](../../docs/providers/anthropic), [Bedrock](../../docs/providers/bedrock), [Vertex AI](../../docs/providers/vertex_partner#vertex-ai---anthropic-claude). + ## Usage From 6e5c7c0008f6c157a27ffc4530fe509b57687976 Mon Sep 17 00:00:00 2001 From: Otavio Brito <69211663+otaviofbrito@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:41:35 -0300 Subject: [PATCH 072/349] fix transcription exception handling - /audio/transcriptions (#16791) * fix transcription exception handling * reraise the exception --- litellm/proxy/proxy_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 13307742860..7c415b8106e 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5483,6 +5483,7 @@ async def audio_transcriptions( file_object = io.BytesIO(file_content) file_object.name = file.filename data["file"] = file_object + try: ### CALL HOOKS ### - modify incoming data / reject request before calling the model data = await proxy_logging_obj.pre_call_hook( @@ -5500,7 +5501,7 @@ async def audio_transcriptions( ) response = await llm_call except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + raise e finally: file_object.close() # close the file read in by io library From 5ec3f19a53dbf6028df7279468552b9db9442320 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 25 Nov 2025 16:57:38 -0800 Subject: [PATCH 073/349] Make model select required for team, add checks for all-proxy-models --- .../src/components/OldTeams.test.tsx | 103 ++++++++++++++---- .../src/components/OldTeams.tsx | 14 ++- .../src/components/team/team_info.test.tsx | 88 ++++++++++++++- .../src/components/team/team_info.tsx | 90 ++++++++------- 4 files changed, 225 insertions(+), 70 deletions(-) diff --git a/ui/litellm-dashboard/src/components/OldTeams.test.tsx b/ui/litellm-dashboard/src/components/OldTeams.test.tsx index 261178191f8..7f4ec3b09ca 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.test.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.test.tsx @@ -1,5 +1,6 @@ -import { act, fireEvent, render, screen } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { fetchAvailableModelsForTeamOrKey } from "./key_team_helpers/fetch_available_models_team_key"; import { teamCreateCall } from "./networking"; import OldTeams from "./OldTeams"; @@ -23,6 +24,28 @@ vi.mock("./molecules/notifications_manager", () => ({ }, })); +vi.mock("./key_team_helpers/fetch_available_models_team_key", () => ({ + fetchAvailableModelsForTeamOrKey: vi.fn(), + getModelDisplayName: vi.fn((model: string) => model), + unfurlWildcardModelsInList: vi.fn((teamModels: string[], allModels: string[]) => { + const wildcardDisplayNames: string[] = []; + const expandedModels: string[] = []; + + teamModels.forEach((teamModel) => { + if (teamModel.endsWith("/*")) { + const provider = teamModel.replace("/*", ""); + const matchingModels = allModels.filter((model) => model.startsWith(provider + "/")); + expandedModels.push(...matchingModels); + wildcardDisplayNames.push(teamModel); + } else { + expandedModels.push(teamModel); + } + }); + + return [...wildcardDisplayNames, ...expandedModels].filter((item, index, array) => array.indexOf(item) === index); + }), +})); + describe("OldTeams - handleCreate organization handling", () => { beforeEach(() => { vi.clearAllMocks(); @@ -236,7 +259,7 @@ describe("OldTeams - handleCreate organization handling", () => { }); it("should clear the delete modal when the cancel button is clicked", async () => { - const { getByRole, getByTestId } = render( + render( { organizations={[]} />, ); - const deleteTeamButton = getByTestId("delete-team-button"); + const deleteTeamButton = screen.getByTestId("delete-team-button"); act(() => { fireEvent.click(deleteTeamButton); }); @@ -275,7 +298,7 @@ describe("OldTeams - empty state", () => { }); it("should display empty state message when teams array is empty", () => { - const { getByText } = render( + render( { />, ); - expect(getByText("No teams found")).toBeInTheDocument(); - expect(getByText("Adjust your filters or create a new team")).toBeInTheDocument(); + expect(screen.getByText("No teams found")).toBeInTheDocument(); + expect(screen.getByText("Adjust your filters or create a new team")).toBeInTheDocument(); }); it("should display empty state message when teams is null", () => { - const { getByText } = render( + render( { />, ); - expect(getByText("No teams found")).toBeInTheDocument(); - expect(getByText("Adjust your filters or create a new team")).toBeInTheDocument(); + expect(screen.getByText("No teams found")).toBeInTheDocument(); + expect(screen.getByText("Adjust your filters or create a new team")).toBeInTheDocument(); }); it("should not display empty state when teams array has items", () => { - const { queryByText, getByText } = render( + render( { />, ); - expect(queryByText("No teams found")).not.toBeInTheDocument(); - expect(queryByText("Adjust your filters or create a new team")).not.toBeInTheDocument(); - expect(getByText("Test Team")).toBeInTheDocument(); + expect(screen.queryByText("No teams found")).not.toBeInTheDocument(); + expect(screen.queryByText("Adjust your filters or create a new team")).not.toBeInTheDocument(); + expect(screen.getByText("Test Team")).toBeInTheDocument(); }); }); @@ -473,7 +496,7 @@ describe("OldTeams - Default Team Settings tab visibility", () => { }); it("should show Default Team Settings tab for Admin role", () => { - const { getByRole } = render( + render( { />, ); - expect(getByRole("tab", { name: "Default Team Settings" })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Default Team Settings" })).toBeInTheDocument(); }); it("should show Default Team Settings tab for proxy_admin role", () => { - const { getByRole } = render( + render( { />, ); - expect(getByRole("tab", { name: "Default Team Settings" })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Default Team Settings" })).toBeInTheDocument(); }); it("should not show Default Team Settings tab for proxy_admin_viewer role", () => { - const { queryByRole } = render( + render( { />, ); - expect(queryByRole("tab", { name: "Default Team Settings" })).not.toBeInTheDocument(); + expect(screen.queryByRole("tab", { name: "Default Team Settings" })).not.toBeInTheDocument(); }); it("should not show Default Team Settings tab for Admin Viewer role", () => { - const { queryByRole } = render( + render( { />, ); - expect(queryByRole("tab", { name: "Default Team Settings" })).not.toBeInTheDocument(); + expect(screen.queryByRole("tab", { name: "Default Team Settings" })).not.toBeInTheDocument(); + }); +}); + +describe("OldTeams - all-proxy-models dropdown visibility", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4", "gpt-3.5-turbo"]); + }); + + it("should not show all-proxy-models option when user has no access to it", async () => { + vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4", "gpt-3.5-turbo"]); + + render( + , + ); + + await waitFor(() => { + expect(fetchAvailableModelsForTeamOrKey).toHaveBeenCalled(); + }); + + const createButton = screen.getByRole("button", { name: /create new team/i }); + act(() => { + fireEvent.click(createButton); + }); + + await waitFor(() => { + expect(screen.getByLabelText(/models/i)).toBeInTheDocument(); + }); + const allProxyModelsOption = screen.queryByText("All Proxy Models"); + expect(allProxyModelsOption).not.toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index cc66a23eb48..83ec28a5177 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -1139,12 +1139,20 @@ const Teams: React.FC = ({ } + rules={[ + { + required: true, + message: "Please select at least one model", + }, + ]} name="models" > - - All Proxy Models - + {(isProxyAdminRole(userRole || "") || userModels.includes("all-proxy-models")) && ( + + All Proxy Models + + )} No Default Models diff --git a/ui/litellm-dashboard/src/components/team/team_info.test.tsx b/ui/litellm-dashboard/src/components/team/team_info.test.tsx index 526f0972d98..17041659cec 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.test.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.test.tsx @@ -1,7 +1,7 @@ +import * as networking from "@/components/networking"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import TeamInfoView from "./team_info"; -import { render, waitFor } from "@testing-library/react"; -import * as networking from "@/components/networking"; // Mock the networking module vi.mock("@/components/networking", () => ({ @@ -61,7 +61,7 @@ describe("TeamInfoView", () => { vi.mocked(networking.getGuardrailsList).mockResolvedValue([]); vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]); - const { getByText } = render( + render( {}} @@ -75,7 +75,87 @@ describe("TeamInfoView", () => { />, ); await waitFor(() => { - expect(getByText("User ID")).toBeInTheDocument(); + expect(screen.queryByText("User ID")).not.toBeNull(); }); }); + + it("should not show all-proxy-models option when user has no access to it", async () => { + vi.mocked(networking.teamInfoCall).mockResolvedValue({ + team_id: "123", + team_info: { + team_alias: "Test Team", + team_id: "123", + organization_id: null, + admins: ["admin@test.com"], + members: ["user1@test.com", "user2@test.com"], + members_with_roles: [ + { + user_id: "user1@test.com", + user_email: "user1@test.com", + role: "member", + spend: 0, + budget_id: "budget1", + }, + ], + metadata: {}, + tpm_limit: null, + rpm_limit: null, + max_budget: null, + budget_duration: null, + models: ["gpt-4"], + blocked: false, + spend: 0, + max_parallel_requests: null, + budget_reset_at: null, + model_id: null, + litellm_model_table: null, + created_at: "2024-01-01T00:00:00Z", + team_member_budget_table: null, + }, + keys: [], + team_memberships: [], + }); + + vi.mocked(networking.getGuardrailsList).mockResolvedValue([]); + vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]); + + render( + {}} + onClose={() => {}} + accessToken="123" + is_team_admin={true} + is_proxy_admin={true} + userModels={["gpt-4", "gpt-3.5-turbo"]} + editTeam={false} + premiumUser={false} + />, + ); + + await waitFor(() => { + expect(screen.getAllByText("Test Team")).not.toBeNull(); + }); + + const settingsTab = screen.getByRole("tab", { name: "Settings" }); + act(() => { + fireEvent.click(settingsTab); + }); + + await waitFor(() => { + expect(screen.getByText("Team Settings")).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + act(() => { + fireEvent.click(editButton); + }); + + await waitFor(() => { + expect(screen.getByLabelText("Models")).toBeInTheDocument(); + }); + + const allProxyModelsOption = screen.queryByText("All Proxy Models"); + expect(allProxyModelsOption).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx index bd52b5aef4d..1c6ba629ef7 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.tsx @@ -1,50 +1,50 @@ -import React, { useState, useEffect } from "react"; -import NumericalInput from "../shared/numerical_input"; +import UserSearchModal from "@/components/common_components/user_search_modal"; import { - Card, - Title, - Text, - Tab, - TabList, - TabGroup, - TabPanel, - TabPanels, - Grid, - Badge, - Button as TremorButton, - TextInput, -} from "@tremor/react"; -import TeamMembersComponent from "./team_member_view"; -import MemberPermissions from "./member_permissions"; -import { - teamInfoCall, - teamMemberDeleteCall, - teamMemberAddCall, - teamMemberUpdateCall, - Member, - teamUpdateCall, getGuardrailsList, + Member, + teamInfoCall, + teamMemberAddCall, + teamMemberDeleteCall, + teamMemberUpdateCall, + teamUpdateCall, } from "@/components/networking"; -import { Button, Form, Input, Select, Switch, message, Tooltip } from "antd"; +import { formatNumberWithCommas } from "@/utils/dataUtils"; +import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils"; import { InfoCircleOutlined } from "@ant-design/icons"; import { ArrowLeftIcon } from "@heroicons/react/outline"; -import MemberModal from "./edit_membership"; -import UserSearchModal from "@/components/common_components/user_search_modal"; +import { + Badge, + Card, + Grid, + Tab, + TabGroup, + TabList, + TabPanel, + TabPanels, + Text, + TextInput, + Title, + Button as TremorButton, +} from "@tremor/react"; +import { Button, Form, Input, message, Select, Switch, Tooltip } from "antd"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import React, { useEffect, useState } from "react"; +import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils"; +import DeleteResourceModal from "../common_components/DeleteResourceModal"; +import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector"; import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; -import ObjectPermissionsView from "../object_permissions_view"; -import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"; +import LoggingSettingsView from "../logging_settings_view"; import MCPServerSelector from "../mcp_server_management/MCPServerSelector"; import MCPToolPermissions from "../mcp_server_management/MCPToolPermissions"; -import { formatNumberWithCommas } from "@/utils/dataUtils"; -import EditLoggingSettings from "./EditLoggingSettings"; -import LoggingSettingsView from "../logging_settings_view"; -import { fetchMCPAccessGroups } from "../networking"; -import { CheckIcon, CopyIcon } from "lucide-react"; -import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils"; import NotificationsManager from "../molecules/notifications_manager"; -import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector"; -import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils"; -import DeleteResourceModal from "../common_components/DeleteResourceModal"; +import { fetchMCPAccessGroups } from "../networking"; +import ObjectPermissionsView from "../object_permissions_view"; +import NumericalInput from "../shared/numerical_input"; +import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"; +import MemberModal from "./edit_membership"; +import EditLoggingSettings from "./EditLoggingSettings"; +import MemberPermissions from "./member_permissions"; +import TeamMembersComponent from "./team_member_view"; export interface TeamMembership { user_id: string; @@ -586,11 +586,17 @@ const TeamInfoView: React.FC = ({ - + From 4b951c3ced340ddfd1d6742b9684229be1769e75 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 26 Nov 2025 17:04:12 -0800 Subject: [PATCH 148/349] Removing flaky tests --- .../organisms/create_key_button.test.tsx | 123 +----------------- 1 file changed, 3 insertions(+), 120 deletions(-) diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx index 34eb1c254b0..08c05f5fadf 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.test.tsx @@ -1,5 +1,5 @@ -import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import CreateKey from "./create_key_button"; const mockKeyCreateCall = vi.fn().mockResolvedValue({ @@ -61,121 +61,4 @@ describe("CreateKey", () => { render(); expect(screen.getByRole("button", { name: /create new key/i })).toBeInTheDocument(); }); - - it("should keep duration as null when nothing is inputted", async () => { - const addKey = vi.fn(); - render(); - - const createButton = screen.getByRole("button", { name: /create new key/i }); - act(() => { - fireEvent.click(createButton); - }); - - await waitFor(() => { - expect(screen.getByText("Key Ownership")).toBeInTheDocument(); - }); - - const keyAliasInput = screen.getByPlaceholderText(""); - act(() => { - fireEvent.change(keyAliasInput, { target: { value: "test-key" } }); - }); - - const modelsSelect = screen.getByPlaceholderText("Select models"); - act(() => { - fireEvent.mouseDown(modelsSelect); - }); - - await waitFor(() => { - const allTeamModelsOption = screen.getByText("All Team Models"); - act(() => { - fireEvent.click(allTeamModelsOption); - }); - }); - - const submitButton = screen.getByRole("button", { name: /create key/i }); - - let formValues: Record = {}; - mockKeyCreateCall.mockImplementation(async (_token: string, _userId: string, values: Record) => { - formValues = values; - return { key: "test-api-key", soft_budget: null }; - }); - - act(() => { - fireEvent.click(submitButton); - }); - - await waitFor(() => { - expect(addKey).toHaveBeenCalled(); - }); - - expect(formValues.duration).toBeNull(); - }, 10000); // 10 second timeout for complex test - - it("should set duration correctly when a value is provided", async () => { - const addKey = vi.fn(); - render(); - - const createButton = screen.getByRole("button", { name: /create new key/i }); - act(() => { - fireEvent.click(createButton); - }); - - await waitFor(() => { - expect(screen.getByText("Key Ownership")).toBeInTheDocument(); - }); - - const keyAliasInput = screen.getByPlaceholderText(""); - act(() => { - fireEvent.change(keyAliasInput, { target: { value: "test-key" } }); - }); - - const modelsSelect = screen.getByPlaceholderText("Select models"); - act(() => { - fireEvent.mouseDown(modelsSelect); - }); - - await waitFor(() => { - const allTeamModelsOption = screen.getByText("All Team Models"); - act(() => { - fireEvent.click(allTeamModelsOption); - }); - }); - - const optionalSettingsAccordion = screen.getByText("Optional Settings"); - act(() => { - fireEvent.click(optionalSettingsAccordion); - }); - - await waitFor(() => { - const keyLifecycleAccordion = screen.getByText("Key Lifecycle"); - act(() => { - fireEvent.click(keyLifecycleAccordion); - }); - }); - - await waitFor(() => { - const durationInput = screen.getByPlaceholderText("e.g., 30d"); - act(() => { - fireEvent.change(durationInput, { target: { value: "30d" } }); - }); - }); - - const submitButton = screen.getByRole("button", { name: /create key/i }); - - let formValues: Record = {}; - mockKeyCreateCall.mockImplementation(async (_token: string, _userId: string, values: Record) => { - formValues = values; - return { key: "test-api-key", soft_budget: null }; - }); - - act(() => { - fireEvent.click(submitButton); - }); - - await waitFor(() => { - expect(addKey).toHaveBeenCalled(); - }); - - expect(formValues.duration).toBe("30d"); - }); -}, 10000); // 10 second timeout for complex test +}); From 831694897e9c33a38206adc1f1be6b6e9611277b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Nov 2025 17:07:30 -0800 Subject: [PATCH 149/349] [Feat] RAG API - QA - allow internal user keys to access api, allow using litellm credentials with API, raise clear exception when RAG API fails (#17169) * allow using a cred with RAG API * add /rag/ingest to llm api routes * add rag endpoints under llm api routes * raise clear exception when RAG API fails * use async methods for bedrock ingest * fix ingestion * fix _create_opensearch_collection * fix qa check and linting --- document.txt | 19 +++ .../llms/vertex_ai/rag_engine/ingestion.py | 9 +- .../vertex_ai/rag_engine/transformation.py | 2 +- ...odel_prices_and_context_window_backup.json | 19 +++ litellm/proxy/_types.py | 6 + litellm/rag/ingestion/base_ingestion.py | 21 +++ litellm/rag/ingestion/bedrock_ingestion.py | 130 +++++++++++++----- litellm/rag/ingestion/openai_ingestion.py | 10 ++ litellm/rag/main.py | 14 +- litellm/rag/utils.py | 2 +- litellm/types/rag.py | 15 ++ 11 files changed, 202 insertions(+), 45 deletions(-) create mode 100644 document.txt diff --git a/document.txt b/document.txt new file mode 100644 index 00000000000..4a91207970a --- /dev/null +++ b/document.txt @@ -0,0 +1,19 @@ +LiteLLM provides a unified interface for calling 100+ different LLM providers. + +Key capabilities: +- Translate requests to provider-specific formats +- Consistent OpenAI-compatible responses +- Retry and fallback logic across deployments +- Proxy server with authentication and rate limiting +- Support for streaming, function calling, and embeddings + +Popular providers supported: +- OpenAI (GPT-4, GPT-3.5) +- Anthropic (Claude) +- AWS Bedrock +- Azure OpenAI +- Google Vertex AI +- Cohere +- And 95+ more + +This allows developers to easily switch between providers without code changes. diff --git a/litellm/llms/vertex_ai/rag_engine/ingestion.py b/litellm/llms/vertex_ai/rag_engine/ingestion.py index 386a38c729a..6b435a46bc3 100644 --- a/litellm/llms/vertex_ai/rag_engine/ingestion.py +++ b/litellm/llms/vertex_ai/rag_engine/ingestion.py @@ -14,21 +14,16 @@ Key differences from OpenAI: from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, List, Optional, Tuple from litellm import get_secret_str from litellm._logging import verbose_logger -from litellm.llms.custom_httpx.http_handler import ( - AsyncHTTPHandler, - get_async_httpx_client, -) from litellm.llms.vertex_ai.rag_engine.transformation import VertexAIRAGTransformation from litellm.rag.ingestion.base_ingestion import BaseRAGIngestion -from litellm.types.llms.custom_http import httpxSpecialProvider if TYPE_CHECKING: from litellm import Router - from litellm.types.rag import RAGChunkingStrategy, RAGIngestOptions + from litellm.types.rag import RAGIngestOptions def _get_str_or_none(value: Any) -> Optional[str]: diff --git a/litellm/llms/vertex_ai/rag_engine/transformation.py b/litellm/llms/vertex_ai/rag_engine/transformation.py index 469bfe990e8..b601da1951a 100644 --- a/litellm/llms/vertex_ai/rag_engine/transformation.py +++ b/litellm/llms/vertex_ai/rag_engine/transformation.py @@ -4,7 +4,7 @@ Transformation utilities for Vertex AI RAG Engine. Handles transforming LiteLLM's unified formats to Vertex AI RAG Engine API format. """ -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Optional from litellm._logging import verbose_logger from litellm.constants import DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 243b5318a3c..0287736af1b 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -20020,6 +20020,25 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "openrouter/anthropic/claude-opus-4.5": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "openrouter", + "max_input_tokens": 200000, + "max_output_tokens": 32000, + "max_tokens": 32000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "openrouter/anthropic/claude-sonnet-4.5": { "input_cost_per_image": 0.0048, "cache_creation_input_token_cost": 3.75e-06, diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index d05454c5784..7e30079e788 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -379,6 +379,11 @@ class LiteLLMRoutes(enum.Enum): ######################################################### passthrough_routes_wildcard = [f"{route}/*" for route in mapped_pass_through_routes] + litellm_native_routes = [ + "/rag/ingest", + "/v1/rag/ingest", + ] + anthropic_routes = [ "/v1/messages", "/v1/messages/count_tokens", @@ -416,6 +421,7 @@ class LiteLLMRoutes(enum.Enum): + passthrough_routes_wildcard + apply_guardrail_routes + mcp_routes + + litellm_native_routes ) info_routes = [ "/key/info", diff --git a/litellm/rag/ingestion/base_ingestion.py b/litellm/rag/ingestion/base_ingestion.py index 538a72b5ca4..20059487b43 100644 --- a/litellm/rag/ingestion/base_ingestion.py +++ b/litellm/rag/ingestion/base_ingestion.py @@ -61,6 +61,26 @@ class BaseRAGIngestion(ABC): ) self.ingest_name = ingest_options.get("name") + # Load credentials from litellm_credential_name if provided in vector_store config + self._load_credentials_from_config() + + def _load_credentials_from_config(self) -> None: + """ + Load credentials from litellm_credential_name if provided in vector_store config. + + This allows users to specify a credential name in the vector_store config + which will be resolved from litellm.credential_list. + """ + from litellm.litellm_core_utils.credential_accessor import CredentialAccessor + + credential_name = self.vector_store_config.get("litellm_credential_name") + if credential_name and litellm.credential_list: + credential_values = CredentialAccessor.get_credential_values(credential_name) + # Merge credentials into vector_store_config (don't overwrite existing values) + for key, value in credential_values.items(): + if key not in self.vector_store_config: + self.vector_store_config[key] = value + @property def custom_llm_provider(self) -> str: """Get the vector store provider.""" @@ -317,5 +337,6 @@ class BaseRAGIngestion(ABC): status="failed", vector_store_id="", file_id=None, + error=str(e), ) diff --git a/litellm/rag/ingestion/bedrock_ingestion.py b/litellm/rag/ingestion/bedrock_ingestion.py index 5fa6145e248..3c880b8849f 100644 --- a/litellm/rag/ingestion/bedrock_ingestion.py +++ b/litellm/rag/ingestion/bedrock_ingestion.py @@ -11,8 +11,8 @@ Supports two modes: from __future__ import annotations +import asyncio import json -import time import uuid from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple @@ -37,6 +37,29 @@ def _get_int(value: Any, default: int) -> int: return int(value) +def _normalize_principal_arn(caller_arn: str, account_id: str) -> str: + """ + Normalize a caller ARN to the format required by OpenSearch data access policies. + + OpenSearch Serverless data access policies require: + - IAM users: arn:aws:iam::account-id:user/user-name + - IAM roles: arn:aws:iam::account-id:role/role-name + + But get_caller_identity() returns for assumed roles: + - arn:aws:sts::account-id:assumed-role/role-name/session-name + + This function converts assumed-role ARNs to the proper IAM role ARN format. + """ + if ":assumed-role/" in caller_arn: + # Extract role name from assumed-role ARN + # Format: arn:aws:sts::ACCOUNT:assumed-role/ROLE-NAME/SESSION-NAME + parts = caller_arn.split("/") + if len(parts) >= 2: + role_name = parts[1] + return f"arn:aws:iam::{account_id}:role/{role_name}" + return caller_arn + + class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): """ Bedrock Knowledge Base RAG ingestion. @@ -99,7 +122,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): # Track resources we create (for cleanup if needed) self._created_resources: Dict[str, Any] = {} - def _ensure_config_initialized(self): + async def _ensure_config_initialized(self): """Lazily initialize KB config - either detect from existing or create new.""" if self._config_initialized: return @@ -109,7 +132,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): self._auto_detect_config() else: # No KB provided - create everything from scratch - self._create_knowledge_base_infrastructure() + await self._create_knowledge_base_infrastructure() self._config_initialized = True @@ -170,7 +193,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): ) self.s3_bucket = self._s3_bucket - def _create_knowledge_base_infrastructure(self): + async def _create_knowledge_base_infrastructure(self): """Create all AWS resources needed for a new Knowledge Base.""" verbose_logger.info("Creating new Bedrock Knowledge Base infrastructure...") @@ -178,26 +201,28 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): unique_id = uuid.uuid4().hex[:8] kb_name = self.ingest_name or f"litellm-kb-{unique_id}" - # Get AWS account ID + # Get AWS account ID and caller ARN (for data access policy) sts = self._get_boto3_client("sts") - account_id = sts.get_caller_identity()["Account"] + caller_identity = sts.get_caller_identity() + account_id = caller_identity["Account"] + caller_arn = caller_identity["Arn"] # Step 1: Create S3 bucket (if not provided) self.s3_bucket = self._s3_bucket or self._create_s3_bucket(unique_id) # Step 2: Create OpenSearch Serverless collection - collection_name, collection_arn = self._create_opensearch_collection( - unique_id, account_id + collection_name, collection_arn = await self._create_opensearch_collection( + unique_id, account_id, caller_arn ) # Step 3: Create OpenSearch index - self._create_opensearch_index(collection_name) + await self._create_opensearch_index(collection_name) # Step 4: Create IAM role for Bedrock - role_arn = self._create_bedrock_role(unique_id, account_id, collection_arn) + role_arn = await self._create_bedrock_role(unique_id, account_id, collection_arn) # Step 5: Create Knowledge Base - self.knowledge_base_id = self._create_knowledge_base( + self.knowledge_base_id = await self._create_knowledge_base( kb_name, role_arn, collection_arn ) @@ -228,8 +253,8 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): verbose_logger.info(f"Created S3 bucket: {bucket_name}") return bucket_name - def _create_opensearch_collection( - self, unique_id: str, account_id: str + async def _create_opensearch_collection( + self, unique_id: str, account_id: str, caller_arn: str ) -> Tuple[str, str]: """Create OpenSearch Serverless collection for vector storage.""" oss = self._get_boto3_client("opensearchserverless") @@ -258,7 +283,16 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): }]), ) - # Create data access policy + # Create data access policy - include both root and actual caller ARN + # This ensures the credentials being used have access to the collection + # Normalize the caller ARN (convert assumed-role ARN to IAM role ARN if needed) + normalized_caller_arn = _normalize_principal_arn(caller_arn, account_id) + verbose_logger.debug(f"Caller ARN: {caller_arn}, Normalized: {normalized_caller_arn}") + + principals = [f"arn:aws:iam::{account_id}:root", normalized_caller_arn] + # Deduplicate in case caller is root + principals = list(set(principals)) + oss.create_access_policy( name=f"{collection_name}-access", type="data", @@ -267,7 +301,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): {"ResourceType": "index", "Resource": [f"index/{collection_name}/*"], "Permission": ["aoss:*"]}, {"ResourceType": "collection", "Resource": [f"collection/{collection_name}"], "Permission": ["aoss:*"]}, ], - "Principal": [f"arn:aws:iam::{account_id}:root"], + "Principal": principals, }]), ) @@ -279,24 +313,29 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): collection_id = response["createCollectionDetail"]["id"] self._created_resources["opensearch_collection"] = collection_name - # Wait for collection to be active + # Wait for collection to be active (use asyncio.sleep to avoid blocking) verbose_logger.debug("Waiting for OpenSearch collection to be active...") for _ in range(60): # 5 min timeout status_response = oss.batch_get_collection(ids=[collection_id]) status = status_response["collectionDetails"][0]["status"] if status == "ACTIVE": break - time.sleep(5) + await asyncio.sleep(5) else: raise TimeoutError("OpenSearch collection did not become active in time") collection_arn = status_response["collectionDetails"][0]["arn"] verbose_logger.info(f"Created OpenSearch collection: {collection_name}") + # Wait for data access policy to propagate before returning + # AWS recommends waiting 60+ seconds for policy propagation + verbose_logger.debug("Waiting for data access policy to propagate (60s)...") + await asyncio.sleep(60) + return collection_name, collection_arn - def _create_opensearch_index(self, collection_name: str): - """Create vector index in OpenSearch collection.""" + async def _create_opensearch_index(self, collection_name: str): + """Create vector index in OpenSearch collection with retry logic.""" from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth @@ -348,10 +387,36 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): }, } - client.indices.create(index=index_name, body=index_body) - verbose_logger.info(f"Created OpenSearch index: {index_name}") + # Retry logic for index creation - data access policy may take time to propagate + max_retries = 8 + retry_delay = 20 # seconds + last_error = None + + for attempt in range(max_retries): + try: + client.indices.create(index=index_name, body=index_body) + verbose_logger.info(f"Created OpenSearch index: {index_name}") + return + except Exception as e: + last_error = e + error_str = str(e) + if "authorization_exception" in error_str.lower() or "security_exception" in error_str.lower(): + verbose_logger.warning( + f"OpenSearch index creation attempt {attempt + 1}/{max_retries} failed due to authorization. " + f"Waiting {retry_delay}s for policy propagation..." + ) + await asyncio.sleep(retry_delay) + else: + # Non-auth error, raise immediately + raise + + # All retries exhausted + raise RuntimeError( + f"Failed to create OpenSearch index after {max_retries} attempts. " + f"Data access policy may not have propagated. Last error: {last_error}" + ) - def _create_bedrock_role( + async def _create_bedrock_role( self, unique_id: str, account_id: str, collection_arn: str ) -> str: """Create IAM role for Bedrock KB.""" @@ -408,13 +473,13 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): PolicyDocument=json.dumps(permissions_policy), ) - # Wait for role to propagate - time.sleep(10) + # Wait for role to propagate (use asyncio.sleep to avoid blocking) + await asyncio.sleep(10) verbose_logger.info(f"Created IAM role: {role_arn}") return role_arn - def _create_knowledge_base( + async def _create_knowledge_base( self, kb_name: str, role_arn: str, collection_arn: str ) -> str: """Create Bedrock Knowledge Base.""" @@ -447,14 +512,14 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): kb_id = response["knowledgeBase"]["knowledgeBaseId"] self._created_resources["knowledge_base"] = kb_id - # Wait for KB to be active + # Wait for KB to be active (use asyncio.sleep to avoid blocking) verbose_logger.debug("Waiting for Knowledge Base to be active...") for _ in range(30): kb_status = bedrock_agent.get_knowledge_base(knowledgeBaseId=kb_id) status = kb_status["knowledgeBase"]["status"] if status == "ACTIVE": break - time.sleep(2) + await asyncio.sleep(2) else: raise TimeoutError("Knowledge Base did not become active in time") @@ -555,7 +620,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): Tuple of (knowledge_base_id, file_key) """ # Auto-detect data source and S3 bucket if needed - self._ensure_config_initialized() + await self._ensure_config_initialized() if not file_content or not filename: verbose_logger.warning("No file content or filename provided for Bedrock ingestion") @@ -587,10 +652,11 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): job_id = ingestion_response["ingestionJob"]["ingestionJobId"] verbose_logger.info(f"Started ingestion job: {job_id}") - # Step 3: Wait for ingestion (optional) + # Step 3: Wait for ingestion (optional) - use asyncio.sleep to avoid blocking if self.wait_for_ingestion: - start_time = time.time() - while time.time() - start_time < self.ingestion_timeout: + import time as time_module + start_time = time_module.time() + while time_module.time() - start_time < self.ingestion_timeout: job_status = bedrock_agent.get_ingestion_job( knowledgeBaseId=self.knowledge_base_id, dataSourceId=self.data_source_id, @@ -610,7 +676,7 @@ class BedrockRAGIngestion(BaseRAGIngestion, BaseAWSLLM): verbose_logger.error(f"Ingestion failed: {failure_reasons}") break elif status in ("STARTING", "IN_PROGRESS"): - time.sleep(2) + await asyncio.sleep(2) else: verbose_logger.warning(f"Unknown ingestion status: {status}") break diff --git a/litellm/rag/ingestion/openai_ingestion.py b/litellm/rag/ingestion/openai_ingestion.py index 034ad38d466..33fe8c06ec7 100644 --- a/litellm/rag/ingestion/openai_ingestion.py +++ b/litellm/rag/ingestion/openai_ingestion.py @@ -78,6 +78,10 @@ class OpenAIRAGIngestion(BaseRAGIngestion): vector_store_id = self.vector_store_config.get("vector_store_id") ttl_days = self.vector_store_config.get("ttl_days") + # Get credentials from vector_store_config (loaded from litellm_credential_name if provided) + api_key = self.vector_store_config.get("api_key") + api_base = self.vector_store_config.get("api_base") + # Create vector store if not provided if not vector_store_id: expires_after = {"anchor": "last_active_at", "days": ttl_days} if ttl_days else None @@ -85,6 +89,8 @@ class OpenAIRAGIngestion(BaseRAGIngestion): name=self.ingest_name or "litellm-rag-ingest", custom_llm_provider="openai", expires_after=expires_after, + api_key=api_key, + api_base=api_base, ) vector_store_id = create_response.get("id") @@ -96,6 +102,8 @@ class OpenAIRAGIngestion(BaseRAGIngestion): file=(filename, file_content, content_type or "application/octet-stream"), purpose="assistants", custom_llm_provider="openai", + api_key=api_key, + api_base=api_base, ) result_file_id = file_response.id @@ -105,6 +113,8 @@ class OpenAIRAGIngestion(BaseRAGIngestion): file_id=result_file_id, custom_llm_provider="openai", chunking_strategy=cast(Optional[Dict[str, Any]], self.chunking_strategy), + api_key=api_key, + api_base=api_base, ) return vector_store_id, result_file_id diff --git a/litellm/rag/main.py b/litellm/rag/main.py index ef1a512998b..e7a9d3a241f 100644 --- a/litellm/rag/main.py +++ b/litellm/rag/main.py @@ -12,7 +12,7 @@ __all__ = ["ingest", "aingest"] import asyncio import contextvars from functools import partial -from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Tuple, Type, Union import httpx @@ -84,7 +84,7 @@ async def _execute_ingest_pipeline( provider = vector_store_config.get("custom_llm_provider", "openai") # Get provider-specific ingestion class - ingestion_class = get_rag_ingestion_class(provider) + ingestion_class = get_ingestion_class(provider) # Create ingestion instance ingestion = ingestion_class( @@ -127,7 +127,10 @@ async def aingest( ```python response = await litellm.aingest( ingest_options={ - "vector_store": {"custom_llm_provider": "openai"} + "vector_store": { + "custom_llm_provider": "openai", + "litellm_credential_name": "my-openai-creds", # optional + } }, file_url="https://example.com/doc.pdf", ) @@ -193,7 +196,10 @@ def ingest( ```python response = litellm.ingest( ingest_options={ - "vector_store": {"custom_llm_provider": "openai"} + "vector_store": { + "custom_llm_provider": "openai", + "litellm_credential_name": "my-openai-creds", # optional + } }, file_data=("doc.txt", b"Hello world", "text/plain"), ) diff --git a/litellm/rag/utils.py b/litellm/rag/utils.py index 2a0f3008dd7..e8ab9c75173 100644 --- a/litellm/rag/utils.py +++ b/litellm/rag/utils.py @@ -4,7 +4,7 @@ RAG utility functions. Provides provider configuration utilities similar to ProviderConfigManager. """ -from typing import TYPE_CHECKING, Optional, Type +from typing import TYPE_CHECKING, Type if TYPE_CHECKING: from litellm.rag.ingestion.base_ingestion import BaseRAGIngestion diff --git a/litellm/types/rag.py b/litellm/types/rag.py index 51ac6eea043..7a964931af1 100644 --- a/litellm/types/rag.py +++ b/litellm/types/rag.py @@ -41,12 +41,20 @@ class OpenAIVectorStoreOptions(TypedDict, total=False): Example (use existing): {"custom_llm_provider": "openai", "vector_store_id": "vs_xxx"} + + Example (with credentials): + {"custom_llm_provider": "openai", "litellm_credential_name": "my-openai-creds"} """ custom_llm_provider: Literal["openai"] vector_store_id: Optional[str] # Existing VS ID (auto-creates if not provided) ttl_days: Optional[int] # Time-to-live in days for indexed content + # Credentials (loaded from litellm.credential_list if litellm_credential_name is provided) + litellm_credential_name: Optional[str] # Credential name to load from litellm.credential_list + api_key: Optional[str] # Direct API key (alternative to litellm_credential_name) + api_base: Optional[str] # Direct API base (alternative to litellm_credential_name) + class BedrockVectorStoreOptions(TypedDict, total=False): """ @@ -58,6 +66,9 @@ class BedrockVectorStoreOptions(TypedDict, total=False): Example (use existing KB): {"custom_llm_provider": "bedrock", "vector_store_id": "KB_ID"} + Example (with credentials): + {"custom_llm_provider": "bedrock", "litellm_credential_name": "my-aws-creds"} + Auto-creation creates: S3 bucket, OpenSearch Serverless collection, IAM role, Knowledge Base, and Data Source. """ @@ -73,6 +84,9 @@ class BedrockVectorStoreOptions(TypedDict, total=False): wait_for_ingestion: Optional[bool] # Wait for completion (default: False - returns immediately) ingestion_timeout: Optional[int] # Timeout in seconds if wait_for_ingestion=True (default: 300) + # Credentials (loaded from litellm.credential_list if litellm_credential_name is provided) + litellm_credential_name: Optional[str] # Credential name to load from litellm.credential_list + # AWS auth (uses BaseAWSLLM) aws_access_key_id: Optional[str] aws_secret_access_key: Optional[str] @@ -160,6 +174,7 @@ class RAGIngestResponse(TypedDict, total=False): status: Literal["completed", "in_progress", "failed"] vector_store_id: str # The vector store ID (created or existing) file_id: Optional[str] # The file ID in the vector store + error: Optional[str] # Error message if status is "failed" From f0e5921bbdce61e5e5074fc663f11107f525bbae Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 26 Nov 2025 17:09:07 -0800 Subject: [PATCH 150/349] Add emoji for exact text match --- .../proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 6c1665a3ae1..5b9a9ab133c 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -12,7 +12,9 @@ test.describe("User Info View", () => { page, }) => { // Wait for loading state to disappear - await page.waitForSelector('text="Loading users..."', { state: "hidden" }); + await page.waitForSelector('text="🚅 Loading users..."', { + state: "hidden", + }); // Wait for users table to load await page.waitForSelector("table"); From 0346d1ea23f6bdc301364d93c111a4663d802577 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 17:08:06 -0800 Subject: [PATCH 151/349] fix --- tests/image_gen_tests/test_image_generation.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/image_gen_tests/test_image_generation.py b/tests/image_gen_tests/test_image_generation.py index add60c755be..1a2d54d203c 100644 --- a/tests/image_gen_tests/test_image_generation.py +++ b/tests/image_gen_tests/test_image_generation.py @@ -135,22 +135,6 @@ class TestVertexAIGeminiImageGeneration(BaseImageGenTest): } -class TestVertexAIGemini3ProImageGeneration(BaseImageGenTest): - """Test Gemini 3 Pro image generation model""" - def get_base_image_generation_call_args(self) -> dict: - # comment this when running locally - load_vertex_ai_credentials() - - litellm.in_memory_llm_clients_cache = InMemoryCache() - return { - "model": "vertex_ai/gemini-3-pro-image-preview", - "vertex_ai_project": "pathrise-convert-1606954137718", - "vertex_ai_location": "us-central1", - "n": 1, - "size": "1024x1024", - } - - class TestBedrockNovaCanvasTextToImage(BaseImageGenTest): def get_base_image_generation_call_args(self) -> dict: litellm.in_memory_llm_clients_cache = InMemoryCache() From 5cfcc98d2ea7c0bf834f60d699e9b4fbf49a5010 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 17:36:37 -0800 Subject: [PATCH 152/349] fix img gen --- litellm/images/main.py | 7 ++++- .../vertex_gemini_transformation.py | 27 ++++++++++++------- .../vertex_imagen_transformation.py | 27 ++++++++++++------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/litellm/images/main.py b/litellm/images/main.py index 878fce83f17..eacd4778299 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -10,7 +10,8 @@ from litellm import client, exception_type, get_litellm_params from litellm.constants import DEFAULT_IMAGE_ENDPOINT_MODEL from litellm.constants import request_timeout as DEFAULT_REQUEST_TIMEOUT from litellm.exceptions import LiteLLMUnknownProvider -from litellm.litellm_core_utils.litellm_logging import Logging, Logging as LiteLLMLoggingObj +from litellm.litellm_core_utils.litellm_logging import Logging +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.mock_functions import mock_image_generation from litellm.llms.base_llm import BaseImageEditConfig, BaseImageGenerationConfig from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler @@ -351,6 +352,10 @@ def image_generation( # noqa: PLR0915 f"image generation config is not supported for {custom_llm_provider}" ) + # Resolve api_base from litellm.api_base if not explicitly provided + _api_base = api_base or litellm.api_base + litellm_params_dict["api_base"] = _api_base + return llm_http_handler.image_generation_handler( api_key=api_key, model=model, diff --git a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py index c863c2f5691..416c611d863 100644 --- a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py +++ b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py @@ -122,6 +122,16 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): """ Get the complete URL for Vertex AI Gemini generateContent API """ + # Use the model name as provided, handling vertex_ai prefix + model_name = model + if model.startswith("vertex_ai/"): + model_name = model.replace("vertex_ai/", "") + + # If a custom api_base is provided, use it directly + # This allows users to use proxies or mock endpoints + if api_base: + return api_base.rstrip("/") + # First check litellm_params (where vertex_ai_project/vertex_ai_location are passed) # then fall back to environment variables and other sources vertex_project = self.safe_get_vertex_ai_project(litellm_params) or self._resolve_vertex_project() @@ -130,15 +140,7 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): if not vertex_project or not vertex_location: raise ValueError("vertex_project and vertex_location are required for Vertex AI") - # Use the model name as provided, handling vertex_ai prefix - model_name = model - if model.startswith("vertex_ai/"): - model_name = model.replace("vertex_ai/", "") - - if api_base: - base_url = api_base.rstrip("/") - else: - base_url = f"https://{vertex_location}-aiplatform.googleapis.com" + base_url = f"https://{vertex_location}-aiplatform.googleapis.com" return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:generateContent" @@ -153,6 +155,13 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): api_base: Optional[str] = None, ) -> dict: headers = headers or {} + + # If a custom api_base is provided, skip credential validation + # This allows users to use proxies or mock endpoints without needing Vertex AI credentials + _api_base = litellm_params.get("api_base") or api_base + if _api_base is not None: + return headers + # First check litellm_params (where vertex_ai_project/vertex_ai_credentials are passed) # then fall back to environment variables and other sources vertex_project = self.safe_get_vertex_ai_project(litellm_params) or self._resolve_vertex_project() diff --git a/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py index 4d86f7ba36d..33f416f9ca8 100644 --- a/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py +++ b/litellm/llms/vertex_ai/image_generation/vertex_imagen_transformation.py @@ -122,6 +122,16 @@ class VertexAIImagenImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): """ Get the complete URL for Vertex AI Imagen predict API """ + # Use the model name as provided, handling vertex_ai prefix + model_name = model + if model.startswith("vertex_ai/"): + model_name = model.replace("vertex_ai/", "") + + # If a custom api_base is provided, use it directly + # This allows users to use proxies or mock endpoints + if api_base: + return api_base.rstrip("/") + # First check litellm_params (where vertex_ai_project/vertex_ai_location are passed) # then fall back to environment variables and other sources vertex_project = self.safe_get_vertex_ai_project(litellm_params) or self._resolve_vertex_project() @@ -130,15 +140,7 @@ class VertexAIImagenImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): if not vertex_project or not vertex_location: raise ValueError("vertex_project and vertex_location are required for Vertex AI") - # Use the model name as provided, handling vertex_ai prefix - model_name = model - if model.startswith("vertex_ai/"): - model_name = model.replace("vertex_ai/", "") - - if api_base: - base_url = api_base.rstrip("/") - else: - base_url = f"https://{vertex_location}-aiplatform.googleapis.com" + base_url = f"https://{vertex_location}-aiplatform.googleapis.com" return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:predict" @@ -153,6 +155,13 @@ class VertexAIImagenImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): api_base: Optional[str] = None, ) -> dict: headers = headers or {} + + # If a custom api_base is provided, skip credential validation + # This allows users to use proxies or mock endpoints without needing Vertex AI credentials + _api_base = litellm_params.get("api_base") or api_base + if _api_base is not None: + return headers + # First check litellm_params (where vertex_ai_project/vertex_ai_credentials are passed) # then fall back to environment variables and other sources vertex_project = self.safe_get_vertex_ai_project(litellm_params) or self._resolve_vertex_project() From 7e3f3c6f657f2a9ca57c6e14cc376b22604336fe Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 26 Nov 2025 18:16:52 -0800 Subject: [PATCH 153/349] Migrate /public/provider/fields to react query --- .../hooks/providers/useProviderFields.ts | 14 + .../add_model/add_model_tab.test.tsx | 297 ++++++++++++------ .../components/add_model/add_model_tab.tsx | 81 ++--- .../provider_specific_fields.test.tsx | 121 ++++--- .../add_model/provider_specific_fields.tsx | 104 +++--- 5 files changed, 359 insertions(+), 258 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.ts new file mode 100644 index 00000000000..5d219f3183e --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.ts @@ -0,0 +1,14 @@ +import { getProviderCreateMetadata, ProviderCreateInfo } from "@/components/networking"; +import { useQuery } from "@tanstack/react-query"; +import { createQueryKeys } from "../common/queryKeysFactory"; + +const providerFieldsKeys = createQueryKeys("providerFields"); + +export const useProviderFields = () => { + return useQuery({ + queryKey: providerFieldsKeys.list({}), + queryFn: async () => await getProviderCreateMetadata(), + staleTime: 24 * 60 * 60 * 1000, // 24 hours - data rarely changes + gcTime: 24 * 60 * 60 * 1000, // 24 hours - keep in cache for 24 hours + }); +}; diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx index 87c5b4c0aa0..201bbb3c180 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx @@ -1,13 +1,13 @@ -import { render, renderHook, waitFor } from "@testing-library/react"; -import { describe, it, vi, expect } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, renderHook, screen } from "@testing-library/react"; import { Form } from "antd"; -import AddModelTab from "./add_model_tab"; -import { Providers } from "../provider_info_helpers"; +import type { UploadProps } from "antd/es/upload"; +import { describe, expect, it, vi } from "vitest"; import type { Team } from "../key_team_helpers/key_list"; import type { CredentialItem } from "../networking"; -import type { UploadProps } from "antd/es/upload"; +import { Providers } from "../provider_info_helpers"; +import AddModelTab from "./add_model_tab"; -// Mock the networking module vi.mock("../networking", async () => { const actual = await vi.importActual("../networking"); return { @@ -19,6 +19,12 @@ vi.mock("../networking", async () => { modelAvailableCall: vi.fn().mockResolvedValue({ data: [{ id: "model-group-1" }, { id: "model-group-2" }], }), + modelHubCall: vi.fn().mockResolvedValue({ + data: [ + { model_group: "gpt-4", mode: "chat" }, + { model_group: "gpt-3.5-turbo", mode: "chat" }, + ], + }), getProviderCreateMetadata: vi.fn().mockResolvedValue([ { provider: "OpenAI", @@ -31,91 +37,198 @@ vi.mock("../networking", async () => { }; }); -describe("Add Model Tab", () => { - it( - "should render", - async () => { - // Create a form instance using renderHook - const { result } = renderHook(() => Form.useForm()); - const [form] = result.current; - - // Mock functions - const handleOk = vi.fn(); - const setSelectedProvider = vi.fn(); - const setProviderModelsFn = vi.fn(); - const getPlaceholder = vi.fn((provider: Providers) => `Enter ${provider} model name`); - const setShowAdvancedSettings = vi.fn(); - - // Mock data - const selectedProvider = Providers.OpenAI; - const providerModels = ["gpt-4", "gpt-3.5-turbo"]; - const showAdvancedSettings = false; - - const teams: Team[] = [ - { - team_id: "team-1", - team_alias: "Test Team", - models: ["gpt-4"], - max_budget: 100, - budget_duration: "monthly", - tpm_limit: null, - rpm_limit: null, - organization_id: "org-1", - created_at: "2024-01-01T00:00:00Z", - keys: [], - members_with_roles: [], - }, - ]; - - const credentials: CredentialItem[] = [ - { - credential_name: "test-credential", - credential_values: {}, - credential_info: { - custom_llm_provider: "openai", - description: "Test credential", - }, - }, - ]; - - const uploadProps: UploadProps = { - beforeUpload: () => false, - showUploadList: false, - }; - - const accessToken = "test-access-token"; - const userRole = "Admin"; - const premiumUser = true; - - const { container, findByText } = render( - , - ); - - // Wait for the tabs to render which indicates the component loaded - await waitFor( - () => { - const tabs = container.querySelectorAll('[role="tab"]'); - expect(tabs.length).toBeGreaterThan(0); - }, - { timeout: 10000 }, - ); +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, }, - 15000, - ); + }); + +const createTestProps = () => { + const { result } = renderHook(() => Form.useForm()); + const [form] = result.current; + + const handleOk = vi.fn(); + const setSelectedProvider = vi.fn(); + const setProviderModelsFn = vi.fn(); + const getPlaceholder = vi.fn((provider: Providers) => `Enter ${provider} model name`); + const setShowAdvancedSettings = vi.fn(); + + const selectedProvider = Providers.OpenAI; + const providerModels = ["gpt-4", "gpt-3.5-turbo"]; + const showAdvancedSettings = false; + + const teams: Team[] = [ + { + team_id: "team-1", + team_alias: "Test Team", + models: ["gpt-4"], + max_budget: 100, + budget_duration: "monthly", + tpm_limit: null, + rpm_limit: null, + organization_id: "org-1", + created_at: "2024-01-01T00:00:00Z", + keys: [], + members_with_roles: [], + }, + ]; + + const credentials: CredentialItem[] = [ + { + credential_name: "test-credential", + credential_values: {}, + credential_info: { + custom_llm_provider: "openai", + description: "Test credential", + }, + }, + ]; + + const uploadProps: UploadProps = { + beforeUpload: () => false, + showUploadList: false, + }; + + return { + form, + handleOk, + setSelectedProvider, + setProviderModelsFn, + getPlaceholder, + setShowAdvancedSettings, + selectedProvider, + providerModels, + showAdvancedSettings, + teams, + credentials, + uploadProps, + accessToken: "test-access-token", + userRole: "Admin", + premiumUser: true, + }; +}; + +describe("Add Model Tab", () => { + it("should render", async () => { + const props = createTestProps(); + const queryClient = createQueryClient(); + + render( + + + , + ); + + expect(await screen.findByRole("tab", { name: "Add Model" })).toBeInTheDocument(); + }); + + it("should display both Add Model and Add Auto Router tabs", async () => { + const props = createTestProps(); + const queryClient = createQueryClient(); + + render( + + + , + ); + + expect(await screen.findByRole("tab", { name: "Add Model" })).toBeInTheDocument(); + expect(await screen.findByRole("tab", { name: "Add Auto Router" })).toBeInTheDocument(); + }); + + it("should display provider selection field", async () => { + const props = createTestProps(); + const queryClient = createQueryClient(); + + render( + + + , + ); + + expect(await screen.findByText("Provider")).toBeInTheDocument(); + }); + + it("should display Test Connect and Add Model buttons", async () => { + const props = createTestProps(); + const queryClient = createQueryClient(); + + render( + + + , + ); + + const testConnectButtons = await screen.findAllByRole("button", { name: "Test Connect" }); + expect(testConnectButtons.length).toBeGreaterThan(0); + expect(await screen.findByRole("button", { name: "Add Model" })).toBeInTheDocument(); + }, 10000); // 10 seconds timeout for complex logic }); diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx index 4efcd7be907..b2e1dec2827 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx @@ -1,31 +1,29 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd"; +import { useProviderFields } from "@/app/(dashboard)/hooks/providers/useProviderFields"; +import { all_admin_roles } from "@/utils/roles"; +import { Switch, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react"; import type { FormInstance } from "antd"; +import { Select as AntdSelect, Button, Card, Col, Form, Modal, Row, Tooltip, Typography } from "antd"; import type { UploadProps } from "antd/es/upload"; -import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; -import LiteLLMModelNameField from "./litellm_model_name"; -import ConditionalPublicModelName from "./conditional_public_model_name"; -import ProviderSpecificFields from "./provider_specific_fields"; -import AdvancedSettings from "./advanced_settings"; -import { Providers, providerLogoMap } from "../provider_info_helpers"; +import React, { useEffect, useMemo, useState } from "react"; +import TeamDropdown from "../common_components/team_dropdown"; import type { Team } from "../key_team_helpers/key_list"; import { type CredentialItem, type ProviderCreateInfo, getGuardrailsList, - getProviderCreateMetadata, modelAvailableCall, tagListCall, } from "../networking"; -import ConnectionErrorDisplay from "./model_connection_test"; -import { TEST_MODES } from "./add_model_modes"; -import { Row, Col } from "antd"; -import { Text, Switch } from "@tremor/react"; -import TeamDropdown from "../common_components/team_dropdown"; -import { all_admin_roles } from "@/utils/roles"; -import AddAutoRouterTab from "./add_auto_router_tab"; -import { handleAddAutoRouterSubmit } from "./handle_add_auto_router_submit"; +import { Providers, providerLogoMap } from "../provider_info_helpers"; import { Tag } from "../tag_management/types"; +import AddAutoRouterTab from "./add_auto_router_tab"; +import { TEST_MODES } from "./add_model_modes"; +import AdvancedSettings from "./advanced_settings"; +import ConditionalPublicModelName from "./conditional_public_model_name"; +import { handleAddAutoRouterSubmit } from "./handle_add_auto_router_submit"; +import LiteLLMModelNameField from "./litellm_model_name"; +import ConnectionErrorDisplay from "./model_connection_test"; +import ProviderSpecificFields from "./provider_specific_fields"; interface AddModelTabProps { form: FormInstance; // For the Add Model tab @@ -76,9 +74,11 @@ const AddModelTab: React.FC = ({ const [connectionTestId, setConnectionTestId] = useState(""); // Provider metadata for driving the provider select from backend config - const [providerMetadata, setProviderMetadata] = useState(null); - const [isProviderMetadataLoading, setIsProviderMetadataLoading] = useState(false); - const [providerMetadataError, setProviderMetadataError] = useState(null); + const { + data: providerMetadata, + isLoading: isProviderMetadataLoading, + error: providerMetadataError, + } = useProviderFields(); useEffect(() => { const fetchGuardrails = async () => { @@ -107,37 +107,6 @@ const AddModelTab: React.FC = ({ fetchTags(); }, [accessToken]); - useEffect(() => { - let isMounted = true; - - const fetchProviderMetadata = async () => { - setIsProviderMetadataLoading(true); - setProviderMetadataError(null); - try { - const metadata = await getProviderCreateMetadata(); - if (!isMounted) { - return; - } - setProviderMetadata(metadata); - } catch (error) { - console.error("Failed to fetch provider metadata:", error); - if (isMounted) { - setProviderMetadataError("Failed to load providers"); - } - } finally { - if (isMounted) { - setIsProviderMetadataLoading(false); - } - } - }; - - fetchProviderMetadata(); - - return () => { - isMounted = false; - }; - }, []); - // Test connection when button is clicked const handleTestConnection = async () => { setIsTestingConnection(true); @@ -168,6 +137,12 @@ const AddModelTab: React.FC = ({ return [...providerMetadata].sort((a, b) => a.provider_display_name.localeCompare(b.provider_display_name)); }, [providerMetadata]); + const providerMetadataErrorText = providerMetadataError + ? providerMetadataError instanceof Error + ? providerMetadataError.message + : "Failed to load providers" + : null; + const isAdmin = all_admin_roles.includes(userRole); const handleAutoRouterOk = () => { @@ -232,9 +207,9 @@ const AddModelTab: React.FC = ({ }); }} > - {providerMetadataError && sortedProviderMetadata.length === 0 && ( + {providerMetadataErrorText && sortedProviderMetadata.length === 0 && ( - {providerMetadataError} + {providerMetadataErrorText} )} {sortedProviderMetadata.map((providerInfo) => { diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx index ab9b3e92e98..4590121acf2 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx @@ -1,6 +1,7 @@ -import { render, waitFor } from "@testing-library/react"; -import { describe, it, expect, beforeAll, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; import { Form } from "antd"; +import { beforeAll, describe, expect, it, vi } from "vitest"; import { Providers } from "../provider_info_helpers"; import ProviderSpecificFields from "./provider_specific_fields"; @@ -97,7 +98,6 @@ vi.mock("../networking", async () => { }; }); -// Mock window.matchMedia for Ant Design components beforeAll(() => { Object.defineProperty(window, "matchMedia", { writable: true, @@ -105,8 +105,8 @@ beforeAll(() => { matches: false, media: query, onchange: null, - addListener: () => {}, // deprecated - removeListener: () => {}, // deprecated + addListener: () => {}, + removeListener: () => {}, addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => false, @@ -114,80 +114,105 @@ beforeAll(() => { }); }); +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }); + describe("ProviderSpecificFields", () => { - it("should render the provider specific fields for OpenAI", async () => { - const { getByLabelText, getByPlaceholderText } = render( - - - , + it("should render", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, ); await waitFor(() => { - // Check for the API Base text input - const apiBaseInput = getByPlaceholderText("https://api.openai.com/v1"); + expect(screen.getByLabelText("OpenAI API Key")).toBeInTheDocument(); + }); + }); + + it("should render the provider specific fields for OpenAI", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, + ); + + await waitFor(() => { + const apiKeyLabel = screen.getByLabelText("OpenAI API Key"); + expect(apiKeyLabel).toBeInTheDocument(); + + const apiBaseInput = screen.getByPlaceholderText("https://api.openai.com/v1"); expect(apiBaseInput).toBeInTheDocument(); expect(apiBaseInput).toHaveAttribute("type", "text"); - // Check for Organization field - const orgInput = getByPlaceholderText("[OPTIONAL] my-unique-org"); + const orgInput = screen.getByPlaceholderText("[OPTIONAL] my-unique-org"); expect(orgInput).toBeInTheDocument(); - - // Check for API Key field - const apiKeyLabel = getByLabelText("OpenAI API Key"); - expect(apiKeyLabel).toBeInTheDocument(); }); }); it("should render the provider specific fields for vLLM", async () => { - const { getByLabelText, getByPlaceholderText } = render( -
- - , + const queryClient = createQueryClient(); + render( + +
+ + +
, ); await waitFor(() => { - const apiBaseInput = getByPlaceholderText("https://..."); + const apiKeyLabel = screen.getByLabelText("vLLM API Key"); + expect(apiKeyLabel).toBeInTheDocument(); + + const apiBaseInput = screen.getByPlaceholderText("https://..."); expect(apiBaseInput).toBeInTheDocument(); expect(apiBaseInput).toHaveAttribute("type", "text"); - - // Check for API Key field - const apiKeyLabel = getByLabelText("vLLM API Key"); - expect(apiKeyLabel).toBeInTheDocument(); }); }); it("should render the provider specific fields for Azure", async () => { - const { getByLabelText, getByPlaceholderText } = render( -
- - , + const queryClient = createQueryClient(); + render( + +
+ + +
, ); await waitFor(() => { - // Check for API Base field - const apiBaseInput = getByPlaceholderText("https://..."); - expect(apiBaseInput).toBeInTheDocument(); - expect(apiBaseInput).toHaveAttribute("type", "text"); - - // Check for API Version field - const apiVersionInput = getByPlaceholderText("2023-07-01-preview"); - expect(apiVersionInput).toBeInTheDocument(); - - // Check for Base Model field - const baseModelInput = getByPlaceholderText("azure/gpt-3.5-turbo"); - expect(baseModelInput).toBeInTheDocument(); - - // Check for API Key field - const apiKeyInput = getByLabelText("Azure API Key"); + const apiKeyInput = screen.getByLabelText("Azure API Key"); expect(apiKeyInput).toBeInTheDocument(); expect(apiKeyInput).toHaveAttribute("type", "password"); expect(apiKeyInput).toHaveAttribute("placeholder", "Enter your Azure API Key"); - // Check for Azure AD Token field - const azureAdTokenInput = getByLabelText("Azure AD Token"); + const azureAdTokenInput = screen.getByLabelText("Azure AD Token"); expect(azureAdTokenInput).toBeInTheDocument(); expect(azureAdTokenInput).toHaveAttribute("type", "password"); expect(azureAdTokenInput).toHaveAttribute("placeholder", "Enter your Azure AD Token"); + + const apiBaseInput = screen.getByPlaceholderText("https://..."); + expect(apiBaseInput).toBeInTheDocument(); + expect(apiBaseInput).toHaveAttribute("type", "text"); + + const apiVersionInput = screen.getByPlaceholderText("2023-07-01-preview"); + expect(apiVersionInput).toBeInTheDocument(); + + const baseModelInput = screen.getByPlaceholderText("azure/gpt-3.5-turbo"); + expect(baseModelInput).toBeInTheDocument(); }); }); }); diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx index c1b17ff4419..892e9e0197e 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx @@ -1,15 +1,10 @@ -import React from "react"; -import { Form, Select } from "antd"; -import { TextInput, Text } from "@tremor/react"; -import { Row, Col, Typography, Button as Button2, Upload, UploadProps } from "antd"; +import { useProviderFields } from "@/app/(dashboard)/hooks/providers/useProviderFields"; import { UploadOutlined } from "@ant-design/icons"; +import { Text, TextInput } from "@tremor/react"; +import { Button as Button2, Col, Form, Row, Select, Typography, Upload, UploadProps } from "antd"; +import React from "react"; +import { CredentialItem, ProviderCredentialFieldMetadata } from "../networking"; import { provider_map, Providers } from "../provider_info_helpers"; -import { - CredentialItem, - ProviderCreateInfo, - ProviderCredentialFieldMetadata, - getProviderCreateMetadata, -} from "../networking"; const { Link } = Typography; interface ProviderSpecificFieldsProps { @@ -99,65 +94,42 @@ const ProviderSpecificFields: React.FC = ({ selecte const selectedProviderEnum = Providers[selectedProvider as keyof typeof Providers] as Providers; const form = Form.useFormInstance(); // Get form instance from context - const [providerMetadata, setProviderMetadata] = React.useState(null); - const [isLoading, setIsLoading] = React.useState(false); - const [loadError, setLoadError] = React.useState(null); + const { data: providerMetadata, isLoading, error: loadError } = useProviderFields(); + // Memoize the expensive cache computation + const cacheEntries = React.useMemo(() => { + if (!providerMetadata) { + return null; + } + + // Compute cache entries keyed by provider display name and identifiers + const entries: Record = {}; + providerMetadata.forEach((providerInfo) => { + const displayName = providerInfo.provider_display_name; + const mappedFields = providerInfo.credential_fields.map(mapFieldMetadataToUiField); + + // Primary key: human-readable display name + entries[displayName] = mappedFields; + + // Also cache by backend identifiers so lookups by provider slug work + if (providerInfo.provider) { + entries[providerInfo.provider] = mappedFields; + } + if (providerInfo.litellm_provider) { + entries[providerInfo.litellm_provider] = mappedFields; + } + }); + return entries; + }, [providerMetadata]); + + // Sync memoized cache entries to module-level cache React.useEffect(() => { - const hasCachedFields = Object.keys(providerFieldsByDisplayName).length > 0; - if (hasCachedFields) { - // We already have fields cached globally; no need to refetch. - // This is important so we can reuse credential field definitions - // across mounts and in non-React helpers. + if (!cacheEntries) { return; } - let isMounted = true; - - const fetchProviderFields = async () => { - setIsLoading(true); - setLoadError(null); - try { - const metadata = await getProviderCreateMetadata(); - if (!isMounted) { - return; - } - setProviderMetadata(metadata); - - // Populate cache keyed by provider display name and identifiers - metadata.forEach((providerInfo) => { - const displayName = providerInfo.provider_display_name; - const mappedFields = providerInfo.credential_fields.map(mapFieldMetadataToUiField); - - // Primary key: human-readable display name - providerFieldsByDisplayName[displayName] = mappedFields; - - // Also cache by backend identifiers so lookups by provider slug work - if (providerInfo.provider) { - providerFieldsByDisplayName[providerInfo.provider] = mappedFields; - } - if (providerInfo.litellm_provider) { - providerFieldsByDisplayName[providerInfo.litellm_provider] = mappedFields; - } - }); - } catch (error) { - console.error("Failed to load provider credential fields:", error); - if (isMounted) { - setLoadError("Failed to load provider credential fields"); - } - } finally { - if (isMounted) { - setIsLoading(false); - } - } - }; - - fetchProviderFields(); - - return () => { - isMounted = false; - }; - }, []); + Object.assign(providerFieldsByDisplayName, cacheEntries); + }, [cacheEntries]); const allFields = React.useMemo(() => { // First try to resolve from the in-memory cache. We support both the @@ -234,7 +206,9 @@ const ProviderSpecificFields: React.FC = ({ selecte {loadError && allFields.length === 0 && ( - {loadError} + + {loadError instanceof Error ? loadError.message : "Failed to load provider credential fields"} + )} From b487e67decdec1771c81efaa43e7434f4ae10004 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 18:23:18 -0800 Subject: [PATCH 154/349] sec fix --- docs/my-website/package-lock.json | 6 +++--- docs/my-website/package.json | 6 ++++-- ui/litellm-dashboard/package-lock.json | 6 +++--- ui/litellm-dashboard/package.json | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/my-website/package-lock.json b/docs/my-website/package-lock.json index aef7bc1fe96..9c7ee7741cd 100644 --- a/docs/my-website/package-lock.json +++ b/docs/my-website/package-lock.json @@ -16891,9 +16891,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" diff --git a/docs/my-website/package.json b/docs/my-website/package.json index 4895b6f518b..f217cef7fcc 100644 --- a/docs/my-website/package.json +++ b/docs/my-website/package.json @@ -52,13 +52,15 @@ "webpack-dev-server": ">=5.2.1", "form-data": ">=4.0.4", "mermaid": ">=11.10.0", - "gray-matter": "4.0.3" + "gray-matter": "4.0.3", + "node-forge": ">=1.3.2" }, "overrides": { "webpack-dev-server": ">=5.2.1", "form-data": ">=4.0.4", "mermaid": ">=11.10.0", "gray-matter": "4.0.3", - "glob": ">=11.1.0" + "glob": ">=11.1.0", + "node-forge": ">=1.3.2" } } diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index 641d71daa9e..9907113c7eb 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -18380,9 +18380,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index e7938e53a40..e366b4febff 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -78,7 +78,8 @@ "webpack-dev-server": ">=5.2.1", "mermaid": ">=11.10.0", "js-yaml": ">=4.1.1", - "glob": ">=11.1.0" + "glob": ">=11.1.0", + "node-forge": ">=1.3.2" }, "engines": { "node": ">=18.17.0", From 48eb34a8750d81269ee454622291b553dde5a4c3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 18:26:07 -0800 Subject: [PATCH 155/349] fix cos tracking --- litellm/model_prices_and_context_window_backup.json | 6 ++++++ model_prices_and_context_window.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 0287736af1b..f07f1cbe5e0 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -249,6 +249,12 @@ "/v1/images/generations" ] }, + "amazon.nova-canvas-v1:0": { + "litellm_provider": "bedrock", + "max_input_tokens": 2600, + "mode": "image_generation", + "output_cost_per_image": 0.06 + }, "amazon.nova-lite-v1:0": { "input_cost_per_token": 6e-08, "litellm_provider": "bedrock_converse", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 0287736af1b..f07f1cbe5e0 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -249,6 +249,12 @@ "/v1/images/generations" ] }, + "amazon.nova-canvas-v1:0": { + "litellm_provider": "bedrock", + "max_input_tokens": 2600, + "mode": "image_generation", + "output_cost_per_image": 0.06 + }, "amazon.nova-lite-v1:0": { "input_cost_per_token": 6e-08, "litellm_provider": "bedrock_converse", From 605bc4e4762c07163a4a9cf2502da21c45c06362 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 26 Nov 2025 22:01:33 +0530 Subject: [PATCH 156/349] type the secrets field --- litellm/responses/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/responses/main.py b/litellm/responses/main.py index be5f890bbd6..950ea7063f5 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -281,7 +281,7 @@ async def aresponses_api_with_mcp( ) # Extract MCP auth headers from the request to pass to MCP server - secret_fields = kwargs.get("secret_fields") + secret_fields: Optional[Dict[str, Any]] = kwargs.get("secret_fields") ( mcp_auth_header, mcp_server_auth_headers, From 30a22f1fed33c1aed3bd725f919534403d6dd749 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 27 Nov 2025 07:58:35 +0530 Subject: [PATCH 157/349] type the secrets field --- .../mcp_server/mcp_server_manager.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 773246d580b..2c03cbdae31 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -2023,25 +2023,6 @@ class MCPServerManager: return server return None - def get_mcp_servers_from_ids( - self, server_ids: List[str] - ) -> List[MCPServer]: - """ - Get MCP servers from a list of server IDs. - - Args: - server_ids: List of server IDs to retrieve - - Returns: - List of MCPServer objects corresponding to the provided IDs - """ - servers: List[MCPServer] = [] - for server_id in server_ids: - server = self.get_mcp_server_by_id(server_id) - if server: - servers.append(server) - return servers - def _generate_stable_server_id( self, server_name: str, From 1cb5fcddba0d0ced22293e36e2c0e90f05801f54 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Nov 2025 18:38:38 -0800 Subject: [PATCH 158/349] make generic api OSS + support multiple generic API's (#17152) * feat(generic_api_callback.py): make generic api OSS + support multiple generic API's Enables https://github.com/BerriAI/litellm/pull/17094#discussion_r2562832967 * feat(callback_utils.py): support custom generic api callbacks * feat(generic_api_callback.py): support specifying which event types to run the generic api for * fix(litellm_logging.py): log system prompt for anthropic messages * feat(generic_api_callback.py): support generic api compatible api's - e.g. rubrik agent cloud * docs(sidebars.js): document new OSS generic api * docs(generic_api.md): document new OSS Generic API * docs(custom_webhook_api.md): document custom webhook api integration tutorial * docs(custom_webhook_api.md): cleanup * docs(custom_webhook_api.md): document what get's logged to custom webhook api * Refactor: Pass callback config to GenericAPILogger Co-authored-by: krrishdholakia * Fix: Handle empty messages list in logging payload Co-authored-by: krrishdholakia * Checkpoint before follow-up message Co-authored-by: krrishdholakia * feat: Cache GenericAPILogger instances to improve performance Co-authored-by: krrishdholakia --------- Co-authored-by: Cursor Agent --- .../custom_webhook_api.md | 106 ++++++++++++++++ .../docs/observability/generic_api.md | 110 ++++++++++++++++ docs/my-website/sidebars.js | 10 ++ litellm/__init__.py | 1 + .../generic_api}/generic_api_callback.py | 120 ++++++++++++++++-- .../generic_api_compatible_callbacks.json | 20 +++ .../custom_logger_registry.py | 7 +- litellm/litellm_core_utils/litellm_logging.py | 49 +++++-- .../logging_callback_manager.py | 74 ++++++++++- litellm/proxy/_new_secret_config.yaml | 24 ++-- litellm/proxy/common_utils/callback_utils.py | 11 ++ litellm/proxy/proxy_server.py | 22 ++-- .../test_generic_api_callback.py | 85 ++++++++----- 13 files changed, 560 insertions(+), 79 deletions(-) create mode 100644 docs/my-website/docs/contribute_integration/custom_webhook_api.md create mode 100644 docs/my-website/docs/observability/generic_api.md rename {enterprise/litellm_enterprise/enterprise_callbacks => litellm/integrations/generic_api}/generic_api_callback.py (67%) create mode 100644 litellm/integrations/generic_api/generic_api_compatible_callbacks.json diff --git a/docs/my-website/docs/contribute_integration/custom_webhook_api.md b/docs/my-website/docs/contribute_integration/custom_webhook_api.md new file mode 100644 index 00000000000..499c7fd51de --- /dev/null +++ b/docs/my-website/docs/contribute_integration/custom_webhook_api.md @@ -0,0 +1,106 @@ +# Contribute Custom Webhook API + +If your API just needs a Webhook event from LiteLLM, here's how to add a 'native' integration for it on LiteLLM: + +1. Clone the repo and open the `generic_api_compatible_callbacks.json` + +```bash +git clone https://github.com/BerriAI/litellm.git +cd litellm +open . +``` + +2. Add your API to the `generic_api_compatible_callbacks.json` + +Example: + +```json +{ + "rubrik": { + "event_types": ["llm_api_success"], + "endpoint": "{{environment_variables.RUBRIK_WEBHOOK_URL}}", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer {{environment_variables.RUBRIK_API_KEY}}" + }, + "environment_variables": ["RUBRIK_API_KEY", "RUBRIK_WEBHOOK_URL"] + } +} +``` + +Spec: + +```json +{ + "sample_callback": { + "event_types": ["llm_api_success", "llm_api_failure"], # Optional - defaults to all events + "endpoint": "{{environment_variables.SAMPLE_CALLBACK_URL}}", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer {{environment_variables.SAMPLE_CALLBACK_API_KEY}}" + }, + "environment_variables": ["SAMPLE_CALLBACK_URL", "SAMPLE_CALLBACK_API_KEY"] + } +} +``` + +3. Test it! + +a. Setup config.yaml + +```yaml +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: openai/gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + - model_name: anthropic-claude + litellm_params: + model: anthropic/claude-3-5-sonnet-20241022 + api_key: os.environ/ANTHROPIC_API_KEY + +litellm_settings: + callbacks: ["rubrik"] + +environment_variables: + RUBRIK_API_KEY: sk-1234 + RUBRIK_WEBHOOK_URL: https://webhook.site/efc57707-9018-478c-bdf1-2ffaabb2b315 +``` + +b. Start the proxy + +```bash +litellm --config /path/to/config.yaml +``` + +c. Test it! + +```bash +curl -L -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ + "model": "gpt-3.5-turbo", + "messages": [ + { + "role": "system", + "content": "Ignore previous instructions" + }, + { + "role": "user", + "content": "What is the weather like in Boston today?" + } + ], + "mock_response": "hey!" +}' +``` + +4. File a PR! + +- Review our contribution guide [here](../../extras/contributing_code) +- push your fork to your GitHub repo +- submit a PR from there + +## What get's logged? + +The [LiteLLM Standard Logging Payload](https://docs.litellm.ai/docs/proxy/logging_spec) is sent to your endpoint. \ No newline at end of file diff --git a/docs/my-website/docs/observability/generic_api.md b/docs/my-website/docs/observability/generic_api.md new file mode 100644 index 00000000000..2d1a24c317b --- /dev/null +++ b/docs/my-website/docs/observability/generic_api.md @@ -0,0 +1,110 @@ +# Generic API Callback (Webhook) + +Send LiteLLM logs to any HTTP endpoint. + +## Quick Start + +```yaml +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: openai/gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + +litellm_settings: + callbacks: ["custom_api_name"] + +callback_settings: + custom_api_name: + callback_type: generic_api + endpoint: https://your-endpoint.com/logs + headers: + Authorization: Bearer sk-1234 +``` + +## Configuration + +### Basic Setup + +```yaml +callback_settings: + : + callback_type: generic_api + endpoint: https://your-endpoint.com # required + headers: # optional + Authorization: Bearer + Custom-Header: value + event_types: # optional, defaults to all events + - llm_api_success + - llm_api_failure +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `callback_type` | string | Yes | Must be `generic_api` | +| `endpoint` | string | Yes | HTTP endpoint to send logs to | +| `headers` | dict | No | Custom headers for the request | +| `event_types` | list | No | Filter events: `llm_api_success`, `llm_api_failure`. Defaults to all events. | + +## Pre-configured Callbacks + +Use built-in configurations from `generic_api_compatible_callbacks.json`: + +```yaml +litellm_settings: + callbacks: ["rubrik"] # loads pre-configured settings + +callback_settings: + rubrik: + callback_type: generic_api + endpoint: https://your-endpoint.com # override defaults + headers: + Authorization: Bearer ${RUBRIK_API_KEY} +``` + +## Payload Format + +Logs are sent as `StandardLoggingPayload` [objects](https://docs.litellm.ai/docs/proxy/logging_spec) in JSON format: + +```json +[ + { + "id": "chatcmpl-123", + "call_type": "litellm.completion", + "model": "gpt-3.5-turbo", + "messages": [...], + "response": {...}, + "usage": {...}, + "cost": 0.0001, + "startTime": "2024-01-01T00:00:00", + "endTime": "2024-01-01T00:00:01", + "metadata": {...} + } +] +``` + +## Environment Variables + +Set via environment variables instead of config: + +```bash +export GENERIC_LOGGER_ENDPOINT=https://your-endpoint.com +export GENERIC_LOGGER_HEADERS="Authorization=Bearer token,Custom-Header=value" +``` + +## Batch Settings + +Control batching behavior (inherits from `CustomBatchLogger`): + +```yaml +callback_settings: + my_api: + callback_type: generic_api + endpoint: https://your-endpoint.com + batch_size: 100 # default: 100 + flush_interval: 60 # seconds, default: 60 +``` + + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index b40a533337e..3ffc3b0669b 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -20,6 +20,16 @@ const sidebars = { type: "category", label: "Observability", items: [ + { + type: "category", + label: "Contributing to Integrations", + items: [ + { + type: "autogenerated", + dirName: "contribute_integration" + } + ] + }, { type: "autogenerated", dirName: "observability" diff --git a/litellm/__init__.py b/litellm/__init__.py index b214db74a76..71be5113e2d 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -177,6 +177,7 @@ _known_custom_logger_compatible_callbacks: List = list( callbacks: List[ Union[Callable, _custom_logger_compatible_callbacks_literal, CustomLogger] ] = [] +callback_settings: Dict[str, Dict[str, Any]] = {} initialized_langfuse_clients: int = 0 langfuse_default_tags: Optional[List[str]] = None langsmith_batch_size: Optional[int] = None diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/generic_api_callback.py b/litellm/integrations/generic_api/generic_api_callback.py similarity index 67% rename from enterprise/litellm_enterprise/enterprise_callbacks/generic_api_callback.py rename to litellm/integrations/generic_api/generic_api_callback.py index 7e259d4e19d..1c8a5b883da 100644 --- a/enterprise/litellm_enterprise/enterprise_callbacks/generic_api_callback.py +++ b/litellm/integrations/generic_api/generic_api_callback.py @@ -7,13 +7,15 @@ Callback to log events to a Generic API Endpoint """ import asyncio +import json import os +import re import traceback -from litellm._uuid import uuid -from typing import Dict, List, Optional, Union +from typing import Dict, List, Literal, Optional, Union import litellm from litellm._logging import verbose_logger +from litellm._uuid import uuid from litellm.integrations.custom_batch_logger import CustomBatchLogger from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.llms.custom_httpx.http_handler import ( @@ -22,12 +24,83 @@ from litellm.llms.custom_httpx.http_handler import ( ) from litellm.types.utils import StandardLoggingPayload +API_EVENT_TYPES = Literal["llm_api_success", "llm_api_failure"] + + +def load_compatible_callbacks() -> Dict: + """ + Load the generic_api_compatible_callbacks.json file + + Returns: + Dict: Dictionary of compatible callbacks configuration + """ + try: + json_path = os.path.join( + os.path.dirname(__file__), "generic_api_compatible_callbacks.json" + ) + with open(json_path, "r") as f: + return json.load(f) + except Exception as e: + verbose_logger.warning( + f"Error loading generic_api_compatible_callbacks.json: {str(e)}" + ) + return {} + + +def is_callback_compatible(callback_name: str) -> bool: + """ + Check if a callback_name exists in the compatible callbacks list + + Args: + callback_name: Name of the callback to check + + Returns: + bool: True if callback_name exists in the compatible callbacks, False otherwise + """ + compatible_callbacks = load_compatible_callbacks() + return callback_name in compatible_callbacks + + +def get_callback_config(callback_name: str) -> Optional[Dict]: + """ + Get the configuration for a specific callback + + Args: + callback_name: Name of the callback to get config for + + Returns: + Optional[Dict]: Configuration dict for the callback, or None if not found + """ + compatible_callbacks = load_compatible_callbacks() + return compatible_callbacks.get(callback_name) + + +def substitute_env_variables(value: str) -> str: + """ + Replace {{environment_variables.VAR_NAME}} patterns with actual environment variable values + + Args: + value: String that may contain {{environment_variables.VAR_NAME}} patterns + + Returns: + str: String with environment variables substituted + """ + pattern = r"\{\{environment_variables\.([A-Z_]+)\}\}" + + def replace_env_var(match): + env_var_name = match.group(1) + return os.getenv(env_var_name, "") + + return re.sub(pattern, replace_env_var, value) + class GenericAPILogger(CustomBatchLogger): def __init__( self, endpoint: Optional[str] = None, headers: Optional[dict] = None, + event_types: Optional[List[API_EVENT_TYPES]] = None, + callback_name: Optional[str] = None, **kwargs, ): """ @@ -36,7 +109,37 @@ class GenericAPILogger(CustomBatchLogger): Args: endpoint: Optional[str] = None, headers: Optional[dict] = None, + event_types: Optional[List[API_EVENT_TYPES]] = None, + callback_name: Optional[str] = None - If provided, loads config from generic_api_compatible_callbacks.json """ + ######################################################### + # Check if callback_name is provided and load config + ######################################################### + if callback_name: + if is_callback_compatible(callback_name): + verbose_logger.debug( + f"Loading configuration for callback: {callback_name}" + ) + callback_config = get_callback_config(callback_name) + + # Use config from JSON if not explicitly provided + if callback_config: + if endpoint is None and "endpoint" in callback_config: + endpoint = substitute_env_variables(callback_config["endpoint"]) + + if "headers" in callback_config: + headers = headers or {} + for key, value in callback_config["headers"].items(): + if key not in headers: + headers[key] = substitute_env_variables(value) + + if event_types is None and "event_types" in callback_config: + event_types = callback_config["event_types"] + else: + verbose_logger.warning( + f"callback_name '{callback_name}' not found in generic_api_compatible_callbacks.json" + ) + ######################################################### # Init httpx client ######################################################### @@ -51,8 +154,10 @@ class GenericAPILogger(CustomBatchLogger): self.headers: Dict = self._get_headers(headers) self.endpoint: str = endpoint + self.event_types: Optional[List[API_EVENT_TYPES]] = event_types + self.callback_name: Optional[str] = callback_name verbose_logger.debug( - f"in init GenericAPILogger, endpoint {self.endpoint}, headers {self.headers}" + f"in init GenericAPILogger, callback_name: {self.callback_name}, endpoint {self.endpoint}, headers {self.headers}, event_types: {self.event_types}" ) ######################################################### @@ -114,9 +219,9 @@ class GenericAPILogger(CustomBatchLogger): Raises: Raises a NON Blocking verbose_logger.exception if an error occurs """ - from litellm.proxy.utils import _premium_user_check - _premium_user_check() + if self.event_types is not None and "llm_api_success" not in self.event_types: + return try: verbose_logger.debug( @@ -153,9 +258,8 @@ class GenericAPILogger(CustomBatchLogger): - Creates a StandardLoggingPayload - Adds to batch queue """ - from litellm.proxy.utils import _premium_user_check - - _premium_user_check() + if self.event_types is not None and "llm_api_failure" not in self.event_types: + return try: verbose_logger.debug( diff --git a/litellm/integrations/generic_api/generic_api_compatible_callbacks.json b/litellm/integrations/generic_api/generic_api_compatible_callbacks.json new file mode 100644 index 00000000000..1e88a39e0a7 --- /dev/null +++ b/litellm/integrations/generic_api/generic_api_compatible_callbacks.json @@ -0,0 +1,20 @@ +{ + "sample_callback": { + "event_types": ["llm_api_success", "llm_api_failure"], + "endpoint": "{{environment_variables.SAMPLE_CALLBACK_URL}}", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer {{environment_variables.SAMPLE_CALLBACK_API_KEY}}" + }, + "environment_variables": ["SAMPLE_CALLBACK_URL", "SAMPLE_CALLBACK_API_KEY"] + }, + "rubrik": { + "event_types": ["llm_api_success"], + "endpoint": "{{environment_variables.RUBRIK_WEBHOOK_URL}}", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer {{environment_variables.RUBRIK_API_KEY}}" + }, + "environment_variables": ["RUBRIK_API_KEY", "RUBRIK_WEBHOOK_URL"] + } +} \ No newline at end of file diff --git a/litellm/litellm_core_utils/custom_logger_registry.py b/litellm/litellm_core_utils/custom_logger_registry.py index 80f2f195839..538ef6be283 100644 --- a/litellm/litellm_core_utils/custom_logger_registry.py +++ b/litellm/litellm_core_utils/custom_logger_registry.py @@ -95,9 +95,6 @@ class CustomLoggerRegistry: } try: - from litellm_enterprise.enterprise_callbacks.generic_api_callback import ( - GenericAPILogger, - ) from litellm_enterprise.enterprise_callbacks.pagerduty.pagerduty import ( PagerDutyAlerting, ) @@ -108,6 +105,10 @@ class CustomLoggerRegistry: SMTPEmailLogger, ) + from litellm.integrations.generic_api.generic_api_callback import ( + GenericAPILogger, + ) + enterprise_loggers = { "pagerduty": PagerDutyAlerting, "generic_api": GenericAPILogger, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 38decd8af99..305b7d6ddc3 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -165,9 +165,6 @@ try: from litellm_enterprise.enterprise_callbacks.callback_controls import ( EnterpriseCallbackControls, ) - from litellm_enterprise.enterprise_callbacks.generic_api_callback import ( - GenericAPILogger, - ) from litellm_enterprise.enterprise_callbacks.pagerduty.pagerduty import ( PagerDutyAlerting, ) @@ -181,6 +178,8 @@ try: StandardLoggingPayloadSetup as EnterpriseStandardLoggingPayloadSetup, ) + from litellm.integrations.generic_api.generic_api_callback import GenericAPILogger + EnterpriseStandardLoggingPayloadSetupVAR: Optional[ Type[EnterpriseStandardLoggingPayloadSetup] ] = EnterpriseStandardLoggingPayloadSetup @@ -315,6 +314,7 @@ class Logging(LiteLLMLoggingBaseClass): for m in messages: new_messages.append({"role": "user", "content": m}) messages = new_messages + self.model = model self.messages = copy.deepcopy(messages) self.stream = stream @@ -4106,10 +4106,8 @@ def _get_custom_logger_settings_from_proxy_server(callback_name: str) -> Dict: otel: message_logging: False """ - from litellm.proxy.proxy_server import callback_settings - - if callback_settings: - return dict(callback_settings.get(callback_name, {})) + if litellm.callback_settings: + return dict(litellm.callback_settings.get(callback_name, {})) return {} @@ -4186,6 +4184,39 @@ class StandardLoggingPayloadSetup: return start_time_float, end_time_float, completion_start_time_float + @staticmethod + def append_system_prompt_messages( + kwargs: Optional[Dict] = None, messages: Optional[Any] = None + ): + """ + Append system prompt messages to the messages + """ + if kwargs is not None: + if kwargs.get("system") is not None and isinstance( + kwargs.get("system"), str + ): + if messages is None: + return [{"role": "system", "content": kwargs.get("system")}] + elif isinstance(messages, list): + if len(messages) == 0: + return [{"role": "system", "content": kwargs.get("system")}] + # check for duplicates + if messages[0].get("role") == "system" and messages[0].get( + "content" + ) == kwargs.get("system"): + return messages + messages = [ + {"role": "system", "content": kwargs.get("system")} + ] + messages + elif isinstance(messages, str): + messages = [ + {"role": "system", "content": kwargs.get("system")}, + {"role": "user", "content": messages}, + ] + return messages + + return messages + @staticmethod def get_standard_logging_metadata( metadata: Optional[Dict[str, Any]], @@ -4908,7 +4939,9 @@ def get_standard_logging_object_payload( model_group=_model_group, model_id=_model_id, requester_ip_address=clean_metadata.get("requester_ip_address", None), - messages=kwargs.get("messages"), + messages=StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=kwargs.get("messages") + ), response=final_response_obj, model_parameters=ModelParamHelper.get_standard_logging_model_parameters( kwargs.get("optional_params", None) or {} diff --git a/litellm/litellm_core_utils/logging_callback_manager.py b/litellm/litellm_core_utils/logging_callback_manager.py index 9ec346c20a1..349cb6f3ceb 100644 --- a/litellm/litellm_core_utils/logging_callback_manager.py +++ b/litellm/litellm_core_utils/logging_callback_manager.py @@ -1,9 +1,10 @@ -from typing import TYPE_CHECKING, Callable, List, Optional, Set, Type, Union +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Set, Type, Union import litellm from litellm._logging import verbose_logger from litellm.integrations.additional_logging_utils import AdditionalLoggingUtils from litellm.integrations.custom_logger import CustomLogger +from litellm.integrations.generic_api.generic_api_callback import GenericAPILogger from litellm.types.utils import CallbacksByType if TYPE_CHECKING: @@ -11,6 +12,8 @@ if TYPE_CHECKING: else: _custom_logger_compatible_callbacks_literal = str +_generic_api_logger_cache: Dict[str, GenericAPILogger] = {} + class LoggingCallbackManager: """ @@ -138,6 +141,57 @@ class LoggingCallbackManager: return False return True + @staticmethod + def _add_custom_callback_generic_api_str( + callback: str, + ) -> Union[GenericAPILogger, str]: + """ + litellm_settings: + success_callback: ["custom_callback_name"] + + callback_settings: + custom_callback_name: + callback_type: generic_api + endpoint: https://webhook-test.com/30343bc33591bc5e6dc44217ceae3e0a + headers: + Authorization: Bearer sk-1234 + """ + callback_config = litellm.callback_settings.get(callback) + + if not isinstance(callback_config, dict): + return callback + + if callback_config.get("callback_type") != "generic_api": + return callback + + endpoint = callback_config.get("endpoint") + headers = callback_config.get("headers") + event_types = callback_config.get("event_types") + + if endpoint is None or headers is None: + verbose_logger.warning( + "generic_api callback '%s' is missing endpoint or headers, skipping.", + callback, + ) + return callback + + cached_logger = _generic_api_logger_cache.get(callback) + if ( + isinstance(cached_logger, GenericAPILogger) + and cached_logger.endpoint == endpoint + and cached_logger.headers == headers + and cached_logger.event_types == event_types + ): + return cached_logger + + new_logger = GenericAPILogger( + endpoint=endpoint, + headers=headers, + event_types=event_types, + ) + _generic_api_logger_cache[callback] = new_logger + return new_logger + def _safe_add_callback_to_list( self, callback: Union[CustomLogger, Callable, str], @@ -152,15 +206,24 @@ class LoggingCallbackManager: if not self._check_callback_list_size(parent_list): return + # Check if the callback is a custom callback + + if isinstance(callback, str): + callback = LoggingCallbackManager._add_custom_callback_generic_api_str( + callback + ) + if isinstance(callback, str): self._add_string_callback_to_list( callback=callback, parent_list=parent_list ) elif isinstance(callback, CustomLogger): + self._add_custom_logger_to_list( custom_logger=callback, parent_list=parent_list, ) + elif callable(callback): self._add_callback_function_to_list( callback=callback, parent_list=parent_list @@ -348,7 +411,6 @@ class LoggingCallbackManager: elif callable(callback): return getattr(callback, "__name__", str(callback)) return str(callback) - def get_active_custom_logger_for_callback_name( self, @@ -362,12 +424,16 @@ class LoggingCallbackManager: ) # get the custom logger class type - custom_logger_class_type = CustomLoggerRegistry.get_class_type_for_custom_logger_name(callback_name) + custom_logger_class_type = ( + CustomLoggerRegistry.get_class_type_for_custom_logger_name(callback_name) + ) # get the active custom logger custom_logger = self.get_custom_loggers_for_type(custom_logger_class_type) if len(custom_logger) == 0: - raise ValueError(f"No active custom logger found for callback name: {callback_name}") + raise ValueError( + f"No active custom logger found for callback name: {callback_name}" + ) return custom_logger[0] diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index f24f9a96428..68761524794 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -3,19 +3,17 @@ model_list: litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - -guardrails: - - guardrail_name: model-armor-shield + - model_name: anthropic-claude litellm_params: - guardrail: model_armor - mode: "post_call" # Run on both input and output - template_id: "test-prompt-template" # Required: Your Model Armor template ID - project_id: "test-vector-store-db" # Your GCP project ID - location: "us" # GCP location (default: us-central1) - mask_request_content: true # Enable request content masking - mask_response_content: true # Enable response content masking - fail_on_error: true # Fail request if Model Armor errors (default: true) - default_on: true # Run by default for all requests + model: anthropic/claude-3-5-sonnet-20241022 + api_key: os.environ/ANTHROPIC_API_KEY litellm_settings: - callbacks: ["arize_phoenix"] \ No newline at end of file + callbacks: ["rubrik"] + +callback_settings: + rubrik: + callback_type: generic_api + endpoint: https://webhook.site/efc57707-9018-478c-bdf1-2ffaabb2b315 + headers: + Authorization: Bearer sk-1234 \ No newline at end of file diff --git a/litellm/proxy/common_utils/callback_utils.py b/litellm/proxy/common_utils/callback_utils.py index af548ecf1be..b914e3e9674 100644 --- a/litellm/proxy/common_utils/callback_utils.py +++ b/litellm/proxy/common_utils/callback_utils.py @@ -24,6 +24,10 @@ def initialize_callbacks_on_proxy( # noqa: PLR0915 litellm_settings: dict, callback_specific_params: dict = {}, ): + from litellm.integrations.custom_logger import CustomLogger + from litellm.litellm_core_utils.logging_callback_manager import ( + LoggingCallbackManager, + ) from litellm.proxy.proxy_server import prisma_client verbose_proxy_logger.debug( @@ -32,6 +36,11 @@ def initialize_callbacks_on_proxy( # noqa: PLR0915 if isinstance(value, list): imported_list: List[Any] = [] for callback in value: # ["presidio", ] + # check if callback is a custom logger compatible callback + if isinstance(callback, str): + callback = LoggingCallbackManager._add_custom_callback_generic_api_str( + callback + ) if ( isinstance(callback, str) and callback in litellm._known_custom_logger_compatible_callbacks @@ -259,6 +268,8 @@ def initialize_callbacks_on_proxy( # noqa: PLR0915 **azure_content_safety_params, ) imported_list.append(azure_content_safety_obj) + elif isinstance(callback, CustomLogger): + imported_list.append(callback) else: verbose_proxy_logger.debug( f"{blue_color_code} attempting to import custom calback={callback} {reset_color_code}" diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 67e874fc55d..e4a1550e000 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -286,9 +286,7 @@ from litellm.proxy.management_endpoints.customer_endpoints import ( from litellm.proxy.management_endpoints.internal_user_endpoints import ( router as internal_user_router, ) -from litellm.proxy.management_endpoints.internal_user_endpoints import ( - user_update, -) +from litellm.proxy.management_endpoints.internal_user_endpoints import user_update from litellm.proxy.management_endpoints.key_management_endpoints import ( delete_verification_tokens, duration_in_seconds, @@ -342,9 +340,7 @@ from litellm.proxy.ocr_endpoints.endpoints import router as ocr_router from litellm.proxy.openai_files_endpoints.files_endpoints import ( router as openai_files_router, ) -from litellm.proxy.openai_files_endpoints.files_endpoints import ( - set_files_config, -) +from litellm.proxy.openai_files_endpoints.files_endpoints import set_files_config from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( passthrough_endpoint_router, ) @@ -436,9 +432,7 @@ from litellm.types.proxy.management_endpoints.ui_sso import ( LiteLLM_UpperboundKeyGenerateParams, ) from litellm.types.realtime import RealtimeQueryParams -from litellm.types.router import ( - DeploymentTypedDict, -) +from litellm.types.router import DeploymentTypedDict from litellm.types.router import ModelInfo as RouterModelInfo from litellm.types.router import ( RouterGeneralSettings, @@ -1084,7 +1078,6 @@ llm_router: Optional[Router] = None llm_model_list: Optional[list] = None general_settings: dict = {} config_passthrough_endpoints: Optional[List[Dict[str, Any]]] = None -callback_settings: dict = {} log_file = "api_log.json" worker_config = None master_key: Optional[str] = None @@ -1241,7 +1234,10 @@ def cost_tracking(): global prisma_client if prisma_client is not None: litellm.logging_callback_manager.add_litellm_callback(_ProxyDBLogger()) - litellm.logging_callback_manager.add_litellm_async_success_callback(_ProxyDBLogger()) + litellm.logging_callback_manager.add_litellm_async_success_callback( + _ProxyDBLogger() + ) + async def update_cache( # noqa: PLR0915 token: Optional[str], @@ -2074,7 +2070,7 @@ class ProxyConfig: """ Load config values into proxy global state """ - global master_key, user_config_file_path, otel_logging, user_custom_auth, user_custom_auth_path, user_custom_key_generate, user_custom_sso, user_custom_ui_sso_sign_in_handler, use_background_health_checks, use_shared_health_check, health_check_interval, use_queue, proxy_budget_rescheduler_max_time, proxy_budget_rescheduler_min_time, ui_access_mode, litellm_master_key_hash, proxy_batch_write_at, disable_spend_logs, prompt_injection_detection_obj, redis_usage_cache, store_model_in_db, premium_user, open_telemetry_logger, health_check_details, callback_settings, proxy_batch_polling_interval, config_passthrough_endpoints + global master_key, user_config_file_path, otel_logging, user_custom_auth, user_custom_auth_path, user_custom_key_generate, user_custom_sso, user_custom_ui_sso_sign_in_handler, use_background_health_checks, use_shared_health_check, health_check_interval, use_queue, proxy_budget_rescheduler_max_time, proxy_budget_rescheduler_min_time, ui_access_mode, litellm_master_key_hash, proxy_batch_write_at, disable_spend_logs, prompt_injection_detection_obj, redis_usage_cache, store_model_in_db, premium_user, open_telemetry_logger, health_check_details, proxy_batch_polling_interval, config_passthrough_endpoints config: dict = await self.get_config(config_file_path=config_file_path) @@ -2082,6 +2078,8 @@ class ProxyConfig: ## Callback settings callback_settings = config.get("callback_settings", {}) + if callback_settings: + litellm.callback_settings = callback_settings ## LITELLM MODULE SETTINGS (e.g. litellm.drop_params=True,..) litellm_settings = config.get("litellm_settings", None) diff --git a/tests/logging_callback_tests/test_generic_api_callback.py b/tests/logging_callback_tests/test_generic_api_callback.py index a2301b0303a..3ddf84f2939 100644 --- a/tests/logging_callback_tests/test_generic_api_callback.py +++ b/tests/logging_callback_tests/test_generic_api_callback.py @@ -28,8 +28,7 @@ from litellm.types.utils import ( ) verbose_logger.setLevel(logging.DEBUG) -from litellm_enterprise.enterprise_callbacks.generic_api_callback import GenericAPILogger - +from litellm.integrations.generic_api.generic_api_callback import GenericAPILogger @pytest.mark.asyncio @@ -52,9 +51,7 @@ async def test_generic_api_callback(): # Initialize the GenericAPILogger and set the mock generic_logger = GenericAPILogger( - endpoint=test_endpoint, - headers=test_headers, - flush_interval=1 + endpoint=test_endpoint, headers=test_headers, flush_interval=1 ) generic_logger.async_httpx_client.post = mock_post litellm.callbacks = [generic_logger] @@ -76,12 +73,21 @@ async def test_generic_api_callback(): # Get the actual request body from the mock actual_url = mock_post.call_args[1]["url"] print("##########\n") - print("logs were flushed to URL", actual_url, "with the following headers", mock_post.call_args[1]["headers"]) - assert actual_url == test_endpoint, f"Expected URL {test_endpoint}, got {actual_url}" + print( + "logs were flushed to URL", + actual_url, + "with the following headers", + mock_post.call_args[1]["headers"], + ) + assert ( + actual_url == test_endpoint + ), f"Expected URL {test_endpoint}, got {actual_url}" # Validate headers - assert mock_post.call_args[1]["headers"]["Content-Type"] == "application/json", "Content-Type should be application/json" - + assert ( + mock_post.call_args[1]["headers"]["Content-Type"] == "application/json" + ), "Content-Type should be application/json" + # For the GenericAPILogger, it sends the payload directly as JSON in the data field json_data = mock_post.call_args[1]["data"] # Parse the JSON string @@ -89,27 +95,30 @@ async def test_generic_api_callback(): print("##########\n") print("json_data", json_data) actual_request = json.loads(json_data) - + # The payload is a list of StandardLoggingPayload objects in the log queue assert isinstance(actual_request, list), "Request body should be a list" assert len(actual_request) > 0, "Request body list should not be empty" - + # Validate the first payload item payload_item: StandardLoggingPayload = StandardLoggingPayload(**actual_request[0]) print("##########\n") print(json.dumps(payload_item, indent=4)) print("##########\n") - # Basic assertions for standard logging payload assert payload_item["response_cost"] > 0, "Response cost should be greater than 0" assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o" - assert payload_item["model_parameters"]["user"] == "test_user", "User should be test_user" + assert ( + payload_item["model_parameters"]["user"] == "test_user" + ), "User should be test_user" assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o" - assert payload_item["messages"] == [{"role": "user", "content": "Hello, world!"}], "Messages should be the same" - assert payload_item["response"]["choices"][0]["message"]["content"] == "hi", "Response should be hi" - - + assert payload_item["messages"] == [ + {"role": "user", "content": "Hello, world!"} + ], "Messages should be the same" + assert ( + payload_item["response"]["choices"][0]["message"]["content"] == "hi" + ), "Response should be hi" @pytest.mark.asyncio @@ -129,9 +138,7 @@ async def test_generic_api_callback_multiple_logs(): # Initialize the GenericAPILogger and set the mock generic_logger = GenericAPILogger( - endpoint=test_endpoint, - headers=test_headers, - flush_interval=5 + endpoint=test_endpoint, headers=test_headers, flush_interval=5 ) generic_logger.async_httpx_client.post = mock_post litellm.callbacks = [generic_logger] @@ -154,9 +161,16 @@ async def test_generic_api_callback_multiple_logs(): # Get the actual request body from the mock actual_url = mock_post.call_args[1]["url"] print("##########\n") - print("logs were flushed to URL", actual_url, "with the following headers", mock_post.call_args[1]["headers"]) - assert actual_url == test_endpoint, f"Expected URL {test_endpoint}, got {actual_url}" - + print( + "logs were flushed to URL", + actual_url, + "with the following headers", + mock_post.call_args[1]["headers"], + ) + assert ( + actual_url == test_endpoint + ), f"Expected URL {test_endpoint}, got {actual_url}" + # For the GenericAPILogger, it sends the payload directly as JSON in the data field json_data = mock_post.call_args[1]["data"] # Parse the JSON string @@ -164,12 +178,14 @@ async def test_generic_api_callback_multiple_logs(): print("##########\n") print("json_data", json_data) actual_request = json.loads(json_data) - + # The payload is a list of StandardLoggingPayload objects in the log queue assert isinstance(actual_request, list), "Request body should be a list" assert len(actual_request) > 0, "Request body list should not be empty" - assert len(actual_request) == 10, "Request body list should be 10 items, since we made 10 calls" - + assert ( + len(actual_request) == 10 + ), "Request body list should be 10 items, since we made 10 calls" + # Validate all payload items for payload_item in actual_request: payload_item: StandardLoggingPayload = StandardLoggingPayload(**payload_item) @@ -177,10 +193,17 @@ async def test_generic_api_callback_multiple_logs(): print(json.dumps(payload_item, indent=4)) print("##########\n") - assert payload_item["response_cost"] > 0, "Response cost should be greater than 0" + assert ( + payload_item["response_cost"] > 0 + ), "Response cost should be greater than 0" assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o" - assert payload_item["model_parameters"]["user"] == "test_user", "User should be test_user" + assert ( + payload_item["model_parameters"]["user"] == "test_user" + ), "User should be test_user" assert payload_item["model"] == "gpt-4o", "Model should be gpt-4o" - assert payload_item["messages"] == [{"role": "user", "content": "Hello, world!"}], "Messages should be the same" - assert payload_item["response"]["choices"][0]["message"]["content"] == "hi", "Response should be hi" - + assert payload_item["messages"] == [ + {"role": "user", "content": "Hello, world!"} + ], "Messages should be the same" + assert ( + payload_item["response"]["choices"][0]["message"]["content"] == "hi" + ), "Response should be hi" From 65f5cc29dd002ecf68460b75312fbc7db4303e64 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 18:55:32 -0800 Subject: [PATCH 159/349] fix ai/ml api --- litellm/llms/aiml/image_generation/transformation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/litellm/llms/aiml/image_generation/transformation.py b/litellm/llms/aiml/image_generation/transformation.py index 006a2c16d7e..d8f3e23fe7e 100644 --- a/litellm/llms/aiml/image_generation/transformation.py +++ b/litellm/llms/aiml/image_generation/transformation.py @@ -97,6 +97,9 @@ class AimlImageGenerationConfig(BaseImageGenerationConfig): ) complete_url = complete_url.rstrip("/") + # Strip /v1 suffix if present since IMAGE_GENERATION_ENDPOINT already includes v1 + if complete_url.endswith("/v1"): + complete_url = complete_url[:-3] complete_url = f"{complete_url}/{self.IMAGE_GENERATION_ENDPOINT}" return complete_url From 01fd4d7cef6161e8b683ef48727ae8ea81b70a80 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 18:58:32 -0800 Subject: [PATCH 160/349] fix fireworks test --- tests/local_testing/test_completion.py | 2 +- tests/local_testing/test_completion_cost.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/local_testing/test_completion.py b/tests/local_testing/test_completion.py index 7166007961b..a72751d6f58 100644 --- a/tests/local_testing/test_completion.py +++ b/tests/local_testing/test_completion.py @@ -1209,7 +1209,7 @@ def test_completion_fireworks_ai(): }, ] response = completion( - model="fireworks_ai/llama4-maverick-instruct-basic", + model="fireworks_ai/llama-v3p3-70b-instruct", messages=messages, ) print(response) diff --git a/tests/local_testing/test_completion_cost.py b/tests/local_testing/test_completion_cost.py index d4edcf58ea9..40efcc23868 100644 --- a/tests/local_testing/test_completion_cost.py +++ b/tests/local_testing/test_completion_cost.py @@ -1198,8 +1198,7 @@ from litellm.llms.fireworks_ai.cost_calculator import get_base_model_for_pricing @pytest.mark.parametrize( "model, base_model", [ - ("fireworks_ai/llama-v3p1-405b-instruct", "fireworks-ai-above-16b"), - ("fireworks_ai/llama4-maverick-instruct-basic", "fireworks-ai-default"), + ("fireworks_ai/llama-v3p3-70b-instruct", "fireworks-ai-above-16b"), ], ) def test_get_model_params_fireworks_ai(model, base_model): @@ -1210,8 +1209,7 @@ def test_get_model_params_fireworks_ai(model, base_model): @pytest.mark.parametrize( "model", [ - "fireworks_ai/llama-v3p1-405b-instruct", - "fireworks_ai/llama4-maverick-instruct-basic", + "fireworks_ai/llama-v3p3-70b-instruct", ], ) def test_completion_cost_fireworks_ai(model): From 772be1778a6a2aaea935f6b822d8bb660c405667 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 26 Nov 2025 19:00:31 -0800 Subject: [PATCH 161/349] test_append_system_prompt_messages --- .../test_litellm_logging.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 477fd1396f7..8065304fd64 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -759,3 +759,62 @@ def test_get_final_response_obj_with_empty_response_obj_and_list_init(): assert len(result) == 2 assert result[0].name == "Object1" assert result[1].name == "Object2" + + +def test_append_system_prompt_messages(): + """ + Test append_system_prompt_messages prepends system message from kwargs to messages list. + """ + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + # Test case 1: system in kwargs with existing messages + kwargs = {"system": "You are a helpful assistant"} + messages = [{"role": "user", "content": "Hello"}] + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=messages + ) + assert len(result) == 2 + assert result[0] == {"role": "system", "content": "You are a helpful assistant"} + assert result[1] == {"role": "user", "content": "Hello"} + + # Test case 2: system in kwargs with None messages + kwargs = {"system": "You are a helpful assistant"} + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=None + ) + assert len(result) == 1 + assert result[0] == {"role": "system", "content": "You are a helpful assistant"} + + # Test case 3: system in kwargs with empty messages list + kwargs = {"system": "You are a helpful assistant"} + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=[] + ) + assert len(result) == 1 + assert result[0] == {"role": "system", "content": "You are a helpful assistant"} + + # Test case 4: duplicate system message should not be added + kwargs = {"system": "You are a helpful assistant"} + messages = [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "Hello"}, + ] + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=messages + ) + assert len(result) == 2 + assert result[0] == {"role": "system", "content": "You are a helpful assistant"} + + # Test case 5: no system in kwargs returns messages unchanged + kwargs = {} + messages = [{"role": "user", "content": "Hello"}] + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=kwargs, messages=messages + ) + assert result == messages + + # Test case 6: None kwargs returns messages unchanged + result = StandardLoggingPayloadSetup.append_system_prompt_messages( + kwargs=None, messages=messages + ) + assert result == messages From 40db4527f709b92eea4217234e804ad4daca5d0a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 15 Nov 2025 16:23:48 -0800 Subject: [PATCH 162/349] [Feature] UI - Organization Usage in Usage Tab (#16614) * Organization Usage UI * Resolved build issues * Added a test --- .../src/app/(dashboard)/usage/page.tsx | 1 + ui/litellm-dashboard/src/app/page.tsx | 1 + .../EntityUsageExportModal.tsx | 2 +- .../EntityUsageExport/ExportTypeSelector.tsx | 3 +- .../EntityUsageExport/UsageExportHeader.tsx | 2 +- .../src/components/EntityUsageExport/types.ts | 3 +- .../src/components/EntityUsageExport/utils.ts | 11 +- .../common_components/default_org.tsx | 2 +- .../src/components/entity_usage.test.tsx | 18 +- .../src/components/entity_usage.tsx | 39 +++- .../src/components/networking.tsx | 205 ++++++++++-------- .../src/components/new_usage.test.tsx | 39 ++++ .../src/components/new_usage.tsx | 105 ++++++--- 13 files changed, 288 insertions(+), 143 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/page.tsx index e4b44e5a450..d77b947df36 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/page.tsx @@ -15,6 +15,7 @@ const UsagePage = () => { userID={userId} teams={teams ?? []} premiumUser={premiumUser} + organizations={[]} /> ); }; diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index f547ab7d057..56c35804df9 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -480,6 +480,7 @@ export default function CreateKeyPage() { userRole={userRole} accessToken={accessToken} teams={(teams as Team[]) ?? []} + organizations={(organizations as Organization[]) ?? []} premiumUser={premiumUser} /> ) : ( diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx index 104e446cb38..672643f2ad9 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx @@ -22,7 +22,7 @@ const EntityUsageExportModal: React.FC = ({ const [exportScope, setExportScope] = useState("daily"); const [isExporting, setIsExporting] = useState(false); - const entityLabel = entityType === "tag" ? "Tag" : "Team"; + const entityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1); const modalTitle = customTitle || `Export ${entityLabel} Usage`; const handleExportCSV = () => { diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/ExportTypeSelector.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/ExportTypeSelector.tsx index 83e719032ce..43e6f986dfb 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/ExportTypeSelector.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/ExportTypeSelector.tsx @@ -5,7 +5,7 @@ import type { ExportScope } from "./types"; interface ExportTypeSelectorProps { value: ExportScope; onChange: (value: ExportScope) => void; - entityType: "tag" | "team"; + entityType: "tag" | "team" | "organization"; } const ExportTypeSelector: React.FC = ({ value, onChange, entityType }) => { @@ -36,4 +36,3 @@ const ExportTypeSelector: React.FC = ({ value, onChange }; export default ExportTypeSelector; - diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx index 1f61ea260ef..3547d65379e 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx @@ -7,7 +7,7 @@ import type { EntitySpendData } from "./types"; interface UsageExportHeaderProps { dateValue: DateRangePickerValue; - entityType: "tag" | "team"; + entityType: "tag" | "team" | "organization"; spendData: EntitySpendData; // Optional filter props showFilters?: boolean; diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/types.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/types.ts index b7ac41c6f33..ea11701f7ee 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/types.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/types.ts @@ -17,7 +17,7 @@ export interface EntitySpendData { export interface EntityUsageExportModalProps { isOpen: boolean; onClose: () => void; - entityType: "tag" | "team"; + entityType: "tag" | "team" | "organization"; spendData: EntitySpendData; dateRange: DateRangePickerValue; selectedFilters: string[]; @@ -59,4 +59,3 @@ export interface EntityBreakdown { id: string; }; } - diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index a63a60e5cb4..87ca860657f 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -50,7 +50,7 @@ export const generateDailyData = (spendData: EntitySpendData, entityLabel: strin [entityLabel]: data.metadata?.team_alias || entity, [`${entityLabel} ID`]: entity, "Spend ($)": formatNumberWithCommas(data.metrics.spend, 4), - "Requests": data.metrics.api_requests, + Requests: data.metrics.api_requests, "Successful Requests": data.metrics.successful_requests, "Failed Requests": data.metrics.failed_requests, "Total Tokens": data.metrics.total_tokens, @@ -109,9 +109,9 @@ export const generateDailyWithModelsData = (spendData: EntitySpendData, entityLa [`${entityLabel} ID`]: entity, Model: model, "Spend ($)": formatNumberWithCommas(metrics.spend, 4), - "Requests": metrics.requests, - "Successful": metrics.successful, - "Failed": metrics.failed, + Requests: metrics.requests, + Successful: metrics.successful, + Failed: metrics.failed, "Total Tokens": metrics.tokens, }); }); @@ -137,7 +137,7 @@ export const generateExportData = ( }; export const generateMetadata = ( - entityType: "tag" | "team", + entityType: "tag" | "team" | "organization", dateRange: { from?: Date; to?: Date }, selectedFilters: string[], exportScope: ExportScope, @@ -159,4 +159,3 @@ export const generateMetadata = ( total_tokens: spendData.metadata.total_tokens, }, }); - diff --git a/ui/litellm-dashboard/src/components/common_components/default_org.tsx b/ui/litellm-dashboard/src/components/common_components/default_org.tsx index 8bb5fdaad44..f50c59a556d 100644 --- a/ui/litellm-dashboard/src/components/common_components/default_org.tsx +++ b/ui/litellm-dashboard/src/components/common_components/default_org.tsx @@ -1,6 +1,6 @@ import { Organization } from "../networking"; export const defaultOrg = { - organization_id: null, + organization_id: "default_organization", organization_alias: "Default Organization", } as Organization; diff --git a/ui/litellm-dashboard/src/components/entity_usage.test.tsx b/ui/litellm-dashboard/src/components/entity_usage.test.tsx index a52e63a391c..d5cc503fd10 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.test.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.test.tsx @@ -3,7 +3,6 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import EntityUsage from "./entity_usage"; import * as networking from "./networking"; -// Polyfill ResizeObserver for test environment beforeAll(() => { if (typeof window !== "undefined" && !window.ResizeObserver) { window.ResizeObserver = class ResizeObserver { @@ -18,6 +17,7 @@ beforeAll(() => { vi.mock("./networking", () => ({ tagDailyActivityCall: vi.fn(), teamDailyActivityCall: vi.fn(), + organizationDailyActivityCall: vi.fn(), })); // Mock the child components to simplify testing @@ -41,6 +41,7 @@ vi.mock("./EntityUsageExport", () => ({ describe("EntityUsage", () => { const mockTagDailyActivityCall = vi.mocked(networking.tagDailyActivityCall); const mockTeamDailyActivityCall = vi.mocked(networking.teamDailyActivityCall); + const mockOrganizationDailyActivityCall = vi.mocked(networking.organizationDailyActivityCall); const mockSpendData = { results: [ @@ -126,8 +127,10 @@ describe("EntityUsage", () => { beforeEach(() => { mockTagDailyActivityCall.mockClear(); mockTeamDailyActivityCall.mockClear(); + mockOrganizationDailyActivityCall.mockClear(); mockTagDailyActivityCall.mockResolvedValue(mockSpendData); mockTeamDailyActivityCall.mockResolvedValue(mockSpendData); + mockOrganizationDailyActivityCall.mockResolvedValue(mockSpendData); }); it("should render with tag entity type and display spend metrics", async () => { @@ -164,6 +167,19 @@ describe("EntityUsage", () => { }); }); + it("should render with organization entity type and call organization API", async () => { + const { getByText, getAllByText } = render(); + + await waitFor(() => { + expect(mockOrganizationDailyActivityCall).toHaveBeenCalled(); + }); + + expect(getByText("Organization Spend Overview")).toBeInTheDocument(); + + const spendElements = getAllByText("$100.50"); + expect(spendElements.length).toBeGreaterThan(0); + }); + it("should switch between tabs", async () => { render(); diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index fc1d03a3729..a5789b7dbac 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -23,7 +23,7 @@ import { } from "@tremor/react"; import { ActivityMetrics, processActivityData } from "./activity_metrics"; import { DailyData, BreakdownMetrics, KeyMetricWithMetadata, EntityMetricWithMetadata, TagUsage } from "./usage/types"; -import { tagDailyActivityCall, teamDailyActivityCall } from "./networking"; +import { organizationDailyActivityCall, tagDailyActivityCall, teamDailyActivityCall } from "./networking"; import TopKeyView from "./top_key_view"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { valueFormatterSpend } from "./usage/utils/value_formatters"; @@ -68,7 +68,7 @@ export interface EntityList { interface EntityUsageProps { accessToken: string | null; - entityType: "tag" | "team"; + entityType: "tag" | "team" | "organization"; entityId?: string | null; userID: string | null; userRole: string | null; @@ -126,6 +126,15 @@ const EntityUsage: React.FC = ({ selectedTags.length > 0 ? selectedTags : null, ); setSpendData(data); + } else if (entityType === "organization") { + const data = await organizationDailyActivityCall( + accessToken, + startTime, + endTime, + 1, + selectedTags.length > 0 ? selectedTags : null, + ); + setSpendData(data); } else { throw new Error("Invalid entity type"); } @@ -325,6 +334,16 @@ const EntityUsage: React.FC = ({ })); }; + const getFilterLabel = (entityType: string) => { + return `Filter by ${entityType}`; + }; + + const getFilterPlaceholder = (entityType: string) => { + return `Select ${entityType} to filter...`; + }; + + const capitalizedEntityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1); + return (
= ({ entityType={entityType} spendData={spendData} showFilters={entityList !== null && entityList.length > 0} - filterLabel={`Filter by ${entityType === "tag" ? "Tags" : "Teams"}`} - filterPlaceholder={`Select ${entityType === "tag" ? "tags" : "teams"} to filter...`} + filterLabel={getFilterLabel(entityType)} + filterPlaceholder={getFilterPlaceholder(entityType)} selectedFilters={selectedTags} onFiltersChange={setSelectedTags} filterOptions={getAllTags() || undefined} @@ -350,7 +369,7 @@ const EntityUsage: React.FC = ({ {/* Total Spend Card */} - {entityType === "tag" ? "Tag" : "Team"} Spend Overview + {capitalizedEntityLabel} Spend Overview Total Spend @@ -413,10 +432,10 @@ const EntityUsage: React.FC = ({

Failed: {data.metrics.failed_requests}

Total Tokens: {data.metrics.total_tokens}

- {entityType === "tag" ? "Total Tags" : "Total Teams"}: {entityCount} + Total {capitalizedEntityLabel}s: {entityCount}

-

Spend by {entityType === "tag" ? "Tag" : "Team"}:

+

Spend by {capitalizedEntityLabel}:

{Object.entries(data.breakdown.entities || {}) .sort(([, a], [, b]) => { const spendA = (a as EntityMetrics).metrics.spend; @@ -449,10 +468,10 @@ const EntityUsage: React.FC = ({
- Spend Per {entityType === "tag" ? "Tag" : "Team"} + Spend Per {capitalizedEntityLabel} Showing Top 5 by Spend
- Get Started by Tracking cost per {entityType} + Get Started by Tracking cost per {capitalizedEntityLabel} = ({ - {entityType === "tag" ? "Tag" : "Team"} + {capitalizedEntityLabel} Spend Successful Failed diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 87c8563e467..14763930aaf 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -143,7 +143,7 @@ export interface ListPromptsResponse { } export interface Organization { - organization_id: string | null; + organization_id: string; organization_alias: string; budget_id: string; metadata: Record; @@ -1570,21 +1570,70 @@ export const transformRequestCall = async (accessToken: string, request: object) } }; -export const userDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date, page: number = 1) => { - /** - * Get daily user activity on proxy - */ - try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/user/daily/activity` : `/user/daily/activity`; - const queryParams = new URLSearchParams(); - queryParams.append("start_date", formatDate(startTime)); - queryParams.append("end_date", formatDate(endTime)); - queryParams.append("page_size", "1000"); - queryParams.append("page", page.toString()); - const queryString = queryParams.toString(); - if (queryString) { - url += `?${queryString}`; +type DailyActivityQueryValue = string | number | string[] | null | undefined; + +const DEFAULT_DAILY_ACTIVITY_PAGE_SIZE = "1000"; + +const appendDailyActivityQueryParam = (params: URLSearchParams, key: string, value: DailyActivityQueryValue) => { + if (value === null || value === undefined) { + return; + } + + if (Array.isArray(value)) { + if (value.length > 0) { + params.append(key, value.join(",")); } + return; + } + + params.append(key, `${value}`); +}; + +const buildDailyActivityUrl = ( + endpoint: string, + startTime: Date, + endTime: Date, + page: number, + extraQueryParams?: Record, +) => { + const resolvedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; + const baseUrl = proxyBaseUrl ? `${proxyBaseUrl}${resolvedEndpoint}` : resolvedEndpoint; + + const params = new URLSearchParams(); + params.append("start_date", formatDate(startTime)); + params.append("end_date", formatDate(endTime)); + params.append("page_size", DEFAULT_DAILY_ACTIVITY_PAGE_SIZE); + params.append("page", page.toString()); + + if (extraQueryParams) { + Object.entries(extraQueryParams).forEach(([key, value]) => { + appendDailyActivityQueryParam(params, key, value); + }); + } + + const queryString = params.toString(); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; +}; + +type DailyActivityCallOptions = { + accessToken: string; + endpoint: string; + startTime: Date; + endTime: Date; + page?: number; + extraQueryParams?: Record; +}; + +const fetchDailyActivity = async ({ + accessToken, + endpoint, + startTime, + endTime, + page = 1, + extraQueryParams, +}: DailyActivityCallOptions) => { + try { + const url = buildDailyActivityUrl(endpoint, startTime, endTime, page, extraQueryParams); const response = await fetch(url, { method: "GET", @@ -1604,11 +1653,24 @@ export const userDailyActivityCall = async (accessToken: string, startTime: Date const data = await response.json(); return data; } catch (error) { - console.error("Failed to create key:", error); + console.error(`Failed to fetch daily activity (${endpoint}):`, error); throw error; } }; +export const userDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date, page: number = 1) => { + /** + * Get daily user activity on proxy + */ + return fetchDailyActivity({ + accessToken, + endpoint: "/user/daily/activity", + startTime, + endTime, + page, + }); +}; + export const tagDailyActivityCall = async ( accessToken: string, startTime: Date, @@ -1619,42 +1681,16 @@ export const tagDailyActivityCall = async ( /** * Get daily user activity on proxy */ - try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/daily/activity` : `/tag/daily/activity`; - const queryParams = new URLSearchParams(); - queryParams.append("start_date", formatDate(startTime)); - queryParams.append("end_date", formatDate(endTime)); - queryParams.append("page_size", "1000"); - queryParams.append("page", page.toString()); - if (tags) { - queryParams.append("tags", tags.join(",")); - } - const queryString = queryParams.toString(); - if (queryString) { - url += `?${queryString}`; - } - - const response = await fetch(url, { - method: "GET", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - const errorData = await response.json(); - const errorMessage = deriveErrorMessage(errorData); - handleError(errorMessage); - throw new Error(errorMessage); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error("Failed to create key:", error); - throw error; - } + return fetchDailyActivity({ + accessToken, + endpoint: "/tag/daily/activity", + startTime, + endTime, + page, + extraQueryParams: { + tags, + }, + }); }; export const teamDailyActivityCall = async ( @@ -1667,43 +1703,36 @@ export const teamDailyActivityCall = async ( /** * Get daily user activity on proxy */ - try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/team/daily/activity` : `/team/daily/activity`; - const queryParams = new URLSearchParams(); - queryParams.append("start_date", formatDate(startTime)); - queryParams.append("end_date", formatDate(endTime)); - queryParams.append("page_size", "1000"); - queryParams.append("page", page.toString()); - if (teamIds) { - queryParams.append("team_ids", teamIds.join(",")); - } - queryParams.append("exclude_team_ids", "litellm-dashboard"); - const queryString = queryParams.toString(); - if (queryString) { - url += `?${queryString}`; - } + return fetchDailyActivity({ + accessToken, + endpoint: "/team/daily/activity", + startTime, + endTime, + page, + extraQueryParams: { + team_ids: teamIds, + exclude_team_ids: "litellm-dashboard", + }, + }); +}; - const response = await fetch(url, { - method: "GET", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - const errorData = await response.json(); - const errorMessage = deriveErrorMessage(errorData); - handleError(errorMessage); - throw new Error(errorMessage); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error("Failed to create key:", error); - throw error; - } +export const organizationDailyActivityCall = async ( + accessToken: string, + startTime: Date, + endTime: Date, + page: number = 1, + organizationIds: string[] | null = null, +) => { + return fetchDailyActivity({ + accessToken, + endpoint: "/organization/daily/activity", + startTime, + endTime, + page, + extraQueryParams: { + organization_ids: organizationIds, + }, + }); }; export const getTotalSpendCall = async (accessToken: string) => { diff --git a/ui/litellm-dashboard/src/components/new_usage.test.tsx b/ui/litellm-dashboard/src/components/new_usage.test.tsx index 6c426401c54..a4969124f1f 100644 --- a/ui/litellm-dashboard/src/components/new_usage.test.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.test.tsx @@ -1,6 +1,7 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; import NewUsagePage from "./new_usage"; +import type { Organization } from "./networking"; import * as networking from "./networking"; // Polyfill ResizeObserver for test environment @@ -153,6 +154,26 @@ describe("NewUsage", () => { }, }; + const mockOrganizations: Organization[] = [ + { + organization_id: "org-123", + organization_alias: "Acme Org", + budget_id: "budget-1", + metadata: {}, + models: [], + spend: 0, + model_spend: {}, + created_at: "2025-01-01T00:00:00Z", + created_by: "user-123", + updated_at: "2025-01-02T00:00:00Z", + updated_by: "user-123", + litellm_budget_table: null, + teams: null, + users: null, + members: null, + }, + ]; + const defaultProps = { accessToken: "test-token", userRole: "Admin", @@ -175,6 +196,7 @@ describe("NewUsage", () => { members_with_roles: [], }, ], + organizations: [], premiumUser: true, }; @@ -250,4 +272,21 @@ describe("NewUsage", () => { expect(entityUsageElements.length).toBeGreaterThan(0); }); }); + + it("should show organization usage banner and tab for admins", async () => { + const { getByText, getAllByText } = render(); + + await waitFor(() => { + expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); + }); + + const organizationTab = getByText("Organization Usage"); + fireEvent.click(organizationTab); + + await waitFor(() => { + expect(getByText("Organization usage is a new feature.")).toBeInTheDocument(); + const entityUsageElements = getAllByText("Entity Usage"); + expect(entityUsageElements.length).toBeGreaterThan(0); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx index 95d721ce9ba..4794a7f091d 100644 --- a/ui/litellm-dashboard/src/components/new_usage.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -6,58 +6,66 @@ * Works at 1m+ spend logs, by querying an aggregate table instead. */ -import React, { useState, useEffect, useMemo, useCallback } from "react"; import { BarChart, Card, - Title, - Text, - Grid, Col, - TabGroup, - TabList, - Tab, - TabPanel, - TabPanels, + DateRangePickerValue, DonutChart, + Grid, + Tab, + TabGroup, Table, - TableHead, - TableRow, - TableHeaderCell, TableBody, TableCell, - DateRangePickerValue, + TableHead, + TableHeaderCell, + TableRow, + TabList, + TabPanel, + TabPanels, + Text, + Title, } from "@tremor/react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { Alert } from "antd"; -import { userDailyActivityCall, userDailyActivityAggregatedCall, tagListCall } from "./networking"; -import { Tag } from "./tag_management/types"; -import ViewUserSpend from "./view_user_spend"; -import TopKeyView from "./top_key_view"; -import { ActivityMetrics, processActivityData } from "./activity_metrics"; -import UserAgentActivity from "./user_agent_activity"; -import { DailyData, MetricWithMetadata, KeyMetricWithMetadata } from "./usage/types"; -import EntityUsage from "./entity_usage"; -import { all_admin_roles } from "../utils/roles"; -import { Team } from "./key_team_helpers/key_list"; -import { EntityList } from "./entity_usage"; import { formatNumberWithCommas } from "@/utils/dataUtils"; -import { valueFormatterSpend } from "./usage/utils/value_formatters"; -import CloudZeroExportModal from "./cloudzero_export_modal"; -import { ChartLoader } from "./shared/chart_loader"; -import { getProviderLogoAndName } from "./provider_info_helpers"; -import EntityUsageExportModal from "./EntityUsageExport"; -import AdvancedDatePicker from "./shared/advanced_date_picker"; import { Button } from "@tremor/react"; +import { all_admin_roles } from "../utils/roles"; +import { ActivityMetrics, processActivityData } from "./activity_metrics"; +import CloudZeroExportModal from "./cloudzero_export_modal"; +import EntityUsage, { EntityList } from "./entity_usage"; +import EntityUsageExportModal from "./EntityUsageExport"; +import { Team } from "./key_team_helpers/key_list"; +import { Organization, tagListCall, userDailyActivityAggregatedCall, userDailyActivityCall } from "./networking"; +import { getProviderLogoAndName } from "./provider_info_helpers"; +import AdvancedDatePicker from "./shared/advanced_date_picker"; +import { ChartLoader } from "./shared/chart_loader"; +import { Tag } from "./tag_management/types"; +import TopKeyView from "./top_key_view"; +import { DailyData, KeyMetricWithMetadata, MetricWithMetadata } from "./usage/types"; +import { valueFormatterSpend } from "./usage/utils/value_formatters"; +import UserAgentActivity from "./user_agent_activity"; +import ViewUserSpend from "./view_user_spend"; interface NewUsagePageProps { accessToken: string | null; userRole: string | null; userID: string | null; teams: Team[]; + organizations: Organization[]; premiumUser: boolean; } -const NewUsagePage: React.FC = ({ accessToken, userRole, userID, teams, premiumUser }) => { +const NewUsagePage: React.FC = ({ + accessToken, + userRole, + userID, + teams, + organizations, + premiumUser, +}) => { const [userSpendData, setUserSpendData] = useState<{ results: DailyData[]; metadata: any; @@ -81,6 +89,7 @@ const NewUsagePage: React.FC = ({ accessToken, userRole, user const [modelViewType, setModelViewType] = useState<"groups" | "individual">("groups"); const [isCloudZeroModalOpen, setIsCloudZeroModalOpen] = useState(false); const [isGlobalExportModalOpen, setIsGlobalExportModalOpen] = useState(false); + const [showOrganizationBanner, setShowOrganizationBanner] = useState(true); const getAllTags = async () => { if (!accessToken) { @@ -415,6 +424,11 @@ const NewUsagePage: React.FC = ({ accessToken, userRole, user
{all_admin_roles.includes(userRole || "") ? Global Usage : Your Usage} + {all_admin_roles.includes(userRole || "") ? ( + Organization Usage + ) : ( + Your Organization Usage + )} Team Usage {all_admin_roles.includes(userRole || "") ? Tag Usage : <>} {all_admin_roles.includes(userRole || "") ? User Agent Activity : <>} @@ -737,6 +751,35 @@ const NewUsagePage: React.FC = ({ accessToken, userRole, user + {/* Organization Usage Panel */} + + {showOrganizationBanner && ( + setShowOrganizationBanner(false)} + className="mb-5" + /> + )} + ({ + label: organization.organization_alias, + value: organization.organization_id, + })) || null + } + premiumUser={premiumUser} + /> + + {/* Team Usage Panel */} Date: Thu, 27 Nov 2025 15:46:04 +0530 Subject: [PATCH 163/349] Added support for twelvelabs pegasus --- litellm/__init__.py | 3 + litellm/constants.py | 1 + ...mazon_twelvelabs_pegasus_transformation.py | 133 ++++++++++++++++++ litellm/llms/bedrock/common_utils.py | 2 + .../test_twelvelabs_pegasus_transformation.py | 85 +++++++++++ 5 files changed, 224 insertions(+) create mode 100644 litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py create mode 100644 tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_twelvelabs_pegasus_transformation.py diff --git a/litellm/__init__.py b/litellm/__init__.py index aebf1404196..e6af8a21ff5 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1222,6 +1222,9 @@ from .llms.bedrock.chat.invoke_transformations.amazon_mistral_transformation imp from .llms.bedrock.chat.invoke_transformations.amazon_titan_transformation import ( AmazonTitanConfig, ) +from .llms.bedrock.chat.invoke_transformations.amazon_twelvelabs_pegasus_transformation import ( + AmazonTwelveLabsPegasusConfig, +) from .llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( AmazonInvokeConfig, ) diff --git a/litellm/constants.py b/litellm/constants.py index cf3d4c6e742..00e17788466 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -851,6 +851,7 @@ BEDROCK_INVOKE_PROVIDERS_LITERAL = Literal[ "nova", "deepseek_r1", "qwen3", + "twelvelabs", ] BEDROCK_EMBEDDING_PROVIDERS_LITERAL = Literal[ diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py new file mode 100644 index 00000000000..7b72968ea3d --- /dev/null +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py @@ -0,0 +1,133 @@ +""" +Transforms OpenAI-style requests into TwelveLabs Pegasus 1.2 requests for Bedrock. + +Reference: +https://docs.twelvelabs.io/docs/models/pegasus +""" + +from typing import Any, Dict, List, Optional + +from litellm.llms.base_llm.base_utils import type_to_response_format_param +from litellm.llms.base_llm.chat.transformation import BaseConfig +from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( + AmazonInvokeConfig, +) +from litellm.types.llms.openai import AllMessageValues +from litellm.utils import get_base64_str + + +class AmazonTwelveLabsPegasusConfig(AmazonInvokeConfig, BaseConfig): + """ + Handles transforming OpenAI-style requests into Bedrock InvokeModel requests for + `twelvelabs.pegasus-1-2-v1:0`. + + Pegasus 1.2 requires an `inputPrompt` and a `mediaSource` that either references + an S3 object or a base64-encoded clip. Optional OpenAI params (temperature, + response_format, max_tokens) are translated to the TwelveLabs schema. + """ + + def get_supported_openai_params(self, model: str) -> List[str]: + return [ + "max_tokens", + "max_completion_tokens", + "temperature", + "response_format", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + for param, value in non_default_params.items(): + if param in {"max_tokens", "max_completion_tokens"}: + optional_params["maxOutputTokens"] = value + if param == "temperature": + optional_params["temperature"] = value + if param == "response_format": + optional_params["responseFormat"] = self._normalize_response_format( + value + ) + return optional_params + + def _normalize_response_format(self, value: Any) -> Any: + if isinstance(value, dict): + return value + return type_to_response_format_param(response_format=value) or value + + def transform_request( + self, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + input_prompt = self._convert_messages_to_prompt(messages=messages) + request_data: Dict[str, Any] = {"inputPrompt": input_prompt} + + media_source = self._build_media_source(optional_params) + if media_source is not None: + request_data["mediaSource"] = media_source + + for key in ("temperature", "maxOutputTokens", "responseFormat"): + if key in optional_params: + request_data[key] = optional_params.get(key) + return request_data + + def _build_media_source(self, optional_params: dict) -> Optional[dict]: + direct_source = optional_params.get("mediaSource") or optional_params.get( + "media_source" + ) + if isinstance(direct_source, dict): + return direct_source + + base64_input = optional_params.get("video_base64") or optional_params.get( + "base64_string" + ) + if base64_input: + return {"base64String": get_base64_str(base64_input)} + + s3_uri = ( + optional_params.get("video_s3_uri") + or optional_params.get("s3_uri") + or optional_params.get("media_source_s3_uri") + ) + if s3_uri: + s3_location = {"uri": s3_uri} + bucket_owner = ( + optional_params.get("video_s3_bucket_owner") + or optional_params.get("s3_bucket_owner") + or optional_params.get("media_source_bucket_owner") + ) + if bucket_owner: + s3_location["bucketOwner"] = bucket_owner + return {"s3Location": s3_location} + return None + + def _convert_messages_to_prompt(self, messages: List[AllMessageValues]) -> str: + prompt_parts: List[str] = [] + for message in messages: + role = message.get("role", "user") + content = message.get("content", "") + if isinstance(content, list): + text_fragments = [] + for item in content: + if isinstance(item, dict): + item_type = item.get("type") + if item_type == "text": + text_fragments.append(item.get("text", "")) + elif item_type == "image_url": + text_fragments.append("") + elif item_type == "video_url": + text_fragments.append("

Order matters: Models will be tried in the order shown above (1st, 2nd, 3rd, etc.) From d612d71ef427c2e2de01448d69e81979c7d14f93 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Nov 2025 12:06:53 -0800 Subject: [PATCH 181/349] [Feat] Add guardrails for pass through endpoints (#17221) * add PassThroughGuardrailsConfig * init JsonPathExtractor * feat PassthroughGuardrailHandler * feat pt guardrails * pt guardrails * add Pass-Through Endpoint Guardrail Translation * add PassThroughEndpointHandler * execute simple guardrail config and dict settings * TestPassthroughGuardrailHandlerNormalizeConfig * add passthrough_guardrails_config on litellm logging obj * add LiteLLMLoggingObj to base trasaltino * cleaner _get_guardrail_settings * update guardrails settings * docs pt guardrail * docs Guardrails on Pass-Through Endpoints * fix typing * fix typing * test_no_fields_set_sends_full_body * fix typing * Potential fix for code scanning alert no. 3834: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../docs/proxy/pass_through_guardrails.md | 214 +++++++++++ docs/my-website/sidebars.js | 3 +- litellm/litellm_core_utils/litellm_logging.py | 3 + .../chat/guardrail_translation/handler.py | 2 + .../guardrail_translation/base_translation.py | 5 +- .../rerank/guardrail_translation/handler.py | 4 +- .../chat/guardrail_translation/handler.py | 2 + .../guardrail_translation/handler.py | 4 +- .../guardrail_translation/handler.py | 4 +- .../guardrail_translation/handler.py | 2 + .../speech/guardrail_translation/handler.py | 4 +- .../guardrail_translation/handler.py | 4 +- litellm/llms/pass_through/__init__.py | 12 + .../guardrail_translation/README.md | 41 +++ .../guardrail_translation/__init__.py | 15 + .../guardrail_translation/handler.py | 165 +++++++++ litellm/proxy/_types.py | 27 +- .../unified_guardrail/unified_guardrail.py | 2 + .../jsonpath_extractor.py | 95 +++++ .../pass_through_endpoints.py | 72 +++- .../passthrough_guardrails.py | 333 ++++++++++++++++++ litellm/proxy/proxy_config.yaml | 17 +- ..._passthrough_guardrails_field_targeting.py | 119 +++++++ .../test_passthrough_guardrails.py | 263 ++++++++++++++ 24 files changed, 1378 insertions(+), 34 deletions(-) create mode 100644 docs/my-website/docs/proxy/pass_through_guardrails.md create mode 100644 litellm/llms/pass_through/__init__.py create mode 100644 litellm/llms/pass_through/guardrail_translation/README.md create mode 100644 litellm/llms/pass_through/guardrail_translation/__init__.py create mode 100644 litellm/llms/pass_through/guardrail_translation/handler.py create mode 100644 litellm/proxy/pass_through_endpoints/jsonpath_extractor.py create mode 100644 litellm/proxy/pass_through_endpoints/passthrough_guardrails.py create mode 100644 test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails_field_targeting.py create mode 100644 tests/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails.py diff --git a/docs/my-website/docs/proxy/pass_through_guardrails.md b/docs/my-website/docs/proxy/pass_through_guardrails.md new file mode 100644 index 00000000000..272285e61c0 --- /dev/null +++ b/docs/my-website/docs/proxy/pass_through_guardrails.md @@ -0,0 +1,214 @@ +# Guardrails on Pass-Through Endpoints + +## Overview + +| Property | Details | +|----------|---------| +| Description | Enable guardrail execution on LiteLLM pass-through endpoints with opt-in activation and automatic inheritance from org/team/key levels | +| Supported Guardrails | All LiteLLM guardrails (Bedrock, Aporia, Lakera, etc.) | +| Default Behavior | Guardrails are **disabled** on pass-through endpoints unless explicitly enabled | + +## Quick Start + +### 1. Define guardrails and pass-through endpoint + +```yaml showLineNumbers title="config.yaml" +guardrails: + - guardrail_name: "pii-guard" + litellm_params: + guardrail: bedrock + mode: pre_call + guardrailIdentifier: "your-guardrail-id" + guardrailVersion: "1" + +general_settings: + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + headers: + Authorization: "bearer os.environ/COHERE_API_KEY" + guardrails: + pii-guard: +``` + +### 2. Start proxy + +```bash +litellm --config config.yaml +``` + +### 3. Test request + +```bash +curl -X POST "http://localhost:4000/v1/rerank" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-1234" \ + -d '{ + "model": "rerank-english-v3.0", + "query": "What is the capital of France?", + "documents": ["Paris is the capital of France."] + }' +``` + +--- + +## Opt-In Behavior + +| Configuration | Behavior | +|--------------|----------| +| `guardrails` not set | No guardrails execute (default) | +| `guardrails` set | All org/team/key + pass-through guardrails execute | + +When guardrails are enabled, the system collects and executes: +- Org-level guardrails +- Team-level guardrails +- Key-level guardrails +- Pass-through specific guardrails + +--- + + +## How It Works + +The diagram below shows what happens when a client makes a request to `/special/rerank` - a pass-through endpoint configured with guardrails in your `config.yaml`. + +When guardrails are configured on a pass-through endpoint: +1. **Pre-call guardrails** run on the request before forwarding to the target API +2. If `request_fields` is specified (e.g., `["query"]`), only those fields are sent to the guardrail. Otherwise, the entire request payload is evaluated. +3. The request is forwarded to the target API only if guardrails pass +4. **Post-call guardrails** run on the response from the target API +5. If `response_fields` is specified (e.g., `["results[*].text"]`), only those fields are evaluated. Otherwise, the entire response is checked. + +:::info +If the `guardrails` block is omitted or empty in your pass-through endpoint config, the request skips the guardrail flow entirely and goes directly to the target API. +::: + +```mermaid +sequenceDiagram + participant Client + box rgb(200, 220, 255) LiteLLM Proxy + participant PassThrough as Pass-through Endpoint + participant Guardrails + end + participant Target as Target API (Cohere, etc.) + + Client->>PassThrough: POST /special/rerank + Note over PassThrough,Guardrails: Collect passthrough + org/team/key guardrails + PassThrough->>Guardrails: Run pre_call (request_fields or full payload) + Guardrails-->>PassThrough: ✓ Pass / ✗ Block + PassThrough->>Target: Forward request + Target-->>PassThrough: Response + PassThrough->>Guardrails: Run post_call (response_fields or full payload) + Guardrails-->>PassThrough: ✓ Pass / ✗ Block + PassThrough-->>Client: Return response (or error) +``` + +--- + +## Field-Level Targeting + +Target specific JSON fields instead of the entire request/response payload. + +```yaml showLineNumbers title="config.yaml" +guardrails: + - guardrail_name: "pii-detection" + litellm_params: + guardrail: bedrock + mode: pre_call + guardrailIdentifier: "pii-guard-id" + guardrailVersion: "1" + + - guardrail_name: "content-moderation" + litellm_params: + guardrail: bedrock + mode: post_call + guardrailIdentifier: "content-guard-id" + guardrailVersion: "1" + +general_settings: + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + headers: + Authorization: "bearer os.environ/COHERE_API_KEY" + guardrails: + pii-detection: + request_fields: ["query", "documents[*].text"] + content-moderation: + response_fields: ["results[*].text"] +``` + +### Field Options + +| Field | Description | +|-------|-------------| +| `request_fields` | JSONPath expressions for input (pre_call) | +| `response_fields` | JSONPath expressions for output (post_call) | +| Neither specified | Guardrail runs on entire payload | + +### JSONPath Examples + +| Expression | Matches | +|------------|---------| +| `query` | Single field named `query` | +| `documents[*].text` | All `text` fields in `documents` array | +| `messages[*].content` | All `content` fields in `messages` array | + +--- + +## Configuration Examples + +### Single guardrail on entire payload + +```yaml showLineNumbers title="config.yaml" +guardrails: + - guardrail_name: "pii-detection" + litellm_params: + guardrail: bedrock + mode: pre_call + guardrailIdentifier: "your-id" + guardrailVersion: "1" + +general_settings: + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + guardrails: + pii-detection: +``` + +### Multiple guardrails with mixed settings + +```yaml showLineNumbers title="config.yaml" +guardrails: + - guardrail_name: "pii-detection" + litellm_params: + guardrail: bedrock + mode: pre_call + guardrailIdentifier: "pii-id" + guardrailVersion: "1" + + - guardrail_name: "content-moderation" + litellm_params: + guardrail: bedrock + mode: post_call + guardrailIdentifier: "content-id" + guardrailVersion: "1" + + - guardrail_name: "prompt-injection" + litellm_params: + guardrail: lakera + mode: pre_call + api_key: os.environ/LAKERA_API_KEY + +general_settings: + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + guardrails: + pii-detection: + request_fields: ["input", "query"] + content-moderation: + prompt-injection: + request_fields: ["messages[*].content"] +``` diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 2002550a258..917fdca6020 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -419,7 +419,8 @@ const sidebars = { ] }, "pass_through/vllm", - "proxy/pass_through" + "proxy/pass_through", + "proxy/pass_through_guardrails" ] }, "rag_ingest", diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 305b7d6ddc3..0b0f483ff74 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -374,6 +374,9 @@ class Logging(LiteLLMLoggingBaseClass): # Init Caching related details self.caching_details: Optional[CachingDetails] = None + # Passthrough endpoint guardrails config for field targeting + self.passthrough_guardrails_config: Optional[Dict[str, Any]] = None + self.model_call_details: Dict[str, Any] = { "litellm_trace_id": litellm_trace_id, "litellm_call_id": litellm_call_id, diff --git a/litellm/llms/anthropic/chat/guardrail_translation/handler.py b/litellm/llms/anthropic/chat/guardrail_translation/handler.py index 06a1b92e1b0..6aba2947d3b 100644 --- a/litellm/llms/anthropic/chat/guardrail_translation/handler.py +++ b/litellm/llms/anthropic/chat/guardrail_translation/handler.py @@ -41,6 +41,7 @@ class AnthropicMessagesHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input messages by applying guardrails to text content. @@ -145,6 +146,7 @@ class AnthropicMessagesHandler(BaseTranslation): self, response: "AnthropicMessagesResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response by applying guardrails to text content. diff --git a/litellm/llms/base_llm/guardrail_translation/base_translation.py b/litellm/llms/base_llm/guardrail_translation/base_translation.py index 4599af1b745..926ad59cee4 100644 --- a/litellm/llms/base_llm/guardrail_translation/base_translation.py +++ b/litellm/llms/base_llm/guardrail_translation/base_translation.py @@ -1,8 +1,9 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from litellm.integrations.custom_guardrail import CustomGuardrail + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj class BaseTranslation(ABC): @@ -11,6 +12,7 @@ class BaseTranslation(ABC): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, ) -> Any: pass @@ -19,5 +21,6 @@ class BaseTranslation(ABC): self, response: Any, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, ) -> Any: pass diff --git a/litellm/llms/cohere/rerank/guardrail_translation/handler.py b/litellm/llms/cohere/rerank/guardrail_translation/handler.py index 0c5e50dc41e..a5a5ef68b8d 100644 --- a/litellm/llms/cohere/rerank/guardrail_translation/handler.py +++ b/litellm/llms/cohere/rerank/guardrail_translation/handler.py @@ -5,7 +5,7 @@ This module provides guardrail translation support for the rerank endpoint. The handler processes only the 'query' parameter for guardrails. """ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation @@ -34,6 +34,7 @@ class CohereRerankHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input query by applying guardrails. @@ -68,6 +69,7 @@ class CohereRerankHandler(BaseTranslation): self, response: "RerankResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response - not applicable for rerank. diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index b01f9f1b980..2a421a8283b 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -42,6 +42,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input messages by applying guardrails to text content. @@ -148,6 +149,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): self, response: "ModelResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response by applying guardrails to text content. diff --git a/litellm/llms/openai/completion/guardrail_translation/handler.py b/litellm/llms/openai/completion/guardrail_translation/handler.py index b5db730620e..5a38d04d75c 100644 --- a/litellm/llms/openai/completion/guardrail_translation/handler.py +++ b/litellm/llms/openai/completion/guardrail_translation/handler.py @@ -5,7 +5,7 @@ This module provides guardrail translation support for OpenAI's text completion The handler processes the 'prompt' parameter for guardrails. """ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation @@ -32,6 +32,7 @@ class OpenAITextCompletionHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input prompt by applying guardrails to text content. @@ -100,6 +101,7 @@ class OpenAITextCompletionHandler(BaseTranslation): self, response: "TextCompletionResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response by applying guardrails to completion text. diff --git a/litellm/llms/openai/image_generation/guardrail_translation/handler.py b/litellm/llms/openai/image_generation/guardrail_translation/handler.py index 5fcb5278f01..de6bca8e571 100644 --- a/litellm/llms/openai/image_generation/guardrail_translation/handler.py +++ b/litellm/llms/openai/image_generation/guardrail_translation/handler.py @@ -5,7 +5,7 @@ This module provides guardrail translation support for OpenAI's image generation The handler processes the 'prompt' parameter for guardrails. """ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation @@ -31,6 +31,7 @@ class OpenAIImageGenerationHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input prompt by applying guardrails to text content. @@ -72,6 +73,7 @@ class OpenAIImageGenerationHandler(BaseTranslation): self, response: "ImageResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response - typically not needed for image generation. diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index fdac13176b1..489a89c60cf 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -56,6 +56,7 @@ class OpenAIResponsesHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input by applying guardrails to text content. @@ -177,6 +178,7 @@ class OpenAIResponsesHandler(BaseTranslation): self, response: "ResponsesAPIResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output response by applying guardrails to text content. diff --git a/litellm/llms/openai/speech/guardrail_translation/handler.py b/litellm/llms/openai/speech/guardrail_translation/handler.py index aa049801d16..47df79833bf 100644 --- a/litellm/llms/openai/speech/guardrail_translation/handler.py +++ b/litellm/llms/openai/speech/guardrail_translation/handler.py @@ -5,7 +5,7 @@ This module provides guardrail translation support for OpenAI's text-to-speech e The handler processes the 'input' text parameter (output is audio, so no text to guardrail). """ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation @@ -30,6 +30,7 @@ class OpenAITextToSpeechHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input text by applying guardrails. @@ -72,6 +73,7 @@ class OpenAITextToSpeechHandler(BaseTranslation): self, response: "HttpxBinaryResponseContent", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output - not applicable for text-to-speech. diff --git a/litellm/llms/openai/transcriptions/guardrail_translation/handler.py b/litellm/llms/openai/transcriptions/guardrail_translation/handler.py index 22b93251be6..51f50c9180a 100644 --- a/litellm/llms/openai/transcriptions/guardrail_translation/handler.py +++ b/litellm/llms/openai/transcriptions/guardrail_translation/handler.py @@ -5,7 +5,7 @@ This module provides guardrail translation support for OpenAI's audio transcript The handler processes the output transcribed text (input is audio, so no text to guardrail). """ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation @@ -30,6 +30,7 @@ class OpenAIAudioTranscriptionHandler(BaseTranslation): self, data: dict, guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process input - not applicable for audio transcription. @@ -54,6 +55,7 @@ class OpenAIAudioTranscriptionHandler(BaseTranslation): self, response: "TranscriptionResponse", guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, ) -> Any: """ Process output transcription by applying guardrails to transcribed text. diff --git a/litellm/llms/pass_through/__init__.py b/litellm/llms/pass_through/__init__.py new file mode 100644 index 00000000000..803772f14d6 --- /dev/null +++ b/litellm/llms/pass_through/__init__.py @@ -0,0 +1,12 @@ +""" +Pass-Through Endpoint Guardrail Translation + +This module exists here (under litellm/llms/) so it can be auto-discovered by +load_guardrail_translation_mappings() which scans for guardrail_translation +directories under litellm/llms/. + +The main passthrough endpoint implementation is in: + litellm/proxy/pass_through_endpoints/ + +See guardrail_translation/README.md for more details. +""" diff --git a/litellm/llms/pass_through/guardrail_translation/README.md b/litellm/llms/pass_through/guardrail_translation/README.md new file mode 100644 index 00000000000..db4c0704e75 --- /dev/null +++ b/litellm/llms/pass_through/guardrail_translation/README.md @@ -0,0 +1,41 @@ +# Pass-Through Endpoint Guardrail Translation + +## Why This Exists Here + +This module is located under `litellm/llms/` (instead of with the main passthrough code) because: + +1. **Auto-discovery**: The `load_guardrail_translation_mappings()` function in `litellm/llms/__init__.py` scans for `guardrail_translation/` directories under `litellm/llms/` +2. **Consistency**: All other guardrail translation handlers follow this pattern (e.g., `openai/chat/guardrail_translation/`, `anthropic/chat/guardrail_translation/`) + +## Main Passthrough Implementation + +The main passthrough endpoint implementation is in: + +``` +litellm/proxy/pass_through_endpoints/ +├── pass_through_endpoints.py # Core passthrough routing logic +├── passthrough_guardrails.py # Guardrail collection and field targeting +├── jsonpath_extractor.py # JSONPath field extraction utility +└── ... +``` + +## What This Handler Does + +The `PassThroughEndpointHandler` enables guardrails to run on passthrough endpoint requests by: + +1. **Field Targeting**: Extracts specific fields from the request/response using JSONPath expressions configured in `request_fields` / `response_fields` +2. **Full Payload Fallback**: If no field targeting is configured, processes the entire payload +3. **Config Access**: Uses `get_passthrough_guardrails_config()` / `set_passthrough_guardrails_config()` helpers to access the passthrough guardrails configuration stored in request metadata + +## Example Config + +```yaml +passthrough_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + guardrails: + bedrock-pre-guard: + request_fields: ["query", "documents[*].text"] + response_fields: ["results[*].text"] +``` + diff --git a/litellm/llms/pass_through/guardrail_translation/__init__.py b/litellm/llms/pass_through/guardrail_translation/__init__.py new file mode 100644 index 00000000000..db69c8e378a --- /dev/null +++ b/litellm/llms/pass_through/guardrail_translation/__init__.py @@ -0,0 +1,15 @@ +"""Pass-Through Endpoint guardrail translation handler.""" + +from litellm.llms.pass_through.guardrail_translation.handler import ( + PassThroughEndpointHandler, +) +from litellm.types.utils import CallTypes + +guardrail_translation_mappings = { + CallTypes.pass_through: PassThroughEndpointHandler, +} + +__all__ = [ + "guardrail_translation_mappings", + "PassThroughEndpointHandler", +] diff --git a/litellm/llms/pass_through/guardrail_translation/handler.py b/litellm/llms/pass_through/guardrail_translation/handler.py new file mode 100644 index 00000000000..5ff9fd25c5f --- /dev/null +++ b/litellm/llms/pass_through/guardrail_translation/handler.py @@ -0,0 +1,165 @@ +""" +Pass-Through Endpoint Message Handler for Unified Guardrails + +This module provides a handler for passthrough endpoint requests. +It uses the field targeting configuration from litellm_logging_obj +to extract specific fields for guardrail processing. +""" + +from typing import TYPE_CHECKING, Any, List, Optional + +from litellm._logging import verbose_proxy_logger +from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation +from litellm.proxy._types import PassThroughGuardrailSettings + +if TYPE_CHECKING: + from litellm.integrations.custom_guardrail import CustomGuardrail + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + + +class PassThroughEndpointHandler(BaseTranslation): + """ + Handler for processing passthrough endpoint requests with guardrails. + + Uses passthrough_guardrails_config from litellm_logging_obj + to determine which fields to extract for guardrail processing. + """ + + def _get_guardrail_settings( + self, + litellm_logging_obj: Optional["LiteLLMLoggingObj"], + guardrail_name: Optional[str], + ) -> Optional[PassThroughGuardrailSettings]: + """ + Get the guardrail settings for a specific guardrail from logging_obj. + """ + from litellm.proxy.pass_through_endpoints.passthrough_guardrails import ( + PassthroughGuardrailHandler, + ) + + if litellm_logging_obj is None: + return None + + passthrough_config = getattr( + litellm_logging_obj, "passthrough_guardrails_config", None + ) + if not passthrough_config or not guardrail_name: + return None + + return PassthroughGuardrailHandler.get_settings( + passthrough_config, guardrail_name + ) + + def _extract_text_for_guardrail( + self, + data: dict, + field_expressions: Optional[List[str]], + ) -> str: + """ + Extract text from data for guardrail processing. + + If field_expressions provided, extracts only those fields. + Otherwise, returns the full payload as JSON. + """ + from litellm.proxy.pass_through_endpoints.jsonpath_extractor import ( + JsonPathExtractor, + ) + + if field_expressions: + text = JsonPathExtractor.extract_fields( + data=data, + jsonpath_expressions=field_expressions, + ) + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: Extracted targeted fields: %s", + text[:200] if text else None, + ) + return text + + # Use entire payload, excluding internal fields + from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + + payload_to_check = { + k: v + for k, v in data.items() + if not k.startswith("_") and k not in ("metadata", "litellm_logging_obj") + } + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: Using full payload for guardrail" + ) + return safe_dumps(payload_to_check) + + async def process_input_messages( + self, + data: dict, + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, + ) -> Any: + """ + Process input by applying guardrails to targeted fields or full payload. + """ + guardrail_name = guardrail_to_apply.guardrail_name + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: Processing input for guardrail=%s", + guardrail_name, + ) + + # Get field targeting settings for this guardrail + settings = self._get_guardrail_settings(litellm_logging_obj, guardrail_name) + field_expressions = settings.request_fields if settings else None + + # Extract text to check + text_to_check = self._extract_text_for_guardrail(data, field_expressions) + + if not text_to_check: + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: No text to check, skipping guardrail" + ) + return data + + # Apply guardrail + await guardrail_to_apply.apply_guardrail( + text=text_to_check, + request_data=data, + ) + + return data + + async def process_output_response( + self, + response: Any, + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, + ) -> Any: + """ + Process output response by applying guardrails to targeted fields. + """ + if not isinstance(response, dict): + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: Response is not a dict, skipping" + ) + return response + + guardrail_name = guardrail_to_apply.guardrail_name + verbose_proxy_logger.debug( + "PassThroughEndpointHandler: Processing output for guardrail=%s", + guardrail_name, + ) + + # Get field targeting settings for this guardrail + settings = self._get_guardrail_settings(litellm_logging_obj, guardrail_name) + field_expressions = settings.response_fields if settings else None + + # Extract text to check + text_to_check = self._extract_text_for_guardrail(response, field_expressions) + + if not text_to_check: + return response + + # Apply guardrail + await guardrail_to_apply.apply_guardrail( + text=text_to_check, + request_data=response, + ) + + return response diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7e30079e788..9e915d4bc5a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1693,27 +1693,26 @@ class DynamoDBArgs(LiteLLMPydanticObjectBase): assume_role_aws_session_name: Optional[str] = None -class PassThroughGuardrailConfig(LiteLLMPydanticObjectBase): +class PassThroughGuardrailSettings(LiteLLMPydanticObjectBase): """ - Configuration for guardrails on passthrough endpoints. + Settings for a specific guardrail on a passthrough endpoint. - Passthrough endpoints are opt-in only for guardrails. Guardrails configured at - org/team/key levels will NOT execute unless explicitly enabled here. + Allows field-level targeting for guardrail execution. """ - enabled: bool = Field( - default=False, - description="Whether to execute guardrails for this passthrough endpoint. When True, all org/team/key level guardrails will execute along with any passthrough-specific guardrails. When False (default), NO guardrails execute.", - ) - specific: Optional[List[str]] = Field( + request_fields: Optional[List[str]] = Field( default=None, - description="Optional list of guardrail names that are specific to this passthrough endpoint. These will execute in addition to org/team/key level guardrails when enabled=True.", + description="JSONPath expressions for input field targeting (pre_call). Examples: 'query', 'documents[*].text', 'messages[*].content'. If not specified, guardrail runs on entire request payload.", ) - target_fields: Optional[List[str]] = Field( + response_fields: Optional[List[str]] = Field( default=None, - description="Optional list of JSON paths to target specific fields for guardrail execution. Examples: 'messages[*].content', 'input', 'messages[?(@.role=='user')].content'. If not specified, guardrails execute on entire payload.", + description="JSONPath expressions for output field targeting (post_call). Examples: 'results[*].text', 'output'. If not specified, guardrail runs on entire response payload.", ) +# Type alias for the guardrails dict: guardrail_name -> settings (or None for defaults) +PassThroughGuardrailsConfig = Dict[str, Optional[PassThroughGuardrailSettings]] + + class PassThroughGenericEndpoint(LiteLLMPydanticObjectBase): id: Optional[str] = Field( default=None, @@ -1739,9 +1738,9 @@ class PassThroughGenericEndpoint(LiteLLMPydanticObjectBase): default=False, description="Whether authentication is required for the pass-through endpoint. If True, requests to the endpoint will require a valid LiteLLM API key.", ) - guardrails: Optional[PassThroughGuardrailConfig] = Field( + guardrails: Optional[PassThroughGuardrailsConfig] = Field( default=None, - description="Guardrail configuration for this passthrough endpoint. When enabled, org/team/key level guardrails will execute along with any passthrough-specific guardrails. Defaults to disabled (no guardrails execute).", + description="Guardrails configuration for this passthrough endpoint. Dict keys are guardrail names, values are optional settings for field targeting. When set, all org/team/key level guardrails will also execute. Defaults to None (no guardrails execute).", ) diff --git a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py index 824ed4e0b0c..9ee1eb86710 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py +++ b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py @@ -86,6 +86,7 @@ class UnifiedLLMGuardrails(CustomLogger): data = await endpoint_translation.process_input_messages( data=data, guardrail_to_apply=guardrail_to_apply, + litellm_logging_obj=data.get("litellm_logging_obj"), ) # Add guardrail to applied guardrails header @@ -148,6 +149,7 @@ class UnifiedLLMGuardrails(CustomLogger): response = await endpoint_translation.process_output_response( response=response, # type: ignore guardrail_to_apply=guardrail_to_apply, + litellm_logging_obj=data.get("litellm_logging_obj"), ) # Add guardrail to applied guardrails header add_guardrail_to_applied_guardrails_header( diff --git a/litellm/proxy/pass_through_endpoints/jsonpath_extractor.py b/litellm/proxy/pass_through_endpoints/jsonpath_extractor.py new file mode 100644 index 00000000000..fde2553be44 --- /dev/null +++ b/litellm/proxy/pass_through_endpoints/jsonpath_extractor.py @@ -0,0 +1,95 @@ +""" +JSONPath Extractor Module + +Extracts field values from data using simple JSONPath-like expressions. +""" + +from typing import Any, List, Union + +from litellm._logging import verbose_proxy_logger + + +class JsonPathExtractor: + """Extracts field values from data using JSONPath-like expressions.""" + + @staticmethod + def extract_fields( + data: dict, + jsonpath_expressions: List[str], + ) -> str: + """ + Extract field values from data using JSONPath-like expressions. + + Supports simple expressions like: + - "query" -> data["query"] + - "documents[*].text" -> all text fields from documents array + - "messages[*].content" -> all content fields from messages array + + Returns concatenated string of all extracted values. + """ + extracted_values: List[str] = [] + + for expr in jsonpath_expressions: + try: + value = JsonPathExtractor.evaluate(data, expr) + if value: + if isinstance(value, list): + extracted_values.extend([str(v) for v in value if v]) + else: + extracted_values.append(str(value)) + except Exception as e: + verbose_proxy_logger.debug( + "Failed to extract field %s: %s", expr, str(e) + ) + + return "\n".join(extracted_values) + + @staticmethod + def evaluate(data: dict, expr: str) -> Union[str, List[str], None]: + """ + Evaluate a simple JSONPath-like expression. + + Supports: + - Simple key: "query" -> data["query"] + - Nested key: "foo.bar" -> data["foo"]["bar"] + - Array wildcard: "items[*].text" -> [item["text"] for item in data["items"]] + """ + if not expr or not data: + return None + + parts = expr.replace("[*]", ".[*]").split(".") + current: Any = data + + for i, part in enumerate(parts): + if current is None: + return None + + if part == "[*]": + # Wildcard - current should be a list + if not isinstance(current, list): + return None + + # Get remaining path + remaining_path = ".".join(parts[i + 1:]) + if not remaining_path: + return current + + # Recursively evaluate remaining path for each item + results = [] + for item in current: + if isinstance(item, dict): + result = JsonPathExtractor.evaluate(item, remaining_path) + if result: + if isinstance(result, list): + results.extend(result) + else: + results.append(result) + return results if results else None + + elif isinstance(current, dict): + current = current.get(part) + else: + return None + + return current + diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 644b5ce9291..df4452f726e 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -599,6 +599,7 @@ async def pass_through_request( # noqa: PLR0915 stream: Optional[bool] = None, cost_per_request: Optional[float] = None, custom_llm_provider: Optional[str] = None, + guardrails_config: Optional[dict] = None, ): """ Pass through endpoint handler, makes the httpx request for pass-through endpoints and ensures logging hooks are called @@ -614,8 +615,13 @@ async def pass_through_request( # noqa: PLR0915 query_params: The query params stream: Whether to stream the response cost_per_request: Optional field - cost per request to the target endpoint + custom_llm_provider: Optional field - custom LLM provider for the endpoint + guardrails_config: Optional field - guardrails configuration for passthrough endpoint """ from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.proxy.pass_through_endpoints.passthrough_guardrails import ( + PassthroughGuardrailHandler, + ) from litellm.proxy.proxy_server import proxy_logging_obj ######################################################### @@ -664,6 +670,45 @@ async def pass_through_request( # noqa: PLR0915 ) ) + ### COLLECT GUARDRAILS FOR PASSTHROUGH ENDPOINT ### + # Passthrough endpoints are opt-in only for guardrails + # When enabled, collect guardrails from org/team/key levels + passthrough-specific + guardrails_to_run = PassthroughGuardrailHandler.collect_guardrails( + user_api_key_dict=user_api_key_dict, + passthrough_guardrails_config=guardrails_config, + ) + + # Add guardrails to metadata if any should run + if guardrails_to_run and len(guardrails_to_run) > 0: + if _parsed_body is None: + _parsed_body = {} + if "metadata" not in _parsed_body: + _parsed_body["metadata"] = {} + _parsed_body["metadata"]["guardrails"] = guardrails_to_run + verbose_proxy_logger.debug( + f"Added guardrails to passthrough request metadata: {guardrails_to_run}" + ) + + ## LOGGING OBJECT ## - initialize before pre_call_hook so guardrails can access it + start_time = datetime.now() + logging_obj = Logging( + model="unknown", + messages=[{"role": "user", "content": safe_dumps(_parsed_body)}], + stream=False, + call_type="pass_through_endpoint", + start_time=start_time, + litellm_call_id=litellm_call_id, + function_id="1245", + ) + + # Store passthrough guardrails config on logging_obj for field targeting + logging_obj.passthrough_guardrails_config = guardrails_config + + # Store logging_obj in data so guardrails can access it + if _parsed_body is None: + _parsed_body = {} + _parsed_body["litellm_logging_obj"] = logging_obj + ### CALL HOOKS ### - modify incoming data / reject request before calling the model _parsed_body = await proxy_logging_obj.pre_call_hook( user_api_key_dict=user_api_key_dict, @@ -675,18 +720,6 @@ async def pass_through_request( # noqa: PLR0915 params={"timeout": 600}, ) async_client = async_client_obj.client - - # create logging object - start_time = datetime.now() - logging_obj = Logging( - model="unknown", - messages=[{"role": "user", "content": safe_dumps(_parsed_body)}], - stream=False, - call_type="pass_through_endpoint", - start_time=start_time, - litellm_call_id=litellm_call_id, - function_id="1245", - ) passthrough_logging_payload = PassthroughStandardLoggingPayload( url=str(url), request_body=_parsed_body, @@ -1011,6 +1044,7 @@ def create_pass_through_route( custom_llm_provider: Optional[str] = None, is_streaming_request: Optional[bool] = False, query_params: Optional[dict] = None, + guardrails: Optional[Dict[str, Any]] = None, ): # check if target is an adapter.py or a url from litellm._uuid import uuid @@ -1079,6 +1113,7 @@ def create_pass_through_route( "forward_headers": _forward_headers, "merge_query_params": _merge_query_params, "cost_per_request": cost_per_request, + "guardrails": None, } if passthrough_params is not None: @@ -1096,6 +1131,7 @@ def create_pass_through_route( param_cost_per_request = target_params.get( "cost_per_request", cost_per_request ) + param_guardrails = target_params.get("guardrails", None) # Construct the full target URL with subpath if needed full_target = ( @@ -1135,6 +1171,7 @@ def create_pass_through_route( custom_body=final_custom_body, cost_per_request=cast(Optional[float], param_cost_per_request), custom_llm_provider=custom_llm_provider, + guardrails_config=param_guardrails, ) return endpoint_func @@ -1769,6 +1806,7 @@ class InitPassThroughEndpointHelpers: dependencies: Optional[List], cost_per_request: Optional[float], endpoint_id: str, + guardrails: Optional[dict] = None, ): """Add exact path route for pass-through endpoint""" route_key = f"{endpoint_id}:exact:{path}" @@ -1799,6 +1837,7 @@ class InitPassThroughEndpointHelpers: merge_query_params, dependencies, cost_per_request=cost_per_request, + guardrails=guardrails, ), methods=["GET", "POST", "PUT", "DELETE", "PATCH"], dependencies=dependencies, @@ -1817,6 +1856,7 @@ class InitPassThroughEndpointHelpers: "merge_query_params": merge_query_params, "dependencies": dependencies, "cost_per_request": cost_per_request, + "guardrails": guardrails, }, } @@ -1831,6 +1871,7 @@ class InitPassThroughEndpointHelpers: dependencies: Optional[List], cost_per_request: Optional[float], endpoint_id: str, + guardrails: Optional[dict] = None, ): """Add wildcard route for sub-paths""" wildcard_path = f"{path}/{{subpath:path}}" @@ -1863,6 +1904,7 @@ class InitPassThroughEndpointHelpers: dependencies, include_subpath=True, cost_per_request=cost_per_request, + guardrails=guardrails, ), methods=["GET", "POST", "PUT", "DELETE", "PATCH"], dependencies=dependencies, @@ -1881,6 +1923,7 @@ class InitPassThroughEndpointHelpers: "merge_query_params": merge_query_params, "dependencies": dependencies, "cost_per_request": cost_per_request, + "guardrails": guardrails, }, } @@ -2057,6 +2100,9 @@ async def initialize_pass_through_endpoints( if _target is None: continue + # Get guardrails config if present + _guardrails = endpoint.get("guardrails", None) + # Add exact path route verbose_proxy_logger.debug( "Initializing pass through endpoint: %s (ID: %s)", _path, endpoint_id @@ -2071,6 +2117,7 @@ async def initialize_pass_through_endpoints( dependencies=_dependencies, cost_per_request=endpoint.get("cost_per_request", None), endpoint_id=endpoint_id, + guardrails=_guardrails, ) visited_endpoints.add(f"{endpoint_id}:exact:{_path}") @@ -2087,6 +2134,7 @@ async def initialize_pass_through_endpoints( dependencies=_dependencies, cost_per_request=endpoint.get("cost_per_request", None), endpoint_id=endpoint_id, + guardrails=_guardrails, ) visited_endpoints.add(f"{endpoint_id}:subpath:{_path}") diff --git a/litellm/proxy/pass_through_endpoints/passthrough_guardrails.py b/litellm/proxy/pass_through_endpoints/passthrough_guardrails.py new file mode 100644 index 00000000000..cec20f3e048 --- /dev/null +++ b/litellm/proxy/pass_through_endpoints/passthrough_guardrails.py @@ -0,0 +1,333 @@ +""" +Passthrough Guardrails Helper Module + +Handles guardrail execution for passthrough endpoints with: +- Opt-in model (guardrails only run when explicitly configured) +- Field-level targeting using JSONPath expressions +- Automatic inheritance from org/team/key levels when enabled +""" + +from typing import Any, Dict, List, Optional, Union + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ( + PassThroughGuardrailsConfig, + PassThroughGuardrailSettings, + UserAPIKeyAuth, +) +from litellm.proxy.pass_through_endpoints.jsonpath_extractor import JsonPathExtractor + +# Type for raw guardrails config input (before normalization) +# Can be a list of names or a dict with settings +PassThroughGuardrailsConfigInput = Union[ + List[str], # Simple list: ["guard-1", "guard-2"] + PassThroughGuardrailsConfig, # Dict: {"guard-1": {"request_fields": [...]}} +] + + +class PassthroughGuardrailHandler: + """ + Handles guardrail execution for passthrough endpoints. + + Passthrough endpoints use an opt-in model for guardrails: + - Guardrails only run when explicitly configured on the endpoint + - Supports field-level targeting using JSONPath expressions + - Automatically inherits org/team/key level guardrails when enabled + + Guardrails can be specified as: + - List format (simple): ["guardrail-1", "guardrail-2"] + - Dict format (with settings): {"guardrail-1": {"request_fields": ["query"]}} + """ + + @staticmethod + def normalize_config( + guardrails_config: Optional[PassThroughGuardrailsConfigInput], + ) -> Optional[PassThroughGuardrailsConfig]: + """ + Normalize guardrails config to dict format. + + Accepts: + - List of guardrail names: ["g1", "g2"] -> {"g1": None, "g2": None} + - Dict with settings: {"g1": {"request_fields": [...]}} + - None: returns None + """ + if guardrails_config is None: + return None + + # Already a dict - return as-is + if isinstance(guardrails_config, dict): + return guardrails_config + + # List of guardrail names - convert to dict + if isinstance(guardrails_config, list): + return {name: None for name in guardrails_config} + + verbose_proxy_logger.debug( + "Passthrough guardrails config is not a dict or list, got: %s", + type(guardrails_config), + ) + return None + + @staticmethod + def is_enabled( + guardrails_config: Optional[PassThroughGuardrailsConfigInput], + ) -> bool: + """ + Check if guardrails are enabled for a passthrough endpoint. + + Passthrough endpoints are opt-in only - guardrails only run when + the guardrails config is set with at least one guardrail. + """ + normalized = PassthroughGuardrailHandler.normalize_config(guardrails_config) + if normalized is None: + return False + return len(normalized) > 0 + + @staticmethod + def get_guardrail_names( + guardrails_config: Optional[PassThroughGuardrailsConfigInput], + ) -> List[str]: + """Get the list of guardrail names configured for a passthrough endpoint.""" + normalized = PassthroughGuardrailHandler.normalize_config(guardrails_config) + if normalized is None: + return [] + return list(normalized.keys()) + + @staticmethod + def get_settings( + guardrails_config: Optional[PassThroughGuardrailsConfigInput], + guardrail_name: str, + ) -> Optional[PassThroughGuardrailSettings]: + """Get settings for a specific guardrail from the passthrough config.""" + normalized = PassthroughGuardrailHandler.normalize_config(guardrails_config) + if normalized is None: + return None + + settings = normalized.get(guardrail_name) + if settings is None: + return None + + if isinstance(settings, dict): + return PassThroughGuardrailSettings(**settings) + + return settings + + @staticmethod + def prepare_input( + request_data: dict, + guardrail_settings: Optional[PassThroughGuardrailSettings], + ) -> str: + """ + Prepare input text for guardrail execution based on field targeting settings. + + If request_fields is specified, extracts only those fields. + Otherwise, uses the entire request payload as text. + """ + if guardrail_settings is None or guardrail_settings.request_fields is None: + from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + return safe_dumps(request_data) + + return JsonPathExtractor.extract_fields( + data=request_data, + jsonpath_expressions=guardrail_settings.request_fields, + ) + + @staticmethod + def prepare_output( + response_data: dict, + guardrail_settings: Optional[PassThroughGuardrailSettings], + ) -> str: + """ + Prepare output text for guardrail execution based on field targeting settings. + + If response_fields is specified, extracts only those fields. + Otherwise, uses the entire response payload as text. + """ + if guardrail_settings is None or guardrail_settings.response_fields is None: + from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + return safe_dumps(response_data) + + return JsonPathExtractor.extract_fields( + data=response_data, + jsonpath_expressions=guardrail_settings.response_fields, + ) + + @staticmethod + async def execute( + request_data: dict, + user_api_key_dict: UserAPIKeyAuth, + guardrails_config: Optional[PassThroughGuardrailsConfig], + event_type: str = "pre_call", + ) -> dict: + """ + Execute guardrails for a passthrough endpoint. + + This is the main entry point for passthrough guardrail execution. + + Args: + request_data: The request payload + user_api_key_dict: User API key authentication info + guardrails_config: Passthrough-specific guardrails configuration + event_type: "pre_call" for request, "post_call" for response + + Returns: + The potentially modified request_data + + Raises: + HTTPException if a guardrail blocks the request + """ + if not PassthroughGuardrailHandler.is_enabled(guardrails_config): + verbose_proxy_logger.debug( + "Passthrough guardrails not enabled, skipping guardrail execution" + ) + return request_data + + guardrail_names = PassthroughGuardrailHandler.get_guardrail_names( + guardrails_config + ) + verbose_proxy_logger.debug( + "Executing passthrough guardrails: %s", guardrail_names + ) + + # Add to request metadata so guardrails know which to run + from litellm.proxy.pass_through_endpoints.passthrough_context import ( + set_passthrough_guardrails_config, + ) + + if "metadata" not in request_data: + request_data["metadata"] = {} + + # Set guardrails in metadata using dict format for compatibility + request_data["metadata"]["guardrails"] = { + name: True for name in guardrail_names + } + + # Store passthrough guardrails config in request-scoped context + set_passthrough_guardrails_config(guardrails_config) + + return request_data + + @staticmethod + def collect_guardrails( + user_api_key_dict: UserAPIKeyAuth, + passthrough_guardrails_config: Optional[PassThroughGuardrailsConfigInput], + ) -> Optional[Dict[str, bool]]: + """ + Collect guardrails for a passthrough endpoint. + + Passthrough endpoints are opt-in only for guardrails. Guardrails only run when + the guardrails config is set with at least one guardrail. + + Accepts both list and dict formats: + - List: ["guardrail-1", "guardrail-2"] + - Dict: {"guardrail-1": {"request_fields": [...]}} + + When enabled, this function collects: + - Passthrough-specific guardrails from the config + - Org/team/key level guardrails (automatic inheritance when passthrough is enabled) + + Args: + user_api_key_dict: User API key authentication info + passthrough_guardrails_config: List or Dict of guardrail names/settings + + Returns: + Dict of guardrail names to run (format: {guardrail_name: True}), or None + """ + from litellm.proxy.litellm_pre_call_utils import ( + _add_guardrails_from_key_or_team_metadata, + ) + + # Normalize config to dict format (handles both list and dict) + normalized_config = PassthroughGuardrailHandler.normalize_config( + passthrough_guardrails_config + ) + + if normalized_config is None: + verbose_proxy_logger.debug( + "Passthrough guardrails not configured, skipping guardrail collection" + ) + return None + + if len(normalized_config) == 0: + verbose_proxy_logger.debug( + "Passthrough guardrails config is empty, skipping" + ) + return None + + # Passthrough is enabled - collect guardrails + guardrails_to_run: Dict[str, bool] = {} + + # Add passthrough-specific guardrails + for guardrail_name in normalized_config.keys(): + guardrails_to_run[guardrail_name] = True + verbose_proxy_logger.debug( + "Added passthrough-specific guardrail" + ) + + # Add org/team/key level guardrails using shared helper + temp_data: Dict[str, Any] = {"metadata": {}} + _add_guardrails_from_key_or_team_metadata( + key_metadata=user_api_key_dict.metadata, + team_metadata=user_api_key_dict.team_metadata, + data=temp_data, + metadata_variable_name="metadata", + ) + + # Merge inherited guardrails into guardrails_to_run + inherited_guardrails = temp_data["metadata"].get("guardrails", []) + for guardrail_name in inherited_guardrails: + if guardrail_name not in guardrails_to_run: + guardrails_to_run[guardrail_name] = True + verbose_proxy_logger.debug( + "Added inherited guardrail (key/team level)" + ) + + verbose_proxy_logger.debug( + "Collected total guardrails for passthrough endpoint: %d", + len(guardrails_to_run), + ) + + return guardrails_to_run if guardrails_to_run else None + + @staticmethod + def get_field_targeted_text( + data: dict, + guardrail_name: str, + is_request: bool = True, + ) -> Optional[str]: + """ + Get the text to check for a guardrail, respecting field targeting settings. + + Called by guardrail hooks to get the appropriate text based on + passthrough field targeting configuration. + + Args: + data: The request/response data dict + guardrail_name: Name of the guardrail being executed + is_request: True for request (pre_call), False for response (post_call) + + Returns: + The text to check, or None to use default behavior + """ + from litellm.proxy.pass_through_endpoints.passthrough_context import ( + get_passthrough_guardrails_config, + ) + + passthrough_config = get_passthrough_guardrails_config() + if passthrough_config is None: + return None + + settings = PassthroughGuardrailHandler.get_settings( + passthrough_config, guardrail_name + ) + if settings is None: + return None + + if is_request: + if settings.request_fields: + return JsonPathExtractor.extract_fields(data, settings.request_fields) + else: + if settings.response_fields: + return JsonPathExtractor.extract_fields(data, settings.response_fields) + + return None diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 26e867dc33e..098cdb80e04 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -3,7 +3,14 @@ model_list: litellm_params: model: bedrock/openai/arn:aws:bedrock:us-east-1:046319184608:imported-model/0m2lasirsp6z - +guardrails: + - guardrail_name: "bedrock-pre-guard" + litellm_params: + guardrail: bedrock + mode: "pre_call" + guardrailIdentifier: ff6ujrregl1q + guardrailVersion: "DRAFT" + # like MCPs/vector stores search_tools: @@ -40,6 +47,14 @@ litellm_settings: general_settings: store_prompts_in_spend_logs: True + pass_through_endpoints: + - path: "/special/rerank" + target: "https://api.cohere.com/v1/rerank" + headers: + Authorization: "Bearer os.environ/COHERE_API_KEY" + guardrails: + bedrock-pre-guard: + request_fields: ["documents[*].text"] vector_store_registry: diff --git a/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails_field_targeting.py b/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails_field_targeting.py new file mode 100644 index 00000000000..05f367ff13d --- /dev/null +++ b/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails_field_targeting.py @@ -0,0 +1,119 @@ +""" +Test passthrough guardrails field-level targeting. + +Tests that request_fields and response_fields correctly extract +and send only specified fields to the guardrail. +""" + +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + +from litellm.proxy._types import PassThroughGuardrailSettings +from litellm.proxy.pass_through_endpoints.passthrough_guardrails import ( + PassthroughGuardrailHandler, +) + + +def test_no_fields_set_sends_full_body(): + """ + Test that when no request_fields are set, the entire request body + is JSON dumped and sent to the guardrail. + """ + request_data = { + "model": "rerank-english-v3.0", + "query": "What is coffee?", + "documents": [ + {"text": "Paris is the capital of France."}, + {"text": "Coffee is a brewed drink."} + ] + } + + # No guardrail settings means full body + result = PassthroughGuardrailHandler.prepare_input( + request_data=request_data, + guardrail_settings=None + ) + + # Result should be JSON string of full request + assert isinstance(result, str) + result_dict = json.loads(result) + + # Should contain all fields + assert "model" in result_dict + assert "query" in result_dict + assert "documents" in result_dict + assert result_dict["query"] == "What is coffee?" + assert len(result_dict["documents"]) == 2 + + +def test_request_fields_query_only(): + """ + Test that when request_fields is set to ["query"], only the query field + is extracted and sent to the guardrail. + """ + request_data = { + "model": "rerank-english-v3.0", + "query": "What is coffee?", + "documents": [ + {"text": "Paris is the capital of France."}, + {"text": "Coffee is a brewed drink."} + ] + } + + # Set request_fields to only extract query + guardrail_settings = PassThroughGuardrailSettings( + request_fields=["query"] + ) + + result = PassthroughGuardrailHandler.prepare_input( + request_data=request_data, + guardrail_settings=guardrail_settings + ) + + # Result should only contain query + assert isinstance(result, str) + assert "What is coffee?" in result + + # Should NOT contain documents + assert "Paris is the capital" not in result + assert "Coffee is a brewed drink" not in result + + +def test_request_fields_documents_wildcard(): + """ + Test that when request_fields is set to ["documents[*]"], only the documents + array is extracted and sent to the guardrail. + """ + request_data = { + "model": "rerank-english-v3.0", + "query": "What is coffee?", + "documents": [ + {"text": "Paris is the capital of France."}, + {"text": "Coffee is a brewed drink."} + ] + } + + # Set request_fields to extract documents array + guardrail_settings = PassThroughGuardrailSettings( + request_fields=["documents[*]"] + ) + + result = PassthroughGuardrailHandler.prepare_input( + request_data=request_data, + guardrail_settings=guardrail_settings + ) + + # Result should contain documents + assert isinstance(result, str) + assert "Paris is the capital" in result + assert "Coffee is a brewed drink" in result + + # Should NOT contain query + assert "What is coffee?" not in result + diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails.py b/tests/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails.py new file mode 100644 index 00000000000..3422e689575 --- /dev/null +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_passthrough_guardrails.py @@ -0,0 +1,263 @@ +""" +Unit tests for passthrough guardrails functionality. + +Tests the opt-in guardrail execution model for passthrough endpoints: +- Guardrails only run when explicitly configured +- Field-level targeting with JSONPath expressions +- Automatic inheritance from org/team/key levels when enabled +""" + +import pytest + +from litellm.proxy._types import PassThroughGuardrailSettings +from litellm.proxy.pass_through_endpoints.jsonpath_extractor import JsonPathExtractor +from litellm.proxy.pass_through_endpoints.passthrough_guardrails import ( + PassthroughGuardrailHandler, +) + + +class TestPassthroughGuardrailHandlerIsEnabled: + """Tests for PassthroughGuardrailHandler.is_enabled method.""" + + def test_returns_false_when_config_is_none(self): + """Guardrails should be disabled when config is None.""" + result = PassthroughGuardrailHandler.is_enabled(None) + assert result is False + + def test_returns_false_when_config_is_empty_dict(self): + """Guardrails should be disabled when config is empty dict.""" + result = PassthroughGuardrailHandler.is_enabled({}) + assert result is False + + def test_returns_true_when_config_is_list(self): + """Guardrails should be enabled when config is a list of names.""" + result = PassthroughGuardrailHandler.is_enabled(["pii-detection"]) + assert result is True + + def test_returns_false_when_config_is_invalid_type(self): + """Guardrails should be disabled when config is not a dict or list.""" + result = PassthroughGuardrailHandler.is_enabled("pii-detection") # type: ignore + assert result is False + + def test_returns_true_when_config_has_guardrails(self): + """Guardrails should be enabled when config has at least one guardrail.""" + config = {"pii-detection": None} + result = PassthroughGuardrailHandler.is_enabled(config) + assert result is True + + def test_returns_true_with_multiple_guardrails(self): + """Guardrails should be enabled with multiple guardrails configured.""" + config = { + "pii-detection": None, + "content-moderation": {"request_fields": ["input"]}, + } + result = PassthroughGuardrailHandler.is_enabled(config) + assert result is True + + +class TestPassthroughGuardrailHandlerGetGuardrailNames: + """Tests for PassthroughGuardrailHandler.get_guardrail_names method.""" + + def test_returns_empty_list_when_disabled(self): + """Should return empty list when guardrails are disabled.""" + result = PassthroughGuardrailHandler.get_guardrail_names(None) + assert result == [] + + def test_returns_guardrail_names(self): + """Should return list of guardrail names from config.""" + config = { + "pii-detection": None, + "content-moderation": {"request_fields": ["input"]}, + } + result = PassthroughGuardrailHandler.get_guardrail_names(config) + assert set(result) == {"pii-detection", "content-moderation"} + + +class TestPassthroughGuardrailHandlerNormalizeConfig: + """Tests for PassthroughGuardrailHandler.normalize_config method.""" + + def test_normalizes_list_to_dict(self): + """List of guardrail names should be converted to dict with None values.""" + config = ["pii-detection", "content-moderation"] + result = PassthroughGuardrailHandler.normalize_config(config) + assert result == {"pii-detection": None, "content-moderation": None} + + def test_returns_dict_unchanged(self): + """Dict config should be returned as-is.""" + config = {"pii-detection": {"request_fields": ["query"]}} + result = PassthroughGuardrailHandler.normalize_config(config) + assert result == config + + +class TestPassthroughGuardrailHandlerGetSettings: + """Tests for PassthroughGuardrailHandler.get_settings method.""" + + def test_returns_none_when_config_is_none(self): + """Should return None when config is None.""" + result = PassthroughGuardrailHandler.get_settings(None, "pii-detection") + assert result is None + + def test_returns_none_when_guardrail_not_in_config(self): + """Should return None when guardrail is not in config.""" + config = {"pii-detection": None} + result = PassthroughGuardrailHandler.get_settings(config, "content-moderation") + assert result is None + + def test_returns_none_when_settings_is_none(self): + """Should return None when guardrail has no settings.""" + config = {"pii-detection": None} + result = PassthroughGuardrailHandler.get_settings(config, "pii-detection") + assert result is None + + def test_returns_settings_object(self): + """Should return PassThroughGuardrailSettings when settings are provided.""" + config = { + "pii-detection": { + "request_fields": ["input", "query"], + "response_fields": ["output"], + } + } + result = PassthroughGuardrailHandler.get_settings(config, "pii-detection") + assert result is not None + assert result.request_fields == ["input", "query"] + assert result.response_fields == ["output"] + + +class TestJsonPathExtractorEvaluate: + """Tests for JsonPathExtractor.evaluate method.""" + + def test_simple_key(self): + """Should extract simple key from dict.""" + data = {"query": "test query", "other": "value"} + result = JsonPathExtractor.evaluate(data, "query") + assert result == "test query" + + def test_nested_key(self): + """Should extract nested key from dict.""" + data = {"foo": {"bar": "nested value"}} + result = JsonPathExtractor.evaluate(data, "foo.bar") + assert result == "nested value" + + def test_array_wildcard(self): + """Should extract values from array using wildcard.""" + data = {"items": [{"text": "item1"}, {"text": "item2"}, {"text": "item3"}]} + result = JsonPathExtractor.evaluate(data, "items[*].text") + assert result == ["item1", "item2", "item3"] + + def test_messages_content(self): + """Should extract content from messages array.""" + data = { + "messages": [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hello"}, + ] + } + result = JsonPathExtractor.evaluate(data, "messages[*].content") + assert result == ["You are helpful", "Hello"] + + def test_missing_key_returns_none(self): + """Should return None for missing key.""" + data = {"query": "test"} + result = JsonPathExtractor.evaluate(data, "missing") + assert result is None + + def test_empty_data_returns_none(self): + """Should return None for empty data.""" + result = JsonPathExtractor.evaluate({}, "query") + assert result is None + + def test_empty_expression_returns_none(self): + """Should return None for empty expression.""" + data = {"query": "test"} + result = JsonPathExtractor.evaluate(data, "") + assert result is None + + +class TestJsonPathExtractorExtractFields: + """Tests for JsonPathExtractor.extract_fields method.""" + + def test_extracts_multiple_fields(self): + """Should extract and concatenate multiple fields.""" + data = {"query": "search query", "input": "additional input"} + result = JsonPathExtractor.extract_fields(data, ["query", "input"]) + assert "search query" in result + assert "additional input" in result + + def test_extracts_array_fields(self): + """Should extract and concatenate array fields.""" + data = {"documents": [{"text": "doc1"}, {"text": "doc2"}]} + result = JsonPathExtractor.extract_fields(data, ["documents[*].text"]) + assert "doc1" in result + assert "doc2" in result + + def test_handles_missing_fields(self): + """Should handle missing fields gracefully.""" + data = {"query": "test"} + result = JsonPathExtractor.extract_fields(data, ["query", "missing"]) + assert result == "test" + + def test_empty_fields_returns_empty_string(self): + """Should return empty string for empty fields list.""" + data = {"query": "test"} + result = JsonPathExtractor.extract_fields(data, []) + assert result == "" + + +class TestPassthroughGuardrailHandlerPrepareInput: + """Tests for PassthroughGuardrailHandler.prepare_input method.""" + + def test_returns_full_payload_when_no_settings(self): + """Should return full JSON payload when no settings provided.""" + data = {"query": "test", "input": "value"} + result = PassthroughGuardrailHandler.prepare_input(data, None) + assert "query" in result + assert "input" in result + + def test_returns_full_payload_when_no_request_fields(self): + """Should return full JSON payload when request_fields not set.""" + data = {"query": "test", "input": "value"} + settings = PassThroughGuardrailSettings(response_fields=["output"]) + result = PassthroughGuardrailHandler.prepare_input(data, settings) + assert "query" in result + assert "input" in result + + def test_returns_targeted_fields(self): + """Should return only targeted fields when request_fields set.""" + data = {"query": "targeted", "input": "also targeted", "other": "ignored"} + settings = PassThroughGuardrailSettings(request_fields=["query", "input"]) + result = PassthroughGuardrailHandler.prepare_input(data, settings) + assert "targeted" in result + assert "also targeted" in result + assert "ignored" not in result + + +class TestPassthroughGuardrailHandlerPrepareOutput: + """Tests for PassthroughGuardrailHandler.prepare_output method.""" + + def test_returns_full_payload_when_no_settings(self): + """Should return full JSON payload when no settings provided.""" + data = {"results": [{"text": "result1"}], "output": "value"} + result = PassthroughGuardrailHandler.prepare_output(data, None) + assert "results" in result + assert "output" in result + + def test_returns_full_payload_when_no_response_fields(self): + """Should return full JSON payload when response_fields not set.""" + data = {"results": [{"text": "result1"}], "output": "value"} + settings = PassThroughGuardrailSettings(request_fields=["input"]) + result = PassthroughGuardrailHandler.prepare_output(data, settings) + assert "results" in result + assert "output" in result + + def test_returns_targeted_fields(self): + """Should return only targeted fields when response_fields set.""" + data = { + "results": [{"text": "targeted1"}, {"text": "targeted2"}], + "other": "ignored", + } + settings = PassThroughGuardrailSettings(response_fields=["results[*].text"]) + result = PassthroughGuardrailHandler.prepare_output(data, settings) + assert "targeted1" in result + assert "targeted2" in result + assert "ignored" not in result + From ffb75b04fd04ee9ac7a969f85ea1418ebb231fd4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Nov 2025 12:27:16 -0800 Subject: [PATCH 182/349] [Feat] UI - allow adding pass through guardrails through UI (#17226) * add PassThroughGuardrailsConfig * init JsonPathExtractor * feat PassthroughGuardrailHandler * feat pt guardrails * pt guardrails * add Pass-Through Endpoint Guardrail Translation * add PassThroughEndpointHandler * execute simple guardrail config and dict settings * TestPassthroughGuardrailHandlerNormalizeConfig * add passthrough_guardrails_config on litellm logging obj * add LiteLLMLoggingObj to base trasaltino * cleaner _get_guardrail_settings * update guardrails settings * docs pt guardrail * docs Guardrails on Pass-Through Endpoints * fix typing * fix typing * test_no_fields_set_sends_full_body * fix typing * init add pass through guardrails * ui allow setting target fields on gd * docs ui settings guardrails --- .../docs/proxy/pass_through_guardrails.md | 42 ++- docs/my-website/img/pt_guard1.png | Bin 0 -> 787745 bytes docs/my-website/img/pt_guard2.png | Bin 0 -> 561454 bytes .../passthrough_guardrails.py | 8 +- .../src/components/add_pass_through.tsx | 18 ++ .../PassThroughGuardrailsSection.tsx | 246 ++++++++++++++++++ .../src/components/pass_through_info.tsx | 41 +++ .../src/components/pass_through_settings.tsx | 1 + 8 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 docs/my-website/img/pt_guard1.png create mode 100644 docs/my-website/img/pt_guard2.png create mode 100644 ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx diff --git a/docs/my-website/docs/proxy/pass_through_guardrails.md b/docs/my-website/docs/proxy/pass_through_guardrails.md index 272285e61c0..cc3d36c866e 100644 --- a/docs/my-website/docs/proxy/pass_through_guardrails.md +++ b/docs/my-website/docs/proxy/pass_through_guardrails.md @@ -1,5 +1,7 @@ # Guardrails on Pass-Through Endpoints +import Image from '@theme/IdealImage'; + ## Overview | Property | Details | @@ -10,7 +12,41 @@ ## Quick Start -### 1. Define guardrails and pass-through endpoint +You can configure guardrails on pass-through endpoints either via the **UI** (recommended) or **config file**. + +### Using the UI + +#### 1. Navigate to Pass-Through Endpoints + +Go to **Models + Endpoints** → Click **+ Add Pass-Through Endpoint** + + + +Scroll to the **Guardrails** section and select which guardrails to enforce. + +:::tip Default Behavior +By default, you don't need to specify fields - LiteLLM will JSON dump the entire request/response payload and send it to the guardrail. +::: + +#### 2. Target Specific Fields (Optional) + + + +To check only specific fields instead of the entire payload: + +1. Select your guardrails +2. In **Field Targeting (Optional)**, specify fields for each guardrail +3. Use the quick-add buttons (`+ query`, `+ documents[*]`) or type custom JSONPath expressions +4. **Request Fields (pre_call)**: Fields to check before sending to target API +5. **Response Fields (post_call)**: Fields to check in the response from target API + +**Example**: In the screenshot above, we set `query` as a request field, so only the `query` field is sent to the guardrail instead of the entire request. + +--- + +### Using Config File + +#### 1. Define guardrails and pass-through endpoint ```yaml showLineNumbers title="config.yaml" guardrails: @@ -31,13 +67,13 @@ general_settings: pii-guard: ``` -### 2. Start proxy +#### 2. Start proxy ```bash litellm --config config.yaml ``` -### 3. Test request +#### 3. Test request ```bash curl -X POST "http://localhost:4000/v1/rerank" \ diff --git a/docs/my-website/img/pt_guard1.png b/docs/my-website/img/pt_guard1.png new file mode 100644 index 0000000000000000000000000000000000000000..85b094a14b9f581742a20357aeaaf7f3e05abaa6 GIT binary patch literal 787745 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%0DV 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 z zzyF?o2qdB8!3a@5(gu70+@g=y#HBm=yuu^0JWShTMqkvKSkN~vP*Crs?l$Z)o9;$0 za8h?$~#}aDm6w6eB(V|#5@;WH(a+9V?Z8K@-O+< za)jQsY>4^g(n8YS*W)8Y;Y45W71)(+T$6Y8d##$R%CAbYo{u3SEkB4#}bdcamPkwAQ2?r2n@KQ>H}F~w8OPWVU4PphR%JZ zrc+C;>yz9QLKFP74=`FvaAiskr3_%eggQh;=!BPoIlGG`@G;-<=hT_M%dg&G9JzG2 z^C=_Fck~0SJj@Ri4bkJ|zT0v@dYDeOD=yxi#SfP4g~C6B*`~M6%2Z02QIhQnag#EA zIchmG-#1X4Qd4%k_6@xnrV;njZW=dt)5n}|i%F{NS4TD5-sv1!W}VY+%l%4O%U!TE z^mzTUKwT2&SH!$WPTUmnJxXM7ovyCSk95Kk{WwX%h4sMwx^axCHtMH`SDd2a+#^k= zR){e_Ec_DQ$X-VnFz55~&|H^sao?hsCpeksvA1M&BN2 zHP=-4M`Cn7DAedFH+eUg5qa=V#~7_};|Kp&*u}|Ze5M`QvHU5R z-Xi@?C%g~hg4!5_U%?A)QqQcZntR!J?NZ;9hDDuM8x7oDadrNs9(>i_Zx0NlZvk=- zpJyky{oEy)lXGYW<5|3bl>Sn>Knjx8>crO^Gs#e5-q)(A*-Z(_G*$5SB=AmDUjS96 zq+LZaA`3e;9HS<`ID-G~6_zVTe?vtsteR4D6=X0%X-MO^0&+$7l?zV-! z^sISeiC4XP`hmu}rKOayn0+^2?(wKG4a^Ny>9O}$UKcpthpELcEI#~Ny)h^NT=z=) zgOfIE8UdIy9!*ZSZ8PrUZFZn7#diO9M9~@@yibBLfyc>teBRb-F|+^>IbmJvKOgK% z*xmx9a1wb0p=F(YqivQAo$!0T)5VObA%lEX;iuFBX?)=QV5NPj_dD*mAfst^ZgEgf3`oPb`FIivRCb12*8g*OeW05Sz1o3_9$4$O8Y9 z5lKwwCaUzjJu1~{vlHIA_Np*jD2NHt{GBg8s$yi5x~QTiLw*qc;|IY=lLP_y`}6(T z^9+nOm<@unBYG^KY&Z}n zOlM_yei^?!XIBT-Rc+B^8l1JkNt4l441TRX_)Z!^8tK0A@Tni3tkJ>}PC%7Au+WB9 zdBK4IjKPrCVGe!Y_sxUk)Hgq>*YoV9u6m&J(?qOAA^ddK$T5`%z6JB&EYTqs%YC0Y zL4!~C8cnyn`rJuJKVyX@P9c*%vwU6_Pg`TBxOb6%-zIM4R z%TlFJXXA23{4)MS&!QHpVhF$W%oe@jzYqxAm;eS=TCfW_Mh>vy=(Ny~#%Eae66qUn z6nkrb!k8BRy0Ekl8HcIoenq&>=@Ni%T-$m*ep`6KJA9u%IHfkRYKRUlYu7|L$}g3tO~Gn=6Z#g7hhGk~7`_a=gyZ;**i_ZvcSief z72%7Sz_Uag3%XtkM{9Y#5i)Usy<#!%mG1voV#S|P?N^L-6e;xj68q!Z8I{*Jm`q

jE)#{;%R|nAx%L8n3!eX{ZVMH@ul^`GL7e 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

=N->X^;W3G_52bjGvl;&g06dw%b| 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

ZPZ3@41ss^gKKJ_y2-8VlsdTa94`}Z;lvP1 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%0A7LH+Zmcaxd7*h)$+P_5{et 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�}03)BK=&6Z@Chx6opWoMHK0K5cSKa391D zWO%vhA0#c$2|(Q1sMmJ05&*5hBZU_7g=vX*KwP}WeVPWR4+J;4xoAZYnOs(-Ajrmi zr$rOIlNN1Be~;Jrrg^yYV7c>ikPUytC#Fb0+2{g9WIr4Jgb|D!r4B*4sy=j}nlow{Q}E7P7k^PlIGtSn z#-UbDu}5(Zz`<6%jJT>?>DozPuJRms#ZTpYzsYH(=a~Fr~t&xl$sHQyy;D7W201cKV57bVM zfjwsDZ-yNkUL1r_n&lD&ixG~y*^R%5rhxxy9!Xl}06Q-=!rB#RD>3YYe)n{@|{5^{gZAC}{zptgP18`b7qAtC^qGvq}GwbJRehS-t>7=ajIcoNu z(b|gm76kUwa$PUHbc=y8mZ8Cvfe`?z8SmV_yjaQ53q|zh0}Sg6Q{6osr_Gg>ZQkz0 zK(;1UuJ1*$HVYwVZ*+6)WxDP1NlRJDeC&C;!?ibdb-prA+N4QFMurJT@++&y0lK>m z{>c$XvD@*j?2a2PPbkwYe8C)^N3Vlvm~E`n=}=a**A{s{)fiJVmAU1U991 zXD+GWc2sb7%6Cw{-Jf9X>5}Y;bv?D`{kltp;;f_=C!5GBVq1#JtaRx<$(?(Xx<$0b zGkG;5Ds+P8voWAEjV!WjBlK)@MCka_hjPd1w*vAjLq*qrl^Oh%NVFeM8mm~9@ZXJ$ zW7&Z&IZds^&~9oVitM*U-zZGfy@nW0!1T8Ut_kCg!=4}-5`Q!7K*6`RO31=m4(=bJ z5n7!&$gf+-FGW4*%gDyQT(l=;LWOc0r8U zpD3TGg_cU{EiqD7EijBhS1m*D^OBzfC;r>@D@4$uXjW3eqRTj zc~GI5fUOPvoe*n3i5BvNJ#gxDnuI-GxhBO6w1t|-UT7Agy6CQEPQ}xrK)Tj2yYY+K z`eT3xL^|j)i5qG~^^^QpC-#W#pTk$Z?#IY$d&{zuQ?1dbolc1{DYPMX6 zGc*4z)E0W3?-6n%1}T#rYyv5JKl|#5Aa|DJHLeB}P$x3G3rJ#8&KxFrhMBf>42^A5 zZnT=5h=+dggNfOo5OfQN*SK>#Kls^1w?4AaZ|q~z1%J*ym5Q;>`qh?Mnf07Z|9|{5GQp3Qf8>jL~H9Qd#uuMh5BlSdk!Wz z|BG3}`Qvd%I!}_B(y5C5ewsVF{k8I8I#|zVxK{ zAJql_y9rk3VQ7PRe0%#OObq2iXkQVLha`1?I7=NGLw;zvHx*6M%$jO?#r8~Uc~XQH zLg$tHqo`lvT zX|VZ{#a`=4^>TUtRM}4@OH^-y734)K&W+;JU^?^I9dDd}Ug>Z*!apim)CKjeY+F5X z7dC07owTifbXI%-o72jnXlH>nY(d1?jCN^z)hJN9ON|nWF4{;Z`h2AMs4F{kg~SY% zji{DqW-3+I?BsMXspjqn7#Mp@7PJ9Znq%Yj&R-m*Io)d-lN|sEAhCU9u(z(OwIEe zE_Rt{iT(;0Z43XvK#))^mSqpqU5gE87MPQ(YWCT|C*8=yLxa=TB^Cz0IqITarJdl< zC^wx#RQK?t#mZvimSfGro^IjyDYGg>u`8!=-OP&yK{L(~FaSx_Wpa3a?XeGPJd*u_ zf)@SO)$=Q^0USfKIIGui{GdZw5f9EyI%gv(ZqUpy$w>+nCm@k#5^`%mt^KaeRcpLp zKFmMPS!kZxVW4~Y7s>wl_5uk1af5IHS-USIBKqb%A+LsaYc8@x(xf7^aU1V;CIKKj zYLk96$Ejd%0DL_EwiatM3}NK*~`BZ39IRf69N0| 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%0D5v)(g;68qn{o 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

4D|Vgg7vTrKr` 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�~uc#2F) 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 literal 0 HcmV?d00001 diff --git a/docs/my-website/img/pt_guard2.png b/docs/my-website/img/pt_guard2.png new file mode 100644 index 0000000000000000000000000000000000000000..32481109bcd88e21dbf7dcffd4dddb2ffa4f9e5a GIT binary patch literal 561454 zcmeFZXH=8x)-EhfK$K!bk*269ASj|p4K52-P*IQ?6e&vYHHldkR0M1gr3FPmfl#HF zzyhQM2t_(1w9t|eNeChRJnr(I{hssgeZu(0_;EyoZ+ZWxgUfrMp zyq4G?Dx|PM?AJ$thpEt>|9X91=+Fj{zr8QKVMFx&4Wj?}j0N!g>)$Kj@#{N(e-=p> z`p2h%_tJ&``Dv-Kbdi6)mPq^cwV2yKmw+cpzYDg38#e4b@arLT@y7n28#b72xOn#T z%`hR-xZ@XZo7!DJOIHUC*!vA$e?Ggl;{DJMDQ;MgXymb*Uv_Rfg7ElgedtK*xuqu> z5_R+gI;ice-rFDVE}v5w*?IuwAoZzvR9>nfyH{RL)8U!UExluNDJiv#5hWKC7v%0n zGGVZiJW&Sev5;8N*eBP`BD!9YS*)$HL0Az{(qFMb>0w#j6%Nc&Z8Tk0l?L+p3 zX%E)l@Zfw+I3!y62chh?aIEAS$>Ze>b3q3wJSSvC_LUpGCdyKEj%J%Wobz0F+Xei) z80p6Nsjt$KL4VX^q3YUiFK!iZ5HpM@=@q_M>aL;a$s@r0SEY!^b@zN29(G0$Eu2oi zJ?M9_9!ov2b+@3qH*=m74r#(O4;&Q|CQ1%XLQOy@;0V{}G3nXDAOV{jPun!)Se-3DP;93*;huK_@z+^_W3HL680jH?^s?uj z>Yas+-28%v%j-9Of^Z1lhv0e)@V>R(i`~t-w*)r+(Q86R6EZ7**{`yMLn^@dYcc}+ z%|9EHy3Ytz2Qq$svg-+5|2eF{r?5q8 z2vvRO+Bp4uJ@&k>cIK0HIIq;EAtvn=+r`>R{de66|gojE?k--K>p8j(NfzKmpYhh%d)f?9RT(d#S zay}NlR5wqoS{%?6IGQEN%@d`Gj$oU?Ief?XSA^3hz$^$v|EG1E#o1A)`bvT37l^%B zRe*1(Ynp#*^-28u@^=F7>ZVv#nOdaH>}W!`HARdf>F_H!d4_KnG^+pCMD^FiNY*Al zH1X)*a=O#W0^Yc%^;toU+PK4mD7`d$Vl1+^U0(}W!4?r>+J6l#=!>eu-2>z0)nD%q zjBfWBt@Sh=tzmuNCosUsz?Up&vwdCcraZ&_z#^m_wXD_;6td(uZ<{4DYV3mJ4iAh; zH8GsDc;|y-L3J(z6~0Ztgkx4M8d6GQ-)q@iZ5LWeD(lEdK?DjTL_>Cbi@d*2cioQi z&@5S$WIdjhdq>+wlEhLTA6O%tA~0{Av>~8ImU8N5d z(`Q1kHVs27<`vIfWek=sBT@@kCfkRIlSx|7*T2%dBhHA0Qpw=Ltor<=hF^xvHPWFy z(xGCao%exyzkm^4x%vfBJy08Rc0P>xFGuj7lZo>lVL}|4!2A^We$dA${N*~L#h=*hr@I5XW);N^V{A?tk^m`rL(ID!6B|_Lb#)m z;FCVAcNG$vL0p}Y^~ijfK5Xrjc>2l%8-|nB^m)+hM0wQMSJUOq&D-=`< zMLO{*Boe8!?H;uxlCi|)l%u(1EEvY6^J&dG(B;9eTEfegee9^F@Xxf6x_GKBF`9p# zeL*ELZ#DlbG*Jm+Gp%i8Nsrtwd%DiV?oDRUS-{o*u@#csHSnxC_YlmF_e3XZaxKBe ziD?vXvO3fvBO?P!Q{bUiUQmlw*1 zX!kC3t(5!XO~a;R^?9o$SX1_gjEoF~RmNz~wM&A7ezWxiip`*)c^EK{3PP_<#qf!! za@-3g3eAGad{)o8fZ4EiDn4jnaT49Lbf@b9WswC&l{ec15N>w=;^r&5Z>?Nks#sRo zQFiS|wrTKj6Zk3?#;C^EE`OAP%ww$zxuDCmonH7SG464KdpJ$FJkaY-+_8?Lrv|e} zTajYR#JZyQW<9xcR-)#^pA1&poaw<_gT$=h>&g zCsD-EVQ3->o$HTC&1Na>e3m{Tt<&(sXkTG&r1!YLnseQ~IH+1I^;EpxhzgH2h~~{~ zvov1zwQ6?HC>ER$$f_28uGfLllJQZ@HgzQD&V$f^o|D3}Y8y;?=M8~vb1q~+Qgo6B z=+#~z%mBXsZ;m4^b@r%VG{SlDhJY#&ludM=F9T_ZjwTPaShM8OB-Z zv*fc^Rv|sF*JGpZSM|OydM=n7(AA7DMla_*<; zv$fvw^3#{ZRb?9*L~iFC@Uc}D|Ac6mPfq!(YWf`ef=hnz+-sEu=z~{ab0)nf9=fvp zE50Oe_u`X-w1-Uho)R#p0FetQ&T$Br9f2Kp;~cNlT2Ak>Qt*$Jo4cKL>~*US7Q;F8 zf38V&0=hwdcx$=FQC_=E9d*VMN`1=@nEoCM8eMGNsP=yZ!~d|5jfY>k>4#=#gqaIF#Nh|U%D9iabEeN#huzAz@mTc0D$_gUS3d zdyAOh+Sh)WvyH3Wa}Sj>s_ctudoZ>Iz6PKF?$uc=B|KV}ZLL(`B|rB%aex4R^en<) z!c-+1#F>;q@|)C6ms3}GGFb+^y@H!V!V5g116#>Z;b#0YDai5+(qS_!;`RB5kd6mi zeP8G;o>OQhgwJ3v+j#32;H#+K@izK0OcU8>>NXLD+b<6lnMs?)tFCxkxb_@#fxiB( zIBne98m|{p7Ek-4Ki*^N`n_vaWv#>Alonwq@1=6%hQNienX|lbJnZKq6%PA;Hk zykOb_nyr{rbVlM^@_eTmb32Q#9r3ia;b9md!6uDEMW8u$EGsQosTV0l@WWIs6oI#Z!zQ1^XAgbn zv!+$}YfcKFkmC4nDK~ceffJ3?kKTq$C@X6{d{S@(2zDMl{=~&|(9Z|X;)d%Y&eqms zKuo2nAQfh-xpL&MB*NuPxx5T2C>p&`<{hGTOAE*5eo^HIb!JJSlb%^t!8tK=vX`j zSIaK&!PPLcU0v3ZxHrI0N{m&9Qb39-DtwF{LNQ2tqHc?Pv!daFYa4fjKA;|~wt_2u zagxugYkU6B8B;uYAHMJ$nwZe`f;u1GlstMZ`cLv@({;=Xlm`KME^*;k0;@ha_1|vg zm`?3?X$M@?e9ZOpYnA(s(0Arl9<^WaH>+~}latWUkVDtDXyGRHHeXG*5}x*rn%;vo zW{d^$^Q}rw+OESJUkMw`E)&6gR^R<)gP*{e)o&GsdL-802-X0O+6`AhYoDg}o7~$< z#^^21Wmi!BHEX}CtE=;uCnhFTt^{(lk9L+kCWMezn3bx*{1KDQxVJTBb0Rn`xbM;veNpOv8#O^4;U^)XN0}IKSU9E~z9}YrNoD5FtPVHCo(VaS^ zhO!6$DA0`OgmXJIeX!AKSBq88pW>+c&kV-Oz}6bekr9hu zLA6Qd+5*bwzvcwQcM6Qp=4-W_&H8}5Q1I;S%ZfJLI9)1n=g9)&kt23Gi@t{5qfsYo zowx;if&8|`_^SAUqdXj=juBwDeEgAJY4bYh8Dv}7?7oA#;;BXwvhUR<#9gIdP4(n2 zy{_^nQZbiz(D$J<W|xLRm+$NLijgLWX%g&F-O()3b>$-wWyNG zD9`wFT^vvZJ}Q6gjm+JHeMyT`8bBc&z;qpR3wIibrxiPeE7O00>y>YWxR9`o)C?$A zH-g8U14RTEIS3vfADV@VX&igg_r7f@zo|pg%R&|OdP?=$j#kT!rIQ{J%01xEMc+@w zgSRisw%^Hlg;z0N9dc2LX4q!`j&ccVtAe*jC>N{vCVtt?GU1dI!Ae&IS?$;Sef71RnR0X_~{4EU5Syw>;m<^AFTP72M$f6&=i_aAL5q-u_g?!bvpW{&$ruTK0h&emP#V*JMyAlK{#CAR~$(0 zP2%)VfwteEwmMxHJ!RA9*~M9wPA1Du+pyY(d+F=3q2sm-MQ^Z~26Gi{v=}{|y;B;2 zPNqN^uM6sOFV*4iAlq%ht>RfihyPnMAh*a!jV z-(w8OsdQkvYVsY8jc?dE#aRu+gJlh3)k^}v@rhef1(k7sk>H5^uUrlUcHDy!cC{;B z-tC`IR1($X_&q!)fFS3e5=_sC8djRN@eU>IcinpXysFQv>&TbO=@jAR%llxbROpbR z0rUSBrT=Zv`;|sF+o`r(I&T))bWK{JX-e{@7|9RZziWWK<~+8VldK%t!&?N*d7_g- zYdpjx$hVYHWTye?_qO=!hJXGRfS{9&0(|C;oc;%wL(>U!vdalf2>lr++^Ap2u3&@_ zOi5m%R2Aj8Rx6Ynic>;=$>~ZBd&&}o`;pNcQ&UBI1KYQPOY@=|-xhd@N?@*#)VtH)MY0Nmgb`^SFJ#>H{-gQ-d$NU_O3l1)d*I+thad;Ny+vn% z>pW2H>l6)=#$KD>P zJ(^V-3Dk?#=#8UE4f&=L$;#yElho>!-ij1|C78gHo{+-oB<`h1l#B=>Li~W$A6lpJQzDSm+kaOSa@kq z(~nye(W%D5P^mp5e;$;#A2xr;W$JbFqTam;`m6`7MYd3AA0=&d1Y@nK^6!1_!%Rn# z&)3EF9sKOxw}G?$DL-uk$HX2V%Dds~A$0ekzG&{~l9WURg?EB9vMG^k7RP#>Uxf?% zLt_Lyh!1w{aT@spKGa~E6~dpnR~5+Rva=6YKy?jG3{unPHgFVOkjnElLs-*&OBtDh zdcNb13qq^+7v~m`DYF~cH8I?V$%ddoc1Fgd1!m9WWDr~~)HR!(??m}N=O};1cGlcs zHpgf@k$Xg;Ri3;Pg+KL5I$9mz*I|QWFLb1uqJ6d7D7kY8Pt5(=`u*OvNVMo|(5hji zpYq<-zL*r=2S+bN4PCxpRIr?I`;zLXPXSuDSfJX2ePKBzCEV>{AcfS_t66YHWXX|b z#G*aHNovMk-3M9I&2jRJO!V7qZC1~9*5Q7acj6X0x)%nspR0e#Pd>vtCp$gEU?z+n z={}-S7mljLd-cUS5UHodS&u672#*G>7GHOqs<4+oUac_Q3 z&ar0a;9L2mz|F>Ycf|-?)u}b4Ay-C zHN6MGRHzzmv8y_xk5ui&@np*{S4gOS`O&1?km=7(8#~E9P4Umsck>QXb=LBG@*x_R9*<3_4$T zu_Ln&r)jj@BFA?1Ede^?A}1kS(Pdg zy8s3v3EgzHm#CHP{&wXt!Q)%#`_NMCX$F%ZiCF@>Y6^CpF8w}ysM55>mawHGu;Z3# zmt~i7(>=n&JjILytO*PMRDR&)(X!mfYuK>NIcS#iB0 zaWQe(xSc@J`m0$Fwd0vg|A0kz6HfE!htH1WJ|f4M;)P5r90R@II5uY%*|16}4LC0t zYl9O-_su?l!F0$TKsZfH&8o;-){+2udqInYc)^Z`h~;K+MGpFFwfH}*2Y+SpT69yY z?Zl_k6xUj+@lJ2XGJ`VrxysMG z9<@BW9JIw!z-0ki)ljba4MpoXjG+M}QXxB7z4<=ba=IkWJknq*f2!4J+Gyk6S2{t^ zbA=FqJjc-qv9fd5L9g05RO7{jg-`VMkS<9*ZPkf7{~JnMRSU1fS8~G?ypVN6nOsue zwuA+&!tgLOW5!T0MJAmuT(ZTo-Szb*q;HE3K1#a;!1fM$P?Z59<#Ara@dV zL>7_3o>$`sgJ1-))Ipdwwt%yLpr#5SaeH9u*BITO2! z;J9RRV)rEhvx>fwMbYuGr4GmL56#O0UmL=-@ zgvUG}%>*yMy>7j95C4Ojw1i&Z&7uNFr#qA5sT)P7uts{_3EC1t7OJK{s7<*Pqw-gK zubgTgw0uOOAxanI%YvGz$iqpzulr!1JS55=o)AnXE4&Ynd7o$NFc9fd7O)xN;wRic z)V|1WIDPwPAy9mPNJMnkVlr#Op%m#Me}CNWQ_Yn??qUI-V>);kT$$$5A}n_g#^)`p zV7HTF8QDf7_zHiQvTAPJoO_sH>{nvtw5oWwlF~R}pYh5-kl1i)yoH5DUmPPcqp?vj zz%cE*5B&~m!0+Nj&RUypm#%(0oq9d1zp6x`1SXHN@wS4fkcq57O&H@;_>reC0fFaRPa_ zwAbxx`N53k$_fIWk?HSLuEwpQ&X->yj`R}{d&gRBP3-BX&H6irN6okF8tjiX>^-t+ z#*svm1N8jid6Jhddnz#>2(LU382;@W`7TH{E{B zF*=&Er4kdYRJ*fPzCt0?76Nt*8#d*GdVF|}AUtk0&5<9VXC6Wkqjk%-w*CM}qKMh^W{wa{$#y6RG% zb!_l(T&{LXRcy@B>@3lo<-{rNwrvy#Jjpz3D~q}Q5AiV(K%13VCOD7pZxGtew|~_jsdmV05QF*ti6Ea zLmgU7^FA#-3hU=og~ZqqR*9=c^S2i}<=&^H#vXKkD_*M1`T9xR0QcXk>woWTW^aXE zQ>^2r72E#_S8jFw@1Rl|Tcc(8z@ys4>ktTEGeEBvzX^5lD|9E#4ZcLI}=U+42v z8UBFeO6Nvu80_uV7cv?P<-b^^i)aq)xIdndWTQ-Ok6>QR)zum747VZ7>9!<_bz!mQ zB{mUy+G(+)RK?8#>rf@T@)cVWT&k$Yl%-0E3lM)tMc3VJ<*ip`gWJm*TG`n<7Z|S{Rln3Fy4L;Osch+UWsKej9V5rLM#A-1MO5Ao#74H2@ zWl0_xSM(g0X$l@K$Ss>+V@|$3N6a@;Cks}7{+)Sx?`-u?2)9{nOSyQSM92^E1BY^g zN3oOF$HW2SF(x(aME<=G{+q0zrwus#_lv<5S!TwE^$L=+78AHLG+keu!@KopiY!&X zTZ#mLY7ZvdGD$?0=PJ>116R}PZvx%rKBB^O;TUMeAS3qCsZsH2fx8o|GRjHG()@&9#^RrX59(1{U-P~#{21c8X#w)w-bAv_ zl(l+{bRi)Elkbk{J3baH>>uc8W4;@>Uegq9_xX%Hn!XM+Hg0|PzU@MEX+-V;zgVE3 zyP#or*mZl*(K(a-#q%|RwN+KiLAM;4^}bs$g3-Rexm?6S*<)|z&e&?}v}1m;%YOPc z|8(!s=TKY?CfHErmD$)g5xe5dt?xBuRgwZVbN(eT{2R+~V5>`_mrCe)QryuKGLGZ7 zOf2W_?UUcS@5A$5&o=wR@4R`q@6o#tX`k{2wp-cF|MPf6(u480ZH8Hk??Cz6&5a&xP4s&aEZ*T^Mxxa!sNXa<-3tGAqq z#{TbHJPM;l?;T=vH~&aL$z7X-dMYXr%Ie)f9{SZ>@^Mt%H{3FnOE0tKwhpYr{-Uep zW^WrDa{q+Tc-DtcwAd4)G_fVaWSb?5r*Np`7eAQ%^Bs}b(wRRy@=LV~NDqjSD2!$~ z?Zi4@<6I#5%}w1NVY6C7uN@#Hj53qz(`11ySUP+^LT(Cy{q$am^7J0e^PSDTGxH^D zmODTgE3-vnP#H)&1kGgIdgjuR0_I949CRO!O67t*EBmvC3v36gEb~2a;L5t;x(~iV z)oyI@R9^;D`CIKOexuNirE;O+!^)Lrv`NI4`;I;e19_NmVgRkqU_J8|0P`ODwa4mk zoxyVLg0Zea(bdNf>$zzaFAa-?;2bkB@l0)S&`lzBqqOI?btISiw5=YUH_EijL#R%b z9Rp;oZ9{ZnVMaA?>tg**iEkU1?Gfb@-O}sHBSW`@QMT;^1jl~K*aL;Pgq7@rRa=Hn z!c$odrNbfKL2P=NJ%#ICebhqW_Wxhm$uHXMEv?2TlQVV)#YldH3QcjVcB>mECv%lo zqq570cNmd2D&Qw}m|ub>Wh)agz_c{$?fN_|4Y$#Sw4 z_?L;^p&4e-`U3i1bb#IuyHWCYsORx@oZbXf%-T)E#!WkU8mE~(?B!zNY$GKxMp?vU zFUuV2W+6`he_rPNP{;SO@jhWA@*}_62348r+Hg)ZB+dkKW!fEDe7A>V{+&oG4y64YaV<2A^FI z2v;S?G|ektE6zq7sU2J%91Yc}#om#3yb5PK1{LYi;bPRruOI9X$-%Sh@MYje3?OXi z0AT|^VkaDVHr*wD<&1tvT)mx2x+e-eOpWy^4?xcn#mCVGaq9_359?<|mGxSNNyz>c z$_Z5IxE+xatVNNl$bPtq!I)V-D7ef0!&yi+fIBdV3FvW(+kPzh#2))9JojYC3K_4# z?yD)|le#Xfhwbeajyz^LJmWN&n}Vip8lylph8d28?{3{9C1H| zyw)?W>NPRDlJ_V~m;~wKul3HjjwhtrQA8N0aoC;69_MbF5nr(2A01yHSA!$j>$s;!RQarPw!2p784LPp4!K%HRCFn{{#^It z=!4y?z4$6lx5a$@I(!oj+1H)a3B+0T@xe~+zV0HhVGc4>a*QHA3beS}h@7mrTT2@! za+CezekJTJX{>>|J>$5L^-M8-sC(v!e&1?`+es^8K#BHTr$2?k9dPk(rbdu$iHg7- z0d_4VPA#C?5@UP+wwXE?HL&auSy*F?!qJ=; z@o*XRD6y#Ix-o`tU-mk;9tXA;PVRI#Ia+XQ80xz)2J<~Njvg-~fBlPFcHOAwQ~7}WqhwUwDuGA#2L?5P!Wp&< zI}sWR!+v=>bUnr!)$UFj2ab02>8ZVN)b{zI_D2Q-`2f|DDP)iP9(ji{#o5i>ADCm7 zu~RxLJJ&OGu#?lly=`eEywR z$!@sYOqbj0a7URvgky}eRtY@LEjqG}MCY8nNpCj>m2(f>-(tV8S{RR}M%CfOe+7$2 zF*Pz(Ls6&PYykV79t?iHj%5~R0C7GAg)z!`t#IbQ;(UnYj5yH^TcO+65qJ9PkL~Ml^ZGhjkIsNH zZAwfRaR#>yIO@p3oAFEATV)=o41^}Fy;#Tc3U>$xB-wwQ-(_THHUTvUE61+mK9s-r z&%wz z_`~Ge!n#>4H zbpg!wQlqLc){o~@5&jgr{X?HRroMsaXun|ENCPqAz*^W;^lj)e0*bP(!shTG*C%IM{s0WmY$IwU20 zS?)?#I=89m-iKtQj*T|>u$5IG;rJXDvGCq~!gMe`KDOU%l{4nBKwfx`bW0TJy#MMB zyP=`6R5xs6Ip8EA-eHkC4t-fN{`W6GU7DM9Q*(f3I@APx4bSbOai2UPsPCSk`Wy*B z?_&5`m#ggFeOT@Kf~m-wK_^z{ooD0VWnZwRt(`*-ks6;Ij<&RDo_JFfuvcR$9#?T4 zPkhd1kruX{2t)cD6bx}?w^U0~uXfi3huiO&yeF!B(`IRfMPKm9j-hBLR&Qlg7!mJ#>A^Ph0tMLKc z^~M#)RPr%jO){CBiAEQ2dh~gdZ+7&dnaMc2jWQ}4j*c2Q&;()l&3K^mSzh_THyqoO zT>f@I$A*el?&lN?3T?bPy2$bzR5QZ|wy>f=;=5)%3GXJkQ6K&u`2Jt{YUo|P+wp|X z)!ffvCxdaQk+AGi_YCd`ij4E%hY&&lU5HwIUk?~%Y3J?Rx3y?eQgS!ytn>nO6uT@QIO_mprIQpZHG^M{%ywEN8=nvL3jucN9A4x)LQfT`_1dsE3WM3tX zZFqCF3@BRmtA$=NDiiHLEuklD%Vf9rYc5w=*YU-9NV{R)f+eJ?qpK@%;lr?7$rfxj z09)U>IY?%9b+$46N494s8_h!S91Nfw4});(S=68C$w$Ml>JN&srX;1n+(dnZ)Ul!I zUczP9dtrJT-)i-%ABp1Hql;4v0&^5e6#UF}ZwrBlKOnY|-T~}1&e16?NGku?8RH-z ze?(i5L;+-mtqJe0K!kI@AS}zKp;6?n(2LIZJ5z*`A+arRSd2<$fzI zJy-g(i-o&;vl(n>r*XU_kTBbsc{|Q->KHO9Njor5iZIn2<9aym?c`jtzHg#SOWdY0 z5a)7L0k#`A;1s90JC8xBJnnuO28P+btmjUi5h48e`j)U(Cxm)Fep7idLGD`_F=f61TUz&8t@t)#g|MSZH_KbJYz3e9gN$zR4FQMXi-uR>>f-pLiKsxrjzskX>lPSj8G2{tt(*8}43 znvT9Qyte-g;}_fjeLUBa7$#U4^g^4x-FE9Mlub_c2U20#(=$;>(n`SKNK zXf3QVh#62BK)>`M_7Yu6Dh8|1Fk4t!(n?iIy}p|hwtLs9+sa0f2Zb3Tgrb(NM9xLm z!@Q1qZmH)WX07R7UZunA?A%nv@zP$%M%J_*eaO*KsR(MAGl@O_5M91;)u!rRvR(FO z`rMBWd%V4OIDyxB(l!Bh-0JDK!1mK3n7#lbLn8!#ag?-tawI=9Y?GMXGq|<@(-8VgB@6`??upnQ9MIXX723%OYbBRGlBv8R%F+&tXv77tHwEMj>1UO zNR37{wF4s5&UYUPh}x9{gq5#WglhgQSi2=&=?E7BxxTjs6%^!_Uiq$B;)46G$OyNx zm$+S;pk*sx9eHgh4K$}K6w%hUd7|Z{oG?1Twc=f}H{rRusY5<^k9GmHUvuB)aYsBB z?DbG5#s9CtrJD94dnr9+0Quab+Wk$FnW_7w2sc*Gl>pKtGRqmYBF4J2V+moruS9 zWtvr=&YlWidhWq@NMQ`S>)b4lN;0!{0fnxvBz>fLQT)DzYLoJ}$RIMq3{Nk;-OII7 zw;Tw!#Cj7)kBkoWHu}I;jo2x7*fEo-7_|)j2h(vXMmH#Lx3$Fg^9uBTs)jy@Ly*&+ z3+-nHTZDsxqc2Ce%ku9$UZR<%#X}vc-aWE$!?eaO?|KuUnzmRt7C+q*XAT-6=ALX9 zFv|mX_-BiWE-_XJ{RYCKt5x*@vOK%TlLhCUp$9ekupm4^+6qIf!j>6p2e>h?v#QGY`kGBMuQepCP#Bp_msrwiK7l#>X!TLFv<+jrCH^KO zcGs9^@`~+JOW0-5Z2J?13Z$mK&)Am+-th1T0=IWmw1t0_IF2Ujm;A+L zzQJ_9OPCPPD2}i;=U>J%Q3R6V9rl=xNTZ0HDgP;GIpKOHCxBTm4!Y3=uGm!|k!PN6 zm~GrrP(v$kcbV%-)Q{qMdcHCqbkF35a=%BaX~8NvGnRa>O1V1=DaD(Zb9JN=%r6Fu zXD#td6(Od2PIxb-S|bOc14gl76g*%3*ux1sO!a}k7S-Ejco=?$ayCeqgPNS(-0eQZ zAB1toOEs9I_+=@%H_DoCw*kSC+uQrc%?sE@*HVrMrk|_k3_2C|1t7gzcvdG^<nw7jA9aU2QhC|V1_xx z*nu3DZ6~)`C+hSXQiK1K!bb(vusz02;*E@T6&}f-Z-;RvkuRs64y&+QTRgA97Tg|V zN@{xsynM|cchIV$=r+*sHd^h{iGr7^EOLW1ifmiSDI7u0hSvgA=`e`u0#e$`_9SHV zQ1T?uvJlK^A^qd~Rgj;(y_GQBXX=|*R#)EHpsBP)I%gHc&ThZ9reZt<#`}7+?`=+6 z8%FY!jR61nk>`1u>pnnhOr$@ALM={|uDZl7TO^;gsu zv-13c2Kr==-4<@8l>7rTcBMPkd;+GmQG8}KSG=zTxoz8YsYaxu_KB8 zNvX7`!F-uPxEMAK1Qub)YdgG9m*_Z|Ja!Be_S0FBU#Y7?LC>FyHy>4BI)zi(QyvAV ztRN$O8_gjwP{ml6z*dK)O7YmTmFi5%P_0?6A7|*bUzhrB2W{H1S89~6afi6b!OtIx z|ALqva}EmnW}T#>0#>f?QR!c%wTT(bPo*)e0@EWq0%b2+&tMy*ZStKok@kat?j^NXOw>{m+C+2|i95q4Ls< zItcBwe{g`+(v_OOqt{-flOf#xYVG-SOM56c0e$Vv3f7oSLe+EOUsCXUCYhU!-Ak9v zO=8aEF=QZnEHGcjQ3qWhW{469) za*DoqfdVx@qwmsKuF`H{EjpYB3-KWkY9ICoTy)!YG2ZFy&N7h5q9us_RRROn;Ljs@ zW?ROxL#0()83W0>d3+FZ?b>4!NKS|$ReFQVfMUS~jM2$m^F=+8LAA6z2Jf%G?vfju zFWa<(Sn7C{k8BL#MwAR8J+9JYI->8&&3YIk5~`o()$Lfk4~o<0vw`2K3YcmtV!adn z$fk?d3eqiI7%i5h=rQgPUF15eWzTDp1pz$wTf6m%+hU-Lk17Q=zKQ4v3Cm&UE zL}M`2Jd-`Tqm7t6$}!(qlb!AMNtl)Srk@-gZT?A@f5p2QF5)ehM2XuF8o97Y$|Dv( zh_KE@rUB`Z{^UvKT11`o6|jSymb}_oTn`S4AkLm zZj4iUBwrO7amnVUKQ>!vS06D zi!D+3BSUWm5IUVLH{DywlN>MCrnGe}{`iMn8dOd{1Ugmj(oXXltSrj_broU7biE+V z_I{S#k+4)aGtUcI;D96jP~JN|eH~@=h?{MhnnxqJ+}p)*+p<0MTrkD_gsU0X>iPn= zk|vL#)KH#Fz`$Q8=dw&{+iR9TJ1&r*u+$~P%MD6 z&pZ;jej4_rPXE}k>(D6>I>YqC#2FRl?Z@9*d`(PLW!?x;)Xqc`c?G;0?2%OenxZ}1 zPQ2q+Q~1tpi)$4AggF9R4!=YSZj9s<4X;{$PyZ9VMhHF6#v&YivTDElsc;!%+ouKbiE7}mik0)X zVMbYsU#MyPAMyU$t}INxC|Y|X5pgu*~e$m0Yl^v^AszR9d2CF?GMdS#rqzz@D17F`$|BEfBZ z2g8*sK1vscik(FMHCDYd0=QcP?x^5# zhdw^nH8^IQ7V~0l+uLm?QqmFz${&neVbh%2)J5+lu zER~enK{hVY7Nmf?A~Hcs&2>E97g=-=F|E_3v-8N=X8xY#cJ;$!5Eb8BDmKdMqsO); zu*|2vO6=zO)cZOf@Cb^p8j?LU(r`1oNU*BKRzT0EfFzO6C(nmO`^vy9}}OFt?=?I8qe``6mq zj1&V%aa}-o>0uG9?MF82C*2V2om@K5ziF){u0K8e>AUjCcE8oN*yUo`z4JOOHQ#5~ z7&pED*!U5f7CRAo^8i1mJn9_k8*ay)pCWRbvChFyKY_XJ2y<7C6hVbEO^JU&C<#Uz zyn@#jl=ihsu|`ER2l_R8F`KNA zT5~!MTKu2v;_<=2a`7}EjBXy0k(O@e-dJ>gs-TSyS509zvO7!nai(=n*@&!>y~!t9 zd6{6&_Ys_8_bW1X6W8`DC|mI;b;imPu`IC+8%98{x1E+7J=WO_`Pme0dZXZsw!Re% z>>G|jpes{iJlDjK{E-O0;;C+BU%Zx1M9(zc)F^~=VfXIZ>%dQaWKVtFgkgt-h7Oj^ zLCSC@eJL2hentxJE$8uvbEr7EjId%+EH7$pd1vWK+QP5)+|@#}jj6}YaCWJYmDShZ znnI0Rb111mGSZA6-28J>jMfWeLg(>^(7hV@?mxqiTUoD|>iJa!g_@^xMv$wL(fN@Omb+>WanH z@;XP|6sWPr3^MNY)@;kP7le#EqY%$zCi98ziBjV)&K`b+8wXm{v-Wk@1$id3ZHGMA zv>^ktjEyVtoBMg=FLNltFJkVI4UN1$^xj^c# zM4JPST%DCHmu}e?MW8D6z37ONe%i`L<>r={9$tN2cKn~e1u)sM&_{6QHz?F$DaaJ6 zlj5@%yjr;-Fy;By&=ickK)e!=rVFP5y&;rypw|qL;Kz=PYVjUp(ZBB+RnRK-U`1Qeu~prBY#M4Hlq1%q_y0g`~Ih)D4P zL~1B1oq+U~fJhet(rf4eLI@;464K6czu&vh-ury#+rH;IKleYbDYC#X83t6H~Wd&PxiJatH36i#bMCF7v_b9i&Nc$pgBY=51#9Fh<8&pP-ZNs zOHakDJsW(`Hk|rw5Gtq@|1sy%gBcix8g>yZyR!j`8S^9m(( zZUlcNGK*VGTMgcVnTr9f!n`qwImUwR=7KRvSoq5A)tLwht`F3yLNLA7&#O(`fm2Z9 z`7xl!0B&q-ExG(_z$ZBKM=_7>Khc8!jgH_yeH3Q@o78hvPJbgTQ;OTCaybpfNLG<+ zD2)?YhjcF%T6cfBouuLSNuUag#g5GQ32RRe?ve7E*F|6{A~UIuF!Mxxg)+!t#M7*~;=b8SM$7B7y5mk*6Jlk=LT5Af{)jLIMsVt#eRSaDEGfG9qV5a|I zQHDUs7V#H1ckC^h)niEj9kb>fR&*M^u>3(qEb+Uo!aK+(s7p|Z$=#k|FP}Gk<>v!< z!>QziEK+U;+cz125u|k~Zd-_gklg_!u!~VOD}#T&@KZSj1YKZN%3;arA7wo#sX@+ifX1sWp8riZh?4~JRd7U_ zN(8RlcCg^(75tcl0q`J40qwvkSMWn#!JVXX$ld)@pw)w`%sH@wTUS)m2!Kf zbc?IyPWb*FJE*@c!?PvE-8+*0Xg5{OAfuV_`Ze9ARiB5dkjsobr|>zzXlSe1Frv%N z0_>UoO@%)9>uT|1eKj@V_V?d56wQ~Yl!k89M~$i%ml&U#NoD+AkPSv{-VJ@JN4xk8 z9dI}msf9rQ{IM%AiH&F-TrjaP2fyW_J@xDK3$U=o#8yE#<#SNwf6U9dIo?hv>P}U4`xKZ8dmJ_uiaa%(DYo zW*j03^y5Rc3r4|Wy-fgr7s)uwvlee2zfB;F7@rPBP#wdUMklne2eyrY)XsR~wc#~M zh;n)y^6f$^EW`B1+j`J?>a6V@V2RJ`>7^ej1)a!ZgsG@sGP|3uX(K3k@~!L0Lu8c~ zZNs2A1S2xCI2@_?-z9;6LXS2N2sh=SuBtYK#j` z9H~(b+u5n|CezAPME9)QIcO;=DFIy&7z}9Xt@GNDEQ7&76VCh ztPNbbMW=FRK|{fATWvOZe{5QCh*BF0>wNOZ7$cL|42ttPYN4(M*|1zeYxE^*NPD>$^zphwQlk zX0rEFXD_g>*<;Hy3V*_}01$mkuf2LZ&aIRHtU>`{I}eMoWO=0p^Yc&Qc-n1s?q<^O zb-k%nBGtTcIUE)NR9NbI#`>;JakjtJYR0Ee1t>eE#a=J}_4r4mjJ!C+($-4eVGztX z9={}-eV*>EMy}G~X7QbNOG8zi6a*`-;es-E<4s>p$!bN8*y zVfgy-Kg;&cdsVIs?F9x)QmH-r*KPn5t|au~WtT%?%sBVCg5qvhHwSC{PLDoyyf=8S zf1*L~tK?W|kWC8)$jm9xt0c}m3?rSj+`U-l1G!}l!Aqt-ouSP%vwz`m*66j{)4tYR zLEhd44HSEfWy^qAn#b22ep6{NZHcV8ES19yh_hB~<ypB*N8Eq?fzvwnh4`0%1yzy@zv=g@_`srV%ofd;1P4c=OmL*sFgwR+c4J)VJw&l~()x-&x>RD3bsU0t|`{klAD z#X}vGhKEBsU}(d{BSxNw6yHO3TLX)j%;@dy8MdxEg+Vu(5*~V9zs73ab=mFPUGG{H zNCl(4)xNJLxr%UBRu&89$G(Q~4+GumH5%PSD}?6N*|i8eQUmf`zIUjc`sQX`MZl!g zjZ!krmgQGCkyx!p9HQ3dQP?`G0R!ND-@_%e@6yeFTwGKxZw;e6w{ zU+3Tr_c1~gM%$lh%$f?g#gtrM?i6A<6WF}Qjv*u2sVauODlNE7uqe2@Hx~D86qZTF^1|=!e;aN z{V*hizF7urJtWyZUjo-ZX68V9S#GAraqC-ye9w^SI#H@yLToZT$mgU>5N zHxCZG*=~od##iFD^Y;jimmXRNi6%}Wnagq?vEl%U2Pmfu`MOukbGtw+G^v;p{^F2# zpMi03T~ZKss^*fFK0P0lM>Qb`3#ZKMu0p4!wKQc&ks1wbwHJ~eAC-gV#aDItu0G{} zSU(|Mzx4>Q>>m@|L#`uT=B9eW;SWD5l7kljcKtrUt~bsz7R{*L^gpRl{JK}e&fXf{ zyC(g}JT0XueE;O#+xnv~*6#e4$4p#^6Im_qVwL3>yKHYlG8zLFypW02<=WKcfr37aNc)1kCByM1wvwtUXzL*>uamnw4FZJc!H23&%=n|Vlx2p6rYh^>?8$}9>G6^trrylhj7qxsxSE=yuWUq zC8{eGE;e_vcv8u5YT(l0xgfiw5XEBEi;otyq!tL5u~$yeyMXAypIk6_6Vmm)b6&11 zKVEMA8D?q)HqJed|kHPs{LTO-BIbQ(`*eV`1>lU9lXkvwMrS*S;&<%ARwOYqt1 zxOZp<-)Oazq;WHTtf75=Xw`?n^@oK?#=vppKv8<#WTpKtFCYi5NrwsWaaKTdze+N# z9mx*2p*L1iWslTwARHz~Dc2NWJ}1D881Fe+nfK~eXP*A_blpSyUdIp*4u{MXxCPhX@@{59y=VOXhyqM$Gm~U1!*u`hSF5{e>XCYu1J zto|A(Z}5B*pPLIgJp;016A+9<8z@}md>_uWtIz)p*0}w#!Ig$@C!wlNHyZ2ftI<}x z!#=6HBlYn4>OyNJ&yj?zttZB@t#pX10c;WKX$1PknjBND)aG;wx8Fh_aK(MFUD%qi z>1%8&*@r~?vU?`9lOF?oFI=fa2$zQ%-g82$E?cL>2|fO#w6)6>8MZTfLc(kFS0Z_u z%68B~%s!sXsYc!?wI_P^4phiZY&`*Os$Ob(QUrv9P^<6}G@F2~18C;=1h3WMnFL^? zZR&SIH>3v8>%p%yA~KlE1J!7pH~+O@>tNWFUfNuYIqi>!qWzPbrt&-NhxhaV2768L zg2ct zp`SrKh4?!*Sikj7m(-6BvD&-7Gq3J#o^T!7l+g5&Etmkm@1ri>PU`-yZuJ&h1sU23 z`QBvks*a*|>5?Bo+o^K5a0F#+tJ6iN;B!tg?OxTlXcpu_kmTJA5F*R&Z58B3$m7S} zf3+L3>Ug_y+{e&~VdO7?;|78+J6uPmrXB_b2N8q?R6c>E*-1*^NLNJdd!zdHd3Hl> zb^+7SopzEBd!A~Ia!?+5lGz?o0a7xWA_F|LOB#;^@8gTN1A6A|Q1I8v_Iz$?mq^DFa981HOKKACut2QC)pAS>)HAA+X{G7MiMO z_7(E6uo{xb{Iw`hE|f%~*W;{tD5yaCZC6cBSvz*fYhy^ky4%9yHfPivSw!hhDoLcj zUOZ1e^!yDGL=r*TE0l8jm3Lp-$Yb*FBPX7IVor_?02|zHgyHL+!IR2 z|DenMWYV5MZu26Mw5E8c3okc-;s?nPQdKURWW}J$6>B-&4!eJQU%bjTh0n$NVJ4k{ zO+J%m+Sn)&b87)0VeMWG3jt`-n_Ggqf#fHqLoNi&b_P!VH;%snZx53ui@IMjI2FDF zX0_w&Py<*F6PFc;87H!rZvzc*% z>6H_*tBM_VWIyf}-tQsloO+ymQs9tO%$1Ynn-;G>?EVO)h(6wE{Z7^!5d&NSxAUMDAa~nk!g&_G1tdCP@P&4SHt$yf+@<9#xtDZL0D*83VcMzx=_pmx z^lxRO&d7aJ{==iW++qeKB0&K_WAmEHVPRo5AK-vMjdw=$v|9$M=rnRdP-->SAOIn6 zmWft8KLAGiH}VpZQK3Or_mL?X7)z+0%Y<=LUE|Xl_CloaqD7i)%lg9}bmiYL^F5gQ z`dl;)hZAj2tMyAOda19gVxyf?z?d~O+pjyG%6RHTh2&%h^Rkn&$@J?5;drMj;-%}?ja^Ft0D~79Mq0_wKj{o84Jei0S))G!b@$0kRM`zRj0NZ{Nt!9L zwpPr|6k+*W5m&5kVO>_oJ}zn7zjxUX8`t-DyO?pcuaY_VvPLQC$;^+Cy)D5(b=mpp z0lJ+tkE$CL3#z1b11~!8^jC#wUxnx@o-$nRU$1$i+8+@ms{Ap(M#^}8g^$j5-n-r| zch;qqwpr96Jqay7KL4CA*1PN7w&al_5`h#=O3hFaH`V`?np%7(#Whtq=?PbtNeF2H zrX|IrO15!&$mtfP>=(#Ub3@1$Yy*W519}{K(*Xr^`9EBjt*$YR$BglR%;^;Wo{78jIvWg?y*Wxgig4miz7LcA+ZWg7mwUl8m zLSW?~3r`-F!L_)iz7Zs&#Auv3W%v_4EZ=Xz!ZHXDHqPu0ZrO!2-eWVA8_J*FV>VI3 zn7Yqw(=-Y{;u^H>DP8-E^V(eyfj4-CVy!C2HVch(4Ir5oTGHX%`n1{Dk#~e*$+T!L z2JkW#} z5-AISf}@(3`@rZ~ zS9HNN2uc4bbnPvi4)6AYy;(Juy~k+*?UYx7v}Vra(oa9g!&{sD@Yir1>n4Fi4xgP!*p}_{))|U4d#-a?ThaZ9+nNoE| zHZ#~om!+<(3%8pZy@kr3e}xFDY{8W&6(&gQsaPm{kOsjkGVoR)0@uq{8~esFdF%jC z;9U~g7zhSia7draxH406PD@}R$-`266 zNE0twZX@ecN7*}9M!>h~8$6dpmt1e?BR^e~8lsEl*l_H&>L2GImz&%7tCc%{siy+{XeAvz|=9dw{kE(djE~99zQ#+>P=*L`eCp}H5ra1 zV%^rIgT0M{IRV;h7bpXZ&H+Y$K6YDQcZs7p*cZ015Q}ElQH)iPL--F*I3;PYUdA=Z#L_FR#uE7uQMzJ=!#srkO zNg9nS2fLoW%0SZk;sY#~urQUP3{;v*6N`=t_HzSS*41lg+prSJ=AH;+3pweBmEN!^ zecihmnr7$~Q-u4W22KlNOZof4EiuXNcZ+vs*v~9}#Fhr=-6>bXJka;lqe~)w>}+n6 z`EHl9B*iDUuf1uE6*%;f#H8SCkT)(~4$K1vV4y>CM<-wC0kB9{q;Pxocb03{=;#A_ zPXGpG@@k|@{`BrPW`a=tl_jQx4HLytrVy1?40jMY{sqEb9#B_A5VlhU00z4 z3wZTh5~d<8QclyZF{qI_3N#Y>&>7&>U`pk#x{tC}tPuzV?3cl`DUNmsY>vWj)|c zBO9&ZtXqB%wzUiaBJi3M<5O=dT_^iHy*xI(@XBuF=8BCOTSJ~n z{4VhYFoHI%i5;_jEA~ZD<2JY2vbbA)gIC_N*_Gkq*oc#7rDv>4U#McD#D*VXXX1CUiZ7+n{^9 zqhPH)M(QED1i-#ZYS6{zh34zX0jnwy1F<&Rzp?&cCyD}&Y8Ejc7`W8&?qdYt!yjR$}Vb=gbE8PVWPYC90nvz@AsVN*ts!Vf~S`<~N|A<1jW-)CtzWMOyVfTXtl?93-OpgEp2HfHiJM)| z?wA_|L8k%eCY6|qN`;nS7ng?E%-Iwu1um%H=^iwfNZ6elI+C@3dtduJb)Skr#=!ko znLf~G&y>ESZaW1%`}!tD^;n|{t)b`6aj7W>d45=lO3VAlt*`wFOV1>>nx^c%Pqx6u(Dl=0=u1im0_)!DRn;B;<-P`Mr;fdp6D z1F=#(;klDx_fCDGOs*|yBKXPLb;-jop14(%fV?tg?Z!R9vHTxK{QdlfsVL(4Enm`fk9O)6(SQeawJ^y<3xJ+!*6650X7yC(*55Tld!1^ zakXi#BgQV%Up2^j$$-1{L#1b9PcJQ;^{0rSr*>*$Jl~;?%^1lE53P-qFMRl;|l z%DA7>WZCNjqqt0CQ9dq9g~guJu1np)7dT^XxH_ezRQFW64M~HbQDZ02)P?II} zpz;X~MzIFsLAyPvVcHW=p>!%1UeG@s7U8F;9y}TCff@E!0L=U9X5E2+1mY6g$LPu* zQ^yx~*8)G|LP#b0Oz$P8Lf4ZB`__3L<(i0P^vD~Q*Iw}A%vhh5%ss7{)=UN1NTYqE zS^i1LMYy93H`T0}wU9rr_7whf`39>wun>N@*9Q_u?@BCG>qtx7rbG~nhBNlizNRq4 zsVea7cRtak7x;4Jo@5N%+gqE;#j@Idt}s3x^xiE%@Xd|f5B-Zxus3z7GC{qmh|G%h zYT=LNJ@h&$PH7iuY=?iMns+k5rGDgFxBgRv%Zxtn+XYyH$d>Dj{`yF`R%j+Pq{)V_ z><`SwOO1b*_m0<-1{!rKt%W<{%Kq)8w>vckyglXj zVe*ZB8GA?q6L{PYv$T3EyhrTC7m|rbXV;6<$dh74pciw2K=rCdv{Ue7U57)Y2#yw| zSb2ppe8Ma#t%|I>9coK6u2STaS{NJ}vd5Pd+`Yp{!i+-^&lqGs=8v-R8hk3O8-}l> zO)GZo;ihZ*Z{3*kTA`6c#{kqPz7*!P@u!}qJJEnb9$h=AeG5Q!M4h?ucS?in^#C?N z(p>nh$HZ5PyCpJ(FmOa0fu`W9|@dh~BfylLV&2*3nQuLMgE0p3uMi~>KX z_+8?$f9>ba^`-1{x1auQu6Zo1SQzl0BU}g=|FH9m?;hdY7dsCkAep~g&pZAvIBpys z#d6>2^>I30ap}1|u4p2fGPGMO&dBO1%o zlYg9TjiQVfV&ePJ{}12%3;5>$9P>Xs7XNkBxJI$>MR5PoFhvo~(l5q77yq@iPe9-w!j9|xW$iX9u{Bh{$n)v_o2OVL*Fl^OQFXjew(wjM3wwOPw`)ZxAeEW z((d)D|Mu?u*QYHEb`D@Og_P@*{uQ^^e_Zq~Fo{eMANymb;otMaPS)=P-mqL}@-MG0 z0T}E>c|JY(uLTxx)uR!>*)le=2`c~P*8O+6doO>HGH{E~9~A%n{QvY10G|iOkpE{v z|4)ncKMVTbJhT7vg#OPH`p=Zr^#4=J4LCU204-nm_cAr=u?H04R$W8+_rBlldK0!V zwg21y)^RM@Ec1!7|1IP{%D}C#vnIHo6XPDSTHR@^$w|AZFXiQh5}NnsL=c|v;Fs2>u?#mHpD&f&sro5z_M^~u$2bg)7U>BR4vR$9K#pMV4F*jf_) z@9aEj3!rz?`qRIO?GoV*a4Fsu@Tuw*%@8V5#{`nr){3r_#OzcpLffaAg))qKK#F8y za`#6_{?8uek8$#RkL}smh?ek(Tfn4iJ@d<8C15B~FHl!mU7gh2!sAzOAHd^$F)Wz> zXHU-vBjmA5!8D^$Pj6eFG!?c7+e$-AN=X&tkd(4C9I8E5Dj$b*ckkwTyuNB0K=noh z1@E;Yy^vl@tX>j5zRJ>7Q?n<-AEtpHtlLZ~Krs?x+G{=`D7!Vq<@W3P%If6W@_lz| z{oXF&E13`7&*+o9wU2=izCW<1061E|-Q(s%KyiWDzkrzsban(B1+X{Sq|(E@pZ4Jh z45EG?&RDMWFbIOj%KL0m{3tr{Zg*@IO}sLF{i{Zh=HnAOj~!Mq{ZjU>0l4JwDmXfN zvgQaS^-Fn9F}vPr#*r=M0uezxc-MCKy=ejy>Icj`PV*QV^AWIxQ})$-Kk%ffH^u>Q zgg0^e}dV zt_@nW>Et|bI1v`z%8`~{id_KE`a}|Q$FqP$ z`b!ixhU2DCD2srD&sRHXDR~eTj41nQJVXOA>#Ro@tf*<)?k`c~K^>=*SVv9I8N5C7PSzwv##y6;D>@gPtCC&aeeZh&W6+K-i}lNyS(FZhPAIXYB*t4C9y`c% z$k*Z3lh)I=;A`-J=mbz|mzJIp@DoDJdg&5Q4B%W8$O>VUBX$X0fT530fh%374N0#B z=MiD6=GcUcnrTngxH5{!TEhmtU{zbQO;RxsS{Q;_C5b~yzAn6^b9Kgysj~ssLHFYV z@T5wZ5)MUXk7M!F-nwZZfa;~{SN=EA16YaQIjef>&L{pgcK}^zgu%9BxWZs(;zUlW zga+!J|9sb|k9}+JPq}$S+;9_&DKzjU?|Pz$xz6dgpPI)r@_dm)=E3ucvp>RFu;8zM zyI66HMIEq{J%VlsBJO(6uy=11C!{B`%DWI&hzE7G7ROJ&8eytX+D46P#Rx)1kyQdq zsr+kyg>@Jkz5ZUSqNgfs>U%<-etWEphQ#Y_LcLTAR*9eGC+bQ|ElL1EvBIXFt~An< ze&$`s{np1-YU&-FeofIUOu!v#8SAwL(ce`SVXYM7+GFcLfZ6-fP1(({G^7Fm$wAR3 zLTBTUvtp|?$5FsQQc@ZJo<$sNQzrgbfES%qmF^D)J%}Z;WVprYza!*s2u1&f>iW1w zxU1MRacN``)f?U$J$)h;*b2@vYZR(>*M+g>=R{XXW|4EhHtw(gcSQ~yh&Y)0F_gngY( zz3T?LzP^y1;UuT8<=!1ywQN8K6w&nLkH{A}W{c>Ya)peeePyx;b~kpON#EM_f}a%{ zxgtuhZ)4LWA5W`d10c1aP0#WR&ix;#H<;{8RJ9{D(qRqNWc_ZaU{EhEYe5DXv00zM z%&;R-O>J4ECdQ>snEEbbeK&n9#Alk+xmNAEPkom)FnM~LB#XqwGK1=+gs4XsAS@3$ z=d^et@Rl=#`xfO{4(yxcKWi*k*wk-V~EISI(=*|sd?Bg&DY9^;=& zPmn%cVEBTM6dSQK&FKDnzK-&9ojh%Ad)Y*s>rkkS!C|ibq;q}dN1csi4U6-}a+1r- zgZ;5%<(y{r#rkrHe}$}}*{`=pi*LN-x*59hO5;%X^_+gLlgN9`M(rTEZ zj4O0G$I}6SNOF2p`@-HRWWLQ6C0(`e-D*nDmGIN%_THVXSE{o=z;{OpXK?9~y2#~WwKH5_0&k`-e7u+Gc;JkGgX7<=yWEB&&(+_}F**aB za_oVtICJcr^u7($o(ayvI^)n@_;QbinyE@*?XhT)ZShp@n}W2H{3iBK=95qMD5$Ba z^J>@y6!$3D2lOO5@w9$KZ4;7I>kdg)f9{#LTSiBJ{A8W>=~HP`Fzr!L(74k2l)uG! zC4|mUMypo3QogHf6(px$BOhyXpvgLIbhnUC>d@C8u7|b{RW;8qiEDOVWD7S9Kar@Z z8luDENg)C8%N0-m+eogJ%*%N1vm*W)IS-h2_ib#naY(74)< zluTRdNSpH^$M6sRWsVjfM*6$cE_~1D4n6j_#mRkKOs~~F6_S$DLh9)sq6-VZ$qv}2 zq`b_?=*=vy{3|r#?}U3?2Q>42PM%IhcYFz$`+AlwCT^(tf*%$k-L_=R)}Mft)jUsJ zNV=E#>)4I8z%TMridcHyQf_vQFjf9m<^t&zcbQpJxRd6kuDgkSqfX9&gKbJ~C)?!m zZ(sYk^iWBor2l$zrt}rL=UN_HA%mjSwgh<_yb>tsfWh7~nB~g^1usK4KQgiKWv!3SgMI z{(&=Ea=@bhmne83ec((eV>>JOzRN#C<8c;Orr~k*hsRiDrzeC2qaR#$;#v@0pOCDm zxV(=+T^}yPZC`;vB91ro?v=ZnE_mHfVyguMy>%q~Yq()V>EtTiAICM!19#fetlJ^ARMRs1)S}wm)>SV^1=6 zXXorTN@CxzA?b35Xzj|b>t4r{gG!CgvGnIU0Ujffx|Q4?e@Hi$c@LW0x^>QL$6Tgt z9)gfQtN3qs5p=-h4tB4Q-cCZatwRv8x_NN0q9>Uvg|p$5w70leTi%OGKN(@kw|t%X zQ3I#tGP9cm)_RHnv48p;Ux7b%`L$%fUbFb-M^a}jQ=FauacODB6--Zi`S9nyTvMSj zO;_r={hr*aq19W!?WmD&n%w@m=C(n0jS%n^n#rKA>Ud(zuBo-!r_pF@$%yk$(K=&W z$8-wQM8vFY;JpX3&m@|h+t~A^2}l7(xDW#|h38iP@!|isIB+<2gNq9NAP+tFkIWz* z&&{iQtOIUvq~ndq&EyV{U!|J=tAJRpw?;JL3b8^qKyL>A=*1O-l9bHc!rMpk+xFd( z?lbGV%}uO-T|PDj<;k#C_}f=Ma@lG2n6;7BS+^VdFOSGT?>op?Hflp;rl68g^cYv2 zQ9ExS80bamG=y$owL;~otcOvN36_?L(wQS80|U(?19*%XXw1%lO#E4Sm*M@M27WJ4IcpP?0br~^w#?iKD-%iFvk zZu>TU|DJZu%E-Z|vN4WVWIX07c_+RX7)5RV`LigStG7TAa%}U1U7sgic74QF(5fpS zNUKDDBWM7ROkyXve@bG{9q_f`!b@`9^>EG`SUKbFFY?P&mq3>R*C%ZR?&yfXzdV#s#L!*m4moMfIaYuec18D?_l}HE^Ev22?BkUv=J&#%IVPsqTe%j=rb~3x zV9+&O1aa;Y;@zrb9Px8uW>Ne%ziPqKf$Mk$V@;plhmvNs_c`tbN`8r*}1GY$sC8&A?T_aeoGjkT{ zm~JQ3+{YI0f%fj&r0^P6L8fVe5NgIn#65TW+O(w^5tZ~e}|wGjA4Add__c~cXLxqOJ@r) zdJ^;q^hvYJ&&O?Z;MpEMBATb+vI81dPtWTtYhTuVd@x+_ghOJrE{#oPa)pk{q36%J z@9vR$(qB-XBdZu|pt{=JDLSc9fGk36KfLiS3ZaV;F-*EMSAIP#Z%E*hclZl$rP_|_ z=st1nE7r?{gP*d^k85?vkxCs5uXmtUbz2yJ3}vFNqX8i*9@#~*~~L# zFFA~&FXvx=czjD_F$eEFv9|U;5gEzJ7K6;Y9v^9wJ6mM0syZ(54CODl5QaDG4Lu#w!Ze`|$fG62#cyxATMx5)ETy2?=Dcs-n3SZXj>=Sz-7Sc` zef(CxP6o11;j_Q{Ryb0Xu83}|h=Q*l7m9y-EX6Z;ZXI80ch|JMe?O*eMO@h$J^cdy zE9U;T-C5}8j5dw&2M4fLmbKBg)8L+@;pFk#Q#XCc$~PSlfk8bzopOn6DXEi{IGxWB zt`8B!23Fi|gnuT<_6ejWRDP&51Oi#;S${?E7J1Mf->ftJWT zWM}xe@po1i&!gbhY_?ICw3z7fr=Hdc@Au*J3FgZNgU-trvoC4+cBrjyN58_{Sk}D% zpx2o)stNzd)L1#SCLq2J1CJlv$2PRTUDh&2u&n`q%HMs_o$gxQW=;54r;uPXTSt8|`g7G;z^tXjl(Aeq#gfT-PVJi#`#ZZ{!YUI2el zXhB%NbJ&8?u$tRqP?1&XkGJ8ON1(!MkwKL=K@+qK?G4w|q4I4Ur9{uh^}L0^<3TUJ zt89@8N@jJm?G(4Ve#!}C>yEKz>|>IA{pfa9sZ$+J{(Tw4h`Ue~aPMAKoy%+>Bh!u<+>8p%c_LV&u2Jor`66`M#KOCw79289kBmI#Xvl@RqPV)D zv>5l+sw2Q;=3-eFBaN#`4reChd_&*p`8=aaK%6gA1iRuF(9fC+Ct{MyHXSGmD^lqVYd^o-RA&40vjZ2{Sx%T) z;W-+*An1jiJ5NL%RaSfFkYIZoX6SceXU8p5***Na-Ik6oZwI&LBj<~inR(`CxCT$j zbr?P|4DXNJzvzs)3JD$DBK-WB)_KJ^xLi*|Yag=5_On5D$KZ2Ae&*2EL+8+#e7?}X z$XU7sT-X&>aU^V0WCn!Ui1?|Le&k$xVd!N^%LAMrw?z9Bqm2e5LT2k9hh1%my_qci7&!qBAYjs=Dk=EluSvpzD6}=i}yiCGYXU$VD-{ke(n=&qn%jID=$eLfy zxi3uhuZO1>KGDdKI+T&ACF3j+# zN0>$I=^QI<%1gL%)p%1c=!3q&Qs4ZjVb^vi4eWhMExIwT!2rBqMfyOSS}tIR;!cXx ze%SdNd^z#7+`>Lg(XaCJ!4~eP!MjO2*nE$KXuS?2yjSDgaE*)xVI_UWPlN@%Oasb3@nZ(`F*C~yO&ck1LoHy|cX)&*x;%Des|ei=zJgU(S9^fa z_~95kx)z6;zH+v&JOh~tVlHq|m%}YML%GKvSeh>Lc z14A9Uu@%lY{hct>2d?X%wyv19+nw&b6NJgU$ufcalzX(?ieA`!|H|j7?_>)0x&{q( zE%Y2}1jcZ7wXCG%)az{KSZ}$0@=q|Wcrx8G{lOFx!Sl?BSRiGufT!il8)(RRW+jQA zOhaVcQ8wr6QW~D7c;x#xB#>J!U)Y*vU^jHgI`ZiGfj#RyD|a_PEI&}@B(R-Yj_yP& zq^WHBM7te(%>RO+5pu`#X~)tJCV3PvzVt!qRHs;^IDR{&|OX z>RqYSFB*1;;to4)P}C-fm~xz0nv_GnmL)IVTUOsEY@gJFptvu^y69i}unIUT9WnDA^;F zS)#`WO-EliOAH@e;Z~R--Mw2&f0r+>A+=yX9$s?(@)bD{s)IJM{5d*uG;{U5aqie) z!)fuw4aBEh3jAi);-;>Xck3mDp&oXauD+|U3y<{C6)>%+a*VhvbRKj#6?9JLDdoW& zl=d3_8h7&k{DfubZGto&boYkzrKJEl>|vrq?&M8>Ca>^r0<&tX?$8v|-la=T(3dQU z)AdJhUxP=blfEk7SX>L(3OGgSDY>4^iSDw_4-rx9+`YuxxJ>yAQ?vC@o}CqqxId4& zx&SF(IA>2L7UZXYGxv%EwL$KGPBHcK@%Wi~Izlmj)AxaKZhX>J7AcTE96d6Ns%bCp zi`^+nP7-a51Gk|@yR_)ndOe1hq3-YR6i2A#E|q_RT-ulN)$kTE_VUA}ZIQ*2l6id7yCnSXT)H!(YEZZGInWwh#No#A6iPG4F;nHZZ1e{nB5QI5cxX^*T=Bec-?=%esT9Q&m6e8Unj5QG|#)ohcaKcUOTH4`4ZaRGcu7a$JJpHdGp@s z=&(5SVwBCe7l+?G@zrlL6Poesdm*OZ)+gTYQ%yvwe zATV0ue(=1>)xXu{e~HL^?N+Yu$S$6SGNO+=rK{{I#QES_KH>E`XVFuwY{lS!kt++ynKP|;Ft&a?Z12lm1ZNj{p>9^IzRH#jt|$0hDO8tzB`o~Y zQ(Ett0hUh+W*0TUJtX+;;*Ddb)>s#V(9;yrc@>5qb@4EQJ`} zr7w;Mj?d|YUL{1SUI<;i@xuKE*Jpo9dr6r;5pxCxE4~V4`&9KMMcM_P*r1@`tWSzn z-vdmJXK2lz_CEE*Ziw5m-1!h|565A9_I7k^%b73)2~E91u%;oGbCAq*arbOia|H66 z_uC7C_nx_WqP9nnA@fngxGLINNiWnU=f+0chib)J7rBCWZSS4uIkrAod&hh9#M*@6 znX95uJ-dhvSQ&Fb=HBx(-#xTV9GYdHb5Q(9pB!g`vsLUw-#?-?p~u?S$91uDipn0} zB@hE}reP{}dmWk%E+5@G%@vPD?D{K^8tiqV;bUYfrWq3v)8Mq-$jIQ^H!c~k1cHITn5&W#12|*Chvh4Bjeq;|feNsv1jYIt$nZ>IvXG@z^<}4w59F9aDcjQ&+ zsOJ-sRIfvCbzLc!l}s<*VIw)4)?bim4o0RvWF9(mj%{p$$k3kh^lwvKc+yglb?x9I z+iL&h5EywUwFp^tWBvO%_=m?H?m`9o}0*FS_>uX z>FiE3zCKwp-@T*vj5Vz^6{=Uak`nuQt^f6NBBV|w8@E^(Fq4aBpK3Q1`b@h?ye8afsJBr6Kex|p@>Oa{pq&tUh7*d*{8M=lVW`@ms_I}UV=equ%pU=;4t$W=CISX~F ztv_ybh?3`oZ+3<+!RL}AnqW_FMEuk(%KZ=hC)ep3*ODtjNjd548Db~8Pt;=vWn5a8 zP#OI0(8Ssm_}V`You==Lxb^+Ik8yC&@~0a0G+>v6)-*uW3;!&l8Gh3sqZxCsP%(v8 zt%C59GQ*Z!y)XPp?)(iUzL?e#wpoFk}2NNwf)D#*Q7FLsagIBaB)Q6Q*RYsF; zH;NYYyEJj~4g|W8$n@`}HX2duG+?k+)O7psOft68ZdRH(AIMeCd=wH{{|J1bu#x53k)O=In`q-R!adGhd_a=NX{)Ast=cSfRb3yA9%=gxj{DGF3XFI2B9*?&3 zJ$GIF8WnBGb=`U59|B2qUXMj++AY=hQyZ&?DYCjG`V(uo&LoE6u6;* z-!MSpdNS^cv@oy%eFyeByx})u^Y1}__Gk{lnw~YAzO}KiixlmJ&k(a;t<@__*PJz=yBxBPr(h_$23dt) zQ;yXL81^j5F`N0Vnpb!Pg#nMkhxzHO0{mX_?dX@d-COQVD{Yz^Xuv$fuA(h}cy4@y z=MMR7JzeD_`|!DScu$+9w^MK~{q(ywv;%X<5rt$nXgDrOQwk_FGB$QPz}Q5wR@h$) z8>OhGGny}RwlN8}oHQN-<>kF(CYqgnATJlTZn5|Sve#!yfBu8SX|D~3KoXdwW21)a zmeH$Cll|!E-M1UXuD)(NDko8Tu%sQ%RT$GFV8&{KXV~*$dTqFc6X@+O;}MtTYr4~+ z%ReoPrkmQ3jR@Gw>pksvYbn2*8P=e^Qc#1DV*zmQLLMyH){GO{7GsNR#@-#45Ycv< z#Zx-TpSF^5R8hZ~hM?b8dHfv|nNAf-n&aF)8QN;9{}*z*QDsp>@SPKIwq0(w9j+ZV zxY%(zL<9S1 zbBK%p+Xi{>c(9B=i9^Rae#wNW^Ol)@GvLH<;ajOAVL9fv`Cr)7 zU5xK>B+tJyDyQ$!9)2{^N@Uz0hId#1GGl%DV~w_$wC^@N00b$9P?7lYiz2&naLz=Q z_+P3&z~xtSG~i#d_qW0el*M4Q$h!&_OF94yJQhr|sgI@M5h@0C>B^CrbJa~mO}r}O4xpWt8xkIqfBF+T@QNv? zOU6_z_q_Bc%&tWYr)eDL6refEG|SAvqsv{WvwVmDGd4aU@QsRYn$C;G)>eXu%O=a~ z4@9qjHCsJ;@c9<|%s7Y>*o|{n(M$c_hU&T|MSK-=`qr(!a+YTK5Ola%zW}S-Wmtp+Qq$06rz9{6R1gX z6sPTWo`w*cE<976jE*S%hlm%O>-#MA)dQ3Zq2qkyp?vuRBH$O_m?t6Ua^+sZk21_8 zn9)LJ)AW}~PkCi>ai26>1)OLVo3twZh~1SwHp!+i>(BT8a60k((tebHCv5q5V_Vr{ zPXa)%?{=!;u^${u(3ZachrFwrMS69RR-X~txBmKM@nIWlgvAPjH}DaC`gwa80dA*h zB6!67Py5sNLA`$=HBK?33?tDN`V#7qQ8?tg0( z*s+$+FmnfeGjl(ukariAZ{-8WEZ9x36-EI33fWCFl%JW8y{Qg6wC5KWPBRC@++FTf zcV0H%p@Ax@it_@>3!LST<-7uAZEY9A0HEZj$3pV*zAMOoUra>AO6YwalcB_6>FL^% zQcN_8lml7vx}9&wSx;8%c`K_w2sa&qA${6U&pD=a!`X#vK+_{r;GOxO+w>V?!Yp4c ztGJ?oKZaUGoVgo*Z?5|H%UtwzH9Jxw)VW{0ez(4yI5NR`^FL*4A-PDIgDI> z3iZIYs#$OQ&UwmN7UtEfn7DwHEy3(oph#p4?71^2Ka!-&$It#q9Hf$trkBgZtC;N)__k<27pS=o{a zmg^T}Z-87|GudL|h*hlA<-n%XlN$&}9QiehJqI{(co~rH+|@6PqVS1NbTE^UAzQ!T ze=#6_HxE*br8s`|^mN7yxcN6j&BBwu$(w2Xnoe}!^r-<)RJnTIVsA+s;)e4>SXPW-E)*KZ(1*i7f&D`H(VHg>r^%=n)W`A zzu_&wAI)xDzq#pdkY_lvU420I4guN(zBOeEQ>S@#<36+Yc3v1|_P3>n$uP#Z59AB< z75X0z3tS9*o|+A@%?Y;j|7VzIX)suvoGP2cG)LSJ9rMmAk^B4Gq7Tc=LlOG2adtq^ zHY9ED3YBDlIu;|PTLm+&fj@sv>N^*>BagY9Ak87bv=PtdGnwQej5KQ-RRSKVMt`CG zJjbn1yZySVg3%Xfns@Drhx?weq?IoUxIVF1=a;WWGCCTTTU8vPo^$IWNXlBz8&%F1 zazdfXVJ(<}!4i&SzLRGOiKgM6gUVYIh!#7_7y9}y#oc_Tl9Gi~!%3)Az5#Q5OSEiX z7!G}Vjud^eLr33-Bh;v}LwCZk3ydK4WvEBF5r_6sMEGvqX&!Tgzp~|? z`3U3}-Tua%Qo*ba-CL~U{XNBvio{raNZ3?ef=EcpN!?q(6`S+NN99mD9tUOn*tr>Dh&@Nrqy2~B9*AUVvRRS+pvw8Iqv2CKHx?kWrVg#_hTL; z^k~97R4ceWZCqPJ*=7Ep<-;n<3zLV^1}q{873dSZQD9NfoCR#sO0= z`|y<03b5SQTh=h35GUN?H_BKLE4Yq&B)t;wogv_90Pomr-(z1f!(oaC0o?ergKJ_T zmKsey0eKiNPOCM8a3|+9rWriBe&lqv>wgG~jnr1-^dd388DJ8qWH@9<{dnYih+F$` zn-=B;n}TVIC}$w+t_k_!W(rh@$i3vdmSzKOnL{3U@~=+Aa4X5w$_&v0O$%Lw8{brldow}**3W6T4N-)@ z5PRF-XC@vLi&X=uGM>N@e7Zd6p1N5?DC`>*A0AQ0KMT0pg%LIhv7-64$~6Gl%n+e= zI(*k;?Yf{xn#hOEkG?e{!3-oJDP&sxc$9bl>ckZGoaI!-g1&qWi92q2U08>N^sWa- z`IpQ&mTwVm8$RtFi=#*g&h3q6`FL_*F8{l4poH+^A1iWi(W7+x70e|YwvCQ9ewQH< zI}F4v7q0RLej};oZi>|5)~I>Adl6{rwJFFcyw}mtinRojuLukinFETGD_tWwluhmQ(W%#W2|?~)GQAW zvtMP%JziP_Ld8_>HYCbMN5!OOyw0qBJL2uQUlVmV5qd=JO*ixxG!+;)yBy+u)8Tpi zK`wZS!X=baU#`p;;OWwGn%N(y+`bODiItVvc;p)NHhGUYO{;)7h^QI6$cdlC5XdV+ zYF!Rr%I_pCDM8tjAjAM{G+Is^^u=aweUsH3A9O|ZX`bPnG82sN{tIbuI&9%gAAQQH zny+mrV%^CiT_q3((J>y1-py1_aeQ|!}JQt*6Sk4P@i&4>9*;RVZ@v?G4<4}z{_JqN$VQD^QlWrk4L1G zH=m-YW!c5o4i>+y=?8VAt?rSzfFrr9p;I0g%JCX!HgIddy@NyUl}?|46S2je-=acN zDr5v+*y}@lU(a!*>2Z}%!=>VB;j&Z0wKpuJno@W``%A6p*w$FzSW|Ir91Az&6j^!qs~BgOA708|0Xfu3VVK>fM6^4lf!h0iI;uUD|XB4ndiJZ+9(Jq%x5B~ zXvJc?<*8Gj^^=~?T+7^c?e*!3 zW1ED{PqS9d#=O9G1F0&(&5mr%;ROT z)Gfuk(<8gi?rXTw=C7^TScJzQ#NlU13a_v=hm7o2WCs1+~3 z2|h8;Ke>NOnpc1JIUOT#_a-)q#;uRWqtCIcCmOaV!#+7RNt)i*FPWv)y7Bj)KG>jxlY~#K| z9~waZ>*#Zk*IS)&%o#`A>E_vdPUdOE+6ty2tcQh&$7$PXqJ!hgizO@CPE~`LngaCJ zKa5n-Q=U6xwkw}U){u(Sq(6@bU>E?Y?ISWV;Sw!&Ao+GbAQ1FsG)yhL8kic|%o5$& ze+W^G{sZ{n?&6laRuO9;fhgFqE^j}cwjy7j!k1$4UyWn3(kG@1z-WBAd&q!C{2doBQ07l0$thNAs zYN|-Spvm6^Wb@T5KfZrNb0O+>U5H*$b@9IA&?-Qtkv8h@)Qn;E+|L}jc?f5 zhhD^W0S>aa03tPYFO3@(kFs}kkSM@KpN!vKIfNp`p#{-LK9izHin~S|UqFJXiV4yC zCABNr2ABk8!^w;Uf~@JpxexqD-FW~$%~j}S((l-e{e4n4qF+IR*%~w``*;q^3XZ04 zaw7h}y=4_1Sn#9S*;(q;^d>v^2r#$D*lWT`8ran{H$Tpy;MV20(u%P#M4<%TL{*KraD(8i`Y||IsEKo>dh$#6zmbsZSoIgj+WgYcO-Hqmze{A<7KnhcuyxmvbMgH3A>EZ<;dW9`fcs1H4!eH;<({i@MthK^h@5P`b(V zaIbt$X)%;s6X>BWe?;jNbsNao95O^;Mm9P92$-h%y@W8kj;}}ztlbjVRKK2xS>tt! zzn8|*tl5SPygQ@Slawg)1ScY|<3igq_Vm&}VZ5ne`aRT2KD8N4_|ayKpob>@5r8(G zSs6-gWTnX+p?Lvk=)WNk2J$$err$nd!5RS zB|EmJj`_4Bpp5VG>RN;Iu~JPB@)8tzcv)gpDE4<~w^7RVgipQWaCsnJ zW8|^e5RgeMCgaJjK5)znL_BBI2a-4|)_^M&?O_q1PtW`N4eUYrDU$rs&MnNz^0{Vi zmmzcsKI^I=M16e4E1V4p<=8FhaLPvPxRUwY1B!^?@oaR zu3|jA#{0;tVLD_vM7Niqdj!f*Cqx&+QPhR4o-Gxf7sif$-g}8_IQjE>tpzz8{R1oA)nKh^KRpzW!kLy z$a^$?a}*`a!dI-D=h7YfBc#vSU|uQ&BJJC z82Pwp9fmtjrplWx;M)TTAo#R-tYQA!`b1ddMVn>qL(tJEEs=m3bYGR4xKcH)^H=>W z+$Co0yBZcCo(h29(Qq52o;G@{F;F1e7NGI-OR1PGq1s8?qZs4XH6vzX=A(+@GEvvA z5(zUW3r@x`HOg>T63MkzbD+(`940f!Fb}N9g7{a*`a}9X7k0TX2ByBm-!NUgAMOlTKVyHh(1mDStE0|C;wQP$f}< z!O;EV!yI+thCT)NZB$p#qEvV+)gs(|dh6#Sr;a*6Vyt?AD8P#*6b{H16iR>IfSo9l zY1rHGa_?8>|0?g>h*&>4yx9)nN!}h9Mw<%Zv&}H@Jk@OmVg>ca8wCDvB{6T-aN zJ4Q~OW!)u9T=2t9F!y9-z1Tz7-fHw>m2m5q<)Y#;Y;00;$*qMKActMwDg_-FPU2P% z!94@Wk_3@a|4I5Kj>b2vL|srMzW0y?D2Ll4AX+i?p9-<29I<;86Y+&rsPocqOFZ{_ z`hH4^T8FaflVSCO`^ zVn4_E*5MuB>)~m`mOs283Ogb&woOT!*|eAVymb$?xMg#Tr~- zNahP1ZZ9yo=8d)NWsR1=D>(S7G%2)YrPd9muJwEC8$fdMI;=3W%8O8x0DEGKyv&)p z4b3(NZ8_GD8CDLTLeIRBFQws_9WMWaggT@nfLKAy?ZTKV?L6m&#izRp$H|bJi4O?M z5z`e`G$X+BauW`|Hm9<}j`;W1q&N#mqI=T(TKQ*kLx0F#obbE+UJSkkr>!o_&LE<; zJ!K6sSF5+qd^CoV0`h@cKy1I<>loPLb->v)KH{7~fNV@$+BTBS ztgz@mGA_>{MN4~^_OqP#ZUE09uOL)o{V*l1Zf&@Sag(5i>Y&<%_X3Wp{9rk*PD7}k zEEaNIGHndcE(Bz8E6recOB)o^x_N^95Bxturry8fefKPl<@oxJX41IFrH_P(e@&pS zbK}xgSlW80+9&3EN_i{-dga{{BLLAW)039^vOSdJ#HMVX6h8`sW|1RNELvHLP-i-F zf*XM8D=1zULeSf1Y~`Dl)=VT(0Yu7IGytTA|zF$ivWyQ4A z({oPdcd=OgtK0x?L=vov^gf*e1duwSg^LK-&TXF`pLd+gV3Le?AA59L(pB1Vc#FqJ zePuvf`x1G3wFy#MY6R&wdMd5?PGm?)N5FmaWJ&oy39R0=B*;l0MraznV+ZTiOm#@Q z{84$aH=BDnSngR98GE^@<7Kn!1&7BIVlK9>(WMPL zN}G?WXcHUtJMJd$Fp2Vns~qS-@bLg$R$bu@sk>n(N~Q2PZA|L0^o*C*GeuqtU_&ge zm33~R`))hvjO3K}V&SH|Um^|ji}P}EQquWG<=T<0cYzUdAH8`6ACtV(B+qj-B9XFS zmBO3V8YxWhcg?$srTuzMq91NiwdIvDY6poPrbp!^5)9MN_^kQYQk)(ctJYlkFJSXQ zyGe&0k!xnjW~wyq2-3o`FDB98*~(>6T4|pTvrMB3@9R$nH%EhT7raS{W9!A%5m~0+!IRT14iGwQQg&6z zCdA7=6JWfz^;PN*nY^zCFBjmQwceMe%L~Ti=WB{|*pXk>E#u)3J_v^ltZ~iTw~;{< z*`l-gDvSPfq`N@(s;Kr~2G#o3qt)>Uw{0=c_iT7B5A#TwH*S51XEI*L-$$bR1p}gq zqZMGdts9KDI49VMwQqD-@8n79%grOp@2;@2K6S5~CS4F~sqyoR$Y8B>_w&=5;E9@KeUcZ^kkq{DxZ~Ujo-O<9W-x-9&KBdj z^;yZyQvj07Inj^j4(Q{Q_e@)~CT$heql!GxYK}LhP~iz)`se_|`g-2adUf1U7k4Hd zpUfP6dbnY9nyKGoL3%=XF3z3xT~ndPbz>yT<2SkZ8&?9G z*ChbHK=Z3_2KzE+OaWJZD#IKM^>OEuC_c}Ko{jf9FLqx-uOXk00lup)CtOfZT7W&8 zjh2SM4D*eu9H5kEpTq6E^xpdG?!V8XmH(E;8o!Jcy+I@hFI%pUDNqvD(UzpD*q-n| zZjVW%L1jqCdJCeDHYl;=a;H3XO~O%1CRc&?b$>*>$pHKIxusSmAg8y zuB1;8Oc8*9N$cCMc7A<&6Nm_M}}OQ*WjL zb|*3F1GUOmjP3#NaXm06&wPbc#Zh@uHQV6$AbKhryc7WS@1kje`cs1Hz~UpR47Rq7 zXO*)GT7AAHo>?;BP8Dj~TsqO$-v;{skZzOC%7Lm0w~gM&g&a;jmAX(cY71V;a~4tH zlBE#Vm5^^ZIiv2p`hGa3gL>4aa2bTM7bY7-`~l^FJu2K7afDF%q-IluB|pd=14;0K z52MHW6Tr1x6wB_xXeM_Lo2m$A^HB2u&AdZa23qi2KhPflyluK<1~KI>gmK6XPCga7 z%J{_jL-fmI)s}d%uZhR(?uUV^eRy96$`2s{LQ}|`YgUVJs9LbTa$iaepE1CQWiXBiD{EDsPeel{<3U&l#WF%{euqd4I zX|pcmK}oC!ZvE`lK@GEcG0RN~w!G;&f3}fQR*DA2?$SdET_5unWId`kO09qw?{kU+ zZ|kj;gbHDTq0Z6klii?FYxs+onMrp8v{3iW-hFw#qe6;l1aEHgdmWS_2qoIC?%^mhntH z(9Y_O{REmWm)}IQ(piE*ixe%%F5E7gT%yDDm!MHH)Llqs{}Bz8bU4ZIkJl9Un8qMK@oCJQv#C-%m6(6p=4V zpc#<)&ZAxDKCHL?@~+9&Rqa4zh{T|_U5(y$Gcbhe>YLWlXS#zBvjKn{xouL3(RavGMp2*sr zN}uGW5rep@+h3pL*F|ENE$0%eX zaI2s36T1)AzYejtAILeO zwVo0l`rH4aN7(NASdZu*M}X$A(Yprxnt>rWMI@Ss8-{dWt2xKsG6wIgMr1oss$yZs zE{tExS}^X#W#-G&w(tTNNo~`~iTeI+jg}BW|JT1_Vh4YS@7dhSvYoOW=6{+ILGM|E zZ-rs_zdVS|7cAQEWp7@eBW{7GKwm{0t_gPiO;AznP5=E3LnEq;5g;_#Nq&XH6&sr~ zqwlc7J+JtXGR^?HekT`584lp=ps?r)n<&gM*;!iGjf2Pa3$_p%sjQC+CVI{(ZYCL+ zd_H9N*{)j=s9M@u1;R}}Wr2t+Daw}Xi^D2N z3pQNc=_#ys0**FspaXD5V@Of*g_@d*a=(R}N`S(1b@Of8W8^vorzBo*+~#fxFor(} z2sl_fgx%S!At2)Dmq?M*xmtS$`|SZZtX0$Q)ZXG(WR#X#fS}YKn=pn8!Pgb}Z)Tay zo@f1gO7JQifQ03fKJv2Rj#teKGs9lDy)J?Ywp9B zKMnK`z}GDtJ$Epd2HXp%O)G`>WO~z{*)TNhRL@p8dJaF~E!fifwr`)Yw;l4ALzysg zpro$w?CV*3UF#JYX6? z$hH*(rF|X0gF7BH2Yvy&EApNL%;1+fEu`hXH|uYv%?XDj_)aiR5>5Co&aXI2OF1KC znD|yj{jL@~yosrCXYKt7h26bXj3lf}PmDgBP$*2f9WrfmjMDS+KNNP%3grsQ7;FslcIiCVAc(;W-R;0DBxAJCu2X7>@bJE&#!0O`DH+n0l9FoLNhn z&0ETS!iWWKVHkiIkabZM1L`{gSEuz#P`aO}t$kzF`|mSx`YSZ$@@qC+r}q&~ zYTldp>&vtB5!B#+vj7Nq`(59X>uftX6bJtk7mqLfS9Zi99j)muvvEPxNO39#uX<(m z%CAHdF+9-Ay_!!Z9du2w-IindV2T3oY29!E1fdj1=`z^I+Q61fYCaD%v*d?c3vCR)*>1}YERAD-BTa(;QckK{v zmuSHi#H6v|FKyui#)yqn-lL5y8t0#;-SQ%kSGFPtD`I=?-`)*jc$aO&yW%t@{p!U=Wg*@dr9%PU800^n*!=#2W*5B^G|Ug( zdn*xxETMK@vc0&nbg972c>GRhH-o)QXo!V;PXiW|J-e*P4V?oJ1PQ*2#mS8?3 zvp;KqwS+sR|AAQIx7O!mToGEfYW@(EMIH!Qm*s5eNScB~N1@s&95;E}(FOK9w9gvJbE z>GCQVf0q|enq~h`6F{5B0M?FpE;Q!~+NVz5J|t)oEZV0GFRI}K5Yn0!7lqcohd0}8 z!_Hs<;sDi`FSW>~!l~4iGacjNaQ+*Y-x39kDp12orHJLUDPGbxb)mPDv5ciQo8ziV zyhU;eJCEfF=rC$2%4#MPW9RET(2>`ej+g*Txy1!Sgo}x`^b${UCpwFe@38~~rw;Tcxu&H=He)c?J7f_PQnilk@hefW+sHfv9V~O_feC(JI?Vs~;q_cPQ>T+Bp-a*tUWZ#60TYWu@xO7gVg8D%-UiN9?lCP#9V z80GG@x6^PCnOfA~yj0FJ_gC~Mk*!;o0?Cm#vC@l&PuRAM4nR)&r-GNOOth%o8~GaScMppkBH zB(qkLw{*8Yy=WGY0=*EOwgLk;sjUgplQM6EtHz~r#y6pQNMo6 z-}=Ia$`&GqHanMlUCkYXa+I`TORAmTQsgi4(ROyrk7sWTd#c}C*U;GZL9 zT31`EUrz)^gnvI50q`a=1AmTR5{ACM<#e9T@|!N`K|Kjh=75sbKb+rFTHq2D0;)5% zD!?WS3Ce!`Np)gEU%a2}wFkarPSG?8dA1J+W>& zvWRYQ;;|k5L&H*|OCtI-U##mQ?g*}U<$=n;_Mp2rSJ zmhIltHMKn|T%6!1mzmLxT!W_8k2unOl@Q^WrlhBskiK*i=Vo0N8MY8}=E4Q=y|eM1 zCGArt;-T)3anrH3fK8>&T*hH9Ll{bGVyobzXLuw?RGgluYdym~f9A|0FQ)0r8gMXqaMc22;hg#Ks8brPP>G2hl{qCkI9rNscO^νea2@>4}lB zo~M85mkhPkD9abvcY+Nt<|HAYBU+2oP zp2dE+A=dh_6wiMREkg_z6@QEsbbbmIN8d|v?q}RdIogmf?F#CpP!XsNWp6C|moxqE zeOIX?CD)`&1h=2#I5G1D;Ja2j+bCgpL^+8Me>%Vcz_g`HFULkEU(j4d()86= zkJI|@kN&1^1E+d3K8YyQx=1r|cX9voU6K1M;pc(G<=LerA)@zgFg=6yeqd{}`$TuL zUSDCFvU4?gHmZV*)3*a)aw}V{KfYLm`?Osq{w`5Ynkx&v5TK)P$mq_p>30#A63!D! z$;_wGPIo`)Z8@7@WLn9*)}UZtn3%A)pg0IC_tcG>A$Z~rb5TUlr!4LIE^Qq`RAg|# zn=-h@VDLJ%tEKxu-~oZV;|J522;r@Krt?E{N^WsErvqH}%?oh-=;)(aD13Cx#VtK*Q8DI2>iwp_&dl?9rqd+FKDRLR7NyeUlv7R@h>Fc*aW6Al*abpU8@m=8L9|>)_YJFP%nr{xx^2K`W zT46%!%0-M(p^42JyxY17A(LS=v+IHQxq>aLlwiwVEj7|ues9rNg*mm!smr{KXf`#U zmq3E(u1(vw*q)mFX%)Xy=_eVGm`xp_E8;!(D{CeaB0^tzk7X5=BKBH;JU$JI4IFDR z8`CjV03N1ig!|0@C7)wT)2=e+HP0i8SKZ~nH^uvn5Cv2b9^{vWI!3W8d&dEq+>_!_ zdNe+o({L*q5ybKMTjE*Tz+wCZ^lCoRFYLl*%ci82ey%G7FU{4!MfVcJ;a$xP4bP zp0Tf`*8cp^G#=$P@TAbDuolgH&ljd85~Pik!Z?d?FQ3FfPbk-*GH!1`E#Pa$`DMs9 zzU5UhcKkM3D@A^!z`r6c-P3w4Fwsn1^`UK96SduN%LL*eFXi;BDA{=xotNZYxQJH6 z^~0TfPuw9~tYIphR6NYT?ZlFvuup>g;i1INWrz~2PeV0LJX3f$!u!K!#;EXw$3*3$ zh=K@4MhUVB!rNq~RcHQkhi?qIkm& zgpBW;?KqUN(zuV`^{Z5AmwWJFCkX8CHDAAfLbG3|soL`3f>E)UPjEmKns6vN+Vf;r z4EZ7M*fo3xt)0B7Utf84AL1CZSvpGX*ldFzQtdfNGO_ZPSSZ71igS#}i$Lcj{*9YZzo&WCBU77@p$>7HZ z2PwaZkNUd7CSmCxBiB}!RQ}yl(@F?hu+_j&EZswPFYfeX+nsM;rf)!{sj61ai0aIw)B)Xlf_LgJ7$6ga1y=WnX`HyX1EXP{ zj~mUnMS(DTdc-rmwlCD-Gir%^jh#6$1z@hzWLrvbHZ}dIlF}o_r-|f70o&L2X_?zP z@&ZU0?`w?(+SB(>OyZJy;oK4lJGJ$3KhRdJN$QOD^_9mw@Qu^-vh4fAz)4jD9{kEx z_-MDm-!^TDIOHeXm|(!hrEsAnuon6%0{wr%bPv@wrP8Q3QI#YGRWxW#tD`; zUMe17iZi%k|EDv%G=fW8BO-fz+g^BR@}YmBEX`!=c&12|4ZM128zEqw7XBcZLoo65 zVzfYKLM8qED`Vd3KUA)^df;4wu4@cwKIaxyy5i&q@yzAm&4e7>kg@fc$H1@V{rHuE z_dtkhnTH#vt^Fb?HaSMK@|Y7siAVjyj^Vl1@?T}VBl*3=y|mkKUz$ne!#5yt_13@)@6P)w z+o#tfU`gL53T7sG#Pr>XvZLS|;drO|Q0@bkCN=2pTcn7#Fd7VUf2&fV=g4D`%WMA; zr>tyt$#KN*$26;QL03{&23Ln8$b#iMHw(S&ErTy81cFC_?;t7%O2E7Z>iNOn_yEI! z∓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

4D)=cIi)lyg5}uS&>LhQRo&~-*FK#%THAYDe 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 z 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

7@rn@LX{2IvVgS!ej+NxKG_9Sm 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�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 gTEV|t0ZtQj-N = ({ const [targetValue, setTargetValue] = useState(""); const [includeSubpath, setIncludeSubpath] = useState(true); const [authEnabled, setAuthEnabled] = useState(false); + const [guardrails, setGuardrails] = useState>({}); const handleCancel = () => { form.resetFields(); setPathValue(""); setTargetValue(""); setIncludeSubpath(true); + setGuardrails({}); setIsModalVisible(false); }; @@ -77,6 +80,12 @@ const AddPassThroughEndpoint: React.FC = ({ if (!premiumUser && 'auth' in formValues) { delete formValues.auth; } + + // Add guardrails to formValues (only if not empty) + if (guardrails && Object.keys(guardrails).length > 0) { + formValues.guardrails = guardrails; + } + console.log(`formValues: ${JSON.stringify(formValues)}`); const response = await createPassThroughEndpoint(accessToken, formValues); @@ -92,6 +101,7 @@ const AddPassThroughEndpoint: React.FC = ({ setPathValue(""); setTargetValue(""); setIncludeSubpath(true); + setGuardrails({}); setIsModalVisible(false); } catch (error) { NotificationsManager.fromBackend("Error creating pass-through endpoint: " + error); @@ -249,6 +259,14 @@ const AddPassThroughEndpoint: React.FC = ({ form.setFieldsValue({ auth: checked }); }} /> + + {/* Guardrails Section */} + + {/* Billing Section */} Billing diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx new file mode 100644 index 00000000000..fc6f783df05 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx @@ -0,0 +1,246 @@ +import React, { useState, useEffect } from "react"; +import { Card, Title, Subtitle } from "@tremor/react"; +import { Form, Input, Select, Tooltip, Alert } from "antd"; +import { InfoCircleOutlined, PlusOutlined, DeleteOutlined } from "@ant-design/icons"; +import GuardrailSelector from "../guardrails/GuardrailSelector"; + +interface PassThroughGuardrailsSectionProps { + accessToken: string; + value?: Record; + onChange?: (guardrails: Record) => void; + disabled?: boolean; +} + +const PassThroughGuardrailsSection: React.FC = ({ + accessToken, + value = {}, + onChange, + disabled = false, +}) => { + const [selectedGuardrails, setSelectedGuardrails] = useState(Object.keys(value)); + const [guardrailSettings, setGuardrailSettings] = useState< + Record + >(value); + + // Sync external value changes + useEffect(() => { + setGuardrailSettings(value); + setSelectedGuardrails(Object.keys(value)); + }, [value]); + + const handleGuardrailChange = (guardrails: string[]) => { + setSelectedGuardrails(guardrails); + + // Create new settings object with selected guardrails + const newSettings: Record = {}; + guardrails.forEach((name) => { + // Preserve existing settings or set to null (uses entire payload) + newSettings[name] = guardrailSettings[name] || null; + }); + + setGuardrailSettings(newSettings); + if (onChange) { + onChange(newSettings); + } + }; + + const handleFieldChange = ( + guardrailName: string, + fieldType: "request_fields" | "response_fields", + fields: string[] + ) => { + const currentSettings = guardrailSettings[guardrailName] || {}; + const newSettings = { + ...guardrailSettings, + [guardrailName]: { + ...currentSettings, + [fieldType]: fields.length > 0 ? fields : undefined, + }, + }; + + // If no fields are set, set to null (entire payload) + if (!newSettings[guardrailName]?.request_fields && !newSettings[guardrailName]?.response_fields) { + newSettings[guardrailName] = null; + } + + setGuardrailSettings(newSettings); + if (onChange) { + onChange(newSettings); + } + }; + + return ( + + Guardrails + + Configure guardrails to enforce policies on requests and responses. Guardrails are opt-in for passthrough + endpoints. + + + + Field-Level Targeting{" "} + + (Learn More) + + + } + description={ +
+
+ Optionally specify which fields to check. If left empty, the entire request/response is sent to the guardrail. +
+
+
Common Examples:
+
• query - Single field
+
• documents[*].text - All text in documents array
+
• messages[*].content - All message contents
+
+
+ } + type="info" + showIcon + className="mb-4" + /> + + + Select Guardrails + + + + + } + > + + + + {selectedGuardrails.length > 0 && ( +
+
+
Field Targeting (Optional)
+
+ 💡 Tip: Leave empty to check entire payload +
+
+ {selectedGuardrails.map((guardrailName) => ( + +
{guardrailName}
+
+
+
+
+ }> + + + +
+ + +
+
+ handleFieldChange(guardrailName, "response_fields", fields)} + disabled={disabled} + tokenSeparators={[","]} + /> +
+
+
+ ))} + + )} +
+ ); +}; + +export default PassThroughGuardrailsSection; + diff --git a/ui/litellm-dashboard/src/components/pass_through_info.tsx b/ui/litellm-dashboard/src/components/pass_through_info.tsx index c42a710caf2..fe3b204b507 100644 --- a/ui/litellm-dashboard/src/components/pass_through_info.tsx +++ b/ui/litellm-dashboard/src/components/pass_through_info.tsx @@ -19,6 +19,7 @@ import { Eye, EyeOff } from "lucide-react"; import RoutePreview from "./route_preview"; import NotificationsManager from "./molecules/notifications_manager"; import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection"; +import PassThroughGuardrailsSection from "./common_components/PassThroughGuardrailsSection"; export interface PassThroughInfoProps { endpointData: PassThroughEndpoint; @@ -37,6 +38,7 @@ interface PassThroughEndpoint { include_subpath?: boolean; cost_per_request?: number; auth?: boolean; + guardrails?: Record; } // Password field component for headers @@ -68,6 +70,9 @@ const PassThroughInfoView: React.FC = ({ const [loading, setLoading] = useState(false); const [isEditing, setIsEditing] = useState(false); const [authEnabled, setAuthEnabled] = useState(initialEndpointData?.auth || false); + const [guardrails, setGuardrails] = useState>( + initialEndpointData?.guardrails || {} + ); const [form] = Form.useForm(); const handleEndpointUpdate = async (values: any) => { @@ -92,6 +97,7 @@ const PassThroughInfoView: React.FC = ({ include_subpath: values.include_subpath, cost_per_request: values.cost_per_request, auth: premiumUser ? values.auth : undefined, + guardrails: guardrails && Object.keys(guardrails).length > 0 ? guardrails : undefined, }; await updatePassThroughEndpoint(accessToken, endpointData.id, updateData); @@ -214,6 +220,33 @@ const PassThroughInfoView: React.FC = ({ )} + + {endpointData.guardrails && Object.keys(endpointData.guardrails).length > 0 && ( + +
+ Guardrails + {Object.keys(endpointData.guardrails).length} guardrails configured +
+
+ {Object.entries(endpointData.guardrails).map(([name, settings]) => ( +
+
{name}
+ {settings && (settings.request_fields || settings.response_fields) && ( +
+ {settings.request_fields && ( +
Request fields: {settings.request_fields.join(", ")}
+ )} + {settings.response_fields && ( +
Response fields: {settings.response_fields.join(", ")}
+ )} +
+ )} + {!settings &&
Uses entire payload
} +
+ ))} +
+
+ )} {/* Settings Panel (only for admins) */} @@ -279,6 +312,14 @@ const PassThroughInfoView: React.FC = ({ }} /> +
+ +
+
Save Changes diff --git a/ui/litellm-dashboard/src/components/pass_through_settings.tsx b/ui/litellm-dashboard/src/components/pass_through_settings.tsx index 6e22533ed14..b37d3df7e41 100644 --- a/ui/litellm-dashboard/src/components/pass_through_settings.tsx +++ b/ui/litellm-dashboard/src/components/pass_through_settings.tsx @@ -39,6 +39,7 @@ export interface passThroughItem { include_subpath?: boolean; cost_per_request?: number; auth?: boolean; + guardrails?: Record; } // Password field component for headers From 19f03d4be3f8bcdd16c513330c790b93dab492f7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 27 Nov 2025 12:32:51 -0800 Subject: [PATCH 183/349] ui fix linting errors --- .../common_components/PassThroughGuardrailsSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx index fc6f783df05..3156fa626f1 100644 --- a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { Card, Title, Subtitle } from "@tremor/react"; -import { Form, Input, Select, Tooltip, Alert } from "antd"; -import { InfoCircleOutlined, PlusOutlined, DeleteOutlined } from "@ant-design/icons"; +import { Form, Select, Tooltip, Alert } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; import GuardrailSelector from "../guardrails/GuardrailSelector"; interface PassThroughGuardrailsSectionProps { From b8542188195bdb3861e71b991461e8cd8203e46e Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 27 Nov 2025 12:33:02 -0800 Subject: [PATCH 184/349] mypy: fix mypy linting errors --- litellm/proxy/pass_through_endpoints/pass_through_endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index df4452f726e..780a3d6dcc6 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -1171,7 +1171,7 @@ def create_pass_through_route( custom_body=final_custom_body, cost_per_request=cast(Optional[float], param_cost_per_request), custom_llm_provider=custom_llm_provider, - guardrails_config=param_guardrails, + guardrails_config=cast(Optional[dict], param_guardrails), ) return endpoint_func From d4be5111308a63fadbe00f6afdff3d59fc323ccf Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 27 Nov 2025 12:33:56 -0800 Subject: [PATCH 185/349] UI new build --- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../out/_next/static/chunks/1263-cf8443d1d71fa593.js | 1 - .../out/_next/static/chunks/1394-bdcf4b8db9c252d2.js | 1 - ...{1442-529c645297e48128.js => 1442-024f7e51804e0d7e.js} | 2 +- .../out/_next/static/chunks/1518-21c80a799b5c426e.js | 1 + .../out/_next/static/chunks/1518-9a77ac5675e15594.js | 1 - .../out/_next/static/chunks/1623-995fddc2b5647961.js | 1 + .../out/_next/static/chunks/1674-475a971a192714f2.js | 1 + .../out/_next/static/chunks/1994-6637a121c9ee1602.js | 1 + .../out/_next/static/chunks/1994-a4d0b99849c16b62.js | 1 - .../out/_next/static/chunks/2012-7e2773c79199687c.js | 1 - .../out/_next/static/chunks/2012-9200c205d5b0405a.js | 1 + .../out/_next/static/chunks/2118-9efce161d33a9757.js | 1 - .../out/_next/static/chunks/2249-01a36f26b1cecba3.js | 1 - .../out/_next/static/chunks/2249-3e3c0a9e241e35dc.js | 1 + ...{2377-8fdad210b7695043.js => 2377-674bd40044d10e16.js} | 2 +- ...{2926-a9eb2d7547cdad95.js => 2926-a9cb83e61fc8ad20.js} | 0 .../out/_next/static/chunks/3218-4aea06837fa340f4.js | 1 - .../out/_next/static/chunks/3221-0a12dcffbc76862d.js | 4 ---- .../out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js | 1 + .../out/_next/static/chunks/3325-4a3c766c7d12465e.js | 1 + .../out/_next/static/chunks/353-33a4d12e099f843a.js | 1 - .../out/_next/static/chunks/353-e55516ea4730f9d4.js | 1 + .../out/_next/static/chunks/3621-5ff5b3101d57f20d.js | 1 - .../out/_next/static/chunks/4182-1ec11708566c0483.js | 1 + .../out/_next/static/chunks/4289-68573041eef5b2d2.js | 1 - ...{4388-2f4ca3419d20af67.js => 4388-eb8fa49a76501802.js} | 2 +- .../out/_next/static/chunks/4925-a8ad75d81592e879.js | 1 - .../out/_next/static/chunks/5074-51f1824c21869900.js | 1 + .../out/_next/static/chunks/5096-5318231023f36448.js | 1 - ...{7732-a406d32f3b9f495f.js => 5170-56859ffa75db75f8.js} | 2 +- .../out/_next/static/chunks/5333-438ba079aae9630c.js | 1 + .../out/_next/static/chunks/54-56a8e045d64789e2.js | 1 + .../out/_next/static/chunks/543-7ae25eb17f21b433.js | 1 - .../out/_next/static/chunks/5572-9290ae3dc2551207.js | 1 + .../out/_next/static/chunks/5572-d4f8dc9b2bf09618.js | 1 - .../out/_next/static/chunks/5690-3bf2d6edf2ad3488.js | 1 + .../out/_next/static/chunks/6062-89f63f71675c6a08.js | 1 + .../out/_next/static/chunks/630-1e0342aa26bb0fe8.js | 1 + .../out/_next/static/chunks/630-569f4366f2ad9eac.js | 1 - .../out/_next/static/chunks/6609-3e081758ffbe3786.js | 1 + .../out/_next/static/chunks/6609-d93906f43161f066.js | 1 - ...{6843-d9a2a4bf3fc7a867.js => 6843-b8ebdf2bb4fe5c67.js} | 2 +- .../out/_next/static/chunks/7140-937050711ba264d3.js | 1 - .../out/_next/static/chunks/7155-1a3e4c5a6aefae2b.js | 1 + .../out/_next/static/chunks/7155-459bc53437553b96.js | 1 - .../out/_next/static/chunks/7482-150cff9b9c47054a.js | 1 - .../out/_next/static/chunks/7641-060a5116952b5019.js | 1 - .../out/_next/static/chunks/7641-f70830b7a61a3f9c.js | 1 + .../out/_next/static/chunks/7975-afe816ddcb35e063.js | 1 + ...{5636-ca3adecf2b222193.js => 8008-851877152eb2be38.js} | 2 +- ...{8235-c9c2c9abd48d9b13.js => 8237-253c15ae006496fe.js} | 2 +- .../out/_next/static/chunks/8468-27ea05e25918ba32.js | 1 - .../out/_next/static/chunks/849-d1cabf66d71a8808.js | 1 - .../out/_next/static/chunks/8533-b7d5b2f50457d35a.js | 1 + .../out/_next/static/chunks/8661-1cf4178f6bffc981.js | 1 + ...{8948-a969f81088a52220.js => 8948-da16df3286be8c9b.js} | 2 +- .../out/_next/static/chunks/905-f629babda9c46af3.js | 1 - .../out/_next/static/chunks/9111-3cb8240098962e8a.js | 1 + .../out/_next/static/chunks/9111-9b9192c9fb4809ff.js | 1 - .../out/_next/static/chunks/9165-82d12d1c73da639d.js | 1 + ...{9301-33470ca245ba2578.js => 9301-905e8491f42289c0.js} | 2 +- ...{9411-e2a18c2e46730a13.js => 9411-3630d7fd1940320c.js} | 2 +- ...{9611-8bd2ffcee22edc34.js => 9611-e0c4dfb8fa3d2ed7.js} | 2 +- .../out/_next/static/chunks/9678-076488a4dc7af149.js | 1 - .../out/_next/static/chunks/9877-363a095158e1e758.js | 1 - .../out/_next/static/chunks/9877-f58702e3cb433729.js | 1 + ...{page-873ebb2fa62f50ef.js => page-6ead8448e1510439.js} | 2 +- ...{page-a4d3d795479444f3.js => page-8047d2cef33b9999.js} | 2 +- ...{page-ce67247c5ba30385.js => page-3234cab0cb418464.js} | 2 +- ...{page-789ec63e0c88d7e0.js => page-0a286b7e7489b565.js} | 2 +- ...{page-8dbfb3870a758842.js => page-bdfb6697f4d6f550.js} | 2 +- .../experimental/prompts/page-02e4f62d0c9ed877.js | 1 - .../experimental/prompts/page-843a18f5283af912.js | 1 + ...{page-973343a42b32fea9.js => page-e5395395c754c862.js} | 2 +- ...{page-668d462ee75b7eeb.js => page-fbd5350eb16ca3ba.js} | 2 +- ...out-e46c8b3b8f848a3d.js => layout-a928c135835301f0.js} | 2 +- .../chunks/app/(dashboard)/logs/page-791e856238f891ed.js | 1 - .../chunks/app/(dashboard)/logs/page-974be1d69803befc.js | 1 + .../app/(dashboard)/model-hub/page-a4e1b2d51a5f5567.js | 1 + .../app/(dashboard)/model-hub/page-eea8d5d5c2e4e69c.js | 1 - .../models-and-endpoints/page-82db406b0194e839.js | 1 - .../models-and-endpoints/page-a11b969ee66b82c0.js | 1 + .../(dashboard)/organizations/page-8bb205a76fefc630.js | 1 - .../(dashboard)/organizations/page-c9a9977ecb7e4aea.js | 1 + ...{page-33d87d224be1fc3b.js => page-6729e021acdc06f3.js} | 2 +- ...{page-312f76bcd644a776.js => page-41bcefda7b19fcbe.js} | 2 +- .../settings/logging-and-alerts/page-2470c91c28de94ff.js | 1 + .../settings/logging-and-alerts/page-ce1c344c56b0301f.js | 1 - ...{page-490d292ead7627ee.js => page-7e77ec8e3ff58278.js} | 2 +- ...{page-863067555596fb73.js => page-ee5d0a8b43105b4e.js} | 2 +- ...{page-85f4f91698b75711.js => page-866cd0b0d541c84f.js} | 2 +- ...{page-c6612da0a3d53b17.js => page-69022c9c481d0375.js} | 2 +- ...{page-cd30956407f5a78f.js => page-9474df83dfce71fa.js} | 2 +- ...{page-684839278a79e680.js => page-6ddf498724555fa1.js} | 2 +- .../chunks/app/(dashboard)/usage/page-6882feee0752e151.js | 1 + .../chunks/app/(dashboard)/usage/page-ad8c80216e699c08.js | 1 - ...{page-fa1d380036ee9c0f.js => page-607b92cfac56e9f9.js} | 2 +- .../app/(dashboard)/virtual-keys/page-40102b28aaaaca01.js | 1 - .../app/(dashboard)/virtual-keys/page-681e2e7643e3068c.js | 1 + ...out-0ca202cb877cb217.js => layout-4e0c2c971ccc1e6d.js} | 2 +- ...{page-d8e0ceba45be7212.js => page-4cdcc0d632ab220d.js} | 2 +- .../static/chunks/app/model_hub/page-16d517915c7f9cff.js | 1 + .../static/chunks/app/model_hub/page-a2e1e6710a4fa2da.js | 1 - .../chunks/app/model_hub_table/page-e60a536062f9ecd9.js | 1 + .../chunks/app/model_hub_table/page-f8aecb1243a432a2.js | 1 - .../static/chunks/app/onboarding/page-4fcd288a81e8dea5.js | 1 - .../static/chunks/app/onboarding/page-7cc24917468a90ab.js | 1 + .../out/_next/static/chunks/app/page-ce79b81673dd7dbb.js | 1 - .../out/_next/static/chunks/app/page-dda848d817541095.js | 1 + ...p-c6945ec5b2d5e671.js => main-app-77a6ca3c04ee9adf.js} | 2 +- litellm/proxy/_experimental/out/api-reference.html | 2 +- litellm/proxy/_experimental/out/api-reference.txt | 8 ++++---- .../_experimental/out/experimental/api-playground.html | 2 +- .../_experimental/out/experimental/api-playground.txt | 8 ++++---- litellm/proxy/_experimental/out/experimental/budgets.html | 2 +- litellm/proxy/_experimental/out/experimental/budgets.txt | 8 ++++---- litellm/proxy/_experimental/out/experimental/caching.html | 2 +- litellm/proxy/_experimental/out/experimental/caching.txt | 8 ++++---- .../proxy/_experimental/out/experimental/old-usage.html | 2 +- .../proxy/_experimental/out/experimental/old-usage.txt | 8 ++++---- litellm/proxy/_experimental/out/experimental/prompts.html | 2 +- litellm/proxy/_experimental/out/experimental/prompts.txt | 8 ++++---- .../_experimental/out/experimental/tag-management.html | 2 +- .../_experimental/out/experimental/tag-management.txt | 8 ++++---- litellm/proxy/_experimental/out/guardrails.html | 2 +- litellm/proxy/_experimental/out/guardrails.txt | 8 ++++---- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 6 +++--- litellm/proxy/_experimental/out/logs.html | 2 +- litellm/proxy/_experimental/out/logs.txt | 8 ++++---- litellm/proxy/_experimental/out/mcp/oauth/callback.html | 2 +- litellm/proxy/_experimental/out/mcp/oauth/callback.txt | 6 +++--- litellm/proxy/_experimental/out/model-hub.html | 2 +- litellm/proxy/_experimental/out/model-hub.txt | 8 ++++---- litellm/proxy/_experimental/out/model_hub.txt | 6 +++--- litellm/proxy/_experimental/out/model_hub_table.html | 2 +- litellm/proxy/_experimental/out/model_hub_table.txt | 6 +++--- litellm/proxy/_experimental/out/models-and-endpoints.html | 2 +- litellm/proxy/_experimental/out/models-and-endpoints.txt | 8 ++++---- litellm/proxy/_experimental/out/onboarding.html | 2 +- litellm/proxy/_experimental/out/onboarding.txt | 6 +++--- litellm/proxy/_experimental/out/organizations.html | 2 +- litellm/proxy/_experimental/out/organizations.txt | 8 ++++---- litellm/proxy/_experimental/out/playground.html | 2 +- litellm/proxy/_experimental/out/playground.txt | 8 ++++---- .../proxy/_experimental/out/settings/admin-settings.html | 2 +- .../proxy/_experimental/out/settings/admin-settings.txt | 8 ++++---- .../_experimental/out/settings/logging-and-alerts.html | 2 +- .../_experimental/out/settings/logging-and-alerts.txt | 8 ++++---- .../proxy/_experimental/out/settings/router-settings.html | 2 +- .../proxy/_experimental/out/settings/router-settings.txt | 8 ++++---- litellm/proxy/_experimental/out/settings/ui-theme.html | 2 +- litellm/proxy/_experimental/out/settings/ui-theme.txt | 8 ++++---- litellm/proxy/_experimental/out/teams.html | 2 +- litellm/proxy/_experimental/out/teams.txt | 8 ++++---- litellm/proxy/_experimental/out/test-key.html | 2 +- litellm/proxy/_experimental/out/test-key.txt | 8 ++++---- litellm/proxy/_experimental/out/tools/mcp-servers.html | 2 +- litellm/proxy/_experimental/out/tools/mcp-servers.txt | 8 ++++---- litellm/proxy/_experimental/out/tools/vector-stores.html | 2 +- litellm/proxy/_experimental/out/tools/vector-stores.txt | 8 ++++---- litellm/proxy/_experimental/out/usage.html | 2 +- litellm/proxy/_experimental/out/usage.txt | 8 ++++---- litellm/proxy/_experimental/out/users.html | 2 +- litellm/proxy/_experimental/out/users.txt | 8 ++++---- litellm/proxy/_experimental/out/virtual-keys.html | 2 +- litellm/proxy/_experimental/out/virtual-keys.txt | 8 ++++---- 169 files changed, 208 insertions(+), 213 deletions(-) rename litellm/proxy/_experimental/out/_next/static/{p0aP-Bq7GZqxIwOsdnqNv => V73dwfVXi9kkAaHXHHR5u}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{p0aP-Bq7GZqxIwOsdnqNv => V73dwfVXi9kkAaHXHHR5u}/_ssgManifest.js (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1263-cf8443d1d71fa593.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1394-bdcf4b8db9c252d2.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1442-529c645297e48128.js => 1442-024f7e51804e0d7e.js} (64%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1518-9a77ac5675e15594.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1623-995fddc2b5647961.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2012-7e2773c79199687c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2118-9efce161d33a9757.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2377-8fdad210b7695043.js => 2377-674bd40044d10e16.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{2926-a9eb2d7547cdad95.js => 2926-a9cb83e61fc8ad20.js} (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3218-4aea06837fa340f4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3221-0a12dcffbc76862d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/353-33a4d12e099f843a.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/353-e55516ea4730f9d4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3621-5ff5b3101d57f20d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4182-1ec11708566c0483.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4289-68573041eef5b2d2.js rename litellm/proxy/_experimental/out/_next/static/chunks/{4388-2f4ca3419d20af67.js => 4388-eb8fa49a76501802.js} (57%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4925-a8ad75d81592e879.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5074-51f1824c21869900.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5096-5318231023f36448.js rename litellm/proxy/_experimental/out/_next/static/chunks/{7732-a406d32f3b9f495f.js => 5170-56859ffa75db75f8.js} (87%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5333-438ba079aae9630c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/54-56a8e045d64789e2.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/543-7ae25eb17f21b433.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5572-9290ae3dc2551207.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5572-d4f8dc9b2bf09618.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5690-3bf2d6edf2ad3488.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6062-89f63f71675c6a08.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/630-1e0342aa26bb0fe8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/630-569f4366f2ad9eac.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6609-3e081758ffbe3786.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6609-d93906f43161f066.js rename litellm/proxy/_experimental/out/_next/static/chunks/{6843-d9a2a4bf3fc7a867.js => 6843-b8ebdf2bb4fe5c67.js} (71%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7140-937050711ba264d3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7155-1a3e4c5a6aefae2b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7155-459bc53437553b96.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7482-150cff9b9c47054a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7641-060a5116952b5019.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7641-f70830b7a61a3f9c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7975-afe816ddcb35e063.js rename litellm/proxy/_experimental/out/_next/static/chunks/{5636-ca3adecf2b222193.js => 8008-851877152eb2be38.js} (74%) rename litellm/proxy/_experimental/out/_next/static/chunks/{8235-c9c2c9abd48d9b13.js => 8237-253c15ae006496fe.js} (69%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8468-27ea05e25918ba32.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/849-d1cabf66d71a8808.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8533-b7d5b2f50457d35a.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8661-1cf4178f6bffc981.js rename litellm/proxy/_experimental/out/_next/static/chunks/{8948-a969f81088a52220.js => 8948-da16df3286be8c9b.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/905-f629babda9c46af3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9111-3cb8240098962e8a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9111-9b9192c9fb4809ff.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9165-82d12d1c73da639d.js rename litellm/proxy/_experimental/out/_next/static/chunks/{9301-33470ca245ba2578.js => 9301-905e8491f42289c0.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{9411-e2a18c2e46730a13.js => 9411-3630d7fd1940320c.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{9611-8bd2ffcee22edc34.js => 9611-e0c4dfb8fa3d2ed7.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9678-076488a4dc7af149.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9877-363a095158e1e758.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9877-f58702e3cb433729.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/api-reference/{page-873ebb2fa62f50ef.js => page-6ead8448e1510439.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/{page-a4d3d795479444f3.js => page-8047d2cef33b9999.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/{page-ce67247c5ba30385.js => page-3234cab0cb418464.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/{page-789ec63e0c88d7e0.js => page-0a286b7e7489b565.js} (94%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/{page-8dbfb3870a758842.js => page-bdfb6697f4d6f550.js} (98%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-02e4f62d0c9ed877.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-843a18f5283af912.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/{page-973343a42b32fea9.js => page-e5395395c754c862.js} (97%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/{page-668d462ee75b7eeb.js => page-fbd5350eb16ca3ba.js} (96%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/{layout-e46c8b3b8f848a3d.js => layout-a928c135835301f0.js} (98%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/page-791e856238f891ed.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/page-974be1d69803befc.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-a4e1b2d51a5f5567.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-eea8d5d5c2e4e69c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-82db406b0194e839.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-a11b969ee66b82c0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-8bb205a76fefc630.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-c9a9977ecb7e4aea.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/{page-33d87d224be1fc3b.js => page-6729e021acdc06f3.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/{page-312f76bcd644a776.js => page-41bcefda7b19fcbe.js} (93%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-2470c91c28de94ff.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-ce1c344c56b0301f.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/{page-490d292ead7627ee.js => page-7e77ec8e3ff58278.js} (88%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/{page-863067555596fb73.js => page-ee5d0a8b43105b4e.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/{page-85f4f91698b75711.js => page-866cd0b0d541c84f.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/{page-c6612da0a3d53b17.js => page-69022c9c481d0375.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/{page-cd30956407f5a78f.js => page-9474df83dfce71fa.js} (96%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/{page-684839278a79e680.js => page-6ddf498724555fa1.js} (97%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/usage/page-6882feee0752e151.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/usage/page-ad8c80216e699c08.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/{page-fa1d380036ee9c0f.js => page-607b92cfac56e9f9.js} (97%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/page-40102b28aaaaca01.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/page-681e2e7643e3068c.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/{layout-0ca202cb877cb217.js => layout-4e0c2c971ccc1e6d.js} (94%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/mcp/oauth/callback/{page-d8e0ceba45be7212.js => page-4cdcc0d632ab220d.js} (89%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-16d517915c7f9cff.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-a2e1e6710a4fa2da.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-e60a536062f9ecd9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-f8aecb1243a432a2.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-4fcd288a81e8dea5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-7cc24917468a90ab.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-ce79b81673dd7dbb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-dda848d817541095.js rename litellm/proxy/_experimental/out/_next/static/chunks/{main-app-c6945ec5b2d5e671.js => main-app-77a6ca3c04ee9adf.js} (81%) diff --git a/litellm/proxy/_experimental/out/_next/static/p0aP-Bq7GZqxIwOsdnqNv/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/p0aP-Bq7GZqxIwOsdnqNv/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/p0aP-Bq7GZqxIwOsdnqNv/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/p0aP-Bq7GZqxIwOsdnqNv/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1263-cf8443d1d71fa593.js b/litellm/proxy/_experimental/out/_next/static/chunks/1263-cf8443d1d71fa593.js deleted file mode 100644 index 828535d7728..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1263-cf8443d1d71fa593.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1263],{5540:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"}}]},name:"clock-circle",theme:"outlined"},l=r(55015),c=o.forwardRef(function(e,t){return o.createElement(l.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},78355:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M893.3 293.3L730.7 130.7c-7.5-7.5-16.7-13-26.7-16V112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 184h256v104H384V184zm456 656H184V184h136v136c0 17.7 14.3 32 32 32h320c17.7 0 32-14.3 32-32V205.8l136 136V840zM512 442c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm0 224c-44.2 0-80-35.8-80-80s35.8-80 80-80 80 35.8 80 80-35.8 80-80 80z"}}]},name:"save",theme:"outlined"},l=r(55015),c=o.forwardRef(function(e,t){return o.createElement(l.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},8881:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z"}}]},name:"stop",theme:"outlined"},l=r(55015),c=o.forwardRef(function(e,t){return o.createElement(l.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},3632:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"},l=r(55015),c=o.forwardRef(function(e,t){return o.createElement(l.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},35291:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"},l=r(55015),c=o.forwardRef(function(e,t){return o.createElement(l.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},59664:function(e,t,r){r.d(t,{Z:function(){return O}});var n=r(5853),o=r(2265),a=r(47625),l=r(93765),c=r(54061),i=r(97059),s=r(62994),d=r(25311),u=(0,l.z)({chartName:"LineChart",GraphicalChild:c.x,axisComponents:[{axisType:"xAxis",AxisComp:i.K},{axisType:"yAxis",AxisComp:s.B}],formatAxisMap:d.t9}),m=r(56940),p=r(26680),f=r(8147),g=r(22190),v=r(81889),b=r(65278),h=r(98593),y=r(92666),k=r(32644),x=r(7084),w=r(26898),C=r(13241),E=r(1153);let O=o.forwardRef((e,t)=>{let{data:r=[],categories:l=[],index:d,colors:O=w.s,valueFormatter:j=E.Cj,startEndOnly:N=!1,showXAxis:Z=!0,showYAxis:L=!0,yAxisWidth:S=56,intervalType:P="equidistantPreserveStart",animationDuration:z=900,showAnimation:M=!1,showTooltip:T=!0,showLegend:B=!0,showGridLines:A=!0,autoMinValue:W=!1,curveType:V="linear",minValue:I,maxValue:R,connectNulls:D=!1,allowDecimals:q=!0,noDataText:F,className:K,onValueChange:H,enableLegendSlider:_=!1,customTooltip:G,rotateLabelX:X,padding:U=Z||L?{left:20,right:20}:{left:0,right:0},tickGap:$=5,xAxisLabel:Y,yAxisLabel:Q}=e,J=(0,n._T)(e,["data","categories","index","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","animationDuration","showAnimation","showTooltip","showLegend","showGridLines","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","padding","tickGap","xAxisLabel","yAxisLabel"]),[ee,et]=(0,o.useState)(60),[er,en]=(0,o.useState)(void 0),[eo,ea]=(0,o.useState)(void 0),el=(0,k.me)(l,O),ec=(0,k.i4)(W,I,R),ei=!!H;function es(e){ei&&(e===eo&&!er||(0,k.FB)(r,e)&&er&&er.dataKey===e?(ea(void 0),null==H||H(null)):(ea(e),null==H||H({eventType:"category",categoryClicked:e})),en(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,C.q)("w-full h-80",K)},J),o.createElement(a.h,{className:"h-full w-full"},(null==r?void 0:r.length)?o.createElement(u,{data:r,onClick:ei&&(eo||er)?()=>{en(void 0),ea(void 0),null==H||H(null)}:void 0,margin:{bottom:Y?30:void 0,left:Q?20:void 0,right:Q?5:void 0,top:5}},A?o.createElement(m.q,{className:(0,C.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(i.K,{padding:U,hide:!Z,dataKey:d,interval:N?"preserveStartEnd":P,tick:{transform:"translate(0, 6)"},ticks:N?[r[0][d],r[r.length-1][d]]:void 0,fill:"",stroke:"",className:(0,C.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,minTickGap:$,angle:null==X?void 0:X.angle,dy:null==X?void 0:X.verticalShift,height:null==X?void 0:X.xAxisHeight},Y&&o.createElement(p._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},Y)),o.createElement(s.B,{width:S,hide:!L,axisLine:!1,tickLine:!1,type:"number",domain:ec,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,C.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:j,allowDecimals:q},Q&&o.createElement(p._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},Q)),o.createElement(f.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:T?e=>{let{active:t,payload:r,label:n}=e;return G?o.createElement(G,{payload:null==r?void 0:r.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=el.get(e.dataKey))&&void 0!==t?t:x.fr.Gray})}),active:t,label:n}):o.createElement(h.ZP,{active:t,payload:r,label:n,valueFormatter:j,categoryColors:el})}:o.createElement(o.Fragment,null),position:{y:0}}),B?o.createElement(g.D,{verticalAlign:"top",height:ee,content:e=>{let{payload:t}=e;return(0,b.Z)({payload:t},el,et,eo,ei?e=>es(e):void 0,_)}}):null,l.map(e=>{var t;return o.createElement(c.x,{className:(0,C.q)((0,E.bM)(null!==(t=el.get(e))&&void 0!==t?t:x.fr.Gray,w.K.text).strokeColor),strokeOpacity:er||eo&&eo!==e?.3:1,activeDot:e=>{var t;let{cx:n,cy:a,stroke:l,strokeLinecap:c,strokeLinejoin:i,strokeWidth:s,dataKey:d}=e;return o.createElement(v.o,{className:(0,C.q)("stroke-tremor-background dark:stroke-dark-tremor-background",H?"cursor-pointer":"",(0,E.bM)(null!==(t=el.get(d))&&void 0!==t?t:x.fr.Gray,w.K.text).fillColor),cx:n,cy:a,r:5,fill:"",stroke:l,strokeLinecap:c,strokeLinejoin:i,strokeWidth:s,onClick:(t,n)=>{n.stopPropagation(),ei&&(e.index===(null==er?void 0:er.index)&&e.dataKey===(null==er?void 0:er.dataKey)||(0,k.FB)(r,e.dataKey)&&eo&&eo===e.dataKey?(ea(void 0),en(void 0),null==H||H(null)):(ea(e.dataKey),en({index:e.index,dataKey:e.dataKey}),null==H||H(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var n;let{stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:i,cx:s,cy:d,dataKey:u,index:m}=t;return(0,k.FB)(r,e)&&!(er||eo&&eo!==e)||(null==er?void 0:er.index)===m&&(null==er?void 0:er.dataKey)===e?o.createElement(v.o,{key:m,cx:s,cy:d,r:5,stroke:a,fill:"",strokeLinecap:l,strokeLinejoin:c,strokeWidth:i,className:(0,C.q)("stroke-tremor-background dark:stroke-dark-tremor-background",H?"cursor-pointer":"",(0,E.bM)(null!==(n=el.get(u))&&void 0!==n?n:x.fr.Gray,w.K.text).fillColor)}):o.createElement(o.Fragment,{key:m})},key:e,name:e,type:V,dataKey:e,stroke:"",strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:M,animationDuration:z,connectNulls:D})}),H?l.map(e=>o.createElement(c.x,{className:(0,C.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:V,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:D,onClick:(e,t)=>{t.stopPropagation();let{name:r}=e;es(r)}})):null):o.createElement(y.Z,{noDataText:F})))});O.displayName="LineChart"},59341:function(e,t,r){r.d(t,{Z:function(){return z}});var n=r(5853),o=r(71049),a=r(11323),l=r(2265),c=r(66797),i=r(40099),s=r(74275),d=r(59456),u=r(93980),m=r(65573),p=r(67561),f=r(87550),g=r(628),v=r(80281),b=r(31370),h=r(20131),y=r(38929),k=r(52307),x=r(52724),w=r(7935);let C=(0,l.createContext)(null);C.displayName="GroupContext";let E=l.Fragment,O=Object.assign((0,y.yV)(function(e,t){var r;let n=(0,l.useId)(),E=(0,v.Q)(),O=(0,f.B)(),{id:j=E||"headlessui-switch-".concat(n),disabled:N=O||!1,checked:Z,defaultChecked:L,onChange:S,name:P,value:z,form:M,autoFocus:T=!1,...B}=e,A=(0,l.useContext)(C),[W,V]=(0,l.useState)(null),I=(0,l.useRef)(null),R=(0,p.T)(I,t,null===A?null:A.setSwitch,V),D=(0,s.L)(L),[q,F]=(0,i.q)(Z,S,null!=D&&D),K=(0,d.G)(),[H,_]=(0,l.useState)(!1),G=(0,u.z)(()=>{_(!0),null==F||F(!q),K.nextFrame(()=>{_(!1)})}),X=(0,u.z)(e=>{if((0,b.P)(e.currentTarget))return e.preventDefault();e.preventDefault(),G()}),U=(0,u.z)(e=>{e.key===x.R.Space?(e.preventDefault(),G()):e.key===x.R.Enter&&(0,h.g)(e.currentTarget)}),$=(0,u.z)(e=>e.preventDefault()),Y=(0,w.wp)(),Q=(0,k.zH)(),{isFocusVisible:J,focusProps:ee}=(0,o.F)({autoFocus:T}),{isHovered:et,hoverProps:er}=(0,a.X)({isDisabled:N}),{pressed:en,pressProps:eo}=(0,c.x)({disabled:N}),ea=(0,l.useMemo)(()=>({checked:q,disabled:N,hover:et,focus:J,active:en,autofocus:T,changing:H}),[q,et,J,en,N,H,T]),el=(0,y.dG)({id:j,ref:R,role:"switch",type:(0,m.f)(e,W),tabIndex:-1===e.tabIndex?0:null!=(r=e.tabIndex)?r:0,"aria-checked":q,"aria-labelledby":Y,"aria-describedby":Q,disabled:N||void 0,autoFocus:T,onClick:X,onKeyUp:U,onKeyPress:$},ee,er,eo),ec=(0,l.useCallback)(()=>{if(void 0!==D)return null==F?void 0:F(D)},[F,D]),ei=(0,y.L6)();return l.createElement(l.Fragment,null,null!=P&&l.createElement(g.Mt,{disabled:N,data:{[P]:z||"on"},overrides:{type:"checkbox",checked:q},form:M,onReset:ec}),ei({ourProps:el,theirProps:B,slot:ea,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var t;let[r,n]=(0,l.useState)(null),[o,a]=(0,w.bE)(),[c,i]=(0,k.fw)(),s=(0,l.useMemo)(()=>({switch:r,setSwitch:n}),[r,n]),d=(0,y.L6)();return l.createElement(i,{name:"Switch.Description",value:c},l.createElement(a,{name:"Switch.Label",value:o,props:{htmlFor:null==(t=s.switch)?void 0:t.id,onClick(e){r&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),r.click(),r.focus({preventScroll:!0}))}}},l.createElement(C.Provider,{value:s},d({ourProps:{},theirProps:e,slot:{},defaultTag:E,name:"Switch.Group"}))))},Label:w.__,Description:k.dk});var j=r(44140),N=r(26898),Z=r(13241),L=r(1153),S=r(47187);let P=(0,L.fn)("Switch"),z=l.forwardRef((e,t)=>{let{checked:r,defaultChecked:o=!1,onChange:a,color:c,name:i,error:s,errorMessage:d,disabled:u,required:m,tooltip:p,id:f}=e,g=(0,n._T)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),v={bgColor:c?(0,L.bM)(c,N.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:c?(0,L.bM)(c,N.K.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[b,h]=(0,j.Z)(o,r),[y,k]=(0,l.useState)(!1),{tooltipProps:x,getReferenceProps:w}=(0,S.l)(300);return l.createElement("div",{className:"flex flex-row items-center justify-start"},l.createElement(S.Z,Object.assign({text:p},x)),l.createElement("div",Object.assign({ref:(0,L.lq)([t,x.refs.setReference]),className:(0,Z.q)(P("root"),"flex flex-row relative h-5")},g,w),l.createElement("input",{type:"checkbox",className:(0,Z.q)(P("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:i,required:m,checked:b,onChange:e=>{e.preventDefault()}}),l.createElement(O,{checked:b,onChange:e=>{h(e),null==a||a(e)},disabled:u,className:(0,Z.q)(P("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",u?"cursor-not-allowed":""),onFocus:()=>k(!0),onBlur:()=>k(!1),id:f},l.createElement("span",{className:(0,Z.q)(P("sr-only"),"sr-only")},"Switch ",b?"on":"off"),l.createElement("span",{"aria-hidden":"true",className:(0,Z.q)(P("background"),b?v.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),l.createElement("span",{"aria-hidden":"true",className:(0,Z.q)(P("round"),b?(0,Z.q)(v.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",y?(0,Z.q)("ring-2",v.ringColor):"")}))),s&&d?l.createElement("p",{className:(0,Z.q)(P("errorMessage"),"text-sm text-red-500 mt-1 ")},d):null)});z.displayName="Switch"},92570:function(e,t,r){r.d(t,{Z:function(){return n}});let n=e=>e?"function"==typeof e?e():e:null},69410:function(e,t,r){var n=r(54998);t.Z=n.Z},867:function(e,t,r){r.d(t,{Z:function(){return O}});var n=r(2265),o=r(54537),a=r(36760),l=r.n(a),c=r(50506),i=r(18694),s=r(71744),d=r(79326),u=r(59367),m=r(92570),p=r(5545),f=r(51248),g=r(55274),v=r(37381),b=r(20435),h=r(99320);let y=e=>{let{componentCls:t,iconCls:r,antCls:n,zIndexPopup:o,colorText:a,colorWarning:l,marginXXS:c,marginXS:i,fontSize:s,fontWeightStrong:d,colorTextHeading:u}=e;return{[t]:{zIndex:o,["&".concat(n,"-popover")]:{fontSize:s},["".concat(t,"-message")]:{marginBottom:i,display:"flex",flexWrap:"nowrap",alignItems:"start",["> ".concat(t,"-message-icon ").concat(r)]:{color:l,fontSize:s,lineHeight:1,marginInlineEnd:i},["".concat(t,"-title")]:{fontWeight:d,color:u,"&:only-child":{fontWeight:"normal"}},["".concat(t,"-description")]:{marginTop:c,color:a}},["".concat(t,"-buttons")]:{textAlign:"end",whiteSpace:"nowrap",button:{marginInlineStart:i}}}}};var k=(0,h.I$)("Popconfirm",e=>y(e),e=>{let{zIndexPopupBase:t}=e;return{zIndexPopup:t+60}},{resetStyle:!1}),x=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let w=e=>{let{prefixCls:t,okButtonProps:r,cancelButtonProps:a,title:l,description:c,cancelText:i,okText:d,okType:b="primary",icon:h=n.createElement(o.Z,null),showCancel:y=!0,close:k,onConfirm:x,onCancel:w,onPopupClick:C}=e,{getPrefixCls:E}=n.useContext(s.E_),[O]=(0,g.Z)("Popconfirm",v.Z.Popconfirm),j=(0,m.Z)(l),N=(0,m.Z)(c);return n.createElement("div",{className:"".concat(t,"-inner-content"),onClick:C},n.createElement("div",{className:"".concat(t,"-message")},h&&n.createElement("span",{className:"".concat(t,"-message-icon")},h),n.createElement("div",{className:"".concat(t,"-message-text")},j&&n.createElement("div",{className:"".concat(t,"-title")},j),N&&n.createElement("div",{className:"".concat(t,"-description")},N))),n.createElement("div",{className:"".concat(t,"-buttons")},y&&n.createElement(p.ZP,Object.assign({onClick:w,size:"small"},a),i||(null==O?void 0:O.cancelText)),n.createElement(u.Z,{buttonProps:Object.assign(Object.assign({size:"small"},(0,f.nx)(b)),r),actionFn:x,close:k,prefixCls:E("btn"),quitOnNullishReturnValue:!0,emitEvent:!0},d||(null==O?void 0:O.okText))))};var C=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let E=n.forwardRef((e,t)=>{var r,a;let{prefixCls:u,placement:m="top",trigger:p="click",okType:f="primary",icon:g=n.createElement(o.Z,null),children:v,overlayClassName:b,onOpenChange:h,onVisibleChange:y,overlayStyle:x,styles:E,classNames:O}=e,j=C(e,["prefixCls","placement","trigger","okType","icon","children","overlayClassName","onOpenChange","onVisibleChange","overlayStyle","styles","classNames"]),{getPrefixCls:N,className:Z,style:L,classNames:S,styles:P}=(0,s.dj)("popconfirm"),[z,M]=(0,c.Z)(!1,{value:null!==(r=e.open)&&void 0!==r?r:e.visible,defaultValue:null!==(a=e.defaultOpen)&&void 0!==a?a:e.defaultVisible}),T=(e,t)=>{M(e,!0),null==y||y(e),null==h||h(e,t)},B=N("popconfirm",u),A=l()(B,Z,b,S.root,null==O?void 0:O.root),W=l()(S.body,null==O?void 0:O.body),[V]=k(B);return V(n.createElement(d.Z,Object.assign({},(0,i.Z)(j,["title"]),{trigger:p,placement:m,onOpenChange:(t,r)=>{let{disabled:n=!1}=e;n||T(t,r)},open:z,ref:t,classNames:{root:A,body:W},styles:{root:Object.assign(Object.assign(Object.assign(Object.assign({},P.root),L),x),null==E?void 0:E.root),body:Object.assign(Object.assign({},P.body),null==E?void 0:E.body)},content:n.createElement(w,Object.assign({okType:f,icon:g},e,{prefixCls:B,close:e=>{T(!1,e)},onConfirm:t=>{var r;return null===(r=e.onConfirm)||void 0===r?void 0:r.call(void 0,t)},onCancel:t=>{var r;T(!1,t),null===(r=e.onCancel)||void 0===r||r.call(void 0,t)}})),"data-popover-inject":!0}),v))});E._InternalPanelDoNotUseOrYouWillBeFired=e=>{let{prefixCls:t,placement:r,className:o,style:a}=e,c=x(e,["prefixCls","placement","className","style"]),{getPrefixCls:i}=n.useContext(s.E_),d=i("popconfirm",t),[u]=k(d);return u(n.createElement(b.ZP,{placement:r,className:l()(d,o),style:a,content:n.createElement(w,Object.assign({prefixCls:d},c))}))};var O=E},20435:function(e,t,r){r.d(t,{aV:function(){return u}});var n=r(2265),o=r(36760),a=r.n(o),l=r(5769),c=r(92570),i=r(71744),s=r(72262),d=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let u=e=>{let{title:t,content:r,prefixCls:o}=e;return t||r?n.createElement(n.Fragment,null,t&&n.createElement("div",{className:"".concat(o,"-title")},t),r&&n.createElement("div",{className:"".concat(o,"-inner-content")},r)):null},m=e=>{let{hashId:t,prefixCls:r,className:o,style:i,placement:s="top",title:d,content:m,children:p}=e,f=(0,c.Z)(d),g=(0,c.Z)(m),v=a()(t,r,"".concat(r,"-pure"),"".concat(r,"-placement-").concat(s),o);return n.createElement("div",{className:v,style:i},n.createElement("div",{className:"".concat(r,"-arrow")}),n.createElement(l.G,Object.assign({},e,{className:t,prefixCls:r}),p||n.createElement(u,{prefixCls:r,title:f,content:g})))};t.ZP=e=>{let{prefixCls:t,className:r}=e,o=d(e,["prefixCls","className"]),{getPrefixCls:l}=n.useContext(i.E_),c=l("popover",t),[u,p,f]=(0,s.Z)(c);return u(n.createElement(m,Object.assign({},o,{prefixCls:c,hashId:p,className:a()(r,f)})))}},79326:function(e,t,r){var n=r(2265),o=r(36760),a=r.n(o),l=r(50506),c=r(95814),i=r(92570),s=r(68710),d=r(19722),u=r(71744),m=r(99981),p=r(20435),f=r(72262),g=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let v=n.forwardRef((e,t)=>{var r,o;let{prefixCls:v,title:b,content:h,overlayClassName:y,placement:k="top",trigger:x="hover",children:w,mouseEnterDelay:C=.1,mouseLeaveDelay:E=.1,onOpenChange:O,overlayStyle:j={},styles:N,classNames:Z}=e,L=g(e,["prefixCls","title","content","overlayClassName","placement","trigger","children","mouseEnterDelay","mouseLeaveDelay","onOpenChange","overlayStyle","styles","classNames"]),{getPrefixCls:S,className:P,style:z,classNames:M,styles:T}=(0,u.dj)("popover"),B=S("popover",v),[A,W,V]=(0,f.Z)(B),I=S(),R=a()(y,W,V,P,M.root,null==Z?void 0:Z.root),D=a()(M.body,null==Z?void 0:Z.body),[q,F]=(0,l.Z)(!1,{value:null!==(r=e.open)&&void 0!==r?r:e.visible,defaultValue:null!==(o=e.defaultOpen)&&void 0!==o?o:e.defaultVisible}),K=(e,t)=>{F(e,!0),null==O||O(e,t)},H=e=>{e.keyCode===c.Z.ESC&&K(!1,e)},_=(0,i.Z)(b),G=(0,i.Z)(h);return A(n.createElement(m.Z,Object.assign({placement:k,trigger:x,mouseEnterDelay:C,mouseLeaveDelay:E},L,{prefixCls:B,classNames:{root:R,body:D},styles:{root:Object.assign(Object.assign(Object.assign(Object.assign({},T.root),z),j),null==N?void 0:N.root),body:Object.assign(Object.assign({},T.body),null==N?void 0:N.body)},ref:t,open:q,onOpenChange:e=>{K(e)},overlay:_||G?n.createElement(p.aV,{prefixCls:B,title:_,content:G}):null,transitionName:(0,s.m)(I,"zoom-big",L.transitionName),"data-popover-inject":!0}),(0,d.Tm)(w,{onKeyDown:e=>{var t,r;(0,n.isValidElement)(w)&&(null===(r=null==w?void 0:(t=w.props).onKeyDown)||void 0===r||r.call(t,e)),H(e)}})))});v._InternalPanelDoNotUseOrYouWillBeFired=p.ZP,t.Z=v},72262:function(e,t,r){var n=r(12918),o=r(691),a=r(88260),l=r(34442),c=r(53454),i=r(99320),s=r(71140);let d=e=>{let{componentCls:t,popoverColor:r,titleMinWidth:o,fontWeightStrong:l,innerPadding:c,boxShadowSecondary:i,colorTextHeading:s,borderRadiusLG:d,zIndexPopup:u,titleMarginBottom:m,colorBgElevated:p,popoverBg:f,titleBorderBottom:g,innerContentPadding:v,titlePadding:b}=e;return[{[t]:Object.assign(Object.assign({},(0,n.Wf)(e)),{position:"absolute",top:0,left:{_skip_check_:!0,value:0},zIndex:u,fontWeight:"normal",whiteSpace:"normal",textAlign:"start",cursor:"auto",userSelect:"text","--valid-offset-x":"var(--arrow-offset-horizontal, var(--arrow-x))",transformOrigin:"var(--valid-offset-x, 50%) var(--arrow-y, 50%)","--antd-arrow-background-color":p,width:"max-content",maxWidth:"100vw","&-rtl":{direction:"rtl"},"&-hidden":{display:"none"},["".concat(t,"-content")]:{position:"relative"},["".concat(t,"-inner")]:{backgroundColor:f,backgroundClip:"padding-box",borderRadius:d,boxShadow:i,padding:c},["".concat(t,"-title")]:{minWidth:o,marginBottom:m,color:s,fontWeight:l,borderBottom:g,padding:b},["".concat(t,"-inner-content")]:{color:r,padding:v}})},(0,a.ZP)(e,"var(--antd-arrow-background-color)"),{["".concat(t,"-pure")]:{position:"relative",maxWidth:"none",margin:e.sizePopupArrow,display:"inline-block",["".concat(t,"-content")]:{display:"inline-block"}}}]},u=e=>{let{componentCls:t}=e;return{[t]:c.i.map(r=>{let n=e["".concat(r,"6")];return{["&".concat(t,"-").concat(r)]:{"--antd-arrow-background-color":n,["".concat(t,"-inner")]:{backgroundColor:n},["".concat(t,"-arrow")]:{background:"transparent"}}}})}};t.Z=(0,i.I$)("Popover",e=>{let{colorBgElevated:t,colorText:r}=e,n=(0,s.IX)(e,{popoverBg:t,popoverColor:r});return[d(n),u(n),(0,o._y)(n,"zoom-big")]},e=>{let{lineWidth:t,controlHeight:r,fontHeight:n,padding:o,wireframe:c,zIndexPopupBase:i,borderRadiusLG:s,marginXS:d,lineType:u,colorSplit:m,paddingSM:p}=e,f=r-n;return Object.assign(Object.assign(Object.assign({titleMinWidth:177,zIndexPopup:i+30},(0,l.w)(e)),(0,a.wZ)({contentRadius:s,limitVerticalRadius:!0})),{innerPadding:c?0:12,titleMarginBottom:c?0:d,titlePadding:c?"".concat(f/2,"px ").concat(o,"px ").concat(f/2-t,"px"):0,titleBorderBottom:c?"".concat(t,"px ").concat(u," ").concat(m):"none",innerContentPadding:c?"".concat(p,"px ").concat(o,"px"):0})},{resetStyle:!1,deprecatedTokens:[["width","titleMinWidth"],["minWidth","titleMinWidth"]]})},47451:function(e,t,r){var n=r(77774);t.Z=n.Z},3810:function(e,t,r){r.d(t,{Z:function(){return S}});var n=r(2265),o=r(36760),a=r.n(o),l=r(18694),c=r(93350),i=r(53445),s=r(19722),d=r(6694),u=r(71744),m=r(93463),p=r(54558),f=r(12918),g=r(71140),v=r(99320);let b=e=>{let{paddingXXS:t,lineWidth:r,tagPaddingHorizontal:n,componentCls:o,calc:a}=e,l=a(n).sub(r).equal(),c=a(t).sub(r).equal();return{[o]:Object.assign(Object.assign({},(0,f.Wf)(e)),{display:"inline-block",height:"auto",marginInlineEnd:e.marginXS,paddingInline:l,fontSize:e.tagFontSize,lineHeight:e.tagLineHeight,whiteSpace:"nowrap",background:e.defaultBg,border:"".concat((0,m.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,opacity:1,transition:"all ".concat(e.motionDurationMid),textAlign:"start",position:"relative",["&".concat(o,"-rtl")]:{direction:"rtl"},"&, a, a:hover":{color:e.defaultColor},["".concat(o,"-close-icon")]:{marginInlineStart:c,fontSize:e.tagIconSize,color:e.colorIcon,cursor:"pointer",transition:"all ".concat(e.motionDurationMid),"&:hover":{color:e.colorTextHeading}},["&".concat(o,"-has-color")]:{borderColor:"transparent",["&, a, a:hover, ".concat(e.iconCls,"-close, ").concat(e.iconCls,"-close:hover")]:{color:e.colorTextLightSolid}},"&-checkable":{backgroundColor:"transparent",borderColor:"transparent",cursor:"pointer",["&:not(".concat(o,"-checkable-checked):hover")]:{color:e.colorPrimary,backgroundColor:e.colorFillSecondary},"&:active, &-checked":{color:e.colorTextLightSolid},"&-checked":{backgroundColor:e.colorPrimary,"&:hover":{backgroundColor:e.colorPrimaryHover}},"&:active":{backgroundColor:e.colorPrimaryActive}},"&-hidden":{display:"none"},["> ".concat(e.iconCls," + span, > span + ").concat(e.iconCls)]:{marginInlineStart:l}}),["".concat(o,"-borderless")]:{borderColor:"transparent",background:e.tagBorderlessBg}}},h=e=>{let{lineWidth:t,fontSizeIcon:r,calc:n}=e,o=e.fontSizeSM;return(0,g.IX)(e,{tagFontSize:o,tagLineHeight:(0,m.bf)(n(e.lineHeightSM).mul(o).equal()),tagIconSize:n(r).sub(n(t).mul(2)).equal(),tagPaddingHorizontal:8,tagBorderlessBg:e.defaultBg})},y=e=>({defaultBg:new p.t(e.colorFillQuaternary).onBackground(e.colorBgContainer).toHexString(),defaultColor:e.colorText});var k=(0,v.I$)("Tag",e=>b(h(e)),y),x=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let w=n.forwardRef((e,t)=>{let{prefixCls:r,style:o,className:l,checked:c,children:i,icon:s,onChange:d,onClick:m}=e,p=x(e,["prefixCls","style","className","checked","children","icon","onChange","onClick"]),{getPrefixCls:f,tag:g}=n.useContext(u.E_),v=f("tag",r),[b,h,y]=k(v),w=a()(v,"".concat(v,"-checkable"),{["".concat(v,"-checkable-checked")]:c},null==g?void 0:g.className,l,h,y);return b(n.createElement("span",Object.assign({},p,{ref:t,style:Object.assign(Object.assign({},o),null==g?void 0:g.style),className:w,onClick:e=>{null==d||d(!c),null==m||m(e)}}),s,n.createElement("span",null,i)))});var C=r(18536);let E=e=>(0,C.Z)(e,(t,r)=>{let{textColor:n,lightBorderColor:o,lightColor:a,darkColor:l}=r;return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:n,background:a,borderColor:o,"&-inverse":{color:e.colorTextLightSolid,background:l,borderColor:l},["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}});var O=(0,v.bk)(["Tag","preset"],e=>E(h(e)),y);let j=(e,t,r)=>{let n="string"!=typeof r?r:r.charAt(0).toUpperCase()+r.slice(1);return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:e["color".concat(r)],background:e["color".concat(n,"Bg")],borderColor:e["color".concat(n,"Border")],["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}};var N=(0,v.bk)(["Tag","status"],e=>{let t=h(e);return[j(t,"success","Success"),j(t,"processing","Info"),j(t,"error","Error"),j(t,"warning","Warning")]},y),Z=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let L=n.forwardRef((e,t)=>{let{prefixCls:r,className:o,rootClassName:m,style:p,children:f,icon:g,color:v,onClose:b,bordered:h=!0,visible:y}=e,x=Z(e,["prefixCls","className","rootClassName","style","children","icon","color","onClose","bordered","visible"]),{getPrefixCls:w,direction:C,tag:E}=n.useContext(u.E_),[j,L]=n.useState(!0),S=(0,l.Z)(x,["closeIcon","closable"]);n.useEffect(()=>{void 0!==y&&L(y)},[y]);let P=(0,c.o2)(v),z=(0,c.yT)(v),M=P||z,T=Object.assign(Object.assign({backgroundColor:v&&!M?v:void 0},null==E?void 0:E.style),p),B=w("tag",r),[A,W,V]=k(B),I=a()(B,null==E?void 0:E.className,{["".concat(B,"-").concat(v)]:M,["".concat(B,"-has-color")]:v&&!M,["".concat(B,"-hidden")]:!j,["".concat(B,"-rtl")]:"rtl"===C,["".concat(B,"-borderless")]:!h},o,m,W,V),R=e=>{e.stopPropagation(),null==b||b(e),e.defaultPrevented||L(!1)},[,D]=(0,i.b)((0,i.w)(e),(0,i.w)(E),{closable:!1,closeIconRender:e=>{let t=n.createElement("span",{className:"".concat(B,"-close-icon"),onClick:R},e);return(0,s.wm)(e,t,e=>({onClick:t=>{var r;null===(r=null==e?void 0:e.onClick)||void 0===r||r.call(e,t),R(t)},className:a()(null==e?void 0:e.className,"".concat(B,"-close-icon"))}))}}),q="function"==typeof x.onClick||f&&"a"===f.type,F=g||null,K=F?n.createElement(n.Fragment,null,F,f&&n.createElement("span",null,f)):f,H=n.createElement("span",Object.assign({},S,{ref:t,className:I,style:T}),K,D,P&&n.createElement(O,{key:"preset",prefixCls:B}),z&&n.createElement(N,{key:"status",prefixCls:B}));return A(q?n.createElement(d.Z,{component:"Tag"},H):H)});L.CheckableTag=w;var S=L},79205:function(e,t,r){r.d(t,{Z:function(){return u}});var n=r(2265);let o=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),a=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,r)=>r?r.toUpperCase():t.toLowerCase()),l=e=>{let t=a(e);return t.charAt(0).toUpperCase()+t.slice(1)},c=function(){for(var e=arguments.length,t=Array(e),r=0;r!!e&&""!==e.trim()&&r.indexOf(e)===t).join(" ").trim()},i=e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0};var s={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let d=(0,n.forwardRef)((e,t)=>{let{color:r="currentColor",size:o=24,strokeWidth:a=2,absoluteStrokeWidth:l,className:d="",children:u,iconNode:m,...p}=e;return(0,n.createElement)("svg",{ref:t,...s,width:o,height:o,stroke:r,strokeWidth:l?24*Number(a)/Number(o):a,className:c("lucide",d),...!u&&!i(p)&&{"aria-hidden":"true"},...p},[...m.map(e=>{let[t,r]=e;return(0,n.createElement)(t,r)}),...Array.isArray(u)?u:[u]])}),u=(e,t)=>{let r=(0,n.forwardRef)((r,a)=>{let{className:i,...s}=r;return(0,n.createElement)(d,{ref:a,iconNode:t,className:c("lucide-".concat(o(l(e))),"lucide-".concat(e),i),...s})});return r.displayName=l(e),r}},78867:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]])},87769:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("eye-off",[["path",{d:"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",key:"ct8e1f"}],["path",{d:"M14.084 14.158a3 3 0 0 1-4.242-4.242",key:"151rxh"}],["path",{d:"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",key:"13bj9a"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]])},42208:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("eye",[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]])},86462:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"}))});t.Z=o},88532:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"}))});t.Z=o},2356:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"}))});t.Z=o},15731:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=o},45589:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"}))});t.Z=o},91126:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=o},49084:function(e,t,r){var n=r(2265);let o=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"}))});t.Z=o}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1394-bdcf4b8db9c252d2.js b/litellm/proxy/_experimental/out/_next/static/chunks/1394-bdcf4b8db9c252d2.js deleted file mode 100644 index c081a650d52..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1394-bdcf4b8db9c252d2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1394],{10353:function(t,e,n){let o;n.d(e,{Z:function(){return D}});var i=n(2265),a=n(36760),c=n.n(a),r=n(71744),l=n(19722),s=n(27380);let d=80*Math.PI,u=t=>{let{dotClassName:e,style:n,hasCircleCls:o}=t;return i.createElement("circle",{className:c()("".concat(e,"-circle"),{["".concat(e,"-circle-bg")]:o}),r:40,cx:50,cy:50,strokeWidth:20,style:n})};var m=t=>{let{percent:e,prefixCls:n}=t,o="".concat(n,"-dot"),a="".concat(o,"-holder"),r="".concat(a,"-hidden"),[l,m]=i.useState(!1);(0,s.Z)(()=>{0!==e&&m(!0)},[0!==e]);let p=Math.max(Math.min(e,100),0);if(!l)return null;let h={strokeDashoffset:"".concat(d/4),strokeDasharray:"".concat(d*p/100," ").concat(d*(100-p)/100)};return i.createElement("span",{className:c()(a,"".concat(o,"-progress"),p<=0&&r)},i.createElement("svg",{viewBox:"0 0 ".concat(100," ").concat(100),role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":p},i.createElement(u,{dotClassName:o,hasCircleCls:!0}),i.createElement(u,{dotClassName:o,style:h})))};function p(t){let{prefixCls:e,percent:n=0}=t,o="".concat(e,"-dot"),a="".concat(o,"-holder"),r="".concat(a,"-hidden");return i.createElement(i.Fragment,null,i.createElement("span",{className:c()(a,n>0&&r)},i.createElement("span",{className:c()(o,"".concat(e,"-dot-spin"))},[1,2,3,4].map(t=>i.createElement("i",{className:"".concat(e,"-dot-item"),key:t})))),i.createElement(m,{prefixCls:e,percent:n}))}function h(t){var e;let{prefixCls:n,indicator:o,percent:a}=t;return o&&i.isValidElement(o)?(0,l.Tm)(o,{className:c()(null===(e=o.props)||void 0===e?void 0:e.className,"".concat(n,"-dot")),percent:a}):i.createElement(p,{prefixCls:n,percent:a})}var v=n(93463),g=n(12918),f=n(99320),S=n(71140);let b=new v.E4("antSpinMove",{to:{opacity:1}}),y=new v.E4("antRotate",{to:{transform:"rotate(405deg)"}}),w=t=>{let{componentCls:e,calc:n}=t;return{[e]:Object.assign(Object.assign({},(0,g.Wf)(t)),{position:"absolute",display:"none",color:t.colorPrimary,fontSize:0,textAlign:"center",verticalAlign:"middle",opacity:0,transition:"transform ".concat(t.motionDurationSlow," ").concat(t.motionEaseInOutCirc),"&-spinning":{position:"relative",display:"inline-block",opacity:1},["".concat(e,"-text")]:{fontSize:t.fontSize,paddingTop:n(n(t.dotSize).sub(t.fontSize)).div(2).add(2).equal()},"&-fullscreen":{position:"fixed",width:"100vw",height:"100vh",backgroundColor:t.colorBgMask,zIndex:t.zIndexPopupBase,inset:0,display:"flex",alignItems:"center",flexDirection:"column",justifyContent:"center",opacity:0,visibility:"hidden",transition:"all ".concat(t.motionDurationMid),"&-show":{opacity:1,visibility:"visible"},[e]:{["".concat(e,"-dot-holder")]:{color:t.colorWhite},["".concat(e,"-text")]:{color:t.colorTextLightSolid}}},"&-nested-loading":{position:"relative",["> div > ".concat(e)]:{position:"absolute",top:0,insetInlineStart:0,zIndex:4,display:"block",width:"100%",height:"100%",maxHeight:t.contentHeight,["".concat(e,"-dot")]:{position:"absolute",top:"50%",insetInlineStart:"50%",margin:n(t.dotSize).mul(-1).div(2).equal()},["".concat(e,"-text")]:{position:"absolute",top:"50%",width:"100%",textShadow:"0 1px 2px ".concat(t.colorBgContainer)},["&".concat(e,"-show-text ").concat(e,"-dot")]:{marginTop:n(t.dotSize).div(2).mul(-1).sub(10).equal()},"&-sm":{["".concat(e,"-dot")]:{margin:n(t.dotSizeSM).mul(-1).div(2).equal()},["".concat(e,"-text")]:{paddingTop:n(n(t.dotSizeSM).sub(t.fontSize)).div(2).add(2).equal()},["&".concat(e,"-show-text ").concat(e,"-dot")]:{marginTop:n(t.dotSizeSM).div(2).mul(-1).sub(10).equal()}},"&-lg":{["".concat(e,"-dot")]:{margin:n(t.dotSizeLG).mul(-1).div(2).equal()},["".concat(e,"-text")]:{paddingTop:n(n(t.dotSizeLG).sub(t.fontSize)).div(2).add(2).equal()},["&".concat(e,"-show-text ").concat(e,"-dot")]:{marginTop:n(t.dotSizeLG).div(2).mul(-1).sub(10).equal()}}},["".concat(e,"-container")]:{position:"relative",transition:"opacity ".concat(t.motionDurationSlow),"&::after":{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,zIndex:10,width:"100%",height:"100%",background:t.colorBgContainer,opacity:0,transition:"all ".concat(t.motionDurationSlow),content:'""',pointerEvents:"none"}},["".concat(e,"-blur")]:{clear:"both",opacity:.5,userSelect:"none",pointerEvents:"none","&::after":{opacity:.4,pointerEvents:"auto"}}},"&-tip":{color:t.spinDotDefault},["".concat(e,"-dot-holder")]:{width:"1em",height:"1em",fontSize:t.dotSize,display:"inline-block",transition:"transform ".concat(t.motionDurationSlow," ease, opacity ").concat(t.motionDurationSlow," ease"),transformOrigin:"50% 50%",lineHeight:1,color:t.colorPrimary,"&-hidden":{transform:"scale(0.3)",opacity:0}},["".concat(e,"-dot-progress")]:{position:"absolute",inset:0},["".concat(e,"-dot")]:{position:"relative",display:"inline-block",fontSize:t.dotSize,width:"1em",height:"1em","&-item":{position:"absolute",display:"block",width:n(t.dotSize).sub(n(t.marginXXS).div(2)).div(2).equal(),height:n(t.dotSize).sub(n(t.marginXXS).div(2)).div(2).equal(),background:"currentColor",borderRadius:"100%",transform:"scale(0.75)",transformOrigin:"50% 50%",opacity:.3,animationName:b,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear",animationDirection:"alternate","&:nth-child(1)":{top:0,insetInlineStart:0,animationDelay:"0s"},"&:nth-child(2)":{top:0,insetInlineEnd:0,animationDelay:"0.4s"},"&:nth-child(3)":{insetInlineEnd:0,bottom:0,animationDelay:"0.8s"},"&:nth-child(4)":{bottom:0,insetInlineStart:0,animationDelay:"1.2s"}},"&-spin":{transform:"rotate(45deg)",animationName:y,animationDuration:"1.2s",animationIterationCount:"infinite",animationTimingFunction:"linear"},"&-circle":{strokeLinecap:"round",transition:["stroke-dashoffset","stroke-dasharray","stroke","stroke-width","opacity"].map(e=>"".concat(e," ").concat(t.motionDurationSlow," ease")).join(","),fillOpacity:0,stroke:"currentcolor"},"&-circle-bg":{stroke:t.colorFillSecondary}},["&-sm ".concat(e,"-dot")]:{"&, &-holder":{fontSize:t.dotSizeSM}},["&-sm ".concat(e,"-dot-holder")]:{i:{width:n(n(t.dotSizeSM).sub(n(t.marginXXS).div(2))).div(2).equal(),height:n(n(t.dotSizeSM).sub(n(t.marginXXS).div(2))).div(2).equal()}},["&-lg ".concat(e,"-dot")]:{"&, &-holder":{fontSize:t.dotSizeLG}},["&-lg ".concat(e,"-dot-holder")]:{i:{width:n(n(t.dotSizeLG).sub(t.marginXXS)).div(2).equal(),height:n(n(t.dotSizeLG).sub(t.marginXXS)).div(2).equal()}},["&".concat(e,"-show-text ").concat(e,"-text")]:{display:"block"}})}};var x=(0,f.I$)("Spin",t=>w((0,S.IX)(t,{spinDotDefault:t.colorTextDescription})),t=>{let{controlHeightLG:e,controlHeight:n}=t;return{contentHeight:400,dotSize:e/2,dotSizeSM:.35*e,dotSizeLG:n}});let z=[[30,.05],[70,.03],[96,.01]];var E=function(t,e){var n={};for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&0>e.indexOf(o)&&(n[o]=t[o]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,o=Object.getOwnPropertySymbols(t);ie.indexOf(o[i])&&Object.prototype.propertyIsEnumerable.call(t,o[i])&&(n[o[i]]=t[o[i]]);return n};let k=t=>{var e;let{prefixCls:n,spinning:a=!0,delay:l=0,className:s,rootClassName:d,size:u="default",tip:m,wrapperClassName:p,style:v,children:g,fullscreen:f=!1,indicator:S,percent:b}=t,y=E(t,["prefixCls","spinning","delay","className","rootClassName","size","tip","wrapperClassName","style","children","fullscreen","indicator","percent"]),{getPrefixCls:w,direction:k,className:D,style:N,indicator:I}=(0,r.dj)("spin"),C=w("spin",n),[O,M,q]=x(C),[T,j]=i.useState(()=>a&&(!a||!l||!!Number.isNaN(Number(l)))),X=function(t,e){let[n,o]=i.useState(0),a=i.useRef(null),c="auto"===e;return i.useEffect(()=>(c&&t&&(o(0),a.current=setInterval(()=>{o(t=>{let e=100-t;for(let n=0;n{a.current&&(clearInterval(a.current),a.current=null)}),[c,t]),c?n:e}(T,b);i.useEffect(()=>{if(a){var t;let e=function(t,e,n){var o,i=n||{},a=i.noTrailing,c=void 0!==a&&a,r=i.noLeading,l=void 0!==r&&r,s=i.debounceMode,d=void 0===s?void 0:s,u=!1,m=0;function p(){o&&clearTimeout(o)}function h(){for(var n=arguments.length,i=Array(n),a=0;at?l?(m=Date.now(),c||(o=setTimeout(d?v:h,t))):h():!0!==c&&(o=setTimeout(d?v:h,void 0===d?t-s:t)))}return h.cancel=function(t){var e=(t||{}).upcomingOnly;p(),u=!(void 0!==e&&e)},h}(l,()=>{j(!0)},{debounceMode:!1!==(void 0!==(t=({}).atBegin)&&t)});return e(),()=>{var t;null===(t=null==e?void 0:e.cancel)||void 0===t||t.call(e)}}j(!1)},[l,a]);let L=i.useMemo(()=>void 0!==g&&!f,[g,f]),G=c()(C,D,{["".concat(C,"-sm")]:"small"===u,["".concat(C,"-lg")]:"large"===u,["".concat(C,"-spinning")]:T,["".concat(C,"-show-text")]:!!m,["".concat(C,"-rtl")]:"rtl"===k},s,!f&&d,M,q),P=c()("".concat(C,"-container"),{["".concat(C,"-blur")]:T}),B=null!==(e=null!=S?S:I)&&void 0!==e?e:o,F=Object.assign(Object.assign({},N),v),H=i.createElement("div",Object.assign({},y,{style:F,className:G,"aria-live":"polite","aria-busy":T}),i.createElement(h,{prefixCls:C,indicator:B,percent:X}),m&&(L||f)?i.createElement("div",{className:"".concat(C,"-text")},m):null);return O(L?i.createElement("div",Object.assign({},y,{className:c()("".concat(C,"-nested-loading"),p,M,q)}),T&&i.createElement("div",{key:"loading"},H),i.createElement("div",{className:P,key:"container"},g)):f?i.createElement("div",{className:c()("".concat(C,"-fullscreen"),{["".concat(C,"-fullscreen-show")]:T},d,M,q)},H):H)};k.setDefaultIndicator=t=>{o=t};var D=k}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1442-529c645297e48128.js b/litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js similarity index 64% rename from litellm/proxy/_experimental/out/_next/static/chunks/1442-529c645297e48128.js rename to litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js index c7651197616..3c42e191d4b 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1442-529c645297e48128.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1442],{42698:function(e,t,n){n.d(t,{Z:function(){return i}});var r=n(2265),o=n(7084);n(13241);let i=(0,r.createContext)(o.fr.Blue)},64016:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(0)},8710:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(void 0)},33232:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},71049:function(e,t,n){n.d(t,{F:function(){return D}});var r,o=n(2265);let i="undefined"!=typeof document?o.useLayoutEffect:()=>{},u=null!==(r=o.useInsertionEffect)&&void 0!==r?r:i;function a(e){return e.nativeEvent=e,e.isDefaultPrevented=()=>e.defaultPrevented,e.isPropagationStopped=()=>e.cancelBubble,e.persist=()=>{},e}function l(e){let t=(0,o.useRef)({isFocused:!1,observer:null});i(()=>{let e=t.current;return()=>{e.observer&&(e.observer.disconnect(),e.observer=null)}},[]);let n=function(e){let t=(0,o.useRef)(null);return u(()=>{t.current=e},[e]),(0,o.useCallback)((...e)=>{let n=t.current;return null==n?void 0:n(...e)},[])}(t=>{null==e||e(t)});return(0,o.useCallback)(e=>{if(e.target instanceof HTMLButtonElement||e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement){t.current.isFocused=!0;let r=e.target;r.addEventListener("focusout",e=>{t.current.isFocused=!1,r.disabled&&n(a(e)),t.current.observer&&(t.current.observer.disconnect(),t.current.observer=null)},{once:!0}),t.current.observer=new MutationObserver(()=>{if(t.current.isFocused&&r.disabled){var e;null===(e=t.current.observer)||void 0===e||e.disconnect();let n=r===document.activeElement?null:document.activeElement;r.dispatchEvent(new FocusEvent("blur",{relatedTarget:n})),r.dispatchEvent(new FocusEvent("focusout",{bubbles:!0,relatedTarget:n}))}}),t.current.observer.observe(r,{attributes:!0,attributeFilter:["disabled"]})}},[n])}function c(e){var t;if("undefined"==typeof window||null==window.navigator)return!1;let n=null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.brands;return Array.isArray(n)&&n.some(t=>e.test(t.brand))||e.test(window.navigator.userAgent)}function s(e){var t;return"undefined"!=typeof window&&null!=window.navigator&&e.test((null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.platform)||window.navigator.platform)}function d(e){let t=null;return()=>(null==t&&(t=e()),t)}let f=d(function(){return s(/^Mac/i)}),v=d(function(){return s(/^iPhone/i)}),p=d(function(){return s(/^iPad/i)||f()&&navigator.maxTouchPoints>1}),g=d(function(){return v()||p()});d(function(){return f()||g()}),d(function(){return c(/AppleWebKit/i)&&!m()});let m=d(function(){return c(/Chrome/i)}),h=d(function(){return c(/Android/i)});d(function(){return c(/Firefox/i)});var y=n(18064);let b=null,E=new Set,w=new Map,T=!1,A=!1,F={Tab:!0,Escape:!0};function L(e,t){for(let n of E)n(e,t)}function k(e){T=!0,e.metaKey||!f()&&e.altKey||e.ctrlKey||"Control"===e.key||"Shift"===e.key||"Meta"===e.key||(b="keyboard",L("keyboard",e))}function N(e){b="pointer",("mousedown"===e.type||"pointerdown"===e.type)&&(T=!0,L("pointer",e))}function P(e){(""===e.pointerType&&e.isTrusted||(h()&&e.pointerType?"click"===e.type&&1===e.buttons:0===e.detail&&!e.pointerType))&&(T=!0,b="virtual")}function O(e){e.target!==window&&e.target!==document&&e.isTrusted&&(T||A||(b="virtual",L("virtual",e)),T=!1,A=!1)}function S(){T=!1,A=!0}function M(e){if("undefined"==typeof window||"undefined"==typeof document||w.get((0,y.kR)(e)))return;let t=(0,y.kR)(e),n=(0,y.r3)(e),r=t.HTMLElement.prototype.focus;t.HTMLElement.prototype.focus=function(){T=!0,r.apply(this,arguments)},n.addEventListener("keydown",k,!0),n.addEventListener("keyup",k,!0),n.addEventListener("click",P,!0),t.addEventListener("focus",O,!0),t.addEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(n.addEventListener("pointerdown",N,!0),n.addEventListener("pointermove",N,!0),n.addEventListener("pointerup",N,!0)),t.addEventListener("beforeunload",()=>{C(e)},{once:!0}),w.set(t,{focus:r})}let C=(e,t)=>{let n=(0,y.kR)(e),r=(0,y.r3)(e);t&&r.removeEventListener("DOMContentLoaded",t),w.has(n)&&(n.HTMLElement.prototype.focus=w.get(n).focus,r.removeEventListener("keydown",k,!0),r.removeEventListener("keyup",k,!0),r.removeEventListener("click",P,!0),n.removeEventListener("focus",O,!0),n.removeEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(r.removeEventListener("pointerdown",N,!0),r.removeEventListener("pointermove",N,!0),r.removeEventListener("pointerup",N,!0)),w.delete(n))};function x(){return"pointer"!==b}"undefined"!=typeof document&&function(e){let t;let n=(0,y.r3)(void 0);"loading"!==n.readyState?M(void 0):(t=()=>{M(void 0)},n.addEventListener("DOMContentLoaded",t)),()=>C(e,t)}();let H=new Set(["checkbox","radio","range","color","file","image","button","submit","reset"]);var R=n(26428),j=n(66852);function D(e={}){var t,n,r;let{autoFocus:i=!1,isTextInput:u,within:c}=e,s=(0,o.useRef)({isFocused:!1,isFocusVisible:i||x()}),[d,f]=(0,o.useState)(!1),[v,p]=(0,o.useState)(()=>s.current.isFocused&&s.current.isFocusVisible),g=(0,o.useCallback)(()=>p(s.current.isFocused&&s.current.isFocusVisible),[]),m=(0,o.useCallback)(e=>{s.current.isFocused=e,f(e),g()},[g]);t=e=>{s.current.isFocusVisible=e,g()},n=[],r={isTextInput:u},M(),(0,o.useEffect)(()=>{let e=(e,n)=>{(function(e,t,n){let r=(0,y.r3)(null==n?void 0:n.target),o="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLInputElement:HTMLInputElement,i="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLTextAreaElement:HTMLTextAreaElement,u="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLElement:HTMLElement,a="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).KeyboardEvent:KeyboardEvent;return!((e=e||r.activeElement instanceof o&&!H.has(r.activeElement.type)||r.activeElement instanceof i||r.activeElement instanceof u&&r.activeElement.isContentEditable)&&"keyboard"===t&&n instanceof a&&!F[n.key])})(!!(null==r?void 0:r.isTextInput),e,n)&&t(x())};return E.add(e),()=>{E.delete(e)}},n);let{focusProps:h}=function(e){let{isDisabled:t,onFocus:n,onBlur:r,onFocusChange:i}=e,u=(0,o.useCallback)(e=>{if(e.target===e.currentTarget)return r&&r(e),i&&i(!1),!0},[r,i]),a=l(u),c=(0,o.useCallback)(e=>{let t=(0,y.r3)(e.target),r=t?(0,R.vY)(t):(0,R.vY)();e.target===e.currentTarget&&r===(0,R.NI)(e.nativeEvent)&&(n&&n(e),i&&i(!0),a(e))},[i,n,a]);return{focusProps:{onFocus:!t&&(n||i||r)?c:void 0,onBlur:!t&&(r||i)?u:void 0}}}({isDisabled:c,onFocusChange:m}),{focusWithinProps:b}=function(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:i}=e,u=(0,o.useRef)({isFocusWithin:!1}),{addGlobalListener:c,removeAllGlobalListeners:s}=(0,j.x)(),d=(0,o.useCallback)(e=>{e.currentTarget.contains(e.target)&&u.current.isFocusWithin&&!e.currentTarget.contains(e.relatedTarget)&&(u.current.isFocusWithin=!1,s(),n&&n(e),i&&i(!1))},[n,i,u,s]),f=l(d),v=(0,o.useCallback)(e=>{if(!e.currentTarget.contains(e.target))return;let t=(0,y.r3)(e.target),n=(0,R.vY)(t);if(!u.current.isFocusWithin&&n===(0,R.NI)(e.nativeEvent)){r&&r(e),i&&i(!0),u.current.isFocusWithin=!0,f(e);let n=e.currentTarget;c(t,"focus",e=>{if(u.current.isFocusWithin&&!(0,R.bE)(n,e.target)){let r=new t.defaultView.FocusEvent("blur",{relatedTarget:e.target});Object.defineProperty(r,"target",{value:n}),Object.defineProperty(r,"currentTarget",{value:n}),d(a(r))}},{capture:!0})}},[r,i,f,c,d]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:v,onBlur:d}}}({isDisabled:!c,onFocusWithinChange:m});return{isFocused:d,isFocusVisible:v,focusProps:c?b:h}}},11323:function(e,t,n){n.d(t,{X:function(){return d}});var r=n(66852),o=n(18064),i=n(26428),u=n(2265);let a=!1,l=0;function c(e){"touch"===e.pointerType&&(a=!0,setTimeout(()=>{a=!1},50))}function s(){if("undefined"!=typeof document)return 0===l&&"undefined"!=typeof PointerEvent&&document.addEventListener("pointerup",c),l++,()=>{--l>0||"undefined"==typeof PointerEvent||document.removeEventListener("pointerup",c)}}function d(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:l,isDisabled:c}=e,[d,f]=(0,u.useState)(!1),v=(0,u.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:"",target:null}).current;(0,u.useEffect)(s,[]);let{addGlobalListener:p,removeAllGlobalListeners:g}=(0,r.x)(),{hoverProps:m,triggerHoverEnd:h}=(0,u.useMemo)(()=>{let e=(e,u)=>{if(v.pointerType=u,c||"touch"===u||v.isHovered||!e.currentTarget.contains(e.target))return;v.isHovered=!0;let a=e.currentTarget;v.target=a,p((0,o.r3)(e.target),"pointerover",e=>{v.isHovered&&v.target&&!(0,i.bE)(v.target,e.target)&&r(e,e.pointerType)},{capture:!0}),t&&t({type:"hoverstart",target:a,pointerType:u}),n&&n(!0),f(!0)},r=(e,t)=>{let r=v.target;v.pointerType="",v.target=null,"touch"!==t&&v.isHovered&&r&&(v.isHovered=!1,g(),l&&l({type:"hoverend",target:r,pointerType:t}),n&&n(!1),f(!1))},u={};return"undefined"!=typeof PointerEvent&&(u.onPointerEnter=t=>{a&&"mouse"===t.pointerType||e(t,t.pointerType)},u.onPointerLeave=e=>{!c&&e.currentTarget.contains(e.target)&&r(e,e.pointerType)}),{hoverProps:u,triggerHoverEnd:r}},[t,n,l,c,v,p,g]);return(0,u.useEffect)(()=>{c&&h({currentTarget:v.target},v.pointerType)},[c]),{hoverProps:m,isHovered:d}}},26428:function(e,t,n){function r(e,t){return!!t&&!!e&&e.contains(t)}n.d(t,{vY:function(){return o},NI:function(){return i},bE:function(){return r}}),n(18064);let o=(e=document)=>e.activeElement;function i(e){return e.target}},18064:function(e,t,n){n.d(t,{Zq:function(){return i},kR:function(){return o},r3:function(){return r}});let r=e=>{var t;return null!==(t=null==e?void 0:e.ownerDocument)&&void 0!==t?t:document},o=e=>e&&"window"in e&&e.window===e?e:r(e).defaultView||window;function i(e){return null!==e&&"object"==typeof e&&"nodeType"in e&&"number"==typeof e.nodeType&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&"host"in e}},66852:function(e,t,n){n.d(t,{x:function(){return o}});var r=n(2265);function o(){let e=(0,r.useRef)(new Map),t=(0,r.useCallback)((t,n,r,o)=>{let i=(null==o?void 0:o.once)?(...t)=>{e.current.delete(r),r(...t)}:r;e.current.set(r,{type:n,eventTarget:t,fn:i,options:o}),t.addEventListener(n,i,o)},[]),n=(0,r.useCallback)((t,n,r,o)=>{var i;let u=(null===(i=e.current.get(r))||void 0===i?void 0:i.fn)||r;t.removeEventListener(n,u,o),e.current.delete(r)},[]),o=(0,r.useCallback)(()=>{e.current.forEach((e,t)=>{n(e.eventTarget,e.type,t,e.options)})},[n]);return(0,r.useEffect)(()=>o,[o]),{addGlobalListener:t,removeGlobalListener:n,removeAllGlobalListeners:o}}},52724:function(e,t,n){let r;n.d(t,{R:function(){return o}});var o=((r=o||{}).Space=" ",r.Enter="Enter",r.Escape="Escape",r.Backspace="Backspace",r.Delete="Delete",r.ArrowLeft="ArrowLeft",r.ArrowUp="ArrowUp",r.ArrowRight="ArrowRight",r.ArrowDown="ArrowDown",r.Home="Home",r.End="End",r.PageUp="PageUp",r.PageDown="PageDown",r.Tab="Tab",r)},66797:function(e,t,n){n.d(t,{x:function(){return a}});var r=n(2265),o=n(5664),i=n(59456),u=n(93980);function a(){let{disabled:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=(0,r.useRef)(null),[n,a]=(0,r.useState)(!1),l=(0,i.G)(),c=(0,u.z)(()=>{t.current=null,a(!1),l.dispose()}),s=(0,u.z)(e=>{if(l.dispose(),null===t.current){t.current=e.currentTarget,a(!0);{let n=(0,o.r)(e.currentTarget);l.addEventListener(n,"pointerup",c,!1),l.addEventListener(n,"pointermove",e=>{if(t.current){var n,r;let o,i;a((o=e.width/2,i=e.height/2,n={top:e.clientY-i,right:e.clientX+o,bottom:e.clientY+i,left:e.clientX-o},r=t.current.getBoundingClientRect(),!(!n||!r||n.rightr.right||n.bottomr.bottom)))}},!1),l.addEventListener(n,"pointercancel",c,!1)}}});return{pressed:n,pressProps:e?{}:{onPointerDown:s,onPointerUp:c,onClick:c}}}},59456:function(e,t,n){n.d(t,{G:function(){return i}});var r=n(2265),o=n(36933);function i(){let[e]=(0,r.useState)(o.k);return(0,r.useEffect)(()=>()=>e.dispose(),[e]),e}},93980:function(e,t,n){n.d(t,{z:function(){return i}});var r=n(2265),o=n(43507);let i=function(e){let t=(0,o.E)(e);return r.useCallback(function(){for(var e=arguments.length,n=Array(e),r=0;r{o.O.isServer?(0,r.useEffect)(e,t):(0,r.useLayoutEffect)(e,t)}},43507:function(e,t,n){n.d(t,{E:function(){return i}});var r=n(2265),o=n(73389);function i(e){let t=(0,r.useRef)(e);return(0,o.e)(()=>{t.current=e},[e]),t}},65573:function(e,t,n){n.d(t,{f:function(){return o}});var r=n(2265);function o(e,t){return(0,r.useMemo)(()=>{var n;if(e.type)return e.type;let r=null!=(n=e.as)?n:"button";if("string"==typeof r&&"button"===r.toLowerCase()||(null==t?void 0:t.tagName)==="BUTTON"&&!t.hasAttribute("type"))return"button"},[e.type,e.as,t])}},67561:function(e,t,n){n.d(t,{T:function(){return a},h:function(){return u}});var r=n(2265),o=n(93980);let i=Symbol();function u(e){let t=!(arguments.length>1)||void 0===arguments[1]||arguments[1];return Object.assign(e,{[i]:t})}function a(){for(var e=arguments.length,t=Array(e),n=0;n{u.current=t},[t]);let a=(0,o.z)(e=>{for(let t of u.current)null!=t&&("function"==typeof t?t(e):t.current=e)});return t.every(e=>null==e||(null==e?void 0:e[i]))?void 0:a}},65639:function(e,t,n){let r;n.d(t,{_:function(){return u},x:function(){return i}});var o=n(38929),i=((r=i||{})[r.None=1]="None",r[r.Focusable=2]="Focusable",r[r.Hidden=4]="Hidden",r);let u=(0,o.yV)(function(e,t){var n;let{features:r=1,...i}=e,u={ref:t,"aria-hidden":(2&r)==2||(null!=(n=i["aria-hidden"])?n:void 0),hidden:(4&r)==4||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(4&r)==4&&(2&r)!=2&&{display:"none"}}};return(0,o.L6)()({ourProps:u,theirProps:i,slot:{},defaultTag:"span",name:"Hidden"})})},95504:function(e,t,n){n.d(t,{A:function(){return r}});function r(){for(var e=arguments.length,t=Array(e),n=0;n"string"==typeof e?e.split(" "):[]))).filter(Boolean).join(" ")}},36933:function(e,t,n){n.d(t,{k:function(){return function e(){let t=[],n={addEventListener:(e,t,r,o)=>(e.addEventListener(t,r,o),n.add(()=>e.removeEventListener(t,r,o))),requestAnimationFrame(){for(var e=arguments.length,t=Array(e),r=0;rcancelAnimationFrame(o))},nextFrame(){for(var e=arguments.length,t=Array(e),r=0;rn.requestAnimationFrame(...t))},setTimeout(){for(var e=arguments.length,t=Array(e),r=0;rclearTimeout(o))},microTask(){for(var e=arguments.length,t=Array(e),o=0;o{i.current&&t[0]()}),n.add(()=>{i.current=!1})},style(e,t,n){let r=e.style.getPropertyValue(t);return Object.assign(e.style,{[t]:n}),this.add(()=>{Object.assign(e.style,{[t]:r})})},group(t){let n=e();return t(n),this.add(()=>n.dispose())},add:e=>(t.includes(e)||t.push(e),()=>{let n=t.indexOf(e);if(n>=0)for(let e of t.splice(n,1))e()}),dispose(){for(let e of t.splice(0))e()}};return n}}});var r=n(24310)},60415:function(e,t,n){n.d(t,{O:function(){return a}});var r=Object.defineProperty,o=(e,t,n)=>t in e?r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,i=(e,t,n)=>(o(e,"symbol"!=typeof t?t+"":t,n),n);class u{set(e){this.current!==e&&(this.handoffState="pending",this.currentId=0,this.current=e)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return"server"===this.current}get isClient(){return"client"===this.current}detect(){return"undefined"==typeof window||"undefined"==typeof document?"server":"client"}handoff(){"pending"===this.handoffState&&(this.handoffState="complete")}get isHandoffComplete(){return"complete"===this.handoffState}constructor(){i(this,"current",this.detect()),i(this,"handoffState","pending"),i(this,"currentId",0)}}let a=new u},93698:function(e,t,n){let r,o,i,u,a;n.d(t,{EO:function(){return E},GO:function(){return g},TO:function(){return f},fE:function(){return v},jA:function(){return w},sP:function(){return h},tJ:function(){return m},y:function(){return s},z2:function(){return b}});var l=n(72468),c=n(5664);let s=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(","),d=["[data-autofocus]"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(",");var f=((r=f||{})[r.First=1]="First",r[r.Previous=2]="Previous",r[r.Next=4]="Next",r[r.Last=8]="Last",r[r.WrapAround=16]="WrapAround",r[r.NoScroll=32]="NoScroll",r[r.AutoFocus=64]="AutoFocus",r),v=((o=v||{})[o.Error=0]="Error",o[o.Overflow=1]="Overflow",o[o.Success=2]="Success",o[o.Underflow=3]="Underflow",o),p=((i=p||{})[i.Previous=-1]="Previous",i[i.Next=1]="Next",i);function g(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(s)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}var m=((u=m||{})[u.Strict=0]="Strict",u[u.Loose=1]="Loose",u);function h(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return e!==(null==(t=(0,c.r)(e))?void 0:t.body)&&(0,l.E)(n,{0:()=>e.matches(s),1(){let t=e;for(;null!==t;){if(t.matches(s))return!0;t=t.parentElement}return!1}})}var y=((a=y||{})[a.Keyboard=0]="Keyboard",a[a.Mouse=1]="Mouse",a);function b(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e=>e;return e.slice().sort((e,n)=>{let r=t(e),o=t(n);if(null===r||null===o)return 0;let i=r.compareDocumentPosition(o);return i&Node.DOCUMENT_POSITION_FOLLOWING?-1:i&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function E(e,t){return w(g(),t,{relativeTo:e})}function w(e,t){var n,r,o;let{sorted:i=!0,relativeTo:u=null,skipElements:a=[]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},l=Array.isArray(e)?e.length>0?e[0].ownerDocument:document:e.ownerDocument,c=Array.isArray(e)?i?b(e):e:64&t?function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(d)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}(e):g(e);a.length>0&&c.length>1&&(c=c.filter(e=>!a.some(t=>null!=t&&"current"in t?(null==t?void 0:t.current)===e:t===e))),u=null!=u?u:l.activeElement;let s=(()=>{if(5&t)return 1;if(10&t)return -1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),f=(()=>{if(1&t)return 0;if(2&t)return Math.max(0,c.indexOf(u))-1;if(4&t)return Math.max(0,c.indexOf(u))+1;if(8&t)return c.length-1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),v=32&t?{preventScroll:!0}:{},p=0,m=c.length,h;do{if(p>=m||p+m<=0)return 0;let e=f+p;if(16&t)e=(e+m)%m;else{if(e<0)return 3;if(e>=m)return 1}null==(h=c[e])||h.focus(v),p+=s}while(h!==l.activeElement);return 6&t&&null!=(o=null==(r=null==(n=h)?void 0:n.matches)?void 0:r.call(n,"textarea,input"))&&o&&h.select(),2}"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("keydown",e=>{e.metaKey||e.altKey||e.ctrlKey||(document.documentElement.dataset.headlessuiFocusVisible="")},!0),document.addEventListener("click",e=>{1===e.detail?delete document.documentElement.dataset.headlessuiFocusVisible:0===e.detail&&(document.documentElement.dataset.headlessuiFocusVisible="")},!0))},72468:function(e,t,n){n.d(t,{E:function(){return r}});function r(e,t){for(var n=arguments.length,o=Array(n>2?n-2:0),i=2;i'"'.concat(e,'"')).join(", "),"."));throw Error.captureStackTrace&&Error.captureStackTrace(u,r),u}},24310:function(e,t,n){n.d(t,{Y:function(){return r}});function r(e){"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e).catch(e=>setTimeout(()=>{throw e}))}},5664:function(e,t,n){n.d(t,{r:function(){return o}});var r=n(60415);function o(e){return r.O.isServer?null:e instanceof Node?e.ownerDocument:null!=e&&e.hasOwnProperty("current")&&e.current instanceof Node?e.current.ownerDocument:document}},38929:function(e,t,n){let r,o;n.d(t,{L6:function(){return s},VN:function(){return l},dG:function(){return p},l4:function(){return c},oA:function(){return m},yV:function(){return g}});var i=n(2265),u=n(95504),a=n(72468),l=((r=l||{})[r.None=0]="None",r[r.RenderStrategy=1]="RenderStrategy",r[r.Static=2]="Static",r),c=((o=c||{})[o.Unmount=0]="Unmount",o[o.Hidden=1]="Hidden",o);function s(){let e,t;let n=(e=(0,i.useRef)([]),t=(0,i.useCallback)(t=>{for(let n of e.current)null!=n&&("function"==typeof n?n(t):n.current=t)},[]),function(){for(var n=arguments.length,r=Array(n),o=0;onull==e))return e.current=r,t});return(0,i.useCallback)(e=>(function(e){let{ourProps:t,theirProps:n,slot:r,defaultTag:o,features:i,visible:u=!0,name:l,mergeRefs:c}=e;c=null!=c?c:f;let s=v(n,t);if(u)return d(s,r,o,l,c);let p=null!=i?i:0;if(2&p){let{static:e=!1,...t}=s;if(e)return d(t,r,o,l,c)}if(1&p){let{unmount:e=!0,...t}=s;return(0,a.E)(e?0:1,{0:()=>null,1:()=>d({...t,hidden:!0,style:{display:"none"}},r,o,l,c)})}return d(s,r,o,l,c)})({mergeRefs:n,...e}),[n])}function d(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0,{as:a=n,children:l,refName:c="ref",...s}=h(e,["unmount","static"]),d=void 0!==e.ref?{[c]:e.ref}:{},f="function"==typeof l?l(t):l;"className"in s&&s.className&&"function"==typeof s.className&&(s.className=s.className(t)),s["aria-labelledby"]&&s["aria-labelledby"]===s.id&&(s["aria-labelledby"]=void 0);let p={};if(t){let e=!1,n=[];for(let[r,o]of Object.entries(t))"boolean"==typeof o&&(e=!0),!0===o&&n.push(r.replace(/([A-Z])/g,e=>"-".concat(e.toLowerCase())));if(e)for(let e of(p["data-headlessui-state"]=n.join(" "),n))p["data-".concat(e)]=""}if(a===i.Fragment&&(Object.keys(m(s)).length>0||Object.keys(m(p)).length>0)){if(!(0,i.isValidElement)(f)||Array.isArray(f)&&f.length>1){if(Object.keys(m(s)).length>0)throw Error(['Passing props on "Fragment"!',"","The current component <".concat(r,' /> is rendering a "Fragment".'),"However we need to passthrough the following props:",Object.keys(m(s)).concat(Object.keys(m(p))).map(e=>" - ".concat(e)).join("\n"),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(e=>" - ".concat(e)).join("\n")].join("\n"))}else{let e=f.props,t=null==e?void 0:e.className,n="function"==typeof t?function(){for(var e=arguments.length,n=Array(e),r=0;r="19"?f.props.ref:f.ref,d.ref)},n?{className:n}:{}))}}return(0,i.createElement)(a,Object.assign({},h(s,["ref"]),a!==i.Fragment&&d,a!==i.Fragment&&p),f)}function f(){for(var e=arguments.length,t=Array(e),n=0;nnull==e)?void 0:e=>{for(let n of t)null!=n&&("function"==typeof n?n(e):n.current=e)}}function v(){for(var e=arguments.length,t=Array(e),n=0;n{var t;return null==(t=null==e?void 0:e.preventDefault)?void 0:t.call(e)}]);for(let e in o)Object.assign(r,{[e](t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:[],n=Object.assign({},e);for(let e of t)e in n&&delete n[e];return n}}}]); \ No newline at end of file +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1442],{42698:function(e,t,n){n.d(t,{Z:function(){return i}});var r=n(2265),o=n(7084);n(13241);let i=(0,r.createContext)(o.fr.Blue)},64016:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(0)},8710:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(void 0)},33232:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},71049:function(e,t,n){n.d(t,{F:function(){return D}});var r,o=n(2265);let i="undefined"!=typeof document?o.useLayoutEffect:()=>{},u=null!==(r=o.useInsertionEffect)&&void 0!==r?r:i;function a(e){return e.nativeEvent=e,e.isDefaultPrevented=()=>e.defaultPrevented,e.isPropagationStopped=()=>e.cancelBubble,e.persist=()=>{},e}function l(e){let t=(0,o.useRef)({isFocused:!1,observer:null});i(()=>{let e=t.current;return()=>{e.observer&&(e.observer.disconnect(),e.observer=null)}},[]);let n=function(e){let t=(0,o.useRef)(null);return u(()=>{t.current=e},[e]),(0,o.useCallback)((...e)=>{let n=t.current;return null==n?void 0:n(...e)},[])}(t=>{null==e||e(t)});return(0,o.useCallback)(e=>{if(e.target instanceof HTMLButtonElement||e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement){t.current.isFocused=!0;let r=e.target;r.addEventListener("focusout",e=>{t.current.isFocused=!1,r.disabled&&n(a(e)),t.current.observer&&(t.current.observer.disconnect(),t.current.observer=null)},{once:!0}),t.current.observer=new MutationObserver(()=>{if(t.current.isFocused&&r.disabled){var e;null===(e=t.current.observer)||void 0===e||e.disconnect();let n=r===document.activeElement?null:document.activeElement;r.dispatchEvent(new FocusEvent("blur",{relatedTarget:n})),r.dispatchEvent(new FocusEvent("focusout",{bubbles:!0,relatedTarget:n}))}}),t.current.observer.observe(r,{attributes:!0,attributeFilter:["disabled"]})}},[n])}function c(e){var t;if("undefined"==typeof window||null==window.navigator)return!1;let n=null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.brands;return Array.isArray(n)&&n.some(t=>e.test(t.brand))||e.test(window.navigator.userAgent)}function s(e){var t;return"undefined"!=typeof window&&null!=window.navigator&&e.test((null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.platform)||window.navigator.platform)}function d(e){let t=null;return()=>(null==t&&(t=e()),t)}let f=d(function(){return s(/^Mac/i)}),v=d(function(){return s(/^iPhone/i)}),p=d(function(){return s(/^iPad/i)||f()&&navigator.maxTouchPoints>1}),g=d(function(){return v()||p()});d(function(){return f()||g()}),d(function(){return c(/AppleWebKit/i)&&!m()});let m=d(function(){return c(/Chrome/i)}),h=d(function(){return c(/Android/i)});d(function(){return c(/Firefox/i)});var y=n(18064);let b=null,E=new Set,w=new Map,T=!1,A=!1,F={Tab:!0,Escape:!0};function L(e,t){for(let n of E)n(e,t)}function k(e){T=!0,e.metaKey||!f()&&e.altKey||e.ctrlKey||"Control"===e.key||"Shift"===e.key||"Meta"===e.key||(b="keyboard",L("keyboard",e))}function N(e){b="pointer",("mousedown"===e.type||"pointerdown"===e.type)&&(T=!0,L("pointer",e))}function P(e){(""===e.pointerType&&e.isTrusted||(h()&&e.pointerType?"click"===e.type&&1===e.buttons:0===e.detail&&!e.pointerType))&&(T=!0,b="virtual")}function O(e){e.target!==window&&e.target!==document&&e.isTrusted&&(T||A||(b="virtual",L("virtual",e)),T=!1,A=!1)}function S(){T=!1,A=!0}function M(e){if("undefined"==typeof window||"undefined"==typeof document||w.get((0,y.kR)(e)))return;let t=(0,y.kR)(e),n=(0,y.r3)(e),r=t.HTMLElement.prototype.focus;t.HTMLElement.prototype.focus=function(){T=!0,r.apply(this,arguments)},n.addEventListener("keydown",k,!0),n.addEventListener("keyup",k,!0),n.addEventListener("click",P,!0),t.addEventListener("focus",O,!0),t.addEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(n.addEventListener("pointerdown",N,!0),n.addEventListener("pointermove",N,!0),n.addEventListener("pointerup",N,!0)),t.addEventListener("beforeunload",()=>{C(e)},{once:!0}),w.set(t,{focus:r})}let C=(e,t)=>{let n=(0,y.kR)(e),r=(0,y.r3)(e);t&&r.removeEventListener("DOMContentLoaded",t),w.has(n)&&(n.HTMLElement.prototype.focus=w.get(n).focus,r.removeEventListener("keydown",k,!0),r.removeEventListener("keyup",k,!0),r.removeEventListener("click",P,!0),n.removeEventListener("focus",O,!0),n.removeEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(r.removeEventListener("pointerdown",N,!0),r.removeEventListener("pointermove",N,!0),r.removeEventListener("pointerup",N,!0)),w.delete(n))};function x(){return"pointer"!==b}"undefined"!=typeof document&&function(e){let t;let n=(0,y.r3)(void 0);"loading"!==n.readyState?M(void 0):(t=()=>{M(void 0)},n.addEventListener("DOMContentLoaded",t)),()=>C(e,t)}();let H=new Set(["checkbox","radio","range","color","file","image","button","submit","reset"]);var R=n(26428),j=n(66852);function D(e={}){var t,n,r;let{autoFocus:i=!1,isTextInput:u,within:c}=e,s=(0,o.useRef)({isFocused:!1,isFocusVisible:i||x()}),[d,f]=(0,o.useState)(!1),[v,p]=(0,o.useState)(()=>s.current.isFocused&&s.current.isFocusVisible),g=(0,o.useCallback)(()=>p(s.current.isFocused&&s.current.isFocusVisible),[]),m=(0,o.useCallback)(e=>{s.current.isFocused=e,f(e),g()},[g]);t=e=>{s.current.isFocusVisible=e,g()},n=[],r={isTextInput:u},M(),(0,o.useEffect)(()=>{let e=(e,n)=>{(function(e,t,n){let r=(0,y.r3)(null==n?void 0:n.target),o="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLInputElement:HTMLInputElement,i="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLTextAreaElement:HTMLTextAreaElement,u="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLElement:HTMLElement,a="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).KeyboardEvent:KeyboardEvent;return!((e=e||r.activeElement instanceof o&&!H.has(r.activeElement.type)||r.activeElement instanceof i||r.activeElement instanceof u&&r.activeElement.isContentEditable)&&"keyboard"===t&&n instanceof a&&!F[n.key])})(!!(null==r?void 0:r.isTextInput),e,n)&&t(x())};return E.add(e),()=>{E.delete(e)}},n);let{focusProps:h}=function(e){let{isDisabled:t,onFocus:n,onBlur:r,onFocusChange:i}=e,u=(0,o.useCallback)(e=>{if(e.target===e.currentTarget)return r&&r(e),i&&i(!1),!0},[r,i]),a=l(u),c=(0,o.useCallback)(e=>{let t=(0,y.r3)(e.target),r=t?(0,R.vY)(t):(0,R.vY)();e.target===e.currentTarget&&r===(0,R.NI)(e.nativeEvent)&&(n&&n(e),i&&i(!0),a(e))},[i,n,a]);return{focusProps:{onFocus:!t&&(n||i||r)?c:void 0,onBlur:!t&&(r||i)?u:void 0}}}({isDisabled:c,onFocusChange:m}),{focusWithinProps:b}=function(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:i}=e,u=(0,o.useRef)({isFocusWithin:!1}),{addGlobalListener:c,removeAllGlobalListeners:s}=(0,j.x)(),d=(0,o.useCallback)(e=>{e.currentTarget.contains(e.target)&&u.current.isFocusWithin&&!e.currentTarget.contains(e.relatedTarget)&&(u.current.isFocusWithin=!1,s(),n&&n(e),i&&i(!1))},[n,i,u,s]),f=l(d),v=(0,o.useCallback)(e=>{if(!e.currentTarget.contains(e.target))return;let t=(0,y.r3)(e.target),n=(0,R.vY)(t);if(!u.current.isFocusWithin&&n===(0,R.NI)(e.nativeEvent)){r&&r(e),i&&i(!0),u.current.isFocusWithin=!0,f(e);let n=e.currentTarget;c(t,"focus",e=>{if(u.current.isFocusWithin&&!(0,R.bE)(n,e.target)){let r=new t.defaultView.FocusEvent("blur",{relatedTarget:e.target});Object.defineProperty(r,"target",{value:n}),Object.defineProperty(r,"currentTarget",{value:n}),d(a(r))}},{capture:!0})}},[r,i,f,c,d]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:v,onBlur:d}}}({isDisabled:!c,onFocusWithinChange:m});return{isFocused:d,isFocusVisible:v,focusProps:c?b:h}}},11323:function(e,t,n){n.d(t,{X:function(){return d}});var r=n(66852),o=n(18064),i=n(26428),u=n(2265);let a=!1,l=0;function c(e){"touch"===e.pointerType&&(a=!0,setTimeout(()=>{a=!1},50))}function s(){if("undefined"!=typeof document)return 0===l&&"undefined"!=typeof PointerEvent&&document.addEventListener("pointerup",c),l++,()=>{--l>0||"undefined"==typeof PointerEvent||document.removeEventListener("pointerup",c)}}function d(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:l,isDisabled:c}=e,[d,f]=(0,u.useState)(!1),v=(0,u.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:"",target:null}).current;(0,u.useEffect)(s,[]);let{addGlobalListener:p,removeAllGlobalListeners:g}=(0,r.x)(),{hoverProps:m,triggerHoverEnd:h}=(0,u.useMemo)(()=>{let e=(e,u)=>{if(v.pointerType=u,c||"touch"===u||v.isHovered||!e.currentTarget.contains(e.target))return;v.isHovered=!0;let a=e.currentTarget;v.target=a,p((0,o.r3)(e.target),"pointerover",e=>{v.isHovered&&v.target&&!(0,i.bE)(v.target,e.target)&&r(e,e.pointerType)},{capture:!0}),t&&t({type:"hoverstart",target:a,pointerType:u}),n&&n(!0),f(!0)},r=(e,t)=>{let r=v.target;v.pointerType="",v.target=null,"touch"!==t&&v.isHovered&&r&&(v.isHovered=!1,g(),l&&l({type:"hoverend",target:r,pointerType:t}),n&&n(!1),f(!1))},u={};return"undefined"!=typeof PointerEvent&&(u.onPointerEnter=t=>{a&&"mouse"===t.pointerType||e(t,t.pointerType)},u.onPointerLeave=e=>{!c&&e.currentTarget.contains(e.target)&&r(e,e.pointerType)}),{hoverProps:u,triggerHoverEnd:r}},[t,n,l,c,v,p,g]);return(0,u.useEffect)(()=>{c&&h({currentTarget:v.target},v.pointerType)},[c]),{hoverProps:m,isHovered:d}}},26428:function(e,t,n){function r(e,t){return!!t&&!!e&&e.contains(t)}n.d(t,{vY:function(){return o},NI:function(){return i},bE:function(){return r}}),n(18064);let o=(e=document)=>e.activeElement;function i(e){return e.target}},18064:function(e,t,n){n.d(t,{Zq:function(){return i},kR:function(){return o},r3:function(){return r}});let r=e=>{var t;return null!==(t=null==e?void 0:e.ownerDocument)&&void 0!==t?t:document},o=e=>e&&"window"in e&&e.window===e?e:r(e).defaultView||window;function i(e){return null!==e&&"object"==typeof e&&"nodeType"in e&&"number"==typeof e.nodeType&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&"host"in e}},66852:function(e,t,n){n.d(t,{x:function(){return o}});var r=n(2265);function o(){let e=(0,r.useRef)(new Map),t=(0,r.useCallback)((t,n,r,o)=>{let i=(null==o?void 0:o.once)?(...t)=>{e.current.delete(r),r(...t)}:r;e.current.set(r,{type:n,eventTarget:t,fn:i,options:o}),t.addEventListener(n,i,o)},[]),n=(0,r.useCallback)((t,n,r,o)=>{var i;let u=(null===(i=e.current.get(r))||void 0===i?void 0:i.fn)||r;t.removeEventListener(n,u,o),e.current.delete(r)},[]),o=(0,r.useCallback)(()=>{e.current.forEach((e,t)=>{n(e.eventTarget,e.type,t,e.options)})},[n]);return(0,r.useEffect)(()=>o,[o]),{addGlobalListener:t,removeGlobalListener:n,removeAllGlobalListeners:o}}},52724:function(e,t,n){let r;n.d(t,{R:function(){return o}});var o=((r=o||{}).Space=" ",r.Enter="Enter",r.Escape="Escape",r.Backspace="Backspace",r.Delete="Delete",r.ArrowLeft="ArrowLeft",r.ArrowUp="ArrowUp",r.ArrowRight="ArrowRight",r.ArrowDown="ArrowDown",r.Home="Home",r.End="End",r.PageUp="PageUp",r.PageDown="PageDown",r.Tab="Tab",r)},66797:function(e,t,n){n.d(t,{x:function(){return a}});var r=n(2265),o=n(5664),i=n(59456),u=n(93980);function a(){let{disabled:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=(0,r.useRef)(null),[n,a]=(0,r.useState)(!1),l=(0,i.G)(),c=(0,u.z)(()=>{t.current=null,a(!1),l.dispose()}),s=(0,u.z)(e=>{if(l.dispose(),null===t.current){t.current=e.currentTarget,a(!0);{let n=(0,o.r)(e.currentTarget);l.addEventListener(n,"pointerup",c,!1),l.addEventListener(n,"pointermove",e=>{if(t.current){var n,r;let o,i;a((o=e.width/2,i=e.height/2,n={top:e.clientY-i,right:e.clientX+o,bottom:e.clientY+i,left:e.clientX-o},r=t.current.getBoundingClientRect(),!(!n||!r||n.rightr.right||n.bottomr.bottom)))}},!1),l.addEventListener(n,"pointercancel",c,!1)}}});return{pressed:n,pressProps:e?{}:{onPointerDown:s,onPointerUp:c,onClick:c}}}},59456:function(e,t,n){n.d(t,{G:function(){return i}});var r=n(2265),o=n(36933);function i(){let[e]=(0,r.useState)(o.k);return(0,r.useEffect)(()=>()=>e.dispose(),[e]),e}},93980:function(e,t,n){n.d(t,{z:function(){return i}});var r=n(2265),o=n(43507);let i=function(e){let t=(0,o.E)(e);return r.useCallback(function(){for(var e=arguments.length,n=Array(e),r=0;r{o.O.isServer?(0,r.useEffect)(e,t):(0,r.useLayoutEffect)(e,t)}},43507:function(e,t,n){n.d(t,{E:function(){return i}});var r=n(2265),o=n(73389);function i(e){let t=(0,r.useRef)(e);return(0,o.e)(()=>{t.current=e},[e]),t}},65573:function(e,t,n){n.d(t,{f:function(){return o}});var r=n(2265);function o(e,t){return(0,r.useMemo)(()=>{var n;if(e.type)return e.type;let r=null!=(n=e.as)?n:"button";if("string"==typeof r&&"button"===r.toLowerCase()||(null==t?void 0:t.tagName)==="BUTTON"&&!t.hasAttribute("type"))return"button"},[e.type,e.as,t])}},67561:function(e,t,n){n.d(t,{T:function(){return a},h:function(){return u}});var r=n(2265),o=n(93980);let i=Symbol();function u(e){let t=!(arguments.length>1)||void 0===arguments[1]||arguments[1];return Object.assign(e,{[i]:t})}function a(){for(var e=arguments.length,t=Array(e),n=0;n{u.current=t},[t]);let a=(0,o.z)(e=>{for(let t of u.current)null!=t&&("function"==typeof t?t(e):t.current=e)});return t.every(e=>null==e||(null==e?void 0:e[i]))?void 0:a}},65639:function(e,t,n){let r;n.d(t,{_:function(){return u},x:function(){return i}});var o=n(38929),i=((r=i||{})[r.None=1]="None",r[r.Focusable=2]="Focusable",r[r.Hidden=4]="Hidden",r);let u=(0,o.yV)(function(e,t){var n;let{features:r=1,...i}=e,u={ref:t,"aria-hidden":(2&r)==2||(null!=(n=i["aria-hidden"])?n:void 0),hidden:(4&r)==4||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(4&r)==4&&(2&r)!=2&&{display:"none"}}};return(0,o.L6)()({ourProps:u,theirProps:i,slot:{},defaultTag:"span",name:"Hidden"})})},95504:function(e,t,n){n.d(t,{A:function(){return r}});function r(){for(var e=arguments.length,t=Array(e),n=0;n"string"==typeof e?e.split(" "):[]))).filter(Boolean).join(" ")}},36933:function(e,t,n){n.d(t,{k:function(){return function e(){let t=[],n={addEventListener:(e,t,r,o)=>(e.addEventListener(t,r,o),n.add(()=>e.removeEventListener(t,r,o))),requestAnimationFrame(){for(var e=arguments.length,t=Array(e),r=0;rcancelAnimationFrame(o))},nextFrame(){for(var e=arguments.length,t=Array(e),r=0;rn.requestAnimationFrame(...t))},setTimeout(){for(var e=arguments.length,t=Array(e),r=0;rclearTimeout(o))},microTask(){for(var e=arguments.length,t=Array(e),o=0;o{i.current&&t[0]()}),n.add(()=>{i.current=!1})},style(e,t,n){let r=e.style.getPropertyValue(t);return Object.assign(e.style,{[t]:n}),this.add(()=>{Object.assign(e.style,{[t]:r})})},group(t){let n=e();return t(n),this.add(()=>n.dispose())},add:e=>(t.includes(e)||t.push(e),()=>{let n=t.indexOf(e);if(n>=0)for(let e of t.splice(n,1))e()}),dispose(){for(let e of t.splice(0))e()}};return n}}});var r=n(24310)},60415:function(e,t,n){n.d(t,{O:function(){return a}});var r=Object.defineProperty,o=(e,t,n)=>t in e?r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,i=(e,t,n)=>(o(e,"symbol"!=typeof t?t+"":t,n),n);class u{set(e){this.current!==e&&(this.handoffState="pending",this.currentId=0,this.current=e)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return"server"===this.current}get isClient(){return"client"===this.current}detect(){return"undefined"==typeof window||"undefined"==typeof document?"server":"client"}handoff(){"pending"===this.handoffState&&(this.handoffState="complete")}get isHandoffComplete(){return"complete"===this.handoffState}constructor(){i(this,"current",this.detect()),i(this,"handoffState","pending"),i(this,"currentId",0)}}let a=new u},93698:function(e,t,n){let r,o,i,u,a;n.d(t,{EO:function(){return E},GO:function(){return g},TO:function(){return f},fE:function(){return v},jA:function(){return w},sP:function(){return h},tJ:function(){return m},z2:function(){return b}});var l=n(72468),c=n(5664);let s=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(","),d=["[data-autofocus]"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(",");var f=((r=f||{})[r.First=1]="First",r[r.Previous=2]="Previous",r[r.Next=4]="Next",r[r.Last=8]="Last",r[r.WrapAround=16]="WrapAround",r[r.NoScroll=32]="NoScroll",r[r.AutoFocus=64]="AutoFocus",r),v=((o=v||{})[o.Error=0]="Error",o[o.Overflow=1]="Overflow",o[o.Success=2]="Success",o[o.Underflow=3]="Underflow",o),p=((i=p||{})[i.Previous=-1]="Previous",i[i.Next=1]="Next",i);function g(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(s)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}var m=((u=m||{})[u.Strict=0]="Strict",u[u.Loose=1]="Loose",u);function h(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return e!==(null==(t=(0,c.r)(e))?void 0:t.body)&&(0,l.E)(n,{0:()=>e.matches(s),1(){let t=e;for(;null!==t;){if(t.matches(s))return!0;t=t.parentElement}return!1}})}var y=((a=y||{})[a.Keyboard=0]="Keyboard",a[a.Mouse=1]="Mouse",a);function b(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e=>e;return e.slice().sort((e,n)=>{let r=t(e),o=t(n);if(null===r||null===o)return 0;let i=r.compareDocumentPosition(o);return i&Node.DOCUMENT_POSITION_FOLLOWING?-1:i&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function E(e,t){return w(g(),t,{relativeTo:e})}function w(e,t){var n,r,o;let{sorted:i=!0,relativeTo:u=null,skipElements:a=[]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},l=Array.isArray(e)?e.length>0?e[0].ownerDocument:document:e.ownerDocument,c=Array.isArray(e)?i?b(e):e:64&t?function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(d)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}(e):g(e);a.length>0&&c.length>1&&(c=c.filter(e=>!a.some(t=>null!=t&&"current"in t?(null==t?void 0:t.current)===e:t===e))),u=null!=u?u:l.activeElement;let s=(()=>{if(5&t)return 1;if(10&t)return -1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),f=(()=>{if(1&t)return 0;if(2&t)return Math.max(0,c.indexOf(u))-1;if(4&t)return Math.max(0,c.indexOf(u))+1;if(8&t)return c.length-1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),v=32&t?{preventScroll:!0}:{},p=0,m=c.length,h;do{if(p>=m||p+m<=0)return 0;let e=f+p;if(16&t)e=(e+m)%m;else{if(e<0)return 3;if(e>=m)return 1}null==(h=c[e])||h.focus(v),p+=s}while(h!==l.activeElement);return 6&t&&null!=(o=null==(r=null==(n=h)?void 0:n.matches)?void 0:r.call(n,"textarea,input"))&&o&&h.select(),2}"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("keydown",e=>{e.metaKey||e.altKey||e.ctrlKey||(document.documentElement.dataset.headlessuiFocusVisible="")},!0),document.addEventListener("click",e=>{1===e.detail?delete document.documentElement.dataset.headlessuiFocusVisible:0===e.detail&&(document.documentElement.dataset.headlessuiFocusVisible="")},!0))},72468:function(e,t,n){n.d(t,{E:function(){return r}});function r(e,t){for(var n=arguments.length,o=Array(n>2?n-2:0),i=2;i'"'.concat(e,'"')).join(", "),"."));throw Error.captureStackTrace&&Error.captureStackTrace(u,r),u}},24310:function(e,t,n){n.d(t,{Y:function(){return r}});function r(e){"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e).catch(e=>setTimeout(()=>{throw e}))}},5664:function(e,t,n){n.d(t,{r:function(){return o}});var r=n(60415);function o(e){return r.O.isServer?null:e instanceof Node?e.ownerDocument:null!=e&&e.hasOwnProperty("current")&&e.current instanceof Node?e.current.ownerDocument:document}},38929:function(e,t,n){let r,o;n.d(t,{L6:function(){return s},VN:function(){return l},dG:function(){return p},l4:function(){return c},oA:function(){return m},yV:function(){return g}});var i=n(2265),u=n(95504),a=n(72468),l=((r=l||{})[r.None=0]="None",r[r.RenderStrategy=1]="RenderStrategy",r[r.Static=2]="Static",r),c=((o=c||{})[o.Unmount=0]="Unmount",o[o.Hidden=1]="Hidden",o);function s(){let e,t;let n=(e=(0,i.useRef)([]),t=(0,i.useCallback)(t=>{for(let n of e.current)null!=n&&("function"==typeof n?n(t):n.current=t)},[]),function(){for(var n=arguments.length,r=Array(n),o=0;onull==e))return e.current=r,t});return(0,i.useCallback)(e=>(function(e){let{ourProps:t,theirProps:n,slot:r,defaultTag:o,features:i,visible:u=!0,name:l,mergeRefs:c}=e;c=null!=c?c:f;let s=v(n,t);if(u)return d(s,r,o,l,c);let p=null!=i?i:0;if(2&p){let{static:e=!1,...t}=s;if(e)return d(t,r,o,l,c)}if(1&p){let{unmount:e=!0,...t}=s;return(0,a.E)(e?0:1,{0:()=>null,1:()=>d({...t,hidden:!0,style:{display:"none"}},r,o,l,c)})}return d(s,r,o,l,c)})({mergeRefs:n,...e}),[n])}function d(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0,{as:a=n,children:l,refName:c="ref",...s}=h(e,["unmount","static"]),d=void 0!==e.ref?{[c]:e.ref}:{},f="function"==typeof l?l(t):l;"className"in s&&s.className&&"function"==typeof s.className&&(s.className=s.className(t)),s["aria-labelledby"]&&s["aria-labelledby"]===s.id&&(s["aria-labelledby"]=void 0);let p={};if(t){let e=!1,n=[];for(let[r,o]of Object.entries(t))"boolean"==typeof o&&(e=!0),!0===o&&n.push(r.replace(/([A-Z])/g,e=>"-".concat(e.toLowerCase())));if(e)for(let e of(p["data-headlessui-state"]=n.join(" "),n))p["data-".concat(e)]=""}if(a===i.Fragment&&(Object.keys(m(s)).length>0||Object.keys(m(p)).length>0)){if(!(0,i.isValidElement)(f)||Array.isArray(f)&&f.length>1){if(Object.keys(m(s)).length>0)throw Error(['Passing props on "Fragment"!',"","The current component <".concat(r,' /> is rendering a "Fragment".'),"However we need to passthrough the following props:",Object.keys(m(s)).concat(Object.keys(m(p))).map(e=>" - ".concat(e)).join("\n"),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(e=>" - ".concat(e)).join("\n")].join("\n"))}else{let e=f.props,t=null==e?void 0:e.className,n="function"==typeof t?function(){for(var e=arguments.length,n=Array(e),r=0;r="19"?f.props.ref:f.ref,d.ref)},n?{className:n}:{}))}}return(0,i.createElement)(a,Object.assign({},h(s,["ref"]),a!==i.Fragment&&d,a!==i.Fragment&&p),f)}function f(){for(var e=arguments.length,t=Array(e),n=0;nnull==e)?void 0:e=>{for(let n of t)null!=n&&("function"==typeof n?n(e):n.current=e)}}function v(){for(var e=arguments.length,t=Array(e),n=0;n{var t;return null==(t=null==e?void 0:e.preventDefault)?void 0:t.call(e)}]);for(let e in o)Object.assign(r,{[e](t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:[],n=Object.assign({},e);for(let e of t)e in n&&delete n[e];return n}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js b/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js new file mode 100644 index 00000000000..52544b97059 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1518],{81518:function(e,s,a){a.r(s),a.d(s,{default:function(){return Y}});var t=a(57437),r=a(2265),n=a(85572),l=a(93837),i=a(37592),o=a(4260),d=a(99981),c=a(5545),m=a(26430),u=a(96473),x=a(9114),h=a(10703),p=a(95459),g=a(32489),f=a(98728),v=a(62831),j=a(17906),y=a(94263),b=a(79862),N=a(82222),k=a(51817),w=a(94331),A=a(38398),S=a(33152);function C(e){let{messages:s,isLoading:a}=e;if(0===s.length)return(0,t.jsx)("div",{className:"h-full"});let r=[],n=0;for(;n(0,t.jsx)("div",{className:"whitespace-pre-wrap break-words",style:{wordWrap:"break-word",overflowWrap:"break-word",wordBreak:"break-word",hyphens:"auto"},children:(0,t.jsx)(v.UG,{components:{code(e){let{node:s,inline:a,className:r,children:n,...l}=e,i=/language-(\w+)/.exec(r||"");return!a&&i?(0,t.jsx)(j.Z,{style:y.Z,language:i[1],PreTag:"div",className:"rounded-md my-2",wrapLines:!0,wrapLongLines:!0,...l,children:String(n).replace(/\n$/,"")}):(0,t.jsx)("code",{className:"".concat(r," px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono"),...l,children:n})},pre:e=>{let{node:s,...a}=e;return(0,t.jsx)("pre",{style:{overflowX:"auto",maxWidth:"100%"},...a})}},children:"string"==typeof e.content?e.content:""})});return(0,t.jsxs)("div",{className:"flex flex-col gap-6 min-w-0 w-full p-4",children:[r.map((e,s)=>{let n=e.assistant,i=(null==n?void 0:n.model)||"Assistant";return(0,t.jsxs)("div",{className:"space-y-4",children:[e.user&&(0,t.jsxs)("div",{className:"space-y-2 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 text-blue-600",children:(0,t.jsx)(b.Z,{size:16})}),(0,t.jsx)("div",{className:"text-sm font-semibold text-gray-700",children:"You"})]}),l(e.user)]}),(0,t.jsx)("div",{className:"border-t border-gray-200"}),n?(0,t.jsxs)("div",{className:"space-y-3 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600",children:(0,t.jsx)(N.Z,{size:16})}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-semibold text-gray-700",children:i}),n.toolName&&(0,t.jsx)("span",{className:"rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600",children:n.toolName})]})]}),n.reasoningContent&&(0,t.jsx)(w.Z,{reasoningContent:n.reasoningContent}),n.searchResults&&(0,t.jsx)(S.J,{searchResults:n.searchResults}),l(n),(n.timeToFirstToken||n.totalLatency||n.usage)&&(0,t.jsx)(A.Z,{timeToFirstToken:n.timeToFirstToken,totalLatency:n.totalLatency,usage:n.usage,toolName:n.toolName})]}):a&&s===r.length-1?(0,t.jsxs)("div",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]}):(0,t.jsx)("div",{className:"text-sm text-gray-500",children:"Waiting for a response..."})]},s)}),a&&0===r.length&&(0,t.jsxs)("div",{className:"flex items-center gap-2 text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]})]})}var T=a(31283);function P(e){let{value:s,onChange:a,models:n,loading:l,disabled:o}=e,[d,c]=(0,r.useState)(!1),[m,u]=(0,r.useState)(""),x=(0,r.useMemo)(()=>Array.from(new Set(n)).sort(),[n]),h=(0,r.useMemo)(()=>s&&!x.includes(s)?[s,...x]:x,[x,s]),p=d?"__custom__":s||void 0,g=()=>{let e=m.trim();if(!e){c(!1),u("");return}a(e),c(!1),u("")};return(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsxs)(i.default,{value:p,onChange:e=>{if("__custom__"===e){c(!0),s&&!x.includes(s)?u(s):u("");return}c(!1),u(""),a(e)},disabled:o,loading:l,placeholder:l?"Loading models...":"Select a model",className:"w-full rounded-md",showSearch:!0,optionFilterProp:"children",children:[h.map(e=>(0,t.jsx)(i.default.Option,{value:e,children:e},e)),(0,t.jsx)(i.default.Option,{value:"__custom__",children:"+ Add custom model"})]}),d&&(0,t.jsx)(T.o,{className:"mt-2",placeholder:"Custom Model Name (Enter to add)",value:m,onValueChange:u,onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),g())},onBlur:g,autoFocus:!0})]})}var Z=a(99020),_=a(97415),L=a(67479),E=a(4156),M=a(23496),I=a(85847),O=a(79326);function R(e){let{comparison:s,onUpdate:a,onRemove:n,canRemove:l,modelOptions:i,isLoadingModels:o,apiKey:d}=e,[c,m]=(0,r.useState)(!1),u=e=>{e?a({applyAcrossModels:!0,temperature:s.temperature,maxTokens:s.maxTokens,tags:[...s.tags],vectorStores:[...s.vectorStores],guardrails:[...s.guardrails],useAdvancedParams:s.useAdvancedParams},{applyToAll:!0,keysToApply:["temperature","maxTokens","tags","vectorStores","guardrails","useAdvancedParams"]}):a({applyAcrossModels:!1})},x=e=>{a({useAdvancedParams:e},s.applyAcrossModels?{applyToAll:!0,keysToApply:["useAdvancedParams"]}:void 0)},h=(e,t)=>{a({[e]:t},s.applyAcrossModels?{applyToAll:!0,keysToApply:[e]}:void 0)},p=s.useAdvancedParams?1:.4,v=s.useAdvancedParams?"text-gray-700":"text-gray-400",j=()=>{m(e=>!e)},y=(0,t.jsxs)("div",{className:"w-[300px] max-h-[65vh] overflow-y-auto relative",children:[(0,t.jsx)("button",{onClick:()=>{m(!1)},className:"absolute top-0 right-0 p-1 hover:bg-gray-100 rounded transition-colors text-gray-500 hover:text-gray-700 z-10",children:(0,t.jsx)(g.Z,{size:14})}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(E.Z,{checked:s.applyAcrossModels,onChange:e=>u(e.target.checked),children:(0,t.jsx)("span",{className:"text-xs font-medium",children:"Sync Settings Across Models"})})}),(0,t.jsx)(M.Z,{className:"border-gray-200"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"General Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Tags"}),(0,t.jsx)(Z.Z,{value:s.tags,onChange:e=>h("tags",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Vector Stores"}),(0,t.jsx)(_.Z,{value:s.vectorStores,onChange:e=>h("vectorStores",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Guardrails"}),(0,t.jsx)(L.Z,{value:s.guardrails,onChange:e=>h("guardrails",e),accessToken:d})]})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"Advanced Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2 pb-1",children:(0,t.jsx)(E.Z,{checked:s.useAdvancedParams,onChange:e=>x(e.target.checked),children:(0,t.jsx)("span",{className:"text-sm font-medium",children:"Use Advanced Parameters"})})}),(0,t.jsxs)("div",{className:"space-y-2 transition-opacity duration-200",style:{opacity:p},children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Temperature"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.temperature.toFixed(2)})]}),(0,t.jsx)(I.Z,{min:0,max:2,step:.01,value:s.temperature,onChange:e=>{h("temperature",Math.min(2,Math.max(0,Number((Array.isArray(e)?e[0]:e).toFixed(2)))))},disabled:!s.useAdvancedParams})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Max Tokens"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.maxTokens})]}),(0,t.jsx)(I.Z,{min:1,max:32768,step:1,value:s.maxTokens,onChange:e=>{h("maxTokens",Math.min(32768,Math.max(1,Math.round(Array.isArray(e)?e[0]:e))))},disabled:!s.useAdvancedParams})]})]})]})]})]})]});return(0,t.jsxs)("div",{className:"bg-white first:border-l-0 border-l border-gray-200 flex flex-col min-h-0",children:[(0,t.jsxs)("div",{className:"border-b flex items-center justify-between gap-3 px-4 py-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 flex-1",children:[(0,t.jsx)(P,{value:s.model,models:i,loading:o,onChange:e=>a({model:e})}),(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(O.Z,{content:y,trigger:[],open:c,onOpenChange:()=>{},placement:"bottomRight",destroyTooltipOnHide:!1,children:(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),j()},className:"p-2 rounded-lg transition-colors ".concat(c?"bg-gray-200 text-gray-700":"hover:bg-gray-100 text-gray-600"),children:(0,t.jsx)(f.Z,{size:18})})})})]}),l&&(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),n()},className:"p-2 hover:bg-red-50 text-red-600 rounded-lg transition-colors",children:(0,t.jsx)(g.Z,{size:18})})]}),(0,t.jsx)("div",{className:"relative flex-1 flex flex-col min-h-0",children:(0,t.jsx)("div",{className:"flex-1 max-h-[calc(100vh-385px)] overflow-auto rounded-b-2xl",children:(0,t.jsx)(C,{messages:s.messages,isLoading:s.isLoading})})})]})}var z=a(79276);let{TextArea:U}=o.default;function K(e){let{value:s,onChange:a,onSend:r,disabled:n}=e;return(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsxs)("div",{className:"flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]",children:[(0,t.jsx)(U,{value:s,onChange:e=>a(e.target.value),onKeyDown:e=>{"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),!n&&s.trim()&&r())},placeholder:"Type your message... (Shift+Enter for new line)",disabled:n,className:"flex-1",autoSize:{minRows:1,maxRows:4},style:{resize:"none",border:"none",boxShadow:"none",background:"transparent",padding:"4px 0",fontSize:"14px",lineHeight:"20px"}}),(0,t.jsx)(c.ZP,{onClick:r,disabled:n||!s.trim(),icon:(0,t.jsx)(z.Z,{}),shape:"circle"})]})})}let B=["Can you summarize the key points?","What assumptions did you make?","What are the next steps?"],D=["Write me a poem","Explain quantum computing","Draft a polite email requesting a meeting"],F="/v1/chat/completions";function W(e){let{accessToken:s,disabledPersonalKeyCreation:a}=e,[n,g]=(0,r.useState)([{id:"1",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1},{id:"2",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1}]),[f,v]=(0,r.useState)([]),[j,y]=(0,r.useState)(!1),[b,N]=(0,r.useState)(""),[k,w]=(0,r.useState)(a?"custom":"session"),[A,S]=(0,r.useState)(""),[C,T]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=setTimeout(()=>{T(A)},300);return()=>clearTimeout(e)},[A]);let P=(0,r.useMemo)(()=>"session"===k?s||"":C.trim(),[k,s,C]),Z=(0,r.useMemo)(()=>n.length>0&&n.every(e=>!e.isLoading&&e.messages.some(e=>"assistant"===e.role)),[n]);(0,r.useEffect)(()=>{let e=!0;return(async()=>{if(!P){v([]);return}y(!0);try{let s=await (0,h.p)(P);if(!e)return;let a=Array.from(new Set(s.map(e=>e.model_group)));v(a)}catch(s){console.error("CompareUI: failed to fetch models",s),e&&v([])}finally{e&&y(!1)}})(),()=>{e=!1}},[P]),(0,r.useEffect)(()=>{0!==f.length&&g(e=>e.map((e,s)=>{var a,t,r,n,l;return{...e,temperature:null!==(a=e.temperature)&&void 0!==a?a:1,maxTokens:null!==(t=e.maxTokens)&&void 0!==t?t:2048,applyAcrossModels:null!==(r=e.applyAcrossModels)&&void 0!==r&&r,useAdvancedParams:null!==(n=e.useAdvancedParams)&&void 0!==n&&n,...e.model?{}:{model:null!==(l=f[s%f.length])&&void 0!==l?l:""}}}))},[f]);let _=e=>{n.length>1&&g(s=>s.filter(s=>s.id!==e))},L=(e,s,a)=>{g(t=>{var r;if((null==a?void 0:a.applyToAll)&&(null===(r=a.keysToApply)||void 0===r?void 0:r.length)){let r={};a.keysToApply.forEach(e=>{let a=s[e];void 0!==a&&(r[e]=Array.isArray(a)?[...a]:a)});let n=Object.keys(r).length>0;return t.map(a=>a.id===e?{...a,...s}:n?{...a,...r}:a)}return t.map(a=>a.id===e?{...a,...s}:a)})},E=(e,s,a)=>{s&&g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];if(n&&"assistant"===n.role){var l;let e="string"==typeof n.content?n.content:"";r[r.length-1]={...n,content:e+s,model:null!==(l=n.model)&&void 0!==l?l:a}}else r.push({role:"assistant",content:s,model:a});return{...t,messages:r}}))},M=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,reasoningContent:(r.reasoningContent||"")+s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",reasoningContent:s}),{...a,messages:t}}))},I=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,timeToFirstToken:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",timeToFirstToken:s}),{...a,messages:t}}))},O=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,totalLatency:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",totalLatency:s}),{...a,messages:t}}))},z=(e,s,a)=>{g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];return n&&"assistant"===n.role&&(r[r.length-1]={...n,usage:s,toolName:a}),{...t,messages:r}}))},U=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role&&(t[t.length-1]={...r,searchResults:s}),{...a,messages:t}}))},W=!!s,G=e=>{let s=e.trim();if(!s)return;if(!P){x.Z.fromBackend("Please provide an API key or select Current UI Session");return}if(0===n.length)return;if(n.some(e=>!e.model)){x.Z.fromBackend("Select a model before sending a message.");return}let a=new Map;n.forEach(e=>{var t;let r=null!==(t=e.traceId)&&void 0!==t?t:(0,l.Z)();a.set(e.id,{id:e.id,model:e.model,traceId:r,tags:e.tags,vectorStores:e.vectorStores,guardrails:e.guardrails,temperature:e.temperature,maxTokens:e.maxTokens,messages:[...e.messages,{role:"user",content:s}]})}),0!==a.size&&(g(e=>e.map(e=>{let s=a.get(e.id);return s?{...e,traceId:s.traceId,messages:s.messages,isLoading:!0}:e})),a.forEach(e=>{var s;let a=e.messages.map(e=>{let{role:s,content:a}=e;return{role:s,content:"string"==typeof a?a:""}}),t=e.tags.length>0?e.tags:void 0,r=e.vectorStores.length>0?e.vectorStores:void 0,l=e.guardrails.length>0?e.guardrails:void 0,i=n.find(s=>s.id===e.id),o=null!==(s=null==i?void 0:i.useAdvancedParams)&&void 0!==s&&s;(0,p.n)(a,(s,a)=>E(e.id,s,a),e.model,P,t,void 0,s=>M(e.id,s),s=>I(e.id,s),s=>z(e.id,s),e.traceId,r,l,void 0,void 0,s=>U(e.id,s),o?e.temperature:void 0,o?e.maxTokens:void 0,s=>O(e.id,s)).catch(s=>{let a=s instanceof Error?s.message:String(s);console.error("CompareUI: failed to fetch response",s),x.Z.fromBackend(a),g(s=>s.map(s=>{if(s.id!==e.id)return s;let t=[...s.messages],r=t[t.length-1],n=r&&"assistant"===r.role&&"string"==typeof r.content?r.content:"";return r&&"assistant"===r.role?t[t.length-1]={...r,content:n?"".concat(n,"\nError fetching response: ").concat(a):"Error fetching response: ".concat(a)}:t.push({role:"assistant",content:"Error fetching response: ".concat(a)}),{...s,messages:t}}))}).finally(()=>{g(s=>s.map(s=>s.id===e.id?{...s,isLoading:!1}:s))})}))},V=e=>{N(e)},X=n.some(e=>e.messages.length>0),Y=n.some(e=>e.isLoading);return(0,t.jsx)("div",{className:"w-full h-full p-4 bg-white",children:(0,t.jsxs)("div",{className:"rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-140px)] flex flex-col",children:[(0,t.jsx)("div",{className:"border-b px-4 py-2",children:(0,t.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"API Key Source"}),(0,t.jsxs)(i.default,{value:k,onChange:e=>w(e),disabled:a,className:"w-48",children:[(0,t.jsx)(i.default.Option,{value:"session",disabled:!W,children:"Current UI Session"}),(0,t.jsx)(i.default.Option,{value:"custom",children:"Virtual Key"})]}),"custom"===k&&(0,t.jsx)(o.default.Password,{value:A,onChange:e=>S(e.target.value),placeholder:"Enter API key",className:"w-56"})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"Endpoint"}),(0,t.jsx)(d.Z,{title:"Other endpoints will be available soon",children:(0,t.jsx)(i.default,{value:F,disabled:!0,className:"w-56",children:(0,t.jsx)(i.default.Option,{value:F,children:F})})})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(c.ZP,{onClick:()=>{g(e=>e.map(e=>({...e,messages:[],traceId:void 0,isLoading:!1}))),N("")},disabled:!X,icon:(0,t.jsx)(m.Z,{}),children:"Clear All Chats"}),(0,t.jsx)(d.Z,{title:n.length>=3?"Compare up to 3 models at a time":"Add another comparison",children:(0,t.jsx)(c.ZP,{onClick:()=>{var e;if(n.length>=3)return;let s=null!==(e=f[n.length%(f.length||1)])&&void 0!==e?e:"",a={id:Date.now().toString(),model:s,messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1};g(e=>[...e,a])},disabled:n.length>=3,icon:(0,t.jsx)(u.Z,{}),children:"Add Comparison"})})]})]})}),(0,t.jsx)("div",{className:"grid flex-1 min-h-0 auto-rows-[minmax(0,1fr)]",style:{gridTemplateColumns:"repeat(".concat(n.length,", minmax(0, 1fr))")},children:n.map(e=>(0,t.jsx)(R,{comparison:e,onUpdate:(s,a)=>L(e.id,s,a),onRemove:()=>_(e.id),canRemove:n.length>1,modelOptions:f,isLoadingModels:j,apiKey:P},e.id))}),(0,t.jsx)("div",{className:"flex justify-center pb-4",children:(0,t.jsx)("div",{className:"w-full max-w-3xl px-4",children:(0,t.jsxs)("div",{className:"border border-gray-200 shadow-lg rounded-xl bg-white p-4",children:[(0,t.jsx)("div",{className:"flex items-center justify-between gap-4 mb-3 min-h-8",children:X||Y?Z?(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:B.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))}):Y?(0,t.jsxs)("span",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)("span",{className:"h-2 w-2 rounded-full bg-blue-500 animate-pulse","aria-hidden":!0}),"Gathering responses from all models..."]}):(0,t.jsx)("span",{className:"text-sm text-gray-500",children:"Send a prompt to compare models"}):(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:D.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))})}),(0,t.jsx)(K,{value:b,onChange:e=>{N(e)},onSend:()=>{G(b),N("")},disabled:0===n.length||n.every(e=>e.isLoading)})]})})})]})})}var G=a(58643),V=a(80443),X=a(91624);function Y(){let{accessToken:e,userRole:s,userId:a,disabledPersonalKeyCreation:l,token:i}=(0,V.Z)(),[o,d]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{(async()=>{if(e){let s=await (0,X.C)(e);s&&d({PROXY_BASE_URL:s.PROXY_BASE_URL,LITELLM_UI_API_DOC_BASE_URL:s.LITELLM_UI_API_DOC_BASE_URL})}})()},[e]),(0,t.jsxs)(G.v0,{className:"h-full w-full",children:[(0,t.jsxs)(G.td,{className:"mb-0",children:[(0,t.jsx)(G.OK,{children:"Chat"}),(0,t.jsx)(G.OK,{children:"Compare"})]}),(0,t.jsxs)(G.nP,{className:"h-full",children:[(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(n.Z,{accessToken:e,token:i,userRole:s,userID:a,disabledPersonalKeyCreation:l,proxySettings:o})}),(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(W,{accessToken:e,disabledPersonalKeyCreation:l})})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1518-9a77ac5675e15594.js b/litellm/proxy/_experimental/out/_next/static/chunks/1518-9a77ac5675e15594.js deleted file mode 100644 index c3ec8596b6f..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1518-9a77ac5675e15594.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1518],{81518:function(e,s,a){a.r(s),a.d(s,{default:function(){return Y}});var t=a(57437),r=a(2265),n=a(85572),l=a(93837),i=a(37592),o=a(4260),d=a(99981),c=a(5545),m=a(26430),u=a(96473),x=a(9114),h=a(10703),p=a(95459),g=a(32489),f=a(98728),v=a(62831),j=a(17906),y=a(94263),b=a(79862),N=a(82222),k=a(51817),w=a(94331),A=a(38398),S=a(33152);function C(e){let{messages:s,isLoading:a}=e;if(0===s.length)return(0,t.jsx)("div",{className:"h-full"});let r=[],n=0;for(;n(0,t.jsx)("div",{className:"whitespace-pre-wrap break-words",style:{wordWrap:"break-word",overflowWrap:"break-word",wordBreak:"break-word",hyphens:"auto"},children:(0,t.jsx)(v.UG,{components:{code(e){let{node:s,inline:a,className:r,children:n,...l}=e,i=/language-(\w+)/.exec(r||"");return!a&&i?(0,t.jsx)(j.Z,{style:y.Z,language:i[1],PreTag:"div",className:"rounded-md my-2",wrapLines:!0,wrapLongLines:!0,...l,children:String(n).replace(/\n$/,"")}):(0,t.jsx)("code",{className:"".concat(r," px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono"),...l,children:n})},pre:e=>{let{node:s,...a}=e;return(0,t.jsx)("pre",{style:{overflowX:"auto",maxWidth:"100%"},...a})}},children:"string"==typeof e.content?e.content:""})});return(0,t.jsxs)("div",{className:"flex flex-col gap-6 min-w-0 w-full p-4",children:[r.map((e,s)=>{let n=e.assistant,i=(null==n?void 0:n.model)||"Assistant";return(0,t.jsxs)("div",{className:"space-y-4",children:[e.user&&(0,t.jsxs)("div",{className:"space-y-2 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 text-blue-600",children:(0,t.jsx)(b.Z,{size:16})}),(0,t.jsx)("div",{className:"text-sm font-semibold text-gray-700",children:"You"})]}),l(e.user)]}),(0,t.jsx)("div",{className:"border-t border-gray-200"}),n?(0,t.jsxs)("div",{className:"space-y-3 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600",children:(0,t.jsx)(N.Z,{size:16})}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-semibold text-gray-700",children:i}),n.toolName&&(0,t.jsx)("span",{className:"rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600",children:n.toolName})]})]}),n.reasoningContent&&(0,t.jsx)(w.Z,{reasoningContent:n.reasoningContent}),n.searchResults&&(0,t.jsx)(S.J,{searchResults:n.searchResults}),l(n),(n.timeToFirstToken||n.totalLatency||n.usage)&&(0,t.jsx)(A.Z,{timeToFirstToken:n.timeToFirstToken,totalLatency:n.totalLatency,usage:n.usage,toolName:n.toolName})]}):a&&s===r.length-1?(0,t.jsxs)("div",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]}):(0,t.jsx)("div",{className:"text-sm text-gray-500",children:"Waiting for a response..."})]},s)}),a&&0===r.length&&(0,t.jsxs)("div",{className:"flex items-center gap-2 text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]})]})}var T=a(31283);function P(e){let{value:s,onChange:a,models:n,loading:l,disabled:o}=e,[d,c]=(0,r.useState)(!1),[m,u]=(0,r.useState)(""),x=(0,r.useMemo)(()=>Array.from(new Set(n)).sort(),[n]),h=(0,r.useMemo)(()=>s&&!x.includes(s)?[s,...x]:x,[x,s]),p=d?"__custom__":s||void 0,g=()=>{let e=m.trim();if(!e){c(!1),u("");return}a(e),c(!1),u("")};return(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsxs)(i.default,{value:p,onChange:e=>{if("__custom__"===e){c(!0),s&&!x.includes(s)?u(s):u("");return}c(!1),u(""),a(e)},disabled:o,loading:l,placeholder:l?"Loading models...":"Select a model",className:"w-full rounded-md",showSearch:!0,optionFilterProp:"children",children:[h.map(e=>(0,t.jsx)(i.default.Option,{value:e,children:e},e)),(0,t.jsx)(i.default.Option,{value:"__custom__",children:"+ Add custom model"})]}),d&&(0,t.jsx)(T.o,{className:"mt-2",placeholder:"Custom Model Name (Enter to add)",value:m,onValueChange:u,onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),g())},onBlur:g,autoFocus:!0})]})}var Z=a(99020),_=a(97415),L=a(67479),E=a(61994),M=a(23496),I=a(85847),O=a(79326);function R(e){let{comparison:s,onUpdate:a,onRemove:n,canRemove:l,modelOptions:i,isLoadingModels:o,apiKey:d}=e,[c,m]=(0,r.useState)(!1),u=e=>{e?a({applyAcrossModels:!0,temperature:s.temperature,maxTokens:s.maxTokens,tags:[...s.tags],vectorStores:[...s.vectorStores],guardrails:[...s.guardrails],useAdvancedParams:s.useAdvancedParams},{applyToAll:!0,keysToApply:["temperature","maxTokens","tags","vectorStores","guardrails","useAdvancedParams"]}):a({applyAcrossModels:!1})},x=e=>{a({useAdvancedParams:e},s.applyAcrossModels?{applyToAll:!0,keysToApply:["useAdvancedParams"]}:void 0)},h=(e,t)=>{a({[e]:t},s.applyAcrossModels?{applyToAll:!0,keysToApply:[e]}:void 0)},p=s.useAdvancedParams?1:.4,v=s.useAdvancedParams?"text-gray-700":"text-gray-400",j=()=>{m(e=>!e)},y=(0,t.jsxs)("div",{className:"w-[300px] max-h-[65vh] overflow-y-auto relative",children:[(0,t.jsx)("button",{onClick:()=>{m(!1)},className:"absolute top-0 right-0 p-1 hover:bg-gray-100 rounded transition-colors text-gray-500 hover:text-gray-700 z-10",children:(0,t.jsx)(g.Z,{size:14})}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(E.Z,{checked:s.applyAcrossModels,onChange:e=>u(e.target.checked),children:(0,t.jsx)("span",{className:"text-xs font-medium",children:"Sync Settings Across Models"})})}),(0,t.jsx)(M.Z,{className:"border-gray-200"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"General Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Tags"}),(0,t.jsx)(Z.Z,{value:s.tags,onChange:e=>h("tags",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Vector Stores"}),(0,t.jsx)(_.Z,{value:s.vectorStores,onChange:e=>h("vectorStores",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Guardrails"}),(0,t.jsx)(L.Z,{value:s.guardrails,onChange:e=>h("guardrails",e),accessToken:d})]})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"Advanced Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2 pb-1",children:(0,t.jsx)(E.Z,{checked:s.useAdvancedParams,onChange:e=>x(e.target.checked),children:(0,t.jsx)("span",{className:"text-sm font-medium",children:"Use Advanced Parameters"})})}),(0,t.jsxs)("div",{className:"space-y-2 transition-opacity duration-200",style:{opacity:p},children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Temperature"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.temperature.toFixed(2)})]}),(0,t.jsx)(I.Z,{min:0,max:2,step:.01,value:s.temperature,onChange:e=>{h("temperature",Math.min(2,Math.max(0,Number((Array.isArray(e)?e[0]:e).toFixed(2)))))},disabled:!s.useAdvancedParams})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Max Tokens"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.maxTokens})]}),(0,t.jsx)(I.Z,{min:1,max:32768,step:1,value:s.maxTokens,onChange:e=>{h("maxTokens",Math.min(32768,Math.max(1,Math.round(Array.isArray(e)?e[0]:e))))},disabled:!s.useAdvancedParams})]})]})]})]})]})]});return(0,t.jsxs)("div",{className:"bg-white first:border-l-0 border-l border-gray-200 flex flex-col min-h-0",children:[(0,t.jsxs)("div",{className:"border-b flex items-center justify-between gap-3 px-4 py-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 flex-1",children:[(0,t.jsx)(P,{value:s.model,models:i,loading:o,onChange:e=>a({model:e})}),(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(O.Z,{content:y,trigger:[],open:c,onOpenChange:()=>{},placement:"bottomRight",destroyTooltipOnHide:!1,children:(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),j()},className:"p-2 rounded-lg transition-colors ".concat(c?"bg-gray-200 text-gray-700":"hover:bg-gray-100 text-gray-600"),children:(0,t.jsx)(f.Z,{size:18})})})})]}),l&&(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),n()},className:"p-2 hover:bg-red-50 text-red-600 rounded-lg transition-colors",children:(0,t.jsx)(g.Z,{size:18})})]}),(0,t.jsx)("div",{className:"relative flex-1 flex flex-col min-h-0",children:(0,t.jsx)("div",{className:"flex-1 max-h-[calc(100vh-385px)] overflow-auto rounded-b-2xl",children:(0,t.jsx)(C,{messages:s.messages,isLoading:s.isLoading})})})]})}var z=a(79276);let{TextArea:U}=o.default;function K(e){let{value:s,onChange:a,onSend:r,disabled:n}=e;return(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsxs)("div",{className:"flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]",children:[(0,t.jsx)(U,{value:s,onChange:e=>a(e.target.value),onKeyDown:e=>{"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),!n&&s.trim()&&r())},placeholder:"Type your message... (Shift+Enter for new line)",disabled:n,className:"flex-1",autoSize:{minRows:1,maxRows:4},style:{resize:"none",border:"none",boxShadow:"none",background:"transparent",padding:"4px 0",fontSize:"14px",lineHeight:"20px"}}),(0,t.jsx)(c.ZP,{onClick:r,disabled:n||!s.trim(),icon:(0,t.jsx)(z.Z,{}),shape:"circle"})]})})}let B=["Can you summarize the key points?","What assumptions did you make?","What are the next steps?"],D=["Write me a poem","Explain quantum computing","Draft a polite email requesting a meeting"],F="/v1/chat/completions";function W(e){let{accessToken:s,disabledPersonalKeyCreation:a}=e,[n,g]=(0,r.useState)([{id:"1",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1},{id:"2",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1}]),[f,v]=(0,r.useState)([]),[j,y]=(0,r.useState)(!1),[b,N]=(0,r.useState)(""),[k,w]=(0,r.useState)(a?"custom":"session"),[A,S]=(0,r.useState)(""),[C,T]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=setTimeout(()=>{T(A)},300);return()=>clearTimeout(e)},[A]);let P=(0,r.useMemo)(()=>"session"===k?s||"":C.trim(),[k,s,C]),Z=(0,r.useMemo)(()=>n.length>0&&n.every(e=>!e.isLoading&&e.messages.some(e=>"assistant"===e.role)),[n]);(0,r.useEffect)(()=>{let e=!0;return(async()=>{if(!P){v([]);return}y(!0);try{let s=await (0,h.p)(P);if(!e)return;let a=Array.from(new Set(s.map(e=>e.model_group)));v(a)}catch(s){console.error("CompareUI: failed to fetch models",s),e&&v([])}finally{e&&y(!1)}})(),()=>{e=!1}},[P]),(0,r.useEffect)(()=>{0!==f.length&&g(e=>e.map((e,s)=>{var a,t,r,n,l;return{...e,temperature:null!==(a=e.temperature)&&void 0!==a?a:1,maxTokens:null!==(t=e.maxTokens)&&void 0!==t?t:2048,applyAcrossModels:null!==(r=e.applyAcrossModels)&&void 0!==r&&r,useAdvancedParams:null!==(n=e.useAdvancedParams)&&void 0!==n&&n,...e.model?{}:{model:null!==(l=f[s%f.length])&&void 0!==l?l:""}}}))},[f]);let _=e=>{n.length>1&&g(s=>s.filter(s=>s.id!==e))},L=(e,s,a)=>{g(t=>{var r;if((null==a?void 0:a.applyToAll)&&(null===(r=a.keysToApply)||void 0===r?void 0:r.length)){let r={};a.keysToApply.forEach(e=>{let a=s[e];void 0!==a&&(r[e]=Array.isArray(a)?[...a]:a)});let n=Object.keys(r).length>0;return t.map(a=>a.id===e?{...a,...s}:n?{...a,...r}:a)}return t.map(a=>a.id===e?{...a,...s}:a)})},E=(e,s,a)=>{s&&g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];if(n&&"assistant"===n.role){var l;let e="string"==typeof n.content?n.content:"";r[r.length-1]={...n,content:e+s,model:null!==(l=n.model)&&void 0!==l?l:a}}else r.push({role:"assistant",content:s,model:a});return{...t,messages:r}}))},M=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,reasoningContent:(r.reasoningContent||"")+s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",reasoningContent:s}),{...a,messages:t}}))},I=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,timeToFirstToken:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",timeToFirstToken:s}),{...a,messages:t}}))},O=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,totalLatency:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",totalLatency:s}),{...a,messages:t}}))},z=(e,s,a)=>{g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];return n&&"assistant"===n.role&&(r[r.length-1]={...n,usage:s,toolName:a}),{...t,messages:r}}))},U=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role&&(t[t.length-1]={...r,searchResults:s}),{...a,messages:t}}))},W=!!s,G=e=>{let s=e.trim();if(!s)return;if(!P){x.Z.fromBackend("Please provide an API key or select Current UI Session");return}if(0===n.length)return;if(n.some(e=>!e.model)){x.Z.fromBackend("Select a model before sending a message.");return}let a=new Map;n.forEach(e=>{var t;let r=null!==(t=e.traceId)&&void 0!==t?t:(0,l.Z)();a.set(e.id,{id:e.id,model:e.model,traceId:r,tags:e.tags,vectorStores:e.vectorStores,guardrails:e.guardrails,temperature:e.temperature,maxTokens:e.maxTokens,messages:[...e.messages,{role:"user",content:s}]})}),0!==a.size&&(g(e=>e.map(e=>{let s=a.get(e.id);return s?{...e,traceId:s.traceId,messages:s.messages,isLoading:!0}:e})),a.forEach(e=>{var s;let a=e.messages.map(e=>{let{role:s,content:a}=e;return{role:s,content:"string"==typeof a?a:""}}),t=e.tags.length>0?e.tags:void 0,r=e.vectorStores.length>0?e.vectorStores:void 0,l=e.guardrails.length>0?e.guardrails:void 0,i=n.find(s=>s.id===e.id),o=null!==(s=null==i?void 0:i.useAdvancedParams)&&void 0!==s&&s;(0,p.n)(a,(s,a)=>E(e.id,s,a),e.model,P,t,void 0,s=>M(e.id,s),s=>I(e.id,s),s=>z(e.id,s),e.traceId,r,l,void 0,void 0,s=>U(e.id,s),o?e.temperature:void 0,o?e.maxTokens:void 0,s=>O(e.id,s)).catch(s=>{let a=s instanceof Error?s.message:String(s);console.error("CompareUI: failed to fetch response",s),x.Z.fromBackend(a),g(s=>s.map(s=>{if(s.id!==e.id)return s;let t=[...s.messages],r=t[t.length-1],n=r&&"assistant"===r.role&&"string"==typeof r.content?r.content:"";return r&&"assistant"===r.role?t[t.length-1]={...r,content:n?"".concat(n,"\nError fetching response: ").concat(a):"Error fetching response: ".concat(a)}:t.push({role:"assistant",content:"Error fetching response: ".concat(a)}),{...s,messages:t}}))}).finally(()=>{g(s=>s.map(s=>s.id===e.id?{...s,isLoading:!1}:s))})}))},V=e=>{N(e)},X=n.some(e=>e.messages.length>0),Y=n.some(e=>e.isLoading);return(0,t.jsx)("div",{className:"w-full h-full p-4 bg-white",children:(0,t.jsxs)("div",{className:"rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-140px)] flex flex-col",children:[(0,t.jsx)("div",{className:"border-b px-4 py-2",children:(0,t.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"API Key Source"}),(0,t.jsxs)(i.default,{value:k,onChange:e=>w(e),disabled:a,className:"w-48",children:[(0,t.jsx)(i.default.Option,{value:"session",disabled:!W,children:"Current UI Session"}),(0,t.jsx)(i.default.Option,{value:"custom",children:"Virtual Key"})]}),"custom"===k&&(0,t.jsx)(o.default.Password,{value:A,onChange:e=>S(e.target.value),placeholder:"Enter API key",className:"w-56"})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"Endpoint"}),(0,t.jsx)(d.Z,{title:"Other endpoints will be available soon",children:(0,t.jsx)(i.default,{value:F,disabled:!0,className:"w-56",children:(0,t.jsx)(i.default.Option,{value:F,children:F})})})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(c.ZP,{onClick:()=>{g(e=>e.map(e=>({...e,messages:[],traceId:void 0,isLoading:!1}))),N("")},disabled:!X,icon:(0,t.jsx)(m.Z,{}),children:"Clear All Chats"}),(0,t.jsx)(d.Z,{title:n.length>=3?"Compare up to 3 models at a time":"Add another comparison",children:(0,t.jsx)(c.ZP,{onClick:()=>{var e;if(n.length>=3)return;let s=null!==(e=f[n.length%(f.length||1)])&&void 0!==e?e:"",a={id:Date.now().toString(),model:s,messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1};g(e=>[...e,a])},disabled:n.length>=3,icon:(0,t.jsx)(u.Z,{}),children:"Add Comparison"})})]})]})}),(0,t.jsx)("div",{className:"grid flex-1 min-h-0 auto-rows-[minmax(0,1fr)]",style:{gridTemplateColumns:"repeat(".concat(n.length,", minmax(0, 1fr))")},children:n.map(e=>(0,t.jsx)(R,{comparison:e,onUpdate:(s,a)=>L(e.id,s,a),onRemove:()=>_(e.id),canRemove:n.length>1,modelOptions:f,isLoadingModels:j,apiKey:P},e.id))}),(0,t.jsx)("div",{className:"flex justify-center pb-4",children:(0,t.jsx)("div",{className:"w-full max-w-3xl px-4",children:(0,t.jsxs)("div",{className:"border border-gray-200 shadow-lg rounded-xl bg-white p-4",children:[(0,t.jsx)("div",{className:"flex items-center justify-between gap-4 mb-3 min-h-8",children:X||Y?Z?(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:B.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))}):Y?(0,t.jsxs)("span",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)("span",{className:"h-2 w-2 rounded-full bg-blue-500 animate-pulse","aria-hidden":!0}),"Gathering responses from all models..."]}):(0,t.jsx)("span",{className:"text-sm text-gray-500",children:"Send a prompt to compare models"}):(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:D.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))})}),(0,t.jsx)(K,{value:b,onChange:e=>{N(e)},onSend:()=>{G(b),N("")},disabled:0===n.length||n.every(e=>e.isLoading)})]})})})]})})}var G=a(58643),V=a(80443),X=a(91624);function Y(){let{accessToken:e,userRole:s,userId:a,disabledPersonalKeyCreation:l,token:i}=(0,V.Z)(),[o,d]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{(async()=>{if(e){let s=await (0,X.C)(e);s&&d({PROXY_BASE_URL:s.PROXY_BASE_URL,LITELLM_UI_API_DOC_BASE_URL:s.LITELLM_UI_API_DOC_BASE_URL})}})()},[e]),(0,t.jsxs)(G.v0,{className:"h-full w-full",children:[(0,t.jsxs)(G.td,{className:"mb-0",children:[(0,t.jsx)(G.OK,{children:"Chat"}),(0,t.jsx)(G.OK,{children:"Compare"})]}),(0,t.jsxs)(G.nP,{className:"h-full",children:[(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(n.Z,{accessToken:e,token:i,userRole:s,userID:a,disabledPersonalKeyCreation:l,proxySettings:o})}),(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(W,{accessToken:e,disabledPersonalKeyCreation:l})})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1623-995fddc2b5647961.js b/litellm/proxy/_experimental/out/_next/static/chunks/1623-995fddc2b5647961.js new file mode 100644 index 00000000000..15400abe793 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1623-995fddc2b5647961.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1623],{2894:function(t,e,s){s.d(e,{R:function(){return u},m:function(){return n}});var i=s(18238),a=s(7989),r=s(11255),n=class extends a.F{#t;#e;#s;#i;constructor(t){super(),this.#t=t.client,this.mutationId=t.mutationId,this.#s=t.mutationCache,this.#e=[],this.state=t.state||u(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){this.#e.includes(t)||(this.#e.push(t),this.clearGcTimeout(),this.#s.notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){this.#e=this.#e.filter(e=>e!==t),this.scheduleGc(),this.#s.notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){this.#e.length||("pending"===this.state.status?this.scheduleGc():this.#s.remove(this))}continue(){return this.#i?.continue()??this.execute(this.state.variables)}async execute(t){let e=()=>{this.#a({type:"continue"})},s={client:this.#t,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#i=(0,r.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(t,s):Promise.reject(Error("No mutationFn found")),onFail:(t,e)=>{this.#a({type:"failed",failureCount:t,error:e})},onPause:()=>{this.#a({type:"pause"})},onContinue:e,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#s.canRun(this)});let i="pending"===this.state.status,a=!this.#i.canStart();try{if(i)e();else{this.#a({type:"pending",variables:t,isPaused:a}),await this.#s.config.onMutate?.(t,this,s);let e=await this.options.onMutate?.(t,s);e!==this.state.context&&this.#a({type:"pending",context:e,variables:t,isPaused:a})}let r=await this.#i.start();return await this.#s.config.onSuccess?.(r,t,this.state.context,this,s),await this.options.onSuccess?.(r,t,this.state.context,s),await this.#s.config.onSettled?.(r,null,this.state.variables,this.state.context,this,s),await this.options.onSettled?.(r,null,t,this.state.context,s),this.#a({type:"success",data:r}),r}catch(e){try{throw await this.#s.config.onError?.(e,t,this.state.context,this,s),await this.options.onError?.(e,t,this.state.context,s),await this.#s.config.onSettled?.(void 0,e,this.state.variables,this.state.context,this,s),await this.options.onSettled?.(void 0,e,t,this.state.context,s),e}finally{this.#a({type:"error",error:e})}}finally{this.#s.runNext(this)}}#a(t){this.state=(e=>{switch(t.type){case"failed":return{...e,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...e,isPaused:!0};case"continue":return{...e,isPaused:!1};case"pending":return{...e,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...e,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...e,data:void 0,error:t.error,failureCount:e.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}})(this.state),i.Vr.batch(()=>{this.#e.forEach(e=>{e.onMutationUpdate(t)}),this.#s.notify({mutation:this,type:"updated",action:t})})}};function u(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21623:function(t,e,s){s.d(e,{S:function(){return y}});var i=s(45345),a=s(21733),r=s(18238),n=s(24112),u=class extends n.l{constructor(t={}){super(),this.config=t,this.#r=new Map}#r;build(t,e,s){let r=e.queryKey,n=e.queryHash??(0,i.Rm)(r,e),u=this.get(n);return u||(u=new a.A({client:t,queryKey:r,queryHash:n,options:t.defaultQueryOptions(e),state:s,defaultOptions:t.getQueryDefaults(r)}),this.add(u)),u}add(t){this.#r.has(t.queryHash)||(this.#r.set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){let e=this.#r.get(t.queryHash);e&&(t.destroy(),e===t&&this.#r.delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){r.Vr.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return this.#r.get(t)}getAll(){return[...this.#r.values()]}find(t){let e={exact:!0,...t};return this.getAll().find(t=>(0,i._x)(e,t))}findAll(t={}){let e=this.getAll();return Object.keys(t).length>0?e.filter(e=>(0,i._x)(t,e)):e}notify(t){r.Vr.batch(()=>{this.listeners.forEach(e=>{e(t)})})}onFocus(){r.Vr.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){r.Vr.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},o=s(2894),h=class extends n.l{constructor(t={}){super(),this.config=t,this.#n=new Set,this.#u=new Map,this.#o=0}#n;#u;#o;build(t,e,s){let i=new o.m({client:t,mutationCache:this,mutationId:++this.#o,options:t.defaultMutationOptions(e),state:s});return this.add(i),i}add(t){this.#n.add(t);let e=l(t);if("string"==typeof e){let s=this.#u.get(e);s?s.push(t):this.#u.set(e,[t])}this.notify({type:"added",mutation:t})}remove(t){if(this.#n.delete(t)){let e=l(t);if("string"==typeof e){let s=this.#u.get(e);if(s){if(s.length>1){let e=s.indexOf(t);-1!==e&&s.splice(e,1)}else s[0]===t&&this.#u.delete(e)}}}this.notify({type:"removed",mutation:t})}canRun(t){let e=l(t);if("string"!=typeof e)return!0;{let s=this.#u.get(e),i=s?.find(t=>"pending"===t.state.status);return!i||i===t}}runNext(t){let e=l(t);if("string"!=typeof e)return Promise.resolve();{let s=this.#u.get(e)?.find(e=>e!==t&&e.state.isPaused);return s?.continue()??Promise.resolve()}}clear(){r.Vr.batch(()=>{this.#n.forEach(t=>{this.notify({type:"removed",mutation:t})}),this.#n.clear(),this.#u.clear()})}getAll(){return Array.from(this.#n)}find(t){let e={exact:!0,...t};return this.getAll().find(t=>(0,i.X7)(e,t))}findAll(t={}){return this.getAll().filter(e=>(0,i.X7)(t,e))}notify(t){r.Vr.batch(()=>{this.listeners.forEach(e=>{e(t)})})}resumePausedMutations(){let t=this.getAll().filter(t=>t.state.isPaused);return r.Vr.batch(()=>Promise.all(t.map(t=>t.continue().catch(i.ZT))))}};function l(t){return t.options.scope?.id}var c=s(87045),d=s(57853);function f(t){return{onFetch:(e,s)=>{let a=e.options,r=e.fetchOptions?.meta?.fetchMore?.direction,n=e.state.data?.pages||[],u=e.state.data?.pageParams||[],o={pages:[],pageParams:[]},h=0,l=async()=>{let s=!1,l=t=>{Object.defineProperty(t,"signal",{enumerable:!0,get:()=>(e.signal.aborted?s=!0:e.signal.addEventListener("abort",()=>{s=!0}),e.signal)})},c=(0,i.cG)(e.options,e.fetchOptions),d=async(t,a,r)=>{if(s)return Promise.reject();if(null==a&&t.pages.length)return Promise.resolve(t);let n=(()=>{let t={client:e.client,queryKey:e.queryKey,pageParam:a,direction:r?"backward":"forward",meta:e.options.meta};return l(t),t})(),u=await c(n),{maxPages:o}=e.options,h=r?i.Ht:i.VX;return{pages:h(t.pages,u,o),pageParams:h(t.pageParams,a,o)}};if(r&&n.length){let t="backward"===r,e={pages:n,pageParams:u},s=(t?function(t,{pages:e,pageParams:s}){return e.length>0?t.getPreviousPageParam?.(e[0],e,s[0],s):void 0}:p)(a,e);o=await d(e,s,t)}else{let e=t??n.length;do{let t=0===h?u[0]??a.initialPageParam:p(a,o);if(h>0&&null==t)break;o=await d(o,t),h++}while(he.options.persister?.(l,{client:e.client,queryKey:e.queryKey,meta:e.options.meta,signal:e.signal},s):e.fetchFn=l}}}function p(t,{pages:e,pageParams:s}){let i=e.length-1;return e.length>0?t.getNextPageParam(e[i],e,s[i],s):void 0}var y=class{#h;#s;#l;#c;#d;#f;#p;#y;constructor(t={}){this.#h=t.queryCache||new u,this.#s=t.mutationCache||new h,this.#l=t.defaultOptions||{},this.#c=new Map,this.#d=new Map,this.#f=0}mount(){this.#f++,1===this.#f&&(this.#p=c.j.subscribe(async t=>{t&&(await this.resumePausedMutations(),this.#h.onFocus())}),this.#y=d.N.subscribe(async t=>{t&&(await this.resumePausedMutations(),this.#h.onOnline())}))}unmount(){this.#f--,0===this.#f&&(this.#p?.(),this.#p=void 0,this.#y?.(),this.#y=void 0)}isFetching(t){return this.#h.findAll({...t,fetchStatus:"fetching"}).length}isMutating(t){return this.#s.findAll({...t,status:"pending"}).length}getQueryData(t){let e=this.defaultQueryOptions({queryKey:t});return this.#h.get(e.queryHash)?.state.data}ensureQueryData(t){let e=this.defaultQueryOptions(t),s=this.#h.build(this,e),a=s.state.data;return void 0===a?this.fetchQuery(t):(t.revalidateIfStale&&s.isStaleByTime((0,i.KC)(e.staleTime,s))&&this.prefetchQuery(e),Promise.resolve(a))}getQueriesData(t){return this.#h.findAll(t).map(({queryKey:t,state:e})=>[t,e.data])}setQueryData(t,e,s){let a=this.defaultQueryOptions({queryKey:t}),r=this.#h.get(a.queryHash),n=r?.state.data,u=(0,i.SE)(e,n);if(void 0!==u)return this.#h.build(this,a).setData(u,{...s,manual:!0})}setQueriesData(t,e,s){return r.Vr.batch(()=>this.#h.findAll(t).map(({queryKey:t})=>[t,this.setQueryData(t,e,s)]))}getQueryState(t){let e=this.defaultQueryOptions({queryKey:t});return this.#h.get(e.queryHash)?.state}removeQueries(t){let e=this.#h;r.Vr.batch(()=>{e.findAll(t).forEach(t=>{e.remove(t)})})}resetQueries(t,e){let s=this.#h;return r.Vr.batch(()=>(s.findAll(t).forEach(t=>{t.reset()}),this.refetchQueries({type:"active",...t},e)))}cancelQueries(t,e={}){let s={revert:!0,...e};return Promise.all(r.Vr.batch(()=>this.#h.findAll(t).map(t=>t.cancel(s)))).then(i.ZT).catch(i.ZT)}invalidateQueries(t,e={}){return r.Vr.batch(()=>(this.#h.findAll(t).forEach(t=>{t.invalidate()}),t?.refetchType==="none")?Promise.resolve():this.refetchQueries({...t,type:t?.refetchType??t?.type??"active"},e))}refetchQueries(t,e={}){let s={...e,cancelRefetch:e.cancelRefetch??!0};return Promise.all(r.Vr.batch(()=>this.#h.findAll(t).filter(t=>!t.isDisabled()&&!t.isStatic()).map(t=>{let e=t.fetch(void 0,s);return s.throwOnError||(e=e.catch(i.ZT)),"paused"===t.state.fetchStatus?Promise.resolve():e}))).then(i.ZT)}fetchQuery(t){let e=this.defaultQueryOptions(t);void 0===e.retry&&(e.retry=!1);let s=this.#h.build(this,e);return s.isStaleByTime((0,i.KC)(e.staleTime,s))?s.fetch(e):Promise.resolve(s.state.data)}prefetchQuery(t){return this.fetchQuery(t).then(i.ZT).catch(i.ZT)}fetchInfiniteQuery(t){return t.behavior=f(t.pages),this.fetchQuery(t)}prefetchInfiniteQuery(t){return this.fetchInfiniteQuery(t).then(i.ZT).catch(i.ZT)}ensureInfiniteQueryData(t){return t.behavior=f(t.pages),this.ensureQueryData(t)}resumePausedMutations(){return d.N.isOnline()?this.#s.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#h}getMutationCache(){return this.#s}getDefaultOptions(){return this.#l}setDefaultOptions(t){this.#l=t}setQueryDefaults(t,e){this.#c.set((0,i.Ym)(t),{queryKey:t,defaultOptions:e})}getQueryDefaults(t){let e=[...this.#c.values()],s={};return e.forEach(e=>{(0,i.to)(t,e.queryKey)&&Object.assign(s,e.defaultOptions)}),s}setMutationDefaults(t,e){this.#d.set((0,i.Ym)(t),{mutationKey:t,defaultOptions:e})}getMutationDefaults(t){let e=[...this.#d.values()],s={};return e.forEach(e=>{(0,i.to)(t,e.mutationKey)&&Object.assign(s,e.defaultOptions)}),s}defaultQueryOptions(t){if(t._defaulted)return t;let e={...this.#l.queries,...this.getQueryDefaults(t.queryKey),...t,_defaulted:!0};return e.queryHash||(e.queryHash=(0,i.Rm)(e.queryKey,e)),void 0===e.refetchOnReconnect&&(e.refetchOnReconnect="always"!==e.networkMode),void 0===e.throwOnError&&(e.throwOnError=!!e.suspense),!e.networkMode&&e.persister&&(e.networkMode="offlineFirst"),e.queryFn===i.CN&&(e.enabled=!1),e}defaultMutationOptions(t){return t?._defaulted?t:{...this.#l.mutations,...t?.mutationKey&&this.getMutationDefaults(t.mutationKey),...t,_defaulted:!0}}clear(){this.#h.clear(),this.#s.clear()}}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js b/litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js new file mode 100644 index 00000000000..cd70c451170 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1674],{14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return eM}});var r=n(5853),o=n(7084),i=n(26898),a=n(13241),c=n(1153),l=n(2265),s=n(60474),u=n(47625),p=n(93765),f=n(86757),d=n.n(f),y=n(61994),m=n(9841),v=n(81889),h=n(82944),b=["points","className","baseLinePoints","connectNulls"];function g(){return(g=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){k(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),k(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},j=function(e,t){var n=x(e);t&&(n=[n.reduce(function(e,t){return[].concat(A(e),A(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},w=function(e,t,n){var r=j(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(j(t.reverse(),n).slice(1))},P=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,b);if(!t||!t.length)return null;var a=(0,y.Z)("recharts-polygon",n);if(r&&r.length){var c=i.stroke&&"none"!==i.stroke,s=w(t,r,o);return l.createElement("g",{className:a},l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(t,o)})):null,c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(r,o)})):null)}var u=j(t,o);return l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},E=n(58811),S=n(41637),T=n(39206);function L(e){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(){return(R=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=I(I({},(0,h.L6)(this.props,!1)),{},{fill:"none"},(0,h.L6)(o,!1));if("circle"===i)return l.createElement(v.o,R({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var c=this.props.ticks.map(function(e){return(0,T.op)(t,n,r,e.coordinate)});return l.createElement(P,R({className:"recharts-polar-angle-axis-line"},a,{points:c}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,o=t.tick,i=t.tickLine,a=t.tickFormatter,c=t.stroke,s=(0,h.L6)(this.props,!1),u=(0,h.L6)(o,!1),p=I(I({},s),{},{fill:"none"},(0,h.L6)(i,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),d=I(I(I({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:c},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return l.createElement(m.m,R({className:(0,y.Z)("recharts-polar-angle-axis-tick",(0,T.$S)(o)),key:"tick-".concat(t.coordinate)},(0,S.bw)(e.props,t,n)),i&&l.createElement("line",R({className:"recharts-polar-angle-axis-tick-line"},p,f)),o&&r.renderTickItem(o,d,a?a(t.value,n):t.value))});return l.createElement(m.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?l.createElement(m.m,{className:(0,y.Z)("recharts-polar-angle-axis",this.props.className)},r&&this.renderAxisLine(),this.renderTicks()):null}}],n=[{key:"renderTickItem",value:function(e,t,n){return l.isValidElement(e)?l.cloneElement(e,t):d()(e)?e(t):l.createElement(E.x,R({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],t&&C(r.prototype,t),n&&C(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(l.PureComponent);Z(M,"displayName","PolarAngleAxis"),Z(M,"axisType","angleAxis"),Z(M,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var V=n(35802),$=n.n(V),q=n(37891),z=n.n(q),G=n(26680),W=["cx","cy","angle","ticks","axisLine"],Y=["ticks","tick","angle","tickFormatter","stroke"];function H(e){return(H="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function U(){return(U=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ee(e,t){for(var n=0;n0?es()(e,"paddingAngle",0):0;if(n){var c=(0,eb.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),l=ew(ew({},e),{},{startAngle:i+a,endAngle:i+c(r)+a});o.push(l),i=l.endAngle}else{var s=e.endAngle,p=e.startAngle,f=(0,eb.k4)(0,s-p)(r),d=ew(ew({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(d),i=d.endAngle}}),l.createElement(m.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!ep()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,c=t.cy,s=t.innerRadius,u=t.outerRadius,p=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eb.hj)(a)||!(0,eb.hj)(c)||!(0,eb.hj)(s)||!(0,eb.hj)(u))return null;var d=(0,y.Z)("recharts-pie",o);return l.createElement(m.m,{tabIndex:this.props.rootTabIndex,className:d,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),G._.renderCallByParent(this.props,null,!1),(!p||f)&&em.e.renderCallByParent(this.props,r,!1))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?A:A-1)*u,k=a.reduce(function(e,t){var n=(0,eg.F$)(t,g,0);return e+((0,eb.hj)(n)?n:0)},0);return k>0&&(t=a.map(function(e,t){var r,o=(0,eg.F$)(e,g,0),i=(0,eg.F$)(e,f,t),a=((0,eb.hj)(o)?o:0)/k,s=(r=t?n.endAngle+(0,eb.uY)(h)*u*(0!==o?1:0):l)+(0,eb.uY)(h)*((0!==o?m:0)+a*O),p=(r+s)/2,d=(v.innerRadius+v.outerRadius)/2,b=[{name:i,value:o,payload:e,dataKey:g,type:y}],A=(0,T.op)(v.cx,v.cy,d,p);return n=ew(ew(ew({percent:a,cornerRadius:c,name:i,tooltipPayload:b,midAngle:p,middleRadius:d,tooltipPosition:A},e),v),{},{value:(0,eg.F$)(e,g),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,eb.uY)(h)*u})})),ew(ew({},v),{},{sectors:t,data:a})});var eI=(0,p.z)({chartName:"PieChart",GraphicalChild:eN,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:M},{axisType:"radiusAxis",AxisComp:ea}],formatAxisMap:T.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eC=n(8147),eD=n(92666),eF=n(98593);let e_=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return l.createElement(eF.$B,null,l.createElement("div",{className:(0,a.q)("px-4 py-2")},l.createElement(eF.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eZ=(e,t)=>e.map((e,n)=>{let r=ne||t((0,c.vP)(n.map(e=>e[r]))),eK=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c}=e;return l.createElement("g",null,l.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c,fill:"",opacity:.3,style:{outline:"none"}}))},eM=l.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:p="name",colors:f=i.s,variant:d="donut",valueFormatter:y=c.Cj,label:m,showLabel:v=!0,animationDuration:h=900,showAnimation:b=!1,showTooltip:g=!0,noDataText:A,onValueChange:O,customTooltip:k,className:x}=e,j=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),w="donut"==d,P=eB(m,y,n,s),[E,S]=l.useState(void 0),T=!!O;return(0,l.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[E]),l.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",x)},j),l.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?l.createElement(eI,{onClick:T&&E?()=>{S(void 0),null==O||O(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},v&&w?l.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},P):null,l.createElement(eN,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",O?"cursor-pointer":"cursor-default"),data:eZ(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:w?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:p,isAnimationActive:b,animationDuration:h,onClick:function(e,t,n){n.stopPropagation(),T&&(E===t?(S(void 0),null==O||O(null)):(S(t),null==O||O(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:E,inactiveShape:eK,style:{outline:"none"}}),l.createElement(eC.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:g?e=>{var t;let{active:n,payload:r}=e;return k?l.createElement(k,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):l.createElement(e_,{active:n,payload:r,valueFormatter:y})}:l.createElement(l.Fragment,null)})):l.createElement(eD.Z,{noDataText:A})))});eM.displayName="DonutChart"},35802:function(e,t,n){var r=n(67646),o=n(58905),i=n(88157);e.exports=function(e,t){return e&&e.length?r(e,i(t,2),o):void 0}},37891:function(e,t,n){var r=n(67646),o=n(88157),i=n(20121);e.exports=function(e,t){return e&&e.length?r(e,o(t,2),i):void 0}},44633:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});t.Z=o},58710:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=o},92668:function(e,t,n){"use strict";n.d(t,{I:function(){return c}});var r=n(59121),o=n(31091),i=n(63497),a=n(99649);function c(e,t){let{years:n=0,months:c=0,weeks:l=0,days:s=0,hours:u=0,minutes:p=0,seconds:f=0}=t,d=(0,a.Q)(e),y=c||n?(0,o.z)(d,c+12*n):d,m=s||l?(0,r.E)(y,s+7*l):y;return(0,i.L)(e,m.getTime()+1e3*(f+60*(p+60*u)))}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js b/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js new file mode 100644 index 00000000000..90f29480d63 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1994],{4156:function(e,n,t){t.d(n,{Z:function(){return O}});var o=t(2265),a=t(36760),r=t.n(a),c=t(20873),l=t(28791),i=t(6694),s=t(34709),u=t(71744),d=t(86586),b=t(64024),p=t(39109);let f=o.createContext(null);var v=t(23159),m=t(66531),h=function(e,n){var t={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>n.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let g=o.forwardRef((e,n)=>{var t;let{prefixCls:a,className:g,rootClassName:C,children:y,indeterminate:k=!1,style:x,onMouseEnter:O,onMouseLeave:E,skipGroup:S=!1,disabled:w}=e,Z=h(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:P,direction:N,checkbox:j}=o.useContext(u.E_),I=o.useContext(f),{isFormItemInput:R}=o.useContext(p.aM),z=o.useContext(d.Z),B=null!==(t=(null==I?void 0:I.disabled)||w)&&void 0!==t?t:z,D=o.useRef(Z.value),M=o.useRef(null),_=(0,l.sQ)(n,M);o.useEffect(()=>{null==I||I.registerValue(Z.value)},[]),o.useEffect(()=>{if(!S)return Z.value!==D.current&&(null==I||I.cancelValue(D.current),null==I||I.registerValue(Z.value),D.current=Z.value),()=>null==I?void 0:I.cancelValue(Z.value)},[Z.value]),o.useEffect(()=>{var e;(null===(e=M.current)||void 0===e?void 0:e.input)&&(M.current.input.indeterminate=k)},[k]);let W=P("checkbox",a),q=(0,b.Z)(W),[H,T,G]=(0,v.ZP)(W,q),V=Object.assign({},Z);I&&!S&&(V.onChange=function(){for(var e=arguments.length,n=Array(e),t=0;tn.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let x=o.forwardRef((e,n)=>{let{defaultValue:t,children:a,options:c=[],prefixCls:l,className:i,rootClassName:s,style:d,onChange:p}=e,m=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:x}=o.useContext(u.E_),[O,E]=o.useState(m.value||t||[]),[S,w]=o.useState([]);o.useEffect(()=>{"value"in m&&E(m.value||[])},[m.value]);let Z=o.useMemo(()=>c.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[c]),P=e=>{w(n=>n.filter(n=>n!==e))},N=e=>{w(n=>[].concat((0,C.Z)(n),[e]))},j=e=>{let n=O.indexOf(e.value),t=(0,C.Z)(O);-1===n?t.push(e.value):t.splice(n,1),"value"in m||E(t),null==p||p(t.filter(e=>S.includes(e)).sort((e,n)=>Z.findIndex(n=>n.value===e)-Z.findIndex(e=>e.value===n)))},I=h("checkbox",l),R="".concat(I,"-group"),z=(0,b.Z)(I),[B,D,M]=(0,v.ZP)(I,z),_=(0,y.Z)(m,["value","disabled"]),W=c.length?Z.map(e=>o.createElement(g,{prefixCls:I,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:O.includes(e.value),onChange:e.onChange,className:r()("".concat(R,"-item"),e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):a,q=o.useMemo(()=>({toggleOption:j,value:O,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:P}),[j,O,m.disabled,m.name,N,P]),H=r()(R,{["".concat(R,"-rtl")]:"rtl"===x},i,s,M,z,D);return B(o.createElement("div",Object.assign({className:H,style:d},_,{ref:n}),o.createElement(f.Provider,{value:q},W)))});g.Group=x,g.__ANT_CHECKBOX=!0;var O=g},23159:function(e,n,t){t.d(n,{C2:function(){return i}});var o=t(93463),a=t(12918),r=t(71140),c=t(99320);let l=e=>{let{checkboxCls:n}=e,t="".concat(n,"-wrapper");return[{["".concat(n,"-group")]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,["> ".concat(e.antCls,"-row")]:{flex:1}}),[t]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},["& + ".concat(t)]:{marginInlineStart:0},["&".concat(t,"-in-form-item")]:{'input[type="checkbox"]':{width:14,height:14}}}),[n]:Object.assign(Object.assign({},(0,a.Wf)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",["".concat(n,"-input")]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,["&:focus-visible + ".concat(n,"-inner")]:(0,a.oN)(e)},["".concat(n,"-inner")]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:"".concat((0,o.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:"all ".concat(e.motionDurationSlow),"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:"".concat((0,o.bf)(e.lineWidthBold)," solid ").concat(e.colorWhite),borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:"all ".concat(e.motionDurationFast," ").concat(e.motionEaseInBack,", opacity ").concat(e.motionDurationFast)}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{["\n ".concat(t,":not(").concat(t,"-disabled),\n ").concat(n,":not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{borderColor:e.colorPrimary}},["".concat(t,":not(").concat(t,"-disabled)")]:{["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled) ").concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled):after")]:{borderColor:e.colorPrimaryHover}}},{["".concat(n,"-checked")]:{["".concat(n,"-inner")]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:"all ".concat(e.motionDurationMid," ").concat(e.motionEaseOutBack," ").concat(e.motionDurationFast)}}},["\n ".concat(t,"-checked:not(").concat(t,"-disabled),\n ").concat(n,"-checked:not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[n]:{"&-indeterminate":{"&":{["".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorBorder),"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},["&:hover ".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorPrimary)}}}}},{["".concat(t,"-disabled")]:{cursor:"not-allowed"},["".concat(n,"-disabled")]:{["&, ".concat(n,"-input")]:{cursor:"not-allowed",pointerEvents:"none"},["".concat(n,"-inner")]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},["&".concat(n,"-indeterminate ").concat(n,"-inner::after")]:{background:e.colorTextDisabled}}}]};function i(e,n){return l((0,r.IX)(n,{checkboxCls:".".concat(e),checkboxSize:n.controlInteractiveSize}))}n.ZP=(0,c.I$)("Checkbox",(e,n)=>{let{prefixCls:t}=n;return[i(t,e)]})},66531:function(e,n,t){t.d(n,{Z:function(){return r}});var o=t(2265),a=t(53346);function r(e){let n=o.useRef(null),t=()=>{a.Z.cancel(n.current),n.current=null};return[()=>{t(),n.current=(0,a.Z)(()=>{n.current=null})},o=>{n.current&&(o.stopPropagation(),t()),null==e||e(o)}]}},20873:function(e,n,t){var o=t(1119),a=t(31686),r=t(11993),c=t(26365),l=t(6989),i=t(36760),s=t.n(i),u=t(50506),d=t(2265),b=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],p=(0,d.forwardRef)(function(e,n){var t=e.prefixCls,i=void 0===t?"rc-checkbox":t,p=e.className,f=e.style,v=e.checked,m=e.disabled,h=e.defaultChecked,g=e.type,C=void 0===g?"checkbox":g,y=e.title,k=e.onChange,x=(0,l.Z)(e,b),O=(0,d.useRef)(null),E=(0,d.useRef)(null),S=(0,u.Z)(void 0!==h&&h,{value:v}),w=(0,c.Z)(S,2),Z=w[0],P=w[1];(0,d.useImperativeHandle)(n,function(){return{focus:function(e){var n;null===(n=O.current)||void 0===n||n.focus(e)},blur:function(){var e;null===(e=O.current)||void 0===e||e.blur()},input:O.current,nativeElement:E.current}});var N=s()(i,p,(0,r.Z)((0,r.Z)({},"".concat(i,"-checked"),Z),"".concat(i,"-disabled"),m));return d.createElement("span",{className:N,title:y,style:f,ref:E},d.createElement("input",(0,o.Z)({},x,{className:"".concat(i,"-input"),ref:O,onChange:function(n){m||("checked"in e||P(n.target.checked),null==k||k({target:(0,a.Z)((0,a.Z)({},e),{},{type:C,checked:n.target.checked}),stopPropagation:function(){n.stopPropagation()},preventDefault:function(){n.preventDefault()},nativeEvent:n.nativeEvent}))},disabled:m,checked:!!Z,type:C})),d.createElement("span",{className:"".concat(i,"-inner")}))});n.Z=p}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js b/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js deleted file mode 100644 index 3211472683d..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1994],{61994:function(e,n,t){t.d(n,{Z:function(){return O}});var o=t(2265),a=t(36760),r=t.n(a),c=t(20873),l=t(28791),i=t(6694),s=t(34709),u=t(71744),d=t(86586),b=t(64024),p=t(39109);let f=o.createContext(null);var v=t(23159),m=t(66531),h=function(e,n){var t={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>n.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let g=o.forwardRef((e,n)=>{var t;let{prefixCls:a,className:g,rootClassName:C,children:y,indeterminate:k=!1,style:x,onMouseEnter:O,onMouseLeave:E,skipGroup:S=!1,disabled:w}=e,Z=h(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:P,direction:N,checkbox:j}=o.useContext(u.E_),I=o.useContext(f),{isFormItemInput:R}=o.useContext(p.aM),z=o.useContext(d.Z),B=null!==(t=(null==I?void 0:I.disabled)||w)&&void 0!==t?t:z,D=o.useRef(Z.value),M=o.useRef(null),_=(0,l.sQ)(n,M);o.useEffect(()=>{null==I||I.registerValue(Z.value)},[]),o.useEffect(()=>{if(!S)return Z.value!==D.current&&(null==I||I.cancelValue(D.current),null==I||I.registerValue(Z.value),D.current=Z.value),()=>null==I?void 0:I.cancelValue(Z.value)},[Z.value]),o.useEffect(()=>{var e;(null===(e=M.current)||void 0===e?void 0:e.input)&&(M.current.input.indeterminate=k)},[k]);let W=P("checkbox",a),q=(0,b.Z)(W),[H,T,G]=(0,v.ZP)(W,q),V=Object.assign({},Z);I&&!S&&(V.onChange=function(){for(var e=arguments.length,n=Array(e),t=0;tn.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let x=o.forwardRef((e,n)=>{let{defaultValue:t,children:a,options:c=[],prefixCls:l,className:i,rootClassName:s,style:d,onChange:p}=e,m=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:x}=o.useContext(u.E_),[O,E]=o.useState(m.value||t||[]),[S,w]=o.useState([]);o.useEffect(()=>{"value"in m&&E(m.value||[])},[m.value]);let Z=o.useMemo(()=>c.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[c]),P=e=>{w(n=>n.filter(n=>n!==e))},N=e=>{w(n=>[].concat((0,C.Z)(n),[e]))},j=e=>{let n=O.indexOf(e.value),t=(0,C.Z)(O);-1===n?t.push(e.value):t.splice(n,1),"value"in m||E(t),null==p||p(t.filter(e=>S.includes(e)).sort((e,n)=>Z.findIndex(n=>n.value===e)-Z.findIndex(e=>e.value===n)))},I=h("checkbox",l),R="".concat(I,"-group"),z=(0,b.Z)(I),[B,D,M]=(0,v.ZP)(I,z),_=(0,y.Z)(m,["value","disabled"]),W=c.length?Z.map(e=>o.createElement(g,{prefixCls:I,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:O.includes(e.value),onChange:e.onChange,className:r()("".concat(R,"-item"),e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):a,q=o.useMemo(()=>({toggleOption:j,value:O,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:P}),[j,O,m.disabled,m.name,N,P]),H=r()(R,{["".concat(R,"-rtl")]:"rtl"===x},i,s,M,z,D);return B(o.createElement("div",Object.assign({className:H,style:d},_,{ref:n}),o.createElement(f.Provider,{value:q},W)))});g.Group=x,g.__ANT_CHECKBOX=!0;var O=g},23159:function(e,n,t){t.d(n,{C2:function(){return i}});var o=t(93463),a=t(12918),r=t(71140),c=t(99320);let l=e=>{let{checkboxCls:n}=e,t="".concat(n,"-wrapper");return[{["".concat(n,"-group")]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,["> ".concat(e.antCls,"-row")]:{flex:1}}),[t]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},["& + ".concat(t)]:{marginInlineStart:0},["&".concat(t,"-in-form-item")]:{'input[type="checkbox"]':{width:14,height:14}}}),[n]:Object.assign(Object.assign({},(0,a.Wf)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",["".concat(n,"-input")]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,["&:focus-visible + ".concat(n,"-inner")]:(0,a.oN)(e)},["".concat(n,"-inner")]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:"".concat((0,o.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:"all ".concat(e.motionDurationSlow),"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:"".concat((0,o.bf)(e.lineWidthBold)," solid ").concat(e.colorWhite),borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:"all ".concat(e.motionDurationFast," ").concat(e.motionEaseInBack,", opacity ").concat(e.motionDurationFast)}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{["\n ".concat(t,":not(").concat(t,"-disabled),\n ").concat(n,":not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{borderColor:e.colorPrimary}},["".concat(t,":not(").concat(t,"-disabled)")]:{["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled) ").concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled):after")]:{borderColor:e.colorPrimaryHover}}},{["".concat(n,"-checked")]:{["".concat(n,"-inner")]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:"all ".concat(e.motionDurationMid," ").concat(e.motionEaseOutBack," ").concat(e.motionDurationFast)}}},["\n ".concat(t,"-checked:not(").concat(t,"-disabled),\n ").concat(n,"-checked:not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[n]:{"&-indeterminate":{"&":{["".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorBorder),"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},["&:hover ".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorPrimary)}}}}},{["".concat(t,"-disabled")]:{cursor:"not-allowed"},["".concat(n,"-disabled")]:{["&, ".concat(n,"-input")]:{cursor:"not-allowed",pointerEvents:"none"},["".concat(n,"-inner")]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},["&".concat(n,"-indeterminate ").concat(n,"-inner::after")]:{background:e.colorTextDisabled}}}]};function i(e,n){return l((0,r.IX)(n,{checkboxCls:".".concat(e),checkboxSize:n.controlInteractiveSize}))}n.ZP=(0,c.I$)("Checkbox",(e,n)=>{let{prefixCls:t}=n;return[i(t,e)]})},66531:function(e,n,t){t.d(n,{Z:function(){return r}});var o=t(2265),a=t(53346);function r(e){let n=o.useRef(null),t=()=>{a.Z.cancel(n.current),n.current=null};return[()=>{t(),n.current=(0,a.Z)(()=>{n.current=null})},o=>{n.current&&(o.stopPropagation(),t()),null==e||e(o)}]}},20873:function(e,n,t){var o=t(1119),a=t(31686),r=t(11993),c=t(26365),l=t(6989),i=t(36760),s=t.n(i),u=t(50506),d=t(2265),b=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],p=(0,d.forwardRef)(function(e,n){var t=e.prefixCls,i=void 0===t?"rc-checkbox":t,p=e.className,f=e.style,v=e.checked,m=e.disabled,h=e.defaultChecked,g=e.type,C=void 0===g?"checkbox":g,y=e.title,k=e.onChange,x=(0,l.Z)(e,b),O=(0,d.useRef)(null),E=(0,d.useRef)(null),S=(0,u.Z)(void 0!==h&&h,{value:v}),w=(0,c.Z)(S,2),Z=w[0],P=w[1];(0,d.useImperativeHandle)(n,function(){return{focus:function(e){var n;null===(n=O.current)||void 0===n||n.focus(e)},blur:function(){var e;null===(e=O.current)||void 0===e||e.blur()},input:O.current,nativeElement:E.current}});var N=s()(i,p,(0,r.Z)((0,r.Z)({},"".concat(i,"-checked"),Z),"".concat(i,"-disabled"),m));return d.createElement("span",{className:N,title:y,style:f,ref:E},d.createElement("input",(0,o.Z)({},x,{className:"".concat(i,"-input"),ref:O,onChange:function(n){m||("checked"in e||P(n.target.checked),null==k||k({target:(0,a.Z)((0,a.Z)({},e),{},{type:C,checked:n.target.checked}),stopPropagation:function(){n.stopPropagation()},preventDefault:function(){n.preventDefault()},nativeEvent:n.nativeEvent}))},disabled:m,checked:!!Z,type:C})),d.createElement("span",{className:"".concat(i,"-inner")}))});n.Z=p}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2012-7e2773c79199687c.js b/litellm/proxy/_experimental/out/_next/static/chunks/2012-7e2773c79199687c.js deleted file mode 100644 index 08e3a6a8542..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2012-7e2773c79199687c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2012],{26210:function(e,l,s){s.d(l,{UQ:function(){return t.Z},X1:function(){return i.Z},_m:function(){return a.Z},oi:function(){return n.Z},xv:function(){return r.Z}});var t=s(87452),i=s(88829),a=s(72208),r=s(84264),n=s(49566)},30078:function(e,l,s){s.d(l,{Ct:function(){return t.Z},Dx:function(){return x.Z},OK:function(){return n.Z},Zb:function(){return a.Z},nP:function(){return c.Z},oi:function(){return h.Z},rj:function(){return r.Z},td:function(){return d.Z},v0:function(){return m.Z},x4:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(67101),n=s(12485),m=s(18135),d=s(35242),o=s(29706),c=s(77991),u=s(84264),h=s(49566),x=s(96761)},62490:function(e,l,s){s.d(l,{Ct:function(){return t.Z},RM:function(){return n.Z},SC:function(){return c.Z},Zb:function(){return a.Z},iA:function(){return r.Z},pj:function(){return m.Z},ss:function(){return d.Z},xs:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(21626),n=s(97214),m=s(28241),d=s(58834),o=s(69552),c=s(71876),u=s(84264)},11318:function(e,l,s){s.d(l,{Z:function(){return n}});var t=s(2265),i=s(80443),a=s(19250);let r=async(e,l,s,t)=>"Admin"!=s&&"Admin Viewer"!=s?await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null,l):await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null);var n=()=>{let[e,l]=(0,t.useState)([]),{accessToken:s,userId:a,userRole:n}=(0,i.Z)();return(0,t.useEffect)(()=>{(async()=>{l(await r(s,a,n,null))})()},[s,a,n]),{teams:e,setTeams:l}}},21609:function(e,l,s){s.d(l,{Z:function(){return o}});var t=s(57437),i=s(57840),a=s(22116),r=s(51653),n=s(76188),m=s(4260),d=s(2265);function o(e){let{isOpen:l,title:s,alertMessage:o,message:c,resourceInformationTitle:u,resourceInformation:h,onCancel:x,onOk:b,confirmLoading:p,requiredConfirmation:g}=e,{Title:_,Text:v}=i.default,[j,f]=(0,d.useState)("");return(0,d.useEffect)(()=>{l&&f("")},[l]),(0,t.jsx)(a.Z,{title:s,open:l,onOk:b,onCancel:x,confirmLoading:p,okText:p?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!g&&j!==g||p},cancelButtonProps:{disabled:p},children:(0,t.jsxs)("div",{className:"space-y-4",children:[o&&(0,t.jsx)(r.Z,{message:o,type:"warning"}),(0,t.jsxs)("div",{className:"mt-4 p-4 bg-red-50 rounded-lg border border-red-200",children:[(0,t.jsx)(_,{level:5,className:"mb-3 text-gray-900",children:u}),(0,t.jsx)(n.Z,{column:1,size:"small",children:h&&h.map(e=>{let{label:l,value:s,...i}=e;return(0,t.jsx)(n.Z.Item,{label:(0,t.jsx)("span",{className:"font-semibold text-gray-700",children:l}),children:(0,t.jsx)(v,{...i,children:null!=s?s:"-"})},l)})})]}),(0,t.jsx)("div",{children:(0,t.jsx)(v,{children:c})}),g&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200",children:[(0,t.jsxs)(v,{className:"block text-base font-medium text-gray-700 mb-2",children:[(0,t.jsx)(v,{children:"Type "}),(0,t.jsx)(v,{strong:!0,type:"danger",children:g}),(0,t.jsx)(v,{children:" to confirm deletion:"})]}),(0,t.jsx)(m.default,{value:j,onChange:e=>f(e.target.value),placeholder:g,className:"rounded-md text-base border-gray-200",autoFocus:!0})]})]})})}},33860:function(e,l,s){var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(37592),m=s(99981),d=s(5545),o=s(7310),c=s.n(o),u=s(19250);l.Z=e=>{let{isVisible:l,onCancel:s,onSubmit:o,accessToken:h,title:x="Add Team Member",roles:b=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:p="user"}=e,[g]=a.Z.useForm(),[_,v]=(0,i.useState)([]),[j,f]=(0,i.useState)(!1),[Z,y]=(0,i.useState)("user_email"),N=async(e,l)=>{if(!e){v([]);return}f(!0);try{let s=new URLSearchParams;if(s.append(l,e),null==h)return;let t=(await (0,u.userFilterUICall)(h,s)).map(e=>({label:"user_email"===l?"".concat(e.user_email):"".concat(e.user_id),value:"user_email"===l?e.user_email:e.user_id,user:e}));v(t)}catch(e){console.error("Error fetching users:",e)}finally{f(!1)}},w=(0,i.useCallback)(c()((e,l)=>N(e,l),300),[]),k=(e,l)=>{y(l),w(e,l)},M=(e,l)=>{let s=l.user;g.setFieldsValue({user_email:s.user_email,user_id:s.user_id,role:g.getFieldValue("role")})};return(0,t.jsx)(r.Z,{title:x,open:l,onCancel:()=>{g.resetFields(),v([]),s()},footer:null,width:800,children:(0,t.jsxs)(a.Z,{form:g,onFinish:o,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:p},children:[(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>k(e,"user_email"),onSelect:(e,l)=>M(e,l),options:"user_email"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>k(e,"user_id"),onSelect:(e,l)=>M(e,l),options:"user_id"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)(a.Z.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(n.default,{defaultValue:p,children:b.map(e=>(0,t.jsx)(n.default.Option,{value:e.value,children:(0,t.jsxs)(m.Z,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(d.ZP,{type:"default",htmlType:"submit",children:"Add Member"})})]})})}},10901:function(e,l,s){s.d(l,{Z:function(){return h}});var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(5545),m=s(27281),d=s(57365),o=s(49566),c=s(92280),u=s(24199),h=e=>{var l,s,h;let{visible:x,onCancel:b,onSubmit:p,initialData:g,mode:_,config:v}=e,[j]=a.Z.useForm();console.log("Initial Data:",g),(0,i.useEffect)(()=>{if(x){if("edit"===_&&g){let e={...g,role:g.role||v.defaultRole,max_budget_in_team:g.max_budget_in_team||null,tpm_limit:g.tpm_limit||null,rpm_limit:g.rpm_limit||null};console.log("Setting form values:",e),j.setFieldsValue(e)}else{var e;j.resetFields(),j.setFieldsValue({role:v.defaultRole||(null===(e=v.roleOptions[0])||void 0===e?void 0:e.value)})}}},[x,g,_,j,v.defaultRole,v.roleOptions]);let f=async e=>{try{let l=Object.entries(e).reduce((e,l)=>{let[s,t]=l;if("string"==typeof t){let l=t.trim();return""===l&&("max_budget_in_team"===s||"tpm_limit"===s||"rpm_limit"===s)?{...e,[s]:null}:{...e,[s]:l}}return{...e,[s]:t}},{});console.log("Submitting form data:",l),p(l),j.resetFields()}catch(e){console.error("Form submission error:",e)}},Z=e=>{switch(e.type){case"input":return(0,t.jsx)(o.Z,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(u.Z,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":var l;return(0,t.jsx)(m.Z,{children:null===(l=e.options)||void 0===l?void 0:l.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))});default:return null}};return(0,t.jsx)(r.Z,{title:v.title||("add"===_?"Add Member":"Edit Member"),open:x,width:1e3,footer:null,onCancel:b,children:(0,t.jsxs)(a.Z,{form:j,onFinish:f,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[v.showEmail&&(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(o.Z,{placeholder:"user@example.com"})}),v.showEmail&&v.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(c.x,{children:"OR"})}),v.showUserId&&(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(o.Z,{placeholder:"user_123"})}),(0,t.jsx)(a.Z.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===_&&g&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(s=g.role,(null===(h=v.roleOptions.find(e=>e.value===s))||void 0===h?void 0:h.label)||s),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(m.Z,{children:"edit"===_&&g?[...v.roleOptions.filter(e=>e.value===g.role),...v.roleOptions.filter(e=>e.value!==g.role)].map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value)):v.roleOptions.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))})}),null===(l=v.additionalFields)||void 0===l?void 0:l.map(e=>(0,t.jsx)(a.Z.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:Z(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(n.ZP,{onClick:b,className:"mr-2",children:"Cancel"}),(0,t.jsx)(n.ZP,{type:"default",htmlType:"submit",children:"add"===_?"Add Member":"Save Changes"})]})]})})}},33293:function(e,l,s){s.d(l,{Z:function(){return el}});var t=s(57437),i=s(33860),a=s(19250),r=s(59872),n=s(33304),m=s(15424),d=s(10900),o=s(30078),c=s(10032),u=s(42264),h=s(5545),x=s(4260),b=s(37592),p=s(99981),g=s(63709),_=s(30401),v=s(78867),j=s(2265),f=s(21609),Z=s(95096),y=s(46468),N=s(27799),w=s(95920),k=s(68473),M=s(9114),C=s(98015),S=s(24199),T=s(97415),I=s(10901),P=s(21425),L=s(78489),F=s(12514),E=s(21626),O=s(97214),D=s(28241),A=s(58834),R=s(69552),U=s(71876),z=s(84264),B=s(96761),V=s(61994),q=s(85180),G=s(89245),K=s(78355);let $={"/key/generate":"Member can generate a virtual key for this team","/key/service-account/generate":"Member can generate a service account key (not belonging to any user) for this team","/key/update":"Member can update a virtual key belonging to this team","/key/delete":"Member can delete a virtual key belonging to this team","/key/info":"Member can get info about a virtual key belonging to this team","/key/regenerate":"Member can regenerate a virtual key belonging to this team","/key/{key_id}/regenerate":"Member can regenerate a virtual key belonging to this team","/key/list":"Member can list virtual keys belonging to this team","/key/block":"Member can block a virtual key belonging to this team","/key/unblock":"Member can unblock a virtual key belonging to this team"},J=e=>e.includes("/info")||e.includes("/list")?"GET":"POST",Q=e=>{let l=J(e),s=$[e];if(!s){for(let[l,t]of Object.entries($))if(e.includes(l)){s=t;break}}return s||(s="Access ".concat(e)),{method:l,endpoint:e,description:s,route:e}};var W=e=>{let{teamId:l,accessToken:s,canEditTeam:i}=e,[r,n]=(0,j.useState)([]),[m,d]=(0,j.useState)([]),[o,c]=(0,j.useState)(!0),[u,x]=(0,j.useState)(!1),[b,p]=(0,j.useState)(!1),g=async()=>{try{if(c(!0),!s)return;let e=await (0,a.getTeamPermissionsCall)(s,l),t=e.all_available_permissions||[];n(t);let i=e.team_member_permissions||[];d(i),p(!1)}catch(e){M.Z.fromBackend("Failed to load permissions"),console.error("Error fetching permissions:",e)}finally{c(!1)}};(0,j.useEffect)(()=>{g()},[l,s]);let _=(e,l)=>{d(l?[...m,e]:m.filter(l=>l!==e)),p(!0)},v=async()=>{try{if(!s)return;x(!0),await (0,a.teamPermissionsUpdateCall)(s,l,m),M.Z.success("Permissions updated successfully"),p(!1)}catch(e){M.Z.fromBackend("Failed to update permissions"),console.error("Error updating permissions:",e)}finally{x(!1)}};if(o)return(0,t.jsx)("div",{className:"p-6 text-center",children:"Loading permissions..."});let f=r.length>0;return(0,t.jsxs)(F.Z,{className:"bg-white shadow-md rounded-md p-6",children:[(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row justify-between items-start sm:items-center border-b pb-4 mb-6",children:[(0,t.jsx)(B.Z,{className:"mb-2 sm:mb-0",children:"Member Permissions"}),i&&b&&(0,t.jsxs)("div",{className:"flex gap-3",children:[(0,t.jsx)(h.ZP,{icon:(0,t.jsx)(G.Z,{}),onClick:()=>{g()},children:"Reset"}),(0,t.jsxs)(L.Z,{onClick:v,loading:u,className:"flex items-center gap-2",children:[(0,t.jsx)(K.Z,{})," Save Changes"]})]})]}),(0,t.jsx)(z.Z,{className:"mb-6 text-gray-600",children:"Control what team members can do when they are not team admins."}),f?(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:" min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"Method"}),(0,t.jsx)(R.Z,{children:"Endpoint"}),(0,t.jsx)(R.Z,{children:"Description"}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:"Allow Access"})]})}),(0,t.jsx)(O.Z,{children:r.map(e=>{let l=Q(e);return(0,t.jsxs)(U.Z,{className:"hover:bg-gray-50 transition-colors",children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"px-2 py-1 rounded text-xs font-medium ".concat("GET"===l.method?"bg-blue-100 text-blue-800":"bg-green-100 text-green-800"),children:l.method})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"font-mono text-sm text-gray-800",children:l.endpoint})}),(0,t.jsx)(D.Z,{className:"text-gray-700",children:l.description}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:(0,t.jsx)(V.Z,{checked:m.includes(e),onChange:l=>_(e,l.target.checked),disabled:!i})})]},e)})})]})}):(0,t.jsx)("div",{className:"py-12",children:(0,t.jsx)(q.Z,{description:"No permissions available"})})]})},X=s(47323),Y=s(53410),H=s(74998),ee=e=>{let{teamData:l,canEditTeam:s,handleMemberDelete:i,setSelectedEditMember:a,setIsEditMemberModalVisible:n,setIsAddMemberModalVisible:d}=e,o=e=>{if(null==e)return"0";if("number"==typeof e){let l=Number(e);return l===Math.floor(l)?l.toString():(0,r.pw)(l,8).replace(/\.?0+$/,"")}return"0"},c=e=>{if(!e)return 0;let s=l.team_memberships.find(l=>l.user_id===e);return(null==s?void 0:s.spend)||0},u=e=>{var s;if(!e)return null;let t=l.team_memberships.find(l=>l.user_id===e);console.log("membership=".concat(t));let i=null==t?void 0:null===(s=t.litellm_budget_table)||void 0===s?void 0:s.max_budget;return null==i?null:o(i)},h=e=>{var s,t;if(!e)return"No Limits";let i=l.team_memberships.find(l=>l.user_id===e),a=null==i?void 0:null===(s=i.litellm_budget_table)||void 0===s?void 0:s.rpm_limit,r=null==i?void 0:null===(t=i.litellm_budget_table)||void 0===t?void 0:t.tpm_limit,n=[a?"".concat(o(a)," RPM"):null,r?"".concat(o(r)," TPM"):null].filter(Boolean);return n.length>0?n.join(" / "):"No Limits"};return(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)(F.Z,{className:"w-full mx-auto flex-auto overflow-auto max-h-[50vh]",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:"min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"User ID"}),(0,t.jsx)(R.Z,{children:"User Email"}),(0,t.jsx)(R.Z,{children:"Role"}),(0,t.jsxs)(R.Z,{children:["Team Member Spend (USD)"," ",(0,t.jsx)(p.Z,{title:"This is the amount spent by a user in the team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{children:"Team Member Budget (USD)"}),(0,t.jsxs)(R.Z,{children:["Team Member Rate Limits"," ",(0,t.jsx)(p.Z,{title:"Rate limits for this member's usage within this team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:"Actions"})]})}),(0,t.jsx)(O.Z,{children:l.team_info.members_with_roles.map((e,m)=>(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_id})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_email?e.user_email:"No Email"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.role})}),(0,t.jsx)(D.Z,{children:(0,t.jsxs)(z.Z,{className:"font-mono",children:["$",(0,r.pw)(c(e.user_id),4)]})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:u(e.user_id)?"$".concat((0,r.pw)(Number(u(e.user_id)),4)):"No Limit"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:h(e.user_id)})}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:s&&(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(X.Z,{icon:Y.Z,size:"sm",onClick:()=>{var s,t,i;let r=l.team_memberships.find(l=>l.user_id===e.user_id);a({...e,max_budget_in_team:(null==r?void 0:null===(s=r.litellm_budget_table)||void 0===s?void 0:s.max_budget)||null,tpm_limit:(null==r?void 0:null===(t=r.litellm_budget_table)||void 0===t?void 0:t.tpm_limit)||null,rpm_limit:(null==r?void 0:null===(i=r.litellm_budget_table)||void 0===i?void 0:i.rpm_limit)||null}),n(!0)},className:"cursor-pointer hover:text-blue-600"}),(0,t.jsx)(X.Z,{icon:H.Z,size:"sm",onClick:()=>i(e),className:"cursor-pointer hover:text-red-600"})]})})]},m))})]})})}),(0,t.jsx)(L.Z,{onClick:()=>d(!0),children:"Add Member"})]})},el=e=>{var l,s,L,F,E,O,D,A,R,U,z,B,V,q,G,K,$,J;let{teamId:Q,onClose:X,accessToken:Y,is_team_admin:H,is_proxy_admin:el,userModels:es,editTeam:et,premiumUser:ei=!1,onUpdate:ea}=e,[er,en]=(0,j.useState)(null),[em,ed]=(0,j.useState)(!0),[eo,ec]=(0,j.useState)(!1),[eu]=c.Z.useForm(),[eh,ex]=(0,j.useState)(!1),[eb,ep]=(0,j.useState)(null),[eg,e_]=(0,j.useState)(!1),[ev,ej]=(0,j.useState)([]),[ef,eZ]=(0,j.useState)(!1),[ey,eN]=(0,j.useState)({}),[ew,ek]=(0,j.useState)([]),[eM,eC]=(0,j.useState)(null),[eS,eT]=(0,j.useState)(!1),[eI,eP]=(0,j.useState)(!1);console.log("userModels in team info",es);let eL=H||el,eF=async()=>{try{if(ed(!0),!Y)return;let e=await (0,a.teamInfoCall)(Y,Q);en(e)}catch(e){M.Z.fromBackend("Failed to load team information"),console.error("Error fetching team info:",e)}finally{ed(!1)}};(0,j.useEffect)(()=>{eF()},[Q,Y]),(0,j.useEffect)(()=>{(async()=>{try{if(!Y)return;let e=(await (0,a.getGuardrailsList)(Y)).guardrails.map(e=>e.guardrail_name);ek(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})()},[Y]);let eE=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,a.teamMemberAddCall)(Y,Q,l),M.Z.success("Team member added successfully"),ec(!1),eu.resetFields();let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(i){var l,s,t;let e="Failed to add team member";(null==i?void 0:null===(t=i.raw)||void 0===t?void 0:null===(s=t.detail)||void 0===s?void 0:null===(l=s.error)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==i?void 0:i.message)&&(e=i.message),M.Z.fromBackend(e),console.error("Error adding team member:",i)}},eO=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role,max_budget_in_team:e.max_budget_in_team,tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit};console.log("Updating member with values:",l),u.ZP.destroy(),await (0,a.teamMemberUpdateCall)(Y,Q,l),M.Z.success("Team member updated successfully"),ex(!1);let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(t){var l,s;let e="Failed to update team member";(null==t?void 0:null===(s=t.raw)||void 0===s?void 0:null===(l=s.detail)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==t?void 0:t.message)&&(e=t.message),ex(!1),u.ZP.destroy(),M.Z.fromBackend(e),console.error("Error updating team member:",t)}},eD=async()=>{if(eM&&Y){eP(!0);try{await (0,a.teamMemberDeleteCall)(Y,Q,eM),M.Z.success("Team member removed successfully");let e=await (0,a.teamInfoCall)(Y,Q);en(e),ea(e)}catch(e){M.Z.fromBackend("Failed to remove team member"),console.error("Error removing team member:",e)}finally{eP(!1),eT(!1),eC(null)}}},eA=async e=>{try{if(!Y)return;let l={};try{l=e.metadata?JSON.parse(e.metadata):{}}catch(e){M.Z.fromBackend("Invalid JSON in metadata field");return}let s=e=>null==e||"string"==typeof e&&""===e.trim()||"number"==typeof e&&Number.isNaN(e)?null:e,t={team_id:Q,team_alias:e.team_alias,models:e.models,tpm_limit:s(e.tpm_limit),rpm_limit:s(e.rpm_limit),max_budget:e.max_budget,budget_duration:e.budget_duration,metadata:{...l,guardrails:e.guardrails||[],logging:e.logging_settings||[]},organization_id:e.organization_id};t.max_budget=(0,n.C)(t.max_budget),void 0!==e.team_member_budget&&(t.team_member_budget=Number(e.team_member_budget)),void 0!==e.team_member_key_duration&&(t.team_member_key_duration=e.team_member_key_duration),(void 0!==e.team_member_tpm_limit||void 0!==e.team_member_rpm_limit)&&(t.team_member_tpm_limit=s(e.team_member_tpm_limit),t.team_member_rpm_limit=s(e.team_member_rpm_limit));let{servers:i,accessGroups:r}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]},m=new Set(i||[]),d=Object.fromEntries(Object.entries(e.mcp_tool_permissions||{}).filter(e=>{let[l]=e;return m.has(l)}));t.object_permission={},i&&(t.object_permission.mcp_servers=i),r&&(t.object_permission.mcp_access_groups=r),d&&(t.object_permission.mcp_tool_permissions=d),delete e.mcp_servers_and_groups,delete e.mcp_tool_permissions,await (0,a.teamUpdateCall)(Y,t),M.Z.success("Team settings updated successfully"),e_(!1),eF()}catch(e){console.error("Error updating team:",e)}};if(em)return(0,t.jsx)("div",{className:"p-4",children:"Loading..."});if(!(null==er?void 0:er.team_info))return(0,t.jsx)("div",{className:"p-4",children:"Team not found"});let{team_info:eR}=er,eU=async(e,l)=>{await (0,r.vQ)(e)&&(eN(e=>({...e,[l]:!0})),setTimeout(()=>{eN(e=>({...e,[l]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)(o.zx,{icon:d.Z,variant:"light",onClick:X,className:"mb-4",children:"Back to Teams"}),(0,t.jsx)(o.Dx,{children:eR.team_alias}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(o.xv,{className:"text-gray-500 font-mono",children:eR.team_id}),(0,t.jsx)(h.ZP,{type:"text",size:"small",icon:ey["team-id"]?(0,t.jsx)(_.Z,{size:12}):(0,t.jsx)(v.Z,{size:12}),onClick:()=>eU(eR.team_id,"team-id"),className:"left-2 z-10 transition-all duration-200 ".concat(ey["team-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,t.jsxs)(o.v0,{defaultIndex:et?3:0,children:[(0,t.jsx)(o.td,{className:"mb-4",children:[(0,t.jsx)(o.OK,{children:"Overview"},"overview"),...eL?[(0,t.jsx)(o.OK,{children:"Members"},"members"),(0,t.jsx)(o.OK,{children:"Member Permissions"},"member-permissions"),(0,t.jsx)(o.OK,{children:"Settings"},"settings")]:[]]}),(0,t.jsxs)(o.nP,{children:[(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Budget Status"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.Dx,{children:["$",(0,r.pw)(eR.spend,4)]}),(0,t.jsxs)(o.xv,{children:["of ",null===eR.max_budget?"Unlimited":"$".concat((0,r.pw)(eR.max_budget,4))]}),eR.budget_duration&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Reset: ",eR.budget_duration]}),(0,t.jsx)("br",{}),eR.team_member_budget_table&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Team Member Budget: $",(0,r.pw)(eR.team_member_budget_table.max_budget,4)]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Rate Limits"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)(o.xv,{children:["RPM: ",eR.rpm_limit||"Unlimited"]}),eR.max_parallel_requests&&(0,t.jsxs)(o.xv,{children:["Max Parallel Requests: ",eR.max_parallel_requests]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Models"}),(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===eR.models.length?(0,t.jsx)(o.Ct,{color:"red",children:"All proxy models"}):eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{className:"font-semibold text-gray-900",children:"Virtual Keys"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["User Keys: ",er.keys.filter(e=>e.user_id).length]}),(0,t.jsxs)(o.xv,{children:["Service Account Keys: ",er.keys.filter(e=>!e.user_id).length]}),(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Total: ",er.keys.length]})]})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"card",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(l=eR.metadata)||void 0===l?void 0:l.logging)||[],disabledCallbacks:[],variant:"card"})]})}),(0,t.jsx)(o.x4,{children:(0,t.jsx)(ee,{teamData:er,canEditTeam:eL,handleMemberDelete:e=>{eC(e),eT(!0)},setSelectedEditMember:ep,setIsEditMemberModalVisible:ex,setIsAddMemberModalVisible:ec})}),eL&&(0,t.jsx)(o.x4,{children:(0,t.jsx)(W,{teamId:Q,accessToken:Y,canEditTeam:eL})}),(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.Zb,{className:"overflow-y-auto max-h-[65vh]",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(o.Dx,{children:"Team Settings"}),eL&&!eg&&(0,t.jsx)(o.zx,{onClick:()=>e_(!0),children:"Edit Settings"})]}),eg?(0,t.jsxs)(c.Z,{form:eu,onFinish:eA,initialValues:{...eR,team_alias:eR.team_alias,models:eR.models,tpm_limit:eR.tpm_limit,rpm_limit:eR.rpm_limit,max_budget:eR.max_budget,budget_duration:eR.budget_duration,team_member_tpm_limit:null===(s=eR.team_member_budget_table)||void 0===s?void 0:s.tpm_limit,team_member_rpm_limit:null===(L=eR.team_member_budget_table)||void 0===L?void 0:L.rpm_limit,guardrails:(null===(F=eR.metadata)||void 0===F?void 0:F.guardrails)||[],disable_global_guardrails:(null===(E=eR.metadata)||void 0===E?void 0:E.disable_global_guardrails)||!1,metadata:eR.metadata?JSON.stringify((e=>{let{logging:l,...s}=e;return s})(eR.metadata),null,2):"",logging_settings:(null===(O=eR.metadata)||void 0===O?void 0:O.logging)||[],organization_id:eR.organization_id,vector_stores:(null===(D=eR.object_permission)||void 0===D?void 0:D.vector_stores)||[],mcp_servers:(null===(A=eR.object_permission)||void 0===A?void 0:A.mcp_servers)||[],mcp_access_groups:(null===(R=eR.object_permission)||void 0===R?void 0:R.mcp_access_groups)||[],mcp_servers_and_groups:{servers:(null===(U=eR.object_permission)||void 0===U?void 0:U.mcp_servers)||[],accessGroups:(null===(z=eR.object_permission)||void 0===z?void 0:z.mcp_access_groups)||[]},mcp_tool_permissions:(null===(B=eR.object_permission)||void 0===B?void 0:B.mcp_tool_permissions)||{}},layout:"vertical",children:[(0,t.jsx)(c.Z.Item,{label:"Team Name",name:"team_alias",rules:[{required:!0,message:"Please input a team name"}],children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Please select at least one model"}],children:(0,t.jsxs)(b.default,{mode:"multiple",placeholder:"Select models",children:[(el||es.includes("all-proxy-models"))&&(0,t.jsx)(b.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,t.jsx)(b.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),Array.from(new Set(es)).map((e,l)=>(0,t.jsx)(b.default.Option,{value:e,children:(0,y.W0)(e)},l))]})}),(0,t.jsx)(c.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Budget (USD)",name:"team_member_budget",tooltip:"This is the individual budget for a user in the team.",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Key Duration (eg: 1d, 1mo)",name:"team_member_key_duration",tooltip:"Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",children:(0,t.jsx)(o.oi,{placeholder:"e.g., 30d"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member TPM Limit",name:"team_member_tpm_limit",tooltip:"Default tokens per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 1000"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member RPM Limit",name:"team_member_rpm_limit",tooltip:"Default requests per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 100"})}),(0,t.jsx)(c.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsxs)(b.default,{placeholder:"n/a",children:[(0,t.jsx)(b.default.Option,{value:"24h",children:"daily"}),(0,t.jsx)(b.default.Option,{value:"7d",children:"weekly"}),(0,t.jsx)(b.default.Option,{value:"30d",children:"monthly"})]})}),(0,t.jsx)(c.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(p.Z,{title:"Setup your first guardrail",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",help:"Select existing guardrails or enter new ones",children:(0,t.jsx)(b.default,{mode:"tags",placeholder:"Select or enter guardrails",options:ew.map(e=>({value:e,label:e}))})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,t.jsx)(p.Z,{title:"When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),name:"disable_global_guardrails",valuePropName:"checked",help:"Bypass global guardrails for this team",children:(0,t.jsx)(g.Z,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(c.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,t.jsx)(T.Z,{onChange:e=>eu.setFieldValue("vector_stores",e),value:eu.getFieldValue("vector_stores"),accessToken:Y||"",placeholder:"Select vector stores"})}),(0,t.jsx)(c.Z.Item,{label:"Allowed Pass Through Routes",name:"allowed_passthrough_routes",children:(0,t.jsx)(Z.Z,{onChange:e=>eu.setFieldValue("allowed_passthrough_routes",e),value:eu.getFieldValue("allowed_passthrough_routes"),accessToken:Y||"",placeholder:"Select pass through routes"})}),(0,t.jsx)(c.Z.Item,{label:"MCP Servers / Access Groups",name:"mcp_servers_and_groups",children:(0,t.jsx)(w.Z,{onChange:e=>eu.setFieldValue("mcp_servers_and_groups",e),value:eu.getFieldValue("mcp_servers_and_groups"),accessToken:Y||"",placeholder:"Select MCP servers or access groups (optional)"})}),(0,t.jsx)(c.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(x.default,{type:"hidden"})}),(0,t.jsx)(c.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.mcp_servers_and_groups!==l.mcp_servers_and_groups||e.mcp_tool_permissions!==l.mcp_tool_permissions,children:()=>{var e;return(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(k.Z,{accessToken:Y||"",selectedServers:(null===(e=eu.getFieldValue("mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:eu.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eu.setFieldsValue({mcp_tool_permissions:e})})})}}),(0,t.jsx)(c.Z.Item,{label:"Organization ID",name:"organization_id",children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Logging Settings",name:"logging_settings",children:(0,t.jsx)(P.Z,{value:eu.getFieldValue("logging_settings"),onChange:e=>eu.setFieldValue("logging_settings",e)})}),(0,t.jsx)(c.Z.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(x.default.TextArea,{rows:10})}),(0,t.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,t.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,t.jsx)(o.zx,{variant:"secondary",onClick:()=>e_(!1),children:"Cancel"}),(0,t.jsx)(o.zx,{type:"submit",children:"Save Changes"})]})})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Name"}),(0,t.jsx)("div",{children:eR.team_alias})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team ID"}),(0,t.jsx)("div",{className:"font-mono",children:eR.team_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Created At"}),(0,t.jsx)("div",{children:new Date(eR.created_at).toLocaleString()})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Rate Limits"}),(0,t.jsxs)("div",{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",eR.rpm_limit||"Unlimited"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Budget"}),(0,t.jsxs)("div",{children:["Max Budget:"," ",null!==eR.max_budget?"$".concat((0,r.pw)(eR.max_budget,4)):"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Reset: ",eR.budget_duration||"Never"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)(o.xv,{className:"font-medium",children:["Team Member Settings"," ",(0,t.jsx)(p.Z,{title:"These are limits on individual team members",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),(0,t.jsxs)("div",{children:["Max Budget: ",(null===(V=eR.team_member_budget_table)||void 0===V?void 0:V.max_budget)||"No Limit"]}),(0,t.jsxs)("div",{children:["Key Duration: ",(null===(q=eR.metadata)||void 0===q?void 0:q.team_member_key_duration)||"No Limit"]}),(0,t.jsxs)("div",{children:["TPM Limit: ",(null===(G=eR.team_member_budget_table)||void 0===G?void 0:G.tpm_limit)||"No Limit"]}),(0,t.jsxs)("div",{children:["RPM Limit: ",(null===(K=eR.team_member_budget_table)||void 0===K?void 0:K.rpm_limit)||"No Limit"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Organization ID"}),(0,t.jsx)("div",{children:eR.organization_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Status"}),(0,t.jsx)(o.Ct,{color:eR.blocked?"red":"green",children:eR.blocked?"Blocked":"Active"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Disable Global Guardrails"}),(0,t.jsx)("div",{children:(null===($=eR.metadata)||void 0===$?void 0:$.disable_global_guardrails)===!0?(0,t.jsx)(o.Ct,{color:"yellow",children:"Enabled - Global guardrails bypassed"}):(0,t.jsx)(o.Ct,{color:"green",children:"Disabled - Global guardrails active"})})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(J=eR.metadata)||void 0===J?void 0:J.logging)||[],disabledCallbacks:[],variant:"inline",className:"pt-4 border-t border-gray-200"})]})]})})]})]}),(0,t.jsx)(I.Z,{visible:eh,onCancel:()=>ex(!1),onSubmit:eO,initialData:eb,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Admin",value:"admin"},{label:"User",value:"user"}],additionalFields:[{name:"max_budget_in_team",label:(0,t.jsxs)("span",{children:["Team Member Budget (USD)"," ",(0,t.jsx)(p.Z,{title:"Maximum amount in USD this member can spend within this team. This is separate from any global user budget limits",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:.01,min:0,placeholder:"Budget limit for this member within this team"},{name:"tpm_limit",label:(0,t.jsxs)("span",{children:["Team Member TPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum tokens per minute this member can use within this team. This is separate from any global user TPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Tokens per minute limit for this member in this team"},{name:"rpm_limit",label:(0,t.jsxs)("span",{children:["Team Member RPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum requests per minute this member can make within this team. This is separate from any global user RPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Requests per minute limit for this member in this team"}]}}),(0,t.jsx)(i.Z,{isVisible:eo,onCancel:()=>ec(!1),onSubmit:eE,accessToken:Y}),(0,t.jsx)(f.Z,{isOpen:eS,title:"Delete Team Member",alertMessage:"Removing team members will also delete any keys created by or created for this member.",message:"Are you sure you want to remove this member from the team? This action cannot be undone.",resourceInformationTitle:"Team Member Information",resourceInformation:[{label:"User ID",value:null==eM?void 0:eM.user_id,code:!0},{label:"Email",value:null==eM?void 0:eM.user_email},{label:"Role",value:null==eM?void 0:eM.role}],onCancel:()=>{eT(!1),eC(null)},onOk:eD,confirmLoading:eI})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js b/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js new file mode 100644 index 00000000000..600ee05936e --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2012],{26210:function(e,l,s){s.d(l,{UQ:function(){return t.Z},X1:function(){return i.Z},_m:function(){return a.Z},oi:function(){return n.Z},xv:function(){return r.Z}});var t=s(87452),i=s(88829),a=s(72208),r=s(84264),n=s(49566)},30078:function(e,l,s){s.d(l,{Ct:function(){return t.Z},Dx:function(){return x.Z},OK:function(){return n.Z},Zb:function(){return a.Z},nP:function(){return c.Z},oi:function(){return h.Z},rj:function(){return r.Z},td:function(){return d.Z},v0:function(){return m.Z},x4:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(67101),n=s(12485),m=s(18135),d=s(35242),o=s(29706),c=s(77991),u=s(84264),h=s(49566),x=s(96761)},62490:function(e,l,s){s.d(l,{Ct:function(){return t.Z},RM:function(){return n.Z},SC:function(){return c.Z},Zb:function(){return a.Z},iA:function(){return r.Z},pj:function(){return m.Z},ss:function(){return d.Z},xs:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(21626),n=s(97214),m=s(28241),d=s(58834),o=s(69552),c=s(71876),u=s(84264)},11318:function(e,l,s){s.d(l,{Z:function(){return n}});var t=s(2265),i=s(80443),a=s(19250);let r=async(e,l,s,t)=>"Admin"!=s&&"Admin Viewer"!=s?await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null,l):await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null);var n=()=>{let[e,l]=(0,t.useState)([]),{accessToken:s,userId:a,userRole:n}=(0,i.Z)();return(0,t.useEffect)(()=>{(async()=>{l(await r(s,a,n,null))})()},[s,a,n]),{teams:e,setTeams:l}}},21609:function(e,l,s){s.d(l,{Z:function(){return o}});var t=s(57437),i=s(57840),a=s(22116),r=s(51653),n=s(76188),m=s(4260),d=s(2265);function o(e){let{isOpen:l,title:s,alertMessage:o,message:c,resourceInformationTitle:u,resourceInformation:h,onCancel:x,onOk:b,confirmLoading:p,requiredConfirmation:g}=e,{Title:_,Text:v}=i.default,[j,f]=(0,d.useState)("");return(0,d.useEffect)(()=>{l&&f("")},[l]),(0,t.jsx)(a.Z,{title:s,open:l,onOk:b,onCancel:x,confirmLoading:p,okText:p?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!g&&j!==g||p},cancelButtonProps:{disabled:p},children:(0,t.jsxs)("div",{className:"space-y-4",children:[o&&(0,t.jsx)(r.Z,{message:o,type:"warning"}),(0,t.jsxs)("div",{className:"mt-4 p-4 bg-red-50 rounded-lg border border-red-200",children:[(0,t.jsx)(_,{level:5,className:"mb-3 text-gray-900",children:u}),(0,t.jsx)(n.Z,{column:1,size:"small",children:h&&h.map(e=>{let{label:l,value:s,...i}=e;return(0,t.jsx)(n.Z.Item,{label:(0,t.jsx)("span",{className:"font-semibold text-gray-700",children:l}),children:(0,t.jsx)(v,{...i,children:null!=s?s:"-"})},l)})})]}),(0,t.jsx)("div",{children:(0,t.jsx)(v,{children:c})}),g&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200",children:[(0,t.jsxs)(v,{className:"block text-base font-medium text-gray-700 mb-2",children:[(0,t.jsx)(v,{children:"Type "}),(0,t.jsx)(v,{strong:!0,type:"danger",children:g}),(0,t.jsx)(v,{children:" to confirm deletion:"})]}),(0,t.jsx)(m.default,{value:j,onChange:e=>f(e.target.value),placeholder:g,className:"rounded-md text-base border-gray-200",autoFocus:!0})]})]})})}},33860:function(e,l,s){var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(37592),m=s(99981),d=s(5545),o=s(7310),c=s.n(o),u=s(19250);l.Z=e=>{let{isVisible:l,onCancel:s,onSubmit:o,accessToken:h,title:x="Add Team Member",roles:b=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:p="user"}=e,[g]=a.Z.useForm(),[_,v]=(0,i.useState)([]),[j,f]=(0,i.useState)(!1),[Z,y]=(0,i.useState)("user_email"),N=async(e,l)=>{if(!e){v([]);return}f(!0);try{let s=new URLSearchParams;if(s.append(l,e),null==h)return;let t=(await (0,u.userFilterUICall)(h,s)).map(e=>({label:"user_email"===l?"".concat(e.user_email):"".concat(e.user_id),value:"user_email"===l?e.user_email:e.user_id,user:e}));v(t)}catch(e){console.error("Error fetching users:",e)}finally{f(!1)}},w=(0,i.useCallback)(c()((e,l)=>N(e,l),300),[]),k=(e,l)=>{y(l),w(e,l)},M=(e,l)=>{let s=l.user;g.setFieldsValue({user_email:s.user_email,user_id:s.user_id,role:g.getFieldValue("role")})};return(0,t.jsx)(r.Z,{title:x,open:l,onCancel:()=>{g.resetFields(),v([]),s()},footer:null,width:800,children:(0,t.jsxs)(a.Z,{form:g,onFinish:o,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:p},children:[(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>k(e,"user_email"),onSelect:(e,l)=>M(e,l),options:"user_email"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>k(e,"user_id"),onSelect:(e,l)=>M(e,l),options:"user_id"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)(a.Z.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(n.default,{defaultValue:p,children:b.map(e=>(0,t.jsx)(n.default.Option,{value:e.value,children:(0,t.jsxs)(m.Z,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(d.ZP,{type:"default",htmlType:"submit",children:"Add Member"})})]})})}},10901:function(e,l,s){s.d(l,{Z:function(){return h}});var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(5545),m=s(27281),d=s(57365),o=s(49566),c=s(92280),u=s(24199),h=e=>{var l,s,h;let{visible:x,onCancel:b,onSubmit:p,initialData:g,mode:_,config:v}=e,[j]=a.Z.useForm();console.log("Initial Data:",g),(0,i.useEffect)(()=>{if(x){if("edit"===_&&g){let e={...g,role:g.role||v.defaultRole,max_budget_in_team:g.max_budget_in_team||null,tpm_limit:g.tpm_limit||null,rpm_limit:g.rpm_limit||null};console.log("Setting form values:",e),j.setFieldsValue(e)}else{var e;j.resetFields(),j.setFieldsValue({role:v.defaultRole||(null===(e=v.roleOptions[0])||void 0===e?void 0:e.value)})}}},[x,g,_,j,v.defaultRole,v.roleOptions]);let f=async e=>{try{let l=Object.entries(e).reduce((e,l)=>{let[s,t]=l;if("string"==typeof t){let l=t.trim();return""===l&&("max_budget_in_team"===s||"tpm_limit"===s||"rpm_limit"===s)?{...e,[s]:null}:{...e,[s]:l}}return{...e,[s]:t}},{});console.log("Submitting form data:",l),p(l),j.resetFields()}catch(e){console.error("Form submission error:",e)}},Z=e=>{switch(e.type){case"input":return(0,t.jsx)(o.Z,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(u.Z,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":var l;return(0,t.jsx)(m.Z,{children:null===(l=e.options)||void 0===l?void 0:l.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))});default:return null}};return(0,t.jsx)(r.Z,{title:v.title||("add"===_?"Add Member":"Edit Member"),open:x,width:1e3,footer:null,onCancel:b,children:(0,t.jsxs)(a.Z,{form:j,onFinish:f,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[v.showEmail&&(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(o.Z,{placeholder:"user@example.com"})}),v.showEmail&&v.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(c.x,{children:"OR"})}),v.showUserId&&(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(o.Z,{placeholder:"user_123"})}),(0,t.jsx)(a.Z.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===_&&g&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(s=g.role,(null===(h=v.roleOptions.find(e=>e.value===s))||void 0===h?void 0:h.label)||s),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(m.Z,{children:"edit"===_&&g?[...v.roleOptions.filter(e=>e.value===g.role),...v.roleOptions.filter(e=>e.value!==g.role)].map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value)):v.roleOptions.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))})}),null===(l=v.additionalFields)||void 0===l?void 0:l.map(e=>(0,t.jsx)(a.Z.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:Z(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(n.ZP,{onClick:b,className:"mr-2",children:"Cancel"}),(0,t.jsx)(n.ZP,{type:"default",htmlType:"submit",children:"add"===_?"Add Member":"Save Changes"})]})]})})}},33293:function(e,l,s){s.d(l,{Z:function(){return el}});var t=s(57437),i=s(33860),a=s(19250),r=s(59872),n=s(33304),m=s(15424),d=s(10900),o=s(30078),c=s(10032),u=s(42264),h=s(5545),x=s(4260),b=s(37592),p=s(99981),g=s(63709),_=s(30401),v=s(78867),j=s(2265),f=s(21609),Z=s(95096),y=s(46468),N=s(27799),w=s(95920),k=s(68473),M=s(9114),C=s(98015),S=s(24199),T=s(97415),I=s(10901),P=s(21425),L=s(78489),F=s(12514),E=s(21626),O=s(97214),D=s(28241),A=s(58834),R=s(69552),U=s(71876),z=s(84264),B=s(96761),V=s(4156),q=s(85180),G=s(89245),K=s(78355);let $={"/key/generate":"Member can generate a virtual key for this team","/key/service-account/generate":"Member can generate a service account key (not belonging to any user) for this team","/key/update":"Member can update a virtual key belonging to this team","/key/delete":"Member can delete a virtual key belonging to this team","/key/info":"Member can get info about a virtual key belonging to this team","/key/regenerate":"Member can regenerate a virtual key belonging to this team","/key/{key_id}/regenerate":"Member can regenerate a virtual key belonging to this team","/key/list":"Member can list virtual keys belonging to this team","/key/block":"Member can block a virtual key belonging to this team","/key/unblock":"Member can unblock a virtual key belonging to this team"},J=e=>e.includes("/info")||e.includes("/list")?"GET":"POST",Q=e=>{let l=J(e),s=$[e];if(!s){for(let[l,t]of Object.entries($))if(e.includes(l)){s=t;break}}return s||(s="Access ".concat(e)),{method:l,endpoint:e,description:s,route:e}};var W=e=>{let{teamId:l,accessToken:s,canEditTeam:i}=e,[r,n]=(0,j.useState)([]),[m,d]=(0,j.useState)([]),[o,c]=(0,j.useState)(!0),[u,x]=(0,j.useState)(!1),[b,p]=(0,j.useState)(!1),g=async()=>{try{if(c(!0),!s)return;let e=await (0,a.getTeamPermissionsCall)(s,l),t=e.all_available_permissions||[];n(t);let i=e.team_member_permissions||[];d(i),p(!1)}catch(e){M.Z.fromBackend("Failed to load permissions"),console.error("Error fetching permissions:",e)}finally{c(!1)}};(0,j.useEffect)(()=>{g()},[l,s]);let _=(e,l)=>{d(l?[...m,e]:m.filter(l=>l!==e)),p(!0)},v=async()=>{try{if(!s)return;x(!0),await (0,a.teamPermissionsUpdateCall)(s,l,m),M.Z.success("Permissions updated successfully"),p(!1)}catch(e){M.Z.fromBackend("Failed to update permissions"),console.error("Error updating permissions:",e)}finally{x(!1)}};if(o)return(0,t.jsx)("div",{className:"p-6 text-center",children:"Loading permissions..."});let f=r.length>0;return(0,t.jsxs)(F.Z,{className:"bg-white shadow-md rounded-md p-6",children:[(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row justify-between items-start sm:items-center border-b pb-4 mb-6",children:[(0,t.jsx)(B.Z,{className:"mb-2 sm:mb-0",children:"Member Permissions"}),i&&b&&(0,t.jsxs)("div",{className:"flex gap-3",children:[(0,t.jsx)(h.ZP,{icon:(0,t.jsx)(G.Z,{}),onClick:()=>{g()},children:"Reset"}),(0,t.jsxs)(L.Z,{onClick:v,loading:u,className:"flex items-center gap-2",children:[(0,t.jsx)(K.Z,{})," Save Changes"]})]})]}),(0,t.jsx)(z.Z,{className:"mb-6 text-gray-600",children:"Control what team members can do when they are not team admins."}),f?(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:" min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"Method"}),(0,t.jsx)(R.Z,{children:"Endpoint"}),(0,t.jsx)(R.Z,{children:"Description"}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:"Allow Access"})]})}),(0,t.jsx)(O.Z,{children:r.map(e=>{let l=Q(e);return(0,t.jsxs)(U.Z,{className:"hover:bg-gray-50 transition-colors",children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"px-2 py-1 rounded text-xs font-medium ".concat("GET"===l.method?"bg-blue-100 text-blue-800":"bg-green-100 text-green-800"),children:l.method})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"font-mono text-sm text-gray-800",children:l.endpoint})}),(0,t.jsx)(D.Z,{className:"text-gray-700",children:l.description}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:(0,t.jsx)(V.Z,{checked:m.includes(e),onChange:l=>_(e,l.target.checked),disabled:!i})})]},e)})})]})}):(0,t.jsx)("div",{className:"py-12",children:(0,t.jsx)(q.Z,{description:"No permissions available"})})]})},X=s(47323),Y=s(53410),H=s(74998),ee=e=>{let{teamData:l,canEditTeam:s,handleMemberDelete:i,setSelectedEditMember:a,setIsEditMemberModalVisible:n,setIsAddMemberModalVisible:d}=e,o=e=>{if(null==e)return"0";if("number"==typeof e){let l=Number(e);return l===Math.floor(l)?l.toString():(0,r.pw)(l,8).replace(/\.?0+$/,"")}return"0"},c=e=>{if(!e)return 0;let s=l.team_memberships.find(l=>l.user_id===e);return(null==s?void 0:s.spend)||0},u=e=>{var s;if(!e)return null;let t=l.team_memberships.find(l=>l.user_id===e);console.log("membership=".concat(t));let i=null==t?void 0:null===(s=t.litellm_budget_table)||void 0===s?void 0:s.max_budget;return null==i?null:o(i)},h=e=>{var s,t;if(!e)return"No Limits";let i=l.team_memberships.find(l=>l.user_id===e),a=null==i?void 0:null===(s=i.litellm_budget_table)||void 0===s?void 0:s.rpm_limit,r=null==i?void 0:null===(t=i.litellm_budget_table)||void 0===t?void 0:t.tpm_limit,n=[a?"".concat(o(a)," RPM"):null,r?"".concat(o(r)," TPM"):null].filter(Boolean);return n.length>0?n.join(" / "):"No Limits"};return(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)(F.Z,{className:"w-full mx-auto flex-auto overflow-auto max-h-[50vh]",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:"min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"User ID"}),(0,t.jsx)(R.Z,{children:"User Email"}),(0,t.jsx)(R.Z,{children:"Role"}),(0,t.jsxs)(R.Z,{children:["Team Member Spend (USD)"," ",(0,t.jsx)(p.Z,{title:"This is the amount spent by a user in the team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{children:"Team Member Budget (USD)"}),(0,t.jsxs)(R.Z,{children:["Team Member Rate Limits"," ",(0,t.jsx)(p.Z,{title:"Rate limits for this member's usage within this team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:"Actions"})]})}),(0,t.jsx)(O.Z,{children:l.team_info.members_with_roles.map((e,m)=>(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_id})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_email?e.user_email:"No Email"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.role})}),(0,t.jsx)(D.Z,{children:(0,t.jsxs)(z.Z,{className:"font-mono",children:["$",(0,r.pw)(c(e.user_id),4)]})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:u(e.user_id)?"$".concat((0,r.pw)(Number(u(e.user_id)),4)):"No Limit"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:h(e.user_id)})}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:s&&(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(X.Z,{icon:Y.Z,size:"sm",onClick:()=>{var s,t,i;let r=l.team_memberships.find(l=>l.user_id===e.user_id);a({...e,max_budget_in_team:(null==r?void 0:null===(s=r.litellm_budget_table)||void 0===s?void 0:s.max_budget)||null,tpm_limit:(null==r?void 0:null===(t=r.litellm_budget_table)||void 0===t?void 0:t.tpm_limit)||null,rpm_limit:(null==r?void 0:null===(i=r.litellm_budget_table)||void 0===i?void 0:i.rpm_limit)||null}),n(!0)},className:"cursor-pointer hover:text-blue-600"}),(0,t.jsx)(X.Z,{icon:H.Z,size:"sm",onClick:()=>i(e),className:"cursor-pointer hover:text-red-600"})]})})]},m))})]})})}),(0,t.jsx)(L.Z,{onClick:()=>d(!0),children:"Add Member"})]})},el=e=>{var l,s,L,F,E,O,D,A,R,U,z,B,V,q,G,K,$,J;let{teamId:Q,onClose:X,accessToken:Y,is_team_admin:H,is_proxy_admin:el,userModels:es,editTeam:et,premiumUser:ei=!1,onUpdate:ea}=e,[er,en]=(0,j.useState)(null),[em,ed]=(0,j.useState)(!0),[eo,ec]=(0,j.useState)(!1),[eu]=c.Z.useForm(),[eh,ex]=(0,j.useState)(!1),[eb,ep]=(0,j.useState)(null),[eg,e_]=(0,j.useState)(!1),[ev,ej]=(0,j.useState)([]),[ef,eZ]=(0,j.useState)(!1),[ey,eN]=(0,j.useState)({}),[ew,ek]=(0,j.useState)([]),[eM,eC]=(0,j.useState)(null),[eS,eT]=(0,j.useState)(!1),[eI,eP]=(0,j.useState)(!1);console.log("userModels in team info",es);let eL=H||el,eF=async()=>{try{if(ed(!0),!Y)return;let e=await (0,a.teamInfoCall)(Y,Q);en(e)}catch(e){M.Z.fromBackend("Failed to load team information"),console.error("Error fetching team info:",e)}finally{ed(!1)}};(0,j.useEffect)(()=>{eF()},[Q,Y]),(0,j.useEffect)(()=>{(async()=>{try{if(!Y)return;let e=(await (0,a.getGuardrailsList)(Y)).guardrails.map(e=>e.guardrail_name);ek(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})()},[Y]);let eE=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,a.teamMemberAddCall)(Y,Q,l),M.Z.success("Team member added successfully"),ec(!1),eu.resetFields();let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(i){var l,s,t;let e="Failed to add team member";(null==i?void 0:null===(t=i.raw)||void 0===t?void 0:null===(s=t.detail)||void 0===s?void 0:null===(l=s.error)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==i?void 0:i.message)&&(e=i.message),M.Z.fromBackend(e),console.error("Error adding team member:",i)}},eO=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role,max_budget_in_team:e.max_budget_in_team,tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit};console.log("Updating member with values:",l),u.ZP.destroy(),await (0,a.teamMemberUpdateCall)(Y,Q,l),M.Z.success("Team member updated successfully"),ex(!1);let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(t){var l,s;let e="Failed to update team member";(null==t?void 0:null===(s=t.raw)||void 0===s?void 0:null===(l=s.detail)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==t?void 0:t.message)&&(e=t.message),ex(!1),u.ZP.destroy(),M.Z.fromBackend(e),console.error("Error updating team member:",t)}},eD=async()=>{if(eM&&Y){eP(!0);try{await (0,a.teamMemberDeleteCall)(Y,Q,eM),M.Z.success("Team member removed successfully");let e=await (0,a.teamInfoCall)(Y,Q);en(e),ea(e)}catch(e){M.Z.fromBackend("Failed to remove team member"),console.error("Error removing team member:",e)}finally{eP(!1),eT(!1),eC(null)}}},eA=async e=>{try{if(!Y)return;let l={};try{l=e.metadata?JSON.parse(e.metadata):{}}catch(e){M.Z.fromBackend("Invalid JSON in metadata field");return}let s=e=>null==e||"string"==typeof e&&""===e.trim()||"number"==typeof e&&Number.isNaN(e)?null:e,t={team_id:Q,team_alias:e.team_alias,models:e.models,tpm_limit:s(e.tpm_limit),rpm_limit:s(e.rpm_limit),max_budget:e.max_budget,budget_duration:e.budget_duration,metadata:{...l,guardrails:e.guardrails||[],logging:e.logging_settings||[]},organization_id:e.organization_id};t.max_budget=(0,n.C)(t.max_budget),void 0!==e.team_member_budget&&(t.team_member_budget=Number(e.team_member_budget)),void 0!==e.team_member_key_duration&&(t.team_member_key_duration=e.team_member_key_duration),(void 0!==e.team_member_tpm_limit||void 0!==e.team_member_rpm_limit)&&(t.team_member_tpm_limit=s(e.team_member_tpm_limit),t.team_member_rpm_limit=s(e.team_member_rpm_limit));let{servers:i,accessGroups:r}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]},m=new Set(i||[]),d=Object.fromEntries(Object.entries(e.mcp_tool_permissions||{}).filter(e=>{let[l]=e;return m.has(l)}));t.object_permission={},i&&(t.object_permission.mcp_servers=i),r&&(t.object_permission.mcp_access_groups=r),d&&(t.object_permission.mcp_tool_permissions=d),delete e.mcp_servers_and_groups,delete e.mcp_tool_permissions,await (0,a.teamUpdateCall)(Y,t),M.Z.success("Team settings updated successfully"),e_(!1),eF()}catch(e){console.error("Error updating team:",e)}};if(em)return(0,t.jsx)("div",{className:"p-4",children:"Loading..."});if(!(null==er?void 0:er.team_info))return(0,t.jsx)("div",{className:"p-4",children:"Team not found"});let{team_info:eR}=er,eU=async(e,l)=>{await (0,r.vQ)(e)&&(eN(e=>({...e,[l]:!0})),setTimeout(()=>{eN(e=>({...e,[l]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)(o.zx,{icon:d.Z,variant:"light",onClick:X,className:"mb-4",children:"Back to Teams"}),(0,t.jsx)(o.Dx,{children:eR.team_alias}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(o.xv,{className:"text-gray-500 font-mono",children:eR.team_id}),(0,t.jsx)(h.ZP,{type:"text",size:"small",icon:ey["team-id"]?(0,t.jsx)(_.Z,{size:12}):(0,t.jsx)(v.Z,{size:12}),onClick:()=>eU(eR.team_id,"team-id"),className:"left-2 z-10 transition-all duration-200 ".concat(ey["team-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,t.jsxs)(o.v0,{defaultIndex:et?3:0,children:[(0,t.jsx)(o.td,{className:"mb-4",children:[(0,t.jsx)(o.OK,{children:"Overview"},"overview"),...eL?[(0,t.jsx)(o.OK,{children:"Members"},"members"),(0,t.jsx)(o.OK,{children:"Member Permissions"},"member-permissions"),(0,t.jsx)(o.OK,{children:"Settings"},"settings")]:[]]}),(0,t.jsxs)(o.nP,{children:[(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Budget Status"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.Dx,{children:["$",(0,r.pw)(eR.spend,4)]}),(0,t.jsxs)(o.xv,{children:["of ",null===eR.max_budget?"Unlimited":"$".concat((0,r.pw)(eR.max_budget,4))]}),eR.budget_duration&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Reset: ",eR.budget_duration]}),(0,t.jsx)("br",{}),eR.team_member_budget_table&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Team Member Budget: $",(0,r.pw)(eR.team_member_budget_table.max_budget,4)]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Rate Limits"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)(o.xv,{children:["RPM: ",eR.rpm_limit||"Unlimited"]}),eR.max_parallel_requests&&(0,t.jsxs)(o.xv,{children:["Max Parallel Requests: ",eR.max_parallel_requests]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Models"}),(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===eR.models.length?(0,t.jsx)(o.Ct,{color:"red",children:"All proxy models"}):eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{className:"font-semibold text-gray-900",children:"Virtual Keys"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["User Keys: ",er.keys.filter(e=>e.user_id).length]}),(0,t.jsxs)(o.xv,{children:["Service Account Keys: ",er.keys.filter(e=>!e.user_id).length]}),(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Total: ",er.keys.length]})]})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"card",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(l=eR.metadata)||void 0===l?void 0:l.logging)||[],disabledCallbacks:[],variant:"card"})]})}),(0,t.jsx)(o.x4,{children:(0,t.jsx)(ee,{teamData:er,canEditTeam:eL,handleMemberDelete:e=>{eC(e),eT(!0)},setSelectedEditMember:ep,setIsEditMemberModalVisible:ex,setIsAddMemberModalVisible:ec})}),eL&&(0,t.jsx)(o.x4,{children:(0,t.jsx)(W,{teamId:Q,accessToken:Y,canEditTeam:eL})}),(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.Zb,{className:"overflow-y-auto max-h-[65vh]",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(o.Dx,{children:"Team Settings"}),eL&&!eg&&(0,t.jsx)(o.zx,{onClick:()=>e_(!0),children:"Edit Settings"})]}),eg?(0,t.jsxs)(c.Z,{form:eu,onFinish:eA,initialValues:{...eR,team_alias:eR.team_alias,models:eR.models,tpm_limit:eR.tpm_limit,rpm_limit:eR.rpm_limit,max_budget:eR.max_budget,budget_duration:eR.budget_duration,team_member_tpm_limit:null===(s=eR.team_member_budget_table)||void 0===s?void 0:s.tpm_limit,team_member_rpm_limit:null===(L=eR.team_member_budget_table)||void 0===L?void 0:L.rpm_limit,guardrails:(null===(F=eR.metadata)||void 0===F?void 0:F.guardrails)||[],disable_global_guardrails:(null===(E=eR.metadata)||void 0===E?void 0:E.disable_global_guardrails)||!1,metadata:eR.metadata?JSON.stringify((e=>{let{logging:l,...s}=e;return s})(eR.metadata),null,2):"",logging_settings:(null===(O=eR.metadata)||void 0===O?void 0:O.logging)||[],organization_id:eR.organization_id,vector_stores:(null===(D=eR.object_permission)||void 0===D?void 0:D.vector_stores)||[],mcp_servers:(null===(A=eR.object_permission)||void 0===A?void 0:A.mcp_servers)||[],mcp_access_groups:(null===(R=eR.object_permission)||void 0===R?void 0:R.mcp_access_groups)||[],mcp_servers_and_groups:{servers:(null===(U=eR.object_permission)||void 0===U?void 0:U.mcp_servers)||[],accessGroups:(null===(z=eR.object_permission)||void 0===z?void 0:z.mcp_access_groups)||[]},mcp_tool_permissions:(null===(B=eR.object_permission)||void 0===B?void 0:B.mcp_tool_permissions)||{}},layout:"vertical",children:[(0,t.jsx)(c.Z.Item,{label:"Team Name",name:"team_alias",rules:[{required:!0,message:"Please input a team name"}],children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Please select at least one model"}],children:(0,t.jsxs)(b.default,{mode:"multiple",placeholder:"Select models",children:[(el||es.includes("all-proxy-models"))&&(0,t.jsx)(b.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,t.jsx)(b.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),Array.from(new Set(es)).map((e,l)=>(0,t.jsx)(b.default.Option,{value:e,children:(0,y.W0)(e)},l))]})}),(0,t.jsx)(c.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Budget (USD)",name:"team_member_budget",tooltip:"This is the individual budget for a user in the team.",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Key Duration (eg: 1d, 1mo)",name:"team_member_key_duration",tooltip:"Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",children:(0,t.jsx)(o.oi,{placeholder:"e.g., 30d"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member TPM Limit",name:"team_member_tpm_limit",tooltip:"Default tokens per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 1000"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member RPM Limit",name:"team_member_rpm_limit",tooltip:"Default requests per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 100"})}),(0,t.jsx)(c.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsxs)(b.default,{placeholder:"n/a",children:[(0,t.jsx)(b.default.Option,{value:"24h",children:"daily"}),(0,t.jsx)(b.default.Option,{value:"7d",children:"weekly"}),(0,t.jsx)(b.default.Option,{value:"30d",children:"monthly"})]})}),(0,t.jsx)(c.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(p.Z,{title:"Setup your first guardrail",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",help:"Select existing guardrails or enter new ones",children:(0,t.jsx)(b.default,{mode:"tags",placeholder:"Select or enter guardrails",options:ew.map(e=>({value:e,label:e}))})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,t.jsx)(p.Z,{title:"When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),name:"disable_global_guardrails",valuePropName:"checked",help:"Bypass global guardrails for this team",children:(0,t.jsx)(g.Z,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(c.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,t.jsx)(T.Z,{onChange:e=>eu.setFieldValue("vector_stores",e),value:eu.getFieldValue("vector_stores"),accessToken:Y||"",placeholder:"Select vector stores"})}),(0,t.jsx)(c.Z.Item,{label:"Allowed Pass Through Routes",name:"allowed_passthrough_routes",children:(0,t.jsx)(Z.Z,{onChange:e=>eu.setFieldValue("allowed_passthrough_routes",e),value:eu.getFieldValue("allowed_passthrough_routes"),accessToken:Y||"",placeholder:"Select pass through routes"})}),(0,t.jsx)(c.Z.Item,{label:"MCP Servers / Access Groups",name:"mcp_servers_and_groups",children:(0,t.jsx)(w.Z,{onChange:e=>eu.setFieldValue("mcp_servers_and_groups",e),value:eu.getFieldValue("mcp_servers_and_groups"),accessToken:Y||"",placeholder:"Select MCP servers or access groups (optional)"})}),(0,t.jsx)(c.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(x.default,{type:"hidden"})}),(0,t.jsx)(c.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.mcp_servers_and_groups!==l.mcp_servers_and_groups||e.mcp_tool_permissions!==l.mcp_tool_permissions,children:()=>{var e;return(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(k.Z,{accessToken:Y||"",selectedServers:(null===(e=eu.getFieldValue("mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:eu.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eu.setFieldsValue({mcp_tool_permissions:e})})})}}),(0,t.jsx)(c.Z.Item,{label:"Organization ID",name:"organization_id",children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Logging Settings",name:"logging_settings",children:(0,t.jsx)(P.Z,{value:eu.getFieldValue("logging_settings"),onChange:e=>eu.setFieldValue("logging_settings",e)})}),(0,t.jsx)(c.Z.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(x.default.TextArea,{rows:10})}),(0,t.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,t.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,t.jsx)(o.zx,{variant:"secondary",onClick:()=>e_(!1),children:"Cancel"}),(0,t.jsx)(o.zx,{type:"submit",children:"Save Changes"})]})})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Name"}),(0,t.jsx)("div",{children:eR.team_alias})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team ID"}),(0,t.jsx)("div",{className:"font-mono",children:eR.team_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Created At"}),(0,t.jsx)("div",{children:new Date(eR.created_at).toLocaleString()})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Rate Limits"}),(0,t.jsxs)("div",{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",eR.rpm_limit||"Unlimited"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Budget"}),(0,t.jsxs)("div",{children:["Max Budget:"," ",null!==eR.max_budget?"$".concat((0,r.pw)(eR.max_budget,4)):"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Reset: ",eR.budget_duration||"Never"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)(o.xv,{className:"font-medium",children:["Team Member Settings"," ",(0,t.jsx)(p.Z,{title:"These are limits on individual team members",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),(0,t.jsxs)("div",{children:["Max Budget: ",(null===(V=eR.team_member_budget_table)||void 0===V?void 0:V.max_budget)||"No Limit"]}),(0,t.jsxs)("div",{children:["Key Duration: ",(null===(q=eR.metadata)||void 0===q?void 0:q.team_member_key_duration)||"No Limit"]}),(0,t.jsxs)("div",{children:["TPM Limit: ",(null===(G=eR.team_member_budget_table)||void 0===G?void 0:G.tpm_limit)||"No Limit"]}),(0,t.jsxs)("div",{children:["RPM Limit: ",(null===(K=eR.team_member_budget_table)||void 0===K?void 0:K.rpm_limit)||"No Limit"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Organization ID"}),(0,t.jsx)("div",{children:eR.organization_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Status"}),(0,t.jsx)(o.Ct,{color:eR.blocked?"red":"green",children:eR.blocked?"Blocked":"Active"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Disable Global Guardrails"}),(0,t.jsx)("div",{children:(null===($=eR.metadata)||void 0===$?void 0:$.disable_global_guardrails)===!0?(0,t.jsx)(o.Ct,{color:"yellow",children:"Enabled - Global guardrails bypassed"}):(0,t.jsx)(o.Ct,{color:"green",children:"Disabled - Global guardrails active"})})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(J=eR.metadata)||void 0===J?void 0:J.logging)||[],disabledCallbacks:[],variant:"inline",className:"pt-4 border-t border-gray-200"})]})]})})]})]}),(0,t.jsx)(I.Z,{visible:eh,onCancel:()=>ex(!1),onSubmit:eO,initialData:eb,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Admin",value:"admin"},{label:"User",value:"user"}],additionalFields:[{name:"max_budget_in_team",label:(0,t.jsxs)("span",{children:["Team Member Budget (USD)"," ",(0,t.jsx)(p.Z,{title:"Maximum amount in USD this member can spend within this team. This is separate from any global user budget limits",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:.01,min:0,placeholder:"Budget limit for this member within this team"},{name:"tpm_limit",label:(0,t.jsxs)("span",{children:["Team Member TPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum tokens per minute this member can use within this team. This is separate from any global user TPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Tokens per minute limit for this member in this team"},{name:"rpm_limit",label:(0,t.jsxs)("span",{children:["Team Member RPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum requests per minute this member can make within this team. This is separate from any global user RPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Requests per minute limit for this member in this team"}]}}),(0,t.jsx)(i.Z,{isVisible:eo,onCancel:()=>ec(!1),onSubmit:eE,accessToken:Y}),(0,t.jsx)(f.Z,{isOpen:eS,title:"Delete Team Member",alertMessage:"Removing team members will also delete any keys created by or created for this member.",message:"Are you sure you want to remove this member from the team? This action cannot be undone.",resourceInformationTitle:"Team Member Information",resourceInformation:[{label:"User ID",value:null==eM?void 0:eM.user_id,code:!0},{label:"Email",value:null==eM?void 0:eM.user_email},{label:"Role",value:null==eM?void 0:eM.role}],onCancel:()=>{eT(!1),eC(null)},onOk:eD,confirmLoading:eI})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2118-9efce161d33a9757.js b/litellm/proxy/_experimental/out/_next/static/chunks/2118-9efce161d33a9757.js deleted file mode 100644 index fb1bfd178c6..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2118-9efce161d33a9757.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2118],{69993:function(e,t,r){r.d(t,{Z:function(){return s}});var n=r(1119),a=r(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},o=r(55015),s=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:i}))})},58747:function(e,t,r){r.d(t,{Z:function(){return i}});var n=r(5853),a=r(2265);let i=e=>{var t=(0,n._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),a.createElement("path",{d:"M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"}))}},4537:function(e,t,r){r.d(t,{Z:function(){return i}});var n=r(5853),a=r(2265);let i=e=>{var t=(0,n._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),a.createElement("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"}))}},47323:function(e,t,r){r.d(t,{Z:function(){return p}});var n=r(5853),a=r(2265),i=r(47187),o=r(7084),s=r(13241),l=r(1153),u=r(26898);let d={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},c={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},h={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},f=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,l.bM)(t,u.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,l.bM)(t,u.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,u.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,l.bM)(t,u.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,u.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,l.bM)(t,u.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,s.q)((0,l.bM)(t,u.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,l.bM)(t,u.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,u.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,l.bM)(t,u.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,s.q)((0,l.bM)(t,u.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},m=(0,l.fn)("Icon"),p=a.forwardRef((e,t)=>{let{icon:r,variant:u="simple",tooltip:p,size:g=o.u8.SM,color:b,className:v}=e,y=(0,n._T)(e,["icon","variant","tooltip","size","color","className"]),w=f(u,b),{tooltipProps:k,getReferenceProps:C}=(0,i.l)();return a.createElement("span",Object.assign({ref:(0,l.lq)([t,k.refs.setReference]),className:(0,s.q)(m("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,h[u].rounded,h[u].border,h[u].shadow,h[u].ring,d[g].paddingX,d[g].paddingY,v)},C,y),a.createElement(i.Z,Object.assign({text:p},k)),a.createElement(r,{className:(0,s.q)(m("icon"),"shrink-0",c[g].height,c[g].width)}))});p.displayName="Icon"},27281:function(e,t,r){r.d(t,{Z:function(){return m}});var n=r(5853),a=r(58747),i=r(2265),o=r(4537),s=r(13241),l=r(1153),u=r(96398),d=r(79228),c=r(85238),h=r(44140);let f=(0,l.fn)("Select"),m=i.forwardRef((e,t)=>{let{defaultValue:r="",value:l,onValueChange:m,placeholder:p="Select...",disabled:g=!1,icon:b,enableClear:v=!1,required:y,children:w,name:k,error:C=!1,errorMessage:x,className:E,id:M}=e,q=(0,n._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","required","children","name","error","errorMessage","className","id"]),O=(0,i.useRef)(null),L=i.Children.toArray(w),[N,P]=(0,h.Z)(r,l),R=(0,i.useMemo)(()=>{let e=i.Children.toArray(w).filter(i.isValidElement);return(0,u.sl)(e)},[w]);return i.createElement("div",{className:(0,s.q)("w-full min-w-[10rem] text-tremor-default",E)},i.createElement("div",{className:"relative"},i.createElement("select",{title:"select-hidden",required:y,className:(0,s.q)("h-full w-full absolute left-0 top-0 -z-10 opacity-0"),value:N,onChange:e=>{e.preventDefault()},name:k,disabled:g,id:M,onFocus:()=>{let e=O.current;e&&e.focus()}},i.createElement("option",{className:"hidden",value:"",disabled:!0,hidden:!0},p),L.map(e=>{let t=e.props.value,r=e.props.children;return i.createElement("option",{className:"hidden",key:t,value:t},r)})),i.createElement(d.Ri,Object.assign({as:"div",ref:t,defaultValue:N,value:N,onChange:e=>{null==m||m(e),P(e)},disabled:g,id:M},q),e=>{var t;let{value:r}=e;return i.createElement(i.Fragment,null,i.createElement(d.Y4,{ref:O,className:(0,s.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",b?"pl-10":"pl-3",(0,u.um)((0,u.Uh)(r),g,C))},b&&i.createElement("span",{className:(0,s.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},i.createElement(b,{className:(0,s.q)(f("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),i.createElement("span",{className:"w-[90%] block truncate"},r&&null!==(t=R.get(r))&&void 0!==t?t:p),i.createElement("span",{className:(0,s.q)("absolute inset-y-0 right-0 flex items-center mr-3")},i.createElement(a.Z,{className:(0,s.q)(f("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),v&&N?i.createElement("button",{type:"button",className:(0,s.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),P(""),null==m||m("")}},i.createElement(o.Z,{className:(0,s.q)(f("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,i.createElement(c.u,{enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},i.createElement(d.O_,{anchor:"bottom start",className:(0,s.q)("z-10 w-[var(--button-width)] divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] border [--anchor-gap:4px]","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},w)))})),C&&x?i.createElement("p",{className:(0,s.q)("errorMessage","text-sm text-rose-500 mt-1")},x):null)});m.displayName="Select"},94789:function(e,t,r){r.d(t,{Z:function(){return u}});var n=r(5853),a=r(2265),i=r(26898),o=r(13241),s=r(1153);let l=(0,s.fn)("Callout"),u=a.forwardRef((e,t)=>{let{title:r,icon:u,color:d,className:c,children:h}=e,f=(0,n._T)(e,["title","icon","color","className","children"]);return a.createElement("div",Object.assign({ref:t,className:(0,o.q)(l("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",d?(0,o.q)((0,s.bM)(d,i.K.background).bgColor,(0,s.bM)(d,i.K.darkBorder).borderColor,(0,s.bM)(d,i.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,o.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),c)},f),a.createElement("div",{className:(0,o.q)(l("header"),"flex items-start")},u?a.createElement(u,{className:(0,o.q)(l("icon"),"flex-none h-5 w-5 mr-1.5")}):null,a.createElement("h4",{className:(0,o.q)(l("title"),"font-semibold")},r)),a.createElement("p",{className:(0,o.q)(l("body"),"overflow-y-auto",h?"mt-2":"")},h))});u.displayName="Callout"},44140:function(e,t,r){r.d(t,{Z:function(){return a}});var n=r(2265);let a=(e,t)=>{let r=void 0!==t,[a,i]=(0,n.useState)(e);return[r?t:a,e=>{r||i(e)}]}},32489:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]])},91777:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"}))});t.Z=a},47686:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 5l7 7-7 7"}))});t.Z=a},44633:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});t.Z=a},58710:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=a},82182:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}))});t.Z=a},79814:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"}))});t.Z=a},2356:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"}))});t.Z=a},93416:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"}))});t.Z=a},77355:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=a},22452:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 4v16m8-8H4"}))});t.Z=a},25327:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"}))});t.Z=a},49084:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"}))});t.Z=a},2894:function(e,t,r){r.d(t,{R:function(){return s},m:function(){return o}});var n=r(18238),a=r(7989),i=r(11255),o=class extends a.F{#e;#t;#r;#n;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#r=e.mutationCache,this.#t=[],this.state=e.state||s(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#r.notify({type:"observerAdded",mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#r.notify({type:"observerRemoved",mutation:this,observer:e})}optionalRemove(){this.#t.length||("pending"===this.state.status?this.scheduleGc():this.#r.remove(this))}continue(){return this.#n?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#a({type:"continue"})},r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#n=(0,i.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(e,r):Promise.reject(Error("No mutationFn found")),onFail:(e,t)=>{this.#a({type:"failed",failureCount:e,error:t})},onPause:()=>{this.#a({type:"pause"})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#r.canRun(this)});let n="pending"===this.state.status,a=!this.#n.canStart();try{if(n)t();else{this.#a({type:"pending",variables:e,isPaused:a}),await this.#r.config.onMutate?.(e,this,r);let t=await this.options.onMutate?.(e,r);t!==this.state.context&&this.#a({type:"pending",context:t,variables:e,isPaused:a})}let i=await this.#n.start();return await this.#r.config.onSuccess?.(i,e,this.state.context,this,r),await this.options.onSuccess?.(i,e,this.state.context,r),await this.#r.config.onSettled?.(i,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(i,null,e,this.state.context,r),this.#a({type:"success",data:i}),i}catch(t){try{throw await this.#r.config.onError?.(t,e,this.state.context,this,r),await this.options.onError?.(t,e,this.state.context,r),await this.#r.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(void 0,t,e,this.state.context,r),t}finally{this.#a({type:"error",error:t})}}finally{this.#r.runNext(this)}}#a(e){this.state=(t=>{switch(e.type){case"failed":return{...t,failureCount:e.failureCount,failureReason:e.error};case"pause":return{...t,isPaused:!0};case"continue":return{...t,isPaused:!1};case"pending":return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:"pending",variables:e.variables,submittedAt:Date.now()};case"success":return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:"error"}}})(this.state),n.Vr.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#r.notify({mutation:this,type:"updated",action:e})})}};function s(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21623:function(e,t,r){r.d(t,{S:function(){return p}});var n=r(45345),a=r(21733),i=r(18238),o=r(24112),s=class extends o.l{constructor(e={}){super(),this.config=e,this.#i=new Map}#i;build(e,t,r){let i=t.queryKey,o=t.queryHash??(0,n.Rm)(i,t),s=this.get(o);return s||(s=new a.A({client:e,queryKey:i,queryHash:o,options:e.defaultQueryOptions(t),state:r,defaultOptions:e.getQueryDefaults(i)}),this.add(s)),s}add(e){this.#i.has(e.queryHash)||(this.#i.set(e.queryHash,e),this.notify({type:"added",query:e}))}remove(e){let t=this.#i.get(e.queryHash);t&&(e.destroy(),t===e&&this.#i.delete(e.queryHash),this.notify({type:"removed",query:e}))}clear(){i.Vr.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#i.get(e)}getAll(){return[...this.#i.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,n._x)(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>(0,n._x)(e,t)):t}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},l=r(2894),u=class extends o.l{constructor(e={}){super(),this.config=e,this.#o=new Set,this.#s=new Map,this.#l=0}#o;#s;#l;build(e,t,r){let n=new l.m({client:e,mutationCache:this,mutationId:++this.#l,options:e.defaultMutationOptions(t),state:r});return this.add(n),n}add(e){this.#o.add(e);let t=d(e);if("string"==typeof t){let r=this.#s.get(t);r?r.push(e):this.#s.set(t,[e])}this.notify({type:"added",mutation:e})}remove(e){if(this.#o.delete(e)){let t=d(e);if("string"==typeof t){let r=this.#s.get(t);if(r){if(r.length>1){let t=r.indexOf(e);-1!==t&&r.splice(t,1)}else r[0]===e&&this.#s.delete(t)}}}this.notify({type:"removed",mutation:e})}canRun(e){let t=d(e);if("string"!=typeof t)return!0;{let r=this.#s.get(t),n=r?.find(e=>"pending"===e.state.status);return!n||n===e}}runNext(e){let t=d(e);if("string"!=typeof t)return Promise.resolve();{let r=this.#s.get(t)?.find(t=>t!==e&&t.state.isPaused);return r?.continue()??Promise.resolve()}}clear(){i.Vr.batch(()=>{this.#o.forEach(e=>{this.notify({type:"removed",mutation:e})}),this.#o.clear(),this.#s.clear()})}getAll(){return Array.from(this.#o)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,n.X7)(t,e))}findAll(e={}){return this.getAll().filter(t=>(0,n.X7)(e,t))}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return i.Vr.batch(()=>Promise.all(e.map(e=>e.continue().catch(n.ZT))))}};function d(e){return e.options.scope?.id}var c=r(87045),h=r(57853);function f(e){return{onFetch:(t,r)=>{let a=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,o=t.state.data?.pages||[],s=t.state.data?.pageParams||[],l={pages:[],pageParams:[]},u=0,d=async()=>{let r=!1,d=e=>{Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(t.signal.aborted?r=!0:t.signal.addEventListener("abort",()=>{r=!0}),t.signal)})},c=(0,n.cG)(t.options,t.fetchOptions),h=async(e,a,i)=>{if(r)return Promise.reject();if(null==a&&e.pages.length)return Promise.resolve(e);let o=(()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:a,direction:i?"backward":"forward",meta:t.options.meta};return d(e),e})(),s=await c(o),{maxPages:l}=t.options,u=i?n.Ht:n.VX;return{pages:u(e.pages,s,l),pageParams:u(e.pageParams,a,l)}};if(i&&o.length){let e="backward"===i,t={pages:o,pageParams:s},r=(e?function(e,{pages:t,pageParams:r}){return t.length>0?e.getPreviousPageParam?.(t[0],t,r[0],r):void 0}:m)(a,t);l=await h(t,r,e)}else{let t=e??o.length;do{let e=0===u?s[0]??a.initialPageParam:m(a,l);if(u>0&&null==e)break;l=await h(l,e),u++}while(ut.options.persister?.(d,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},r):t.fetchFn=d}}}function m(e,{pages:t,pageParams:r}){let n=t.length-1;return t.length>0?e.getNextPageParam(t[n],t,r[n],r):void 0}var p=class{#u;#r;#d;#c;#h;#f;#m;#p;constructor(e={}){this.#u=e.queryCache||new s,this.#r=e.mutationCache||new u,this.#d=e.defaultOptions||{},this.#c=new Map,this.#h=new Map,this.#f=0}mount(){this.#f++,1===this.#f&&(this.#m=c.j.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#u.onFocus())}),this.#p=h.N.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#u.onOnline())}))}unmount(){this.#f--,0===this.#f&&(this.#m?.(),this.#m=void 0,this.#p?.(),this.#p=void 0)}isFetching(e){return this.#u.findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return this.#r.findAll({...e,status:"pending"}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#u.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),r=this.#u.build(this,t),a=r.state.data;return void 0===a?this.fetchQuery(e):(e.revalidateIfStale&&r.isStaleByTime((0,n.KC)(t.staleTime,r))&&this.prefetchQuery(t),Promise.resolve(a))}getQueriesData(e){return this.#u.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,r){let a=this.defaultQueryOptions({queryKey:e}),i=this.#u.get(a.queryHash),o=i?.state.data,s=(0,n.SE)(t,o);if(void 0!==s)return this.#u.build(this,a).setData(s,{...r,manual:!0})}setQueriesData(e,t,r){return i.Vr.batch(()=>this.#u.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,r)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#u.get(t.queryHash)?.state}removeQueries(e){let t=this.#u;i.Vr.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let r=this.#u;return i.Vr.batch(()=>(r.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){let r={revert:!0,...t};return Promise.all(i.Vr.batch(()=>this.#u.findAll(e).map(e=>e.cancel(r)))).then(n.ZT).catch(n.ZT)}invalidateQueries(e,t={}){return i.Vr.batch(()=>(this.#u.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType==="none")?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??"active"},t))}refetchQueries(e,t={}){let r={...t,cancelRefetch:t.cancelRefetch??!0};return Promise.all(i.Vr.batch(()=>this.#u.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,r);return r.throwOnError||(t=t.catch(n.ZT)),"paused"===e.state.fetchStatus?Promise.resolve():t}))).then(n.ZT)}fetchQuery(e){let t=this.defaultQueryOptions(e);void 0===t.retry&&(t.retry=!1);let r=this.#u.build(this,t);return r.isStaleByTime((0,n.KC)(t.staleTime,r))?r.fetch(t):Promise.resolve(r.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(n.ZT).catch(n.ZT)}fetchInfiniteQuery(e){return e.behavior=f(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(n.ZT).catch(n.ZT)}ensureInfiniteQueryData(e){return e.behavior=f(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return h.N.isOnline()?this.#r.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#u}getMutationCache(){return this.#r}getDefaultOptions(){return this.#d}setDefaultOptions(e){this.#d=e}setQueryDefaults(e,t){this.#c.set((0,n.Ym)(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#c.values()],r={};return t.forEach(t=>{(0,n.to)(e,t.queryKey)&&Object.assign(r,t.defaultOptions)}),r}setMutationDefaults(e,t){this.#h.set((0,n.Ym)(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#h.values()],r={};return t.forEach(t=>{(0,n.to)(e,t.mutationKey)&&Object.assign(r,t.defaultOptions)}),r}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#d.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=(0,n.Rm)(t.queryKey,t)),void 0===t.refetchOnReconnect&&(t.refetchOnReconnect="always"!==t.networkMode),void 0===t.throwOnError&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===n.CN&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#d.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#u.clear(),this.#r.clear()}}},85238:function(e,t,r){let n;r.d(t,{u:function(){return L}});var a=r(2265),i=r(59456),o=r(93980),s=r(25289),l=r(73389),u=r(43507),d=r(180),c=r(67561),h=r(98218),f=r(28294),m=r(95504),p=r(72468),g=r(38929);function b(e){var t;return!!(e.enter||e.enterFrom||e.enterTo||e.leave||e.leaveFrom||e.leaveTo)||(null!=(t=e.as)?t:x)!==a.Fragment||1===a.Children.count(e.children)}let v=(0,a.createContext)(null);v.displayName="TransitionContext";var y=((n=y||{}).Visible="visible",n.Hidden="hidden",n);let w=(0,a.createContext)(null);function k(e){return"children"in e?k(e.children):e.current.filter(e=>{let{el:t}=e;return null!==t.current}).filter(e=>{let{state:t}=e;return"visible"===t}).length>0}function C(e,t){let r=(0,u.E)(e),n=(0,a.useRef)([]),l=(0,s.t)(),d=(0,i.G)(),c=(0,o.z)(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:g.l4.Hidden,a=n.current.findIndex(t=>{let{el:r}=t;return r===e});-1!==a&&((0,p.E)(t,{[g.l4.Unmount](){n.current.splice(a,1)},[g.l4.Hidden](){n.current[a].state="hidden"}}),d.microTask(()=>{var e;!k(n)&&l.current&&(null==(e=r.current)||e.call(r))}))}),h=(0,o.z)(e=>{let t=n.current.find(t=>{let{el:r}=t;return r===e});return t?"visible"!==t.state&&(t.state="visible"):n.current.push({el:e,state:"visible"}),()=>c(e,g.l4.Unmount)}),f=(0,a.useRef)([]),m=(0,a.useRef)(Promise.resolve()),b=(0,a.useRef)({enter:[],leave:[]}),v=(0,o.z)((e,r,n)=>{f.current.splice(0),t&&(t.chains.current[r]=t.chains.current[r].filter(t=>{let[r]=t;return r!==e})),null==t||t.chains.current[r].push([e,new Promise(e=>{f.current.push(e)})]),null==t||t.chains.current[r].push([e,new Promise(e=>{Promise.all(b.current[r].map(e=>{let[t,r]=e;return r})).then(()=>e())})]),"enter"===r?m.current=m.current.then(()=>null==t?void 0:t.wait.current).then(()=>n(r)):n(r)}),y=(0,o.z)((e,t,r)=>{Promise.all(b.current[t].splice(0).map(e=>{let[t,r]=e;return r})).then(()=>{var e;null==(e=f.current.shift())||e()}).then(()=>r(t))});return(0,a.useMemo)(()=>({children:n,register:h,unregister:c,onStart:v,onStop:y,wait:m,chains:b}),[h,c,n,v,y,b,m])}w.displayName="NestingContext";let x=a.Fragment,E=g.VN.RenderStrategy,M=(0,g.yV)(function(e,t){let{show:r,appear:n=!1,unmount:i=!0,...s}=e,u=(0,a.useRef)(null),h=b(e),m=(0,c.T)(...h?[u,t]:null===t?[]:[t]);(0,d.H)();let p=(0,f.oJ)();if(void 0===r&&null!==p&&(r=(p&f.ZM.Open)===f.ZM.Open),void 0===r)throw Error("A is used but it is missing a `show={true | false}` prop.");let[y,x]=(0,a.useState)(r?"visible":"hidden"),M=C(()=>{r||x("hidden")}),[O,L]=(0,a.useState)(!0),N=(0,a.useRef)([r]);(0,l.e)(()=>{!1!==O&&N.current[N.current.length-1]!==r&&(N.current.push(r),L(!1))},[N,r]);let P=(0,a.useMemo)(()=>({show:r,appear:n,initial:O}),[r,n,O]);(0,l.e)(()=>{r?x("visible"):k(M)||null===u.current||x("hidden")},[r,M]);let R={unmount:i},T=(0,o.z)(()=>{var t;O&&L(!1),null==(t=e.beforeEnter)||t.call(e)}),j=(0,o.z)(()=>{var t;O&&L(!1),null==(t=e.beforeLeave)||t.call(e)}),Z=(0,g.L6)();return a.createElement(w.Provider,{value:M},a.createElement(v.Provider,{value:P},Z({ourProps:{...R,as:a.Fragment,children:a.createElement(q,{ref:m,...R,...s,beforeEnter:T,beforeLeave:j})},theirProps:{},defaultTag:a.Fragment,features:E,visible:"visible"===y,name:"Transition"})))}),q=(0,g.yV)(function(e,t){var r,n;let{transition:i=!0,beforeEnter:s,afterEnter:u,beforeLeave:y,afterLeave:M,enter:q,enterFrom:O,enterTo:L,entered:N,leave:P,leaveFrom:R,leaveTo:T,...j}=e,[Z,D]=(0,a.useState)(null),Q=(0,a.useRef)(null),A=b(e),S=(0,c.T)(...A?[Q,t,D]:null===t?[]:[t]),V=null==(r=j.unmount)||r?g.l4.Unmount:g.l4.Hidden,{show:F,appear:K,initial:z}=function(){let e=(0,a.useContext)(v);if(null===e)throw Error("A is used but it is missing a parent or .");return e}(),[H,B]=(0,a.useState)(F?"visible":"hidden"),I=function(){let e=(0,a.useContext)(w);if(null===e)throw Error("A is used but it is missing a parent or .");return e}(),{register:_,unregister:W}=I;(0,l.e)(()=>_(Q),[_,Q]),(0,l.e)(()=>{if(V===g.l4.Hidden&&Q.current){if(F&&"visible"!==H){B("visible");return}return(0,p.E)(H,{hidden:()=>W(Q),visible:()=>_(Q)})}},[H,Q,_,W,F,V]);let Y=(0,d.H)();(0,l.e)(()=>{if(A&&Y&&"visible"===H&&null===Q.current)throw Error("Did you forget to passthrough the `ref` to the actual DOM node?")},[Q,H,Y,A]);let X=z&&!K,G=K&&F&&z,U=(0,a.useRef)(!1),J=C(()=>{U.current||(B("hidden"),W(Q))},I),$=(0,o.z)(e=>{U.current=!0,J.onStart(Q,e?"enter":"leave",e=>{"enter"===e?null==s||s():"leave"===e&&(null==y||y())})}),ee=(0,o.z)(e=>{let t=e?"enter":"leave";U.current=!1,J.onStop(Q,t,e=>{"enter"===e?null==u||u():"leave"===e&&(null==M||M())}),"leave"!==t||k(J)||(B("hidden"),W(Q))});(0,a.useEffect)(()=>{A&&i||($(F),ee(F))},[F,A,i]);let et=!(!i||!A||!Y||X),[,er]=(0,h.Y)(et,Z,F,{start:$,end:ee}),en=(0,g.oA)({ref:S,className:(null==(n=(0,m.A)(j.className,G&&q,G&&O,er.enter&&q,er.enter&&er.closed&&O,er.enter&&!er.closed&&L,er.leave&&P,er.leave&&!er.closed&&R,er.leave&&er.closed&&T,!er.transition&&F&&N))?void 0:n.trim())||void 0,...(0,h.X)(er)}),ea=0;"visible"===H&&(ea|=f.ZM.Open),"hidden"===H&&(ea|=f.ZM.Closed),er.enter&&(ea|=f.ZM.Opening),er.leave&&(ea|=f.ZM.Closing);let ei=(0,g.L6)();return a.createElement(w.Provider,{value:J},a.createElement(f.up,{value:ea},ei({ourProps:en,theirProps:j,defaultTag:x,features:E,visible:"visible"===H,name:"Transition.Child"})))}),O=(0,g.yV)(function(e,t){let r=null!==(0,a.useContext)(v),n=null!==(0,f.oJ)();return a.createElement(a.Fragment,null,!r&&n?a.createElement(M,{ref:t,...e}):a.createElement(q,{ref:t,...e}))}),L=Object.assign(M,{Child:O,Root:M})},92668:function(e,t,r){r.d(t,{I:function(){return s}});var n=r(59121),a=r(31091),i=r(63497),o=r(99649);function s(e,t){let{years:r=0,months:s=0,weeks:l=0,days:u=0,hours:d=0,minutes:c=0,seconds:h=0}=t,f=(0,o.Q)(e),m=s||r?(0,a.z)(f,s+12*r):f,p=u||l?(0,n.E)(m,u+7*l):m;return(0,i.L)(e,p.getTime()+1e3*(h+60*(c+60*d)))}},59121:function(e,t,r){r.d(t,{E:function(){return i}});var n=r(99649),a=r(63497);function i(e,t){let r=(0,n.Q)(e);return isNaN(t)?(0,a.L)(e,NaN):(t&&r.setDate(r.getDate()+t),r)}},31091:function(e,t,r){r.d(t,{z:function(){return i}});var n=r(99649),a=r(63497);function i(e,t){let r=(0,n.Q)(e);if(isNaN(t))return(0,a.L)(e,NaN);if(!t)return r;let i=r.getDate(),o=(0,a.L)(e,r.getTime());return(o.setMonth(r.getMonth()+t+1,0),i>=o.getDate())?o:(r.setFullYear(o.getFullYear(),o.getMonth(),i),r)}},63497:function(e,t,r){r.d(t,{L:function(){return n}});function n(e,t){return e instanceof Date?new e.constructor(t):new Date(t)}},99649:function(e,t,r){r.d(t,{Q:function(){return n}});function n(e){let t=Object.prototype.toString.call(e);return e instanceof Date||"object"==typeof e&&"[object Date]"===t?new e.constructor(+e):new Date("number"==typeof e||"[object Number]"===t||"string"==typeof e||"[object String]"===t?e:NaN)}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js b/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js deleted file mode 100644 index b21241beec0..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2249],{64748:function(e,s,l){l.d(s,{Ct:function(){return a.Z},Dx:function(){return m.Z},OK:function(){return n.Z},Zb:function(){return r.Z},nP:function(){return o.Z},td:function(){return c.Z},v0:function(){return i.Z},x4:function(){return d.Z},xv:function(){return x.Z},zx:function(){return t.Z}});var a=l(41649),t=l(78489),r=l(12514),n=l(12485),i=l(18135),c=l(35242),d=l(29706),o=l(77991),x=l(84264),m=l(96761)},78801:function(e,s,l){l.d(s,{Z:function(){return a.Z},x:function(){return t.Z}});var a=l(12514),t=l(84264)},92249:function(e,s,l){l.d(s,{Z:function(){return W}});var a=l(57437),t=l(23639),r=l(64748),n=l(22116),i=l(78867),c=l(99376),d=l(2265),o=l(17906),x=l(20347),m=l(41649),h=l(78489),u=l(84264),p=l(99981),g=l(3810),j=l(15424),v=l(15690),b=l(10032),N=l(61994),f=l(5545),y=l(96761),_=l(19250),k=l(9114);let{Step:w}=v.default;var Z=e=>{let{visible:s,onClose:l,accessToken:t,agentHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),Z=()=>{o(0),h(new Set),j.resetFields(),l()},C=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.agent_id||e.name))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>!0===e.is_public).map(e=>e.agent_id||e.name)))},[s,r]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeAgentsPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," agent(s) public!")),Z(),i()}catch(e){console.error("Error making agents public:",e),k.Z.fromBackend("Failed to make agents public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.agent_id||e.name)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Agents to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the agents you want to be visible on the public model hub. Users will still require a valid API key to use these agents."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No agents available."})}):r.map(e=>{let s=e.agent_id||e.name;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(s),onChange:e=>C(s,e.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.name}),(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",e.version]})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description}),e.skills&&e.skills.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.skills.slice(0,3).map(e=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e.name},e.id)),e.skills.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.skills.length-3," more"]})]})]})]},s)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Agents Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these agents public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Agents to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>(s.agent_id||s.name)===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.name)||e}),s&&(0,a.jsxs)(m.Z,{color:"blue",size:"xs",children:["v",s.version]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Agents Public",open:s,onCancel:Z,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(w,{title:"Select Agents"}),(0,a.jsx)(w,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?Z:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})};let{Step:C}=v.default;var S=e=>{let{visible:s,onClose:l,accessToken:t,mcpHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),w=()=>{o(0),h(new Set),j.resetFields(),l()},Z=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.server_id))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>{var s;return(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0}).map(e=>e.server_id)))},[s]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeMCPPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," MCP server(s) public!")),w(),i()}catch(e){console.error("Error making MCP servers public:",e),k.Z.fromBackend("Failed to make MCP servers public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.server_id)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select MCP Servers to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the MCP servers you want to be visible on the public model hub. Users will still require a valid API key to use these servers."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No MCP servers available."})}):r.map(e=>{var s;let l=(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.server_id),onChange:s=>Z(e.server_id,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.server_name}),l&&(0,a.jsx)(m.Z,{color:"emerald",size:"sm",children:"Public"}),(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:e.transport}),(0,a.jsx)(m.Z,{color:"active"===e.status||"healthy"===e.status?"green":"inactive"===e.status||"unhealthy"===e.status?"red":"gray",size:"sm",children:e.status||"unknown"})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description||e.url}),e.allowed_tools&&e.allowed_tools.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.allowed_tools.slice(0,3).map((e,s)=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e},s)),e.allowed_tools.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.allowed_tools.length-3," more"]})]})]})]},e.server_id)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making MCP Servers Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these MCP servers public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"MCP Servers to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.server_id===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.server_name)||e}),s&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:s.transport}),(0,a.jsx)(m.Z,{color:"active"===s.status||"healthy"===s.status?"green":"inactive"===s.status||"unhealthy"===s.status?"red":"gray",size:"xs",children:s.status||"unknown"})]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description}),(null==s?void 0:s.url)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-500 mt-1",children:s.url})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make MCP Servers Public",open:s,onCancel:w,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(C,{title:"Select Servers"}),(0,a.jsx)(C,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?w:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})},P=l(78801),M=e=>{let{modelHubData:s,onFilteredDataChange:l,showFiltersCard:t=!0,className:r=""}=e,[n,i]=(0,d.useState)(""),[c,o]=(0,d.useState)(""),[x,m]=(0,d.useState)(""),[h,u]=(0,d.useState)(""),p=(0,d.useRef)([]),g=(0,d.useMemo)(()=>(null==s?void 0:s.filter(e=>{let s=e.model_group.toLowerCase().includes(n.toLowerCase()),l=""===c||e.providers.includes(c),a=""===x||e.mode===x,t=""===h||Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).some(e=>{let[s]=e;return s.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")===h});return s&&l&&a&&t}))||[],[s,n,c,x,h]);(0,d.useEffect)(()=>{(g.length!==p.current.length||g.some((e,s)=>{var l;return e.model_group!==(null===(l=p.current[s])||void 0===l?void 0:l.model_group)}))&&(p.current=g,l(g))},[g,l]);let j=(0,a.jsxs)("div",{className:"flex flex-wrap gap-4 items-center",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Search Models:"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names...",value:n,onChange:e=>i(e.target.value),className:"border rounded px-3 py-2 w-64 h-10 text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Provider:"}),(0,a.jsxs)("select",{value:c,onChange:e=>o(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Providers"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.providers.forEach(e=>s.add(e))}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Mode:"}),(0,a.jsxs)("select",{value:x,onChange:e=>m(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Modes"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.mode&&s.add(e.mode)}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Features:"}),(0,a.jsxs)("select",{value:h,onChange:e=>u(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Features"}),s&&(e=>{let s=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).forEach(e=>{let[l]=e,a=l.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");s.add(a)})}),Array.from(s).sort()})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(n||c||x||h)&&(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsx)("button",{onClick:()=>{i(""),o(""),m(""),u("")},className:"text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center",children:"Clear Filters"})})]});return t?(0,a.jsx)(P.Z,{className:"mb-6 ".concat(r),children:j}):(0,a.jsx)("div",{className:r,children:j})};let{Step:z}=v.default;var A=e=>{let{visible:s,onClose:l,accessToken:t,modelHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)([]),[j,w]=(0,d.useState)(!1),[Z]=b.Z.useForm(),C=()=>{o(0),h(new Set),g([]),Z.resetFields(),l()},S=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},P=e=>{e?h(new Set(p.map(e=>e.model_group))):h(new Set)},A=(0,d.useCallback)(e=>{g(e)},[]);(0,d.useEffect)(()=>{s&&r.length>0&&(g(r),h(new Set(r.filter(e=>!0===e.is_public_model_group).map(e=>e.model_group))))},[s,r]);let F=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}w(!0);try{let e=Array.from(x);await (0,_.makeModelGroupPublic)(t,e),k.Z.success("Successfully made ".concat(e.length," model group(s) public!")),C(),i()}catch(e){console.error("Error making model groups public:",e),k.Z.fromBackend("Failed to make model groups public. Please try again.")}finally{w(!1)}},L=()=>{let e=p.length>0&&p.every(e=>x.has(e.model_group)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Models to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>P(e.target.checked),disabled:0===p.length,children:["Select All ",p.length>0&&"(".concat(p.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the models you want to be visible on the public model hub. Users will still require a valid API key to use these models."}),(0,a.jsx)(M,{modelHubData:r,onFilteredDataChange:A,showFiltersCard:!1,className:"border rounded-lg p-4 bg-gray-50"}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===p.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No models match the current filters."})}):p.map(e=>(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.model_group),onChange:s=>S(e.model_group,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.model_group}),e.mode&&(0,a.jsx)(m.Z,{color:"green",size:"sm",children:e.mode})]}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})]},e.model_group))})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," selected"]})})]})},D=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Models Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these models public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Models to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.model_group===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e}),s&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:s.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Models Public",open:s,onCancel:C,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:Z,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(z,{title:"Select Models"}),(0,a.jsx)(z,{title:"Confirm"})]}),(()=>{switch(c){case 0:return L();case 1:return D();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?C:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:F,loading:j,children:"Make Public"})]})]})]})})},F=l(8048);let L=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),D=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),O=e=>"$".concat((1e6*e).toFixed(2)),U=e=>e>=1e6?"".concat((e/1e6).toFixed(1),"M"):e>=1e3?"".concat((e/1e3).toFixed(1),"K"):e.toString(),E=function(e,s){let l=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=[{header:"Public Model Name",accessorKey:"model_group",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.model_group}),(0,a.jsx)(p.Z,{title:"Copy model name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.providers.join(", ")})})]})}},{header:"Provider",accessorKey:"providers",enableSorting:!0,sortingFn:(e,s)=>{let l=e.original.providers.join(", "),a=s.original.providers.join(", ");return l.localeCompare(a)},cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.providers.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"blue",className:"text-xs",children:e},e)),l.providers.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.providers.length-2]})]})},meta:{className:"hidden md:table-cell"}},{header:"Mode",accessorKey:"mode",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return l.mode?(0,a.jsx)(m.Z,{color:"green",size:"sm",children:l.mode}):(0,a.jsx)(u.Z,{className:"text-gray-500",children:"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Tokens",accessorKey:"max_input_tokens",enableSorting:!0,sortingFn:(e,s)=>(e.original.max_input_tokens||0)+(e.original.max_output_tokens||0)-((s.original.max_input_tokens||0)+(s.original.max_output_tokens||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)("div",{className:"space-y-1",children:(0,a.jsxs)(u.Z,{className:"text-xs",children:[l.max_input_tokens?U(l.max_input_tokens):"-"," /"," ",l.max_output_tokens?U(l.max_output_tokens):"-"]})})},meta:{className:"hidden lg:table-cell"}},{header:"Cost/1M",accessorKey:"input_cost_per_token",enableSorting:!0,sortingFn:(e,s)=>(e.original.input_cost_per_token||0)+(e.original.output_cost_per_token||0)-((s.original.input_cost_per_token||0)+(s.original.output_cost_per_token||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs",children:l.input_cost_per_token?O(l.input_cost_per_token):"-"}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-500",children:l.output_cost_per_token?O(l.output_cost_per_token):"-"})]})}},{header:"Features",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=D(s.original),t=["green","blue","purple","orange","red","yellow"];return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map((e,s)=>(0,a.jsx)(m.Z,{color:t[s%t.length],size:"xs",children:L(e)},e))})}},{header:"Public",accessorKey:"is_public_model_group",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public_model_group?1:0)-(!0===s.original.is_public_model_group?1:0),cell:e=>{let{row:s}=e;return!0===s.original.is_public_model_group?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}];return l?r.filter(e=>!("accessorKey"in e)||"is_public_model_group"!==e.accessorKey):r};var K=l(87526),T=l(86462),H=l(47686),I=l(77355),R=l(93416),B=l(74998),Y=l(95704),V=e=>{let{accessToken:s,userRole:l}=e,[t,r]=(0,d.useState)([]),[i,c]=(0,d.useState)({url:"",displayName:""}),[o,m]=(0,d.useState)(null),[h,u]=(0,d.useState)(!1),[p,g]=(0,d.useState)(!0),j=async()=>{if(s)try{u(!0);let e=await (0,_.getPublicModelHubInfo)();if(e&&e.useful_links){let s=e.useful_links||{},l=Object.entries(s).map((e,s)=>{let[l,a]=e;return{id:"".concat(s,"-").concat(l),displayName:l,url:a}});r(l)}else r([])}catch(e){console.error("Error fetching useful links:",e),r([])}finally{u(!1)}};if((0,d.useEffect)(()=>{j()},[s]),!(0,x.tY)(l||""))return null;let v=async e=>{if(!s)return!1;try{let l={};return e.forEach(e=>{l[e.displayName]=e.url}),await (0,_.updateUsefulLinksCall)(s,l),n.Z.success({title:"Links Saved Successfully",content:(0,a.jsxs)("div",{className:"py-4",children:[(0,a.jsx)("p",{className:"text-gray-600 mb-4",children:"Your useful links have been saved and are now visible on the public model hub."}),(0,a.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4",children:[(0,a.jsx)("p",{className:"text-sm text-blue-800 mb-2 font-medium",children:"View your updated model hub:"}),(0,a.jsx)("a",{href:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table"),target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center text-blue-600 hover:text-blue-800 underline text-sm font-medium",children:"Open Public Model Hub →"})]})]}),width:500,okText:"Close",maskClosable:!0,keyboard:!0}),!0}catch(e){return console.error("Error saving links:",e),k.Z.fromBackend("Failed to save links - ".concat(e)),!1}},b=async()=>{if(!i.url||!i.displayName)return;try{new URL(i.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.displayName===i.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=[...t,{id:"".concat(Date.now(),"-").concat(i.displayName),displayName:i.displayName,url:i.url}];await v(e)&&(r(e),c({url:"",displayName:""}),k.Z.success("Link added successfully"))},N=e=>{m({...e})},f=async()=>{if(!o)return;try{new URL(o.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.id!==o.id&&e.displayName===o.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=t.map(e=>e.id===o.id?o:e);await v(e)&&(r(e),m(null),k.Z.success("Link updated successfully"))},y=()=>{m(null)},w=async e=>{let s=t.filter(s=>s.id!==e);await v(s)&&(r(s),k.Z.success("Link deleted successfully"))},Z=e=>{window.open(e,"_blank")};return(0,a.jsxs)(Y.Zb,{className:"mb-6",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between cursor-pointer",onClick:()=>g(!p),children:[(0,a.jsxs)("div",{className:"flex flex-col",children:[(0,a.jsx)(Y.Dx,{className:"mb-0",children:"Link Management"}),(0,a.jsx)("p",{className:"text-sm text-gray-500",children:"Manage the links that are displayed under 'Useful Links' on the public model hub."})]}),(0,a.jsx)("div",{className:"flex items-center",children:p?(0,a.jsx)(T.Z,{className:"w-5 h-5 text-gray-500"}):(0,a.jsx)(H.Z,{className:"w-5 h-5 text-gray-500"})})]}),p&&(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Link"}),(0,a.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"URL"}),(0,a.jsx)("input",{type:"text",value:i.url,onChange:e=>c({...i,url:e.target.value}),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Display Name"}),(0,a.jsx)("input",{type:"text",value:i.displayName,onChange:e=>c({...i,displayName:e.target.value}),placeholder:"Friendly name",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsxs)("button",{onClick:b,disabled:!i.url||!i.displayName,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(i.url&&i.displayName?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,a.jsx)(I.Z,{className:"w-4 h-4 mr-1"}),"Add Link"]})})]})]}),(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Links"}),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(Y.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(Y.ss,{children:(0,a.jsxs)(Y.SC,{children:[(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Display Name"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"URL"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,a.jsxs)(Y.RM,{children:[t.map(e=>(0,a.jsx)(Y.SC,{className:"h-8",children:o&&o.id===e.id?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.displayName,onChange:e=>m({...o,displayName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.url,onChange:e=>m({...o,url:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:f,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,a.jsx)("button",{onClick:y,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-900",children:e.displayName}),(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-500",children:e.url}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:()=>Z(e.url),className:"text-xs bg-green-50 text-green-600 px-2 py-1 rounded hover:bg-green-100",children:"Use"}),(0,a.jsx)("button",{onClick:()=>N(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,a.jsx)(R.Z,{className:"w-3 h-3"})}),(0,a.jsx)("button",{onClick:()=>w(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,a.jsx)(B.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===t.length&&(0,a.jsx)(Y.SC,{children:(0,a.jsx)(Y.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No links added yet. Add a new link above."})})]})]})})})]})]})},W=e=>{var s,l,v,b;let{accessToken:N,publicPage:f,premiumUser:y,userRole:w}=e,[C,P]=(0,d.useState)(!1),[z,L]=(0,d.useState)(null),[D,O]=(0,d.useState)(!0),[U,T]=(0,d.useState)(!1),[H,I]=(0,d.useState)(!1),[R,B]=(0,d.useState)(null),[Y,W]=(0,d.useState)([]),[q,G]=(0,d.useState)(!1),[J,$]=(0,d.useState)(null),[Q,X]=(0,d.useState)(!1),[ee,es]=(0,d.useState)(!0),[el,ea]=(0,d.useState)(null),[et,er]=(0,d.useState)(!1),[en,ei]=(0,d.useState)(null),[ec,ed]=(0,d.useState)(!0),[eo,ex]=(0,d.useState)(null),[em,eh]=(0,d.useState)(!1),[eu,ep]=(0,d.useState)(!1),eg=(0,c.useRouter)(),ej=(0,d.useRef)(null),ev=(0,d.useRef)(null),eb=(0,d.useRef)(null);(0,d.useEffect)(()=>{let e=async e=>{try{O(!0);let s=await (0,_.modelHubCall)(e);console.log("ModelHubData:",s),L(s.data),(0,_.getConfigFieldSetting)(e,"enable_public_model_hub").then(e=>{console.log("data: ".concat(JSON.stringify(e))),!0==e.field_value&&P(!0)}).catch(e=>{})}catch(e){console.error("There was an error fetching the model data",e)}finally{O(!1)}},s=async()=>{try{var e,s;O(!0),await (0,_.getUiConfig)();let l=await (0,_.modelHubPublicModelsCall)();console.log("ModelHubData:",l),console.log("First model structure:",l[0]),console.log("Model has model_group?",null===(e=l[0])||void 0===e?void 0:e.model_group),console.log("Model has providers?",null===(s=l[0])||void 0===s?void 0:s.providers),L(l),P(!0)}catch(e){console.error("There was an error fetching the public model data",e)}finally{O(!1)}};N?e(N):f&&s()},[N,f]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{es(!0);let e=await (0,_.getAgentsList)(N);console.log("AgentHubData:",e);let s=e.agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.litellm_params.is_public}));$(s)}catch(e){console.error("There was an error fetching the agent data",e)}finally{es(!1)}};f||e()},[f,N]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{ed(!0);let e=await (0,_.fetchMCPServers)(N);console.log("MCPHubData:",e),ei(e)}catch(e){console.error("There was an error fetching the MCP server data",e)}finally{ed(!1)}};f||e()},[f,N]);let eN=()=>{N&&G(!0)},ef=()=>{N&&X(!0)},ey=()=>{N&&ep(!0)},e_=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ek=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ew=e=>{navigator.clipboard.writeText(e),k.Z.success("Copied to clipboard!")},eZ=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),eC=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),eS=e=>"$".concat((1e6*e).toFixed(2)),eP=(0,d.useCallback)(e=>{W(e)},[]);return(console.log("publicPage: ",f),console.log("publicPageAllowed: ",C),f&&C)?(0,a.jsx)(K.Z,{accessToken:N}):(0,a.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[!1==f?(0,a.jsxs)("div",{className:"w-full m-2 mt-2 p-8",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,a.jsxs)("div",{className:"flex flex-col items-start",children:[(0,a.jsx)(r.Dx,{className:"text-center",children:"AI Hub"}),(0,x.tY)(w||"")?(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"Make models, agents, and MCP servers public for developers to know what's available."}):(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"A list of all public model names personally available to you."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,a.jsx)(r.xv,{children:"Model Hub URL:"}),(0,a.jsxs)("div",{className:"flex items-center bg-gray-200 px-2 py-1 rounded",children:[(0,a.jsx)(r.xv,{className:"mr-2",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")}),(0,a.jsx)("button",{onClick:()=>ew("".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")),className:"p-1 hover:bg-gray-300 rounded transition-colors",title:"Copy URL",children:(0,a.jsx)(i.Z,{size:16,className:"text-gray-600"})})]})]})]}),(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"mt-8 mb-2",children:(0,a.jsx)(V,{accessToken:N,userRole:w})}),(0,a.jsxs)(r.v0,{children:[(0,a.jsxs)(r.td,{className:"mb-4",children:[(0,a.jsx)(r.OK,{children:"Model Hub"}),(0,a.jsx)(r.OK,{children:"Agent Hub"}),(0,a.jsx)(r.OK,{children:"MCP Hub"})]}),(0,a.jsxs)(r.nP,{children:[(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>eN(),children:"Select Models to Make Public"})}),(0,a.jsx)(M,{modelHubData:z||[],onFilteredDataChange:eP}),(0,a.jsx)(F.C,{columns:E(e=>{B(e),T(!0)},ew,f),data:Y,isLoading:D,table:ej,defaultSorting:[{id:"model_group",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",Y.length," of ",(null==z?void 0:z.length)||0," models"]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ef(),children:"Select Agents to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Agent Name",accessorKey:"name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.name}),(0,a.jsx)(p.Z,{title:"Copy agent name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"Version",accessorKey:"version",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",l.version]})},meta:{className:"hidden lg:table-cell"}},{header:"Protocol",accessorKey:"protocolVersion",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.protocolVersion||"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Skills",accessorKey:"skills",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.skills||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs font-medium",children:[l.length," skill",1!==l.length?"s":""]}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e.name},e.id)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})}},{header:"Capabilities",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=Object.entries(s.original.capabilities||{}).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return s});return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map(e=>(0,a.jsx)(m.Z,{color:"green",size:"xs",children:e},e))})}},{header:"I/O Modes",accessorKey:"defaultInputModes",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original,t=l.defaultInputModes||[],r=l.defaultOutputModes||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"In:"})," ",t.join(", ")||"-"]}),(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"Out:"})," ",r.join(", ")||"-"]})]})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"is_public",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public?1:0)-(!0===s.original.is_public?1:0),cell:e=>{let{row:s}=e;return console.log("CHECKPOINT 1: ".concat(JSON.stringify(s.original))),!0===s.original.is_public?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ea(e),er(!0)},ew,f),data:J||[],isLoading:ee,table:ev,defaultSorting:[{id:"name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==J?void 0:J.length)||0," agent",(null==J?void 0:J.length)!==1?"s":""]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ey(),children:"Select MCP Servers to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Server Name",accessorKey:"server_name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.server_name}),(0,a.jsx)(p.Z,{title:"Copy server name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.server_name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description||"-"})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"URL",accessorKey:"url",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"text-xs truncate max-w-xs",children:r.url}),(0,a.jsx)(p.Z,{title:"Copy URL",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs flex-shrink-0"})})]})},meta:{className:"hidden lg:table-cell"}},{header:"Transport",accessorKey:"transport",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:l.transport})},meta:{className:"hidden md:table-cell"}},{header:"Auth Type",accessorKey:"auth_type",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t="none"===l.auth_type?"gray":"green";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.auth_type})},meta:{className:"hidden md:table-cell"}},{header:"Status",accessorKey:"status",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t={active:"green",inactive:"red",unknown:"gray",healthy:"green",unhealthy:"red"}[l.status]||"gray";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.status||"unknown"})}},{header:"Tools",accessorKey:"allowed_tools",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.allowed_tools||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs font-medium",children:l.length>0?"".concat(l.length," tool").concat(1!==l.length?"s":""):"All tools"}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map((e,s)=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e},s)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})},meta:{className:"hidden lg:table-cell"}},{header:"Created By",accessorKey:"created_by",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.created_by||"-"})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"mcp_info.is_public",enableSorting:!0,sortingFn:(e,s)=>{var l,a;return((null===(l=e.original.mcp_info)||void 0===l?void 0:l.is_public)===!0?1:0)-((null===(a=s.original.mcp_info)||void 0===a?void 0:a.is_public)===!0?1:0)},cell:e=>{var s;let{row:l}=e;return(null===(s=l.original.mcp_info)||void 0===s?void 0:s.is_public)===!0?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ex(e),eh(!0)},ew,f),data:en||[],isLoading:ec,table:eb,defaultSorting:[{id:"server_name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==en?void 0:en.length)||0," MCP server",(null==en?void 0:en.length)!==1?"s":""]})})]})]})]})]}):(0,a.jsxs)(r.Zb,{className:"mx-auto max-w-xl mt-10",children:[(0,a.jsx)(r.xv,{className:"text-xl text-center mb-2 text-black",children:"Public Model Hub not enabled."}),(0,a.jsx)("p",{className:"text-base text-center text-slate-800",children:"Ask your proxy admin to enable this on their Admin UI."})]}),(0,a.jsx)(n.Z,{title:"Public Model Hub",width:600,visible:H,footer:null,onOk:e_,onCancel:ek,children:(0,a.jsxs)("div",{className:"pt-5 pb-5",children:[(0,a.jsxs)("div",{className:"flex justify-between mb-4",children:[(0,a.jsx)(r.xv,{className:"text-base mr-2",children:"Shareable Link:"}),(0,a.jsx)(r.xv,{className:"max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")})]}),(0,a.jsx)("div",{className:"flex justify-end",children:(0,a.jsx)(r.zx,{onClick:()=>{eg.replace("/model_hub_table?key=".concat(N))},children:"See Page"})})]})}),(0,a.jsx)(n.Z,{title:(null==R?void 0:R.model_group)||"Model Details",width:1e3,visible:U,footer:null,onOk:e_,onCancel:ek,children:R&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Model Group:"}),(0,a.jsx)(r.xv,{children:R.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(r.xv,{children:R.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:R.providers.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e))})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(s=R.max_input_tokens)||void 0===s?void 0:s.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(l=R.max_output_tokens)||void 0===l?void 0:l.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.input_cost_per_token?eS(R.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.output_cost_per_token?eS(R.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=eC(R),s=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(r.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,l)=>(0,a.jsx)(r.Ct,{color:s[l%s.length],children:eZ(e)},e))})()})]}),(R.tpm||R.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[R.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(r.xv,{children:R.tpm.toLocaleString()})]}),R.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(r.xv,{children:R.rpm.toLocaleString()})]})]})]}),R.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:R.supported_openai_params.map(e=>(0,a.jsx)(r.Ct,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'import openai\n\nclient = openai.OpenAI(\n api_key="your_api_key",\n base_url="http://0.0.0.0:4000" # Your LiteLLM Proxy URL\n)\n\nresponse = client.chat.completions.create(\n model="'.concat(R.model_group,'",\n messages=[\n {\n "role": "user",\n "content": "Hello, how are you?"\n }\n ]\n)\n\nprint(response.choices[0].message.content)')})]})]})}),(0,a.jsx)(n.Z,{title:(null==el?void 0:el.name)||"Agent Details",width:1e3,visible:et,footer:null,onOk:e_,onCancel:ek,children:el&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Agent Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Name:"}),(0,a.jsx)(r.xv,{children:el.name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Version:"}),(0,a.jsxs)(r.Ct,{color:"blue",children:["v",el.version]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Protocol Version:"}),(0,a.jsx)(r.xv,{children:el.protocolVersion})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"truncate",children:el.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(el.url),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:el.description})]})]}),el.capabilities&&Object.keys(el.capabilities).length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(el.capabilities).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return(0,a.jsx)(r.Ct,{color:"green",children:s},s)})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Input/Output Modes"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(v=el.defaultInputModes)||void 0===v?void 0:v.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(b=el.defaultOutputModes)||void 0===b?void 0:b.map(e=>(0,a.jsx)(r.Ct,{color:"purple",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]})]})]}),el.skills&&el.skills.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Skills"}),(0,a.jsx)("div",{className:"space-y-4",children:el.skills.map(e=>(0,a.jsxs)("div",{className:"border border-gray-200 rounded p-4",children:[(0,a.jsxs)("div",{className:"flex justify-between items-start mb-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium text-base",children:e.name}),(0,a.jsxs)(r.xv,{className:"text-xs text-gray-500",children:["ID: ",e.id]})]}),e.tags&&e.tags.length>0&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:e.tags.map(e=>(0,a.jsx)(r.Ct,{color:"purple",size:"xs",children:e},e))})]}),(0,a.jsx)(r.xv,{className:"text-sm mb-2",children:e.description}),e.examples&&e.examples.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-xs font-medium text-gray-700",children:"Examples:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.examples.map((e,s)=>(0,a.jsx)(r.Ct,{color:"gray",size:"xs",children:e},s))})]})]},e.id))})]}),el.supportsAuthenticatedExtendedCard&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Additional Features"}),(0,a.jsx)(r.Ct,{color:"green",children:"Supports Authenticated Extended Card"})]})]})}),(0,a.jsx)(n.Z,{title:(null==eo?void 0:eo.server_name)||"MCP Server Details",width:1e3,visible:em,footer:null,onOk:e_,onCancel:ek,children:eo&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Server Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server Name:"}),(0,a.jsx)(r.xv,{children:eo.server_name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server ID:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"text-xs truncate",children:eo.server_id}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.server_id),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]}),eo.alias&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Alias:"}),(0,a.jsx)(r.xv,{children:eo.alias})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Transport:"}),(0,a.jsx)(r.Ct,{color:"blue",children:eo.transport})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Auth Type:"}),(0,a.jsx)(r.Ct,{color:"none"===eo.auth_type?"gray":"green",children:eo.auth_type})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Status:"}),(0,a.jsx)(r.Ct,{color:"active"===eo.status||"healthy"===eo.status?"green":"inactive"===eo.status||"unhealthy"===eo.status?"red":"gray",children:eo.status||"unknown"})]})]}),eo.description&&(0,a.jsxs)("div",{className:"mt-2",children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:eo.description})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Connection Details"}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2 mt-1",children:[(0,a.jsx)(r.xv,{className:"text-sm break-all bg-gray-100 p-2 rounded flex-1",children:eo.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0"})]})]}),eo.command&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Command:"}),(0,a.jsx)(r.xv,{className:"text-sm bg-gray-100 p-2 rounded mt-1 font-mono",children:eo.command})]})]})]}),eo.allowed_tools&&eo.allowed_tools.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Allowed Tools"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.allowed_tools.map((e,s)=>(0,a.jsx)(r.Ct,{color:"purple",children:e},s))})]}),eo.teams&&eo.teams.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Teams"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.teams.map((e,s)=>(0,a.jsx)(r.Ct,{color:"blue",children:e},s))})]}),eo.mcp_access_groups&&eo.mcp_access_groups.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Access Groups"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.mcp_access_groups.map((e,s)=>(0,a.jsx)(r.Ct,{color:"green",children:e},s))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Metadata"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created By:"}),(0,a.jsx)(r.xv,{children:eo.created_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated By:"}),(0,a.jsx)(r.xv,{children:eo.updated_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.created_at).toLocaleString()})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.updated_at).toLocaleString()})]}),eo.last_health_check&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Last Health Check:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.last_health_check).toLocaleString()})]})]}),eo.health_check_error&&(0,a.jsxs)("div",{className:"mt-2 p-2 bg-red-50 rounded",children:[(0,a.jsx)(r.xv,{className:"font-medium text-red-700",children:"Health Check Error:"}),(0,a.jsx)(r.xv,{className:"text-sm text-red-600 mt-1",children:eo.health_check_error})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'from fastmcp import Client\nimport asyncio\n\n# Standard MCP configuration\nconfig = {\n "mcpServers": {\n "'.concat(eo.server_name,'": {\n "url": "http://localhost:4000/').concat(eo.server_name,'/mcp",\n "headers": {\n "x-litellm-api-key": "Bearer sk-1234"\n }\n }\n }\n}\n\n# Create a client that connects to the server\nclient = Client(config)\n\nasync def main():\n async with client:\n # List available tools\n tools = await client.list_tools()\n print(f"Available tools: {[tool.name for tool in tools]}")\n\n # Call a tool\n response = await client.call_tool(\n name="tool_name", \n arguments={"arg": "value"}\n )\n print(f"Response: {response}")\n\nif __name__ == "__main__":\n asyncio.run(main())')})]})]})}),(0,a.jsx)(A,{visible:q,onClose:()=>G(!1),accessToken:N||"",modelHubData:z||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.modelHubCall)(N);L(e.data)}catch(e){console.error("Error refreshing model data:",e)}})()}}),(0,a.jsx)(Z,{visible:Q,onClose:()=>X(!1),accessToken:N||"",agentHubData:J||[],onSuccess:()=>{N&&(async()=>{try{let e=(await (0,_.getAgentsList)(N)).agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.is_public}));$(e)}catch(e){console.error("Error refreshing agent data:",e)}})()}}),(0,a.jsx)(S,{visible:eu,onClose:()=>ep(!1),accessToken:N||"",mcpHubData:en||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.fetchMCPServers)(N);ei(e)}catch(e){console.error("Error refreshing MCP server data:",e)}})()}})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js b/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js new file mode 100644 index 00000000000..1c6e112db48 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2249],{64748:function(e,s,l){l.d(s,{Ct:function(){return a.Z},Dx:function(){return m.Z},OK:function(){return n.Z},Zb:function(){return r.Z},nP:function(){return o.Z},td:function(){return c.Z},v0:function(){return i.Z},x4:function(){return d.Z},xv:function(){return x.Z},zx:function(){return t.Z}});var a=l(41649),t=l(78489),r=l(12514),n=l(12485),i=l(18135),c=l(35242),d=l(29706),o=l(77991),x=l(84264),m=l(96761)},78801:function(e,s,l){l.d(s,{Z:function(){return a.Z},x:function(){return t.Z}});var a=l(12514),t=l(84264)},92249:function(e,s,l){l.d(s,{Z:function(){return W}});var a=l(57437),t=l(23639),r=l(64748),n=l(22116),i=l(78867),c=l(99376),d=l(2265),o=l(17906),x=l(20347),m=l(41649),h=l(78489),u=l(84264),p=l(99981),g=l(3810),j=l(15424),v=l(15690),b=l(10032),N=l(4156),f=l(5545),y=l(96761),_=l(19250),k=l(9114);let{Step:w}=v.default;var Z=e=>{let{visible:s,onClose:l,accessToken:t,agentHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),Z=()=>{o(0),h(new Set),j.resetFields(),l()},C=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.agent_id||e.name))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>!0===e.is_public).map(e=>e.agent_id||e.name)))},[s,r]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeAgentsPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," agent(s) public!")),Z(),i()}catch(e){console.error("Error making agents public:",e),k.Z.fromBackend("Failed to make agents public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.agent_id||e.name)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Agents to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the agents you want to be visible on the public model hub. Users will still require a valid API key to use these agents."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No agents available."})}):r.map(e=>{let s=e.agent_id||e.name;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(s),onChange:e=>C(s,e.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.name}),(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",e.version]})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description}),e.skills&&e.skills.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.skills.slice(0,3).map(e=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e.name},e.id)),e.skills.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.skills.length-3," more"]})]})]})]},s)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Agents Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these agents public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Agents to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>(s.agent_id||s.name)===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.name)||e}),s&&(0,a.jsxs)(m.Z,{color:"blue",size:"xs",children:["v",s.version]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Agents Public",open:s,onCancel:Z,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(w,{title:"Select Agents"}),(0,a.jsx)(w,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?Z:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})};let{Step:C}=v.default;var S=e=>{let{visible:s,onClose:l,accessToken:t,mcpHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),w=()=>{o(0),h(new Set),j.resetFields(),l()},Z=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.server_id))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>{var s;return(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0}).map(e=>e.server_id)))},[s]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeMCPPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," MCP server(s) public!")),w(),i()}catch(e){console.error("Error making MCP servers public:",e),k.Z.fromBackend("Failed to make MCP servers public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.server_id)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select MCP Servers to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the MCP servers you want to be visible on the public model hub. Users will still require a valid API key to use these servers."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No MCP servers available."})}):r.map(e=>{var s;let l=(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.server_id),onChange:s=>Z(e.server_id,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.server_name}),l&&(0,a.jsx)(m.Z,{color:"emerald",size:"sm",children:"Public"}),(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:e.transport}),(0,a.jsx)(m.Z,{color:"active"===e.status||"healthy"===e.status?"green":"inactive"===e.status||"unhealthy"===e.status?"red":"gray",size:"sm",children:e.status||"unknown"})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description||e.url}),e.allowed_tools&&e.allowed_tools.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.allowed_tools.slice(0,3).map((e,s)=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e},s)),e.allowed_tools.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.allowed_tools.length-3," more"]})]})]})]},e.server_id)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making MCP Servers Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these MCP servers public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"MCP Servers to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.server_id===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.server_name)||e}),s&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:s.transport}),(0,a.jsx)(m.Z,{color:"active"===s.status||"healthy"===s.status?"green":"inactive"===s.status||"unhealthy"===s.status?"red":"gray",size:"xs",children:s.status||"unknown"})]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description}),(null==s?void 0:s.url)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-500 mt-1",children:s.url})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make MCP Servers Public",open:s,onCancel:w,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(C,{title:"Select Servers"}),(0,a.jsx)(C,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?w:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})},P=l(78801),M=e=>{let{modelHubData:s,onFilteredDataChange:l,showFiltersCard:t=!0,className:r=""}=e,[n,i]=(0,d.useState)(""),[c,o]=(0,d.useState)(""),[x,m]=(0,d.useState)(""),[h,u]=(0,d.useState)(""),p=(0,d.useRef)([]),g=(0,d.useMemo)(()=>(null==s?void 0:s.filter(e=>{let s=e.model_group.toLowerCase().includes(n.toLowerCase()),l=""===c||e.providers.includes(c),a=""===x||e.mode===x,t=""===h||Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).some(e=>{let[s]=e;return s.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")===h});return s&&l&&a&&t}))||[],[s,n,c,x,h]);(0,d.useEffect)(()=>{(g.length!==p.current.length||g.some((e,s)=>{var l;return e.model_group!==(null===(l=p.current[s])||void 0===l?void 0:l.model_group)}))&&(p.current=g,l(g))},[g,l]);let j=(0,a.jsxs)("div",{className:"flex flex-wrap gap-4 items-center",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Search Models:"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names...",value:n,onChange:e=>i(e.target.value),className:"border rounded px-3 py-2 w-64 h-10 text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Provider:"}),(0,a.jsxs)("select",{value:c,onChange:e=>o(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Providers"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.providers.forEach(e=>s.add(e))}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Mode:"}),(0,a.jsxs)("select",{value:x,onChange:e=>m(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Modes"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.mode&&s.add(e.mode)}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Features:"}),(0,a.jsxs)("select",{value:h,onChange:e=>u(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Features"}),s&&(e=>{let s=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).forEach(e=>{let[l]=e,a=l.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");s.add(a)})}),Array.from(s).sort()})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(n||c||x||h)&&(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsx)("button",{onClick:()=>{i(""),o(""),m(""),u("")},className:"text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center",children:"Clear Filters"})})]});return t?(0,a.jsx)(P.Z,{className:"mb-6 ".concat(r),children:j}):(0,a.jsx)("div",{className:r,children:j})};let{Step:z}=v.default;var A=e=>{let{visible:s,onClose:l,accessToken:t,modelHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)([]),[j,w]=(0,d.useState)(!1),[Z]=b.Z.useForm(),C=()=>{o(0),h(new Set),g([]),Z.resetFields(),l()},S=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},P=e=>{e?h(new Set(p.map(e=>e.model_group))):h(new Set)},A=(0,d.useCallback)(e=>{g(e)},[]);(0,d.useEffect)(()=>{s&&r.length>0&&(g(r),h(new Set(r.filter(e=>!0===e.is_public_model_group).map(e=>e.model_group))))},[s,r]);let F=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}w(!0);try{let e=Array.from(x);await (0,_.makeModelGroupPublic)(t,e),k.Z.success("Successfully made ".concat(e.length," model group(s) public!")),C(),i()}catch(e){console.error("Error making model groups public:",e),k.Z.fromBackend("Failed to make model groups public. Please try again.")}finally{w(!1)}},L=()=>{let e=p.length>0&&p.every(e=>x.has(e.model_group)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Models to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>P(e.target.checked),disabled:0===p.length,children:["Select All ",p.length>0&&"(".concat(p.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the models you want to be visible on the public model hub. Users will still require a valid API key to use these models."}),(0,a.jsx)(M,{modelHubData:r,onFilteredDataChange:A,showFiltersCard:!1,className:"border rounded-lg p-4 bg-gray-50"}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===p.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No models match the current filters."})}):p.map(e=>(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.model_group),onChange:s=>S(e.model_group,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.model_group}),e.mode&&(0,a.jsx)(m.Z,{color:"green",size:"sm",children:e.mode})]}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})]},e.model_group))})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," selected"]})})]})},D=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Models Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these models public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Models to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.model_group===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e}),s&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:s.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Models Public",open:s,onCancel:C,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:Z,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(z,{title:"Select Models"}),(0,a.jsx)(z,{title:"Confirm"})]}),(()=>{switch(c){case 0:return L();case 1:return D();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?C:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:F,loading:j,children:"Make Public"})]})]})]})})},F=l(8048);let L=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),D=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),O=e=>"$".concat((1e6*e).toFixed(2)),U=e=>e>=1e6?"".concat((e/1e6).toFixed(1),"M"):e>=1e3?"".concat((e/1e3).toFixed(1),"K"):e.toString(),E=function(e,s){let l=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=[{header:"Public Model Name",accessorKey:"model_group",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.model_group}),(0,a.jsx)(p.Z,{title:"Copy model name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.providers.join(", ")})})]})}},{header:"Provider",accessorKey:"providers",enableSorting:!0,sortingFn:(e,s)=>{let l=e.original.providers.join(", "),a=s.original.providers.join(", ");return l.localeCompare(a)},cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.providers.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"blue",className:"text-xs",children:e},e)),l.providers.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.providers.length-2]})]})},meta:{className:"hidden md:table-cell"}},{header:"Mode",accessorKey:"mode",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return l.mode?(0,a.jsx)(m.Z,{color:"green",size:"sm",children:l.mode}):(0,a.jsx)(u.Z,{className:"text-gray-500",children:"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Tokens",accessorKey:"max_input_tokens",enableSorting:!0,sortingFn:(e,s)=>(e.original.max_input_tokens||0)+(e.original.max_output_tokens||0)-((s.original.max_input_tokens||0)+(s.original.max_output_tokens||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)("div",{className:"space-y-1",children:(0,a.jsxs)(u.Z,{className:"text-xs",children:[l.max_input_tokens?U(l.max_input_tokens):"-"," /"," ",l.max_output_tokens?U(l.max_output_tokens):"-"]})})},meta:{className:"hidden lg:table-cell"}},{header:"Cost/1M",accessorKey:"input_cost_per_token",enableSorting:!0,sortingFn:(e,s)=>(e.original.input_cost_per_token||0)+(e.original.output_cost_per_token||0)-((s.original.input_cost_per_token||0)+(s.original.output_cost_per_token||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs",children:l.input_cost_per_token?O(l.input_cost_per_token):"-"}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-500",children:l.output_cost_per_token?O(l.output_cost_per_token):"-"})]})}},{header:"Features",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=D(s.original),t=["green","blue","purple","orange","red","yellow"];return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map((e,s)=>(0,a.jsx)(m.Z,{color:t[s%t.length],size:"xs",children:L(e)},e))})}},{header:"Public",accessorKey:"is_public_model_group",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public_model_group?1:0)-(!0===s.original.is_public_model_group?1:0),cell:e=>{let{row:s}=e;return!0===s.original.is_public_model_group?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}];return l?r.filter(e=>!("accessorKey"in e)||"is_public_model_group"!==e.accessorKey):r};var K=l(87526),T=l(86462),H=l(47686),I=l(77355),R=l(93416),B=l(74998),Y=l(95704),V=e=>{let{accessToken:s,userRole:l}=e,[t,r]=(0,d.useState)([]),[i,c]=(0,d.useState)({url:"",displayName:""}),[o,m]=(0,d.useState)(null),[h,u]=(0,d.useState)(!1),[p,g]=(0,d.useState)(!0),j=async()=>{if(s)try{u(!0);let e=await (0,_.getPublicModelHubInfo)();if(e&&e.useful_links){let s=e.useful_links||{},l=Object.entries(s).map((e,s)=>{let[l,a]=e;return{id:"".concat(s,"-").concat(l),displayName:l,url:a}});r(l)}else r([])}catch(e){console.error("Error fetching useful links:",e),r([])}finally{u(!1)}};if((0,d.useEffect)(()=>{j()},[s]),!(0,x.tY)(l||""))return null;let v=async e=>{if(!s)return!1;try{let l={};return e.forEach(e=>{l[e.displayName]=e.url}),await (0,_.updateUsefulLinksCall)(s,l),n.Z.success({title:"Links Saved Successfully",content:(0,a.jsxs)("div",{className:"py-4",children:[(0,a.jsx)("p",{className:"text-gray-600 mb-4",children:"Your useful links have been saved and are now visible on the public model hub."}),(0,a.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4",children:[(0,a.jsx)("p",{className:"text-sm text-blue-800 mb-2 font-medium",children:"View your updated model hub:"}),(0,a.jsx)("a",{href:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table"),target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center text-blue-600 hover:text-blue-800 underline text-sm font-medium",children:"Open Public Model Hub →"})]})]}),width:500,okText:"Close",maskClosable:!0,keyboard:!0}),!0}catch(e){return console.error("Error saving links:",e),k.Z.fromBackend("Failed to save links - ".concat(e)),!1}},b=async()=>{if(!i.url||!i.displayName)return;try{new URL(i.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.displayName===i.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=[...t,{id:"".concat(Date.now(),"-").concat(i.displayName),displayName:i.displayName,url:i.url}];await v(e)&&(r(e),c({url:"",displayName:""}),k.Z.success("Link added successfully"))},N=e=>{m({...e})},f=async()=>{if(!o)return;try{new URL(o.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.id!==o.id&&e.displayName===o.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=t.map(e=>e.id===o.id?o:e);await v(e)&&(r(e),m(null),k.Z.success("Link updated successfully"))},y=()=>{m(null)},w=async e=>{let s=t.filter(s=>s.id!==e);await v(s)&&(r(s),k.Z.success("Link deleted successfully"))},Z=e=>{window.open(e,"_blank")};return(0,a.jsxs)(Y.Zb,{className:"mb-6",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between cursor-pointer",onClick:()=>g(!p),children:[(0,a.jsxs)("div",{className:"flex flex-col",children:[(0,a.jsx)(Y.Dx,{className:"mb-0",children:"Link Management"}),(0,a.jsx)("p",{className:"text-sm text-gray-500",children:"Manage the links that are displayed under 'Useful Links' on the public model hub."})]}),(0,a.jsx)("div",{className:"flex items-center",children:p?(0,a.jsx)(T.Z,{className:"w-5 h-5 text-gray-500"}):(0,a.jsx)(H.Z,{className:"w-5 h-5 text-gray-500"})})]}),p&&(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Link"}),(0,a.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"URL"}),(0,a.jsx)("input",{type:"text",value:i.url,onChange:e=>c({...i,url:e.target.value}),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Display Name"}),(0,a.jsx)("input",{type:"text",value:i.displayName,onChange:e=>c({...i,displayName:e.target.value}),placeholder:"Friendly name",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsxs)("button",{onClick:b,disabled:!i.url||!i.displayName,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(i.url&&i.displayName?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,a.jsx)(I.Z,{className:"w-4 h-4 mr-1"}),"Add Link"]})})]})]}),(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Links"}),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(Y.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(Y.ss,{children:(0,a.jsxs)(Y.SC,{children:[(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Display Name"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"URL"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,a.jsxs)(Y.RM,{children:[t.map(e=>(0,a.jsx)(Y.SC,{className:"h-8",children:o&&o.id===e.id?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.displayName,onChange:e=>m({...o,displayName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.url,onChange:e=>m({...o,url:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:f,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,a.jsx)("button",{onClick:y,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-900",children:e.displayName}),(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-500",children:e.url}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:()=>Z(e.url),className:"text-xs bg-green-50 text-green-600 px-2 py-1 rounded hover:bg-green-100",children:"Use"}),(0,a.jsx)("button",{onClick:()=>N(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,a.jsx)(R.Z,{className:"w-3 h-3"})}),(0,a.jsx)("button",{onClick:()=>w(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,a.jsx)(B.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===t.length&&(0,a.jsx)(Y.SC,{children:(0,a.jsx)(Y.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No links added yet. Add a new link above."})})]})]})})})]})]})},W=e=>{var s,l,v,b;let{accessToken:N,publicPage:f,premiumUser:y,userRole:w}=e,[C,P]=(0,d.useState)(!1),[z,L]=(0,d.useState)(null),[D,O]=(0,d.useState)(!0),[U,T]=(0,d.useState)(!1),[H,I]=(0,d.useState)(!1),[R,B]=(0,d.useState)(null),[Y,W]=(0,d.useState)([]),[q,G]=(0,d.useState)(!1),[J,$]=(0,d.useState)(null),[Q,X]=(0,d.useState)(!1),[ee,es]=(0,d.useState)(!0),[el,ea]=(0,d.useState)(null),[et,er]=(0,d.useState)(!1),[en,ei]=(0,d.useState)(null),[ec,ed]=(0,d.useState)(!0),[eo,ex]=(0,d.useState)(null),[em,eh]=(0,d.useState)(!1),[eu,ep]=(0,d.useState)(!1),eg=(0,c.useRouter)(),ej=(0,d.useRef)(null),ev=(0,d.useRef)(null),eb=(0,d.useRef)(null);(0,d.useEffect)(()=>{let e=async e=>{try{O(!0);let s=await (0,_.modelHubCall)(e);console.log("ModelHubData:",s),L(s.data),(0,_.getConfigFieldSetting)(e,"enable_public_model_hub").then(e=>{console.log("data: ".concat(JSON.stringify(e))),!0==e.field_value&&P(!0)}).catch(e=>{})}catch(e){console.error("There was an error fetching the model data",e)}finally{O(!1)}},s=async()=>{try{var e,s;O(!0),await (0,_.getUiConfig)();let l=await (0,_.modelHubPublicModelsCall)();console.log("ModelHubData:",l),console.log("First model structure:",l[0]),console.log("Model has model_group?",null===(e=l[0])||void 0===e?void 0:e.model_group),console.log("Model has providers?",null===(s=l[0])||void 0===s?void 0:s.providers),L(l),P(!0)}catch(e){console.error("There was an error fetching the public model data",e)}finally{O(!1)}};N?e(N):f&&s()},[N,f]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{es(!0);let e=await (0,_.getAgentsList)(N);console.log("AgentHubData:",e);let s=e.agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.litellm_params.is_public}));$(s)}catch(e){console.error("There was an error fetching the agent data",e)}finally{es(!1)}};f||e()},[f,N]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{ed(!0);let e=await (0,_.fetchMCPServers)(N);console.log("MCPHubData:",e),ei(e)}catch(e){console.error("There was an error fetching the MCP server data",e)}finally{ed(!1)}};f||e()},[f,N]);let eN=()=>{N&&G(!0)},ef=()=>{N&&X(!0)},ey=()=>{N&&ep(!0)},e_=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ek=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ew=e=>{navigator.clipboard.writeText(e),k.Z.success("Copied to clipboard!")},eZ=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),eC=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),eS=e=>"$".concat((1e6*e).toFixed(2)),eP=(0,d.useCallback)(e=>{W(e)},[]);return(console.log("publicPage: ",f),console.log("publicPageAllowed: ",C),f&&C)?(0,a.jsx)(K.Z,{accessToken:N}):(0,a.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[!1==f?(0,a.jsxs)("div",{className:"w-full m-2 mt-2 p-8",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,a.jsxs)("div",{className:"flex flex-col items-start",children:[(0,a.jsx)(r.Dx,{className:"text-center",children:"AI Hub"}),(0,x.tY)(w||"")?(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"Make models, agents, and MCP servers public for developers to know what's available."}):(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"A list of all public model names personally available to you."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,a.jsx)(r.xv,{children:"Model Hub URL:"}),(0,a.jsxs)("div",{className:"flex items-center bg-gray-200 px-2 py-1 rounded",children:[(0,a.jsx)(r.xv,{className:"mr-2",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")}),(0,a.jsx)("button",{onClick:()=>ew("".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")),className:"p-1 hover:bg-gray-300 rounded transition-colors",title:"Copy URL",children:(0,a.jsx)(i.Z,{size:16,className:"text-gray-600"})})]})]})]}),(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"mt-8 mb-2",children:(0,a.jsx)(V,{accessToken:N,userRole:w})}),(0,a.jsxs)(r.v0,{children:[(0,a.jsxs)(r.td,{className:"mb-4",children:[(0,a.jsx)(r.OK,{children:"Model Hub"}),(0,a.jsx)(r.OK,{children:"Agent Hub"}),(0,a.jsx)(r.OK,{children:"MCP Hub"})]}),(0,a.jsxs)(r.nP,{children:[(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>eN(),children:"Select Models to Make Public"})}),(0,a.jsx)(M,{modelHubData:z||[],onFilteredDataChange:eP}),(0,a.jsx)(F.C,{columns:E(e=>{B(e),T(!0)},ew,f),data:Y,isLoading:D,table:ej,defaultSorting:[{id:"model_group",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",Y.length," of ",(null==z?void 0:z.length)||0," models"]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ef(),children:"Select Agents to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Agent Name",accessorKey:"name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.name}),(0,a.jsx)(p.Z,{title:"Copy agent name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"Version",accessorKey:"version",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",l.version]})},meta:{className:"hidden lg:table-cell"}},{header:"Protocol",accessorKey:"protocolVersion",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.protocolVersion||"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Skills",accessorKey:"skills",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.skills||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs font-medium",children:[l.length," skill",1!==l.length?"s":""]}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e.name},e.id)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})}},{header:"Capabilities",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=Object.entries(s.original.capabilities||{}).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return s});return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map(e=>(0,a.jsx)(m.Z,{color:"green",size:"xs",children:e},e))})}},{header:"I/O Modes",accessorKey:"defaultInputModes",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original,t=l.defaultInputModes||[],r=l.defaultOutputModes||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"In:"})," ",t.join(", ")||"-"]}),(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"Out:"})," ",r.join(", ")||"-"]})]})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"is_public",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public?1:0)-(!0===s.original.is_public?1:0),cell:e=>{let{row:s}=e;return console.log("CHECKPOINT 1: ".concat(JSON.stringify(s.original))),!0===s.original.is_public?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ea(e),er(!0)},ew,f),data:J||[],isLoading:ee,table:ev,defaultSorting:[{id:"name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==J?void 0:J.length)||0," agent",(null==J?void 0:J.length)!==1?"s":""]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ey(),children:"Select MCP Servers to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Server Name",accessorKey:"server_name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.server_name}),(0,a.jsx)(p.Z,{title:"Copy server name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.server_name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description||"-"})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"URL",accessorKey:"url",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"text-xs truncate max-w-xs",children:r.url}),(0,a.jsx)(p.Z,{title:"Copy URL",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs flex-shrink-0"})})]})},meta:{className:"hidden lg:table-cell"}},{header:"Transport",accessorKey:"transport",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:l.transport})},meta:{className:"hidden md:table-cell"}},{header:"Auth Type",accessorKey:"auth_type",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t="none"===l.auth_type?"gray":"green";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.auth_type})},meta:{className:"hidden md:table-cell"}},{header:"Status",accessorKey:"status",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t={active:"green",inactive:"red",unknown:"gray",healthy:"green",unhealthy:"red"}[l.status]||"gray";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.status||"unknown"})}},{header:"Tools",accessorKey:"allowed_tools",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.allowed_tools||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs font-medium",children:l.length>0?"".concat(l.length," tool").concat(1!==l.length?"s":""):"All tools"}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map((e,s)=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e},s)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})},meta:{className:"hidden lg:table-cell"}},{header:"Created By",accessorKey:"created_by",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.created_by||"-"})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"mcp_info.is_public",enableSorting:!0,sortingFn:(e,s)=>{var l,a;return((null===(l=e.original.mcp_info)||void 0===l?void 0:l.is_public)===!0?1:0)-((null===(a=s.original.mcp_info)||void 0===a?void 0:a.is_public)===!0?1:0)},cell:e=>{var s;let{row:l}=e;return(null===(s=l.original.mcp_info)||void 0===s?void 0:s.is_public)===!0?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ex(e),eh(!0)},ew,f),data:en||[],isLoading:ec,table:eb,defaultSorting:[{id:"server_name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==en?void 0:en.length)||0," MCP server",(null==en?void 0:en.length)!==1?"s":""]})})]})]})]})]}):(0,a.jsxs)(r.Zb,{className:"mx-auto max-w-xl mt-10",children:[(0,a.jsx)(r.xv,{className:"text-xl text-center mb-2 text-black",children:"Public Model Hub not enabled."}),(0,a.jsx)("p",{className:"text-base text-center text-slate-800",children:"Ask your proxy admin to enable this on their Admin UI."})]}),(0,a.jsx)(n.Z,{title:"Public Model Hub",width:600,visible:H,footer:null,onOk:e_,onCancel:ek,children:(0,a.jsxs)("div",{className:"pt-5 pb-5",children:[(0,a.jsxs)("div",{className:"flex justify-between mb-4",children:[(0,a.jsx)(r.xv,{className:"text-base mr-2",children:"Shareable Link:"}),(0,a.jsx)(r.xv,{className:"max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")})]}),(0,a.jsx)("div",{className:"flex justify-end",children:(0,a.jsx)(r.zx,{onClick:()=>{eg.replace("/model_hub_table?key=".concat(N))},children:"See Page"})})]})}),(0,a.jsx)(n.Z,{title:(null==R?void 0:R.model_group)||"Model Details",width:1e3,visible:U,footer:null,onOk:e_,onCancel:ek,children:R&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Model Group:"}),(0,a.jsx)(r.xv,{children:R.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(r.xv,{children:R.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:R.providers.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e))})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(s=R.max_input_tokens)||void 0===s?void 0:s.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(l=R.max_output_tokens)||void 0===l?void 0:l.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.input_cost_per_token?eS(R.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.output_cost_per_token?eS(R.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=eC(R),s=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(r.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,l)=>(0,a.jsx)(r.Ct,{color:s[l%s.length],children:eZ(e)},e))})()})]}),(R.tpm||R.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[R.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(r.xv,{children:R.tpm.toLocaleString()})]}),R.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(r.xv,{children:R.rpm.toLocaleString()})]})]})]}),R.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:R.supported_openai_params.map(e=>(0,a.jsx)(r.Ct,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'import openai\n\nclient = openai.OpenAI(\n api_key="your_api_key",\n base_url="http://0.0.0.0:4000" # Your LiteLLM Proxy URL\n)\n\nresponse = client.chat.completions.create(\n model="'.concat(R.model_group,'",\n messages=[\n {\n "role": "user",\n "content": "Hello, how are you?"\n }\n ]\n)\n\nprint(response.choices[0].message.content)')})]})]})}),(0,a.jsx)(n.Z,{title:(null==el?void 0:el.name)||"Agent Details",width:1e3,visible:et,footer:null,onOk:e_,onCancel:ek,children:el&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Agent Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Name:"}),(0,a.jsx)(r.xv,{children:el.name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Version:"}),(0,a.jsxs)(r.Ct,{color:"blue",children:["v",el.version]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Protocol Version:"}),(0,a.jsx)(r.xv,{children:el.protocolVersion})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"truncate",children:el.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(el.url),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:el.description})]})]}),el.capabilities&&Object.keys(el.capabilities).length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(el.capabilities).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return(0,a.jsx)(r.Ct,{color:"green",children:s},s)})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Input/Output Modes"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(v=el.defaultInputModes)||void 0===v?void 0:v.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(b=el.defaultOutputModes)||void 0===b?void 0:b.map(e=>(0,a.jsx)(r.Ct,{color:"purple",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]})]})]}),el.skills&&el.skills.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Skills"}),(0,a.jsx)("div",{className:"space-y-4",children:el.skills.map(e=>(0,a.jsxs)("div",{className:"border border-gray-200 rounded p-4",children:[(0,a.jsxs)("div",{className:"flex justify-between items-start mb-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium text-base",children:e.name}),(0,a.jsxs)(r.xv,{className:"text-xs text-gray-500",children:["ID: ",e.id]})]}),e.tags&&e.tags.length>0&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:e.tags.map(e=>(0,a.jsx)(r.Ct,{color:"purple",size:"xs",children:e},e))})]}),(0,a.jsx)(r.xv,{className:"text-sm mb-2",children:e.description}),e.examples&&e.examples.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-xs font-medium text-gray-700",children:"Examples:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.examples.map((e,s)=>(0,a.jsx)(r.Ct,{color:"gray",size:"xs",children:e},s))})]})]},e.id))})]}),el.supportsAuthenticatedExtendedCard&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Additional Features"}),(0,a.jsx)(r.Ct,{color:"green",children:"Supports Authenticated Extended Card"})]})]})}),(0,a.jsx)(n.Z,{title:(null==eo?void 0:eo.server_name)||"MCP Server Details",width:1e3,visible:em,footer:null,onOk:e_,onCancel:ek,children:eo&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Server Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server Name:"}),(0,a.jsx)(r.xv,{children:eo.server_name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server ID:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"text-xs truncate",children:eo.server_id}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.server_id),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]}),eo.alias&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Alias:"}),(0,a.jsx)(r.xv,{children:eo.alias})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Transport:"}),(0,a.jsx)(r.Ct,{color:"blue",children:eo.transport})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Auth Type:"}),(0,a.jsx)(r.Ct,{color:"none"===eo.auth_type?"gray":"green",children:eo.auth_type})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Status:"}),(0,a.jsx)(r.Ct,{color:"active"===eo.status||"healthy"===eo.status?"green":"inactive"===eo.status||"unhealthy"===eo.status?"red":"gray",children:eo.status||"unknown"})]})]}),eo.description&&(0,a.jsxs)("div",{className:"mt-2",children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:eo.description})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Connection Details"}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2 mt-1",children:[(0,a.jsx)(r.xv,{className:"text-sm break-all bg-gray-100 p-2 rounded flex-1",children:eo.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0"})]})]}),eo.command&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Command:"}),(0,a.jsx)(r.xv,{className:"text-sm bg-gray-100 p-2 rounded mt-1 font-mono",children:eo.command})]})]})]}),eo.allowed_tools&&eo.allowed_tools.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Allowed Tools"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.allowed_tools.map((e,s)=>(0,a.jsx)(r.Ct,{color:"purple",children:e},s))})]}),eo.teams&&eo.teams.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Teams"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.teams.map((e,s)=>(0,a.jsx)(r.Ct,{color:"blue",children:e},s))})]}),eo.mcp_access_groups&&eo.mcp_access_groups.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Access Groups"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.mcp_access_groups.map((e,s)=>(0,a.jsx)(r.Ct,{color:"green",children:e},s))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Metadata"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created By:"}),(0,a.jsx)(r.xv,{children:eo.created_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated By:"}),(0,a.jsx)(r.xv,{children:eo.updated_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.created_at).toLocaleString()})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.updated_at).toLocaleString()})]}),eo.last_health_check&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Last Health Check:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.last_health_check).toLocaleString()})]})]}),eo.health_check_error&&(0,a.jsxs)("div",{className:"mt-2 p-2 bg-red-50 rounded",children:[(0,a.jsx)(r.xv,{className:"font-medium text-red-700",children:"Health Check Error:"}),(0,a.jsx)(r.xv,{className:"text-sm text-red-600 mt-1",children:eo.health_check_error})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'from fastmcp import Client\nimport asyncio\n\n# Standard MCP configuration\nconfig = {\n "mcpServers": {\n "'.concat(eo.server_name,'": {\n "url": "http://localhost:4000/').concat(eo.server_name,'/mcp",\n "headers": {\n "x-litellm-api-key": "Bearer sk-1234"\n }\n }\n }\n}\n\n# Create a client that connects to the server\nclient = Client(config)\n\nasync def main():\n async with client:\n # List available tools\n tools = await client.list_tools()\n print(f"Available tools: {[tool.name for tool in tools]}")\n\n # Call a tool\n response = await client.call_tool(\n name="tool_name", \n arguments={"arg": "value"}\n )\n print(f"Response: {response}")\n\nif __name__ == "__main__":\n asyncio.run(main())')})]})]})}),(0,a.jsx)(A,{visible:q,onClose:()=>G(!1),accessToken:N||"",modelHubData:z||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.modelHubCall)(N);L(e.data)}catch(e){console.error("Error refreshing model data:",e)}})()}}),(0,a.jsx)(Z,{visible:Q,onClose:()=>X(!1),accessToken:N||"",agentHubData:J||[],onSuccess:()=>{N&&(async()=>{try{let e=(await (0,_.getAgentsList)(N)).agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.is_public}));$(e)}catch(e){console.error("Error refreshing agent data:",e)}})()}}),(0,a.jsx)(S,{visible:eu,onClose:()=>ep(!1),accessToken:N||"",mcpHubData:en||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.fetchMCPServers)(N);ei(e)}catch(e){console.error("Error refreshing MCP server data:",e)}})()}})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2377-8fdad210b7695043.js b/litellm/proxy/_experimental/out/_next/static/chunks/2377-674bd40044d10e16.js similarity index 99% rename from litellm/proxy/_experimental/out/_next/static/chunks/2377-8fdad210b7695043.js rename to litellm/proxy/_experimental/out/_next/static/chunks/2377-674bd40044d10e16.js index acecc608c06..2efa4c5269d 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2377-8fdad210b7695043.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2377-674bd40044d10e16.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2377],{41649:function(e,r,t){t.d(r,{Z:function(){return b}});var o=t(5853),a=t(2265),n=t(47187),d=t(7084),l=t(26898),i=t(13241),s=t(1153);let c={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},m={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},u=(0,s.fn)("Badge"),b=a.forwardRef((e,r)=>{let{color:t,icon:b,size:g=d.u8.SM,tooltip:p,className:h,children:x}=e,f=(0,o._T)(e,["color","icon","size","tooltip","className","children"]),k=b||null,{tooltipProps:w,getReferenceProps:v}=(0,n.l)();return a.createElement("span",Object.assign({ref:(0,s.lq)([r,w.refs.setReference]),className:(0,i.q)(u("root"),"w-max shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-small ring-1 ring-inset",t?(0,i.q)((0,s.bM)(t,l.K.background).bgColor,(0,s.bM)(t,l.K.iconText).textColor,(0,s.bM)(t,l.K.iconRing).ringColor,"bg-opacity-10 ring-opacity-20","dark:bg-opacity-5 dark:ring-opacity-60"):(0,i.q)("bg-tremor-brand-faint text-tremor-brand-emphasis ring-tremor-brand/20","dark:bg-dark-tremor-brand-muted/50 dark:text-dark-tremor-brand dark:ring-dark-tremor-subtle/20"),c[g].paddingX,c[g].paddingY,c[g].fontSize,h)},v,f),a.createElement(n.Z,Object.assign({text:p},w)),k?a.createElement(k,{className:(0,i.q)(u("icon"),"shrink-0 -ml-1 mr-1.5",m[g].height,m[g].width)}):null,a.createElement("span",{className:(0,i.q)(u("text"),"whitespace-nowrap")},x))});b.displayName="Badge"},47323:function(e,r,t){t.d(r,{Z:function(){return p}});var o=t(5853),a=t(2265),n=t(47187),d=t(7084),l=t(13241),i=t(1153),s=t(26898);let c={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},m={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},u={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},b=(e,r)=>{switch(e){case"simple":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:r?(0,i.bM)(r,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:r?(0,l.q)((0,i.bM)(r,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},g=(0,i.fn)("Icon"),p=a.forwardRef((e,r)=>{let{icon:t,variant:s="simple",tooltip:p,size:h=d.u8.SM,color:x,className:f}=e,k=(0,o._T)(e,["icon","variant","tooltip","size","color","className"]),w=b(s,x),{tooltipProps:v,getReferenceProps:C}=(0,n.l)();return a.createElement("span",Object.assign({ref:(0,i.lq)([r,v.refs.setReference]),className:(0,l.q)(g("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,u[s].rounded,u[s].border,u[s].shadow,u[s].ring,c[h].paddingX,c[h].paddingY,f)},C,k),a.createElement(n.Z,Object.assign({text:p},v)),a.createElement(t,{className:(0,l.q)(g("icon"),"shrink-0",m[h].height,m[h].width)}))});p.displayName="Icon"},78489:function(e,r,t){t.d(r,{Z:function(){return E}});var o=t(5853),a=t(47187),n=t(2265);let d=["preEnter","entering","entered","preExit","exiting","exited","unmounted"],l=e=>({_s:e,status:d[e],isEnter:e<3,isMounted:6!==e,isResolved:2===e||e>4}),i=e=>e?6:5,s=(e,r)=>{switch(e){case 1:case 0:return 2;case 4:case 3:return i(r)}},c=e=>"object"==typeof e?[e.enter,e.exit]:[e,e],m=(e,r)=>setTimeout(()=>{isNaN(document.body.offsetTop)||e(r+1)},0),u=(e,r,t,o,a)=>{clearTimeout(o.current);let n=l(e);r(n),t.current=n,a&&a({current:n})},b=({enter:e=!0,exit:r=!0,preEnter:t,preExit:o,timeout:a,initialEntered:d,mountOnEnter:b,unmountOnExit:g,onStateChange:p}={})=>{let[h,x]=(0,n.useState)(()=>l(d?2:i(b))),f=(0,n.useRef)(h),k=(0,n.useRef)(),[w,v]=c(a),C=(0,n.useCallback)(()=>{let e=s(f.current._s,g);e&&u(e,x,f,k,p)},[p,g]);return[h,(0,n.useCallback)(a=>{let n=e=>{switch(u(e,x,f,k,p),e){case 1:w>=0&&(k.current=setTimeout(C,w));break;case 4:v>=0&&(k.current=setTimeout(C,v));break;case 0:case 3:k.current=m(n,e)}},d=f.current.isEnter;"boolean"!=typeof a&&(a=!d),a?d||n(e?t?0:1:2):d&&n(r?o?3:4:i(g))},[C,p,e,r,t,o,w,v,g]),C]};var g=t(7084),p=t(13241),h=t(1153);let x=e=>{var r=(0,o._T)(e,[]);return n.createElement("svg",Object.assign({},r,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{fill:"none",d:"M0 0h24v24H0z"}),n.createElement("path",{d:"M18.364 5.636L16.95 7.05A7 7 0 1 0 19 12h2a9 9 0 1 1-2.636-6.364z"}))};var f=t(26898);let k={xs:{height:"h-4",width:"w-4"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-6",width:"w-6"},xl:{height:"h-6",width:"w-6"}},w=e=>"light"!==e?{xs:{paddingX:"px-2.5",paddingY:"py-1.5",fontSize:"text-xs"},sm:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-sm"},md:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-md"},lg:{paddingX:"px-4",paddingY:"py-2.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-3",fontSize:"text-xl"}}:{xs:{paddingX:"",paddingY:"",fontSize:"text-xs"},sm:{paddingX:"",paddingY:"",fontSize:"text-sm"},md:{paddingX:"",paddingY:"",fontSize:"text-md"},lg:{paddingX:"",paddingY:"",fontSize:"text-lg"},xl:{paddingX:"",paddingY:"",fontSize:"text-xl"}},v=(e,r)=>{switch(e){case"primary":return{textColor:r?(0,h.bM)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",hoverTextColor:r?(0,h.bM)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,h.bM)(r,f.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",hoverBgColor:r?(0,h.bM)(r,f.K.darkBackground).hoverBgColor:"hover:bg-tremor-brand-emphasis dark:hover:bg-dark-tremor-brand-emphasis",borderColor:r?(0,h.bM)(r,f.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",hoverBorderColor:r?(0,h.bM)(r,f.K.darkBorder).hoverBorderColor:"hover:border-tremor-brand-emphasis dark:hover:border-dark-tremor-brand-emphasis"};case"secondary":return{textColor:r?(0,h.bM)(r,f.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:r?(0,h.bM)(r,f.K.text).textColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,h.bM)("transparent").bgColor,hoverBgColor:r?(0,p.q)((0,h.bM)(r,f.K.background).hoverBgColor,"hover:bg-opacity-20 dark:hover:bg-opacity-20"):"hover:bg-tremor-brand-faint dark:hover:bg-dark-tremor-brand-faint",borderColor:r?(0,h.bM)(r,f.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand"};case"light":return{textColor:r?(0,h.bM)(r,f.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:r?(0,h.bM)(r,f.K.darkText).hoverTextColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,h.bM)("transparent").bgColor,borderColor:"",hoverBorderColor:""}}},C=(0,h.fn)("Button"),y=e=>{let{loading:r,iconSize:t,iconPosition:o,Icon:a,needMargin:d,transitionStatus:l}=e,i=d?o===g.zS.Left?(0,p.q)("-ml-1","mr-1.5"):(0,p.q)("-mr-1","ml-1.5"):"",s=(0,p.q)("w-0 h-0"),c={default:s,entering:s,entered:t,exiting:t,exited:s};return r?n.createElement(x,{className:(0,p.q)(C("icon"),"animate-spin shrink-0",i,c.default,c[l]),style:{transition:"width 150ms"}}):n.createElement(a,{className:(0,p.q)(C("icon"),"shrink-0",t,i)})},E=n.forwardRef((e,r)=>{let{icon:t,iconPosition:d=g.zS.Left,size:l=g.u8.SM,color:i,variant:s="primary",disabled:c,loading:m=!1,loadingText:u,children:x,tooltip:f,className:E}=e,N=(0,o._T)(e,["icon","iconPosition","size","color","variant","disabled","loading","loadingText","children","tooltip","className"]),M=m||c,q=void 0!==t||m,S=m&&u,z=!(!x&&!S),T=(0,p.q)(k[l].height,k[l].width),j="light"!==s?(0,p.q)("rounded-tremor-default border","shadow-tremor-input","dark:shadow-dark-tremor-input"):"",K=v(s,i),R=w(s)[l],{tooltipProps:B,getReferenceProps:Y}=(0,a.l)(300),[X,Z]=b({timeout:50});return(0,n.useEffect)(()=>{Z(m)},[m]),n.createElement("button",Object.assign({ref:(0,h.lq)([r,B.refs.setReference]),className:(0,p.q)(C("root"),"shrink-0 inline-flex justify-center items-center group font-medium outline-none",j,R.paddingX,R.paddingY,R.fontSize,K.textColor,K.bgColor,K.borderColor,K.hoverBorderColor,M?"opacity-50 cursor-not-allowed":(0,p.q)(v(s,i).hoverTextColor,v(s,i).hoverBgColor,v(s,i).hoverBorderColor),E),disabled:M},Y,N),n.createElement(a.Z,Object.assign({text:f},B)),q&&d!==g.zS.Right?n.createElement(y,{loading:m,iconSize:T,iconPosition:d,Icon:t,transitionStatus:X.status,needMargin:z}):null,S||x?n.createElement("span",{className:(0,p.q)(C("text"),"text-tremor-default whitespace-nowrap")},S?u:x):null,q&&d===g.zS.Right?n.createElement(y,{loading:m,iconSize:T,iconPosition:d,Icon:t,transitionStatus:X.status,needMargin:z}):null)});E.displayName="Button"},92414:function(e,r,t){t.d(r,{Z:function(){return x}});var o=t(5853),a=t(2265);t(42698),t(64016),t(8710);var n=t(33232),d=t(44140),l=t(58747);let i=e=>{var r=(0,o._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},r),a.createElement("path",{d:"M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"}))};var s=t(4537);let c=e=>{var r=(0,o._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:"100%",height:"100%",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},r),a.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),a.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))};var m=t(13241),u=t(1153),b=t(96398),g=t(79228),p=t(85238);let h=(0,u.fn)("MultiSelect"),x=a.forwardRef((e,r)=>{let{defaultValue:t=[],value:u,onValueChange:x,placeholder:f="Select...",placeholderSearch:k="Search",disabled:w=!1,icon:v,children:C,className:y,required:E,name:N,error:M=!1,errorMessage:q,id:S}=e,z=(0,o._T)(e,["defaultValue","value","onValueChange","placeholder","placeholderSearch","disabled","icon","children","className","required","name","error","errorMessage","id"]),T=(0,a.useRef)(null),[j,K]=(0,d.Z)(t,u),{reactElementChildren:R,optionsAvailable:B}=(0,a.useMemo)(()=>{let e=a.Children.toArray(C).filter(a.isValidElement);return{reactElementChildren:e,optionsAvailable:(0,b.n0)("",e)}},[C]),[Y,X]=(0,a.useState)(""),Z=(null!=j?j:[]).length>0,I=(0,a.useMemo)(()=>Y?(0,b.n0)(Y,R):B,[Y,R,B]),O=()=>{X("")};return a.createElement("div",{className:(0,m.q)("w-full min-w-[10rem] text-tremor-default",y)},a.createElement("div",{className:"relative"},a.createElement("select",{title:"multi-select-hidden",required:E,className:(0,m.q)("h-full w-full absolute left-0 top-0 -z-10 opacity-0"),value:j,onChange:e=>{e.preventDefault()},name:N,disabled:w,multiple:!0,id:S,onFocus:()=>{let e=T.current;e&&e.focus()}},a.createElement("option",{className:"hidden",value:"",disabled:!0,hidden:!0},f),I.map(e=>{let r=e.props.value,t=e.props.children;return a.createElement("option",{className:"hidden",key:r,value:r},t)})),a.createElement(g.Ri,Object.assign({as:"div",ref:r,defaultValue:j,value:j,onChange:e=>{null==x||x(e),K(e)},disabled:w,id:S,multiple:!0},z),e=>{let{value:r}=e;return a.createElement(a.Fragment,null,a.createElement(g.Y4,{className:(0,m.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-1.5","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",v?"pl-11 -ml-0.5":"pl-3",(0,b.um)(r.length>0,w,M)),ref:T},v&&a.createElement("span",{className:(0,m.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},a.createElement(v,{className:(0,m.q)(h("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),a.createElement("div",{className:"h-6 flex items-center"},r.length>0?a.createElement("div",{className:"flex flex-nowrap overflow-x-scroll [&::-webkit-scrollbar]:hidden [scrollbar-width:none] gap-x-1 mr-5 -ml-1.5 relative"},B.filter(e=>r.includes(e.props.value)).map((e,t)=>{var o;return a.createElement("div",{key:t,className:(0,m.q)("max-w-[100px] lg:max-w-[200px] flex justify-center items-center pl-2 pr-1.5 py-1 font-medium","rounded-tremor-small","bg-tremor-background-muted dark:bg-dark-tremor-background-muted","bg-tremor-background-subtle dark:bg-dark-tremor-background-subtle","text-tremor-content-default dark:text-dark-tremor-content-default","text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis")},a.createElement("div",{className:"text-xs truncate "},null!==(o=e.props.children)&&void 0!==o?o:e.props.value),a.createElement("div",{onClick:t=>{t.preventDefault();let o=r.filter(r=>r!==e.props.value);null==x||x(o),K(o)}},a.createElement(c,{className:(0,m.q)(h("clearIconItem"),"cursor-pointer rounded-tremor-full w-3.5 h-3.5 ml-2","text-tremor-content-subtle hover:text-tremor-content","dark:text-dark-tremor-content-subtle dark:hover:text-tremor-content")})))})):a.createElement("span",null,f)),a.createElement("span",{className:(0,m.q)("absolute inset-y-0 right-0 flex items-center mr-2.5")},a.createElement(l.Z,{className:(0,m.q)(h("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),Z&&!w?a.createElement("button",{type:"button",className:(0,m.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),K([]),null==x||x([])}},a.createElement(s.Z,{className:(0,m.q)(h("clearIconAllItems"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,a.createElement(p.u,{enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},a.createElement(g.O_,{anchor:"bottom start",className:(0,m.q)("z-10 divide-y w-[var(--button-width)] overflow-y-auto outline-none rounded-tremor-default max-h-[228px] border [--anchor-gap:4px]","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},a.createElement("div",{className:(0,m.q)("flex items-center w-full px-2.5","bg-tremor-background-muted","dark:bg-dark-tremor-background-muted")},a.createElement("span",null,a.createElement(i,{className:(0,m.q)("flex-none w-4 h-4 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),a.createElement("input",{name:"search",type:"input",autoComplete:"off",placeholder:k,className:(0,m.q)("w-full focus:outline-none focus:ring-none bg-transparent text-tremor-default py-2","text-tremor-content-emphasis","dark:text-dark-tremor-content-subtle"),onKeyDown:e=>{"Space"===e.code&&""!==e.target.value&&e.stopPropagation()},onChange:e=>X(e.target.value),value:Y})),a.createElement(n.Z.Provider,Object.assign({},{onBlur:{handleResetSearch:O}},{value:{selectedValue:r}}),I))))})),M&&q?a.createElement("p",{className:(0,m.q)("errorMessage","text-sm text-rose-500 mt-1")},q):null)});x.displayName="MultiSelect"},46030:function(e,r,t){t.d(r,{Z:function(){return c}});var o=t(5853);t(42698),t(64016),t(8710);var a=t(33232),n=t(2265),d=t(13241),l=t(1153),i=t(79228);let s=(0,l.fn)("MultiSelectItem"),c=n.forwardRef((e,r)=>{let{value:t,className:c,children:m}=e,u=(0,o._T)(e,["value","className","children"]),{selectedValue:b}=(0,n.useContext)(a.Z),g=(0,l.NZ)(t,b);return n.createElement(i.wt,Object.assign({className:(0,d.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","data-[focus]:bg-tremor-background-muted data-[focus]:text-tremor-content-strong data-[select]ed:text-tremor-content-strong text-tremor-content-emphasis","dark:data-[focus]:bg-dark-tremor-background-muted dark:data-[focus]:text-dark-tremor-content-strong dark:data-[select]ed:text-dark-tremor-content-strong dark:data-[select]ed:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",c),ref:r,key:t,value:t},u),n.createElement("input",{type:"checkbox",className:(0,d.q)(s("checkbox"),"flex-none focus:ring-none focus:outline-none cursor-pointer mr-2.5","accent-tremor-brand","dark:accent-dark-tremor-brand"),checked:g,readOnly:!0}),n.createElement("span",{className:"whitespace-nowrap truncate"},null!=m?m:t))});c.displayName="MultiSelectItem"},12514:function(e,r,t){t.d(r,{Z:function(){return m}});var o=t(5853),a=t(2265),n=t(7084),d=t(26898),l=t(13241),i=t(1153);let s=(0,i.fn)("Card"),c=e=>{if(!e)return"";switch(e){case n.zS.Left:return"border-l-4";case n.m.Top:return"border-t-4";case n.zS.Right:return"border-r-4";case n.m.Bottom:return"border-b-4";default:return""}},m=a.forwardRef((e,r)=>{let{decoration:t="",decorationColor:n,children:m,className:u}=e,b=(0,o._T)(e,["decoration","decorationColor","children","className"]);return a.createElement("div",Object.assign({ref:r,className:(0,l.q)(s("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",n?(0,i.bM)(n,d.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",c(t),u)},b),m)});m.displayName="Card"},84264:function(e,r,t){t.d(r,{Z:function(){return l}});var o=t(26898),a=t(13241),n=t(1153),d=t(2265);let l=d.forwardRef((e,r)=>{let{color:t,className:l,children:i}=e;return d.createElement("p",{ref:r,className:(0,a.q)("text-tremor-default",t?(0,n.bM)(t,o.K.text).textColor:(0,a.q)("text-tremor-content","dark:text-dark-tremor-content"),l)},i)});l.displayName="Text"},44643:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=a},51853:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"}))});r.Z=a},71157:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=a},14474:function(e,r,t){t.d(r,{o:function(){return a}});class o extends Error{}function a(e,r){let t;if("string"!=typeof e)throw new o("Invalid token specified: must be a string");r||(r={});let a=!0===r.header?0:1,n=e.split(".")[a];if("string"!=typeof n)throw new o(`Invalid token specified: missing part #${a+1}`);try{t=function(e){let r=e.replace(/-/g,"+").replace(/_/g,"/");switch(r.length%4){case 0:break;case 2:r+="==";break;case 3:r+="=";break;default:throw Error("base64 string is not of the correct length")}try{var t;return t=r,decodeURIComponent(atob(t).replace(/(.)/g,(e,r)=>{let t=r.charCodeAt(0).toString(16).toUpperCase();return t.length<2&&(t="0"+t),"%"+t}))}catch(e){return atob(r)}}(n)}catch(e){throw new o(`Invalid token specified: invalid base64 for part #${a+1} (${e.message})`)}try{return JSON.parse(t)}catch(e){throw new o(`Invalid token specified: invalid json for part #${a+1} (${e.message})`)}}o.prototype.name="InvalidTokenError"}}]); \ No newline at end of file +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2377],{41649:function(e,r,t){t.d(r,{Z:function(){return b}});var o=t(5853),a=t(2265),n=t(47187),d=t(7084),l=t(26898),i=t(13241),s=t(1153);let c={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},m={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},u=(0,s.fn)("Badge"),b=a.forwardRef((e,r)=>{let{color:t,icon:b,size:g=d.u8.SM,tooltip:p,className:h,children:x}=e,f=(0,o._T)(e,["color","icon","size","tooltip","className","children"]),k=b||null,{tooltipProps:w,getReferenceProps:v}=(0,n.l)();return a.createElement("span",Object.assign({ref:(0,s.lq)([r,w.refs.setReference]),className:(0,i.q)(u("root"),"w-max shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-small ring-1 ring-inset",t?(0,i.q)((0,s.bM)(t,l.K.background).bgColor,(0,s.bM)(t,l.K.iconText).textColor,(0,s.bM)(t,l.K.iconRing).ringColor,"bg-opacity-10 ring-opacity-20","dark:bg-opacity-5 dark:ring-opacity-60"):(0,i.q)("bg-tremor-brand-faint text-tremor-brand-emphasis ring-tremor-brand/20","dark:bg-dark-tremor-brand-muted/50 dark:text-dark-tremor-brand dark:ring-dark-tremor-subtle/20"),c[g].paddingX,c[g].paddingY,c[g].fontSize,h)},v,f),a.createElement(n.Z,Object.assign({text:p},w)),k?a.createElement(k,{className:(0,i.q)(u("icon"),"shrink-0 -ml-1 mr-1.5",m[g].height,m[g].width)}):null,a.createElement("span",{className:(0,i.q)(u("text"),"whitespace-nowrap")},x))});b.displayName="Badge"},47323:function(e,r,t){t.d(r,{Z:function(){return p}});var o=t(5853),a=t(2265),n=t(47187),d=t(7084),l=t(13241),i=t(1153),s=t(26898);let c={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},m={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},u={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},b=(e,r)=>{switch(e){case"simple":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:r?(0,i.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,l.q)((0,i.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:r?(0,i.bM)(r,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:r?(0,l.q)((0,i.bM)(r,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},g=(0,i.fn)("Icon"),p=a.forwardRef((e,r)=>{let{icon:t,variant:s="simple",tooltip:p,size:h=d.u8.SM,color:x,className:f}=e,k=(0,o._T)(e,["icon","variant","tooltip","size","color","className"]),w=b(s,x),{tooltipProps:v,getReferenceProps:C}=(0,n.l)();return a.createElement("span",Object.assign({ref:(0,i.lq)([r,v.refs.setReference]),className:(0,l.q)(g("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,u[s].rounded,u[s].border,u[s].shadow,u[s].ring,c[h].paddingX,c[h].paddingY,f)},C,k),a.createElement(n.Z,Object.assign({text:p},v)),a.createElement(t,{className:(0,l.q)(g("icon"),"shrink-0",m[h].height,m[h].width)}))});p.displayName="Icon"},78489:function(e,r,t){t.d(r,{Z:function(){return E}});var o=t(5853),a=t(47187),n=t(2265);let d=["preEnter","entering","entered","preExit","exiting","exited","unmounted"],l=e=>({_s:e,status:d[e],isEnter:e<3,isMounted:6!==e,isResolved:2===e||e>4}),i=e=>e?6:5,s=(e,r)=>{switch(e){case 1:case 0:return 2;case 4:case 3:return i(r)}},c=e=>"object"==typeof e?[e.enter,e.exit]:[e,e],m=(e,r)=>setTimeout(()=>{isNaN(document.body.offsetTop)||e(r+1)},0),u=(e,r,t,o,a)=>{clearTimeout(o.current);let n=l(e);r(n),t.current=n,a&&a({current:n})},b=({enter:e=!0,exit:r=!0,preEnter:t,preExit:o,timeout:a,initialEntered:d,mountOnEnter:b,unmountOnExit:g,onStateChange:p}={})=>{let[h,x]=(0,n.useState)(()=>l(d?2:i(b))),f=(0,n.useRef)(h),k=(0,n.useRef)(),[w,v]=c(a),C=(0,n.useCallback)(()=>{let e=s(f.current._s,g);e&&u(e,x,f,k,p)},[p,g]);return[h,(0,n.useCallback)(a=>{let n=e=>{switch(u(e,x,f,k,p),e){case 1:w>=0&&(k.current=setTimeout(C,w));break;case 4:v>=0&&(k.current=setTimeout(C,v));break;case 0:case 3:k.current=m(n,e)}},d=f.current.isEnter;"boolean"!=typeof a&&(a=!d),a?d||n(e?t?0:1:2):d&&n(r?o?3:4:i(g))},[C,p,e,r,t,o,w,v,g]),C]};var g=t(7084),p=t(13241),h=t(1153);let x=e=>{var r=(0,o._T)(e,[]);return n.createElement("svg",Object.assign({},r,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{fill:"none",d:"M0 0h24v24H0z"}),n.createElement("path",{d:"M18.364 5.636L16.95 7.05A7 7 0 1 0 19 12h2a9 9 0 1 1-2.636-6.364z"}))};var f=t(26898);let k={xs:{height:"h-4",width:"w-4"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-6",width:"w-6"},xl:{height:"h-6",width:"w-6"}},w=e=>"light"!==e?{xs:{paddingX:"px-2.5",paddingY:"py-1.5",fontSize:"text-xs"},sm:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-sm"},md:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-md"},lg:{paddingX:"px-4",paddingY:"py-2.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-3",fontSize:"text-xl"}}:{xs:{paddingX:"",paddingY:"",fontSize:"text-xs"},sm:{paddingX:"",paddingY:"",fontSize:"text-sm"},md:{paddingX:"",paddingY:"",fontSize:"text-md"},lg:{paddingX:"",paddingY:"",fontSize:"text-lg"},xl:{paddingX:"",paddingY:"",fontSize:"text-xl"}},v=(e,r)=>{switch(e){case"primary":return{textColor:r?(0,h.bM)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",hoverTextColor:r?(0,h.bM)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,h.bM)(r,f.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",hoverBgColor:r?(0,h.bM)(r,f.K.darkBackground).hoverBgColor:"hover:bg-tremor-brand-emphasis dark:hover:bg-dark-tremor-brand-emphasis",borderColor:r?(0,h.bM)(r,f.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",hoverBorderColor:r?(0,h.bM)(r,f.K.darkBorder).hoverBorderColor:"hover:border-tremor-brand-emphasis dark:hover:border-dark-tremor-brand-emphasis"};case"secondary":return{textColor:r?(0,h.bM)(r,f.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:r?(0,h.bM)(r,f.K.text).textColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,h.bM)("transparent").bgColor,hoverBgColor:r?(0,p.q)((0,h.bM)(r,f.K.background).hoverBgColor,"hover:bg-opacity-20 dark:hover:bg-opacity-20"):"hover:bg-tremor-brand-faint dark:hover:bg-dark-tremor-brand-faint",borderColor:r?(0,h.bM)(r,f.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand"};case"light":return{textColor:r?(0,h.bM)(r,f.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:r?(0,h.bM)(r,f.K.darkText).hoverTextColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,h.bM)("transparent").bgColor,borderColor:"",hoverBorderColor:""}}},C=(0,h.fn)("Button"),y=e=>{let{loading:r,iconSize:t,iconPosition:o,Icon:a,needMargin:d,transitionStatus:l}=e,i=d?o===g.zS.Left?(0,p.q)("-ml-1","mr-1.5"):(0,p.q)("-mr-1","ml-1.5"):"",s=(0,p.q)("w-0 h-0"),c={default:s,entering:s,entered:t,exiting:t,exited:s};return r?n.createElement(x,{className:(0,p.q)(C("icon"),"animate-spin shrink-0",i,c.default,c[l]),style:{transition:"width 150ms"}}):n.createElement(a,{className:(0,p.q)(C("icon"),"shrink-0",t,i)})},E=n.forwardRef((e,r)=>{let{icon:t,iconPosition:d=g.zS.Left,size:l=g.u8.SM,color:i,variant:s="primary",disabled:c,loading:m=!1,loadingText:u,children:x,tooltip:f,className:E}=e,N=(0,o._T)(e,["icon","iconPosition","size","color","variant","disabled","loading","loadingText","children","tooltip","className"]),M=m||c,q=void 0!==t||m,S=m&&u,z=!(!x&&!S),T=(0,p.q)(k[l].height,k[l].width),j="light"!==s?(0,p.q)("rounded-tremor-default border","shadow-tremor-input","dark:shadow-dark-tremor-input"):"",K=v(s,i),R=w(s)[l],{tooltipProps:B,getReferenceProps:Y}=(0,a.l)(300),[X,Z]=b({timeout:50});return(0,n.useEffect)(()=>{Z(m)},[m]),n.createElement("button",Object.assign({ref:(0,h.lq)([r,B.refs.setReference]),className:(0,p.q)(C("root"),"shrink-0 inline-flex justify-center items-center group font-medium outline-none",j,R.paddingX,R.paddingY,R.fontSize,K.textColor,K.bgColor,K.borderColor,K.hoverBorderColor,M?"opacity-50 cursor-not-allowed":(0,p.q)(v(s,i).hoverTextColor,v(s,i).hoverBgColor,v(s,i).hoverBorderColor),E),disabled:M},Y,N),n.createElement(a.Z,Object.assign({text:f},B)),q&&d!==g.zS.Right?n.createElement(y,{loading:m,iconSize:T,iconPosition:d,Icon:t,transitionStatus:X.status,needMargin:z}):null,S||x?n.createElement("span",{className:(0,p.q)(C("text"),"text-tremor-default whitespace-nowrap")},S?u:x):null,q&&d===g.zS.Right?n.createElement(y,{loading:m,iconSize:T,iconPosition:d,Icon:t,transitionStatus:X.status,needMargin:z}):null)});E.displayName="Button"},92414:function(e,r,t){t.d(r,{Z:function(){return x}});var o=t(5853),a=t(2265);t(42698),t(64016),t(8710);var n=t(33232),d=t(44140),l=t(58747);let i=e=>{var r=(0,o._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},r),a.createElement("path",{d:"M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"}))};var s=t(4537);let c=e=>{var r=(0,o._T)(e,[]);return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:"100%",height:"100%",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},r),a.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),a.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))};var m=t(13241),u=t(1153),b=t(96398),g=t(51975),p=t(85238);let h=(0,u.fn)("MultiSelect"),x=a.forwardRef((e,r)=>{let{defaultValue:t=[],value:u,onValueChange:x,placeholder:f="Select...",placeholderSearch:k="Search",disabled:w=!1,icon:v,children:C,className:y,required:E,name:N,error:M=!1,errorMessage:q,id:S}=e,z=(0,o._T)(e,["defaultValue","value","onValueChange","placeholder","placeholderSearch","disabled","icon","children","className","required","name","error","errorMessage","id"]),T=(0,a.useRef)(null),[j,K]=(0,d.Z)(t,u),{reactElementChildren:R,optionsAvailable:B}=(0,a.useMemo)(()=>{let e=a.Children.toArray(C).filter(a.isValidElement);return{reactElementChildren:e,optionsAvailable:(0,b.n0)("",e)}},[C]),[Y,X]=(0,a.useState)(""),Z=(null!=j?j:[]).length>0,I=(0,a.useMemo)(()=>Y?(0,b.n0)(Y,R):B,[Y,R,B]),O=()=>{X("")};return a.createElement("div",{className:(0,m.q)("w-full min-w-[10rem] text-tremor-default",y)},a.createElement("div",{className:"relative"},a.createElement("select",{title:"multi-select-hidden",required:E,className:(0,m.q)("h-full w-full absolute left-0 top-0 -z-10 opacity-0"),value:j,onChange:e=>{e.preventDefault()},name:N,disabled:w,multiple:!0,id:S,onFocus:()=>{let e=T.current;e&&e.focus()}},a.createElement("option",{className:"hidden",value:"",disabled:!0,hidden:!0},f),I.map(e=>{let r=e.props.value,t=e.props.children;return a.createElement("option",{className:"hidden",key:r,value:r},t)})),a.createElement(g.Ri,Object.assign({as:"div",ref:r,defaultValue:j,value:j,onChange:e=>{null==x||x(e),K(e)},disabled:w,id:S,multiple:!0},z),e=>{let{value:r}=e;return a.createElement(a.Fragment,null,a.createElement(g.Y4,{className:(0,m.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-1.5","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",v?"pl-11 -ml-0.5":"pl-3",(0,b.um)(r.length>0,w,M)),ref:T},v&&a.createElement("span",{className:(0,m.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},a.createElement(v,{className:(0,m.q)(h("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),a.createElement("div",{className:"h-6 flex items-center"},r.length>0?a.createElement("div",{className:"flex flex-nowrap overflow-x-scroll [&::-webkit-scrollbar]:hidden [scrollbar-width:none] gap-x-1 mr-5 -ml-1.5 relative"},B.filter(e=>r.includes(e.props.value)).map((e,t)=>{var o;return a.createElement("div",{key:t,className:(0,m.q)("max-w-[100px] lg:max-w-[200px] flex justify-center items-center pl-2 pr-1.5 py-1 font-medium","rounded-tremor-small","bg-tremor-background-muted dark:bg-dark-tremor-background-muted","bg-tremor-background-subtle dark:bg-dark-tremor-background-subtle","text-tremor-content-default dark:text-dark-tremor-content-default","text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis")},a.createElement("div",{className:"text-xs truncate "},null!==(o=e.props.children)&&void 0!==o?o:e.props.value),a.createElement("div",{onClick:t=>{t.preventDefault();let o=r.filter(r=>r!==e.props.value);null==x||x(o),K(o)}},a.createElement(c,{className:(0,m.q)(h("clearIconItem"),"cursor-pointer rounded-tremor-full w-3.5 h-3.5 ml-2","text-tremor-content-subtle hover:text-tremor-content","dark:text-dark-tremor-content-subtle dark:hover:text-tremor-content")})))})):a.createElement("span",null,f)),a.createElement("span",{className:(0,m.q)("absolute inset-y-0 right-0 flex items-center mr-2.5")},a.createElement(l.Z,{className:(0,m.q)(h("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),Z&&!w?a.createElement("button",{type:"button",className:(0,m.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),K([]),null==x||x([])}},a.createElement(s.Z,{className:(0,m.q)(h("clearIconAllItems"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,a.createElement(p.u,{enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},a.createElement(g.O_,{anchor:"bottom start",className:(0,m.q)("z-10 divide-y w-[var(--button-width)] overflow-y-auto outline-none rounded-tremor-default max-h-[228px] border [--anchor-gap:4px]","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},a.createElement("div",{className:(0,m.q)("flex items-center w-full px-2.5","bg-tremor-background-muted","dark:bg-dark-tremor-background-muted")},a.createElement("span",null,a.createElement(i,{className:(0,m.q)("flex-none w-4 h-4 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),a.createElement("input",{name:"search",type:"input",autoComplete:"off",placeholder:k,className:(0,m.q)("w-full focus:outline-none focus:ring-none bg-transparent text-tremor-default py-2","text-tremor-content-emphasis","dark:text-dark-tremor-content-subtle"),onKeyDown:e=>{"Space"===e.code&&""!==e.target.value&&e.stopPropagation()},onChange:e=>X(e.target.value),value:Y})),a.createElement(n.Z.Provider,Object.assign({},{onBlur:{handleResetSearch:O}},{value:{selectedValue:r}}),I))))})),M&&q?a.createElement("p",{className:(0,m.q)("errorMessage","text-sm text-rose-500 mt-1")},q):null)});x.displayName="MultiSelect"},46030:function(e,r,t){t.d(r,{Z:function(){return c}});var o=t(5853);t(42698),t(64016),t(8710);var a=t(33232),n=t(2265),d=t(13241),l=t(1153),i=t(51975);let s=(0,l.fn)("MultiSelectItem"),c=n.forwardRef((e,r)=>{let{value:t,className:c,children:m}=e,u=(0,o._T)(e,["value","className","children"]),{selectedValue:b}=(0,n.useContext)(a.Z),g=(0,l.NZ)(t,b);return n.createElement(i.wt,Object.assign({className:(0,d.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","data-[focus]:bg-tremor-background-muted data-[focus]:text-tremor-content-strong data-[select]ed:text-tremor-content-strong text-tremor-content-emphasis","dark:data-[focus]:bg-dark-tremor-background-muted dark:data-[focus]:text-dark-tremor-content-strong dark:data-[select]ed:text-dark-tremor-content-strong dark:data-[select]ed:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",c),ref:r,key:t,value:t},u),n.createElement("input",{type:"checkbox",className:(0,d.q)(s("checkbox"),"flex-none focus:ring-none focus:outline-none cursor-pointer mr-2.5","accent-tremor-brand","dark:accent-dark-tremor-brand"),checked:g,readOnly:!0}),n.createElement("span",{className:"whitespace-nowrap truncate"},null!=m?m:t))});c.displayName="MultiSelectItem"},12514:function(e,r,t){t.d(r,{Z:function(){return m}});var o=t(5853),a=t(2265),n=t(7084),d=t(26898),l=t(13241),i=t(1153);let s=(0,i.fn)("Card"),c=e=>{if(!e)return"";switch(e){case n.zS.Left:return"border-l-4";case n.m.Top:return"border-t-4";case n.zS.Right:return"border-r-4";case n.m.Bottom:return"border-b-4";default:return""}},m=a.forwardRef((e,r)=>{let{decoration:t="",decorationColor:n,children:m,className:u}=e,b=(0,o._T)(e,["decoration","decorationColor","children","className"]);return a.createElement("div",Object.assign({ref:r,className:(0,l.q)(s("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",n?(0,i.bM)(n,d.K.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",c(t),u)},b),m)});m.displayName="Card"},84264:function(e,r,t){t.d(r,{Z:function(){return l}});var o=t(26898),a=t(13241),n=t(1153),d=t(2265);let l=d.forwardRef((e,r)=>{let{color:t,className:l,children:i}=e;return d.createElement("p",{ref:r,className:(0,a.q)("text-tremor-default",t?(0,n.bM)(t,o.K.text).textColor:(0,a.q)("text-tremor-content","dark:text-dark-tremor-content"),l)},i)});l.displayName="Text"},44643:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=a},51853:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"}))});r.Z=a},71157:function(e,r,t){var o=t(2265);let a=o.forwardRef(function(e,r){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=a},14474:function(e,r,t){t.d(r,{o:function(){return a}});class o extends Error{}function a(e,r){let t;if("string"!=typeof e)throw new o("Invalid token specified: must be a string");r||(r={});let a=!0===r.header?0:1,n=e.split(".")[a];if("string"!=typeof n)throw new o(`Invalid token specified: missing part #${a+1}`);try{t=function(e){let r=e.replace(/-/g,"+").replace(/_/g,"/");switch(r.length%4){case 0:break;case 2:r+="==";break;case 3:r+="=";break;default:throw Error("base64 string is not of the correct length")}try{var t;return t=r,decodeURIComponent(atob(t).replace(/(.)/g,(e,r)=>{let t=r.charCodeAt(0).toString(16).toUpperCase();return t.length<2&&(t="0"+t),"%"+t}))}catch(e){return atob(r)}}(n)}catch(e){throw new o(`Invalid token specified: invalid base64 for part #${a+1} (${e.message})`)}try{return JSON.parse(t)}catch(e){throw new o(`Invalid token specified: invalid json for part #${a+1} (${e.message})`)}}o.prototype.name="InvalidTokenError"}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2926-a9eb2d7547cdad95.js b/litellm/proxy/_experimental/out/_next/static/chunks/2926-a9cb83e61fc8ad20.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/chunks/2926-a9eb2d7547cdad95.js rename to litellm/proxy/_experimental/out/_next/static/chunks/2926-a9cb83e61fc8ad20.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3218-4aea06837fa340f4.js b/litellm/proxy/_experimental/out/_next/static/chunks/3218-4aea06837fa340f4.js deleted file mode 100644 index b0d6a6add9d..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/3218-4aea06837fa340f4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3218],{83669:function(e,t,n){n.d(t,{Z:function(){return o}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"},s=n(55015),o=r.forwardRef(function(e,t){return r.createElement(s.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},62670:function(e,t,n){n.d(t,{Z:function(){return o}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm47.7-395.2l-25.4-5.9V348.6c38 5.2 61.5 29 65.5 58.2.5 4 3.9 6.9 7.9 6.9h44.9c4.7 0 8.4-4.1 8-8.8-6.1-62.3-57.4-102.3-125.9-109.2V263c0-4.4-3.6-8-8-8h-28.1c-4.4 0-8 3.6-8 8v33c-70.8 6.9-126.2 46-126.2 119 0 67.6 49.8 100.2 102.1 112.7l24.7 6.3v142.7c-44.2-5.9-69-29.5-74.1-61.3-.6-3.8-4-6.6-7.9-6.6H363c-4.7 0-8.4 4-8 8.7 4.5 55 46.2 105.6 135.2 112.1V761c0 4.4 3.6 8 8 8h28.4c4.4 0 8-3.6 8-8.1l-.2-31.7c78.3-6.9 134.3-48.8 134.3-124-.1-69.4-44.2-100.4-109-116.4zm-68.6-16.2c-5.6-1.6-10.3-3.1-15-5-33.8-12.2-49.5-31.9-49.5-57.3 0-36.3 27.5-57 64.5-61.7v124zM534.3 677V543.3c3.1.9 5.9 1.6 8.8 2.2 47.3 14.4 63.2 34.4 63.2 65.1 0 39.1-29.4 62.6-72 66.4z"}}]},name:"dollar",theme:"outlined"},s=n(55015),o=r.forwardRef(function(e,t){return r.createElement(s.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},29271:function(e,t,n){n.d(t,{Z:function(){return o}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M464 688a48 48 0 1096 0 48 48 0 10-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"}}]},name:"exclamation-circle",theme:"outlined"},s=n(55015),o=r.forwardRef(function(e,t){return r.createElement(s.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},69993:function(e,t,n){n.d(t,{Z:function(){return o}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},s=n(55015),o=r.forwardRef(function(e,t){return r.createElement(s.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},58630:function(e,t,n){n.d(t,{Z:function(){return o}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 01144-53.5L537 318.9a32.05 32.05 0 000 45.3l124.5 124.5a32.05 32.05 0 0045.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z"}}]},name:"tool",theme:"outlined"},s=n(55015),o=r.forwardRef(function(e,t){return r.createElement(s.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},67101:function(e,t,n){n.d(t,{Z:function(){return u}});var a=n(5853),r=n(13241),i=n(1153),s=n(2265),o=n(9496);let c=(0,i.fn)("Grid"),l=(e,t)=>e&&Object.keys(t).includes(String(e))?t[e]:"",u=s.forwardRef((e,t)=>{let{numItems:n=1,numItemsSm:i,numItemsMd:u,numItemsLg:d,children:h,className:p}=e,m=(0,a._T)(e,["numItems","numItemsSm","numItemsMd","numItemsLg","children","className"]),f=l(n,o._m),g=l(i,o.LH),y=l(u,o.l5),b=l(d,o.N4),v=(0,r.q)(f,g,y,b);return s.createElement("div",Object.assign({ref:t,className:(0,r.q)(c("root"),"grid",v,p)},m),h)});u.displayName="Grid"},9496:function(e,t,n){n.d(t,{LH:function(){return r},N4:function(){return s},PT:function(){return o},SP:function(){return c},VS:function(){return l},_m:function(){return a},_w:function(){return u},l5:function(){return i}});let a={0:"grid-cols-none",1:"grid-cols-1",2:"grid-cols-2",3:"grid-cols-3",4:"grid-cols-4",5:"grid-cols-5",6:"grid-cols-6",7:"grid-cols-7",8:"grid-cols-8",9:"grid-cols-9",10:"grid-cols-10",11:"grid-cols-11",12:"grid-cols-12"},r={0:"sm:grid-cols-none",1:"sm:grid-cols-1",2:"sm:grid-cols-2",3:"sm:grid-cols-3",4:"sm:grid-cols-4",5:"sm:grid-cols-5",6:"sm:grid-cols-6",7:"sm:grid-cols-7",8:"sm:grid-cols-8",9:"sm:grid-cols-9",10:"sm:grid-cols-10",11:"sm:grid-cols-11",12:"sm:grid-cols-12"},i={0:"md:grid-cols-none",1:"md:grid-cols-1",2:"md:grid-cols-2",3:"md:grid-cols-3",4:"md:grid-cols-4",5:"md:grid-cols-5",6:"md:grid-cols-6",7:"md:grid-cols-7",8:"md:grid-cols-8",9:"md:grid-cols-9",10:"md:grid-cols-10",11:"md:grid-cols-11",12:"md:grid-cols-12"},s={0:"lg:grid-cols-none",1:"lg:grid-cols-1",2:"lg:grid-cols-2",3:"lg:grid-cols-3",4:"lg:grid-cols-4",5:"lg:grid-cols-5",6:"lg:grid-cols-6",7:"lg:grid-cols-7",8:"lg:grid-cols-8",9:"lg:grid-cols-9",10:"lg:grid-cols-10",11:"lg:grid-cols-11",12:"lg:grid-cols-12"},o={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12",13:"col-span-13"},c={1:"sm:col-span-1",2:"sm:col-span-2",3:"sm:col-span-3",4:"sm:col-span-4",5:"sm:col-span-5",6:"sm:col-span-6",7:"sm:col-span-7",8:"sm:col-span-8",9:"sm:col-span-9",10:"sm:col-span-10",11:"sm:col-span-11",12:"sm:col-span-12",13:"sm:col-span-13"},l={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12",13:"md:col-span-13"},u={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12",13:"lg:col-span-13"}},44851:function(e,t,n){n.d(t,{default:function(){return H}});var a=n(2265),r=n(77565),i=n(36760),s=n.n(i),o=n(1119),c=n(83145),l=n(26365),u=n(41154),d=n(50506),h=n(32559),p=n(6989),m=n(45287),f=n(31686),g=n(11993),y=n(66632),b=n(95814),v=a.forwardRef(function(e,t){var n=e.prefixCls,r=e.forceRender,i=e.className,o=e.style,c=e.children,u=e.isActive,d=e.role,h=e.classNames,p=e.styles,m=a.useState(u||r),f=(0,l.Z)(m,2),y=f[0],b=f[1];return(a.useEffect(function(){(r||u)&&b(!0)},[r,u]),y)?a.createElement("div",{ref:t,className:s()("".concat(n,"-content"),(0,g.Z)((0,g.Z)({},"".concat(n,"-content-active"),u),"".concat(n,"-content-inactive"),!u),i),style:o,role:d},a.createElement("div",{className:s()("".concat(n,"-content-box"),null==h?void 0:h.body),style:null==p?void 0:p.body},c)):null});v.displayName="PanelContent";var x=["showArrow","headerClass","isActive","onItemClick","forceRender","className","classNames","styles","prefixCls","collapsible","accordion","panelKey","extra","header","expandIcon","openMotion","destroyInactivePanel","children"],C=a.forwardRef(function(e,t){var n=e.showArrow,r=e.headerClass,i=e.isActive,c=e.onItemClick,l=e.forceRender,u=e.className,d=e.classNames,h=void 0===d?{}:d,m=e.styles,C=void 0===m?{}:m,w=e.prefixCls,O=e.collapsible,k=e.accordion,M=e.panelKey,E=e.extra,Z=e.header,I=e.expandIcon,P=e.openMotion,S=e.destroyInactivePanel,R=e.children,N=(0,p.Z)(e,x),q="disabled"===O,A=(0,g.Z)((0,g.Z)((0,g.Z)({onClick:function(){null==c||c(M)},onKeyDown:function(e){("Enter"===e.key||e.keyCode===b.Z.ENTER||e.which===b.Z.ENTER)&&(null==c||c(M))},role:k?"tab":"button"},"aria-expanded",i),"aria-disabled",q),"tabIndex",q?-1:0),j="function"==typeof I?I(e):a.createElement("i",{className:"arrow"}),z=j&&a.createElement("div",(0,o.Z)({className:"".concat(w,"-expand-icon")},["header","icon"].includes(O)?A:{}),j),Q=s()("".concat(w,"-item"),(0,g.Z)((0,g.Z)({},"".concat(w,"-item-active"),i),"".concat(w,"-item-disabled"),q),u),L=s()(r,"".concat(w,"-header"),(0,g.Z)({},"".concat(w,"-collapsible-").concat(O),!!O),h.header),D=(0,f.Z)({className:L,style:C.header},["header","icon"].includes(O)?{}:A);return a.createElement("div",(0,o.Z)({},N,{ref:t,className:Q}),a.createElement("div",D,(void 0===n||n)&&z,a.createElement("span",(0,o.Z)({className:"".concat(w,"-header-text")},"header"===O?A:{}),Z),null!=E&&"boolean"!=typeof E&&a.createElement("div",{className:"".concat(w,"-extra")},E)),a.createElement(y.ZP,(0,o.Z)({visible:i,leavedClassName:"".concat(w,"-content-hidden")},P,{forceRender:l,removeOnLeave:S}),function(e,t){var n=e.className,r=e.style;return a.createElement(v,{ref:t,prefixCls:w,className:n,classNames:h,style:r,styles:C,isActive:i,forceRender:l,role:k?"tabpanel":void 0},R)}))}),w=["children","label","key","collapsible","onItemClick","destroyInactivePanel"],O=function(e,t){var n=t.prefixCls,r=t.accordion,i=t.collapsible,s=t.destroyInactivePanel,c=t.onItemClick,l=t.activeKey,u=t.openMotion,d=t.expandIcon;return e.map(function(e,t){var h=e.children,m=e.label,f=e.key,g=e.collapsible,y=e.onItemClick,b=e.destroyInactivePanel,v=(0,p.Z)(e,w),x=String(null!=f?f:t),O=null!=g?g:i,k=!1;return k=r?l[0]===x:l.indexOf(x)>-1,a.createElement(C,(0,o.Z)({},v,{prefixCls:n,key:x,panelKey:x,isActive:k,accordion:r,openMotion:u,expandIcon:d,header:m,collapsible:O,onItemClick:function(e){"disabled"!==O&&(c(e),null==y||y(e))},destroyInactivePanel:null!=b?b:s}),h)})},k=function(e,t,n){if(!e)return null;var r=n.prefixCls,i=n.accordion,s=n.collapsible,o=n.destroyInactivePanel,c=n.onItemClick,l=n.activeKey,u=n.openMotion,d=n.expandIcon,h=e.key||String(t),p=e.props,m=p.header,f=p.headerClass,g=p.destroyInactivePanel,y=p.collapsible,b=p.onItemClick,v=!1;v=i?l[0]===h:l.indexOf(h)>-1;var x=null!=y?y:s,C={key:h,panelKey:h,header:m,headerClass:f,isActive:v,prefixCls:r,destroyInactivePanel:null!=g?g:o,openMotion:u,accordion:i,children:e.props.children,onItemClick:function(e){"disabled"!==x&&(c(e),null==b||b(e))},expandIcon:d,collapsible:x};return"string"==typeof e.type?e:(Object.keys(C).forEach(function(e){void 0===C[e]&&delete C[e]}),a.cloneElement(e,C))},M=n(18242);function E(e){var t=e;if(!Array.isArray(t)){var n=(0,u.Z)(t);t="number"===n||"string"===n?[t]:[]}return t.map(function(e){return String(e)})}var Z=Object.assign(a.forwardRef(function(e,t){var n,r=e.prefixCls,i=void 0===r?"rc-collapse":r,u=e.destroyInactivePanel,p=e.style,f=e.accordion,g=e.className,y=e.children,b=e.collapsible,v=e.openMotion,x=e.expandIcon,C=e.activeKey,w=e.defaultActiveKey,Z=e.onChange,I=e.items,P=s()(i,g),S=(0,d.Z)([],{value:C,onChange:function(e){return null==Z?void 0:Z(e)},defaultValue:w,postState:E}),R=(0,l.Z)(S,2),N=R[0],q=R[1];(0,h.ZP)(!y,"[rc-collapse] `children` will be removed in next major version. Please use `items` instead.");var A=(n={prefixCls:i,accordion:f,openMotion:v,expandIcon:x,collapsible:b,destroyInactivePanel:void 0!==u&&u,onItemClick:function(e){return q(function(){return f?N[0]===e?[]:[e]:N.indexOf(e)>-1?N.filter(function(t){return t!==e}):[].concat((0,c.Z)(N),[e])})},activeKey:N},Array.isArray(I)?O(I,n):(0,m.Z)(y).map(function(e,t){return k(e,t,n)}));return a.createElement("div",(0,o.Z)({ref:t,className:P,style:p,role:f?"tablist":void 0},(0,M.Z)(e,{aria:!0,data:!0})),A)}),{Panel:C});Z.Panel;var I=n(18694),P=n(68710),S=n(19722),R=n(71744),N=n(33759);let q=a.forwardRef((e,t)=>{let{getPrefixCls:n}=a.useContext(R.E_),{prefixCls:r,className:i,showArrow:o=!0}=e,c=n("collapse",r),l=s()({["".concat(c,"-no-arrow")]:!o},i);return a.createElement(Z.Panel,Object.assign({ref:t},e,{prefixCls:c,className:l}))});var A=n(93463),j=n(12918),z=n(63074),Q=n(99320),L=n(71140);let D=e=>{let{componentCls:t,contentBg:n,padding:a,headerBg:r,headerPadding:i,collapseHeaderPaddingSM:s,collapseHeaderPaddingLG:o,collapsePanelBorderRadius:c,lineWidth:l,lineType:u,colorBorder:d,colorText:h,colorTextHeading:p,colorTextDisabled:m,fontSizeLG:f,lineHeight:g,lineHeightLG:y,marginSM:b,paddingSM:v,paddingLG:x,paddingXS:C,motionDurationSlow:w,fontSizeIcon:O,contentPadding:k,fontHeight:M,fontHeightLG:E}=e,Z="".concat((0,A.bf)(l)," ").concat(u," ").concat(d);return{[t]:Object.assign(Object.assign({},(0,j.Wf)(e)),{backgroundColor:r,border:Z,borderRadius:c,"&-rtl":{direction:"rtl"},["& > ".concat(t,"-item")]:{borderBottom:Z,"&:first-child":{["\n &,\n & > ".concat(t,"-header")]:{borderRadius:"".concat((0,A.bf)(c)," ").concat((0,A.bf)(c)," 0 0")}},"&:last-child":{["\n &,\n & > ".concat(t,"-header")]:{borderRadius:"0 0 ".concat((0,A.bf)(c)," ").concat((0,A.bf)(c))}},["> ".concat(t,"-header")]:Object.assign(Object.assign({position:"relative",display:"flex",flexWrap:"nowrap",alignItems:"flex-start",padding:i,color:p,lineHeight:g,cursor:"pointer",transition:"all ".concat(w,", visibility 0s")},(0,j.Qy)(e)),{["> ".concat(t,"-header-text")]:{flex:"auto"},["".concat(t,"-expand-icon")]:{height:M,display:"flex",alignItems:"center",paddingInlineEnd:b},["".concat(t,"-arrow")]:Object.assign(Object.assign({},(0,j.Ro)()),{fontSize:O,transition:"transform ".concat(w),svg:{transition:"transform ".concat(w)}}),["".concat(t,"-header-text")]:{marginInlineEnd:"auto"}}),["".concat(t,"-collapsible-header")]:{cursor:"default",["".concat(t,"-header-text")]:{flex:"none",cursor:"pointer"},["".concat(t,"-expand-icon")]:{cursor:"pointer"}},["".concat(t,"-collapsible-icon")]:{cursor:"unset",["".concat(t,"-expand-icon")]:{cursor:"pointer"}}},["".concat(t,"-content")]:{color:h,backgroundColor:n,borderTop:Z,["& > ".concat(t,"-content-box")]:{padding:k},"&-hidden":{display:"none"}},"&-small":{["> ".concat(t,"-item")]:{["> ".concat(t,"-header")]:{padding:s,paddingInlineStart:C,["> ".concat(t,"-expand-icon")]:{marginInlineStart:e.calc(v).sub(C).equal()}},["> ".concat(t,"-content > ").concat(t,"-content-box")]:{padding:v}}},"&-large":{["> ".concat(t,"-item")]:{fontSize:f,lineHeight:y,["> ".concat(t,"-header")]:{padding:o,paddingInlineStart:a,["> ".concat(t,"-expand-icon")]:{height:E,marginInlineStart:e.calc(x).sub(a).equal()}},["> ".concat(t,"-content > ").concat(t,"-content-box")]:{padding:x}}},["".concat(t,"-item:last-child")]:{borderBottom:0,["> ".concat(t,"-content")]:{borderRadius:"0 0 ".concat((0,A.bf)(c)," ").concat((0,A.bf)(c))}},["& ".concat(t,"-item-disabled > ").concat(t,"-header")]:{"\n &,\n & > .arrow\n ":{color:m,cursor:"not-allowed"}},["&".concat(t,"-icon-position-end")]:{["& > ".concat(t,"-item")]:{["> ".concat(t,"-header")]:{["".concat(t,"-expand-icon")]:{order:1,paddingInlineEnd:0,paddingInlineStart:b}}}}})}},K=e=>{let{componentCls:t}=e,n="> ".concat(t,"-item > ").concat(t,"-header ").concat(t,"-arrow");return{["".concat(t,"-rtl")]:{[n]:{transform:"rotate(180deg)"}}}},V=e=>{let{componentCls:t,headerBg:n,borderlessContentPadding:a,borderlessContentBg:r,colorBorder:i}=e;return{["".concat(t,"-borderless")]:{backgroundColor:n,border:0,["> ".concat(t,"-item")]:{borderBottom:"1px solid ".concat(i)},["\n > ".concat(t,"-item:last-child,\n > ").concat(t,"-item:last-child ").concat(t,"-header\n ")]:{borderRadius:0},["> ".concat(t,"-item:last-child")]:{borderBottom:0},["> ".concat(t,"-item > ").concat(t,"-content")]:{backgroundColor:r,borderTop:0},["> ".concat(t,"-item > ").concat(t,"-content > ").concat(t,"-content-box")]:{padding:a}}}},G=e=>{let{componentCls:t,paddingSM:n}=e;return{["".concat(t,"-ghost")]:{backgroundColor:"transparent",border:0,["> ".concat(t,"-item")]:{borderBottom:0,["> ".concat(t,"-content")]:{backgroundColor:"transparent",border:0,["> ".concat(t,"-content-box")]:{paddingBlock:n}}}}}};var T=(0,Q.I$)("Collapse",e=>{let t=(0,L.IX)(e,{collapseHeaderPaddingSM:"".concat((0,A.bf)(e.paddingXS)," ").concat((0,A.bf)(e.paddingSM)),collapseHeaderPaddingLG:"".concat((0,A.bf)(e.padding)," ").concat((0,A.bf)(e.paddingLG)),collapsePanelBorderRadius:e.borderRadiusLG});return[D(t),V(t),G(t),K(t),(0,z.Z)(t)]},e=>({headerPadding:"".concat(e.paddingSM,"px ").concat(e.padding,"px"),headerBg:e.colorFillAlter,contentPadding:"".concat(e.padding,"px 16px"),contentBg:e.colorBgContainer,borderlessContentPadding:"".concat(e.paddingXXS,"px 16px ").concat(e.padding,"px"),borderlessContentBg:"transparent"})),H=Object.assign(a.forwardRef((e,t)=>{let{getPrefixCls:n,direction:i,expandIcon:o,className:c,style:l}=(0,R.dj)("collapse"),{prefixCls:u,className:d,rootClassName:h,style:p,bordered:f=!0,ghost:g,size:y,expandIconPosition:b="start",children:v,destroyInactivePanel:x,destroyOnHidden:C,expandIcon:w}=e,O=(0,N.Z)(e=>{var t;return null!==(t=null!=y?y:e)&&void 0!==t?t:"middle"}),k=n("collapse",u),M=n(),[E,q,A]=T(k),j=a.useMemo(()=>"left"===b?"start":"right"===b?"end":b,[b]),z=null!=w?w:o,Q=a.useCallback(function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t="function"==typeof z?z(e):a.createElement(r.Z,{rotate:e.isActive?"rtl"===i?-90:90:void 0,"aria-label":e.isActive?"expanded":"collapsed"});return(0,S.Tm)(t,()=>{var e;return{className:s()(null===(e=t.props)||void 0===e?void 0:e.className,"".concat(k,"-arrow"))}})},[z,k,i]),L=s()("".concat(k,"-icon-position-").concat(j),{["".concat(k,"-borderless")]:!f,["".concat(k,"-rtl")]:"rtl"===i,["".concat(k,"-ghost")]:!!g,["".concat(k,"-").concat(O)]:"middle"!==O},c,d,h,q,A),D=a.useMemo(()=>Object.assign(Object.assign({},(0,P.Z)(M)),{motionAppear:!1,leavedClassName:"".concat(k,"-content-hidden")}),[M,k]),K=a.useMemo(()=>v?(0,m.Z)(v).map((e,t)=>{var n,a;let r=e.props;if(null==r?void 0:r.disabled){let i=null!==(n=e.key)&&void 0!==n?n:String(t),s=Object.assign(Object.assign({},(0,I.Z)(e.props,["disabled"])),{key:i,collapsible:null!==(a=r.collapsible)&&void 0!==a?a:"disabled"});return(0,S.Tm)(e,s)}return e}):null,[v]);return E(a.createElement(Z,Object.assign({ref:t,openMotion:D},(0,I.Z)(e,["rootClassName"]),{expandIcon:Q,prefixCls:k,className:L,style:Object.assign(Object.assign({},l),p),destroyInactivePanel:null!=C?C:x}),K))}),{Panel:q})},58760:function(e,t,n){n.d(t,{Z:function(){return E}});var a=n(2265),r=n(36760),i=n.n(r),s=n(45287);function o(e){return["small","middle","large"].includes(e)}function c(e){return!!e&&"number"==typeof e&&!Number.isNaN(e)}var l=n(71744),u=n(77685),d=n(17691),h=n(99320);let p=e=>{let{componentCls:t,borderRadius:n,paddingSM:a,colorBorder:r,paddingXS:i,fontSizeLG:s,fontSizeSM:o,borderRadiusLG:c,borderRadiusSM:l,colorBgContainerDisabled:u,lineWidth:h}=e;return{[t]:[{display:"inline-flex",alignItems:"center",gap:0,paddingInline:a,margin:0,background:u,borderWidth:h,borderStyle:"solid",borderColor:r,borderRadius:n,"&-large":{fontSize:s,borderRadius:c},"&-small":{paddingInline:i,borderRadius:l,fontSize:o},"&-compact-last-item":{borderEndStartRadius:0,borderStartStartRadius:0},"&-compact-first-item":{borderEndEndRadius:0,borderStartEndRadius:0},"&-compact-item:not(:first-child):not(:last-child)":{borderRadius:0},"&-compact-item:not(:last-child)":{borderInlineEndWidth:0}},(0,d.c)(e,{focus:!1})]}};var m=(0,h.I$)(["Space","Addon"],e=>[p(e)]),f=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};let g=a.forwardRef((e,t)=>{let{className:n,children:r,style:s,prefixCls:o}=e,c=f(e,["className","children","style","prefixCls"]),{getPrefixCls:d,direction:h}=a.useContext(l.E_),p=d("space-addon",o),[g,y,b]=m(p),{compactItemClassnames:v,compactSize:x}=(0,u.ri)(p,h),C=i()(p,y,v,b,{["".concat(p,"-").concat(x)]:x},n);return g(a.createElement("div",Object.assign({ref:t,className:C,style:s},c),r))}),y=a.createContext({latestIndex:0}),b=y.Provider;var v=e=>{let{className:t,index:n,children:r,split:i,style:s}=e,{latestIndex:o}=a.useContext(y);return null==r?null:a.createElement(a.Fragment,null,a.createElement("div",{className:t,style:s},r),n{let{componentCls:t,antCls:n}=e;return{[t]:{display:"inline-flex","&-rtl":{direction:"rtl"},"&-vertical":{flexDirection:"column"},"&-align":{flexDirection:"column","&-center":{alignItems:"center"},"&-start":{alignItems:"flex-start"},"&-end":{alignItems:"flex-end"},"&-baseline":{alignItems:"baseline"}},["".concat(t,"-item:empty")]:{display:"none"},["".concat(t,"-item > ").concat(n,"-badge-not-a-wrapper:only-child")]:{display:"block"}}}},w=e=>{let{componentCls:t}=e;return{[t]:{"&-gap-row-small":{rowGap:e.spaceGapSmallSize},"&-gap-row-middle":{rowGap:e.spaceGapMiddleSize},"&-gap-row-large":{rowGap:e.spaceGapLargeSize},"&-gap-col-small":{columnGap:e.spaceGapSmallSize},"&-gap-col-middle":{columnGap:e.spaceGapMiddleSize},"&-gap-col-large":{columnGap:e.spaceGapLargeSize}}}};var O=(0,h.I$)("Space",e=>{let t=(0,x.IX)(e,{spaceGapSmallSize:e.paddingXS,spaceGapMiddleSize:e.padding,spaceGapLargeSize:e.paddingLG});return[C(t),w(t)]},()=>({}),{resetStyle:!1}),k=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};let M=a.forwardRef((e,t)=>{var n;let{getPrefixCls:r,direction:u,size:d,className:h,style:p,classNames:m,styles:f}=(0,l.dj)("space"),{size:g=null!=d?d:"small",align:y,className:x,rootClassName:C,children:w,direction:M="horizontal",prefixCls:E,split:Z,style:I,wrap:P=!1,classNames:S,styles:R}=e,N=k(e,["size","align","className","rootClassName","children","direction","prefixCls","split","style","wrap","classNames","styles"]),[q,A]=Array.isArray(g)?g:[g,g],j=o(A),z=o(q),Q=c(A),L=c(q),D=(0,s.Z)(w,{keepEmpty:!0}),K=void 0===y&&"horizontal"===M?"center":y,V=r("space",E),[G,T,H]=O(V),B=i()(V,h,T,"".concat(V,"-").concat(M),{["".concat(V,"-rtl")]:"rtl"===u,["".concat(V,"-align-").concat(K)]:K,["".concat(V,"-gap-row-").concat(A)]:j,["".concat(V,"-gap-col-").concat(q)]:z},x,C,H),F=i()("".concat(V,"-item"),null!==(n=null==S?void 0:S.item)&&void 0!==n?n:m.item),_=Object.assign(Object.assign({},f.item),null==R?void 0:R.item),W=D.map((e,t)=>{let n=(null==e?void 0:e.key)||"".concat(F,"-").concat(t);return a.createElement(v,{className:F,key:n,index:t,split:Z,style:_},e)}),X=a.useMemo(()=>({latestIndex:D.reduce((e,t,n)=>null!=t?n:e,0)}),[D]);if(0===D.length)return null;let U={};return P&&(U.flexWrap="wrap"),!z&&L&&(U.columnGap=q),!j&&Q&&(U.rowGap=A),G(a.createElement("div",Object.assign({ref:t,className:B,style:Object.assign(Object.assign(Object.assign({},U),p),I)},N),a.createElement(b,{value:X},W)))});M.Compact=u.ZP,M.Addon=g;var E=M},79205:function(e,t,n){n.d(t,{Z:function(){return d}});var a=n(2265);let r=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),i=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),s=e=>{let t=i(e);return t.charAt(0).toUpperCase()+t.slice(1)},o=function(){for(var e=arguments.length,t=Array(e),n=0;n!!e&&""!==e.trim()&&n.indexOf(e)===t).join(" ").trim()},c=e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0};var l={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let u=(0,a.forwardRef)((e,t)=>{let{color:n="currentColor",size:r=24,strokeWidth:i=2,absoluteStrokeWidth:s,className:u="",children:d,iconNode:h,...p}=e;return(0,a.createElement)("svg",{ref:t,...l,width:r,height:r,stroke:n,strokeWidth:s?24*Number(i)/Number(r):i,className:o("lucide",u),...!d&&!c(p)&&{"aria-hidden":"true"},...p},[...h.map(e=>{let[t,n]=e;return(0,a.createElement)(t,n)}),...Array.isArray(d)?d:[d]])}),d=(e,t)=>{let n=(0,a.forwardRef)((n,i)=>{let{className:c,...l}=n;return(0,a.createElement)(u,{ref:i,iconNode:t,className:o("lucide-".concat(r(s(e))),"lucide-".concat(e),c),...l})});return n.displayName=s(e),n}},64935:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("code",[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]])},78867:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]])},96362:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("external-link",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]])},29202:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]])},54001:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("key",[["path",{d:"m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4",key:"g0fldk"}],["path",{d:"m21 2-9.6 9.6",key:"1j0ho8"}],["circle",{cx:"7.5",cy:"15.5",r:"5.5",key:"yqb3hr"}]])},96137:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("server",[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]])},80221:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("terminal",[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]])},11239:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]])},71437:function(e,t,n){var a=n(2265);let r=a.forwardRef(function(e,t){return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),a.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),a.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"}))});t.Z=r},82376:function(e,t,n){var a=n(2265);let r=a.forwardRef(function(e,t){return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),a.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"}))});t.Z=r},74998:function(e,t,n){var a=n(2265);let r=a.forwardRef(function(e,t){return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),a.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});t.Z=r},2894:function(e,t,n){n.d(t,{R:function(){return o},m:function(){return s}});var a=n(18238),r=n(7989),i=n(11255),s=class extends r.F{#e;#t;#n;#a;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#n=e.mutationCache,this.#t=[],this.state=e.state||o(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#n.notify({type:"observerAdded",mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#n.notify({type:"observerRemoved",mutation:this,observer:e})}optionalRemove(){this.#t.length||("pending"===this.state.status?this.scheduleGc():this.#n.remove(this))}continue(){return this.#a?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#r({type:"continue"})},n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#a=(0,i.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error("No mutationFn found")),onFail:(e,t)=>{this.#r({type:"failed",failureCount:e,error:t})},onPause:()=>{this.#r({type:"pause"})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#n.canRun(this)});let a="pending"===this.state.status,r=!this.#a.canStart();try{if(a)t();else{this.#r({type:"pending",variables:e,isPaused:r}),await this.#n.config.onMutate?.(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&this.#r({type:"pending",context:t,variables:e,isPaused:r})}let i=await this.#a.start();return await this.#n.config.onSuccess?.(i,e,this.state.context,this,n),await this.options.onSuccess?.(i,e,this.state.context,n),await this.#n.config.onSettled?.(i,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(i,null,e,this.state.context,n),this.#r({type:"success",data:i}),i}catch(t){try{throw await this.#n.config.onError?.(t,e,this.state.context,this,n),await this.options.onError?.(t,e,this.state.context,n),await this.#n.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(void 0,t,e,this.state.context,n),t}finally{this.#r({type:"error",error:t})}}finally{this.#n.runNext(this)}}#r(e){this.state=(t=>{switch(e.type){case"failed":return{...t,failureCount:e.failureCount,failureReason:e.error};case"pause":return{...t,isPaused:!0};case"continue":return{...t,isPaused:!1};case"pending":return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:"pending",variables:e.variables,submittedAt:Date.now()};case"success":return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:"error"}}})(this.state),a.Vr.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#n.notify({mutation:this,type:"updated",action:e})})}};function o(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21623:function(e,t,n){n.d(t,{S:function(){return f}});var a=n(45345),r=n(21733),i=n(18238),s=n(24112),o=class extends s.l{constructor(e={}){super(),this.config=e,this.#i=new Map}#i;build(e,t,n){let i=t.queryKey,s=t.queryHash??(0,a.Rm)(i,t),o=this.get(s);return o||(o=new r.A({client:e,queryKey:i,queryHash:s,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(i)}),this.add(o)),o}add(e){this.#i.has(e.queryHash)||(this.#i.set(e.queryHash,e),this.notify({type:"added",query:e}))}remove(e){let t=this.#i.get(e.queryHash);t&&(e.destroy(),t===e&&this.#i.delete(e.queryHash),this.notify({type:"removed",query:e}))}clear(){i.Vr.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#i.get(e)}getAll(){return[...this.#i.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,a._x)(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>(0,a._x)(e,t)):t}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},c=n(2894),l=class extends s.l{constructor(e={}){super(),this.config=e,this.#s=new Set,this.#o=new Map,this.#c=0}#s;#o;#c;build(e,t,n){let a=new c.m({client:e,mutationCache:this,mutationId:++this.#c,options:e.defaultMutationOptions(t),state:n});return this.add(a),a}add(e){this.#s.add(e);let t=u(e);if("string"==typeof t){let n=this.#o.get(t);n?n.push(e):this.#o.set(t,[e])}this.notify({type:"added",mutation:e})}remove(e){if(this.#s.delete(e)){let t=u(e);if("string"==typeof t){let n=this.#o.get(t);if(n){if(n.length>1){let t=n.indexOf(e);-1!==t&&n.splice(t,1)}else n[0]===e&&this.#o.delete(t)}}}this.notify({type:"removed",mutation:e})}canRun(e){let t=u(e);if("string"!=typeof t)return!0;{let n=this.#o.get(t),a=n?.find(e=>"pending"===e.state.status);return!a||a===e}}runNext(e){let t=u(e);if("string"!=typeof t)return Promise.resolve();{let n=this.#o.get(t)?.find(t=>t!==e&&t.state.isPaused);return n?.continue()??Promise.resolve()}}clear(){i.Vr.batch(()=>{this.#s.forEach(e=>{this.notify({type:"removed",mutation:e})}),this.#s.clear(),this.#o.clear()})}getAll(){return Array.from(this.#s)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,a.X7)(t,e))}findAll(e={}){return this.getAll().filter(t=>(0,a.X7)(e,t))}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return i.Vr.batch(()=>Promise.all(e.map(e=>e.continue().catch(a.ZT))))}};function u(e){return e.options.scope?.id}var d=n(87045),h=n(57853);function p(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,s=t.state.data?.pages||[],o=t.state.data?.pageParams||[],c={pages:[],pageParams:[]},l=0,u=async()=>{let n=!1,u=e=>{Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(t.signal.aborted?n=!0:t.signal.addEventListener("abort",()=>{n=!0}),t.signal)})},d=(0,a.cG)(t.options,t.fetchOptions),h=async(e,r,i)=>{if(n)return Promise.reject();if(null==r&&e.pages.length)return Promise.resolve(e);let s=(()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?"backward":"forward",meta:t.options.meta};return u(e),e})(),o=await d(s),{maxPages:c}=t.options,l=i?a.Ht:a.VX;return{pages:l(e.pages,o,c),pageParams:l(e.pageParams,r,c)}};if(i&&s.length){let e="backward"===i,t={pages:s,pageParams:o},n=(e?function(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}:m)(r,t);c=await h(t,n,e)}else{let t=e??s.length;do{let e=0===l?o[0]??r.initialPageParam:m(r,c);if(l>0&&null==e)break;c=await h(c,e),l++}while(lt.options.persister?.(u,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=u}}}function m(e,{pages:t,pageParams:n}){let a=t.length-1;return t.length>0?e.getNextPageParam(t[a],t,n[a],n):void 0}var f=class{#l;#n;#u;#d;#h;#p;#m;#f;constructor(e={}){this.#l=e.queryCache||new o,this.#n=e.mutationCache||new l,this.#u=e.defaultOptions||{},this.#d=new Map,this.#h=new Map,this.#p=0}mount(){this.#p++,1===this.#p&&(this.#m=d.j.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#l.onFocus())}),this.#f=h.N.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#l.onOnline())}))}unmount(){this.#p--,0===this.#p&&(this.#m?.(),this.#m=void 0,this.#f?.(),this.#f=void 0)}isFetching(e){return this.#l.findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return this.#n.findAll({...e,status:"pending"}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#l.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=this.#l.build(this,t),r=n.state.data;return void 0===r?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime((0,a.KC)(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return this.#l.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=this.#l.get(r.queryHash),s=i?.state.data,o=(0,a.SE)(t,s);if(void 0!==o)return this.#l.build(this,r).setData(o,{...n,manual:!0})}setQueriesData(e,t,n){return i.Vr.batch(()=>this.#l.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#l.get(t.queryHash)?.state}removeQueries(e){let t=this.#l;i.Vr.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=this.#l;return i.Vr.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t};return Promise.all(i.Vr.batch(()=>this.#l.findAll(e).map(e=>e.cancel(n)))).then(a.ZT).catch(a.ZT)}invalidateQueries(e,t={}){return i.Vr.batch(()=>(this.#l.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType==="none")?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??"active"},t))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0};return Promise.all(i.Vr.batch(()=>this.#l.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(a.ZT)),"paused"===e.state.fetchStatus?Promise.resolve():t}))).then(a.ZT)}fetchQuery(e){let t=this.defaultQueryOptions(e);void 0===t.retry&&(t.retry=!1);let n=this.#l.build(this,t);return n.isStaleByTime((0,a.KC)(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(a.ZT).catch(a.ZT)}fetchInfiniteQuery(e){return e.behavior=p(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(a.ZT).catch(a.ZT)}ensureInfiniteQueryData(e){return e.behavior=p(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return h.N.isOnline()?this.#n.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#l}getMutationCache(){return this.#n}getDefaultOptions(){return this.#u}setDefaultOptions(e){this.#u=e}setQueryDefaults(e,t){this.#d.set((0,a.Ym)(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#d.values()],n={};return t.forEach(t=>{(0,a.to)(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){this.#h.set((0,a.Ym)(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#h.values()],n={};return t.forEach(t=>{(0,a.to)(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#u.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=(0,a.Rm)(t.queryKey,t)),void 0===t.refetchOnReconnect&&(t.refetchOnReconnect="always"!==t.networkMode),void 0===t.throwOnError&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===a.CN&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#u.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#l.clear(),this.#n.clear()}}},21770:function(e,t,n){n.d(t,{D:function(){return u}});var a=n(2265),r=n(2894),i=n(18238),s=n(24112),o=n(45345),c=class extends s.l{#e;#g=void 0;#y;#b;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#v()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),(0,o.VS)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#y,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,o.Ym)(t.mutationKey)!==(0,o.Ym)(this.options.mutationKey)?this.reset():this.#y?.state.status==="pending"&&this.#y.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#y?.removeObserver(this)}onMutationUpdate(e){this.#v(),this.#x(e)}getCurrentResult(){return this.#g}reset(){this.#y?.removeObserver(this),this.#y=void 0,this.#v(),this.#x()}mutate(e,t){return this.#b=t,this.#y?.removeObserver(this),this.#y=this.#e.getMutationCache().build(this.#e,this.options),this.#y.addObserver(this),this.#y.execute(e)}#v(){let e=this.#y?.state??(0,r.R)();this.#g={...e,isPending:"pending"===e.status,isSuccess:"success"===e.status,isError:"error"===e.status,isIdle:"idle"===e.status,mutate:this.mutate,reset:this.reset}}#x(e){i.Vr.batch(()=>{if(this.#b&&this.hasListeners()){let t=this.#g.variables,n=this.#g.context,a={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};e?.type==="success"?(this.#b.onSuccess?.(e.data,t,n,a),this.#b.onSettled?.(e.data,null,t,n,a)):e?.type==="error"&&(this.#b.onError?.(e.error,t,n,a),this.#b.onSettled?.(void 0,e.error,t,n,a))}this.listeners.forEach(e=>{e(this.#g)})})}},l=n(29827);function u(e,t){let n=(0,l.NL)(t),[r]=a.useState(()=>new c(n,e));a.useEffect(()=>{r.setOptions(e)},[r,e]);let s=a.useSyncExternalStore(a.useCallback(e=>r.subscribe(i.Vr.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),u=a.useCallback((e,t)=>{r.mutate(e,t).catch(o.ZT)},[r]);if(s.error&&(0,o.L3)(r.options.throwOnError,[s.error]))throw s.error;return{...s,mutate:u,mutateAsync:s.mutate}}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3221-0a12dcffbc76862d.js b/litellm/proxy/_experimental/out/_next/static/chunks/3221-0a12dcffbc76862d.js deleted file mode 100644 index 6362cd74b8d..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/3221-0a12dcffbc76862d.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3221],{47323:function(e,t,o){o.d(t,{Z:function(){return b}});var n=o(5853),r=o(2265),i=o(47187),l=o(7084),a=o(13241),s=o(1153),d=o(26898);let u={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},c={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},m={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},p=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,s.bM)(t,d.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,s.bM)(t,d.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,a.q)((0,s.bM)(t,d.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,s.bM)(t,d.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,a.q)((0,s.bM)(t,d.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,s.bM)(t,d.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,a.q)((0,s.bM)(t,d.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,s.bM)(t,d.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,a.q)((0,s.bM)(t,d.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,s.bM)(t,d.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,a.q)((0,s.bM)(t,d.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},h=(0,s.fn)("Icon"),b=r.forwardRef((e,t)=>{let{icon:o,variant:d="simple",tooltip:b,size:f=l.u8.SM,color:g,className:v}=e,x=(0,n._T)(e,["icon","variant","tooltip","size","color","className"]),E=p(d,g),{tooltipProps:C,getReferenceProps:y}=(0,i.l)();return r.createElement("span",Object.assign({ref:(0,s.lq)([t,C.refs.setReference]),className:(0,a.q)(h("root"),"inline-flex shrink-0 items-center justify-center",E.bgColor,E.textColor,E.borderColor,E.ringColor,m[d].rounded,m[d].border,m[d].shadow,m[d].ring,u[f].paddingX,u[f].paddingY,v)},y,x),r.createElement(i.Z,Object.assign({text:b},C)),r.createElement(o,{className:(0,a.q)(h("icon"),"shrink-0",c[f].height,c[f].width)}))});b.displayName="Icon"},54250:function(e,t,o){o.d(t,{Z:function(){return b}});var n=o(5853),r=o(44140),i=o(2265),l=o(954),a=o(85238),s=o(58747),d=o(4537),u=o(13241),c=o(1153),m=o(96398);let p=(0,c.fn)("SearchSelect"),h=(0,c.fn)("SearchSelect"),b=i.forwardRef((e,t)=>{let{defaultValue:o="",searchValue:c,onSearchValueChange:b,value:f,onValueChange:g,placeholder:v="Select...",disabled:x=!1,icon:E,enableClear:C=!0,name:y,required:w,error:S=!1,errorMessage:k,children:O,className:T,id:I,autoComplete:M="off"}=e,R=(0,n._T)(e,["defaultValue","searchValue","onSearchValueChange","value","onValueChange","placeholder","disabled","icon","enableClear","name","required","error","errorMessage","children","className","id","autoComplete"]),z=(0,i.useRef)(null),[P,F]=(0,r.Z)("",c),[N,D]=(0,r.Z)(o,f),{reactElementChildren:_,valueToNameMapping:A}=(0,i.useMemo)(()=>{let e=i.Children.toArray(O).filter(i.isValidElement);return{reactElementChildren:e,valueToNameMapping:(0,m.sl)(e)}},[O]),L=(0,i.useMemo)(()=>(0,m.n0)(null!=P?P:"",_),[P,_]);return i.createElement("div",{className:(0,u.q)("w-full min-w-[10rem] text-tremor-default",T)},i.createElement("div",{className:"relative"},i.createElement("select",{title:"search-select-hidden",required:w,className:(0,u.q)("h-full w-full absolute left-0 top-0 -z-10 opacity-0"),value:N,onChange:e=>{e.preventDefault()},name:y,disabled:x,id:I,onFocus:()=>{let e=z.current;e&&e.focus()}},i.createElement("option",{className:"hidden",value:"",disabled:!0,hidden:!0},v),L.map(e=>{let t=e.props.value,o=e.props.children;return i.createElement("option",{className:"hidden",key:t,value:t},o)})),i.createElement(l.hQ,Object.assign({as:"div",ref:t,defaultValue:N,value:N,onChange:e=>{null==g||g(e),D(e)},disabled:x,id:I},R),e=>{let{value:t}=e;return i.createElement(i.Fragment,null,i.createElement(l.Q$,{className:"w-full"},E&&i.createElement("span",{className:(0,u.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},i.createElement(E,{className:(0,u.q)(p("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),i.createElement(l.gA,{ref:z,className:(0,u.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 text-tremor-default pr-14 border py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",E?"pl-10":"pl-3",x?"placeholder:text-tremor-content-subtle dark:placeholder:text-tremor-content-subtle":"placeholder:text-tremor-content dark:placeholder:text-tremor-content",(0,m.um)((0,m.Uh)(t),x,S)),placeholder:v,onChange:e=>{null==b||b(e.target.value),F(e.target.value)},displayValue:e=>{var t;return null!==(t=A.get(e))&&void 0!==t?t:""},autoComplete:M}),i.createElement("div",{className:(0,u.q)("absolute inset-y-0 right-0 flex items-center pr-2.5")},i.createElement(s.Z,{className:(0,u.q)(p("arrowDownIcon"),"flex-none h-5 w-5","!text-tremor-content-subtle","!dark:text-dark-tremor-content-subtle")}))),C&&N?i.createElement("button",{type:"button",className:(0,u.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),D(""),F(""),null==g||g(""),null==b||b("")}},i.createElement(d.Z,{className:(0,u.q)(h("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,L.length>0&&i.createElement(a.u,{enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},i.createElement(l.L5,{anchor:"bottom start",className:(0,u.q)("z-10 divide-y w-[var(--button-width)] overflow-y-auto outline-none rounded-tremor-default text-tremor-default max-h-[228px] border [--anchor-gap:4px]","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},L)))})),S&&k?i.createElement("p",{className:(0,u.q)("errorMessage","text-sm text-rose-500 mt-1")},k):null)});b.displayName="SearchSelect"},70450:function(e,t,o){o.d(t,{Z:function(){return d}});var n=o(5853),r=o(2265),i=o(13241),l=o(1153),a=o(954);let s=(0,l.fn)("SearchSelectItem"),d=r.forwardRef((e,t)=>{let{value:o,icon:l,className:d,children:u}=e,c=(0,n._T)(e,["value","icon","className","children"]);return r.createElement(a.O2,Object.assign({className:(0,i.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","data-[focus]:bg-tremor-background-muted data-[focus]:text-tremor-content-strong data-[selected]:text-tremor-content-strong data-[selected]:bg-tremor-background-muted text-tremor-content-emphasis","dark:data-[focus]:bg-dark-tremor-background-muted dark:data-[focus]:text-dark-tremor-content-strong dark:data-[selected]:text-dark-tremor-content-strong dark:data-[selected]:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",d),ref:t,key:o,value:o},c),l&&r.createElement(l,{className:(0,i.q)(s("icon"),"flex-none h-5 w-5 mr-3","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),r.createElement("span",{className:"whitespace-nowrap truncate"},null!=u?u:o))});d.displayName="SearchSelectItem"},59341:function(e,t,o){o.d(t,{Z:function(){return P}});var n=o(5853),r=o(71049),i=o(11323),l=o(2265),a=o(66797),s=o(40099),d=o(74275),u=o(59456),c=o(93980),m=o(65573),p=o(67561),h=o(87550),b=o(628),f=o(80281),g=o(31370),v=o(20131),x=o(38929),E=o(52307),C=o(52724),y=o(7935);let w=(0,l.createContext)(null);w.displayName="GroupContext";let S=l.Fragment,k=Object.assign((0,x.yV)(function(e,t){var o;let n=(0,l.useId)(),S=(0,f.Q)(),k=(0,h.B)(),{id:O=S||"headlessui-switch-".concat(n),disabled:T=k||!1,checked:I,defaultChecked:M,onChange:R,name:z,value:P,form:F,autoFocus:N=!1,...D}=e,_=(0,l.useContext)(w),[A,L]=(0,l.useState)(null),q=(0,l.useRef)(null),V=(0,p.T)(q,t,null===_?null:_.setSwitch,L),j=(0,d.L)(M),[B,K]=(0,s.q)(I,R,null!=j&&j),W=(0,u.G)(),[Z,H]=(0,l.useState)(!1),U=(0,c.z)(()=>{H(!0),null==K||K(!B),W.nextFrame(()=>{H(!1)})}),G=(0,c.z)(e=>{if((0,g.P)(e.currentTarget))return e.preventDefault();e.preventDefault(),U()}),X=(0,c.z)(e=>{e.key===C.R.Space?(e.preventDefault(),U()):e.key===C.R.Enter&&(0,v.g)(e.currentTarget)}),$=(0,c.z)(e=>e.preventDefault()),Y=(0,y.wp)(),Q=(0,E.zH)(),{isFocusVisible:J,focusProps:ee}=(0,r.F)({autoFocus:N}),{isHovered:et,hoverProps:eo}=(0,i.X)({isDisabled:T}),{pressed:en,pressProps:er}=(0,a.x)({disabled:T}),ei=(0,l.useMemo)(()=>({checked:B,disabled:T,hover:et,focus:J,active:en,autofocus:N,changing:Z}),[B,et,J,en,T,Z,N]),el=(0,x.dG)({id:O,ref:V,role:"switch",type:(0,m.f)(e,A),tabIndex:-1===e.tabIndex?0:null!=(o=e.tabIndex)?o:0,"aria-checked":B,"aria-labelledby":Y,"aria-describedby":Q,disabled:T||void 0,autoFocus:N,onClick:G,onKeyUp:X,onKeyPress:$},ee,eo,er),ea=(0,l.useCallback)(()=>{if(void 0!==j)return null==K?void 0:K(j)},[K,j]),es=(0,x.L6)();return l.createElement(l.Fragment,null,null!=z&&l.createElement(b.Mt,{disabled:T,data:{[z]:P||"on"},overrides:{type:"checkbox",checked:B},form:F,onReset:ea}),es({ourProps:el,theirProps:D,slot:ei,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var t;let[o,n]=(0,l.useState)(null),[r,i]=(0,y.bE)(),[a,s]=(0,E.fw)(),d=(0,l.useMemo)(()=>({switch:o,setSwitch:n}),[o,n]),u=(0,x.L6)();return l.createElement(s,{name:"Switch.Description",value:a},l.createElement(i,{name:"Switch.Label",value:r,props:{htmlFor:null==(t=d.switch)?void 0:t.id,onClick(e){o&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),o.click(),o.focus({preventScroll:!0}))}}},l.createElement(w.Provider,{value:d},u({ourProps:{},theirProps:e,slot:{},defaultTag:S,name:"Switch.Group"}))))},Label:y.__,Description:E.dk});var O=o(44140),T=o(26898),I=o(13241),M=o(1153),R=o(47187);let z=(0,M.fn)("Switch"),P=l.forwardRef((e,t)=>{let{checked:o,defaultChecked:r=!1,onChange:i,color:a,name:s,error:d,errorMessage:u,disabled:c,required:m,tooltip:p,id:h}=e,b=(0,n._T)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),f={bgColor:a?(0,M.bM)(a,T.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:a?(0,M.bM)(a,T.K.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[g,v]=(0,O.Z)(r,o),[x,E]=(0,l.useState)(!1),{tooltipProps:C,getReferenceProps:y}=(0,R.l)(300);return l.createElement("div",{className:"flex flex-row items-center justify-start"},l.createElement(R.Z,Object.assign({text:p},C)),l.createElement("div",Object.assign({ref:(0,M.lq)([t,C.refs.setReference]),className:(0,I.q)(z("root"),"flex flex-row relative h-5")},b,y),l.createElement("input",{type:"checkbox",className:(0,I.q)(z("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:s,required:m,checked:g,onChange:e=>{e.preventDefault()}}),l.createElement(k,{checked:g,onChange:e=>{v(e),null==i||i(e)},disabled:c,className:(0,I.q)(z("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",c?"cursor-not-allowed":""),onFocus:()=>E(!0),onBlur:()=>E(!1),id:h},l.createElement("span",{className:(0,I.q)(z("sr-only"),"sr-only")},"Switch ",g?"on":"off"),l.createElement("span",{"aria-hidden":"true",className:(0,I.q)(z("background"),g?f.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),l.createElement("span",{"aria-hidden":"true",className:(0,I.q)(z("round"),g?(0,I.q)(f.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",x?(0,I.q)("ring-2",f.ringColor):"")}))),d&&u?l.createElement("p",{className:(0,I.q)(z("errorMessage"),"text-sm text-red-500 mt-1 ")},u):null)});P.displayName="Switch"},44643:function(e,t,o){var n=o(2265);let r=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=r},91126:function(e,t,o){var n=o(2265);let r=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=r},954:function(e,t,o){let n,r,i,l,a;o.d(t,{hQ:function(){return e_},Q$:function(){return ez},gA:function(){return eP},O2:function(){return eD},L5:function(){return eN}});var s=o(71049),d=o(11323),u=o(2265),c=o(54887);function m(e,t,o){let n,r=o.initialDeps??[];function i(){var i,l,a,s;let d,u;o.key&&(null==(i=o.debug)?void 0:i.call(o))&&(d=Date.now());let c=e();if(!(c.length!==r.length||c.some((e,t)=>r[t]!==e)))return n;if(r=c,o.key&&(null==(l=o.debug)?void 0:l.call(o))&&(u=Date.now()),n=t(...c),o.key&&(null==(a=o.debug)?void 0:a.call(o))){let e=Math.round((Date.now()-d)*100)/100,t=Math.round((Date.now()-u)*100)/100,n=t/16,r=(e,t)=>{for(e=String(e);e.length{r=e},i}function p(e,t){if(void 0!==e)return e;throw Error(`Unexpected undefined${t?`: ${t}`:""}`)}let h=(e,t)=>1.01>Math.abs(e-t),b=(e,t,o)=>{let n;return function(...r){e.clearTimeout(n),n=e.setTimeout(()=>t.apply(this,r),o)}},f=e=>{let{offsetWidth:t,offsetHeight:o}=e;return{width:t,height:o}},g=e=>e,v=e=>{let t=Math.max(e.startIndex-e.overscan,0),o=Math.min(e.endIndex+e.overscan,e.count-1),n=[];for(let e=t;e<=o;e++)n.push(e);return n},x=(e,t)=>{let o=e.scrollElement;if(!o)return;let n=e.targetWindow;if(!n)return;let r=e=>{let{width:o,height:n}=e;t({width:Math.round(o),height:Math.round(n)})};if(r(f(o)),!n.ResizeObserver)return()=>{};let i=new n.ResizeObserver(t=>{let n=()=>{let e=t[0];if(null==e?void 0:e.borderBoxSize){let t=e.borderBoxSize[0];if(t){r({width:t.inlineSize,height:t.blockSize});return}}r(f(o))};e.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(n):n()});return i.observe(o,{box:"border-box"}),()=>{i.unobserve(o)}},E={passive:!0},C="undefined"==typeof window||"onscrollend"in window,y=(e,t)=>{let o=e.scrollElement;if(!o)return;let n=e.targetWindow;if(!n)return;let r=0,i=e.options.useScrollendEvent&&C?()=>void 0:b(n,()=>{t(r,!1)},e.options.isScrollingResetDelay),l=n=>()=>{let{horizontal:l,isRtl:a}=e.options;r=l?o.scrollLeft*(a&&-1||1):o.scrollTop,i(),t(r,n)},a=l(!0),s=l(!1);s(),o.addEventListener("scroll",a,E);let d=e.options.useScrollendEvent&&C;return d&&o.addEventListener("scrollend",s,E),()=>{o.removeEventListener("scroll",a),d&&o.removeEventListener("scrollend",s)}},w=(e,t,o)=>{if(null==t?void 0:t.borderBoxSize){let e=t.borderBoxSize[0];if(e)return Math.round(e[o.options.horizontal?"inlineSize":"blockSize"])}return e[o.options.horizontal?"offsetWidth":"offsetHeight"]},S=(e,{adjustments:t=0,behavior:o},n)=>{var r,i;null==(i=null==(r=n.scrollElement)?void 0:r.scrollTo)||i.call(r,{[n.options.horizontal?"left":"top"]:e+t,behavior:o})};class k{constructor(e){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.measurementsCache=[],this.itemSizeCache=new Map,this.pendingMeasuredCacheIndexes=[],this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this.elementsCache=new Map,this.observer=(()=>{let e=null,t=()=>e||(this.targetWindow&&this.targetWindow.ResizeObserver?e=new this.targetWindow.ResizeObserver(e=>{e.forEach(e=>{let t=()=>{this._measureElement(e.target,e)};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(t):t()})}):null);return{disconnect:()=>{var o;null==(o=t())||o.disconnect(),e=null},observe:e=>{var o;return null==(o=t())?void 0:o.observe(e,{box:"border-box"})},unobserve:e=>{var o;return null==(o=t())?void 0:o.unobserve(e)}}})(),this.range=null,this.setOptions=e=>{Object.entries(e).forEach(([t,o])=>{void 0===o&&delete e[t]}),this.options={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:g,rangeExtractor:v,onChange:()=>{},measureElement:w,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:"data-index",initialMeasurementsCache:[],lanes:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,...e}},this.notify=e=>{var t,o;null==(o=(t=this.options).onChange)||o.call(t,this,e)},this.maybeNotify=m(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),e=>{this.notify(e)},{key:!1,debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(e=>e()),this.unsubs=[],this.observer.disconnect(),this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{var e;let t=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==t){if(this.cleanup(),!t){this.maybeNotify();return}this.scrollElement=t,this.scrollElement&&"ownerDocument"in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=(null==(e=this.scrollElement)?void 0:e.window)??null,this.elementsCache.forEach(e=>{this.observer.observe(e)}),this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0}),this.unsubs.push(this.options.observeElementRect(this,e=>{this.scrollRect=e,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(e,t)=>{this.scrollAdjustments=0,this.scrollDirection=t?this.getScrollOffset()this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?"width":"height"]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??("function"==typeof this.options.initialOffset?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(e,t)=>{let o=new Map,n=new Map;for(let r=t-1;r>=0;r--){let t=e[r];if(o.has(t.lane))continue;let i=n.get(t.lane);if(null==i||t.end>i.end?n.set(t.lane,t):t.ende.end===t.end?e.index-t.index:e.end-t.end)[0]:void 0},this.getMeasurementOptions=m(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled],(e,t,o,n,r)=>(this.pendingMeasuredCacheIndexes=[],{count:e,paddingStart:t,scrollMargin:o,getItemKey:n,enabled:r}),{key:!1}),this.getMeasurements=m(()=>[this.getMeasurementOptions(),this.itemSizeCache],({count:e,paddingStart:t,scrollMargin:o,getItemKey:n,enabled:r},i)=>{if(!r)return this.measurementsCache=[],this.itemSizeCache.clear(),[];0===this.measurementsCache.length&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(e=>{this.itemSizeCache.set(e.key,e.size)}));let l=this.pendingMeasuredCacheIndexes.length>0?Math.min(...this.pendingMeasuredCacheIndexes):0;this.pendingMeasuredCacheIndexes=[];let a=this.measurementsCache.slice(0,l);for(let r=l;rthis.options.debug}),this.calculateRange=m(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(e,t,o,n)=>this.range=e.length>0&&t>0?function({measurements:e,outerSize:t,scrollOffset:o,lanes:n}){let r=e.length-1;if(e.length<=n)return{startIndex:0,endIndex:r};let i=O(0,r,t=>e[t].start,o),l=i;if(1===n)for(;l1){let a=Array(n).fill(0);for(;le=0&&s.some(e=>e>=o);){let t=e[i];s[t.lane]=t.start,i--}i=Math.max(0,i-i%n),l=Math.min(r,l+(n-1-l%n))}return{startIndex:i,endIndex:l}}({measurements:e,outerSize:t,scrollOffset:o,lanes:n}):null,{key:!1,debug:()=>this.options.debug}),this.getVirtualIndexes=m(()=>{let e=null,t=null,o=this.calculateRange();return o&&(e=o.startIndex,t=o.endIndex),this.maybeNotify.updateDeps([this.isScrolling,e,t]),[this.options.rangeExtractor,this.options.overscan,this.options.count,e,t]},(e,t,o,n,r)=>null===n||null===r?[]:e({startIndex:n,endIndex:r,overscan:t,count:o}),{key:!1,debug:()=>this.options.debug}),this.indexFromElement=e=>{let t=this.options.indexAttribute,o=e.getAttribute(t);return o?parseInt(o,10):(console.warn(`Missing attribute name '${t}={index}' on measured element.`),-1)},this._measureElement=(e,t)=>{let o=this.indexFromElement(e),n=this.measurementsCache[o];if(!n)return;let r=n.key,i=this.elementsCache.get(r);i!==e&&(i&&this.observer.unobserve(i),this.observer.observe(e),this.elementsCache.set(r,e)),e.isConnected&&this.resizeItem(o,this.options.measureElement(e,t,this))},this.resizeItem=(e,t)=>{let o=this.measurementsCache[e];if(!o)return;let n=t-(this.itemSizeCache.get(o.key)??o.size);0!==n&&((void 0!==this.shouldAdjustScrollPositionOnItemSizeChange?this.shouldAdjustScrollPositionOnItemSizeChange(o,n,this):o.start{if(!e){this.elementsCache.forEach((e,t)=>{e.isConnected||(this.observer.unobserve(e),this.elementsCache.delete(t))});return}this._measureElement(e,void 0)},this.getVirtualItems=m(()=>[this.getVirtualIndexes(),this.getMeasurements()],(e,t)=>{let o=[];for(let n=0,r=e.length;nthis.options.debug}),this.getVirtualItemForOffset=e=>{let t=this.getMeasurements();if(0!==t.length)return p(t[O(0,t.length-1,e=>p(t[e]).start,e)])},this.getOffsetForAlignment=(e,t,o=0)=>{let n=this.getSize(),r=this.getScrollOffset();return"auto"===t&&(t=e>=r+n?"end":"start"),"center"===t?e+=(o-n)/2:"end"===t&&(e-=n),Math.max(Math.min(this.getTotalSize()+this.options.scrollMargin-n,e),0)},this.getOffsetForIndex=(e,t="auto")=>{e=Math.max(0,Math.min(e,this.options.count-1));let o=this.measurementsCache[e];if(!o)return;let n=this.getSize(),r=this.getScrollOffset();if("auto"===t){if(o.end>=r+n-this.options.scrollPaddingEnd)t="end";else{if(!(o.start<=r+this.options.scrollPaddingStart))return[r,t];t="start"}}let i="end"===t?o.end+this.options.scrollPaddingEnd:o.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(i,t,o.size),t]},this.isDynamicMode=()=>this.elementsCache.size>0,this.scrollToOffset=(e,{align:t="start",behavior:o}={})=>{"smooth"===o&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getOffsetForAlignment(e,t),{adjustments:void 0,behavior:o})},this.scrollToIndex=(e,{align:t="auto",behavior:o}={})=>{"smooth"===o&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),e=Math.max(0,Math.min(e,this.options.count-1));let n=0,r=t=>{if(!this.targetWindow)return;let n=this.getOffsetForIndex(e,t);if(!n){console.warn("Failed to get offset for index:",e);return}let[r,l]=n;this._scrollToOffset(r,{adjustments:void 0,behavior:o}),this.targetWindow.requestAnimationFrame(()=>{let t=this.getScrollOffset(),o=this.getOffsetForIndex(e,l);if(!o){console.warn("Failed to get offset for index:",e);return}h(o[0],t)||i(l)})},i=t=>{this.targetWindow&&(++n<10?this.targetWindow.requestAnimationFrame(()=>r(t)):console.warn(`Failed to scroll to index ${e} after 10 attempts.`))};r(t)},this.scrollBy=(e,{behavior:t}={})=>{"smooth"===t&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getScrollOffset()+e,{adjustments:void 0,behavior:t})},this.getTotalSize=()=>{var e;let t;let o=this.getMeasurements();if(0===o.length)t=this.options.paddingStart;else if(1===this.options.lanes)t=(null==(e=o[o.length-1])?void 0:e.end)??0;else{let e=Array(this.options.lanes).fill(null),n=o.length-1;for(;n>=0&&e.some(e=>null===e);){let t=o[n];null===e[t.lane]&&(e[t.lane]=t.end),n--}t=Math.max(...e.filter(e=>null!==e))}return Math.max(t-this.options.scrollMargin+this.options.paddingEnd,0)},this._scrollToOffset=(e,{adjustments:t,behavior:o})=>{this.options.scrollToFn(e,{behavior:o,adjustments:t},this)},this.measure=()=>{this.itemSizeCache=new Map,this.notify(!1)},this.setOptions(e)}}let O=(e,t,o,n)=>{for(;e<=t;){let r=(e+t)/2|0,i=o(r);if(in))return r;t=r-1}}return e>0?e-1:0},T="undefined"!=typeof document?u.useLayoutEffect:u.useEffect;var I=o(66797),M=o(93142),R=o(40099),z=o(74275),P=o(59456),F=o(86852),N=o(93980),D=o(17675),_=o(73389),A=o(43507),L=o(12315),q=o(23137),V=o(84574),j=o(31693);function B(e){let t=(0,u.useRef)({value:"",selectionStart:null,selectionEnd:null});return(0,j.O)(e,"blur",e=>{let o=e.target;o instanceof HTMLInputElement&&(t.current={value:o.value,selectionStart:o.selectionStart,selectionEnd:o.selectionEnd})}),(0,N.z)(()=>{if(document.activeElement!==e&&e instanceof HTMLInputElement&&e.isConnected){if(e.focus({preventScroll:!0}),e.value!==t.current.value)e.setSelectionRange(e.value.length,e.value.length);else{let{selectionStart:o,selectionEnd:n}=t.current;null!==o&&null!==n&&e.setSelectionRange(o,n)}t.current={value:"",selectionStart:null,selectionEnd:null}}})}var K=o(65573),W=o(48852),Z=o(67561),H=o(78866),U=o(98218),G=o(5664);function X(e,t){let o=(0,u.useRef)([]),n=(0,N.z)(e);(0,u.useEffect)(()=>{let e=[...o.current];for(let[r,i]of t.entries())if(o.current[r]!==i){let r=n(t,e);return o.current=t,r}},[n,...t])}var $=o(87550),Y=o(47506),Q=o(628),J=o(22389),ee=o(80281),et=o(28294),eo=o(93698);let en=[];!function(e){function t(){"loading"!==document.readyState&&(e(),document.removeEventListener("DOMContentLoaded",t))}"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("DOMContentLoaded",t),t())}(()=>{function e(e){if(!(e.target instanceof HTMLElement)||e.target===document.body||en[0]===e.target)return;let t=e.target;t=t.closest(eo.y),en.unshift(null!=t?t:e.target),(en=en.filter(e=>null!=e&&e.isConnected)).splice(10)}window.addEventListener("click",e,{capture:!0}),window.addEventListener("mousedown",e,{capture:!0}),window.addEventListener("focus",e,{capture:!0}),document.body.addEventListener("click",e,{capture:!0}),document.body.addEventListener("mousedown",e,{capture:!0}),document.body.addEventListener("focus",e,{capture:!0})});var er=o(31370),ei=o(74057),el=o(36933),ea=o(72468),es=o(85614),ed=o(38929),eu=o(52307),ec=o(52724),em=o(7935),ep=((n=ep||{})[n.Left=0]="Left",n[n.Right=2]="Right",n),eh=o(4796),eb=((r=eb||{})[r.Open=0]="Open",r[r.Closed=1]="Closed",r),ef=((i=ef||{})[i.Single=0]="Single",i[i.Multi=1]="Multi",i),eg=((l=eg||{})[l.Pointer=0]="Pointer",l[l.Focus=1]="Focus",l[l.Other=2]="Other",l),ev=((a=ev||{})[a.OpenCombobox=0]="OpenCombobox",a[a.CloseCombobox=1]="CloseCombobox",a[a.GoToOption=2]="GoToOption",a[a.SetTyping=3]="SetTyping",a[a.RegisterOption=4]="RegisterOption",a[a.UnregisterOption=5]="UnregisterOption",a[a.SetActivationTrigger=6]="SetActivationTrigger",a[a.UpdateVirtualConfiguration=7]="UpdateVirtualConfiguration",a[a.SetInputElement=8]="SetInputElement",a[a.SetButtonElement=9]="SetButtonElement",a[a.SetOptionsElement=10]="SetOptionsElement",a);function ex(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e=>e,o=null!==e.activeOptionIndex?e.options[e.activeOptionIndex]:null,n=t(e.options.slice()),r=n.length>0&&null!==n[0].dataRef.current.order?n.sort((e,t)=>e.dataRef.current.order-t.dataRef.current.order):(0,eo.z2)(n,e=>e.dataRef.current.domRef.current),i=o?r.indexOf(o):null;return -1===i&&(i=null),{options:r,activeOptionIndex:i}}let eE={1(e){var t;return null!=(t=e.dataRef.current)&&t.disabled||1===e.comboboxState?e:{...e,activeOptionIndex:null,comboboxState:1,isTyping:!1,activationTrigger:2,__demoMode:!1}},0(e){var t,o;if(null!=(t=e.dataRef.current)&&t.disabled||0===e.comboboxState)return e;if(null!=(o=e.dataRef.current)&&o.value){let t=e.dataRef.current.calculateIndex(e.dataRef.current.value);if(-1!==t)return{...e,activeOptionIndex:t,comboboxState:0,__demoMode:!1}}return{...e,comboboxState:0,__demoMode:!1}},3:(e,t)=>e.isTyping===t.isTyping?e:{...e,isTyping:t.isTyping},2(e,t){var o,n,r,i;if(null!=(o=e.dataRef.current)&&o.disabled||e.optionsElement&&!(null!=(n=e.dataRef.current)&&n.optionsPropsRef.current.static)&&1===e.comboboxState)return e;if(e.virtual){let{options:o,disabled:n}=e.virtual,i=t.focus===ei.T.Specific?t.idx:(0,ei.d)(t,{resolveItems:()=>o,resolveActiveIndex:()=>{var t,r;return null!=(r=null!=(t=e.activeOptionIndex)?t:o.findIndex(e=>!n(e)))?r:null},resolveDisabled:n,resolveId(){throw Error("Function not implemented.")}}),l=null!=(r=t.trigger)?r:2;return e.activeOptionIndex===i&&e.activationTrigger===l?e:{...e,activeOptionIndex:i,activationTrigger:l,isTyping:!1,__demoMode:!1}}let l=ex(e);if(null===l.activeOptionIndex){let e=l.options.findIndex(e=>!e.dataRef.current.disabled);-1!==e&&(l.activeOptionIndex=e)}let a=t.focus===ei.T.Specific?t.idx:(0,ei.d)(t,{resolveItems:()=>l.options,resolveActiveIndex:()=>l.activeOptionIndex,resolveId:e=>e.id,resolveDisabled:e=>e.dataRef.current.disabled}),s=null!=(i=t.trigger)?i:2;return e.activeOptionIndex===a&&e.activationTrigger===s?e:{...e,...l,isTyping:!1,activeOptionIndex:a,activationTrigger:s,__demoMode:!1}},4:(e,t)=>{var o,n,r;if(null!=(o=e.dataRef.current)&&o.virtual)return{...e,options:[...e.options,t.payload]};let i=t.payload,l=ex(e,e=>(e.push(i),e));null===e.activeOptionIndex&&null!=(n=e.dataRef.current)&&n.isSelected(t.payload.dataRef.current.value)&&(l.activeOptionIndex=l.options.indexOf(i));let a={...e,...l,activationTrigger:2};return null!=(r=e.dataRef.current)&&r.__demoMode&&void 0===e.dataRef.current.value&&(a.activeOptionIndex=0),a},5:(e,t)=>{var o;if(null!=(o=e.dataRef.current)&&o.virtual)return{...e,options:e.options.filter(e=>e.id!==t.id)};let n=ex(e,e=>{let o=e.findIndex(e=>e.id===t.id);return -1!==o&&e.splice(o,1),e});return{...e,...n,activationTrigger:2}},6:(e,t)=>e.activationTrigger===t.trigger?e:{...e,activationTrigger:t.trigger},7:(e,t)=>{var o,n;if(null===e.virtual)return{...e,virtual:{options:t.options,disabled:null!=(o=t.disabled)?o:()=>!1}};if(e.virtual.options===t.options&&e.virtual.disabled===t.disabled)return e;let r=e.activeOptionIndex;if(null!==e.activeOptionIndex){let o=t.options.indexOf(e.virtual.options[e.activeOptionIndex]);r=-1!==o?o:null}return{...e,activeOptionIndex:r,virtual:{options:t.options,disabled:null!=(n=t.disabled)?n:()=>!1}}},8:(e,t)=>e.inputElement===t.element?e:{...e,inputElement:t.element},9:(e,t)=>e.buttonElement===t.element?e:{...e,buttonElement:t.element},10:(e,t)=>e.optionsElement===t.element?e:{...e,optionsElement:t.element}},eC=(0,u.createContext)(null);function ey(e){let t=(0,u.useContext)(eC);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,ey),t}return t}eC.displayName="ComboboxActionsContext";let ew=(0,u.createContext)(null);function eS(e){let t=eO("VirtualProvider"),{options:o}=t.virtual,[n,r]=(0,u.useMemo)(()=>{let e=t.optionsElement;if(!e)return[0,0];let o=window.getComputedStyle(e);return[parseFloat(o.paddingBlockStart||o.paddingTop),parseFloat(o.paddingBlockEnd||o.paddingBottom)]},[t.optionsElement]),i=function(e){let t=u.useReducer(()=>({}),{})[1],o={...e,onChange:(o,n)=>{var r;n?(0,c.flushSync)(t):t(),null==(r=e.onChange)||r.call(e,o,n)}},[n]=u.useState(()=>new k(o));return n.setOptions(o),T(()=>n._didMount(),[]),T(()=>n._willUpdate()),n}({observeElementRect:x,observeElementOffset:y,scrollToFn:S,enabled:0!==o.length,scrollPaddingStart:n,scrollPaddingEnd:r,count:o.length,estimateSize:()=>40,getScrollElement:()=>t.optionsElement,overscan:12}),[l,a]=(0,u.useState)(0);(0,_.e)(()=>{a(e=>e+1)},[o]);let s=i.getVirtualItems();return 0===s.length?null:u.createElement(ew.Provider,{value:i},u.createElement("div",{style:{position:"relative",width:"100%",height:"".concat(i.getTotalSize(),"px")},ref:e=>{e&&0!==t.activationTrigger&&null!==t.activeOptionIndex&&o.length>t.activeOptionIndex&&i.scrollToIndex(t.activeOptionIndex)}},s.map(t=>{var n;return u.createElement(u.Fragment,{key:t.key},u.cloneElement(null==(n=e.children)?void 0:n.call(e,{...e.slot,option:o[t.index]}),{key:"".concat(l,"-").concat(t.key),"data-index":t.index,"aria-setsize":o.length,"aria-posinset":t.index+1,style:{position:"absolute",top:0,left:0,transform:"translateY(".concat(t.start,"px)"),overflowAnchor:"none"}}))})))}let ek=(0,u.createContext)(null);function eO(e){let t=(0,u.useContext)(ek);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,eO),t}return t}function eT(e,t){return(0,ea.E)(t.type,eE,e,t)}ek.displayName="ComboboxDataContext";let eI=u.Fragment,eM=ed.VN.RenderStrategy|ed.VN.Static,eR=(0,ed.yV)(function(e,t){var o,n;let r=(0,$.B)(),{value:i,defaultValue:l,onChange:a,form:s,name:d,by:c,disabled:m=r||!1,onClose:p,__demoMode:h=!1,multiple:b=!1,immediate:f=!1,virtual:g=null,nullable:v,...x}=e,E=(0,z.L)(l),[C=b?[]:void 0,y]=(0,R.q)(i,a,E),[w,S]=(0,u.useReducer)(eT,{dataRef:(0,u.createRef)(),comboboxState:h?0:1,isTyping:!1,options:[],virtual:g?{options:g.options,disabled:null!=(o=g.disabled)?o:()=>!1}:null,activeOptionIndex:null,activationTrigger:2,inputElement:null,buttonElement:null,optionsElement:null,__demoMode:h}),k=(0,u.useRef)(!1),O=(0,u.useRef)({static:!1,hold:!1}),T=(0,M.J)(c),I=(0,N.z)(e=>g?null===c?g.options.indexOf(e):g.options.findIndex(t=>T(t,e)):w.options.findIndex(t=>T(t.dataRef.current.value,e))),P=(0,u.useCallback)(e=>(0,ea.E)(D.mode,{1:()=>C.some(t=>T(t,e)),0:()=>T(C,e)}),[C]),F=(0,N.z)(e=>w.activeOptionIndex===I(e)),D=(0,u.useMemo)(()=>({...w,immediate:f,optionsPropsRef:O,value:C,defaultValue:E,disabled:m,mode:b?1:0,virtual:g?w.virtual:null,get activeOptionIndex(){if(k.current&&null===w.activeOptionIndex&&(g?g.options.length>0:w.options.length>0)){if(g){let e=g.options.findIndex(e=>{var t,o;return!(null!=(o=null==(t=g.disabled)?void 0:t.call(g,e))&&o)});if(-1!==e)return e}let e=w.options.findIndex(e=>!e.dataRef.current.disabled);if(-1!==e)return e}return w.activeOptionIndex},calculateIndex:I,compare:T,isSelected:P,isActive:F}),[C,E,m,b,h,w,g]);(0,_.e)(()=>{var e;g&&S({type:7,options:g.options,disabled:null!=(e=g.disabled)?e:null})},[g,null==g?void 0:g.options,null==g?void 0:g.disabled]),(0,_.e)(()=>{w.dataRef.current=D},[D]);let A=0===D.comboboxState;(0,q.O)(A,[D.buttonElement,D.inputElement,D.optionsElement],()=>ee.closeCombobox());let L=(0,u.useMemo)(()=>{var e,t,o;return{open:0===D.comboboxState,disabled:m,activeIndex:D.activeOptionIndex,activeOption:null===D.activeOptionIndex?null:D.virtual?D.virtual.options[null!=(e=D.activeOptionIndex)?e:0]:null!=(o=null==(t=D.options[D.activeOptionIndex])?void 0:t.dataRef.current.value)?o:null,value:C}},[D,m,C]),V=(0,N.z)(()=>{if(null!==D.activeOptionIndex){if(ee.setIsTyping(!1),D.virtual)H(D.virtual.options[D.activeOptionIndex]);else{let{dataRef:e}=D.options[D.activeOptionIndex];H(e.current.value)}ee.goToOption(ei.T.Specific,D.activeOptionIndex)}}),j=(0,N.z)(()=>{S({type:0}),k.current=!0}),B=(0,N.z)(()=>{S({type:1}),k.current=!1,null==p||p()}),K=(0,N.z)(e=>{S({type:3,isTyping:e})}),W=(0,N.z)((e,t,o)=>(k.current=!1,e===ei.T.Specific?S({type:2,focus:ei.T.Specific,idx:t,trigger:o}):S({type:2,focus:e,trigger:o}))),Z=(0,N.z)((e,t)=>(S({type:4,payload:{id:e,dataRef:t}}),()=>{D.isActive(t.current.value)&&(k.current=!0),S({type:5,id:e})})),H=(0,N.z)(e=>(0,ea.E)(D.mode,{0:()=>null==y?void 0:y(e),1(){let t=D.value.slice(),o=t.findIndex(t=>T(t,e));return -1===o?t.push(e):t.splice(o,1),null==y?void 0:y(t)}})),U=(0,N.z)(e=>{S({type:6,trigger:e})}),G=(0,N.z)(e=>{S({type:8,element:e})}),X=(0,N.z)(e=>{S({type:9,element:e})}),J=(0,N.z)(e=>{S({type:10,element:e})}),ee=(0,u.useMemo)(()=>({onChange:H,registerOption:Z,goToOption:W,setIsTyping:K,closeCombobox:B,openCombobox:j,setActivationTrigger:U,selectActiveOption:V,setInputElement:G,setButtonElement:X,setOptionsElement:J}),[]),[eo,en]=(0,em.bE)(),er=(0,u.useCallback)(()=>{if(void 0!==E)return null==y?void 0:y(E)},[y,E]),el=(0,ed.L6)();return u.createElement(en,{value:eo,props:{htmlFor:null==(n=D.inputElement)?void 0:n.id},slot:{open:0===D.comboboxState,disabled:m}},u.createElement(Y.HO,null,u.createElement(eC.Provider,{value:ee},u.createElement(ek.Provider,{value:D},u.createElement(et.up,{value:(0,ea.E)(D.comboboxState,{0:et.ZM.Open,1:et.ZM.Closed})},null!=d&&u.createElement(Q.Mt,{disabled:m,data:null!=C?{[d]:C}:{},form:s,onReset:er}),el({ourProps:null===t?{}:{ref:t},theirProps:x,slot:L,defaultTag:eI,name:"Combobox"}))))))}),ez=(0,ed.yV)(function(e,t){var o;let n=eO("Combobox.Button"),r=ey("Combobox.Button"),i=(0,Z.T)(t,r.setButtonElement),l=(0,u.useId)(),{id:a="headlessui-combobox-button-".concat(l),disabled:m=n.disabled||!1,autoFocus:p=!1,...h}=e,b=B(n.inputElement),f=(0,N.z)(e=>{switch(e.key){case ec.R.Space:case ec.R.Enter:e.preventDefault(),e.stopPropagation(),1===n.comboboxState&&(0,c.flushSync)(()=>r.openCombobox()),b();return;case ec.R.ArrowDown:e.preventDefault(),e.stopPropagation(),1===n.comboboxState&&((0,c.flushSync)(()=>r.openCombobox()),n.value||r.goToOption(ei.T.First)),b();return;case ec.R.ArrowUp:e.preventDefault(),e.stopPropagation(),1===n.comboboxState&&((0,c.flushSync)(()=>r.openCombobox()),n.value||r.goToOption(ei.T.Last)),b();return;case ec.R.Escape:if(0!==n.comboboxState)return;e.preventDefault(),n.optionsElement&&!n.optionsPropsRef.current.static&&e.stopPropagation(),(0,c.flushSync)(()=>r.closeCombobox()),b();return;default:return}}),g=(0,N.z)(e=>{e.preventDefault(),(0,er.P)(e.currentTarget)||(e.button===ep.Left&&(0===n.comboboxState?r.closeCombobox():r.openCombobox()),b())}),v=(0,em.wp)([a]),{isFocusVisible:x,focusProps:E}=(0,s.F)({autoFocus:p}),{isHovered:C,hoverProps:y}=(0,d.X)({isDisabled:m}),{pressed:w,pressProps:S}=(0,I.x)({disabled:m}),k=(0,u.useMemo)(()=>({open:0===n.comboboxState,active:w||0===n.comboboxState,disabled:m,value:n.value,hover:C,focus:x}),[n,C,x,w,m]),O=(0,ed.dG)({ref:i,id:a,type:(0,K.f)(e,n.buttonElement),tabIndex:-1,"aria-haspopup":"listbox","aria-controls":null==(o=n.optionsElement)?void 0:o.id,"aria-expanded":0===n.comboboxState,"aria-labelledby":v,disabled:m||void 0,autoFocus:p,onMouseDown:g,onKeyDown:f},E,y,S);return(0,ed.L6)()({ourProps:O,theirProps:h,slot:k,defaultTag:"button",name:"Combobox.Button"})}),eP=(0,ed.yV)(function(e,t){var o,n,r,i,l;let a=eO("Combobox.Input"),m=ey("Combobox.Input"),p=(0,u.useId)(),h=(0,ee.Q)(),{id:b=h||"headlessui-combobox-input-".concat(p),onChange:f,displayValue:g,disabled:v=a.disabled||!1,autoFocus:x=!1,type:E="text",...C}=e,y=(0,u.useRef)(null),w=(0,Z.T)(y,t,(0,Y.AZ)(),m.setInputElement),S=(0,V.i)(a.inputElement),k=(0,P.G)(),O=(0,N.z)(()=>{m.onChange(null),a.optionsElement&&(a.optionsElement.scrollTop=0),m.goToOption(ei.T.Nothing)});X((e,t)=>{let[o,n]=e,[r,i]=t;if(a.isTyping)return;let l=y.current;l&&((0===i&&1===n||o!==r)&&(l.value=o),requestAnimationFrame(()=>{if(a.isTyping||!l||(null==S?void 0:S.activeElement)!==l)return;let{selectionStart:e,selectionEnd:t}=l;0===Math.abs((null!=t?t:0)-(null!=e?e:0))&&0===e&&l.setSelectionRange(l.value.length,l.value.length)}))},[(0,u.useMemo)(()=>{var e;return"function"==typeof g&&void 0!==a.value?null!=(e=g(a.value))?e:"":"string"==typeof a.value?a.value:""},[a.value,g]),a.comboboxState,S,a.isTyping]),X((e,t)=>{let[o]=e,[n]=t;if(0===o&&1===n){if(a.isTyping)return;let e=y.current;if(!e)return;let t=e.value,{selectionStart:o,selectionEnd:n,selectionDirection:r}=e;e.value="",e.value=t,null!==r?e.setSelectionRange(o,n,r):e.setSelectionRange(o,n)}},[a.comboboxState]);let T=(0,u.useRef)(!1),I=(0,N.z)(()=>{T.current=!0}),M=(0,N.z)(()=>{k.nextFrame(()=>{T.current=!1})}),R=(0,N.z)(e=>{switch(m.setIsTyping(!0),e.key){case ec.R.Enter:if(0!==a.comboboxState||T.current)return;if(e.preventDefault(),e.stopPropagation(),null===a.activeOptionIndex){m.closeCombobox();return}m.selectActiveOption(),0===a.mode&&m.closeCombobox();break;case ec.R.ArrowDown:return e.preventDefault(),e.stopPropagation(),(0,ea.E)(a.comboboxState,{0:()=>m.goToOption(ei.T.Next),1:()=>m.openCombobox()});case ec.R.ArrowUp:return e.preventDefault(),e.stopPropagation(),(0,ea.E)(a.comboboxState,{0:()=>m.goToOption(ei.T.Previous),1:()=>{(0,c.flushSync)(()=>m.openCombobox()),a.value||m.goToOption(ei.T.Last)}});case ec.R.Home:if(e.shiftKey)break;return e.preventDefault(),e.stopPropagation(),m.goToOption(ei.T.First);case ec.R.PageUp:return e.preventDefault(),e.stopPropagation(),m.goToOption(ei.T.First);case ec.R.End:if(e.shiftKey)break;return e.preventDefault(),e.stopPropagation(),m.goToOption(ei.T.Last);case ec.R.PageDown:return e.preventDefault(),e.stopPropagation(),m.goToOption(ei.T.Last);case ec.R.Escape:return 0!==a.comboboxState?void 0:(e.preventDefault(),a.optionsElement&&!a.optionsPropsRef.current.static&&e.stopPropagation(),0===a.mode&&null===a.value&&O(),m.closeCombobox());case ec.R.Tab:if(0!==a.comboboxState)return;0===a.mode&&1!==a.activationTrigger&&m.selectActiveOption(),m.closeCombobox()}}),z=(0,N.z)(e=>{null==f||f(e),0===a.mode&&""===e.target.value&&O(),m.openCombobox()}),F=(0,N.z)(e=>{var t,o,n;let r=null!=(t=e.relatedTarget)?t:en.find(t=>t!==e.currentTarget);if(!(null!=(o=a.optionsElement)&&o.contains(r))&&!(null!=(n=a.buttonElement)&&n.contains(r))&&0===a.comboboxState)return e.preventDefault(),0===a.mode&&null===a.value&&O(),m.closeCombobox()}),D=(0,N.z)(e=>{var t,o,n;let r=null!=(t=e.relatedTarget)?t:en.find(t=>t!==e.currentTarget);null!=(o=a.buttonElement)&&o.contains(r)||null!=(n=a.optionsElement)&&n.contains(r)||a.disabled||a.immediate&&0!==a.comboboxState&&k.microTask(()=>{(0,c.flushSync)(()=>m.openCombobox()),m.setActivationTrigger(1)})}),_=(0,em.wp)(),A=(0,eu.zH)(),{isFocused:L,focusProps:q}=(0,s.F)({autoFocus:x}),{isHovered:j,hoverProps:B}=(0,d.X)({isDisabled:v}),K=(0,u.useMemo)(()=>({open:0===a.comboboxState,disabled:v,hover:j,focus:L,autofocus:x}),[a,j,L,x,v]),W=(0,ed.dG)({ref:w,id:b,role:"combobox",type:E,"aria-controls":null==(o=a.optionsElement)?void 0:o.id,"aria-expanded":0===a.comboboxState,"aria-activedescendant":null===a.activeOptionIndex?void 0:a.virtual?null==(n=a.options.find(e=>!e.dataRef.current.disabled&&a.compare(e.dataRef.current.value,a.virtual.options[a.activeOptionIndex])))?void 0:n.id:null==(r=a.options[a.activeOptionIndex])?void 0:r.id,"aria-labelledby":_,"aria-describedby":A,"aria-autocomplete":"list",defaultValue:null!=(l=null!=(i=e.defaultValue)?i:void 0!==a.defaultValue?null==g?void 0:g(a.defaultValue):null)?l:a.defaultValue,disabled:v||void 0,autoFocus:x,onCompositionStart:I,onCompositionEnd:M,onKeyDown:R,onChange:z,onFocus:D,onBlur:F},q,B);return(0,ed.L6)()({ourProps:W,theirProps:C,slot:K,defaultTag:"input",name:"Combobox.Input"})}),eF=em.__,eN=(0,ed.yV)(function(e,t){var o,n,r;let i=(0,u.useId)(),{id:l="headlessui-combobox-options-".concat(i),hold:a=!1,anchor:s,portal:d=!1,modal:c=!0,transition:m=!1,...p}=e,h=eO("Combobox.Options"),b=ey("Combobox.Options"),f=(0,Y.Vy)(s);f&&(d=!0);let[g,v]=(0,Y.ES)(f),[x,E]=(0,u.useState)(null),C=(0,Y.U8)(),y=(0,Z.T)(t,f?g:null,b.setOptionsElement,E),w=(0,V.i)(h.optionsElement),S=(0,et.oJ)(),[k,O]=(0,U.Y)(m,x,null!==S?(S&et.ZM.Open)===et.ZM.Open:0===h.comboboxState);(0,L.m)(k,h.inputElement,b.closeCombobox);let T=!h.__demoMode&&c&&0===h.comboboxState;(0,W.P)(T,w);let I=!h.__demoMode&&c&&0===h.comboboxState;(0,D.s)(I,{allowed:(0,u.useCallback)(()=>[h.inputElement,h.buttonElement,h.optionsElement],[h.inputElement,h.buttonElement,h.optionsElement])}),(0,_.e)(()=>{var t;h.optionsPropsRef.current.static=null!=(t=e.static)&&t},[h.optionsPropsRef,e.static]),(0,_.e)(()=>{h.optionsPropsRef.current.hold=a},[h.optionsPropsRef,a]),function(e,t){let{container:o,accept:n,walk:r}=t,i=(0,u.useRef)(n),l=(0,u.useRef)(r);(0,u.useEffect)(()=>{i.current=n,l.current=r},[n,r]),(0,_.e)(()=>{if(!o||!e)return;let t=(0,G.r)(o);if(!t)return;let n=i.current,r=l.current,a=Object.assign(e=>n(e),{acceptNode:n}),s=t.createTreeWalker(o,NodeFilter.SHOW_ELEMENT,a,!1);for(;s.nextNode();)r(s.currentNode)},[o,e,i,l])}(0===h.comboboxState,{container:h.optionsElement,accept:e=>"option"===e.getAttribute("role")?NodeFilter.FILTER_REJECT:e.hasAttribute("role")?NodeFilter.FILTER_SKIP:NodeFilter.FILTER_ACCEPT,walk(e){e.setAttribute("role","none")}});let M=(0,em.wp)([null==(o=h.buttonElement)?void 0:o.id]),R=(0,u.useMemo)(()=>({open:0===h.comboboxState,option:void 0}),[h.comboboxState]),z=(0,N.z)(()=>{b.setActivationTrigger(0)}),P=(0,N.z)(e=>{e.preventDefault(),b.setActivationTrigger(0)}),A=(0,ed.dG)(f?C():{},{"aria-labelledby":M,role:"listbox","aria-multiselectable":1===h.mode||void 0,id:l,ref:y,style:{...p.style,...v,"--input-width":(0,F.h)(h.inputElement,!0).width,"--button-width":(0,F.h)(h.buttonElement,!0).width},onWheel:0===h.activationTrigger?void 0:z,onMouseDown:P,...(0,U.X)(O)}),q=k&&1===h.comboboxState,j=(0,J._)(q,null==(n=h.virtual)?void 0:n.options),B=(0,J._)(q,h.value),K=(0,N.z)(e=>h.compare(B,e));if(h.virtual){if(void 0===j)throw Error("Missing `options` in virtual mode");Object.assign(p,{children:u.createElement(ek.Provider,{value:j!==h.virtual.options?{...h,virtual:{...h.virtual,options:j}}:h},u.createElement(eS,{slot:R},p.children))})}let H=(0,ed.L6)();return u.createElement(eh.h_,{enabled:!!d&&(e.static||k)},u.createElement(ek.Provider,{value:1===h.mode?h:{...h,isSelected:K}},H({ourProps:A,theirProps:{...p,children:u.createElement(J.F,{freeze:q},"function"==typeof p.children?null==(r=p.children)?void 0:r.call(p,R):p.children)},slot:R,defaultTag:"div",features:eM,visible:k,name:"Combobox.Options"})))}),eD=(0,ed.yV)(function(e,t){var o,n,r,i;let l=eO("Combobox.Option"),a=ey("Combobox.Option"),s=(0,u.useId)(),{id:d="headlessui-combobox-option-".concat(s),value:c,disabled:m=null!=(r=null==(n=null==(o=l.virtual)?void 0:o.disabled)?void 0:n.call(o,c))&&r,order:p=null,...h}=e,b=B(l.inputElement),f=l.virtual?l.activeOptionIndex===l.calculateIndex(c):null!==l.activeOptionIndex&&(null==(i=l.options[l.activeOptionIndex])?void 0:i.id)===d,g=l.isSelected(c),v=(0,u.useRef)(null),x=(0,A.E)({disabled:m,value:c,domRef:v,order:p}),E=(0,u.useContext)(ew),C=(0,Z.T)(t,v,E?E.measureElement:null),y=(0,N.z)(()=>{a.setIsTyping(!1),a.onChange(c)});(0,_.e)(()=>a.registerOption(d,x),[x,d]);let w=(0,u.useRef)(!(l.virtual||l.__demoMode));(0,_.e)(()=>{if(!l.virtual&&!l.__demoMode)return(0,el.k)().requestAnimationFrame(()=>{w.current=!0})},[l.virtual,l.__demoMode]),(0,_.e)(()=>{if(w.current&&0===l.comboboxState&&f&&0!==l.activationTrigger)return(0,el.k)().requestAnimationFrame(()=>{var e,t;null==(t=null==(e=v.current)?void 0:e.scrollIntoView)||t.call(e,{block:"nearest"})})},[v,f,l.comboboxState,l.activationTrigger,l.activeOptionIndex]);let S=(0,N.z)(e=>{e.preventDefault(),e.button===ep.Left&&(m||(y(),(0,es.tq)()||requestAnimationFrame(()=>b()),0===l.mode&&a.closeCombobox()))}),k=(0,N.z)(()=>{if(m)return a.goToOption(ei.T.Nothing);let e=l.calculateIndex(c);a.goToOption(ei.T.Specific,e)}),O=(0,H.g)(),T=(0,N.z)(e=>O.update(e)),I=(0,N.z)(e=>{if(!O.wasMoved(e)||m||f)return;let t=l.calculateIndex(c);a.goToOption(ei.T.Specific,t,0)}),M=(0,N.z)(e=>{O.wasMoved(e)&&(m||f&&(l.optionsPropsRef.current.hold||a.goToOption(ei.T.Nothing)))}),R=(0,u.useMemo)(()=>({active:f,focus:f,selected:g,disabled:m}),[f,g,m]);return(0,ed.L6)()({ourProps:{id:d,ref:C,role:"option",tabIndex:!0===m?void 0:-1,"aria-disabled":!0===m||void 0,"aria-selected":g,disabled:void 0,onMouseDown:S,onFocus:k,onPointerEnter:T,onMouseEnter:T,onPointerMove:I,onMouseMove:I,onPointerLeave:M,onMouseLeave:M},theirProps:h,slot:R,defaultTag:"div",name:"Combobox.Option"})}),e_=Object.assign(eR,{Input:eP,Button:ez,Label:eF,Options:eN,Option:eD})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js b/litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js new file mode 100644 index 00000000000..0416de210bc --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3250],{49634:function(e,r,o){o.d(r,{Z:function(){return s}});var t=o(1119),l=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"}}]},name:"calendar",theme:"outlined"},n=o(55015),s=l.forwardRef(function(e,r){return l.createElement(n.Z,(0,t.Z)({},e,{ref:r,icon:a}))})},94789:function(e,r,o){o.d(r,{Z:function(){return d}});var t=o(5853),l=o(2265),a=o(26898),n=o(13241),s=o(1153);let i=(0,s.fn)("Callout"),d=l.forwardRef((e,r)=>{let{title:o,icon:d,color:c,className:m,children:p}=e,u=(0,t._T)(e,["title","icon","color","className","children"]);return l.createElement("div",Object.assign({ref:r,className:(0,n.q)(i("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",c?(0,n.q)((0,s.bM)(c,a.K.background).bgColor,(0,s.bM)(c,a.K.darkBorder).borderColor,(0,s.bM)(c,a.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,n.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),m)},u),l.createElement("div",{className:(0,n.q)(i("header"),"flex items-start")},d?l.createElement(d,{className:(0,n.q)(i("icon"),"flex-none h-5 w-5 mr-1.5")}):null,l.createElement("h4",{className:(0,n.q)(i("title"),"font-semibold")},o)),l.createElement("p",{className:(0,n.q)(i("body"),"overflow-y-auto",p?"mt-2":"")},p))});d.displayName="Callout"},35829:function(e,r,o){o.d(r,{Z:function(){return i}});var t=o(5853),l=o(26898),a=o(13241),n=o(1153),s=o(2265);let i=s.forwardRef((e,r)=>{let{color:o,children:i,className:d}=e,c=(0,t._T)(e,["color","children","className"]);return s.createElement("p",Object.assign({ref:r,className:(0,a.q)("font-semibold text-tremor-metric",o?(0,n.bM)(o,l.K.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",d)},c),i)});i.displayName="Metric"},49096:function(e,r,o){o.d(r,{ZD:function(){return a}});var t=o(61994);let l=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,a=e=>{let r=function(){for(var r,o,l=arguments.length,a=Array(l),n=0;n{let t=Object.fromEntries(Object.entries(e||{}).filter(e=>{let[r]=e;return!["class","className"].includes(r)}));return r(o.map(e=>e(t)),null==e?void 0:e.class,null==e?void 0:e.className)}},cva:e=>o=>{var t;if((null==e?void 0:e.variants)==null)return r(null==e?void 0:e.base,null==o?void 0:o.class,null==o?void 0:o.className);let{variants:a,defaultVariants:n}=e,s=Object.keys(a).map(e=>{let r=null==o?void 0:o[e],t=null==n?void 0:n[e],s=l(r)||l(t);return a[e][s]}),i={...n,...o&&Object.entries(o).reduce((e,r)=>{let[o,t]=r;return void 0===t?e:{...e,[o]:t}},{})},d=null==e?void 0:null===(t=e.compoundVariants)||void 0===t?void 0:t.reduce((e,r)=>{let{class:o,className:t,...l}=r;return Object.entries(l).every(e=>{let[r,o]=e,t=i[r];return Array.isArray(o)?o.includes(t):t===o})?[...e,o,t]:e},[]);return r(null==e?void 0:e.base,s,d,null==o?void 0:o.class,null==o?void 0:o.className)},cx:r}},{compose:n,cva:s,cx:i}=a()},53335:function(e,r,o){o.d(r,{m6:function(){return ev}});let t=(e,r)=>{let o=Array(e.length+r.length);for(let r=0;r({classGroupId:e,validator:r}),a=(e=new Map,r=null,o)=>({nextPart:e,validators:r,classGroupId:o}),n=[],s=e=>{let r=c(e),{conflictingClassGroups:o,conflictingClassGroupModifiers:l}=e;return{getClassGroupId:e=>{if(e.startsWith("[")&&e.endsWith("]"))return d(e);let o=e.split("-"),t=""===o[0]&&o.length>1?1:0;return i(o,t,r)},getConflictingClassGroupIds:(e,r)=>{if(r){let r=l[e],a=o[e];return r?a?t(a,r):r:a||n}return o[e]||n}}},i=(e,r,o)=>{if(0==e.length-r)return o.classGroupId;let t=e[r],l=o.nextPart.get(t);if(l){let o=i(e,r+1,l);if(o)return o}let a=o.validators;if(null===a)return;let n=0===r?e.join("-"):e.slice(r).join("-"),s=a.length;for(let e=0;e-1===e.slice(1,-1).indexOf(":")?void 0:(()=>{let r=e.slice(1,-1),o=r.indexOf(":"),t=r.slice(0,o);return t?"arbitrary.."+t:void 0})(),c=e=>{let{theme:r,classGroups:o}=e;return m(o,r)},m=(e,r)=>{let o=a();for(let t in e)p(e[t],o,t,r);return o},p=(e,r,o,t)=>{let l=e.length;for(let a=0;a{if("string"==typeof e){b(e,r,o);return}if("function"==typeof e){f(e,r,o,t);return}g(e,r,o,t)},b=(e,r,o)=>{(""===e?r:h(r,e)).classGroupId=o},f=(e,r,o,t)=>{if(k(e)){p(e(t),r,o,t);return}null===r.validators&&(r.validators=[]),r.validators.push(l(o,e))},g=(e,r,o,t)=>{let l=Object.entries(e),a=l.length;for(let e=0;e{let o=e,t=r.split("-"),l=t.length;for(let e=0;e"isThemeGetter"in e&&!0===e.isThemeGetter,v=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let r=0,o=Object.create(null),t=Object.create(null),l=(l,a)=>{o[l]=a,++r>e&&(r=0,t=o,o=Object.create(null))};return{get(e){let r=o[e];return void 0!==r?r:void 0!==(r=t[e])?(l(e,r),r):void 0},set(e,r){e in o?o[e]=r:l(e,r)}}},x=[],w=(e,r,o,t,l)=>({modifiers:e,hasImportantModifier:r,baseClassName:o,maybePostfixModifierPosition:t,isExternal:l}),y=e=>{let{prefix:r,experimentalParseClassName:o}=e,t=e=>{let r;let o=[],t=0,l=0,a=0,n=e.length;for(let s=0;sa?r-a:void 0)};if(r){let e=r+":",o=t;t=r=>r.startsWith(e)?o(r.slice(e.length)):w(x,!1,r,void 0,!0)}if(o){let e=t;t=r=>o({className:r,parseClassName:e})}return t},z=e=>{let r=new Map;return e.orderSensitiveModifiers.forEach((e,o)=>{r.set(e,1e6+o)}),e=>{let o=[],t=[];for(let l=0;l0&&(t.sort(),o.push(...t),t=[]),o.push(a)):t.push(a)}return t.length>0&&(t.sort(),o.push(...t)),o}},j=e=>({cache:v(e.cacheSize),parseClassName:y(e),sortModifiers:z(e),...s(e)}),N=/\s+/,C=(e,r)=>{let{parseClassName:o,getClassGroupId:t,getConflictingClassGroupIds:l,sortModifiers:a}=r,n=[],s=e.trim().split(N),i="";for(let e=s.length-1;e>=0;e-=1){let r=s[e],{isExternal:d,modifiers:c,hasImportantModifier:m,baseClassName:p,maybePostfixModifierPosition:u}=o(r);if(d){i=r+(i.length>0?" "+i:i);continue}let b=!!u,f=t(b?p.substring(0,u):p);if(!f){if(!b||!(f=t(p))){i=r+(i.length>0?" "+i:i);continue}b=!1}let g=0===c.length?"":1===c.length?c[0]:a(c).join(":"),h=m?g+"!":g,k=h+f;if(n.indexOf(k)>-1)continue;n.push(k);let v=l(f,b);for(let e=0;e0?" "+i:i)}return i},O=(...e)=>{let r,o,t=0,l="";for(;t{let r;if("string"==typeof e)return e;let o="";for(let t=0;t{let r=r=>r[e]||E;return r.isThemeGetter=!0,r},q=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,W=/^\((?:(\w[\w-]*):)?(.+)\)$/i,$=/^\d+\/\d+$/,_=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,T=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,I=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,Z=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,A=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,H=e=>$.test(e),K=e=>!!e&&!Number.isNaN(Number(e)),S=e=>!!e&&Number.isInteger(Number(e)),V=e=>e.endsWith("%")&&K(e.slice(0,-1)),P=e=>_.test(e),R=()=>!0,B=e=>T.test(e)&&!I.test(e),D=()=>!1,F=e=>Z.test(e),J=e=>A.test(e),L=e=>!U(e)&&!et(e),Q=e=>ec(e,eb,D),U=e=>q.test(e),X=e=>ec(e,ef,B),Y=e=>ec(e,eg,K),ee=e=>ec(e,ep,D),er=e=>ec(e,eu,J),eo=e=>ec(e,ek,F),et=e=>W.test(e),el=e=>em(e,ef),ea=e=>em(e,eh),en=e=>em(e,ep),es=e=>em(e,eb),ei=e=>em(e,eu),ed=e=>em(e,ek,!0),ec=(e,r,o)=>{let t=q.exec(e);return!!t&&(t[1]?r(t[1]):o(t[2]))},em=(e,r,o=!1)=>{let t=W.exec(e);return!!t&&(t[1]?r(t[1]):o)},ep=e=>"position"===e||"percentage"===e,eu=e=>"image"===e||"url"===e,eb=e=>"length"===e||"size"===e||"bg-size"===e,ef=e=>"length"===e,eg=e=>"number"===e,eh=e=>"family-name"===e,ek=e=>"shadow"===e,ev=((e,...r)=>{let o,t,l,a;let n=e=>{let r=t(e);if(r)return r;let a=C(e,o);return l(e,a),a};return a=s=>(t=(o=j(r.reduce((e,r)=>r(e),e()))).cache.get,l=o.cache.set,a=n,n(s)),(...e)=>a(O(...e))})(()=>{let e=G("color"),r=G("font"),o=G("text"),t=G("font-weight"),l=G("tracking"),a=G("leading"),n=G("breakpoint"),s=G("container"),i=G("spacing"),d=G("radius"),c=G("shadow"),m=G("inset-shadow"),p=G("text-shadow"),u=G("drop-shadow"),b=G("blur"),f=G("perspective"),g=G("aspect"),h=G("ease"),k=G("animate"),v=()=>["auto","avoid","all","avoid-page","page","left","right","column"],x=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],w=()=>[...x(),et,U],y=()=>["auto","hidden","clip","visible","scroll"],z=()=>["auto","contain","none"],j=()=>[et,U,i],N=()=>[H,"full","auto",...j()],C=()=>[S,"none","subgrid",et,U],O=()=>["auto",{span:["full",S,et,U]},S,et,U],M=()=>[S,"auto",et,U],E=()=>["auto","min","max","fr",et,U],q=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],W=()=>["start","end","center","stretch","center-safe","end-safe"],$=()=>["auto",...j()],_=()=>[H,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...j()],T=()=>[e,et,U],I=()=>[...x(),en,ee,{position:[et,U]}],Z=()=>["no-repeat",{repeat:["","x","y","space","round"]}],A=()=>["auto","cover","contain",es,Q,{size:[et,U]}],B=()=>[V,el,X],D=()=>["","none","full",d,et,U],F=()=>["",K,el,X],J=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],em=()=>[K,V,en,ee],ep=()=>["","none",b,et,U],eu=()=>["none",K,et,U],eb=()=>["none",K,et,U],ef=()=>[K,et,U],eg=()=>[H,"full",...j()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[P],breakpoint:[P],color:[R],container:[P],"drop-shadow":[P],ease:["in","out","in-out"],font:[L],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[P],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[P],shadow:[P],spacing:["px",K],text:[P],"text-shadow":[P],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",H,U,et,g]}],container:["container"],columns:[{columns:[K,U,et,s]}],"break-after":[{"break-after":v()}],"break-before":[{"break-before":v()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:w()}],overflow:[{overflow:y()}],"overflow-x":[{"overflow-x":y()}],"overflow-y":[{"overflow-y":y()}],overscroll:[{overscroll:z()}],"overscroll-x":[{"overscroll-x":z()}],"overscroll-y":[{"overscroll-y":z()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:N()}],"inset-x":[{"inset-x":N()}],"inset-y":[{"inset-y":N()}],start:[{start:N()}],end:[{end:N()}],top:[{top:N()}],right:[{right:N()}],bottom:[{bottom:N()}],left:[{left:N()}],visibility:["visible","invisible","collapse"],z:[{z:[S,"auto",et,U]}],basis:[{basis:[H,"full","auto",s,...j()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[K,H,"auto","initial","none",U]}],grow:[{grow:["",K,et,U]}],shrink:[{shrink:["",K,et,U]}],order:[{order:[S,"first","last","none",et,U]}],"grid-cols":[{"grid-cols":C()}],"col-start-end":[{col:O()}],"col-start":[{"col-start":M()}],"col-end":[{"col-end":M()}],"grid-rows":[{"grid-rows":C()}],"row-start-end":[{row:O()}],"row-start":[{"row-start":M()}],"row-end":[{"row-end":M()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":E()}],"auto-rows":[{"auto-rows":E()}],gap:[{gap:j()}],"gap-x":[{"gap-x":j()}],"gap-y":[{"gap-y":j()}],"justify-content":[{justify:[...q(),"normal"]}],"justify-items":[{"justify-items":[...W(),"normal"]}],"justify-self":[{"justify-self":["auto",...W()]}],"align-content":[{content:["normal",...q()]}],"align-items":[{items:[...W(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...W(),{baseline:["","last"]}]}],"place-content":[{"place-content":q()}],"place-items":[{"place-items":[...W(),"baseline"]}],"place-self":[{"place-self":["auto",...W()]}],p:[{p:j()}],px:[{px:j()}],py:[{py:j()}],ps:[{ps:j()}],pe:[{pe:j()}],pt:[{pt:j()}],pr:[{pr:j()}],pb:[{pb:j()}],pl:[{pl:j()}],m:[{m:$()}],mx:[{mx:$()}],my:[{my:$()}],ms:[{ms:$()}],me:[{me:$()}],mt:[{mt:$()}],mr:[{mr:$()}],mb:[{mb:$()}],ml:[{ml:$()}],"space-x":[{"space-x":j()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":j()}],"space-y-reverse":["space-y-reverse"],size:[{size:_()}],w:[{w:[s,"screen",..._()]}],"min-w":[{"min-w":[s,"screen","none",..._()]}],"max-w":[{"max-w":[s,"screen","none","prose",{screen:[n]},..._()]}],h:[{h:["screen","lh",..._()]}],"min-h":[{"min-h":["screen","lh","none",..._()]}],"max-h":[{"max-h":["screen","lh",..._()]}],"font-size":[{text:["base",o,el,X]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[t,et,Y]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",V,U]}],"font-family":[{font:[ea,U,r]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[l,et,U]}],"line-clamp":[{"line-clamp":[K,"none",et,Y]}],leading:[{leading:[a,...j()]}],"list-image":[{"list-image":["none",et,U]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",et,U]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:T()}],"text-color":[{text:T()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...J(),"wavy"]}],"text-decoration-thickness":[{decoration:[K,"from-font","auto",et,X]}],"text-decoration-color":[{decoration:T()}],"underline-offset":[{"underline-offset":[K,"auto",et,U]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:j()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",et,U]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",et,U]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:I()}],"bg-repeat":[{bg:Z()}],"bg-size":[{bg:A()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},S,et,U],radial:["",et,U],conic:[S,et,U]},ei,er]}],"bg-color":[{bg:T()}],"gradient-from-pos":[{from:B()}],"gradient-via-pos":[{via:B()}],"gradient-to-pos":[{to:B()}],"gradient-from":[{from:T()}],"gradient-via":[{via:T()}],"gradient-to":[{to:T()}],rounded:[{rounded:D()}],"rounded-s":[{"rounded-s":D()}],"rounded-e":[{"rounded-e":D()}],"rounded-t":[{"rounded-t":D()}],"rounded-r":[{"rounded-r":D()}],"rounded-b":[{"rounded-b":D()}],"rounded-l":[{"rounded-l":D()}],"rounded-ss":[{"rounded-ss":D()}],"rounded-se":[{"rounded-se":D()}],"rounded-ee":[{"rounded-ee":D()}],"rounded-es":[{"rounded-es":D()}],"rounded-tl":[{"rounded-tl":D()}],"rounded-tr":[{"rounded-tr":D()}],"rounded-br":[{"rounded-br":D()}],"rounded-bl":[{"rounded-bl":D()}],"border-w":[{border:F()}],"border-w-x":[{"border-x":F()}],"border-w-y":[{"border-y":F()}],"border-w-s":[{"border-s":F()}],"border-w-e":[{"border-e":F()}],"border-w-t":[{"border-t":F()}],"border-w-r":[{"border-r":F()}],"border-w-b":[{"border-b":F()}],"border-w-l":[{"border-l":F()}],"divide-x":[{"divide-x":F()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":F()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...J(),"hidden","none"]}],"divide-style":[{divide:[...J(),"hidden","none"]}],"border-color":[{border:T()}],"border-color-x":[{"border-x":T()}],"border-color-y":[{"border-y":T()}],"border-color-s":[{"border-s":T()}],"border-color-e":[{"border-e":T()}],"border-color-t":[{"border-t":T()}],"border-color-r":[{"border-r":T()}],"border-color-b":[{"border-b":T()}],"border-color-l":[{"border-l":T()}],"divide-color":[{divide:T()}],"outline-style":[{outline:[...J(),"none","hidden"]}],"outline-offset":[{"outline-offset":[K,et,U]}],"outline-w":[{outline:["",K,el,X]}],"outline-color":[{outline:T()}],shadow:[{shadow:["","none",c,ed,eo]}],"shadow-color":[{shadow:T()}],"inset-shadow":[{"inset-shadow":["none",m,ed,eo]}],"inset-shadow-color":[{"inset-shadow":T()}],"ring-w":[{ring:F()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:T()}],"ring-offset-w":[{"ring-offset":[K,X]}],"ring-offset-color":[{"ring-offset":T()}],"inset-ring-w":[{"inset-ring":F()}],"inset-ring-color":[{"inset-ring":T()}],"text-shadow":[{"text-shadow":["none",p,ed,eo]}],"text-shadow-color":[{"text-shadow":T()}],opacity:[{opacity:[K,et,U]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[K]}],"mask-image-linear-from-pos":[{"mask-linear-from":em()}],"mask-image-linear-to-pos":[{"mask-linear-to":em()}],"mask-image-linear-from-color":[{"mask-linear-from":T()}],"mask-image-linear-to-color":[{"mask-linear-to":T()}],"mask-image-t-from-pos":[{"mask-t-from":em()}],"mask-image-t-to-pos":[{"mask-t-to":em()}],"mask-image-t-from-color":[{"mask-t-from":T()}],"mask-image-t-to-color":[{"mask-t-to":T()}],"mask-image-r-from-pos":[{"mask-r-from":em()}],"mask-image-r-to-pos":[{"mask-r-to":em()}],"mask-image-r-from-color":[{"mask-r-from":T()}],"mask-image-r-to-color":[{"mask-r-to":T()}],"mask-image-b-from-pos":[{"mask-b-from":em()}],"mask-image-b-to-pos":[{"mask-b-to":em()}],"mask-image-b-from-color":[{"mask-b-from":T()}],"mask-image-b-to-color":[{"mask-b-to":T()}],"mask-image-l-from-pos":[{"mask-l-from":em()}],"mask-image-l-to-pos":[{"mask-l-to":em()}],"mask-image-l-from-color":[{"mask-l-from":T()}],"mask-image-l-to-color":[{"mask-l-to":T()}],"mask-image-x-from-pos":[{"mask-x-from":em()}],"mask-image-x-to-pos":[{"mask-x-to":em()}],"mask-image-x-from-color":[{"mask-x-from":T()}],"mask-image-x-to-color":[{"mask-x-to":T()}],"mask-image-y-from-pos":[{"mask-y-from":em()}],"mask-image-y-to-pos":[{"mask-y-to":em()}],"mask-image-y-from-color":[{"mask-y-from":T()}],"mask-image-y-to-color":[{"mask-y-to":T()}],"mask-image-radial":[{"mask-radial":[et,U]}],"mask-image-radial-from-pos":[{"mask-radial-from":em()}],"mask-image-radial-to-pos":[{"mask-radial-to":em()}],"mask-image-radial-from-color":[{"mask-radial-from":T()}],"mask-image-radial-to-color":[{"mask-radial-to":T()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":x()}],"mask-image-conic-pos":[{"mask-conic":[K]}],"mask-image-conic-from-pos":[{"mask-conic-from":em()}],"mask-image-conic-to-pos":[{"mask-conic-to":em()}],"mask-image-conic-from-color":[{"mask-conic-from":T()}],"mask-image-conic-to-color":[{"mask-conic-to":T()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:I()}],"mask-repeat":[{mask:Z()}],"mask-size":[{mask:A()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",et,U]}],filter:[{filter:["","none",et,U]}],blur:[{blur:ep()}],brightness:[{brightness:[K,et,U]}],contrast:[{contrast:[K,et,U]}],"drop-shadow":[{"drop-shadow":["","none",u,ed,eo]}],"drop-shadow-color":[{"drop-shadow":T()}],grayscale:[{grayscale:["",K,et,U]}],"hue-rotate":[{"hue-rotate":[K,et,U]}],invert:[{invert:["",K,et,U]}],saturate:[{saturate:[K,et,U]}],sepia:[{sepia:["",K,et,U]}],"backdrop-filter":[{"backdrop-filter":["","none",et,U]}],"backdrop-blur":[{"backdrop-blur":ep()}],"backdrop-brightness":[{"backdrop-brightness":[K,et,U]}],"backdrop-contrast":[{"backdrop-contrast":[K,et,U]}],"backdrop-grayscale":[{"backdrop-grayscale":["",K,et,U]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[K,et,U]}],"backdrop-invert":[{"backdrop-invert":["",K,et,U]}],"backdrop-opacity":[{"backdrop-opacity":[K,et,U]}],"backdrop-saturate":[{"backdrop-saturate":[K,et,U]}],"backdrop-sepia":[{"backdrop-sepia":["",K,et,U]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":j()}],"border-spacing-x":[{"border-spacing-x":j()}],"border-spacing-y":[{"border-spacing-y":j()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",et,U]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[K,"initial",et,U]}],ease:[{ease:["linear","initial",h,et,U]}],delay:[{delay:[K,et,U]}],animate:[{animate:["none",k,et,U]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[f,et,U]}],"perspective-origin":[{"perspective-origin":w()}],rotate:[{rotate:eu()}],"rotate-x":[{"rotate-x":eu()}],"rotate-y":[{"rotate-y":eu()}],"rotate-z":[{"rotate-z":eu()}],scale:[{scale:eb()}],"scale-x":[{"scale-x":eb()}],"scale-y":[{"scale-y":eb()}],"scale-z":[{"scale-z":eb()}],"scale-3d":["scale-3d"],skew:[{skew:ef()}],"skew-x":[{"skew-x":ef()}],"skew-y":[{"skew-y":ef()}],transform:[{transform:[et,U,"","none","gpu","cpu"]}],"transform-origin":[{origin:w()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:eg()}],"translate-x":[{"translate-x":eg()}],"translate-y":[{"translate-y":eg()}],"translate-z":[{"translate-z":eg()}],"translate-none":["translate-none"],accent:[{accent:T()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:T()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",et,U]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":j()}],"scroll-mx":[{"scroll-mx":j()}],"scroll-my":[{"scroll-my":j()}],"scroll-ms":[{"scroll-ms":j()}],"scroll-me":[{"scroll-me":j()}],"scroll-mt":[{"scroll-mt":j()}],"scroll-mr":[{"scroll-mr":j()}],"scroll-mb":[{"scroll-mb":j()}],"scroll-ml":[{"scroll-ml":j()}],"scroll-p":[{"scroll-p":j()}],"scroll-px":[{"scroll-px":j()}],"scroll-py":[{"scroll-py":j()}],"scroll-ps":[{"scroll-ps":j()}],"scroll-pe":[{"scroll-pe":j()}],"scroll-pt":[{"scroll-pt":j()}],"scroll-pr":[{"scroll-pr":j()}],"scroll-pb":[{"scroll-pb":j()}],"scroll-pl":[{"scroll-pl":j()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",et,U]}],fill:[{fill:["none",...T()]}],"stroke-w":[{stroke:[K,el,X,Y]}],stroke:[{stroke:["none",...T()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js b/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js new file mode 100644 index 00000000000..3b9478ef07f --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3325],{41649:function(e,r,t){t.d(r,{Z:function(){return f}});var n=t(5853),o=t(2265),a=t(47187),l=t(7084),i=t(26898),d=t(13241),s=t(1153);let c={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},u={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},m=(0,s.fn)("Badge"),f=o.forwardRef((e,r)=>{let{color:t,icon:f,size:p=l.u8.SM,tooltip:g,className:b,children:h}=e,k=(0,n._T)(e,["color","icon","size","tooltip","className","children"]),v=f||null,{tooltipProps:x,getReferenceProps:w}=(0,a.l)();return o.createElement("span",Object.assign({ref:(0,s.lq)([r,x.refs.setReference]),className:(0,d.q)(m("root"),"w-max shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-small ring-1 ring-inset",t?(0,d.q)((0,s.bM)(t,i.K.background).bgColor,(0,s.bM)(t,i.K.iconText).textColor,(0,s.bM)(t,i.K.iconRing).ringColor,"bg-opacity-10 ring-opacity-20","dark:bg-opacity-5 dark:ring-opacity-60"):(0,d.q)("bg-tremor-brand-faint text-tremor-brand-emphasis ring-tremor-brand/20","dark:bg-dark-tremor-brand-muted/50 dark:text-dark-tremor-brand dark:ring-dark-tremor-subtle/20"),c[p].paddingX,c[p].paddingY,c[p].fontSize,b)},w,k),o.createElement(a.Z,Object.assign({text:g},x)),v?o.createElement(v,{className:(0,d.q)(m("icon"),"shrink-0 -ml-1 mr-1.5",u[p].height,u[p].width)}):null,o.createElement("span",{className:(0,d.q)(m("text"),"whitespace-nowrap")},h))});f.displayName="Badge"},47323:function(e,r,t){t.d(r,{Z:function(){return g}});var n=t(5853),o=t(2265),a=t(47187),l=t(7084),i=t(13241),d=t(1153),s=t(26898);let c={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},m={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},f=(e,r)=>{switch(e){case"simple":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:r?(0,d.bM)(r,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:r?(0,i.q)((0,d.bM)(r,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},p=(0,d.fn)("Icon"),g=o.forwardRef((e,r)=>{let{icon:t,variant:s="simple",tooltip:g,size:b=l.u8.SM,color:h,className:k}=e,v=(0,n._T)(e,["icon","variant","tooltip","size","color","className"]),x=f(s,h),{tooltipProps:w,getReferenceProps:C}=(0,a.l)();return o.createElement("span",Object.assign({ref:(0,d.lq)([r,w.refs.setReference]),className:(0,i.q)(p("root"),"inline-flex shrink-0 items-center justify-center",x.bgColor,x.textColor,x.borderColor,x.ringColor,m[s].rounded,m[s].border,m[s].shadow,m[s].ring,c[b].paddingX,c[b].paddingY,k)},C,v),o.createElement(a.Z,Object.assign({text:g},w)),o.createElement(t,{className:(0,i.q)(p("icon"),"shrink-0",u[b].height,u[b].width)}))});g.displayName="Icon"},59341:function(e,r,t){t.d(r,{Z:function(){return R}});var n=t(5853),o=t(71049),a=t(11323),l=t(2265),i=t(66797),d=t(40099),s=t(74275),c=t(59456),u=t(93980),m=t(65573),f=t(67561),p=t(87550),g=t(628),b=t(80281),h=t(31370),k=t(20131),v=t(38929),x=t(52307),w=t(52724),C=t(7935);let y=(0,l.createContext)(null);y.displayName="GroupContext";let E=l.Fragment,N=Object.assign((0,v.yV)(function(e,r){var t;let n=(0,l.useId)(),E=(0,b.Q)(),N=(0,p.B)(),{id:T=E||"headlessui-switch-".concat(n),disabled:M=N||!1,checked:S,defaultChecked:q,onChange:L,name:j,value:R,form:O,autoFocus:P=!1,...F}=e,z=(0,l.useContext)(y),[I,_]=(0,l.useState)(null),K=(0,l.useRef)(null),B=(0,f.T)(K,r,null===z?null:z.setSwitch,_),H=(0,s.L)(q),[Z,D]=(0,d.q)(S,L,null!=H&&H),Y=(0,c.G)(),[X,A]=(0,l.useState)(!1),G=(0,u.z)(()=>{A(!0),null==D||D(!Z),Y.nextFrame(()=>{A(!1)})}),U=(0,u.z)(e=>{if((0,h.P)(e.currentTarget))return e.preventDefault();e.preventDefault(),G()}),V=(0,u.z)(e=>{e.key===w.R.Space?(e.preventDefault(),G()):e.key===w.R.Enter&&(0,k.g)(e.currentTarget)}),$=(0,u.z)(e=>e.preventDefault()),Q=(0,C.wp)(),W=(0,x.zH)(),{isFocusVisible:J,focusProps:ee}=(0,o.F)({autoFocus:P}),{isHovered:er,hoverProps:et}=(0,a.X)({isDisabled:M}),{pressed:en,pressProps:eo}=(0,i.x)({disabled:M}),ea=(0,l.useMemo)(()=>({checked:Z,disabled:M,hover:er,focus:J,active:en,autofocus:P,changing:X}),[Z,er,J,en,M,X,P]),el=(0,v.dG)({id:T,ref:B,role:"switch",type:(0,m.f)(e,I),tabIndex:-1===e.tabIndex?0:null!=(t=e.tabIndex)?t:0,"aria-checked":Z,"aria-labelledby":Q,"aria-describedby":W,disabled:M||void 0,autoFocus:P,onClick:U,onKeyUp:V,onKeyPress:$},ee,et,eo),ei=(0,l.useCallback)(()=>{if(void 0!==H)return null==D?void 0:D(H)},[D,H]),ed=(0,v.L6)();return l.createElement(l.Fragment,null,null!=j&&l.createElement(g.Mt,{disabled:M,data:{[j]:R||"on"},overrides:{type:"checkbox",checked:Z},form:O,onReset:ei}),ed({ourProps:el,theirProps:F,slot:ea,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var r;let[t,n]=(0,l.useState)(null),[o,a]=(0,C.bE)(),[i,d]=(0,x.fw)(),s=(0,l.useMemo)(()=>({switch:t,setSwitch:n}),[t,n]),c=(0,v.L6)();return l.createElement(d,{name:"Switch.Description",value:i},l.createElement(a,{name:"Switch.Label",value:o,props:{htmlFor:null==(r=s.switch)?void 0:r.id,onClick(e){t&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),t.click(),t.focus({preventScroll:!0}))}}},l.createElement(y.Provider,{value:s},c({ourProps:{},theirProps:e,slot:{},defaultTag:E,name:"Switch.Group"}))))},Label:C.__,Description:x.dk});var T=t(44140),M=t(26898),S=t(13241),q=t(1153),L=t(47187);let j=(0,q.fn)("Switch"),R=l.forwardRef((e,r)=>{let{checked:t,defaultChecked:o=!1,onChange:a,color:i,name:d,error:s,errorMessage:c,disabled:u,required:m,tooltip:f,id:p}=e,g=(0,n._T)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),b={bgColor:i?(0,q.bM)(i,M.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:i?(0,q.bM)(i,M.K.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[h,k]=(0,T.Z)(o,t),[v,x]=(0,l.useState)(!1),{tooltipProps:w,getReferenceProps:C}=(0,L.l)(300);return l.createElement("div",{className:"flex flex-row items-center justify-start"},l.createElement(L.Z,Object.assign({text:f},w)),l.createElement("div",Object.assign({ref:(0,q.lq)([r,w.refs.setReference]),className:(0,S.q)(j("root"),"flex flex-row relative h-5")},g,C),l.createElement("input",{type:"checkbox",className:(0,S.q)(j("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:d,required:m,checked:h,onChange:e=>{e.preventDefault()}}),l.createElement(N,{checked:h,onChange:e=>{k(e),null==a||a(e)},disabled:u,className:(0,S.q)(j("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",u?"cursor-not-allowed":""),onFocus:()=>x(!0),onBlur:()=>x(!1),id:p},l.createElement("span",{className:(0,S.q)(j("sr-only"),"sr-only")},"Switch ",h?"on":"off"),l.createElement("span",{"aria-hidden":"true",className:(0,S.q)(j("background"),h?b.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),l.createElement("span",{"aria-hidden":"true",className:(0,S.q)(j("round"),h?(0,S.q)(b.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",v?(0,S.q)("ring-2",b.ringColor):"")}))),s&&c?l.createElement("p",{className:(0,S.q)(j("errorMessage"),"text-sm text-red-500 mt-1 ")},c):null)});R.displayName="Switch"},21626:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("Table"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement("div",{className:(0,a.q)(l("root"),"overflow-auto",i)},o.createElement("table",Object.assign({ref:r,className:(0,a.q)(l("table"),"w-full text-tremor-default","text-tremor-content","dark:text-dark-tremor-content")},d),t))});i.displayName="Table"},97214:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableBody"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("tbody",Object.assign({ref:r,className:(0,a.q)(l("root"),"align-top divide-y","divide-tremor-border","dark:divide-dark-tremor-border",i)},d),t))});i.displayName="TableBody"},28241:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableCell"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("td",Object.assign({ref:r,className:(0,a.q)(l("root"),"align-middle whitespace-nowrap text-left p-4",i)},d),t))});i.displayName="TableCell"},58834:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableHead"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("thead",Object.assign({ref:r,className:(0,a.q)(l("root"),"text-left","text-tremor-content","dark:text-dark-tremor-content",i)},d),t))});i.displayName="TableHead"},69552:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableHeaderCell"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("th",Object.assign({ref:r,className:(0,a.q)(l("root"),"whitespace-nowrap text-left font-semibold top-0 px-4 py-3.5","text-tremor-content-strong","dark:text-dark-tremor-content-strong",i)},d),t))});i.displayName="TableHeaderCell"},71876:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableRow"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("tr",Object.assign({ref:r,className:(0,a.q)(l("row"),i)},d),t))});i.displayName="TableRow"},44140:function(e,r,t){t.d(r,{Z:function(){return o}});var n=t(2265);let o=(e,r)=>{let t=void 0!==r,[o,a]=(0,n.useState)(e);return[t?r:o,e=>{t||a(e)}]}},44643:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=o},91126:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=o},74998:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});r.Z=o},52307:function(e,r,t){t.d(r,{dk:function(){return m},fw:function(){return u},zH:function(){return c}});var n=t(2265),o=t(93980),a=t(73389),l=t(67561),i=t(87550),d=t(38929);let s=(0,n.createContext)(null);function c(){var e,r;return null!=(r=null==(e=(0,n.useContext)(s))?void 0:e.value)?r:void 0}function u(){let[e,r]=(0,n.useState)([]);return[e.length>0?e.join(" "):void 0,(0,n.useMemo)(()=>function(e){let t=(0,o.z)(e=>(r(r=>[...r,e]),()=>r(r=>{let t=r.slice(),n=t.indexOf(e);return -1!==n&&t.splice(n,1),t}))),a=(0,n.useMemo)(()=>({register:t,slot:e.slot,name:e.name,props:e.props,value:e.value}),[t,e.slot,e.name,e.props,e.value]);return n.createElement(s.Provider,{value:a},e.children)},[r])]}s.displayName="DescriptionContext";let m=Object.assign((0,d.yV)(function(e,r){let t=(0,n.useId)(),o=(0,i.B)(),{id:c="headlessui-description-".concat(t),...u}=e,m=function e(){let r=(0,n.useContext)(s);if(null===r){let r=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(r,e),r}return r}(),f=(0,l.T)(r);(0,a.e)(()=>m.register(c),[c,m.register]);let p=o||!1,g=(0,n.useMemo)(()=>({...m.slot,disabled:p}),[m.slot,p]),b={ref:f,...m.props,id:c};return(0,d.L6)()({ourProps:b,theirProps:u,slot:g,defaultTag:"p",name:m.name||"Description"})}),{})},7935:function(e,r,t){t.d(r,{__:function(){return f},bE:function(){return m},wp:function(){return u}});var n=t(2265),o=t(93980),a=t(73389),l=t(67561),i=t(87550),d=t(80281),s=t(38929);let c=(0,n.createContext)(null);function u(e){var r,t,o;let a=null!=(t=null==(r=(0,n.useContext)(c))?void 0:r.value)?t:void 0;return(null!=(o=null==e?void 0:e.length)?o:0)>0?[a,...e].filter(Boolean).join(" "):a}function m(){let{inherit:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=u(),[t,a]=(0,n.useState)([]),l=e?[r,...t].filter(Boolean):t;return[l.length>0?l.join(" "):void 0,(0,n.useMemo)(()=>function(e){let r=(0,o.z)(e=>(a(r=>[...r,e]),()=>a(r=>{let t=r.slice(),n=t.indexOf(e);return -1!==n&&t.splice(n,1),t}))),t=(0,n.useMemo)(()=>({register:r,slot:e.slot,name:e.name,props:e.props,value:e.value}),[r,e.slot,e.name,e.props,e.value]);return n.createElement(c.Provider,{value:t},e.children)},[a])]}c.displayName="LabelContext";let f=Object.assign((0,s.yV)(function(e,r){var t;let u=(0,n.useId)(),m=function e(){let r=(0,n.useContext)(c);if(null===r){let r=Error("You used a
-
-            {/* {JSON.stringify(getRawRequest(), null, 2)} */}
-            
-          
+
@@ -136,17 +129,9 @@ export function RequestResponsePanel({ -
+
{hasResponse ? ( -
-              {/* {JSON.stringify(formattedResponse(), null, 2)} */}
-              
-            
+ ) : (
Response data not available
)} From 2f045f07a2c262f277ba599f30d9e2027974cfb3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 15:08:00 -0800 Subject: [PATCH 190/349] Adding loading states to edit settings --- .../src/components/model_info_view.tsx | 1 + .../organization/organization_view.tsx | 10 ++++++-- .../src/components/team/team_info.tsx | 10 ++++++-- .../components/templates/key_edit_view.tsx | 24 ++++++++++++------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 2563c90f59c..64f96ac915d 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -966,6 +966,7 @@ export default function ModelInfoView({ setIsDirty(false); setIsEditing(false); }} + disabled={isSaving} > Cancel diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx index b284519ce13..76f5e81b2fc 100644 --- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx +++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx @@ -69,6 +69,7 @@ const OrganizationInfoView: React.FC = ({ const [isEditMemberModalVisible, setIsEditMemberModalVisible] = useState(false); const [selectedEditMember, setSelectedEditMember] = useState(null); const [copiedStates, setCopiedStates] = useState>({}); + const [isOrgSaving, setIsOrgSaving] = useState(false); const canEditOrg = is_org_admin || is_proxy_admin; const fetchOrgInfo = async () => { @@ -151,6 +152,7 @@ const OrganizationInfoView: React.FC = ({ const handleOrgUpdate = async (values: any) => { try { if (!accessToken) return; + setIsOrgSaving(true); const updateData: any = { organization_id: organizationId, @@ -194,6 +196,8 @@ const OrganizationInfoView: React.FC = ({ } catch (error) { NotificationsManager.fromBackend("Failed to update organization settings"); console.error("Error updating organization:", error); + } finally { + setIsOrgSaving(false); } }; @@ -492,10 +496,12 @@ const OrganizationInfoView: React.FC = ({
- setIsEditing(false)}> + setIsEditing(false)} disabled={isOrgSaving}> Cancel - Save Changes + + Save Changes +
diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx index 1c6ba629ef7..a53d35e57bb 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.tsx @@ -142,6 +142,7 @@ const TeamInfoView: React.FC = ({ const [memberToDelete, setMemberToDelete] = useState(null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); + const [isTeamSaving, setIsTeamSaving] = useState(false); console.log("userModels in team info", userModels); @@ -310,6 +311,7 @@ const TeamInfoView: React.FC = ({ const handleTeamUpdate = async (values: any) => { try { if (!accessToken) return; + setIsTeamSaving(true); let parsedMetadata = {}; try { @@ -387,6 +389,8 @@ const TeamInfoView: React.FC = ({ fetchTeamInfo(); } catch (error) { console.error("Error updating team:", error); + } finally { + setIsTeamSaving(false); } }; @@ -770,10 +774,12 @@ const TeamInfoView: React.FC = ({
- setIsEditing(false)}> + setIsEditing(false)} disabled={isTeamSaving}> Cancel - Save Changes + + Save Changes +
diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx index 8c101486404..d7376fde146 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx @@ -95,6 +95,7 @@ export function KeyEditView({ ); const [autoRotationEnabled, setAutoRotationEnabled] = useState(keyData.auto_rotate || false); const [rotationInterval, setRotationInterval] = useState(keyData.rotation_interval || ""); + const [isKeySaving, setIsKeySaving] = useState(false); const fetchMcpAccessGroups = async () => { if (!accessToken) return; @@ -236,8 +237,17 @@ export function KeyEditView({ console.log("premiumUser:", premiumUser); + const handleSubmit = async (values: any) => { + try { + setIsKeySaving(true); + await onSubmit(values); + } finally { + setIsKeySaving(false); + } + }; + return ( -
+ @@ -403,11 +413,7 @@ export function KeyEditView({ name="disable_global_guardrails" valuePropName="checked" > - + @@ -573,10 +579,12 @@ export function KeyEditView({
- + Cancel - Save Changes + + Save Changes +
From 4aea2df98617249681c140640811cbcb306cf135 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 16:19:50 -0800 Subject: [PATCH 191/349] Various Text, button state, and test changes --- .../EntityUsageExportModal.test.tsx | 20 ++++---- .../EntityUsageExportModal.tsx | 42 ++------------- .../src/components/EntityUsageExport/utils.ts | 51 ++++++++++++++++++- .../components/guardrails/pii_components.tsx | 16 +++--- .../src/components/mcp_tools/mcp_servers.tsx | 1 + 5 files changed, 71 insertions(+), 59 deletions(-) diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.test.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.test.tsx index dae758e7e71..d881e225273 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.test.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.test.tsx @@ -17,6 +17,8 @@ import EntityUsageExportModal from "./EntityUsageExportModal"; // Mock utilities that format/export data so tests stay fast and deterministic vi.mock("./utils", () => { return { + handleExportCSV: vi.fn(), + handleExportJSON: vi.fn(), generateExportData: vi.fn(() => [{ Date: "2025-10-01" }]), generateMetadata: vi.fn(() => ({ meta: true })), }; @@ -66,11 +68,11 @@ describe("EntityUsageExportModal", () => { it("renders default state and exports CSV (daily) successfully", async () => { /** * Tests the happy path: user opens modal and exports with defaults. - * Verifies that generateExportData is called with 'daily' scope + * Verifies that handleExportCSV is called with correct parameters * and modal closes after export completes. */ const user = userEvent.setup(); - const { generateExportData } = await import("./utils"); + const { handleExportCSV } = await import("./utils"); const { getByRole } = render(); @@ -80,10 +82,8 @@ describe("EntityUsageExportModal", () => { // Click export await user.click(getByRole("button", { name: /Export CSV/i })); - // Verifies export pipeline was invoked with default scope 'daily' - expect(generateExportData).toHaveBeenCalled(); - const callArgs = (generateExportData as any).mock.calls[0]; - expect(callArgs[1]).toBe("daily"); + // Verifies export function was invoked with correct parameters + expect(handleExportCSV).toHaveBeenCalledWith(baseProps.spendData, "daily", "Tag", "tag"); // Modal closes after export expect(baseProps.onClose).toHaveBeenCalled(); @@ -92,11 +92,11 @@ describe("EntityUsageExportModal", () => { it("exports with 'day-by-day by tag and model' scope when selected", async () => { /** * Tests that user can change export type (scope). - * Verifies generateExportData receives 'daily_with_models' scope + * Verifies handleExportCSV receives 'daily_with_models' scope * when the second radio option is selected. */ const user = userEvent.setup(); - const { generateExportData } = await import("./utils"); + const { handleExportCSV } = await import("./utils"); const { getByText, getByRole } = render(); @@ -109,9 +109,7 @@ describe("EntityUsageExportModal", () => { await user.click(exportBtn); // Ensure the selected scope flowed through - expect(generateExportData).toHaveBeenCalled(); - const callArgs = (generateExportData as any).mock.calls.at(-1); - expect(callArgs[1]).toBe("daily_with_models"); + expect(handleExportCSV).toHaveBeenCalledWith(baseProps.spendData, "daily_with_models", "Tag", "tag"); // Modal closes after export expect(baseProps.onClose).toHaveBeenCalled(); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx index 672643f2ad9..5b40d761983 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/EntityUsageExportModal.tsx @@ -1,12 +1,11 @@ import React, { useState } from "react"; import { Button } from "@tremor/react"; import { Modal } from "antd"; -import Papa from "papaparse"; import NotificationsManager from "../molecules/notifications_manager"; import ExportSummary from "./ExportSummary"; import ExportTypeSelector from "./ExportTypeSelector"; import ExportFormatSelector from "./ExportFormatSelector"; -import { generateExportData, generateMetadata } from "./utils"; +import { handleExportCSV, handleExportJSON } from "./utils"; import type { EntityUsageExportModalProps, ExportFormat, ExportScope } from "./types"; const EntityUsageExportModal: React.FC = ({ @@ -25,50 +24,15 @@ const EntityUsageExportModal: React.FC = ({ const entityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1); const modalTitle = customTitle || `Export ${entityLabel} Usage`; - const handleExportCSV = () => { - const data = generateExportData(spendData, exportScope, entityLabel); - const csv = Papa.unparse(data); - const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.csv`; - a.download = fileName; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }; - - const handleExportJSON = () => { - const data = generateExportData(spendData, exportScope, entityLabel); - const metadata = generateMetadata(entityType, dateRange, selectedFilters, exportScope, spendData); - const exportObject = { - metadata, - data, - }; - const jsonString = JSON.stringify(exportObject, null, 2); - const blob = new Blob([jsonString], { type: "application/json" }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.json`; - a.download = fileName; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }; - const handleExport = async (format?: ExportFormat) => { const formatToUse = format || exportFormat; setIsExporting(true); try { if (formatToUse === "csv") { - handleExportCSV(); + handleExportCSV(spendData, exportScope, entityLabel, entityType); NotificationsManager.success(`${entityLabel} usage data exported successfully as CSV`); } else { - handleExportJSON(); + handleExportJSON(spendData, exportScope, entityLabel, entityType, dateRange, selectedFilters); NotificationsManager.success(`${entityLabel} usage data exported successfully as JSON`); } onClose(); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index 87ca860657f..1327e158a6e 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -1,5 +1,7 @@ import { formatNumberWithCommas } from "@/utils/dataUtils"; +import Papa from "papaparse"; import type { EntitySpendData, EntityBreakdown, ExportMetadata, ExportScope } from "./types"; +import type { DateRangePickerValue } from "@tremor/react"; export const getEntityBreakdown = (spendData: EntitySpendData): EntityBreakdown[] => { const entitySpend: { [key: string]: EntityBreakdown } = {}; @@ -138,7 +140,7 @@ export const generateExportData = ( export const generateMetadata = ( entityType: "tag" | "team" | "organization", - dateRange: { from?: Date; to?: Date }, + dateRange: DateRangePickerValue, selectedFilters: string[], exportScope: ExportScope, spendData: EntitySpendData, @@ -159,3 +161,50 @@ export const generateMetadata = ( total_tokens: spendData.metadata.total_tokens, }, }); + +export const handleExportCSV = ( + spendData: EntitySpendData, + exportScope: ExportScope, + entityLabel: string, + entityType: "tag" | "team" | "organization", +): void => { + const data = generateExportData(spendData, exportScope, entityLabel); + const csv = Papa.unparse(data); + const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.csv`; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); +}; + +export const handleExportJSON = ( + spendData: EntitySpendData, + exportScope: ExportScope, + entityLabel: string, + entityType: "tag" | "team" | "organization", + dateRange: DateRangePickerValue, + selectedFilters: string[], +): void => { + const data = generateExportData(spendData, exportScope, entityLabel); + const metadata = generateMetadata(entityType, dateRange, selectedFilters, exportScope, spendData); + const exportObject = { + metadata, + data, + }; + const jsonString = JSON.stringify(exportObject, null, 2); + const blob = new Blob([jsonString], { type: "application/json" }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.json`; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); +}; diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx index e3b3926b994..3365e866aa9 100644 --- a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx @@ -82,31 +82,31 @@ export const QuickActions: React.FC = ({ onSelectAll, onUnsel
From 6bbc17771ff98ae8188292bedeab5004d7ac47c5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 16:44:28 -0800 Subject: [PATCH 192/349] Fix fallbacks immediately deleting before api resolves --- .../src/components/fallbacks.test.tsx | 76 +++++++------------ .../src/components/fallbacks.tsx | 44 ++++++----- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/ui/litellm-dashboard/src/components/fallbacks.test.tsx b/ui/litellm-dashboard/src/components/fallbacks.test.tsx index 45aa06dbaf1..e6db11270cd 100644 --- a/ui/litellm-dashboard/src/components/fallbacks.test.tsx +++ b/ui/litellm-dashboard/src/components/fallbacks.test.tsx @@ -1,5 +1,5 @@ -import { render, waitFor } from "@testing-library/react"; -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import Fallbacks from "./fallbacks"; import { getCallbacksCall, setCallbacksCall } from "./networking"; @@ -8,17 +8,6 @@ vi.mock("./networking", () => ({ setCallbacksCall: vi.fn(), })); -vi.mock("./molecules/notifications_manager", () => ({ - __esModule: true, - default: { - success: vi.fn(), - fromBackend: vi.fn(), - info: vi.fn(), - warning: vi.fn(), - clear: vi.fn(), - }, -})); - vi.mock("./add_fallbacks", () => ({ __esModule: true, default: () =>
Mock Add Fallbacks
, @@ -29,38 +18,15 @@ vi.mock("openai", () => ({ OpenAI: vi.fn().mockImplementation(() => ({ chat: { completions: { - create: vi.fn(), + create: vi.fn().mockResolvedValue({ + model: "test-model", + }), }, }, })), }, })); -// Polyfill ResizeObserver for components relying on it in tests -if (typeof window !== "undefined" && !window.ResizeObserver) { - window.ResizeObserver = class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} - }; -} - -beforeAll(() => { - Object.defineProperty(window, "matchMedia", { - writable: true, - value: vi.fn().mockImplementation((query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), - removeListener: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), - }); -}); - describe("Fallbacks", () => { const defaultProps = { accessToken: "token", @@ -78,15 +44,14 @@ describe("Fallbacks", () => { fallbacks: [], }, }); + mockSetCallbacksCall.mockResolvedValue({}); }); - it("should render an empty table with headers when access token is provided", async () => { - const { getByText } = render(); + it("should render", async () => { + render(); await waitFor(() => { - expect(getByText("Model Name")).toBeInTheDocument(); - expect(getByText("Fallbacks")).toBeInTheDocument(); - expect(getByText("Actions")).toBeInTheDocument(); + expect(screen.getByRole("columnheader", { name: "Model Name" })).toBeInTheDocument(); }); }); @@ -99,13 +64,13 @@ describe("Fallbacks", () => { mockGetCallbacksCall.mockResolvedValue(mockFallbackData); - const { getByText } = render(); + render(); await waitFor(() => { - expect(getByText("xai/grok-2")).toBeInTheDocument(); - expect(getByText("xai/grok-4, gpt-4")).toBeInTheDocument(); - expect(getByText("gpt-3.5-turbo")).toBeInTheDocument(); - expect(getByText("gpt-4")).toBeInTheDocument(); + expect(screen.getByText("xai/grok-2")).toBeInTheDocument(); + expect(screen.getByText("xai/grok-4, gpt-4")).toBeInTheDocument(); + expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument(); + expect(screen.getByText("gpt-4")).toBeInTheDocument(); }); expect(mockGetCallbacksCall).toHaveBeenCalledWith( @@ -114,4 +79,17 @@ describe("Fallbacks", () => { defaultProps.userRole, ); }); + + it("should render AddFallbacks component", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText("Mock Add Fallbacks")).toBeInTheDocument(); + }); + }); + + it("should not render when access token is not provided", () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); }); diff --git a/ui/litellm-dashboard/src/components/fallbacks.tsx b/ui/litellm-dashboard/src/components/fallbacks.tsx index cacf3869b13..dc90ae79136 100644 --- a/ui/litellm-dashboard/src/components/fallbacks.tsx +++ b/ui/litellm-dashboard/src/components/fallbacks.tsx @@ -1,9 +1,10 @@ import { PlayIcon, TrashIcon } from "@heroicons/react/outline"; import { Icon, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "@tremor/react"; -import { Modal, Tooltip } from "antd"; +import { Tooltip } from "antd"; import openai from "openai"; import React, { useEffect, useState } from "react"; import AddFallbacks from "./add_fallbacks"; +import DeleteResourceModal from "./common_components/DeleteResourceModal"; import NotificationsManager from "./molecules/notifications_manager"; import { getCallbacksCall, setCallbacksCall } from "./networking"; @@ -68,6 +69,7 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({}); const [isDeleting, setIsDeleting] = useState(false); const [fallbackToDelete, setFallbackToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); useEffect(() => { if (!accessToken || !userRole || !userID) { @@ -85,6 +87,7 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const handleDeleteClick = (fallbackEntry: FallbackEntry) => { setFallbackToDelete(fallbackEntry); + setIsDeleteModalOpen(true); }; const handleDeleteConfirm = async () => { @@ -100,10 +103,11 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const updatedFallbacks = routerSettings["fallbacks"] .map((dict: FallbackEntry) => { - if (key in dict && Array.isArray(dict[key])) { - delete dict[key]; + const newDict = { ...dict }; + if (key in newDict && Array.isArray(newDict[key])) { + delete newDict[key]; } - return dict; + return newDict; }) .filter((dict: FallbackEntry) => Object.keys(dict).length > 0); @@ -124,11 +128,13 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo NotificationsManager.fromBackend("Failed to update router settings: " + error); } finally { setIsDeleting(false); + setIsDeleteModalOpen(false); setFallbackToDelete(null); } }; const handleDeleteCancel = () => { + setIsDeleteModalOpen(false); setFallbackToDelete(null); }; @@ -183,20 +189,22 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo )}
- {fallbackToDelete && ( - -

Are you sure you want to delete fallback: {Object.keys(fallbackToDelete)[0]} ?

-

This action cannot be undone.

-
- )} + ); }; From fb5429bfe7a14c0eb4ae9657e4eb7e00a4ca4250 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 17:21:51 -0800 Subject: [PATCH 193/349] Remove Feature Flags --- .../components/SidebarProvider.tsx | 11 +- ui/litellm-dashboard/src/app/layout.tsx | 8 +- ui/litellm-dashboard/src/app/page.tsx | 2 - .../src/components/navbar.tsx | 16 +- .../src/components/public_model_hub.test.tsx | 7 +- .../src/hooks/useFeatureFlags.test.tsx | 296 ------------------ .../src/hooks/useFeatureFlags.tsx | 137 -------- 7 files changed, 6 insertions(+), 471 deletions(-) delete mode 100644 ui/litellm-dashboard/src/hooks/useFeatureFlags.test.tsx delete mode 100644 ui/litellm-dashboard/src/hooks/useFeatureFlags.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx index cb7f0f1a32c..c522d4ce1e5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx @@ -1,21 +1,16 @@ -import useFeatureFlags from "@/hooks/useFeatureFlags"; -import Sidebar from "@/components/leftnav"; -import Sidebar2 from "@/app/(dashboard)/components/Sidebar2"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import Sidebar from "@/components/leftnav"; interface SidebarProviderProps { + setPage: (page: string) => void; defaultSelectedKey: string; - setPage: (newPage: string) => void; sidebarCollapsed: boolean; } const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: SidebarProviderProps) => { - const { refactoredUIFlag } = useFeatureFlags(); const { accessToken, userRole } = useAuthorized(); - return refactoredUIFlag ? ( - - ) : ( + return ( ) { return ( - - - - {children} - - + {children} ); } diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 56c35804df9..422d140e07c 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -40,7 +40,6 @@ import UIThemeSettings from "@/components/ui_theme_settings"; import { CostTrackingSettings } from "@/components/CostTrackingSettings"; import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { cx } from "@/lib/cva.config"; -import useFeatureFlags from "@/hooks/useFeatureFlags"; import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider"; import OldTeams from "@/components/OldTeams"; import { SearchTools } from "@/components/search_tools"; @@ -147,7 +146,6 @@ export default function CreateKeyPage() { const [createClicked, setCreateClicked] = useState(false); const [authLoading, setAuthLoading] = useState(true); const [userID, setUserID] = useState(null); - const { refactoredUIFlag } = useFeatureFlags(); const invitation_id = searchParams.get("invitation_id"); diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index 667691168ca..9113a0f54c0 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import React, { useState, useEffect } from "react"; import type { MenuProps } from "antd"; -import { Dropdown, Tooltip, Switch } from "antd"; +import { Dropdown, Tooltip } from "antd"; import { getProxyBaseUrl } from "@/components/networking"; import { UserOutlined, @@ -15,7 +15,6 @@ import { import { clearTokenCookies } from "@/utils/cookieUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; import { useTheme } from "@/contexts/ThemeContext"; -import useFeatureFlags from "@/hooks/useFeatureFlags"; interface NavbarProps { userID: string | null; @@ -45,7 +44,6 @@ const Navbar: React.FC = ({ const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); const { logoUrl } = useTheme(); - const { refactoredUIFlag, setRefactoredUIFlag } = useFeatureFlags(); // Simple logo URL: use custom logo if available, otherwise default const imageUrl = logoUrl || `${baseUrl}/get_image`; @@ -114,18 +112,6 @@ const Navbar: React.FC = ({ {userEmail || "Unknown"}
- - {/* NEW: Feature flag label + toggle below the email field */} -
- Refactored UI - setRefactoredUIFlag(checked)} - aria-label="Toggle refactored UI feature flag" - /> -
), diff --git a/ui/litellm-dashboard/src/components/public_model_hub.test.tsx b/ui/litellm-dashboard/src/components/public_model_hub.test.tsx index 8ff57b49e08..47a44fa5147 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.test.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.test.tsx @@ -1,7 +1,6 @@ import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; import { render } from "@testing-library/react"; import PublicModelHub from "./public_model_hub"; -import { FeatureFlagsProvider } from "@/hooks/useFeatureFlags"; vi.mock("next/navigation", () => ({ useRouter: vi.fn(() => ({ @@ -58,11 +57,7 @@ beforeEach(() => { describe("PublicModelHub", () => { it("renders", () => { - const { container } = render( - - - , - ); + const { container } = render(); expect(container).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/hooks/useFeatureFlags.test.tsx b/ui/litellm-dashboard/src/hooks/useFeatureFlags.test.tsx deleted file mode 100644 index ca0529b0f28..00000000000 --- a/ui/litellm-dashboard/src/hooks/useFeatureFlags.test.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { renderHook, waitFor } from "@testing-library/react"; -import { useRouter } from "next/navigation"; -import useFeatureFlags, { FeatureFlagsProvider } from "./useFeatureFlags"; - -// Mock next/navigation -vi.mock("next/navigation", () => ({ - useRouter: vi.fn(), -})); - -// Mock the networking module to control serverRootPath -vi.mock("@/components/networking", () => ({ - serverRootPath: "/", -})); - -describe("useFeatureFlags", () => { - let mockReplace: ReturnType; - let originalLocation: Location; - - beforeEach(() => { - // Mock router - mockReplace = vi.fn(); - (useRouter as ReturnType).mockReturnValue({ - replace: mockReplace, - }); - - // Store original location - originalLocation = window.location; - - // Mock localStorage - Storage.prototype.getItem = vi.fn(() => null); - Storage.prototype.setItem = vi.fn(); - Storage.prototype.removeItem = vi.fn(); - }); - - afterEach(() => { - vi.clearAllMocks(); - // Restore location - Object.defineProperty(window, "location", { - writable: true, - value: originalLocation, - }); - }); - - describe("FeatureFlagsProvider - redirect logic", () => { - it("should not redirect when refactoredUIFlag is true", async () => { - // Set flag to true - Storage.prototype.getItem = vi.fn(() => "true"); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - expect(result.current.refactoredUIFlag).toBe(true); - - // Wait for any effects - await waitFor(() => { - expect(mockReplace).not.toHaveBeenCalled(); - }); - }); - - it("should not redirect when already on a /ui path (race condition protection)", async () => { - // Set flag to false to trigger redirect logic - Storage.prototype.getItem = vi.fn(() => "false"); - - // Mock window.location to be on a custom UI path - delete (window as any).location; - window.location = { - pathname: "/my-custom-path/ui/", - } as Location; - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Wait for timeout and check redirect was NOT called - await new Promise((resolve) => setTimeout(resolve, 150)); - - expect(mockReplace).not.toHaveBeenCalled(); - }); - - it("should not redirect when on /ui path without custom root", async () => { - // Set flag to false - Storage.prototype.getItem = vi.fn(() => "false"); - - // Mock window.location to be on standard UI path - delete (window as any).location; - window.location = { - pathname: "/ui/", - } as Location; - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Wait for timeout and check redirect was NOT called - await new Promise((resolve) => setTimeout(resolve, 150)); - - expect(mockReplace).not.toHaveBeenCalled(); - }); - - it("should redirect when flag is false and not on a /ui path", async () => { - // Set flag to false - Storage.prototype.getItem = vi.fn(() => "false"); - - // Mock window.location to be on a non-UI path - delete (window as any).location; - window.location = { - pathname: "/some-other-path/", - } as Location; - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Wait for timeout plus a bit more - await new Promise((resolve) => setTimeout(resolve, 150)); - - // Should have called replace to redirect to base path - expect(mockReplace).toHaveBeenCalledWith("/"); - }); - - it("should not redirect if already at base path", async () => { - // Set flag to false - Storage.prototype.getItem = vi.fn(() => "false"); - - // Mock window.location to be at root - delete (window as any).location; - window.location = { - pathname: "/", - } as Location; - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Wait for timeout - await new Promise((resolve) => setTimeout(resolve, 150)); - - expect(mockReplace).not.toHaveBeenCalled(); - }); - }); - - describe("useFeatureFlags - flag management", () => { - it("should initialize with false when no value in localStorage", () => { - Storage.prototype.getItem = vi.fn(() => null); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - expect(result.current.refactoredUIFlag).toBe(false); - }); - - it("should initialize with true when localStorage has true", () => { - Storage.prototype.getItem = vi.fn(() => "true"); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - expect(result.current.refactoredUIFlag).toBe(true); - }); - - it("should update localStorage when setRefactoredUIFlag is called", () => { - const setItemMock = vi.fn(); - Storage.prototype.setItem = setItemMock; - Storage.prototype.getItem = vi.fn(() => "false"); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - result.current.setRefactoredUIFlag(true); - - expect(setItemMock).toHaveBeenCalledWith( - "feature.refactoredUIFlag", - "true" - ); - }); - - it("should handle malformed localStorage values gracefully", () => { - Storage.prototype.getItem = vi.fn(() => "invalid-value"); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Should default to false for malformed values - expect(result.current.refactoredUIFlag).toBe(false); - }); - }); - - describe("getBasePath logic with serverRootPath", () => { - it("should handle serverRootPath being set to custom path", async () => { - // Mock the networking module with custom serverRootPath - vi.doMock("@/components/networking", () => ({ - serverRootPath: "/my-custom-path", - })); - - // Set flag to false to trigger redirect - Storage.prototype.getItem = vi.fn(() => "false"); - - // Mock location to be on wrong path - delete (window as any).location; - window.location = { - pathname: "/wrong-path/", - } as Location; - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Wait for timeout - await new Promise((resolve) => setTimeout(resolve, 150)); - - // With default NEXT_PUBLIC_BASE_URL being empty, should redirect to "/" - // (In reality, with serverRootPath="/my-custom-path", it would be "/my-custom-path/") - expect(mockReplace).toHaveBeenCalled(); - }); - }); - - describe("storage event synchronization", () => { - it("should update flag when storage event is fired", async () => { - Storage.prototype.getItem = vi.fn(() => "false"); - - const { result } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - expect(result.current.refactoredUIFlag).toBe(false); - - // Simulate storage event from another tab - const storageEvent = new StorageEvent("storage", { - key: "feature.refactoredUIFlag", - newValue: "true", - }); - - window.dispatchEvent(storageEvent); - - await waitFor(() => { - expect(result.current.refactoredUIFlag).toBe(true); - }); - }); - - it("should self-heal when storage key is cleared", async () => { - const setItemMock = vi.fn(); - Storage.prototype.setItem = setItemMock; - Storage.prototype.getItem = vi.fn(() => "true"); - - renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Simulate storage event where key was cleared - const storageEvent = new StorageEvent("storage", { - key: "feature.refactoredUIFlag", - newValue: null, - }); - - window.dispatchEvent(storageEvent); - - await waitFor(() => { - expect(setItemMock).toHaveBeenCalledWith( - "feature.refactoredUIFlag", - "false" - ); - }); - }); - }); - - describe("timeout cleanup", () => { - it("should cleanup timeout on unmount", async () => { - Storage.prototype.getItem = vi.fn(() => "false"); - - delete (window as any).location; - window.location = { - pathname: "/some-path/", - } as Location; - - const { unmount } = renderHook(() => useFeatureFlags(), { - wrapper: FeatureFlagsProvider, - }); - - // Unmount immediately before timeout fires - unmount(); - - // Wait past the timeout - await new Promise((resolve) => setTimeout(resolve, 150)); - - // Should not have called replace since component unmounted - expect(mockReplace).not.toHaveBeenCalled(); - }); - }); -}); - diff --git a/ui/litellm-dashboard/src/hooks/useFeatureFlags.tsx b/ui/litellm-dashboard/src/hooks/useFeatureFlags.tsx deleted file mode 100644 index 03b4465b096..00000000000 --- a/ui/litellm-dashboard/src/hooks/useFeatureFlags.tsx +++ /dev/null @@ -1,137 +0,0 @@ -"use client"; - -import React, { createContext, useContext, useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import { serverRootPath } from "@/components/networking"; - -const getBasePath = () => { - const raw = process.env.NEXT_PUBLIC_BASE_URL ?? ""; - const trimmed = raw.replace(/^\/+|\/+$/g, ""); // strip leading/trailing slashes - const uiPath = trimmed ? `/${trimmed}/` : "/"; - - // If serverRootPath is set and not "/", prepend it to the UI path - if (serverRootPath && serverRootPath !== "/") { - // Remove trailing slash from serverRootPath and ensure uiPath has no leading slash for proper joining - const cleanServerRoot = serverRootPath.replace(/\/+$/, ""); - const cleanUiPath = uiPath.replace(/^\/+/, ""); - return `${cleanServerRoot}/${cleanUiPath}`; - } - - return uiPath; -} - -type Flags = { - refactoredUIFlag: boolean; - setRefactoredUIFlag: (v: boolean) => void; -}; - -const STORAGE_KEY = "feature.refactoredUIFlag"; - -const FeatureFlagsCtx = createContext(null); - -/** Safely read the flag from localStorage. If anything goes wrong, reset to false. */ -function readFlagSafely(): boolean { - try { - const raw = localStorage.getItem(STORAGE_KEY); - if (raw === null) { - localStorage.setItem(STORAGE_KEY, "false"); - return false; - } - - const v = raw.trim().toLowerCase(); - if (v === "true" || v === "1") return true; - if (v === "false" || v === "0") return false; - - // Last chance: try JSON.parse in case something odd was stored. - const parsed = JSON.parse(raw); - if (typeof parsed === "boolean") return parsed; - - // Malformed → reset to false - localStorage.setItem(STORAGE_KEY, "false"); - return false; - } catch { - // If even accessing localStorage throws, best effort reset then default to false - try { - localStorage.setItem(STORAGE_KEY, "false"); - } catch {} - return false; - } -} - -function writeFlagSafely(v: boolean) { - try { - localStorage.setItem(STORAGE_KEY, String(v)); - } catch { - // Ignore write errors; state will still reflect the intended value. - } -} - -export const FeatureFlagsProvider = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); // ⟵ add this - - // Lazy init reads from localStorage only on the client - const [refactoredUIFlag, setRefactoredUIFlagState] = useState(() => readFlagSafely()); - - const setRefactoredUIFlag = (v: boolean) => { - setRefactoredUIFlagState(v); - writeFlagSafely(v); - }; - - // Keep this flag in sync across tabs/windows. - useEffect(() => { - const onStorage = (e: StorageEvent) => { - if (e.key === STORAGE_KEY && e.newValue != null) { - const next = e.newValue.trim().toLowerCase(); - setRefactoredUIFlagState(next === "true" || next === "1"); - } - // If the key was cleared elsewhere, self-heal to false. - if (e.key === STORAGE_KEY && e.newValue === null) { - writeFlagSafely(false); - setRefactoredUIFlagState(false); - } - }; - window.addEventListener("storage", onStorage); - return () => window.removeEventListener("storage", onStorage); - }, []); - - // Redirect to base path the moment the flag is OFF. - useEffect(() => { - if (refactoredUIFlag) return; // only act when turned off - - // Wait a moment for serverRootPath to be initialized from getUiConfig() - // This prevents a race condition where we redirect before knowing the correct path - const checkAndRedirect = () => { - const base = getBasePath(); - const normalize = (p: string) => (p.endsWith("/") ? p : p + "/"); - const current = normalize(window.location.pathname); - - // Don't redirect if we're already on a UI path (even if serverRootPath hasn't loaded yet) - // This handles the case where the page is mounted at a custom server root path - if (current.includes("/ui")) { - return; - } - - // Avoid a redirect loop if we're already at the base path. - if (current !== base) { - // Replace so the "off" redirect doesn't pollute history. - router.replace(base); - } - }; - - // Small delay to allow serverRootPath to be set by getUiConfig() - const timeoutId = setTimeout(checkAndRedirect, 100); - return () => clearTimeout(timeoutId); - }, [refactoredUIFlag, router]); - - return ( - {children} - ); -}; - -const useFeatureFlags = () => { - const ctx = useContext(FeatureFlagsCtx); - if (!ctx) throw new Error("useFeatureFlags must be used within FeatureFlagsProvider"); - return ctx; -}; - -export default useFeatureFlags; From 0d329826f10da90a6b8205ac58ce0344ef82ee9c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 17:40:38 -0800 Subject: [PATCH 194/349] Fix flaky tests --- .../src/components/entity_usage.test.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/entity_usage.test.tsx b/ui/litellm-dashboard/src/components/entity_usage.test.tsx index d5cc503fd10..17016b6479d 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.test.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.test.tsx @@ -168,16 +168,18 @@ describe("EntityUsage", () => { }); it("should render with organization entity type and call organization API", async () => { - const { getByText, getAllByText } = render(); + render(); await waitFor(() => { expect(mockOrganizationDailyActivityCall).toHaveBeenCalled(); }); - expect(getByText("Organization Spend Overview")).toBeInTheDocument(); + expect(screen.getByText("Organization Spend Overview")).toBeInTheDocument(); - const spendElements = getAllByText("$100.50"); - expect(spendElements.length).toBeGreaterThan(0); + await waitFor(() => { + const spendElements = screen.getAllByText("$100.50"); + expect(spendElements.length).toBeGreaterThan(0); + }); }); it("should switch between tabs", async () => { From a33a2cb5b54d98dc061406cbec62840274e7a811 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 27 Nov 2025 17:53:09 -0800 Subject: [PATCH 195/349] Adding timeout to flaky test --- .../e2e_ui_tests/view_user_info.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 5b9a9ab133c..01eadc9ad1a 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -29,8 +29,12 @@ test.describe("User Info View", () => { await firstUserIdCell.click(); // Check for tabs - await expect(page.locator('button:has-text("Overview")')).toBeVisible(); - await expect(page.locator('button:has-text("Details")')).toBeVisible(); + await expect(page.locator('button:has-text("Overview")')).toBeVisible({ + timeout: 10000, + }); + await expect(page.locator('button:has-text("Details")')).toBeVisible({ + timeout: 10000, + }); // Switch to details tab await page.locator('button:has-text("Details")').click(); From d43c0776534251213c47323e5abfcc9b2a645a60 Mon Sep 17 00:00:00 2001 From: Wei-Chiet Ku Date: Fri, 28 Nov 2025 13:24:04 +0800 Subject: [PATCH 196/349] Fix/issue 16759 streaming error validation (#17242) * Enhance error handling in OpenAIResponsesAPIConfig to coalesce null error codes into a default string, preventing validation errors and improving stability during streaming iterations. * Add test for coalescing null error codes in streaming responses This test ensures that when a streaming error event has error.code set to None, the system correctly transforms it to 'unknown_error' and returns an ErrorEvent instance without raising a ValidationError. --------- Co-authored-by: Ku Wei Chiet --- .../llms/openai/responses/transformation.py | 18 +++++++++++++ .../test_openai_responses_transformation.py | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index f75213b0688..4c9d3828383 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -238,6 +238,24 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): event_pydantic_model = OpenAIResponsesAPIConfig.get_event_model_class( event_type=event_type ) + # Defensive: Some OpenAI-compatible providers may send `error.code: null`. + # Pydantic will raise a ValidationError when it expects a string but gets None. + # Coalesce a None `error.code` to a stable default string so streaming + # iteration does not crash (see issue report). This keeps behavior similar + # to previous fixes (coalesce before validation) and lets higher-level + # handlers still receive an `ErrorEvent` object. + try: + error_obj = parsed_chunk.get("error") + if isinstance(error_obj, dict) and error_obj.get("code") is None: + # Preserve other fields, but ensure `code` is a non-null string + parsed_chunk = dict(parsed_chunk) + parsed_chunk["error"] = dict(error_obj) + parsed_chunk["error"]["code"] = "unknown_error" + except Exception: + # If anything unexpected happens here, fall back to attempting + # instantiation and let higher-level handlers manage errors. + verbose_logger.debug("Failed to coalesce error.code in parsed_chunk") + return event_pydantic_model(**parsed_chunk) @staticmethod diff --git a/tests/test_litellm/llms/openai/responses/test_openai_responses_transformation.py b/tests/test_litellm/llms/openai/responses/test_openai_responses_transformation.py index fa5231a2a2f..074378fd562 100644 --- a/tests/test_litellm/llms/openai/responses/test_openai_responses_transformation.py +++ b/tests/test_litellm/llms/openai/responses/test_openai_responses_transformation.py @@ -390,6 +390,33 @@ class TestOpenAIResponsesAPIConfig: assert result["partial_images"] == partial_images_value assert result["stream"] is True + def test_transform_streaming_response_coalesces_null_error_code(self): + """Ensure that when a streaming error event contains error.code=None, + transform_streaming_response coalesces it to 'unknown_error' and returns + an ErrorEvent instance without raising a ValidationError. + """ + from litellm.types.llms.openai import ErrorEvent + + parsed_chunk = { + "type": "error", + "sequence_number": 1, + "error": { + "type": "invalid_request_error", + "code": None, + "message": "Something went wrong", + "param": None, + }, + } + + event = self.config.transform_streaming_response( + model=self.model, parsed_chunk=parsed_chunk, logging_obj=self.logging_obj + ) + + # Validate returned type and coalesced code + assert isinstance(event, ErrorEvent) + assert event.error.code == "unknown_error" + assert event.error.message == "Something went wrong" + class TestAzureResponsesAPIConfig: def setup_method(self): From 334d09b3b21c728e6f7152d1804cb8b5aac643ef Mon Sep 17 00:00:00 2001 From: YutaSaito <36355491+uc4w6c@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:26:27 +0900 Subject: [PATCH 197/349] feat: add regex-based tool_name/tool_type matching for tool-permission (#17164) * feat: add regex-based tool_name/tool_type matching for tool-permission * docs: update tool permission quick start for UI workflow --- .../docs/proxy/guardrails/tool_permission.md | 45 +++++- .../guardrail_hooks/tool_permission.py | 139 +++++++++++------ .../guardrail_hooks/tool_permission.py | 33 +++- .../guardrail_hooks/test_tool_permission.py | 144 ++++++++++++------ .../ToolPermissionRulesEditor.tsx | 36 ++++- 5 files changed, 286 insertions(+), 111 deletions(-) diff --git a/docs/my-website/docs/proxy/guardrails/tool_permission.md b/docs/my-website/docs/proxy/guardrails/tool_permission.md index 19b674c9e55..897c31d9dab 100644 --- a/docs/my-website/docs/proxy/guardrails/tool_permission.md +++ b/docs/my-website/docs/proxy/guardrails/tool_permission.md @@ -7,9 +7,38 @@ import TabItem from '@theme/TabItem'; LiteLLM provides the LiteLLM Tool Permission Guardrail that lets you control which **tool calls** a model is allowed to invoke, using configurable allow/deny rules. This offers fine-grained, provider-agnostic control over tool execution (e.g., OpenAI Chat Completions `tool_calls`, Anthropic Messages `tool_use`, MCP tools). ## Quick Start -### 1. Define Guardrails on your LiteLLM config.yaml -Define your guardrails under the `guardrails` section +### LiteLLM UI + +#### Step 1: Select Tool Permission Guardrail + +Open the LiteLLM Dashboard, click **Add New Guardrail**, and choose **LiteLLM Tool Permission Guardrail**. This loads the rule builder UI. + +Configure tool permission guardrail in LiteLLM UI + +#### Step 2: Define Regex Rules + +1. Click **Add Rule**. +2. Enter a unique Rule ID. +3. Provide a regex for the tool name (e.g., `^mcp__github_.*$`). +4. Optionally add a regex for tool type (e.g., `^function$`). +5. Pick **Allow** or **Deny**. + +Configure tool permission guardrail in LiteLLM UI + +#### Step 3: Restrict Tool Arguments (Optional) + +Select **+ Restrict tool arguments** to attach regex validations to nested paths (dot + `[]` notation). This enforces that sensitive parameters (such as `arguments.to[]`) conform to pre-approved formats. + +#### Step 4: Choose Defaults & Actions + +- Set the fallback decision (`default_action`) for tools that do not hit any rule. +- Decide how disallowed tools behave: **Block** halts the request, **Rewrite** strips forbidden tools and returns an error message inside the response. +- Customize `violation_message_template` if you want branded error copy. +- Save the guardrail. + +### LiteLLM Config.yaml Setup + ```yaml guardrails: - guardrail_name: "tool-permission-guardrail" @@ -21,16 +50,17 @@ guardrails: tool_name: "Bash" decision: "allow" - id: "allow_github_mcp" - tool_name: "mcp__github_*" + tool_name: "^mcp__github_.*$" decision: "allow" - id: "allow_aws_documentation" - tool_name: "mcp__aws-documentation_*_documentation" + tool_name: "^mcp__aws-documentation_.*_documentation$" decision: "allow" - id: "deny_read_commands" tool_name: "Read" - decision: "Deny" + decision: "deny" - id: "mail-domain" - tool_name: "send_email" + tool_name: "^send_email$" + tool_type: "^function$" decision: "allow" allowed_param_patterns: "to[]": "^.+@berri\\.ai$" @@ -44,7 +74,8 @@ guardrails: ```yaml - id: "unique_rule_id" # Unique identifier for the rule - tool_name: "pattern" # Tool name or pattern to match + tool_name: "^regex$" # Regex for tool name (optional, at least one of name/type required) + tool_type: "^function$" # Regex for tool type (optional) decision: "allow" # "allow" or "deny" allowed_param_patterns: # Optional - regex map for argument paths (dot + [] notation) "path.to[].field": "^regex$" diff --git a/litellm/proxy/guardrails/guardrail_hooks/tool_permission.py b/litellm/proxy/guardrails/guardrail_hooks/tool_permission.py index 02e06acbda4..64753d9fa85 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/tool_permission.py +++ b/litellm/proxy/guardrails/guardrail_hooks/tool_permission.py @@ -62,6 +62,7 @@ class ToolPermissionGuardrail(CustomGuardrail): self.rules: List[ToolPermissionRule] = [] self._compiled_rule_patterns: Dict[str, Dict[str, re.Pattern]] = {} + self._compiled_rule_targets: Dict[str, Dict[str, Optional[re.Pattern]]] = {} if rules: for rule_item in rules: if isinstance(rule_item, ToolPermissionRule): @@ -70,6 +71,30 @@ class ToolPermissionGuardrail(CustomGuardrail): rule = ToolPermissionRule(**rule_item) self.rules.append(rule) + compiled_target_patterns: Dict[str, Optional[re.Pattern]] = { + "tool_name": None, + "tool_type": None, + } + if rule.tool_name is not None: + try: + compiled_target_patterns["tool_name"] = re.compile( + rule.tool_name + ) + except re.error as exc: + raise ValueError( + f"Invalid regex for tool_name in rule '{rule.id}': {exc}" + ) from exc + if rule.tool_type is not None: + try: + compiled_target_patterns["tool_type"] = re.compile( + rule.tool_type + ) + except re.error as exc: + raise ValueError( + f"Invalid regex for tool_type in rule '{rule.id}': {exc}" + ) from exc + self._compiled_rule_targets[rule.id] = compiled_target_patterns + if rule.allowed_param_patterns: compiled_patterns: Dict[str, re.Pattern] = {} for path, pattern in rule.allowed_param_patterns.items(): @@ -100,59 +125,75 @@ class ToolPermissionGuardrail(CustomGuardrail): return ToolPermissionGuardrailConfigModel - def _matches_pattern(self, tool_name: str, pattern: str) -> bool: - """ - Check if a tool name matches a pattern - - Supports patterns like: - - "Bash" - exact match - - "mcp__*" - prefix pattern (matches names starting wich "mcp__") - - "*_read" - suffix wildcard (matches names ending with "_read") - - "mcp__github_*_read" - infix wildcard (matches names like "mcp__github_mark_all_notifications_read") - - Args: - tool_name: Name of the tool to check - pattern: Pattern to match against - - Returns: - True if the tool name matches the pattern - """ - # Handle exact matches - if tool_name == pattern: + def _matches_regex( + self, pattern: Optional[re.Pattern], value: Optional[str] + ) -> bool: + if pattern is None: return True + if value is None: + return False + return bool(pattern.fullmatch(value)) - if "*" in pattern: - # Escape regex special chars except '*' - escaped_pattern = re.escape(pattern) - # Turn \* into .* - regex_pattern = escaped_pattern.replace(r"\*", ".*") - return bool(re.fullmatch(regex_pattern, tool_name)) + def _rule_matches_tool( + self, + rule: ToolPermissionRule, + *, + tool_name: Optional[str], + tool_type: Optional[str] = None, + ) -> tuple[bool, bool]: + target_patterns = self._compiled_rule_targets.get(rule.id, {}) + name_pattern = target_patterns.get("tool_name") + type_pattern = target_patterns.get("tool_type") - return False + name_required = rule.tool_name is not None + type_required = rule.tool_type is not None + + name_matched = ( + self._matches_regex(name_pattern, tool_name) if name_required else True + ) + type_matched = ( + self._matches_regex(type_pattern, tool_type) if type_required else True + ) + + overall_match = name_matched and type_matched + should_check_params = name_required and name_matched + + return overall_match, should_check_params def _check_tool_permission( - self, tool_name: str + self, + tool_name: Optional[str], + tool_type: Optional[str] = None, ) -> tuple[bool, Optional[str], Optional[str]]: """ Check if a tool is allowed based on the configured rules Args: tool_name: Name of the tool to check + tool_type: Type of the tool to check Returns: Tuple of (is_allowed, rule_id, message) """ - verbose_proxy_logger.debug(f"Checking permission for tool: {tool_name}") + verbose_proxy_logger.debug( + f"Checking permission for tool: {tool_name or tool_type}" + ) # Check each rule in order for rule in self.rules: - if self._matches_pattern(tool_name, rule.tool_name): + matches, _ = self._rule_matches_tool( + rule, + tool_name=tool_name, + tool_type=tool_type, + ) + if matches: is_allowed = rule.decision == "allow" - default_message = f"Tool '{tool_name}' {'allowed' if is_allowed else 'denied'} by rule '{rule.id}'" + tool_identifier = tool_name or tool_type or "unknown_tool" + default_message = f"Tool '{tool_identifier}' {'allowed' if is_allowed else 'denied'} by rule '{rule.id}'" message = self.render_violation_message( default=default_message, context={ - "tool_name": tool_name, + "tool_name": tool_name or tool_identifier, "rule_id": rule.id, }, ) @@ -161,11 +202,12 @@ class ToolPermissionGuardrail(CustomGuardrail): # No rule matched, use default action is_allowed = self.default_action == "allow" - default_message = f"Tool '{tool_name}' {'allowed' if is_allowed else 'denied'} by default action" + tool_identifier = tool_name or tool_type or "unknown_tool" + default_message = f"Tool '{tool_identifier}' {'allowed' if is_allowed else 'denied'} by default action" message = self.render_violation_message( default=default_message, context={ - "tool_name": tool_name, + "tool_name": tool_name or tool_identifier, "rule_id": None, }, ) @@ -228,7 +270,7 @@ class ToolPermissionGuardrail(CustomGuardrail): *, arguments: Dict[str, Any], rule: ToolPermissionRule, - tool_name: str, + tool_name: Optional[str], ) -> tuple[bool, Optional[str]]: compiled_patterns = self._compiled_rule_patterns.get(rule.id) if not compiled_patterns: @@ -249,7 +291,7 @@ class ToolPermissionGuardrail(CustomGuardrail): return ( False, f"Value '{raw_value}' for path '{path}' does not match allowed pattern" - f" '{compiled_pattern.pattern}' for tool '{tool_name}'", + f" '{compiled_pattern.pattern}' for tool '{tool_name or 'unknown_tool'}'", ) return True, None @@ -258,19 +300,27 @@ class ToolPermissionGuardrail(CustomGuardrail): self, tool_call: ChatCompletionMessageToolCall ) -> tuple[bool, Optional[str], Optional[str]]: tool_name = tool_call.function.name if tool_call.function else None - if not tool_name: + tool_type = getattr(tool_call, "type", None) + if not tool_name and not tool_type: return self.default_action == "allow", None, None + tool_identifier = tool_name or tool_type or "unknown_tool" + last_pattern_failure_msg: Optional[str] = None for rule in self.rules: - if not self._matches_pattern(tool_name, rule.tool_name): + matches, should_check_params = self._rule_matches_tool( + rule, + tool_name=tool_name, + tool_type=tool_type, + ) + if not matches: continue - if rule.allowed_param_patterns: + if rule.allowed_param_patterns and should_check_params: arguments = self._parse_tool_call_arguments(tool_call) if not arguments: - last_pattern_failure_msg = f"Tool '{tool_name}' is missing arguments required by rule '{rule.id}'" + last_pattern_failure_msg = f"Tool '{tool_identifier}' is missing arguments required by rule '{rule.id}'" continue patterns_match, failure_message = self._patterns_match_for_rule( @@ -283,10 +333,10 @@ class ToolPermissionGuardrail(CustomGuardrail): continue is_allowed = rule.decision == "allow" - default_message = f"Tool '{tool_name}' {'allowed' if is_allowed else 'denied'} by rule '{rule.id}'" + default_message = f"Tool '{tool_identifier}' {'allowed' if is_allowed else 'denied'} by rule '{rule.id}'" message = self.render_violation_message( default=default_message, - context={"tool_name": tool_name, "rule_id": rule.id}, + context={"tool_name": tool_identifier, "rule_id": rule.id}, ) return is_allowed, rule.id, message @@ -294,11 +344,11 @@ class ToolPermissionGuardrail(CustomGuardrail): default_message = ( last_pattern_failure_msg if (last_pattern_failure_msg and not is_allowed) - else f"Tool '{tool_name}' {'allowed' if is_allowed else 'denied'} by default action" + else f"Tool '{tool_identifier}' {'allowed' if is_allowed else 'denied'} by default action" ) message = self.render_violation_message( default=default_message, - context={"tool_name": tool_name, "rule_id": None}, + context={"tool_name": tool_identifier, "rule_id": None}, ) return is_allowed, None, message @@ -469,8 +519,9 @@ class ToolPermissionGuardrail(CustomGuardrail): if tool["type"] != "function": continue tool_name: str = tool["function"]["name"] + tool_type: Optional[str] = tool.get("type") - is_allowed, _, message = self._check_tool_permission(tool_name) + is_allowed, _, message = self._check_tool_permission(tool_name, tool_type) if not is_allowed and message is not None: verbose_proxy_logger.warning(f"Tool Permission Guardrail: {message}") diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/tool_permission.py b/litellm/types/proxy/guardrails/guardrail_hooks/tool_permission.py index e78cfad8bdb..2ed1f3d2e3a 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/tool_permission.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/tool_permission.py @@ -1,7 +1,7 @@ # Tool Permission Guardrail Type Definitions from typing import Dict, List, Literal, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator, model_validator from .base import GuardrailConfigModel @@ -12,8 +12,13 @@ class ToolPermissionRule(BaseModel): """ id: str = Field(description="Unique identifier for the rule") - tool_name: str = Field( - description="Tool name or pattern (e.g., 'Bash', 'mcp__github_*', 'mcp__github_*_read', '*_read')" + tool_name: Optional[str] = Field( + default=None, + description="Regex pattern applied to the tool's function name", + ) + tool_type: Optional[str] = Field( + default=None, + description="Regex pattern applied to the tool type (e.g., function)", ) decision: Literal["allow", "deny"] = Field( description="Whether to allow or deny this tool usage" @@ -23,6 +28,26 @@ class ToolPermissionRule(BaseModel): description="Optional regex map enforcing nested parameter values using dot/[] paths", ) + @field_validator("tool_name", "tool_type", mode="before") + @classmethod + def _blank_to_none(cls, value: Optional[str]) -> Optional[str]: + if value is None: + return None + if isinstance(value, str): + stripped = value.strip() + if not stripped: + return None + return stripped + return value + + @model_validator(mode="after") + def _ensure_target_present(self): + if self.tool_name is None and self.tool_type is None: + raise ValueError( + "Each rule must specify at least a tool_name or tool_type regex" + ) + return self + class ToolResult(BaseModel): """ @@ -52,7 +77,7 @@ class ToolPermissionGuardrailConfigModel(GuardrailConfigModel): rules: Optional[List[ToolPermissionRule]] = Field( default=None, - description="Ordered allow/deny rules. Patterns support * wildcards and optional regex constraints on tool arguments.", + description="Ordered allow/deny rules. Patterns use regex for tool names/types and optional regex constraints on tool arguments.", ) default_action: Literal["allow", "deny"] = Field( default="deny", description="Fallback decision when no rule matches" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_tool_permission.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_tool_permission.py index 5468dcf9491..a7fd1c64955 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_tool_permission.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_tool_permission.py @@ -3,6 +3,7 @@ Unit tests for Tool Permission Guardrail (OpenAI tool_calls semantics) """ import os +import re import sys from unittest.mock import patch @@ -36,15 +37,19 @@ class TestToolPermissionGuardrail: def setup_method(self): """Set up test fixtures""" self.test_rules = [ - {"id": "allow_bash", "tool_name": "Bash", "decision": "allow"}, - {"id": "allow_github", "tool_name": "mcp__github_*", "decision": "allow"}, + {"id": "allow_bash", "tool_name": r"^Bash$", "decision": "allow"}, { - "id": "allow_documentation", - "tool_name": "mcp__aws-documentation_*_documentation", + "id": "allow_github", + "tool_name": r"^mcp__github_.*$", "decision": "allow", }, - {"id": "deny_read", "tool_name": "Read", "decision": "deny"}, - {"id": "deny_get", "tool_name": "*_get", "decision": "deny"}, + { + "id": "allow_documentation", + "tool_name": r"^mcp__aws-documentation_.*_documentation$", + "decision": "allow", + }, + {"id": "deny_read", "tool_name": r"^Read$", "decision": "deny"}, + {"id": "deny_get", "tool_name": r".*_get$", "decision": "deny"}, ] self.guardrail = ToolPermissionGuardrail( @@ -64,50 +69,91 @@ class TestToolPermissionGuardrail: self.guardrail.supported_event_hooks or [] ) - def test_pattern_matching_exact(self): - """Test exact pattern matching""" - assert self.guardrail._matches_pattern("Read", "Read") is True - assert self.guardrail._matches_pattern("Write", "Read") is False + def test_matches_regex_helper(self): + pattern = re.compile(r"^Read$") + assert self.guardrail._matches_regex(pattern, "Read") is True + assert self.guardrail._matches_regex(pattern, "Write") is False + assert self.guardrail._matches_regex(None, "Any") is True + assert self.guardrail._matches_regex(pattern, None) is False - def test_pattern_matching_wildcards(self): - """Test wildcard pattern matching""" - assert ( - self.guardrail._matches_pattern( - "mcp__github_add_issue_comment", "mcp__github_*" - ) - is True + def test_rule_matches_tool_with_type_only(self): + guardrail = ToolPermissionGuardrail( + guardrail_name="type-only", + rules=[ + { + "id": "allow_functions", + "tool_type": r"^function$", + "decision": "allow", + } + ], + default_action="deny", + on_disallowed_action="block", ) - assert ( - self.guardrail._matches_pattern( - "mcp__github_add_issue_comment", "mcp__github_*_comment" - ) - is True + + is_allowed, rule_id, _ = guardrail._check_tool_permission("AnyTool", "function") + assert is_allowed is True + assert rule_id == "allow_functions" + + is_allowed, rule_id, _ = guardrail._check_tool_permission("AnyTool", "custom") + assert is_allowed is False + assert rule_id is None + + def test_rule_matches_tool_with_name_and_type(self): + guardrail = ToolPermissionGuardrail( + guardrail_name="name-type", + rules=[ + { + "id": "allow_specific", + "tool_name": r"^Bash$", + "tool_type": r"^function$", + "decision": "allow", + } + ], + default_action="deny", + on_disallowed_action="block", ) - assert ( - self.guardrail._matches_pattern( - "mcp__github_add_issue_comment", "*_comment" + + is_allowed, rule_id, _ = guardrail._check_tool_permission("Bash", "function") + assert is_allowed is True + assert rule_id == "allow_specific" + + is_allowed, rule_id, _ = guardrail._check_tool_permission("Bash", "custom") + assert is_allowed is False + assert rule_id is None + + def test_rule_requires_name_or_type(self): + with pytest.raises(ValueError): + ToolPermissionGuardrail( + guardrail_name="invalid-rule", + rules=[{"id": "no_target", "decision": "allow"}], + default_action="deny", + on_disallowed_action="block", ) - is True + + def test_type_only_rule_skips_param_patterns(self): + guardrail = ToolPermissionGuardrail( + guardrail_name="type-param", + rules=[ + { + "id": "allow_type_only", + "tool_type": r"^function$", + "decision": "allow", + "allowed_param_patterns": {"foo": r"^bar$"}, + } + ], + default_action="deny", + on_disallowed_action="block", ) - assert ( - self.guardrail._matches_pattern( - "mcp__git_add_issue_comment", "mcp__github_*" - ) - is False - ) - assert ( - self.guardrail._matches_pattern( - "mcp__github_assign_copilot_to_issue", "mcp__github_*_comment" - ) - is False - ) - assert ( - self.guardrail._matches_pattern( - "mcp__github_assign_copilot_to_issue", "*_comment" - ) - is False + + tool_call = ChatCompletionMessageToolCall( + function={"name": "AnyTool", "arguments": "{}"}, + type="function", ) + is_allowed, rule_id, _ = guardrail._get_permission_for_tool_call(tool_call) + assert is_allowed is True + assert rule_id == "allow_type_only" + def test_check_tool_permission_allow(self): is_allowed, rule_id, msg = self.guardrail._check_tool_permission("Bash") assert is_allowed is True @@ -232,7 +278,7 @@ class TestToolPermissionGuardrail: rules=[ { "id": "allow_mail", - "tool_name": "mail_mcp-send_email", + "tool_name": r"^mail_mcp-send_email$", "decision": "allow", "allowed_param_patterns": { "to[]": r"^.+@berri\.ai$", @@ -267,7 +313,7 @@ class TestToolPermissionGuardrail: rules=[ { "id": "allow_mail", - "tool_name": "mail_mcp-send_email", + "tool_name": r"^mail_mcp-send_email$", "decision": "allow", "allowed_param_patterns": {"to[]": r"^.+@berri\.ai$"}, } @@ -300,7 +346,7 @@ class TestToolPermissionGuardrail: rules=[ { "id": "allow_mail", - "tool_name": "mail_mcp-send_email", + "tool_name": r"^mail_mcp-send_email$", "decision": "allow", "allowed_param_patterns": {"to[]": r"^.+@berri\.ai$"}, } @@ -339,7 +385,7 @@ class TestToolPermissionGuardrail: rules=[ { "id": "deny_gmail", - "tool_name": "mail_mcp-send_email", + "tool_name": r"^mail_mcp-send_email$", "decision": "deny", "allowed_param_patterns": {"to[]": r"^.+@gmail\.com$"}, } @@ -486,7 +532,9 @@ class TestToolPermissionGuardrailIntegration: def test_default_action_allow(self): guardrail = ToolPermissionGuardrail( guardrail_name="test-allow-default", - rules=[{"id": "deny_read", "tool_name": "Read", "decision": "deny"}], + rules=[ + {"id": "deny_read", "tool_name": r"^Read$", "decision": "deny"} + ], default_action="allow", ) diff --git a/ui/litellm-dashboard/src/components/guardrails/tool_permission/ToolPermissionRulesEditor.tsx b/ui/litellm-dashboard/src/components/guardrails/tool_permission/ToolPermissionRulesEditor.tsx index 790876ed3f0..68bbc055f45 100644 --- a/ui/litellm-dashboard/src/components/guardrails/tool_permission/ToolPermissionRulesEditor.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/tool_permission/ToolPermissionRulesEditor.tsx @@ -9,7 +9,8 @@ export type ToolPermissionOnDisallowedAction = "block" | "rewrite"; export interface ToolPermissionRuleConfig { id: string; - tool_name: string; + tool_name?: string; + tool_type?: string; decision: ToolPermissionDecision; allowed_param_patterns?: Record; } @@ -67,7 +68,6 @@ const ToolPermissionRulesEditor: React.FC = ({ ...config.rules, { id: `rule_${Math.random().toString(36).slice(2, 8)}`, - tool_name: "", decision: "allow" as ToolPermissionDecision, allowed_param_patterns: undefined, }, @@ -195,8 +195,8 @@ const ToolPermissionRulesEditor: React.FC = ({
LiteLLM Tool Permission Guardrail - Use wildcards (e.g., mcp__github_*) to scope which tools can run and optionally constrain - payload fields. + Provide regex patterns (e.g., ^mcp__github_.*$) for tool names or types and optionally + constrain payload fields.
{!disabled && ( @@ -242,12 +242,32 @@ const ToolPermissionRulesEditor: React.FC = ({ />
- Tool Name / Pattern + Tool Name (optional) updateRule(index, { tool_name: e.target.value })} + placeholder="^mcp__github_.*$" + value={rule.tool_name ?? ""} + onChange={(e) => + updateRule(index, { + tool_name: e.target.value.trim() === "" ? undefined : e.target.value, + }) + } + /> +
+
+ +
+
+ Tool Type (optional) + + updateRule(index, { + tool_type: e.target.value.trim() === "" ? undefined : e.target.value, + }) + } />
From 87050c6a022053147fde198d418312615a08981b Mon Sep 17 00:00:00 2001 From: Saar wintrov Date: Fri, 28 Nov 2025 07:27:23 +0200 Subject: [PATCH 198/349] SSO: fix the generic SSO provider (#17227) * SSO: fix the generic SSO provider * adding tests --- litellm/proxy/management_endpoints/ui_sso.py | 12 +- .../proxy/management_endpoints/test_ui_sso.py | 221 ++++++++++++++++++ 2 files changed, 225 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index 44b593efea4..a033e2cf5f4 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -1253,7 +1253,8 @@ class SSOAuthenticationHandler: Priority order: 1. CLI state (if provided) 2. GENERIC_CLIENT_STATE environment variable - 3. Generated UUID for Okta (if Okta endpoint detected) + 3. Generated UUID (required by Okta and most OAuth providers) + Args: state: Optional state parameter (e.g., CLI state) @@ -1275,13 +1276,8 @@ class SSOAuthenticationHandler: generic_client_state = os.getenv("GENERIC_CLIENT_STATE", None) if generic_client_state: redirect_params["state"] = generic_client_state - elif ( - generic_authorization_endpoint - and "okta" in generic_authorization_endpoint - ): - redirect_params["state"] = ( - uuid.uuid4().hex - ) # set state param for okta - required + else: + redirect_params["state"] = uuid.uuid4().hex # Handle PKCE (Proof Key for Code Exchange) if enabled # Set GENERIC_CLIENT_USE_PKCE=true to enable PKCE for enhanced OAuth security diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py index 4f5f4e0d858..f01813fa587 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py +++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py @@ -2239,6 +2239,227 @@ class TestGenericResponseConvertorNestedAttributes: assert result.display_name == "user-sub-123" # Top-level attribute works +class TestGetGenericSSORedirectParams: + """Test _get_generic_sso_redirect_params state parameter priority handling""" + + def test_state_priority_cli_state_provided(self): + """ + Test that CLI state takes highest priority when provided + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + cli_state = "litellm-session-token:sk-test123" + + with patch.dict(os.environ, {"GENERIC_CLIENT_STATE": "env_state_value"}): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=cli_state, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert + assert redirect_params["state"] == cli_state + assert code_verifier is None # PKCE not enabled by default + + def test_state_priority_env_variable_when_no_cli_state(self): + """ + Test that GENERIC_CLIENT_STATE environment variable is used when CLI state is not provided + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + env_state = "custom_env_state_value" + + with patch.dict(os.environ, {"GENERIC_CLIENT_STATE": env_state}): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=None, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert + assert redirect_params["state"] == env_state + assert code_verifier is None + + def test_state_priority_generated_uuid_fallback(self): + """ + Test that a UUID is generated when neither CLI state nor env variable is provided + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange - no CLI state and no env variable + with patch.dict(os.environ, {}, clear=False): + # Remove GENERIC_CLIENT_STATE if it exists + os.environ.pop("GENERIC_CLIENT_STATE", None) + + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=None, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert + assert "state" in redirect_params + assert redirect_params["state"] is not None + assert len(redirect_params["state"]) == 32 # UUID hex is 32 chars + assert code_verifier is None + + def test_state_with_pkce_enabled(self): + """ + Test that PKCE parameters are generated when GENERIC_CLIENT_USE_PKCE is enabled + """ + import base64 + import hashlib + + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + test_state = "test_state_123" + + with patch.dict(os.environ, {"GENERIC_CLIENT_USE_PKCE": "true"}): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=test_state, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert state + assert redirect_params["state"] == test_state + + # Assert PKCE parameters + assert code_verifier is not None + assert len(code_verifier) == 43 # Standard PKCE verifier length + assert "code_challenge" in redirect_params + assert "code_challenge_method" in redirect_params + assert redirect_params["code_challenge_method"] == "S256" + + # Verify code_challenge is correctly derived from code_verifier + expected_challenge_bytes = hashlib.sha256( + code_verifier.encode("utf-8") + ).digest() + expected_challenge = ( + base64.urlsafe_b64encode(expected_challenge_bytes) + .decode("utf-8") + .rstrip("=") + ) + assert redirect_params["code_challenge"] == expected_challenge + + def test_state_with_pkce_disabled(self): + """ + Test that PKCE parameters are NOT generated when GENERIC_CLIENT_USE_PKCE is false + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + test_state = "test_state_456" + + with patch.dict(os.environ, {"GENERIC_CLIENT_USE_PKCE": "false"}): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=test_state, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert + assert redirect_params["state"] == test_state + assert code_verifier is None + assert "code_challenge" not in redirect_params + assert "code_challenge_method" not in redirect_params + + def test_state_priority_cli_state_overrides_env_with_pkce(self): + """ + Test that CLI state takes priority over env variable even when PKCE is enabled + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + cli_state = "cli_state_priority" + env_state = "env_state_should_not_be_used" + + with patch.dict( + os.environ, + { + "GENERIC_CLIENT_STATE": env_state, + "GENERIC_CLIENT_USE_PKCE": "true", + }, + ): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=cli_state, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert + assert redirect_params["state"] == cli_state # CLI state takes priority + assert redirect_params["state"] != env_state + + # PKCE should still be generated + assert code_verifier is not None + assert "code_challenge" in redirect_params + assert "code_challenge_method" in redirect_params + + def test_empty_string_state_uses_env_variable(self): + """ + Test that empty string state is treated as None and uses env variable + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange + env_state = "env_state_for_empty_cli" + + with patch.dict(os.environ, {"GENERIC_CLIENT_STATE": env_state}): + # Act + redirect_params, code_verifier = ( + SSOAuthenticationHandler._get_generic_sso_redirect_params( + state="", # Empty string + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + ) + + # Assert - empty string is falsy, so env variable should be used + # Note: This tests current implementation behavior + # If empty string should be treated differently, implementation needs update + assert redirect_params["state"] == env_state + + def test_multiple_calls_generate_different_uuids(self): + """ + Test that multiple calls without state generate different UUIDs + """ + from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler + + # Arrange - no state provided + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("GENERIC_CLIENT_STATE", None) + + # Act + params1, _ = SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=None, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + params2, _ = SSOAuthenticationHandler._get_generic_sso_redirect_params( + state=None, + generic_authorization_endpoint="https://auth.example.com/authorize", + ) + + # Assert + assert params1["state"] != params2["state"] + assert len(params1["state"]) == 32 + assert len(params2["state"]) == 32 + + class TestPKCEFunctionality: """Test PKCE (Proof Key for Code Exchange) functionality""" From 8aa4f3d476f373bd8c0520dfdb15147353641118 Mon Sep 17 00:00:00 2001 From: Andy Forest Date: Fri, 28 Nov 2025 00:50:35 -0500 Subject: [PATCH 199/349] fix(bedrock): handle cohere v4 embed response dictionary format (#17220) --- .../llms/cohere/embed/v1_transformation.py | 10 +++- .../bedrock/embed/test_bedrock_embedding.py | 55 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/litellm/llms/cohere/embed/v1_transformation.py b/litellm/llms/cohere/embed/v1_transformation.py index 1a4bc393e84..feca9cb5b88 100644 --- a/litellm/llms/cohere/embed/v1_transformation.py +++ b/litellm/llms/cohere/embed/v1_transformation.py @@ -1,5 +1,5 @@ """ -Legacy /v1/embedding transformation logic for Bedrock Cohere. +Legacy /v1/embedding transformation logic for Bedrock Cohere. """ from typing import Any, List, Optional, Union @@ -123,7 +123,13 @@ class CohereEmbeddingConfig: """ embeddings = response_json["embeddings"] output_data = [] - is_embeddings_by_type = response_json.get("response_type") == "embeddings_by_type" + is_embeddings_by_type = ( + response_json.get("response_type") == "embeddings_by_type" + ) + + if isinstance(embeddings, dict): + is_embeddings_by_type = True + if is_embeddings_by_type: for embedding_type in embeddings: for idx, embedding in enumerate(embeddings[embedding_type]): diff --git a/tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py b/tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py index a266bea3513..d6253e59488 100644 --- a/tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py +++ b/tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py @@ -554,4 +554,57 @@ def test_bedrock_embedding_extra_headers_and_headers_merge(): print(f" Final headers: {list(headers.keys())}") except Exception as e: - pytest.fail(f"Failed to merge and forward headers: {str(e)}") \ No newline at end of file + pytest.fail(f"Failed to merge and forward headers: {str(e)}") + + +def test_bedrock_cohere_v4_embedding_response_parsing(): + """ + Test parsing of Bedrock Cohere v4 embedding response which returns a dictionary of embeddings + keyed by type (e.g. 'float', 'int8') instead of a direct list. + """ + litellm.set_verbose = True + client = HTTPHandler() + test_api_key = "test-bearer-token-12345" + model = "bedrock/cohere.embed-v4:0" + + # Mock response for Cohere v4 with multiple embedding types + cohere_v4_response = { + "embeddings": { + "float": [[0.1, 0.2, 0.3]], + "int8": [[1, 2, 3]] + }, + "response_type": "embeddings_by_type", + "id": "test-id", + "texts": ["test input"] + } + + with patch.object(client, "post") as mock_post: + mock_response = Mock() + mock_response.status_code = 200 + mock_response.text = json.dumps(cohere_v4_response) + mock_response.json = lambda: json.loads(mock_response.text) + mock_post.return_value = mock_response + + response = litellm.embedding( + model=model, + input=["test input"], + client=client, + aws_region_name="us-east-1", + aws_bedrock_runtime_endpoint="https://bedrock-runtime.us-east-1.amazonaws.com", + api_key=test_api_key + ) + + assert isinstance(response, litellm.EmbeddingResponse) + + # Verify we get two embedding objects back (one for float, one for int8) + assert len(response.data) == 2 + + # Check first embedding (float) + assert response.data[0]['object'] == 'embedding' + assert response.data[0]['embedding'] == [0.1, 0.2, 0.3] + assert response.data[0]['type'] == 'float' + + # Check second embedding (int8) + assert response.data[1]['object'] == 'embedding' + assert response.data[1]['embedding'] == [1, 2, 3] + assert response.data[1]['type'] == 'int8' From bbea83fd9366bf6cd2592eed133b00bda0421015 Mon Sep 17 00:00:00 2001 From: Omkar Malpure <77787482+omkar806@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:29:24 +0530 Subject: [PATCH 200/349] Fix : acompletion throws error with SambaNova models (#17217) Co-authored-by: Omkar Malpure --- litellm/llms/sambanova/chat.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/litellm/llms/sambanova/chat.py b/litellm/llms/sambanova/chat.py index b0534347c9a..2218c808721 100644 --- a/litellm/llms/sambanova/chat.py +++ b/litellm/llms/sambanova/chat.py @@ -121,5 +121,10 @@ class SambanovaConfig(OpenAIGPTConfig): SambaNova API doesn't support content as a list - only string content. This converts content lists like [{"type": "text", "text": "..."}] to strings. """ + async def _async_transform(): + return handle_messages_with_content_list_to_str_conversion(messages) + + if is_async: + return _async_transform() messages = handle_messages_with_content_list_to_str_conversion(messages) return messages From 205a563b65d179063ea90128081a0676f75235c2 Mon Sep 17 00:00:00 2001 From: v0rtex20k <55466324+v0rtex20k@users.noreply.github.com> Date: Fri, 28 Nov 2025 01:10:19 -0500 Subject: [PATCH 201/349] Allow wildcard routes for nonproxy admin (SCIM) (#17178) * checked for wildcards in nonproxy * ready --- litellm/proxy/auth/route_checks.py | 6 ++++ .../proxy/auth/test_route_checks.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 664b8a9ddce..76621e95cd3 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -241,6 +241,12 @@ class RouteChecks: route_allowed = True break + if RouteChecks._route_matches_wildcard_pattern( + route=route, pattern=allowed_route + ): + route_allowed = True + break + if not route_allowed: RouteChecks._raise_admin_only_route_exception( user_obj=user_obj, route=route diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index b2a51de3d67..de2aa2427ca 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -732,3 +732,31 @@ def test_videos_route_with_virtual_key_llm_api_routes(): assert ( result is True ), f"Virtual key with llm_api_routes should be able to access {route}" + +def test_non_proxy_admin_wildcard_allowed_routes(): + """Test that nonproxy admin users can still use wildcard routes""" + + user_obj = LiteLLM_UserTable( + user_id="test_user", + user_email="test@example.com", + user_role=LitellmUserRoles.INTERNAL_USER.value, + ) + + valid_token = UserAPIKeyAuth( + user_id="test_user", + user_role=LitellmUserRoles.INTERNAL_USER.value, + allowed_routes=["/scim/*"], + ) + + request = MagicMock(spec=Request) + request.query_params = {} + + RouteChecks.non_proxy_admin_allowed_routes_check( + user_obj=user_obj, + _user_role=LitellmUserRoles.INTERNAL_USER.value, + route="/scim/v2/Users", + request=request, + valid_token=valid_token, + request_data={}, + ) + From 8700c5ced665728e51624e7c95dc0d2894115a9f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 28 Nov 2025 14:56:46 +0530 Subject: [PATCH 202/349] Add nova embedding support --- .../docs/embedding/supported_embedding.md | 2 + .../docs/providers/bedrock_embedding.md | 51 +- litellm/__init__.py | 3 + litellm/constants.py | 2 + litellm/llms/bedrock/base_aws_llm.py | 11 +- .../embed/amazon_nova_transformation.py | 258 ++++++++++ litellm/llms/bedrock/embed/embedding.py | 22 + litellm/types/llms/bedrock.py | 127 +++++ litellm/utils.py | 2 + .../test_bedrock_nova_embedding.py | 469 ++++++++++++++++++ 10 files changed, 939 insertions(+), 8 deletions(-) create mode 100644 litellm/llms/bedrock/embed/amazon_nova_transformation.py create mode 100644 tests/llm_translation/test_bedrock_nova_embedding.py diff --git a/docs/my-website/docs/embedding/supported_embedding.md b/docs/my-website/docs/embedding/supported_embedding.md index e63d9403665..0e8252b409b 100644 --- a/docs/my-website/docs/embedding/supported_embedding.md +++ b/docs/my-website/docs/embedding/supported_embedding.md @@ -263,6 +263,8 @@ print(response) | Model Name | Function Call | |----------------------|---------------------------------------------| +| Amazon Nova Multimodal Embeddings | `embedding(model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", input=input)` | [Nova Docs](../providers/bedrock_embedding#amazon-nova-multimodal-embeddings) | +| Amazon Nova (Async) | `embedding(model="bedrock/async_invoke/amazon.nova-2-multimodal-embeddings-v1:0", input=input, input_type="text", output_s3_uri="s3://bucket/")` | [Nova Async Docs](../providers/bedrock_embedding#asynchronous-embeddings-with-segmentation) | | Titan Embeddings - G1 | `embedding(model="amazon.titan-embed-text-v1", input=input)` | | Cohere Embeddings - English | `embedding(model="cohere.embed-english-v3", input=input)` | | Cohere Embeddings - Multilingual | `embedding(model="cohere.embed-multilingual-v3", input=input)` | diff --git a/docs/my-website/docs/providers/bedrock_embedding.md b/docs/my-website/docs/providers/bedrock_embedding.md index 76c9606533e..e2e7c0dcedd 100644 --- a/docs/my-website/docs/providers/bedrock_embedding.md +++ b/docs/my-website/docs/providers/bedrock_embedding.md @@ -4,7 +4,8 @@ | Provider | LiteLLM Route | AWS Documentation | Cost Tracking | |----------|---------------|-------------------|---------------| -| Amazon Titan | `bedrock/amazon.*` | [Amazon Titan Embeddings](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html) | ✅ | +| Amazon Titan | `bedrock/amazon.titan-*` | [Amazon Titan Embeddings](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html) | ✅ | +| Amazon Nova | `bedrock/amazon.nova-*` | [Amazon Nova Embeddings](https://docs.aws.amazon.com/bedrock/latest/userguide/nova-embed.html) | ✅ | | Cohere | `bedrock/cohere.*` | [Cohere Embeddings](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-embed.html) | ✅ | | TwelveLabs | `bedrock/us.twelvelabs.*` | [TwelveLabs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-twelvelabs.html) | ✅ | @@ -16,6 +17,7 @@ LiteLLM supports AWS Bedrock's async-invoke feature for embedding models that re | Provider | Async Invoke Route | Use Case | |----------|-------------------|----------| +| Amazon Nova | `bedrock/async_invoke/amazon.nova-2-multimodal-embeddings-v1:0` | Multimodal embeddings with segmentation for long text, video, and audio | | TwelveLabs Marengo | `bedrock/async_invoke/us.twelvelabs.marengo-embed-2-7-v1:0` | Video, audio, image, and text embeddings | ### Required Parameters @@ -116,7 +118,7 @@ def check_async_job_status(invocation_arn, aws_region_name="us-east-1"): """Check the status of an async invoke job using LiteLLM batch API""" try: response = retrieve_batch( - batch_id=invocation_arn, + batch_id=invocation_arn, # Pass the invocation ARN here custom_llm_provider="bedrock", aws_region_name=aws_region_name ) @@ -128,11 +130,47 @@ def check_async_job_status(invocation_arn, aws_region_name="us-east-1"): # Check status status = check_async_job_status(invocation_arn, "us-east-1") if status: - print(f"Job Status: {status.status}") - print(f"Output Location: {status.output_file_id}") + print(f"Job Status: {status.status}") # "in_progress", "completed", or "failed" + print(f"Output Location: {status.metadata['output_file_id']}") # S3 URI where results are stored ``` -**Note:** The actual embedding results are stored in S3. The `output_file_id` from the batch status can be used to locate the results file in your S3 bucket. +#### Polling Until Complete + +Here's a complete example of polling for job completion: + +```python +def wait_for_async_job(invocation_arn, aws_region_name="us-east-1", max_wait=3600): + """Poll job status until completion""" + start_time = time.time() + + while True: + status = retrieve_batch( + batch_id=invocation_arn, + custom_llm_provider="bedrock", + aws_region_name=aws_region_name, + ) + + if status.status == "completed": + print("✅ Job completed!") + return status + elif status.status == "failed": + error_msg = status.metadata.get('failure_message', 'Unknown error') + raise Exception(f"❌ Job failed: {error_msg}") + else: + elapsed = time.time() - start_time + if elapsed > max_wait: + raise TimeoutError(f"Job timed out after {max_wait} seconds") + + print(f"⏳ Job still processing... (elapsed: {elapsed:.0f}s)") + time.sleep(10) # Wait 10 seconds before checking again + +# Wait for completion +completed_status = wait_for_async_job(invocation_arn) +output_s3_uri = completed_status.metadata['output_file_id'] +print(f"Results available at: {output_s3_uri}") +``` + +**Note:** The actual embedding results are stored in S3. When the job is completed, download the results from the S3 location specified in `status.metadata['output_file_id']`. The results will be in JSON/JSONL format containing the embedding vectors. ### Error Handling @@ -179,7 +217,7 @@ except Exception as e: ### Limitations -- Async-invoke is currently only supported for TwelveLabs Marengo models +- Async-invoke is supported for TwelveLabs Marengo and Amazon Nova models - Results are stored in S3 and must be retrieved separately using the output file ID - Job status checking requires using LiteLLM's `retrieve_batch()` function - No built-in polling mechanism in LiteLLM (must implement your own status checking loop) @@ -259,6 +297,7 @@ print(response) | Model Name | Usage | Supported Additional OpenAI params | |----------------------|---------------------------------------------|-----| +| **Amazon Nova Multimodal Embeddings** | `embedding(model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", input=input)` | Supports multimodal input (text, image, video, audio), multiple purposes, dimensions (256, 384, 1024, 3072) | | Titan Embeddings V2 | `embedding(model="bedrock/amazon.titan-embed-text-v2:0", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py#L59) | | Titan Embeddings - V1 | `embedding(model="bedrock/amazon.titan-embed-text-v1", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py#L53) | Titan Multimodal Embeddings | `embedding(model="bedrock/amazon.titan-embed-image-v1", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py#L28) | diff --git a/litellm/__init__.py b/litellm/__init__.py index 71be5113e2d..6a9ced0f4b9 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1252,6 +1252,9 @@ from .llms.bedrock.embed.cohere_transformation import BedrockCohereEmbeddingConf from .llms.bedrock.embed.twelvelabs_marengo_transformation import ( TwelveLabsMarengoEmbeddingConfig, ) +from .llms.bedrock.embed.amazon_nova_transformation import ( + AmazonNovaEmbeddingConfig, +) from .llms.openai.openai import OpenAIConfig, MistralEmbeddingConfig from .llms.openai.image_variations.transformation import OpenAIImageVariationConfig from .llms.deepinfra.chat.transformation import DeepInfraConfig diff --git a/litellm/constants.py b/litellm/constants.py index 9235916dd43..fd3858ae2f8 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -858,6 +858,7 @@ BEDROCK_EMBEDDING_PROVIDERS_LITERAL = Literal[ "cohere", "amazon", "twelvelabs", + "nova", ] BEDROCK_CONVERSE_MODELS = [ @@ -918,6 +919,7 @@ cohere_embedding_models: set = set( bedrock_embedding_models: set = set( [ "amazon.titan-embed-text-v1", + "amazon.nova-2-multimodal-embeddings-v1:0", "cohere.embed-english-v3", "cohere.embed-multilingual-v3", "cohere.embed-v4:0", diff --git a/litellm/llms/bedrock/base_aws_llm.py b/litellm/llms/bedrock/base_aws_llm.py index 72e270428ac..ed658c793af 100644 --- a/litellm/llms/bedrock/base_aws_llm.py +++ b/litellm/llms/bedrock/base_aws_llm.py @@ -387,9 +387,16 @@ class BaseAWSLLM: Handles scenarios like: 1. model=cohere.embed-english-v3:0 -> Returns `cohere` 2. model=amazon.titan-embed-text-v1 -> Returns `amazon` - 3. model=us.twelvelabs.marengo-embed-2-7-v1:0 -> Returns `twelvelabs` - 4. model=twelvelabs.marengo-embed-2-7-v1:0 -> Returns `twelvelabs` + 3. model=amazon.nova-2-multimodal-embeddings-v1:0 -> Returns `nova` + 4. model=us.twelvelabs.marengo-embed-2-7-v1:0 -> Returns `twelvelabs` + 5. model=twelvelabs.marengo-embed-2-7-v1:0 -> Returns `twelvelabs` """ + # Special case: Check for "nova" in model name first (before "amazon") + # This handles amazon.nova-* models + if "nova" in model.lower(): + if "nova" in get_args(BEDROCK_EMBEDDING_PROVIDERS_LITERAL): + return cast(BEDROCK_EMBEDDING_PROVIDERS_LITERAL, "nova") + # Handle regional models like us.twelvelabs.marengo-embed-2-7-v1:0 if "." in model: parts = model.split(".") diff --git a/litellm/llms/bedrock/embed/amazon_nova_transformation.py b/litellm/llms/bedrock/embed/amazon_nova_transformation.py new file mode 100644 index 00000000000..97652175a94 --- /dev/null +++ b/litellm/llms/bedrock/embed/amazon_nova_transformation.py @@ -0,0 +1,258 @@ +""" +Transformation logic from OpenAI /v1/embeddings format to Bedrock Amazon Nova /invoke and /async-invoke format. + +Why separate file? Make it easy to see how transformation works + +Supports: +- Synchronous embeddings (SINGLE_EMBEDDING) +- Asynchronous embeddings with segmentation (SEGMENTED_EMBEDDING) +- Multimodal inputs: text, image, video, audio +- Multiple embedding purposes and dimensions + +Docs - https://docs.aws.amazon.com/bedrock/latest/userguide/nova-embed.html +""" + +from typing import List, Optional + +from litellm.types.utils import Embedding, EmbeddingResponse, Usage + + +class AmazonNovaEmbeddingConfig: + """ + Reference: https://docs.aws.amazon.com/bedrock/latest/userguide/nova-embed.html + + Amazon Nova Multimodal Embeddings supports: + - Text, image, video, and audio inputs + - Synchronous (InvokeModel) and asynchronous (StartAsyncInvoke) APIs + - Multiple embedding purposes and dimensions + """ + + def __init__(self) -> None: + pass + + def get_supported_openai_params(self) -> List[str]: + return [ + "dimensions", + ] + + def map_openai_params( + self, non_default_params: dict, optional_params: dict + ) -> dict: + """Map OpenAI-style parameters to Nova parameters.""" + for k, v in non_default_params.items(): + if k == "dimensions": + # Map OpenAI dimensions to Nova embedding_dimension + optional_params["embedding_dimension"] = v + elif k in self.get_supported_openai_params(): + optional_params[k] = v + return optional_params + + def _transform_request( + self, + input: str, + inference_params: dict, + async_invoke_route: bool = False, + model_id: Optional[str] = None, + output_s3_uri: Optional[str] = None, + ) -> dict: + """ + Transform OpenAI-style input to Nova format. + + Only handles OpenAI params (dimensions). All other Nova-specific params + should be passed via inference_params and will be passed through as-is. + + Args: + input: The input text or media reference + inference_params: Additional parameters (will be passed through) + async_invoke_route: Whether this is for async invoke + model_id: Model ID (for async invoke) + output_s3_uri: S3 URI for output (for async invoke) + + Returns: + dict: Nova embedding request + """ + # Determine task type + task_type = "SEGMENTED_EMBEDDING" if async_invoke_route else "SINGLE_EMBEDDING" + + # Build the base request structure + request: dict = { + "schemaVersion": "nova-multimodal-embed-v1", + "taskType": task_type, + } + + # Start with inference_params (user-provided params) + embedding_params = inference_params.copy() + + # Map OpenAI dimensions to embeddingDimension if provided + if "dimensions" in embedding_params: + embedding_params["embeddingDimension"] = embedding_params.pop("dimensions") + elif "embedding_dimension" in embedding_params: + embedding_params["embeddingDimension"] = embedding_params.pop("embedding_dimension") + + # Add required embeddingPurpose if not provided (required by Nova API) + if "embeddingPurpose" not in embedding_params: + embedding_params["embeddingPurpose"] = "GENERIC_INDEX" + + # Add required embeddingDimension if not provided (required by Nova API) + if "embeddingDimension" not in embedding_params: + embedding_params["embeddingDimension"] = 3072 + + # For text input, add basic text structure if user hasn't provided text/image/video/audio + if "text" not in embedding_params and "image" not in embedding_params and "video" not in embedding_params and "audio" not in embedding_params: + # Default to text if no modality specified + if input.startswith("s3://"): + embedding_params["text"] = { + "source": {"s3Location": {"uri": input}}, + "truncationMode": "END" # Required by Nova API + } + else: + embedding_params["text"] = { + "value": input, + "truncationMode": "END" # Required by Nova API + } + + # Set the embedding params in the request + if task_type == "SINGLE_EMBEDDING": + request["singleEmbeddingParams"] = embedding_params + else: + request["segmentedEmbeddingParams"] = embedding_params + + # For async invoke, wrap in the async invoke format + if async_invoke_route and model_id: + return self._wrap_async_invoke_request( + model_input=request, + model_id=model_id, + output_s3_uri=output_s3_uri, + ) + + return request + + def _wrap_async_invoke_request( + self, + model_input: dict, + model_id: str, + output_s3_uri: Optional[str] = None, + ) -> dict: + """ + Wrap the transformed request in the AWS Bedrock async invoke format. + + Args: + model_input: The transformed Nova embedding request + model_id: The model identifier (without async_invoke prefix) + output_s3_uri: S3 URI for output data config + + Returns: + dict: The wrapped async invoke request + """ + import urllib.parse + + # Clean the model ID + unquoted_model_id = urllib.parse.unquote(model_id) + if unquoted_model_id.startswith("async_invoke/"): + unquoted_model_id = unquoted_model_id.replace("async_invoke/", "") + + # Validate that the S3 URI is not empty + if not output_s3_uri or output_s3_uri.strip() == "": + raise ValueError("output_s3_uri is required for async invoke requests") + + return { + "modelId": unquoted_model_id, + "modelInput": model_input, + "outputDataConfig": { + "s3OutputDataConfig": { + "s3Uri": output_s3_uri + } + }, + } + + def _transform_response( + self, response_list: List[dict], model: str + ) -> EmbeddingResponse: + """ + Transform Nova response to OpenAI format. + + Nova response format: + { + "embeddings": [ + { + "embeddingType": "TEXT" | "IMAGE" | "VIDEO" | "AUDIO" | "AUDIO_VIDEO_COMBINED", + "embedding": [0.1, 0.2, ...], + "truncatedCharLength": 100 # Optional, only for text + } + ] + } + """ + embeddings: List[Embedding] = [] + total_tokens = 0 + + for response in response_list: + # Nova response has an "embeddings" array + if "embeddings" in response and isinstance(response["embeddings"], list): + for item in response["embeddings"]: + if "embedding" in item: + embedding = Embedding( + embedding=item["embedding"], + index=len(embeddings), + object="embedding", + ) + embeddings.append(embedding) + + # Estimate token count + # For text, use truncatedCharLength if available + if "truncatedCharLength" in item: + total_tokens += item["truncatedCharLength"] // 4 + else: + # Rough estimate based on embedding dimension + total_tokens += len(item["embedding"]) // 4 + elif "embedding" in response: + # Direct embedding response (fallback) + embedding = Embedding( + embedding=response["embedding"], + index=len(embeddings), + object="embedding", + ) + embeddings.append(embedding) + total_tokens += len(response["embedding"]) // 4 + + usage = Usage(prompt_tokens=total_tokens, total_tokens=total_tokens) + + return EmbeddingResponse(data=embeddings, model=model, usage=usage) + + def _transform_async_invoke_response( + self, response: dict, model: str + ) -> EmbeddingResponse: + """ + Transform async invoke response (invocation ARN) to OpenAI format. + + AWS async invoke returns: + { + "invocationArn": "arn:aws:bedrock:us-east-1:123456789012:async-invoke/abc123" + } + + We transform this to a job-like embedding response with the ARN in hidden params. + """ + invocation_arn = response.get("invocationArn", "") + + # Create a placeholder embedding object for the job + embedding = Embedding( + embedding=[], # Empty embedding for async jobs + index=0, + object="embedding", + ) + + # Create usage object (empty for async jobs) + usage = Usage(prompt_tokens=0, total_tokens=0) + + # Create hidden params with job ID + from litellm.types.llms.base import HiddenParams + + hidden_params = HiddenParams() + setattr(hidden_params, "_invocation_arn", invocation_arn) + + return EmbeddingResponse( + data=[embedding], + model=model, + usage=usage, + hidden_params=hidden_params, + ) + diff --git a/litellm/llms/bedrock/embed/embedding.py b/litellm/llms/bedrock/embed/embedding.py index fea29935975..be2bfcd70ec 100644 --- a/litellm/llms/bedrock/embed/embedding.py +++ b/litellm/llms/bedrock/embed/embedding.py @@ -27,6 +27,7 @@ from litellm.types.utils import EmbeddingResponse, LlmProviders from ..base_aws_llm import BaseAWSLLM from ..common_utils import BedrockError +from .amazon_nova_transformation import AmazonNovaEmbeddingConfig from .amazon_titan_g1_transformation import AmazonTitanG1Config from .amazon_titan_multimodal_transformation import ( AmazonTitanMultimodalEmbeddingG1Config, @@ -175,6 +176,12 @@ class BedrockEmbedding(BaseAWSLLM): response=response_list[0], model=model ) ) + elif provider == "nova": + returned_response = ( + AmazonNovaEmbeddingConfig()._transform_async_invoke_response( + response=response_list[0], model=model + ) + ) else: # For other providers, create a generic async response invocation_arn = response_list[0].get("invocationArn", "") @@ -222,6 +229,10 @@ class BedrockEmbedding(BaseAWSLLM): response_list=response_list, model=model ) ) + elif provider == "nova": + returned_response = AmazonNovaEmbeddingConfig()._transform_response( + response_list=response_list, model=model + ) ########################################################## # Validate returned response @@ -467,6 +478,17 @@ class BedrockEmbedding(BaseAWSLLM): ) ) batch_data.append(twelvelabs_request) + elif provider == "nova": + batch_data = [] + for i in input: + nova_request = AmazonNovaEmbeddingConfig()._transform_request( + input=i, + inference_params=inference_params, + async_invoke_route=has_async_invoke, + model_id=modelId, + output_s3_uri=inference_params.get("output_s3_uri"), + ) + batch_data.append(nova_request) ### SET RUNTIME ENDPOINT ### endpoint_url, proxy_endpoint_url = self.get_runtime_endpoint( diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index 330308e179c..3696f679640 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -427,6 +427,133 @@ class TwelveLabsAsyncInvokeStatusResponse(TypedDict): failureMessage: Optional[str] +# Amazon Nova Multimodal Embeddings types +NOVA_EMBEDDING_PURPOSES = Literal[ + "GENERIC_INDEX", + "GENERIC_RETRIEVAL", + "TEXT_RETRIEVAL", + "IMAGE_RETRIEVAL", + "VIDEO_RETRIEVAL", + "DOCUMENT_RETRIEVAL", + "AUDIO_RETRIEVAL", + "CLASSIFICATION", + "CLUSTERING", +] + +NOVA_EMBEDDING_DIMENSIONS = Literal[256, 384, 1024, 3072] + +NOVA_TRUNCATION_MODES = Literal["START", "END", "NONE"] + +NOVA_DETAIL_LEVELS = Literal["STANDARD_IMAGE", "DOCUMENT_IMAGE"] + +NOVA_EMBEDDING_MODES = Literal["AUDIO_VIDEO_COMBINED", "AUDIO_VIDEO_SEPARATE"] + +NOVA_EMBEDDING_TYPES = Literal[ + "TEXT", "IMAGE", "VIDEO", "AUDIO", "AUDIO_VIDEO_COMBINED" +] + + +class NovaSourceS3Location(TypedDict): + uri: str + + +class NovaSourceObject(TypedDict, total=False): + bytes: str # base64 encoded + s3Location: NovaSourceS3Location + + +class NovaTextParams(TypedDict, total=False): + truncationMode: NOVA_TRUNCATION_MODES + value: str + source: NovaSourceObject + + +class NovaImageParams(TypedDict, total=False): + format: str # png, jpeg, gif, webp + source: Required[NovaSourceObject] + detailLevel: NOVA_DETAIL_LEVELS + + +class NovaVideoParams(TypedDict, total=False): + format: str # mp4, mov, mkv, webm, flv, mpeg, mpg, wmv, 3gp + source: Required[NovaSourceObject] + embeddingMode: Required[NOVA_EMBEDDING_MODES] + + +class NovaAudioParams(TypedDict, total=False): + format: str # mp3, wav, ogg + source: Required[NovaSourceObject] + + +class NovaTextSegmentationConfig(TypedDict, total=False): + maxLengthChars: int # 800-50,000, default 32,000 + + +class NovaMediaSegmentationConfig(TypedDict, total=False): + durationSeconds: int # 1-30, default 5 + + +class NovaTextParamsWithSegmentation(NovaTextParams, total=False): + segmentationConfig: NovaTextSegmentationConfig + + +class NovaVideoParamsWithSegmentation(NovaVideoParams, total=False): + segmentationConfig: NovaMediaSegmentationConfig + + +class NovaAudioParamsWithSegmentation(NovaAudioParams, total=False): + segmentationConfig: NovaMediaSegmentationConfig + + +class NovaSingleEmbeddingParams(TypedDict, total=False): + embeddingPurpose: Required[NOVA_EMBEDDING_PURPOSES] + embeddingDimension: NOVA_EMBEDDING_DIMENSIONS + text: NovaTextParams + image: NovaImageParams + video: NovaVideoParams + audio: NovaAudioParams + + +class NovaSegmentedEmbeddingParams(TypedDict, total=False): + embeddingPurpose: Required[NOVA_EMBEDDING_PURPOSES] + embeddingDimension: NOVA_EMBEDDING_DIMENSIONS + text: NovaTextParamsWithSegmentation + image: NovaImageParams + video: NovaVideoParamsWithSegmentation + audio: NovaAudioParamsWithSegmentation + + +class NovaEmbeddingRequest(TypedDict, total=False): + schemaVersion: str # "nova-multimodal-embed-v1" + taskType: Literal["SINGLE_EMBEDDING", "SEGMENTED_EMBEDDING"] + singleEmbeddingParams: NovaSingleEmbeddingParams + segmentedEmbeddingParams: NovaSegmentedEmbeddingParams + + +class NovaEmbeddingItem(TypedDict, total=False): + embeddingType: NOVA_EMBEDDING_TYPES + embedding: Required[List[float]] + truncatedCharLength: int # Only for text + + +class NovaEmbeddingResponse(TypedDict): + embeddings: List[NovaEmbeddingItem] + + +class NovaS3OutputDataConfig(TypedDict): + s3Uri: str + + +class NovaOutputDataConfig(TypedDict): + s3OutputDataConfig: NovaS3OutputDataConfig + + +class NovaAsyncInvokeRequest(TypedDict): + modelId: str + modelInput: NovaEmbeddingRequest + outputDataConfig: NovaOutputDataConfig + + AmazonEmbeddingRequest = Union[ AmazonTitanMultimodalEmbeddingRequest, AmazonTitanV2EmbeddingRequest, diff --git a/litellm/utils.py b/litellm/utils.py index 053368a3e0a..15e068a4681 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2827,6 +2827,8 @@ def get_optional_params_embeddings( # noqa: PLR0915 object = litellm.BedrockCohereEmbeddingConfig() elif "twelvelabs" in model or "marengo" in model: object = litellm.TwelveLabsMarengoEmbeddingConfig() + elif "nova" in model.lower(): + object = litellm.AmazonNovaEmbeddingConfig() else: # unmapped model supported_params = [] _check_valid_arg(supported_params=supported_params) diff --git a/tests/llm_translation/test_bedrock_nova_embedding.py b/tests/llm_translation/test_bedrock_nova_embedding.py new file mode 100644 index 00000000000..8cc77b3c3cb --- /dev/null +++ b/tests/llm_translation/test_bedrock_nova_embedding.py @@ -0,0 +1,469 @@ +""" +Test suite for Amazon Nova Multimodal Embeddings integration with LiteLLM. + +Tests cover: +- Synchronous text embeddings +- Synchronous image embeddings +- Synchronous video/audio embeddings +- Asynchronous embeddings with segmentation +- Different embedding purposes and dimensions +- Error handling +""" + +import json +import os +import sys +from unittest.mock import MagicMock, Mock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.llms.bedrock.embed.amazon_nova_transformation import ( + AmazonNovaEmbeddingConfig, +) + + +class TestNovaTransformationRequest: + """Test request transformation for Nova embeddings.""" + + def test_text_embedding_sync_request(self): + """Test synchronous text embedding request transformation.""" + config = AmazonNovaEmbeddingConfig() + + inference_params = { + "embeddingPurpose": "GENERIC_INDEX", + "embedding_dimension": 1024, + "truncation_mode": "END", + } + + request = config._transform_request( + input="Hello, world!", + inference_params=inference_params, + async_invoke_route=False, + ) + + assert request["schemaVersion"] == "nova-multimodal-embed-v1" + assert request["taskType"] == "SINGLE_EMBEDDING" + assert "singleEmbeddingParams" in request + + params = request["singleEmbeddingParams"] + assert params["embeddingPurpose"] == "GENERIC_INDEX" + assert params["embeddingDimension"] == 1024 + assert params["text"]["truncationMode"] == "END" + assert params["text"]["value"] == "Hello, world!" + + def test_text_embedding_async_request(self): + """Test asynchronous text embedding request transformation.""" + config = AmazonNovaEmbeddingConfig() + + inference_params = { + "embeddingPurpose": "TEXT_RETRIEVAL", + "embeddingDimension": 3072, + "text": { + "value": "Long text content...", + "segmentationConfig": {"maxLengthChars": 10000} + }, + "output_s3_uri": "s3://my-bucket/output/", + } + + request = config._transform_request( + input="Long text content...", + inference_params=inference_params, + async_invoke_route=True, + model_id="amazon.nova-2-multimodal-embeddings-v1:0", + output_s3_uri="s3://my-bucket/output/", + ) + + assert "modelId" in request + assert "modelInput" in request + assert "outputDataConfig" in request + + model_input = request["modelInput"] + assert model_input["taskType"] == "SEGMENTED_EMBEDDING" + assert "segmentedEmbeddingParams" in model_input + + params = model_input["segmentedEmbeddingParams"] + assert params["embeddingPurpose"] == "TEXT_RETRIEVAL" + assert params["embeddingDimension"] == 3072 + assert params["text"]["segmentationConfig"]["maxLengthChars"] == 10000 + + def test_image_embedding_request(self): + """Test image embedding request transformation.""" + config = AmazonNovaEmbeddingConfig() + + # Mock base64 image data + image_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + + inference_params = { + "embeddingPurpose": "IMAGE_RETRIEVAL", + "embeddingDimension": 1024, + "image": { + "format": "png", + "source": {"bytes": image_data}, + "detailLevel": "STANDARD_IMAGE" + }, + } + + request = config._transform_request( + input=image_data, + inference_params=inference_params, + async_invoke_route=False, + ) + + params = request["singleEmbeddingParams"] + assert params["embeddingPurpose"] == "IMAGE_RETRIEVAL" + assert params["embeddingDimension"] == 1024 + assert params["image"]["format"] == "png" + assert params["image"]["detailLevel"] == "STANDARD_IMAGE" + assert "source" in params["image"] + assert "bytes" in params["image"]["source"] + + def test_video_embedding_request(self): + """Test video embedding request transformation.""" + config = AmazonNovaEmbeddingConfig() + + inference_params = { + "embeddingPurpose": "VIDEO_RETRIEVAL", + "embeddingDimension": 3072, + "video": { + "format": "mp4", + "source": {"s3Location": {"uri": "s3://my-bucket/video.mp4"}}, + "embeddingMode": "AUDIO_VIDEO_COMBINED" + }, + } + + request = config._transform_request( + input="s3://my-bucket/video.mp4", + inference_params=inference_params, + async_invoke_route=False, + ) + + params = request["singleEmbeddingParams"] + assert params["embeddingPurpose"] == "VIDEO_RETRIEVAL" + assert params["embeddingDimension"] == 3072 + assert params["video"]["format"] == "mp4" + assert params["video"]["embeddingMode"] == "AUDIO_VIDEO_COMBINED" + assert params["video"]["source"]["s3Location"]["uri"] == "s3://my-bucket/video.mp4" + + def test_audio_embedding_request(self): + """Test audio embedding request transformation.""" + config = AmazonNovaEmbeddingConfig() + + inference_params = { + "embeddingPurpose": "AUDIO_RETRIEVAL", + "embeddingDimension": 1024, + "audio": { + "format": "mp3", + "source": {"s3Location": {"uri": "s3://my-bucket/audio.mp3"}} + }, + } + + request = config._transform_request( + input="s3://my-bucket/audio.mp3", + inference_params=inference_params, + async_invoke_route=False, + ) + + params = request["singleEmbeddingParams"] + assert params["embeddingPurpose"] == "AUDIO_RETRIEVAL" + assert params["embeddingDimension"] == 1024 + assert params["audio"]["format"] == "mp3" + assert params["audio"]["source"]["s3Location"]["uri"] == "s3://my-bucket/audio.mp3" + + def test_async_invoke_requires_output_s3_uri(self): + """Test that async invoke requires output_s3_uri.""" + config = AmazonNovaEmbeddingConfig() + + inference_params = { + "embedding_purpose": "GENERIC_INDEX", + } + + with pytest.raises(ValueError, match="output_s3_uri is required"): + config._transform_request( + input="Test text", + inference_params=inference_params, + async_invoke_route=True, + model_id="amazon.nova-2-multimodal-embeddings-v1:0", + output_s3_uri=None, + ) + + def test_default_embedding_purpose(self): + """Test default embedding purpose is GENERIC_INDEX.""" + config = AmazonNovaEmbeddingConfig() + + request = config._transform_request( + input="Test text", + inference_params={}, + async_invoke_route=False, + ) + + params = request["singleEmbeddingParams"] + assert params["embeddingPurpose"] == "GENERIC_INDEX" + + def test_default_embedding_dimension(self): + """Test default embedding dimension is 3072.""" + config = AmazonNovaEmbeddingConfig() + + request = config._transform_request( + input="Test text", + inference_params={}, + async_invoke_route=False, + ) + + params = request["singleEmbeddingParams"] + assert params["embeddingDimension"] == 3072 + + +class TestNovaTransformationResponse: + """Test response transformation for Nova embeddings.""" + + def test_text_embedding_response(self): + """Test text embedding response transformation.""" + config = AmazonNovaEmbeddingConfig() + + response_list = [ + { + "embeddings": [ + { + "embeddingType": "TEXT", + "embedding": [0.1, 0.2, 0.3, 0.4, 0.5], + } + ] + } + ] + + result = config._transform_response(response_list, model="amazon.nova-2-multimodal-embeddings-v1:0") + + assert result.model == "amazon.nova-2-multimodal-embeddings-v1:0" + assert len(result.data) == 1 + assert result.data[0].embedding == [0.1, 0.2, 0.3, 0.4, 0.5] + assert result.data[0].index == 0 + assert result.data[0].object == "embedding" + assert result.usage.total_tokens > 0 + + def test_multiple_embeddings_response(self): + """Test response with multiple embeddings.""" + config = AmazonNovaEmbeddingConfig() + + response_list = [ + { + "embeddings": [ + { + "embeddingType": "TEXT", + "embedding": [0.1, 0.2, 0.3], + } + ] + }, + { + "embeddings": [ + { + "embeddingType": "TEXT", + "embedding": [0.4, 0.5, 0.6], + } + ] + }, + ] + + result = config._transform_response(response_list, model="amazon.nova-2-multimodal-embeddings-v1:0") + + assert len(result.data) == 2 + assert result.data[0].embedding == [0.1, 0.2, 0.3] + assert result.data[1].embedding == [0.4, 0.5, 0.6] + assert result.data[0].index == 0 + assert result.data[1].index == 1 + + def test_video_embedding_response_separate_mode(self): + """Test video embedding response with separate audio/video.""" + config = AmazonNovaEmbeddingConfig() + + response_list = [ + { + "embeddings": [ + { + "embeddingType": "VIDEO", + "embedding": [0.1, 0.2, 0.3], + }, + { + "embeddingType": "AUDIO", + "embedding": [0.4, 0.5, 0.6], + } + ] + } + ] + + result = config._transform_response(response_list, model="amazon.nova-2-multimodal-embeddings-v1:0") + + assert len(result.data) == 2 + assert result.data[0].embedding == [0.1, 0.2, 0.3] + assert result.data[1].embedding == [0.4, 0.5, 0.6] + + def test_async_invoke_response(self): + """Test async invoke response transformation.""" + config = AmazonNovaEmbeddingConfig() + + response = { + "invocationArn": "arn:aws:bedrock:us-east-1:123456789012:async-invoke/abc123" + } + + result = config._transform_async_invoke_response(response, model="amazon.nova-2-multimodal-embeddings-v1:0") + + assert result.model == "amazon.nova-2-multimodal-embeddings-v1:0" + assert len(result.data) == 1 + assert result.data[0].embedding == [] # Empty for async jobs + assert result.usage.total_tokens == 0 + assert hasattr(result, "_hidden_params") + assert hasattr(result._hidden_params, "_invocation_arn") + assert result._hidden_params._invocation_arn == "arn:aws:bedrock:us-east-1:123456789012:async-invoke/abc123" + + +class TestNovaEmbeddingIntegration: + """Integration tests for Nova embeddings through LiteLLM.""" + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_sync_text_embedding_e2e(self): + """End-to-end test for synchronous text embedding.""" + response = litellm.embedding( + model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", + input=["Hello, world!"], + aws_region_name="us-east-1", + ) + + assert response is not None + assert len(response.data) == 1 + assert len(response.data[0].embedding) > 0 + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_async_text_embedding_e2e(self): + """End-to-end test for asynchronous text embedding.""" + response = litellm.embedding( + model="bedrock/async_invoke/amazon.nova-2-multimodal-embeddings-v1:0", + input=["Long text content for segmentation..."], + aws_region_name="us-east-1", + output_s3_uri="s3://my-bucket/output/", + segmentation_config={"maxLengthChars": 10000}, + ) + + assert response is not None + assert hasattr(response, "_hidden_params") + assert hasattr(response._hidden_params, "_invocation_arn") + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_image_embedding_e2e(self): + """End-to-end test for image embedding.""" + response = litellm.embedding( + model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", + input=["s3://my-bucket/image.png"], + aws_region_name="us-east-1", + input_type="image", + format="png", + embedding_purpose="IMAGE_RETRIEVAL", + ) + + assert response is not None + assert len(response.data) == 1 + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_video_embedding_e2e(self): + """End-to-end test for video embedding.""" + response = litellm.embedding( + model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", + input=["s3://my-bucket/video.mp4"], + aws_region_name="us-east-1", + input_type="video", + format="mp4", + embedding_mode="AUDIO_VIDEO_COMBINED", + embedding_purpose="VIDEO_RETRIEVAL", + ) + + assert response is not None + assert len(response.data) == 1 + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_different_dimensions(self): + """Test different embedding dimensions.""" + for dimension in [256, 384, 1024, 3072]: + response = litellm.embedding( + model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", + input=["Test text"], + aws_region_name="us-east-1", + dimensions=dimension, + ) + + assert response is not None + assert len(response.data[0].embedding) == dimension + + @pytest.mark.skip(reason="Requires AWS credentials and actual API calls") + def test_different_embedding_purposes(self): + """Test different embedding purposes.""" + purposes = [ + "GENERIC_INDEX", + "GENERIC_RETRIEVAL", + "TEXT_RETRIEVAL", + "CLASSIFICATION", + "CLUSTERING", + ] + + for purpose in purposes: + response = litellm.embedding( + model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0", + input=["Test text"], + aws_region_name="us-east-1", + embedding_purpose=purpose, + ) + + assert response is not None + assert len(response.data) == 1 + + +class TestNovaProviderDetection: + """Test provider detection for Nova models.""" + + def test_nova_provider_detection(self): + """Test that Nova provider is correctly detected.""" + from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM + + provider = BaseAWSLLM.get_bedrock_embedding_provider( + "amazon.nova-2-multimodal-embeddings-v1:0" + ) + + # Should detect "amazon" as provider since "nova" is in the model name + # but the provider detection looks at the first part before the dot + assert provider in ["amazon", "nova"] + + def test_nova_in_model_name(self): + """Test that models with 'nova' in the name are detected.""" + from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM + + # Test various Nova model name formats + test_models = [ + "amazon.nova-2-multimodal-embeddings-v1:0", + "us.amazon.nova-2-multimodal-embeddings-v1:0", + ] + + for model in test_models: + provider = BaseAWSLLM.get_bedrock_embedding_provider(model) + assert provider is not None + + +if __name__ == "__main__": + # Run basic transformation tests + print("Running Nova Embedding Transformation Tests...") + + test_request = TestNovaTransformationRequest() + test_request.test_text_embedding_sync_request() + test_request.test_text_embedding_async_request() + test_request.test_image_embedding_request() + test_request.test_video_embedding_request() + test_request.test_audio_embedding_request() + + test_response = TestNovaTransformationResponse() + test_response.test_text_embedding_response() + test_response.test_multiple_embeddings_response() + test_response.test_async_invoke_response() + + print("All transformation tests passed!") + From f0d3c96a8d5a42763798d4f76afa71464d87d97f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 28 Nov 2025 17:23:15 +0530 Subject: [PATCH 203/349] Add tags and other field in UI logs and add responses api cost tracking --- .../proxy/hooks/proxy_track_cost_callback.py | 17 +- .../openai_passthrough_logging_handler.py | 84 ++++++-- .../pass_through_endpoints.py | 26 ++- ...test_openai_passthrough_logging_handler.py | 185 ++++++++++++++++++ 4 files changed, 292 insertions(+), 20 deletions(-) diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index e165f96b663..dab5fb1bfd5 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -80,10 +80,25 @@ class _ProxyDBLogger(CustomLogger): if "litellm_params" not in request_data: request_data["litellm_params"] = {} + + existing_litellm_params = request_data.get("litellm_params", {}) + existing_litellm_metadata = existing_litellm_params.get("metadata", {}) or {} + + # Preserve tags from existing metadata + if existing_litellm_metadata.get("tags"): + existing_metadata["tags"] = existing_litellm_metadata.get("tags") + request_data["litellm_params"]["proxy_server_request"] = ( - request_data.get("proxy_server_request") or {} + request_data.get("proxy_server_request") or existing_litellm_params.get("proxy_server_request") or {} ) request_data["litellm_params"]["metadata"] = existing_metadata + + # Preserve model name and custom_llm_provider + if "model" not in request_data: + request_data["model"] = existing_litellm_params.get("model") or request_data.get("model", "") + if "custom_llm_provider" not in request_data: + request_data["custom_llm_provider"] = existing_litellm_params.get("custom_llm_provider") or request_data.get("custom_llm_provider", "") + await proxy_logging_obj.db_spend_update_writer.update_database( token=user_api_key_dict.api_key, response_cost=0.0, diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/openai_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/openai_passthrough_logging_handler.py index d6ab121096e..6745c559cd2 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/openai_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/openai_passthrough_logging_handler.py @@ -91,6 +91,21 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): and "/v1/images/edits" in parsed_url.path ) + @staticmethod + def is_openai_responses_route(url_route: str) -> bool: + """Check if the URL route is an OpenAI responses API endpoint.""" + if not url_route: + return False + parsed_url = urlparse(url_route) + return bool( + parsed_url.hostname + and ( + "api.openai.com" in parsed_url.hostname + or "openai.azure.com" in parsed_url.hostname + ) + and ("/v1/responses" in parsed_url.path or "/responses" in parsed_url.path) + ) + def _get_user_from_metadata( self, passthrough_logging_payload: PassthroughStandardLoggingPayload, @@ -187,7 +202,7 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): **kwargs, ) -> PassThroughEndpointLoggingTypedDict: """ - Handle OpenAI passthrough logging with cost tracking for chat completions, image generation, and image editing. + Handle OpenAI passthrough logging with cost tracking for chat completions, image generation, image editing, and responses API. """ # Check if this is a supported endpoint for cost tracking is_chat_completions = ( @@ -199,8 +214,11 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): is_image_editing = ( OpenAIPassthroughLoggingHandler.is_openai_image_editing_route(url_route) ) + is_responses = ( + OpenAIPassthroughLoggingHandler.is_openai_responses_route(url_route) + ) - if not (is_chat_completions or is_image_generation or is_image_editing): + if not (is_chat_completions or is_image_generation or is_image_editing or is_responses): # For unsupported endpoints, return None to let the system fall back to generic behavior return { "result": None, @@ -232,9 +250,13 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): litellm_model_response: Optional[Union[ModelResponse, TextCompletionResponse, ImageResponse]] = None handler_instance = OpenAIPassthroughLoggingHandler() + custom_llm_provider = kwargs.get("custom_llm_provider", "openai") + if is_chat_completions: # Handle chat completions with existing logic provider_config = handler_instance.get_provider_config(model=model) + # Preserve existing litellm_params to maintain metadata tags + existing_litellm_params = kwargs.get("litellm_params", {}) or {} litellm_model_response = provider_config.transform_response( raw_response=httpx_response, model_response=litellm.ModelResponse(), @@ -247,14 +269,14 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): encoding=litellm.encoding, json_mode=request_body.get("response_format", {}).get("type") == "json_object", - litellm_params={}, + litellm_params=existing_litellm_params, ) # Calculate cost using LiteLLM's cost calculator response_cost = litellm.completion_cost( completion_response=litellm_model_response, model=model, - custom_llm_provider="openai", + custom_llm_provider=custom_llm_provider, ) elif is_image_generation: # Handle image generation cost calculation @@ -306,11 +328,36 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): if not hasattr(litellm_model_response, "_hidden_params"): litellm_model_response._hidden_params = {} litellm_model_response._hidden_params["response_cost"] = response_cost + elif is_responses: + # Handle responses API cost calculation + provider_config = handler_instance.get_provider_config(model=model) + existing_litellm_params = kwargs.get("litellm_params", {}) or {} + litellm_model_response = provider_config.transform_response( + raw_response=httpx_response, + model_response=litellm.ModelResponse(), + model=model, + messages=request_body.get("messages", []), + logging_obj=logging_obj, + optional_params=request_body.get("optional_params", {}), + api_key="", + request_data=request_body, + encoding=litellm.encoding, + json_mode=False, + litellm_params=existing_litellm_params, + ) + + # Calculate cost using LiteLLM's cost calculator with responses call type + response_cost = litellm.completion_cost( + completion_response=litellm_model_response, + model=model, + custom_llm_provider=custom_llm_provider, + call_type="responses", + ) # Update kwargs with cost information kwargs["response_cost"] = response_cost kwargs["model"] = model - kwargs["custom_llm_provider"] = "openai" + kwargs["custom_llm_provider"] = custom_llm_provider # Extract user information for tracking passthrough_logging_payload: Optional[ @@ -321,10 +368,7 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): passthrough_logging_payload=passthrough_logging_payload, ) if user: - kwargs.setdefault("litellm_params", {}) - kwargs["litellm_params"].update( - {"proxy_server_request": {"body": {"user": user}}} - ) + kwargs["litellm_params"].setdefault("proxy_server_request", {}).setdefault("body", {})["user"] = user # Create standard logging object if litellm_model_response is not None: @@ -339,7 +383,7 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): # Update logging object with cost information logging_obj.model_call_details["model"] = model - logging_obj.model_call_details["custom_llm_provider"] = "openai" + logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider logging_obj.model_call_details["response_cost"] = response_cost endpoint_type = ( @@ -481,18 +525,27 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): "kwargs": {}, } + custom_llm_provider = litellm_logging_obj.model_call_details.get( + "custom_llm_provider", "openai" + ) # Calculate cost using LiteLLM's cost calculator response_cost = litellm.completion_cost( completion_response=complete_response, model=model, - custom_llm_provider="openai", + custom_llm_provider=custom_llm_provider, ) + # Preserve existing litellm_params to maintain metadata tags + existing_litellm_params = litellm_logging_obj.model_call_details.get( + "litellm_params", {} + ) or {} + # Prepare kwargs for logging kwargs = { "response_cost": response_cost, "model": model, - "custom_llm_provider": "openai", + "custom_llm_provider": custom_llm_provider, + "litellm_params": existing_litellm_params.copy(), } # Extract user information for tracking @@ -506,10 +559,7 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): passthrough_logging_payload=passthrough_logging_payload, ) if user: - kwargs.setdefault("litellm_params", {}) - kwargs["litellm_params"].update( - {"proxy_server_request": {"body": {"user": user}}} - ) + kwargs["litellm_params"].setdefault("proxy_server_request", {}).setdefault("body", {})["user"] = user # Create standard logging object get_standard_logging_object_payload( @@ -523,7 +573,7 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler): # Update logging object with cost information litellm_logging_obj.model_call_details["model"] = model - litellm_logging_obj.model_call_details["custom_llm_provider"] = "openai" + litellm_logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider litellm_logging_obj.model_call_details["response_cost"] = response_cost verbose_proxy_logger.debug( diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 780a3d6dcc6..5b47a8af7a5 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -735,6 +735,12 @@ async def pass_through_request( # noqa: PLR0915 logging_obj=logging_obj, ) + # Store custom_llm_provider in kwargs and logging object if provided + if custom_llm_provider: + kwargs["custom_llm_provider"] = custom_llm_provider + logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider + logging_obj.model_call_details["litellm_params"] = kwargs.get("litellm_params", {}) + # done for supporting 'parallel_request_limiter.py' with pass-through endpoints logging_obj.update_environment_variables( model="unknown", @@ -923,6 +929,12 @@ async def pass_through_request( # noqa: PLR0915 if kwargs: for key, value in kwargs.items(): request_payload[key] = value + + if "model" not in request_payload and _parsed_body and isinstance(_parsed_body, dict): + request_payload["model"] = _parsed_body.get("model", "") + if "custom_llm_provider" not in request_payload and custom_llm_provider: + request_payload["custom_llm_provider"] = custom_llm_provider + await proxy_logging_obj.post_call_failure_hook( user_api_key_dict=user_api_key_dict, original_exception=e, @@ -957,11 +969,21 @@ def _update_metadata_with_tags_in_header(request: Request, metadata: dict) -> di """ If tags are in the request headers, add them to the metadata - Used for google and vertex JS SDKs + Used for google and vertex JS SDKs, and Azure passthrough + Checks both 'tags' and 'x-litellm-tags' headers """ + # Initialize tags list if it doesn't exist + if "tags" not in metadata: + metadata["tags"] = [] + + # Check for 'tags' header first _tags = request.headers.get("tags") if _tags: - metadata["tags"] = _tags.split(",") + metadata["tags"].extend([tag.strip() for tag in _tags.split(",")]) + + _tags = request.headers.get("x-litellm-tags") + if _tags: + metadata["tags"].extend([tag.strip() for tag in _tags.split(",")]) return metadata diff --git a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_openai_passthrough_logging_handler.py b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_openai_passthrough_logging_handler.py index 789b16f9515..becb34409b1 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_openai_passthrough_logging_handler.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_openai_passthrough_logging_handler.py @@ -130,6 +130,19 @@ class TestOpenAIPassthroughLoggingHandler: assert OpenAIPassthroughLoggingHandler.is_openai_image_editing_route("http://localhost:4000/openai/v1/images/edits") == False assert OpenAIPassthroughLoggingHandler.is_openai_image_editing_route("") == False + def test_is_openai_responses_route(self): + """Test OpenAI responses API route detection""" + # Positive cases + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("https://api.openai.com/v1/responses") == True + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("https://openai.azure.com/v1/responses") == True + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("https://api.openai.com/responses") == True + + # Negative cases + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("https://api.openai.com/v1/chat/completions") == False + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("https://api.openai.com/v1/images/generations") == False + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("http://localhost:4000/openai/v1/responses") == False + assert OpenAIPassthroughLoggingHandler.is_openai_responses_route("") == False + @patch('litellm.completion_cost') @patch('litellm.litellm_core_utils.litellm_logging.get_standard_logging_object_payload') def test_openai_passthrough_handler_success(self, mock_get_standard_logging, mock_completion_cost): @@ -369,6 +382,178 @@ class TestOpenAIPassthroughLoggingHandler: handler = OpenAIPassthroughLoggingHandler() assert handler.get_provider_config("gpt-4o") is not None + @patch('litellm.completion_cost') + @patch('litellm.litellm_core_utils.litellm_logging.get_standard_logging_object_payload') + def test_azure_passthrough_tags_metadata_model_provider(self, mock_get_standard_logging, mock_completion_cost): + """Test that tags, metadata, model, and custom_llm_provider are preserved for Azure passthrough in UI""" + # Arrange + mock_completion_cost.return_value = 0.000045 + mock_get_standard_logging.return_value = {"test": "logging_payload"} + + mock_httpx_response = self._create_mock_httpx_response() + mock_logging_obj = self._create_mock_logging_obj() + + # Create payload with metadata tags + passthrough_payload = PassthroughStandardLoggingPayload( + url="https://openai.azure.com/v1/chat/completions", + request_body={ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "Hello"}] + }, + request_method="POST", + ) + + # Set up kwargs with existing litellm_params containing metadata tags + kwargs = { + "passthrough_logging_payload": passthrough_payload, + "model": "gpt-4o", + "custom_llm_provider": "azure", # Azure passthrough + "litellm_params": { + "metadata": { + "tags": ["production", "azure-deployment"], + "user_id": "user_123" + }, + "proxy_server_request": { + "body": { + "user": "test_user" + } + } + } + } + + # Act + result = OpenAIPassthroughLoggingHandler.openai_passthrough_handler( + httpx_response=mock_httpx_response, + response_body=self.mock_openai_response, + logging_obj=mock_logging_obj, + url_route="https://openai.azure.com/v1/chat/completions", + result="", + start_time=self.start_time, + end_time=self.end_time, + cache_hit=False, + request_body={"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}, + **kwargs + ) + + # Assert - Verify tags, model, and custom_llm_provider are preserved + assert result is not None + assert "kwargs" in result + + # Verify model and custom_llm_provider are set correctly + assert result["kwargs"]["model"] == "gpt-4o" + assert result["kwargs"]["custom_llm_provider"] == "azure" # Should preserve Azure, not default to "openai" + assert result["kwargs"]["response_cost"] == 0.000045 + + # Verify metadata tags are preserved in litellm_params + assert "litellm_params" in result["kwargs"] + assert "metadata" in result["kwargs"]["litellm_params"] + assert "tags" in result["kwargs"]["litellm_params"]["metadata"] + assert result["kwargs"]["litellm_params"]["metadata"]["tags"] == ["production", "azure-deployment"] + assert result["kwargs"]["litellm_params"]["metadata"]["user_id"] == "user_123" + + # Verify logging object has correct values for UI display + assert mock_logging_obj.model_call_details["model"] == "gpt-4o" + assert mock_logging_obj.model_call_details["custom_llm_provider"] == "azure" + assert mock_logging_obj.model_call_details["response_cost"] == 0.000045 + + # Verify cost calculation was called with correct custom_llm_provider + mock_completion_cost.assert_called_once() + call_args = mock_completion_cost.call_args + assert call_args[1]["custom_llm_provider"] == "azure" + + @patch('litellm.completion_cost') + @patch('litellm.litellm_core_utils.litellm_logging.get_standard_logging_object_payload') + @patch('litellm.proxy.pass_through_endpoints.llm_provider_handlers.openai_passthrough_logging_handler.OpenAIPassthroughLoggingHandler.get_provider_config') + def test_responses_api_cost_tracking(self, mock_get_provider_config, mock_get_standard_logging, mock_completion_cost): + """Test cost tracking for responses API route""" + # Arrange + mock_completion_cost.return_value = 0.000050 + mock_get_standard_logging.return_value = {"test": "logging_payload"} + + # Mock the provider config's transform_response to return a valid ModelResponse + from litellm import ModelResponse + mock_model_response = ModelResponse( + id="resp_abc123", + model="gpt-4o-2024-08-06", + choices=[{ + "message": { + "role": "assistant", + "content": "Hello! How can I help you today?" + } + }], + usage={ + "prompt_tokens": 20, + "completion_tokens": 15, + "total_tokens": 35 + } + ) + + mock_provider_config = MagicMock() + mock_provider_config.transform_response.return_value = mock_model_response + mock_get_provider_config.return_value = mock_provider_config + + # Mock responses API response + mock_responses_response = { + "id": "resp_abc123", + "object": "response", + "created": 1677652288, + "model": "gpt-4o-2024-08-06", + "output": [ + { + "type": "text", + "text": "Hello! How can I help you today?" + } + ], + "usage": { + "input_tokens": 20, + "output_tokens": 15 + } + } + + mock_httpx_response = self._create_mock_httpx_response(mock_responses_response) + mock_logging_obj = self._create_mock_logging_obj() + passthrough_payload = self._create_passthrough_logging_payload() + + kwargs = { + "passthrough_logging_payload": passthrough_payload, + "model": "gpt-4o", + "custom_llm_provider": "openai", + } + + # Act + result = OpenAIPassthroughLoggingHandler.openai_passthrough_handler( + httpx_response=mock_httpx_response, + response_body=mock_responses_response, + logging_obj=mock_logging_obj, + url_route="https://api.openai.com/v1/responses", + result="", + start_time=self.start_time, + end_time=self.end_time, + cache_hit=False, + request_body={"model": "gpt-4o", "input": "Tell me about AI"}, + **kwargs + ) + + # Assert + assert result is not None + assert "result" in result + assert "kwargs" in result + assert result["kwargs"]["response_cost"] == 0.000050 + assert result["kwargs"]["model"] == "gpt-4o" + assert result["kwargs"]["custom_llm_provider"] == "openai" + + # Verify cost calculation was called with responses call type + mock_completion_cost.assert_called_once() + call_args = mock_completion_cost.call_args + assert call_args[1]["call_type"] == "responses" + assert call_args[1]["model"] == "gpt-4o" + assert call_args[1]["custom_llm_provider"] == "openai" + + # Verify logging object was updated + assert mock_logging_obj.model_call_details["response_cost"] == 0.000050 + assert mock_logging_obj.model_call_details["model"] == "gpt-4o" + assert mock_logging_obj.model_call_details["custom_llm_provider"] == "openai" + class TestOpenAIPassthroughIntegration: """Integration tests for OpenAI passthrough cost tracking""" From eab0ec95f01a61f14c3d441fdcd7960a49a003fd Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 28 Nov 2025 18:06:53 +0530 Subject: [PATCH 204/349] Fix async get request --- litellm/batches/main.py | 44 +++++++++++++------ .../embed/amazon_nova_transformation.py | 2 + litellm/llms/bedrock/embed/embedding.py | 39 +++++++++++----- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/litellm/batches/main.py b/litellm/batches/main.py index 995c45b925a..57a9857dd6a 100644 --- a/litellm/batches/main.py +++ b/litellm/batches/main.py @@ -1045,28 +1045,44 @@ def _handle_async_invoke_status( # Transform response to a LiteLLMBatch object from litellm.types.utils import LiteLLMBatch + # Normalize status to lowercase (AWS returns 'Completed', 'Failed', etc.) + aws_status_raw = status_response.get("status", "") + aws_status_lower = aws_status_raw.lower() + # Map AWS status values to LiteLLM expected values + status_mapping = { + "completed": "completed", + "failed": "failed", + "inprogress": "in_progress", + "in_progress": "in_progress", + } + normalized_status = status_mapping.get(aws_status_lower, aws_status_lower) + + # Get output S3 URI safely + output_s3_uri = "" + try: + output_s3_uri = status_response["outputDataConfig"]["s3OutputDataConfig"]["s3Uri"] + except (KeyError, TypeError): + pass + + # Use BedrockBatchesConfig's timestamp parsing method (expects raw AWS status string) + from litellm.llms.bedrock.batches.transformation import BedrockBatchesConfig + created_at, in_progress_at, completed_at, failed_at, _, _ = BedrockBatchesConfig()._parse_timestamps_and_status(status_response, aws_status_raw) result = LiteLLMBatch( id=status_response["invocationArn"], object="batch", - status=status_response["status"], - created_at=status_response["submitTime"], - in_progress_at=status_response["lastModifiedTime"], - completed_at=status_response.get("endTime"), - failed_at=( - status_response.get("endTime") - if status_response["status"] == "failed" - else None - ), + status=normalized_status, + created_at=created_at, + in_progress_at=in_progress_at, + completed_at=completed_at, + failed_at=failed_at, request_counts=BatchRequestCounts( total=1, - completed=1 if status_response["status"] == "completed" else 0, - failed=1 if status_response["status"] == "failed" else 0, + completed=1 if normalized_status == "completed" else 0, + failed=1 if normalized_status == "failed" else 0, ), metadata=dict( **{ - "output_file_id": status_response["outputDataConfig"][ - "s3OutputDataConfig" - ]["s3Uri"], + "output_file_id": output_s3_uri, "failure_message": status_response.get("failureMessage") or "", "model_arn": status_response["modelArn"], } diff --git a/litellm/llms/bedrock/embed/amazon_nova_transformation.py b/litellm/llms/bedrock/embed/amazon_nova_transformation.py index 97652175a94..ada49d0ff21 100644 --- a/litellm/llms/bedrock/embed/amazon_nova_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_nova_transformation.py @@ -83,6 +83,8 @@ class AmazonNovaEmbeddingConfig: # Start with inference_params (user-provided params) embedding_params = inference_params.copy() + embedding_params.pop("output_s3_uri", None) + # Map OpenAI dimensions to embeddingDimension if provided if "dimensions" in embedding_params: embedding_params["embeddingDimension"] = embedding_params.pop("dimensions") diff --git a/litellm/llms/bedrock/embed/embedding.py b/litellm/llms/bedrock/embed/embedding.py index be2bfcd70ec..c9eea516eb0 100644 --- a/litellm/llms/bedrock/embed/embedding.py +++ b/litellm/llms/bedrock/embed/embedding.py @@ -603,22 +603,39 @@ class BedrockEmbedding(BaseAWSLLM): aws_region_name=aws_region_name, ) - # Construct the status check URL - status_url = f"{endpoint_url}/async-invoke/{invocation_arn}" - # Prepare headers + from urllib.parse import quote + + # Encode the ARN for use in URL path + encoded_arn = quote(invocation_arn, safe="") + status_url = f"{endpoint_url.rstrip('/')}/async-invoke/{encoded_arn}" + + # Prepare headers for GET request headers = {"Content-Type": "application/json"} - # Get AWS signed headers - prepped = self.get_request_headers( # type: ignore - credentials=credentials, - aws_region_name=aws_region_name, - extra_headers=None, - endpoint_url=status_url, - data="", # GET request, no body + # Use AWSRequest directly for GET requests (get_request_headers hardcodes POST) + try: + from botocore.auth import SigV4Auth + from botocore.awsrequest import AWSRequest + except ImportError: + raise ImportError( + "Missing boto3 to call bedrock. Run 'pip install boto3'." + ) + + # Create AWSRequest with GET method and encoded URL + request = AWSRequest( + method="GET", + url=status_url, + data=None, # GET request, no body headers=headers, - api_key=None, ) + + # Sign the request - SigV4Auth will create canonical string from request URL + sigv4 = SigV4Auth(credentials, "bedrock", aws_region_name) + sigv4.add_auth(request) + + # Prepare the request + prepped = request.prepare() # LOGGING if logging_obj is not None: From 4cf7a74e6092b7f73d4da12f1bdd3d57e3857a86 Mon Sep 17 00:00:00 2001 From: abi_jey Date: Fri, 28 Nov 2025 14:27:57 +0000 Subject: [PATCH 205/349] fix: Azure OpenAI GA path relies soley on model paramter as deployment --- litellm/llms/azure/realtime/handler.py | 10 ++++++---- .../azure/realtime/test_azure_realtime_handler.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/litellm/llms/azure/realtime/handler.py b/litellm/llms/azure/realtime/handler.py index 0dc42dad43e..217a05c83a4 100644 --- a/litellm/llms/azure/realtime/handler.py +++ b/litellm/llms/azure/realtime/handler.py @@ -12,6 +12,7 @@ from ....litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from ....litellm_core_utils.realtime_streaming import RealTimeStreaming from ....llms.custom_httpx.http_handler import get_shared_realtime_ssl_context from ..azure import AzureChatCompletion +from litellm._logging import verbose_proxy_logger # BACKEND_WS_URL = "ws://localhost:8080/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01" @@ -51,18 +52,18 @@ class AzureOpenAIRealtime(AzureChatCompletion): Examples: beta/default: "wss://.../openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview" - GA/v1: "wss://.../openai/v1/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview" + GA/v1: "wss://.../openai/v1/realtime?model=gpt-realtime-deployment" """ api_base = api_base.replace("https://", "wss://") # Determine path based on realtime_protocol if realtime_protocol in ("GA", "v1"): - path = "/openai/v1/realtime" + path = "/openai/v1/realtime" + return f"{api_base}{path}?model={model}" else: # Default to beta path for backwards compatibility path = "/openai/realtime" - - return f"{api_base}{path}?api-version={api_version}&deployment={model}" + return f"{api_base}{path}?api-version={api_version}&deployment={model}" async def async_realtime( self, @@ -107,4 +108,5 @@ class AzureOpenAIRealtime(AzureChatCompletion): except websockets.exceptions.InvalidStatusCode as e: # type: ignore await websocket.close(code=e.status_code, reason=str(e)) except Exception: + verbose_proxy_logger.exception("Error in AzureOpenAIRealtime.async_realtime") pass diff --git a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py index ca8d01e158f..2a110c8f9a7 100644 --- a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py +++ b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py @@ -117,6 +117,7 @@ async def test_construct_url_beta_protocol_explicit(): async def test_construct_url_ga_protocol(): """ Test that realtime_protocol='GA' uses /openai/v1/realtime (GA path). + GA path uses ?model= instead of ?api-version=&deployment= format. """ from litellm.llms.azure.realtime.handler import AzureOpenAIRealtime @@ -132,8 +133,10 @@ async def test_construct_url_ga_protocol(): assert "/openai/v1/realtime?" in url # Ensure it doesn't have both paths assert url.count("/realtime") == 1 - assert "api-version=2024-10-01-preview" in url - assert "deployment=gpt-4o-realtime-preview" in url + # GA path uses model= query param, not api-version and deployment + assert "model=gpt-4o-realtime-preview" in url + assert "api-version" not in url + assert "deployment" not in url @pytest.mark.asyncio @@ -203,8 +206,10 @@ async def test_async_realtime_uses_ga_protocol_end_to_end(): called_url = mock_ws_connect.call_args[0][0] assert "/openai/v1/realtime" in called_url assert called_url.startswith("wss://") - assert "api-version=2024-10-01-preview" in called_url - assert "deployment=gpt-4o-realtime-preview" in called_url + # GA path uses model= query param, not api-version and deployment + assert "model=gpt-4o-realtime-preview" in called_url + assert "api-version" not in called_url + assert "deployment" not in called_url @pytest.mark.asyncio From af8f1475bfd709e7126847e15831abe17a72df2b Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 28 Nov 2025 21:12:42 +0530 Subject: [PATCH 206/349] fix PLR0915 --- litellm/llms/bedrock/embed/embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/bedrock/embed/embedding.py b/litellm/llms/bedrock/embed/embedding.py index c9eea516eb0..7152d7ce15c 100644 --- a/litellm/llms/bedrock/embed/embedding.py +++ b/litellm/llms/bedrock/embed/embedding.py @@ -377,7 +377,7 @@ class BedrockEmbedding(BaseAWSLLM): is_async_invoke=is_async_invoke, ) - def embeddings( + def embeddings( # noqa: PLR0915 self, model: str, input: List[str], From 9d058398dfdacf65791cea3cd3ed34ee0427d8e1 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 28 Nov 2025 21:41:25 +0530 Subject: [PATCH 207/349] Fix pegasus response and add doc --- docs/my-website/docs/providers/bedrock.md | 127 +++++++++++++++ ...mazon_twelvelabs_pegasus_transformation.py | 151 +++++++++++++++++- .../base_invoke_transformation.py | 22 +++ .../test_twelvelabs_pegasus_transformation.py | 4 +- 4 files changed, 301 insertions(+), 3 deletions(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 9e22f67527e..a9ac85a7571 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -1683,6 +1683,131 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ +## TwelveLabs Pegasus - Video Understanding + +TwelveLabs Pegasus 1.2 is a video understanding model that can analyze and describe video content. LiteLLM supports this model through Bedrock's `/invoke` endpoint. + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/us.twelvelabs.pegasus-1-2-v1:0`, `bedrock/eu.twelvelabs.pegasus-1-2-v1:0` | +| Provider Documentation | [TwelveLabs Pegasus Docs ↗](https://docs.twelvelabs.io/docs/models/pegasus) | +| Supported Parameters | `max_tokens`, `temperature`, `response_format` | +| Media Input | S3 URI or base64-encoded video | + +### Supported Features + +- **Video Analysis**: Analyze video content from S3 or base64 input +- **Structured Output**: Support for JSON schema response format +- **S3 Integration**: Support for S3 video URLs with bucket owner specification + +### Usage with S3 Video + + + + +```python title="TwelveLabs Pegasus SDK Usage" showLineNumbers +from litellm import completion +import os + +# Set AWS credentials +os.environ["AWS_ACCESS_KEY_ID"] = "your-aws-access-key" +os.environ["AWS_SECRET_ACCESS_KEY"] = "your-aws-secret-key" +os.environ["AWS_REGION_NAME"] = "us-east-1" + +response = completion( + model="bedrock/us.twelvelabs.pegasus-1-2-v1:0", + messages=[{"role": "user", "content": "Describe what happens in this video."}], + mediaSource={ + "s3Location": { + "uri": "s3://your-bucket/video.mp4", + "bucketOwner": "123456789012", # 12-digit AWS account ID + } + }, + temperature=0.2 +) + +print(response.choices[0].message.content) +``` + + + + + +**1. Add to config** + +```yaml title="config.yaml" showLineNumbers +model_list: + - model_name: pegasus-video + litellm_params: + model: bedrock/us.twelvelabs.pegasus-1-2-v1:0 + aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID + aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY + aws_region_name: os.environ/AWS_REGION_NAME +``` + +**2. Start proxy** + +```bash title="Start LiteLLM Proxy" showLineNumbers +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +```bash title="Test Pegasus via Proxy" showLineNumbers +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "pegasus-video", + "messages": [ + { + "role": "user", + "content": "Describe what happens in this video." + } + ], + "mediaSource": { + "s3Location": { + "uri": "s3://your-bucket/video.mp4", + "bucketOwner": "123456789012" + } + }, + "temperature": 0.2 + }' +``` + + + + +### Usage with Base64 Video + +You can also pass video content directly as base64: + +```python title="Base64 Video Input" showLineNumbers +from litellm import completion +import base64 + +# Read video file and encode to base64 +with open("video.mp4", "rb") as video_file: + video_base64 = base64.b64encode(video_file.read()).decode("utf-8") + +response = completion( + model="bedrock/us.twelvelabs.pegasus-1-2-v1:0", + messages=[{"role": "user", "content": "What is happening in this video?"}], + mediaSource={ + "base64String": video_base64 + }, + temperature=0.2, +) + +print(response.choices[0].message.content) +``` + +### Important Notes + +- **Response Format**: The model supports structured output via `response_format` with JSON schema + ## Provisioned throughput models To use provisioned throughput Bedrock models pass - `model=bedrock/`, example `model=bedrock/anthropic.claude-v2`. Set `model` to any of the [Supported AWS models](#supported-aws-bedrock-models) @@ -1743,6 +1868,8 @@ Here's an example of using a bedrock model with LiteLLM. For a complete list, re | Meta Llama 2 Chat 70b | `completion(model='bedrock/meta.llama2-70b-chat-v1', messages=messages)` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` | | Mistral 7B Instruct | `completion(model='bedrock/mistral.mistral-7b-instruct-v0:2', messages=messages)` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` | | Mixtral 8x7B Instruct | `completion(model='bedrock/mistral.mixtral-8x7b-instruct-v0:1', messages=messages)` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` | +| TwelveLabs Pegasus 1.2 (US) | `completion(model='bedrock/us.twelvelabs.pegasus-1-2-v1:0', messages=messages, mediaSource={...})` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` | +| TwelveLabs Pegasus 1.2 (EU) | `completion(model='bedrock/eu.twelvelabs.pegasus-1-2-v1:0', messages=messages, mediaSource={...})` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` | ## Bedrock Embedding diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py index 7b72968ea3d..62e98f7472f 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_twelvelabs_pegasus_transformation.py @@ -5,16 +5,32 @@ Reference: https://docs.twelvelabs.io/docs/models/pegasus """ -from typing import Any, Dict, List, Optional +import json +import time +from typing import TYPE_CHECKING, Any, Dict, List, Optional +import httpx + +import litellm +from litellm._logging import verbose_logger +from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.base_llm.base_utils import type_to_response_format_param from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( AmazonInvokeConfig, ) +from litellm.llms.bedrock.common_utils import BedrockError from litellm.types.llms.openai import AllMessageValues +from litellm.types.utils import ModelResponse, Usage from litellm.utils import get_base64_str +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + class AmazonTwelveLabsPegasusConfig(AmazonInvokeConfig, BaseConfig): """ @@ -53,7 +69,35 @@ class AmazonTwelveLabsPegasusConfig(AmazonInvokeConfig, BaseConfig): return optional_params def _normalize_response_format(self, value: Any) -> Any: + """Normalize response_format to TwelveLabs format. + + TwelveLabs expects: + { + "jsonSchema": {...} + } + + But OpenAI format is: + { + "type": "json_schema", + "json_schema": { + "name": "...", + "schema": {...} + } + } + """ if isinstance(value, dict): + # If it has json_schema field, extract and transform it + if "json_schema" in value: + json_schema = value["json_schema"] + # Extract the schema if nested + if isinstance(json_schema, dict) and "schema" in json_schema: + return {"jsonSchema": json_schema["schema"]} + # Otherwise use json_schema directly + return {"jsonSchema": json_schema} + # If it already has jsonSchema, return as is + if "jsonSchema" in value: + return value + # Otherwise return the dict as is return value return type_to_response_format_param(response_format=value) or value @@ -72,9 +116,18 @@ class AmazonTwelveLabsPegasusConfig(AmazonInvokeConfig, BaseConfig): if media_source is not None: request_data["mediaSource"] = media_source - for key in ("temperature", "maxOutputTokens", "responseFormat"): + # Handle temperature and maxOutputTokens + for key in ("temperature", "maxOutputTokens"): if key in optional_params: request_data[key] = optional_params.get(key) + + # Handle responseFormat - transform to TwelveLabs format + if "responseFormat" in optional_params: + response_format = optional_params["responseFormat"] + transformed_format = self._normalize_response_format(response_format) + if transformed_format: + request_data["responseFormat"] = transformed_format + return request_data def _build_media_source(self, optional_params: dict) -> Optional[dict]: @@ -131,3 +184,97 @@ class AmazonTwelveLabsPegasusConfig(AmazonInvokeConfig, BaseConfig): prompt_parts.append(f"{role}: {content}") return "\n".join(part for part in prompt_parts if part).strip() + def transform_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ModelResponse: + """ + Transform TwelveLabs Pegasus response to LiteLLM format. + + TwelveLabs response format: + { + "message": "...", + "finishReason": "stop" | "length" + } + + LiteLLM format: + ModelResponse with choices[0].message.content and finish_reason + """ + try: + completion_response = raw_response.json() + except Exception as e: + raise BedrockError( + message=f"Error parsing response: {raw_response.text}, error: {str(e)}", + status_code=raw_response.status_code, + ) + + verbose_logger.debug( + "twelvelabs pegasus response: %s", + json.dumps(completion_response, indent=4, default=str), + ) + + # Extract message content + message_content = completion_response.get("message", "") + + # Extract finish reason and map to LiteLLM format + finish_reason_raw = completion_response.get("finishReason", "stop") + finish_reason = map_finish_reason(finish_reason_raw) + + # Set the response content + try: + if ( + message_content + and hasattr(model_response.choices[0], "message") + and getattr(model_response.choices[0].message, "tool_calls", None) is None + ): + model_response.choices[0].message.content = message_content # type: ignore + model_response.choices[0].finish_reason = finish_reason + else: + raise Exception("Unable to set message content") + except Exception as e: + raise BedrockError( + message=f"Error setting response content: {str(e)}. Response: {completion_response}", + status_code=raw_response.status_code, + ) + + # Calculate usage from headers + bedrock_input_tokens = raw_response.headers.get( + "x-amzn-bedrock-input-token-count", None + ) + bedrock_output_tokens = raw_response.headers.get( + "x-amzn-bedrock-output-token-count", None + ) + + prompt_tokens = int( + bedrock_input_tokens or litellm.token_counter(messages=messages) + ) + + completion_tokens = int( + bedrock_output_tokens + or litellm.token_counter( + text=model_response.choices[0].message.content, # type: ignore + count_response_tokens=True, + ) + ) + + model_response.created = int(time.time()) + model_response.model = model + usage = Usage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=prompt_tokens + completion_tokens, + ) + setattr(model_response, "usage", usage) + + return model_response + diff --git a/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py index e6146f1064e..6c389ff3b7d 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py @@ -250,6 +250,14 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM): ): # completion(top_k=3) > anthropic_config(top_k=3) <- allows for dynamic variables to be passed in inference_params[k] = v request_data = {"prompt": prompt, **inference_params} + elif provider == "twelvelabs": + return litellm.AmazonTwelveLabsPegasusConfig().transform_request( + model=model, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + headers=headers, + ) else: raise BedrockError( status_code=404, @@ -321,6 +329,20 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM): litellm_params=litellm_params, encoding=encoding, ) + elif provider == "twelvelabs": + return litellm.AmazonTwelveLabsPegasusConfig().transform_response( + model=model, + raw_response=raw_response, + model_response=model_response, + logging_obj=logging_obj, + request_data=request_data, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + encoding=encoding, + api_key=api_key, + json_mode=json_mode, + ) elif provider == "ai21": outputText = ( completion_response.get("completions")[0].get("data").get("text") diff --git a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_twelvelabs_pegasus_transformation.py b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_twelvelabs_pegasus_transformation.py index 9063c0f1c94..f2cf6f9857c 100644 --- a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_twelvelabs_pegasus_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_twelvelabs_pegasus_transformation.py @@ -38,7 +38,9 @@ def test_map_openai_params_translates_fields(): assert optional_params["maxOutputTokens"] == 20 assert optional_params["temperature"] == 0.6 assert "responseFormat" in optional_params - assert optional_params["responseFormat"]["json_schema"]["name"] == "video_schema" + # TwelveLabs format: responseFormat contains jsonSchema directly (not json_schema) + assert "jsonSchema" in optional_params["responseFormat"] + assert optional_params["responseFormat"]["jsonSchema"]["type"] == "object" def test_transform_request_includes_base64_media(): From b85df0b1fb38abfc01255db50f49f066bf61ae14 Mon Sep 17 00:00:00 2001 From: hxomer <164746029+hxomer@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:22:19 +0200 Subject: [PATCH 208/349] Better handle anonymization (#17207) * Better handle anonymization * Fix tests --- .../guardrails/guardrail_hooks/aim/aim.py | 37 +++++-------------- tests/local_testing/test_aim_guardrails.py | 24 ++++++------ 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/aim/aim.py b/litellm/proxy/guardrails/guardrail_hooks/aim/aim.py index 3a0b0c3202b..7711a934998 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/aim/aim.py +++ b/litellm/proxy/guardrails/guardrail_hooks/aim/aim.py @@ -117,7 +117,7 @@ class AimGuardrail(CustomGuardrail): self._handle_block_action(res["analysis_result"], required_action) elif action_type == "anonymize_action": return self._anonymize_request( - res["analysis_result"], required_action, data + res, data ) else: verbose_proxy_logger.error(f"Aim: {action_type} action") @@ -133,27 +133,18 @@ class AimGuardrail(CustomGuardrail): raise HTTPException(status_code=400, detail=detection_message) def _anonymize_request( - self, analysis_result: Any, required_action: Any, data: dict + self, res: Any, data: dict ) -> dict: verbose_proxy_logger.info("Aim: anonymize action") - redaction_result = required_action and required_action.get( - "chat_redaction_result" - ) - if not redaction_result: + redacted_chat = res.get("redacted_chat") + if not redacted_chat: return data - if analysis_result and analysis_result.get("session_entities"): - self._set_dlp_entities(analysis_result.get("session_entities")) data["messages"] = [ - { - "role": redaction_result["redacted_new_message"]["role"], - "content": redaction_result["redacted_new_message"]["content"], - } - ] + [ { "role": message["role"], "content": message["content"], } - for message in redaction_result["all_redacted_messages"] + for message in redacted_chat["all_redacted_messages"] ] return data @@ -185,7 +176,11 @@ class AimGuardrail(CustomGuardrail): return self._handle_block_action_on_output( res["analysis_result"], required_action ) - return self._deanonymize_output(output) + redacted_chat = res.get("redacted_chat", None) + + if action_type and action_type == "anonymize_action" and redacted_chat: + return {"redacted_output": redacted_chat["all_redacted_messages"][-1]["content"]} + return {"redacted_output": output} def _handle_block_action_on_output( self, analysis_result: Any, required_action: Any @@ -199,15 +194,6 @@ class AimGuardrail(CustomGuardrail): ) return {"detection_message": detection_message} - def _deanonymize_output(self, output: str) -> dict | None: - try: - for entity in self.dlp_entities: - output = output.replace(f"[{entity['name']}]", entity["content"]) - return {"redacted_output": output} - except Exception as e: - verbose_proxy_logger.error(f"Aim: Error while redacting output: {e}") - return None - def _build_aim_headers( self, *, @@ -323,9 +309,6 @@ class AimGuardrail(CustomGuardrail): await websocket.send(chunk) await websocket.send(json.dumps({"done": True})) - def _set_dlp_entities(self, entities: list[dict]) -> None: - self.dlp_entities = entities[: self._max_dlp_entities] - @staticmethod def get_config_model() -> Optional[Type["GuardrailConfigModel"]]: from litellm.types.proxy.guardrails.guardrail_hooks.aim import ( diff --git a/tests/local_testing/test_aim_guardrails.py b/tests/local_testing/test_aim_guardrails.py index b271875c2ec..f24b74e5110 100644 --- a/tests/local_testing/test_aim_guardrails.py +++ b/tests/local_testing/test_aim_guardrails.py @@ -443,23 +443,23 @@ response_with_detections = Response( "required_action": { "action_type": "anonymize_action", "policy_name": "PII", - "chat_redaction_result": { - "all_redacted_messages": [ - { - "content": "Hi my name is [NAME_1]", - "role": "user", - "additional_contents": [], - "received_message_id": "0", - "extra_fields": {}, - } - ], - "redacted_new_message": { + }, + "redacted_chat": { + "all_redacted_messages": [ + { "content": "Hi my name is [NAME_1]", "role": "user", "additional_contents": [], "received_message_id": "0", "extra_fields": {}, - }, + } + ], + "redacted_new_message": { + "content": "Hi my name is [NAME_1]", + "role": "user", + "additional_contents": [], + "received_message_id": "0", + "extra_fields": {}, }, }, }, From f7380a51de3aa1823194a8b23ed1a60f2818f190 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 08:36:29 +0530 Subject: [PATCH 209/349] Respect custom llm provider in header --- litellm/proxy/batches_endpoints/endpoints.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/batches_endpoints/endpoints.py b/litellm/proxy/batches_endpoints/endpoints.py index 98492bcc2d6..03b9ac3deaa 100644 --- a/litellm/proxy/batches_endpoints/endpoints.py +++ b/litellm/proxy/batches_endpoints/endpoints.py @@ -31,7 +31,6 @@ from litellm.proxy.openai_files_endpoints.common_utils import ( get_original_file_id, prepare_data_with_credentials, ) - from litellm.proxy.utils import handle_exception_on_proxy, is_known_model from litellm.types.llms.openai import LiteLLMBatchCreateRequest @@ -112,7 +111,10 @@ async def create_batch( # noqa: PLR0915 is_router_model = is_known_model(model=router_model, llm_router=llm_router) custom_llm_provider = ( - provider or data.pop("custom_llm_provider", None) or "openai" + provider + or data.pop("custom_llm_provider", None) + or get_custom_llm_provider_from_request_headers(request=request) + or "openai" ) _create_batch_data = LiteLLMBatchCreateRequest(**data) input_file_id = _create_batch_data.get("input_file_id", None) From 9edc50efbd117b38e54c038cbc1af1dd32572206 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 10:21:44 +0530 Subject: [PATCH 210/349] Fix 500 error for malformed request --- litellm/proxy/common_request_processing.py | 19 ++++++++- tests/proxy_unit_tests/test_proxy_server.py | 44 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index d2b04410026..1c6c9b97173 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -809,7 +809,24 @@ class ProxyBaseLLMRequestProcessing: status_code=e.response.status_code, detail={"error": error_text}, ) - error_msg = f"{str(e)}" + error_msg = f"{str(e)}" + # Check for AttributeError in various places: + # 1. Direct AttributeError (already handled above) + # 2. In underlying exception (__cause__, __context__, original_exception) + has_attribute_error = ( + (isinstance(e, Exception) and isinstance(getattr(e, "__cause__", None), AttributeError)) + or (isinstance(e, Exception) and isinstance(getattr(e, "__context__", None), AttributeError)) + or (isinstance(e, Exception) and isinstance(getattr(e, "original_exception", None), AttributeError)) + ) + + if has_attribute_error: + raise ProxyException( + message=f"Invalid request format: {error_msg}", + type="invalid_request_error", + param=None, + code=status.HTTP_400_BAD_REQUEST, + headers=headers, + ) raise ProxyException( message=getattr(e, "message", error_msg), type=getattr(e, "type", "None"), diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index 6dad7cb08d0..dc34a50f87e 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -175,6 +175,50 @@ def test_chat_completion(mock_acompletion, client_no_auth): pytest.fail(f"LiteLLM Proxy test failed. Exception - {str(e)}") +def test_chat_completion_malformed_messages_returns_400(client_no_auth): + """ + Test that malformed messages (strings instead of dicts) return 400 instead of 500. + + This test verifies that when a client sends messages as raw strings instead of + {role, content} objects, LiteLLM returns a 400 invalid_request_error instead + of a 500 Internal Server Error. + """ + global headers + try: + # Test data with malformed messages (string instead of dict) + test_data = { + "model": "gpt-3.5-turbo", + "messages": ["hi how are you"], # Invalid: should be [{"role": "user", "content": "hi how are you"}] + } + + print("testing proxy server with malformed messages") + response = client_no_auth.post("/v1/chat/completions", json=test_data, headers=headers) + + print(f"response status: {response.status_code}") + print(f"response text: {response.text}") + + # Should return 400, not 500 + assert response.status_code == 400, f"Expected 400, got {response.status_code}. Response: {response.text}" + + # Verify error format + result = response.json() + assert "error" in result, "Response should contain 'error' key" + error = result["error"] + + # Verify error type and message + assert error.get("type") == "invalid_request_error" or error.get("type") is None, \ + f"Expected invalid_request_error or None, got {error.get('type')}" + assert error.get("code") == "400" or error.get("code") == 400, \ + f"Expected code 400, got {error.get('code')}" + + # Error message should indicate invalid request format + error_message = error.get("message", "") + assert len(error_message) > 0, "Error message should not be empty" + + except Exception as e: + pytest.fail(f"LiteLLM Proxy test failed. Exception - {str(e)}") + + def test_get_settings_request_timeout(client_no_auth): """ When no timeout is set, it should use the litellm.request_timeout value From 02510a908f389055ccf55db0a59a141a49bc222c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 10:42:38 +0530 Subject: [PATCH 211/349] Add better handling image generation for gemini models --- .../llms/gemini/image_generation/transformation.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/litellm/llms/gemini/image_generation/transformation.py b/litellm/llms/gemini/image_generation/transformation.py index e79414394fa..2d8d82e6ad8 100644 --- a/litellm/llms/gemini/image_generation/transformation.py +++ b/litellm/llms/gemini/image_generation/transformation.py @@ -21,12 +21,6 @@ else: LiteLLMLoggingObj = Any -FLASH_IMAGE_PREVIEW_MODEL_IDENTIFIERS = ( - "2.0-flash-preview-image", - "2.0-flash-preview-image-generation", - "2.5-flash-image-preview", - "3-pro-image-preview", -) class GoogleImageGenConfig(BaseImageGenerationConfig): DEFAULT_BASE_URL: str = "https://generativelanguage.googleapis.com/v1beta" @@ -104,7 +98,7 @@ class GoogleImageGenConfig(BaseImageGenerationConfig): complete_url = complete_url.rstrip("/") # Gemini Flash Image Preview models use generateContent endpoint - if any(identifier in model for identifier in FLASH_IMAGE_PREVIEW_MODEL_IDENTIFIERS): + if "gemini" in model: complete_url = f"{complete_url}/models/{model}:generateContent" else: # All other Imagen models use predict endpoint @@ -159,7 +153,7 @@ class GoogleImageGenConfig(BaseImageGenerationConfig): } """ # For Gemini Flash Image Preview models, use standard Gemini format - if any(identifier in model for identifier in FLASH_IMAGE_PREVIEW_MODEL_IDENTIFIERS): + if "gemini" in model: request_body: dict = { "contents": [ { @@ -218,7 +212,7 @@ class GoogleImageGenConfig(BaseImageGenerationConfig): model_response.data = [] # Handle different response formats based on model - if any(identifier in model for identifier in FLASH_IMAGE_PREVIEW_MODEL_IDENTIFIERS): + if "gemini" in model: # Gemini Flash Image Preview models return in candidates format candidates = response_data.get("candidates", []) for candidate in candidates: From 7dac498efbaf9142c44ba560e736baa28f1223c2 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 14:33:03 +0530 Subject: [PATCH 212/349] Add passthrough cost tracking for veo --- litellm/proxy/_types.py | 2 + .../llm_passthrough_endpoints.py | 9 +-- .../gemini_passthrough_logging_handler.py | 38 +++++++++ .../vertex_passthrough_logging_handler.py | 43 +++++++++- .../pass_through_endpoints.py | 26 +++++- .../pass_through_endpoints/success_handler.py | 4 +- ...test_gemini_passthrough_logging_handler.py | 79 ++++++++++++++++++- 7 files changed, 191 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 9e915d4bc5a..fe87a70b244 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -47,6 +47,7 @@ from litellm.types.utils import ( StandardPassThroughResponseObject, TextCompletionResponse, ) +from litellm.types.videos.main import VideoObject from .types_utils.utils import get_instance_fn, validate_custom_validate_return_type @@ -3275,6 +3276,7 @@ PassThroughEndpointLoggingResultValues = Union[ TextCompletionResponse, ImageResponse, EmbeddingResponse, + VideoObject, StandardPassThroughResponseObject, ] diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 7afb6868c73..d1294f996ec 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -187,13 +187,10 @@ async def gemini_proxy_route( """ [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" - ) - + # Get LiteLLM API key from Authorization header for authentication + api_key_to_use = get_litellm_virtual_key(request=request) user_api_key_dict = await user_api_key_auth( - request=request, api_key=f"Bearer {google_ai_studio_api_key}" + request=request, api_key=api_key_to_use ) base_target_url = ( diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/gemini_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/gemini_passthrough_logging_handler.py index 16e8d5b4349..2bda9ba4856 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/gemini_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/gemini_passthrough_logging_handler.py @@ -7,6 +7,7 @@ import httpx import litellm from litellm._logging import verbose_proxy_logger from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.gemini.videos.transformation import GeminiVideoConfig from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( ModelResponseIterator as GeminiModelResponseIterator, ) @@ -39,6 +40,43 @@ class GeminiPassthroughLoggingHandler: request_body: dict, **kwargs, ) -> PassThroughEndpointLoggingTypedDict: + if "predictLongRunning" in url_route: + model = GeminiPassthroughLoggingHandler.extract_model_from_url(url_route) + + gemini_video_config = GeminiVideoConfig() + litellm_video_response = gemini_video_config.transform_video_create_response( + model=model, + raw_response=httpx_response, + logging_obj=logging_obj, + custom_llm_provider="gemini", + request_data=request_body, + ) + logging_obj.model = model + logging_obj.model_call_details["model"] = model + logging_obj.model_call_details["custom_llm_provider"] = "gemini" + logging_obj.custom_llm_provider = "gemini" + + response_cost = litellm.completion_cost( + completion_response=litellm_video_response, + model=model, + custom_llm_provider="gemini", + call_type="create_video", + ) + + # Set response_cost in _hidden_params to prevent recalculation + if not hasattr(litellm_video_response, "_hidden_params"): + litellm_video_response._hidden_params = {} + litellm_video_response._hidden_params["response_cost"] = response_cost + + kwargs["response_cost"] = response_cost + kwargs["model"] = model + kwargs["custom_llm_provider"] = "gemini" + logging_obj.model_call_details["response_cost"] = response_cost + return { + "result": litellm_video_response, + "kwargs": kwargs, + } + if "generateContent" in url_route: model = GeminiPassthroughLoggingHandler.extract_model_from_url(url_route) diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py index b34a6f455c3..0962fafe3f6 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py @@ -14,6 +14,7 @@ from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( from litellm.llms.vertex_ai.vector_stores.search_api.transformation import ( VertexSearchAPIVectorStoreConfig, ) +from litellm.llms.vertex_ai.videos.transformation import VertexAIVideoConfig from litellm.proxy._types import PassThroughEndpointLoggingTypedDict from litellm.types.utils import ( Choices, @@ -49,9 +50,49 @@ class VertexPassthroughLoggingHandler: start_time: datetime, end_time: datetime, cache_hit: bool, + request_body: Optional[dict] = None, **kwargs, ) -> PassThroughEndpointLoggingTypedDict: - if "generateContent" in url_route: + if "predictLongRunning" in url_route: + model = VertexPassthroughLoggingHandler.extract_model_from_url(url_route) + + vertex_video_config = VertexAIVideoConfig() + litellm_video_response = vertex_video_config.transform_video_create_response( + model=model, + raw_response=httpx_response, + logging_obj=logging_obj, + custom_llm_provider="vertex_ai", + request_data=request_body, + ) + + logging_obj.model = model + logging_obj.model_call_details["model"] = model + logging_obj.model_call_details["custom_llm_provider"] = "vertex_ai" + logging_obj.custom_llm_provider = "vertex_ai" + + response_cost = litellm.completion_cost( + completion_response=litellm_video_response, + model=model, + custom_llm_provider="vertex_ai", + call_type="create_video", + ) + + # Set response_cost in _hidden_params to prevent recalculation + if not hasattr(litellm_video_response, "_hidden_params"): + litellm_video_response._hidden_params = {} + litellm_video_response._hidden_params["response_cost"] = response_cost + + kwargs["response_cost"] = response_cost + kwargs["model"] = model + kwargs["custom_llm_provider"] = "vertex_ai" + logging_obj.model_call_details["response_cost"] = response_cost + + return { + "result": litellm_video_response, + "kwargs": kwargs, + } + + elif "generateContent" in url_route: model = VertexPassthroughLoggingHandler.extract_model_from_url(url_route) instance_of_vertex_llm = litellm.VertexGeminiConfig() diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 5b47a8af7a5..8e297f645c6 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -412,6 +412,31 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils): params=requested_query_params, json=_parsed_body, ) + # Mock httpx response emulating a Google AI video generation operation status + # Attach a dummy request with headers set, so response.request.headers is always present + dummy_request = httpx.Request( + method=request.method, + url=str(url), + headers=headers or {}, + params=requested_query_params, + json=_parsed_body, + ) + # Ensure the .headers attribute exists and is a dict (httpx will normalize it) + mock_headers = httpx.Headers({"content-type": "application/json"}) + response = httpx.Response( + status_code=200, + headers=mock_headers, + json={ + "name": "operations/1234567890123456789", + "metadata": { + "@type": "type.googleapis.com/google.ai.generativelanguage.v1beta.GenerateVideoMetadata", + "state": "RUNNING", + "createTime": "2025-01-01T12:00:00Z" + }, + "done": False + }, + request=dummy_request + ) return response @staticmethod @@ -737,7 +762,6 @@ async def pass_through_request( # noqa: PLR0915 # Store custom_llm_provider in kwargs and logging object if provided if custom_llm_provider: - kwargs["custom_llm_provider"] = custom_llm_provider logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider logging_obj.model_call_details["litellm_params"] = kwargs.get("litellm_params", {}) diff --git a/litellm/proxy/pass_through_endpoints/success_handler.py b/litellm/proxy/pass_through_endpoints/success_handler.py index cc50d2c2d8e..6d93ef68dfd 100644 --- a/litellm/proxy/pass_through_endpoints/success_handler.py +++ b/litellm/proxy/pass_through_endpoints/success_handler.py @@ -42,6 +42,7 @@ class PassThroughEndpointLogging: "streamRawPredict", "search", "batchPredictionJobs", + "predictLongRunning", ] # Anthropic @@ -57,7 +58,7 @@ class PassThroughEndpointLogging: self.TRACKED_LANGFUSE_ROUTES = ["/langfuse/"] # Gemini - self.TRACKED_GEMINI_ROUTES = ["generateContent", "streamGenerateContent"] + self.TRACKED_GEMINI_ROUTES = ["generateContent", "streamGenerateContent", "predictLongRunning"] # Vertex AI Live API WebSocket self.TRACKED_VERTEX_AI_LIVE_ROUTES = ["/vertex_ai/live"] @@ -149,6 +150,7 @@ class PassThroughEndpointLogging: start_time=start_time, end_time=end_time, cache_hit=cache_hit, + request_body=request_body, **kwargs, ) ) diff --git a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_gemini_passthrough_logging_handler.py b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_gemini_passthrough_logging_handler.py index 6f87d8f6ab5..2c3bbc0e6ed 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_gemini_passthrough_logging_handler.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_gemini_passthrough_logging_handler.py @@ -75,7 +75,9 @@ class TestGeminiPassthroughLoggingHandler: def test_is_gemini_route(self): """Test that Gemini routes are correctly identified""" - from litellm.proxy.pass_through_endpoints.success_handler import PassThroughEndpointLogging + from litellm.proxy.pass_through_endpoints.success_handler import ( + PassThroughEndpointLogging, + ) handler = PassThroughEndpointLogging() @@ -285,3 +287,78 @@ class TestGeminiPassthroughLoggingHandler: assert call_kwargs["response_cost"] is not None assert call_kwargs["model"] == "gemini-1.5-flash" assert call_kwargs["custom_llm_provider"] == "gemini" + + @patch("litellm.completion_cost") + def test_veo3_passthrough_cost_tracking(self, mock_completion_cost): + """Test Veo3 video generation cost tracking for passthrough requests""" + # Mock the completion_cost to return the expected video generation cost + # For veo-2.0-generate-001 with 8 seconds: 0.35 * 8 = 2.8 + expected_cost = 0.35 * 8.0 # $2.80 + mock_completion_cost.return_value = expected_cost + + # Mock Veo3 predictLongRunning response + mock_veo_response = { + "name": "operations/1234567890123456789" + } + + mock_httpx_response = MagicMock(spec=httpx.Response) + mock_httpx_response.status_code = 200 + mock_httpx_response.json.return_value = mock_veo_response + mock_httpx_response.headers = {"content-type": "application/json"} + + mock_logging_obj = self._create_mock_logging_obj() + + # Request body with durationSeconds + request_body = { + "instances": [{"prompt": "A close up of two people staring at a cryptic drawing on a wall,"}], + "parameters": {"durationSeconds": 8} + } + + kwargs = { + "passthrough_logging_payload": PassthroughStandardLoggingPayload( + url="https://generativelanguage.googleapis.com/v1beta/models/veo-2.0-generate-001:predictLongRunning", + request_body=request_body, + request_method="POST", + ), + } + + # Act + result = GeminiPassthroughLoggingHandler.gemini_passthrough_handler( + httpx_response=mock_httpx_response, + response_body=mock_veo_response, + logging_obj=mock_logging_obj, + url_route="https://generativelanguage.googleapis.com/v1beta/models/veo-2.0-generate-001:predictLongRunning", + result="", + start_time=self.start_time, + end_time=self.end_time, + cache_hit=False, + request_body=request_body, + **kwargs, + ) + + # Assert + assert result is not None + assert "result" in result + assert "kwargs" in result + + # Verify the cost is calculated correctly + assert result["kwargs"]["response_cost"] == expected_cost + assert result["kwargs"]["model"] == "veo-2.0-generate-001" + assert result["kwargs"]["custom_llm_provider"] == "gemini" + + # Verify completion_cost was called with create_video call_type + mock_completion_cost.assert_called_once() + call_args = mock_completion_cost.call_args + assert call_args.kwargs.get("call_type") == "create_video" + assert call_args.kwargs.get("custom_llm_provider") == "gemini" + assert call_args.kwargs.get("model") == "veo-2.0-generate-001" + + # Verify the response object has _hidden_params with response_cost + video_response = result["result"] + assert hasattr(video_response, "_hidden_params") + assert video_response._hidden_params.get("response_cost") == expected_cost + + # Verify logging object was updated + assert mock_logging_obj.model_call_details["response_cost"] == expected_cost + assert mock_logging_obj.model_call_details["model"] == "veo-2.0-generate-001" + assert mock_logging_obj.model_call_details["custom_llm_provider"] == "gemini" From ae132abff42cf7a3e65a28e40e7811bedc2b9a04 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 14:36:30 +0530 Subject: [PATCH 213/349] Revert auth change --- .../llm_passthrough_endpoints.py | 9 ++++--- .../pass_through_endpoints.py | 25 ------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index d1294f996ec..7afb6868c73 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -187,10 +187,13 @@ async def gemini_proxy_route( """ [Docs](https://docs.litellm.ai/docs/pass_through/google_ai_studio) """ - # Get LiteLLM API key from Authorization header for authentication - api_key_to_use = get_litellm_virtual_key(request=request) + ## 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" + ) + user_api_key_dict = await user_api_key_auth( - request=request, api_key=api_key_to_use + request=request, api_key=f"Bearer {google_ai_studio_api_key}" ) base_target_url = ( diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 8e297f645c6..a447dad0b19 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -412,31 +412,6 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils): params=requested_query_params, json=_parsed_body, ) - # Mock httpx response emulating a Google AI video generation operation status - # Attach a dummy request with headers set, so response.request.headers is always present - dummy_request = httpx.Request( - method=request.method, - url=str(url), - headers=headers or {}, - params=requested_query_params, - json=_parsed_body, - ) - # Ensure the .headers attribute exists and is a dict (httpx will normalize it) - mock_headers = httpx.Headers({"content-type": "application/json"}) - response = httpx.Response( - status_code=200, - headers=mock_headers, - json={ - "name": "operations/1234567890123456789", - "metadata": { - "@type": "type.googleapis.com/google.ai.generativelanguage.v1beta.GenerateVideoMetadata", - "state": "RUNNING", - "createTime": "2025-01-01T12:00:00Z" - }, - "done": False - }, - request=dummy_request - ) return response @staticmethod From 983ba7aa0f2e4e7f3e6ffe6082fcd93c366a76ce Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 1 Dec 2025 17:22:04 +0530 Subject: [PATCH 214/349] Remove not compatible beta header from claude code --- .../anthropic_claude3_transformation.py | 2 +- .../anthropic_claude3_transformation.py | 2 +- .../bedrock/test_anthropic_beta_support.py | 138 +++++++++++++++++- 3 files changed, 133 insertions(+), 9 deletions(-) diff --git a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py index f003c0ed95f..53e08229799 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py @@ -103,7 +103,7 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): model=model, optional_params=optional_params, computer_tool_used=self.is_computer_tool_used(tools), - prompt_caching_set=self.is_cache_control_set(messages), + prompt_caching_set=False, file_id_used=self.is_file_id_used(messages), mcp_server_used=self.is_mcp_server_used(optional_params.get("mcp_servers")), ) diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index aea8a4b5a8f..32be1a780a3 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -157,7 +157,7 @@ class AmazonAnthropicClaudeMessagesConfig( model=model, optional_params=anthropic_messages_optional_request_params, computer_tool_used=anthropic_model_info.is_computer_tool_used(tools), - prompt_caching_set=anthropic_model_info.is_cache_control_set(messages_typed), + prompt_caching_set=False, file_id_used=anthropic_model_info.is_file_id_used(messages_typed), mcp_server_used=anthropic_model_info.is_mcp_server_used( anthropic_messages_optional_request_params.get("mcp_servers") diff --git a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py index 1b9e1b5284c..bd64670517c 100644 --- a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py +++ b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py @@ -5,14 +5,19 @@ Tests that anthropic-beta headers are correctly processed and passed to AWS Bedr for enabling beta features like 1M context window, computer use tools, etc. """ -import pytest -from unittest.mock import patch, MagicMock import json +from unittest.mock import MagicMock, patch + +import pytest -from litellm.llms.bedrock.common_utils import get_anthropic_beta_from_headers from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig -from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import AmazonAnthropicClaudeConfig -from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import AmazonAnthropicClaudeMessagesConfig +from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, +) +from litellm.llms.bedrock.common_utils import get_anthropic_beta_from_headers +from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, +) class TestAnthropicBetaHeaderSupport: @@ -56,7 +61,8 @@ class TestAnthropicBetaHeaderSupport: ) assert "anthropic_beta" in result - assert result["anthropic_beta"] == ["context-1m-2025-08-07", "computer-use-2024-10-22"] + # Beta flags are stored as sets, so order may vary + assert set(result["anthropic_beta"]) == {"context-1m-2025-08-07", "computer-use-2024-10-22"} def test_converse_transformation_anthropic_beta(self): """Test that Converse API transformation includes anthropic_beta in additionalModelRequestFields.""" @@ -163,4 +169,122 @@ class TestAnthropicBetaHeaderSupport: ) assert "anthropic_beta" in result - assert result["anthropic_beta"] == supported_features \ No newline at end of file + # Beta flags are stored as sets, so order may vary + assert set(result["anthropic_beta"]) == set(supported_features) + + def test_prompt_caching_no_beta_header_messages_api(self): + """Test that prompt caching (cache_control) does NOT add prompt-caching-2024-07-31 beta header for Bedrock. + + Bedrock recognizes prompt caching via the request body (cache_control field), + not through beta headers. This test verifies the fix. + """ + config = AmazonAnthropicClaudeMessagesConfig() + headers = {} + + # Messages with cache_control set (prompt caching enabled) + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Hello", + "cache_control": {"type": "ephemeral"} + } + ] + } + ] + + result = config.transform_anthropic_messages_request( + model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=messages, + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers=headers + ) + + # Verify prompt-caching-2024-07-31 is NOT in anthropic_beta + if "anthropic_beta" in result: + assert "prompt-caching-2024-07-31" not in result["anthropic_beta"], ( + "prompt-caching-2024-07-31 should not be added as a beta header for Bedrock. " + "Bedrock recognizes prompt caching via cache_control in the request body, not beta headers." + ) + else: + # It's also valid if anthropic_beta is not present at all + assert True + + def test_prompt_caching_no_beta_header_chat_api(self): + """Test that prompt caching (cache_control) does NOT add prompt-caching-2024-07-31 beta header for Bedrock Chat API. + + Bedrock recognizes prompt caching via the request body (cache_control field), + not through beta headers. This test verifies the fix. + """ + config = AmazonAnthropicClaudeConfig() + headers = {} + + # Messages with cache_control set (prompt caching enabled) + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Hello", + "cache_control": {"type": "ephemeral"} + } + ] + } + ] + + result = config.transform_request( + model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers + ) + + # Verify prompt-caching-2024-07-31 is NOT in anthropic_beta + if "anthropic_beta" in result: + assert "prompt-caching-2024-07-31" not in result["anthropic_beta"], ( + "prompt-caching-2024-07-31 should not be added as a beta header for Bedrock. " + "Bedrock recognizes prompt caching via cache_control in the request body, not beta headers." + ) + else: + # It's also valid if anthropic_beta is not present at all + assert True + + def test_prompt_caching_with_other_beta_headers(self): + """Test that prompt caching doesn't interfere with other valid beta headers.""" + config = AmazonAnthropicClaudeMessagesConfig() + headers = {"anthropic-beta": "context-1m-2025-08-07"} + + # Messages with cache_control set + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Hello", + "cache_control": {"type": "ephemeral"} + } + ] + } + ] + + result = config.transform_anthropic_messages_request( + model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=messages, + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers=headers + ) + + # Should have the user-provided beta header but NOT prompt-caching + if "anthropic_beta" in result: + assert "context-1m-2025-08-07" in result["anthropic_beta"] + assert "prompt-caching-2024-07-31" not in result["anthropic_beta"] + else: + # If no beta headers, that's also fine + assert True \ No newline at end of file From 8f6822a64264474d2643c12a2f5b1d8857d169d9 Mon Sep 17 00:00:00 2001 From: Chesars Date: Mon, 1 Dec 2025 12:55:01 -0300 Subject: [PATCH 215/349] Fix: Allow reasoning_effort='none' for Azure gpt-5.1 models PR #17071 drops or errors on reasoning_effort='none' for all GPT models. It doesn't actually allow 'none' to be sent to Azure for gpt-5.1 which supports it according to Azure documentation. See: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/reasoning "none is only supported for gpt-5.1" --- .../llms/azure/chat/gpt_5_transformation.py | 12 +++- .../chat/test_azure_gpt5_transformation.py | 59 +++++++++++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/litellm/llms/azure/chat/gpt_5_transformation.py b/litellm/llms/azure/chat/gpt_5_transformation.py index 209475730f8..2d0ce8b5bce 100644 --- a/litellm/llms/azure/chat/gpt_5_transformation.py +++ b/litellm/llms/azure/chat/gpt_5_transformation.py @@ -40,7 +40,11 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): or optional_params.get("reasoning_effort") ) - if reasoning_effort_value == "none": + # gpt-5.1 supports reasoning_effort='none', but other gpt-5 models don't + # See: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/reasoning + is_gpt_5_1 = self.is_model_gpt_5_1_model(model) + + if reasoning_effort_value == "none" and not is_gpt_5_1: if litellm.drop_params is True or ( drop_params is not None and drop_params is True ): @@ -54,8 +58,9 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): raise UnsupportedParamsError( status_code=400, message=( - "Azure OpenAI does not support reasoning_effort='none'. " + "Azure OpenAI does not support reasoning_effort='none' for this model. " "Supported values are: 'low', 'medium', and 'high'. " + "Note: gpt-5.1 does support reasoning_effort='none'. " "To drop this parameter, set `litellm.drop_params=True` or for proxy:\n\n" "`litellm_settings:\n drop_params: true`\n" "Issue: https://github.com/BerriAI/litellm/issues/16704" @@ -70,7 +75,8 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): drop_params=drop_params, ) - if result.get("reasoning_effort") == "none": + # Only drop reasoning_effort='none' for non-gpt-5.1 models + if result.get("reasoning_effort") == "none" and not is_gpt_5_1: result.pop("reasoning_effort") return result diff --git a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py index 3095ff87f5a..91d664c3216 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py @@ -104,34 +104,33 @@ def test_azure_gpt5_codex_series_transform_request(config: AzureOpenAIGPT5Config # GPT-5.1 temperature handling tests for Azure def test_azure_gpt5_1_temperature_with_reasoning_effort_none(config: AzureOpenAIGPT5Config): - """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none' and drop_params=True. - - Note: Azure OpenAI doesn't support reasoning_effort='none', so it's dropped from the params - when drop_params=True. The temperature logic still works correctly because the parent treats - missing reasoning_effort the same as 'none' for gpt-5.1. + """Test that Azure GPT-5.1 supports any temperature when reasoning_effort='none'. + + Azure OpenAI supports reasoning_effort='none' for gpt-5.1 models. + See: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/reasoning """ params = config.map_openai_params( non_default_params={"temperature": 0.5, "reasoning_effort": "none"}, optional_params={}, model="azure/gpt-5.1", - drop_params=True, + drop_params=False, api_version="2024-05-01-preview", ) assert params["temperature"] == 0.5 - # Azure doesn't support reasoning_effort="none", so it should be dropped - assert "reasoning_effort" not in params or params.get("reasoning_effort") != "none" + # Azure supports reasoning_effort="none" for gpt-5.1 + assert params.get("reasoning_effort") == "none" -def test_azure_gpt5_1_reasoning_effort_none_error_when_drop_params_false(config: AzureOpenAIGPT5Config): - """Test that Azure GPT-5.1 raises error for reasoning_effort='none' when drop_params=False.""" - with pytest.raises(litellm.utils.UnsupportedParamsError): - config.map_openai_params( - non_default_params={"reasoning_effort": "none"}, - optional_params={}, - model="azure/gpt-5.1", - drop_params=False, - api_version="2024-05-01-preview", - ) +def test_azure_gpt5_1_reasoning_effort_none_supported(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5.1 supports reasoning_effort='none' without error.""" + params = config.map_openai_params( + non_default_params={"reasoning_effort": "none"}, + optional_params={}, + model="azure/gpt-5.1", + drop_params=False, + api_version="2024-05-01-preview", + ) + assert params.get("reasoning_effort") == "none" def test_azure_gpt5_1_temperature_without_reasoning_effort(config: AzureOpenAIGPT5Config): @@ -181,3 +180,27 @@ def test_azure_gpt5_1_series_temperature_handling(config: AzureOpenAIGPT5Config) ) assert params["temperature"] == 0.6 + +def test_azure_gpt5_reasoning_effort_none_error(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5 (non-5.1) raises error for reasoning_effort='none' when drop_params=False.""" + with pytest.raises(litellm.utils.UnsupportedParamsError): + config.map_openai_params( + non_default_params={"reasoning_effort": "none"}, + optional_params={}, + model="azure/gpt-5", + drop_params=False, + api_version="2024-05-01-preview", + ) + + +def test_azure_gpt5_reasoning_effort_none_dropped(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5 (non-5.1) drops reasoning_effort='none' when drop_params=True.""" + params = config.map_openai_params( + non_default_params={"reasoning_effort": "none"}, + optional_params={}, + model="azure/gpt-5", + drop_params=True, + api_version="2024-05-01-preview", + ) + assert "reasoning_effort" not in params or params.get("reasoning_effort") != "none" + From 6de610767340cadd6df1c5508325128045c8fae5 Mon Sep 17 00:00:00 2001 From: YutaSaito <36355491+uc4w6c@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:59:01 +0900 Subject: [PATCH 216/349] fix: respect guardrail mock_response during during_call to return blocked output (#17247) --- litellm/proxy/common_request_processing.py | 23 +++-- .../proxy/test_common_request_processing.py | 99 ++++++++++++++++++- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index d2b04410026..ed4c451f8d3 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -536,7 +536,11 @@ class ProxyBaseLLMRequestProcessing: responses = await llm_responses - response = responses[1] + # Guardrails (pre/during-call) can inject a mock response to short-circuit the LLM call. + # Prefer it when present so blocked/filtered output is returned instead of the model response. + response = self.data.get("mock_response") + if response is None: + response = responses[1] hidden_params = getattr(response, "_hidden_params", {}) or {} model_id = hidden_params.get("model_id", None) or "" @@ -804,7 +808,7 @@ class ProxyBaseLLMRequestProcessing: # This matches the original behavior before the refactor in commit 511d435f6f error_body = await e.response.aread() error_text = error_body.decode("utf-8") - + raise HTTPException( status_code=e.response.status_code, detail={"error": error_text}, @@ -1072,9 +1076,9 @@ class ProxyBaseLLMRequestProcessing: # Add cache-related fields to **params (handled by Usage.__init__) if cache_creation_input_tokens is not None: - usage_kwargs["cache_creation_input_tokens"] = ( - cache_creation_input_tokens - ) + usage_kwargs[ + "cache_creation_input_tokens" + ] = cache_creation_input_tokens if cache_read_input_tokens is not None: usage_kwargs["cache_read_input_tokens"] = cache_read_input_tokens @@ -1093,7 +1097,9 @@ class ProxyBaseLLMRequestProcessing: return obj return None - def maybe_get_model_id(self, _logging_obj: Optional[LiteLLMLoggingObj]) -> Optional[str]: + def maybe_get_model_id( + self, _logging_obj: Optional[LiteLLMLoggingObj] + ) -> Optional[str]: """ Get model_id from logging object or request metadata. @@ -1103,10 +1109,7 @@ class ProxyBaseLLMRequestProcessing: model_id = None if _logging_obj: # 1. Try getting from litellm_params (updated during call) - if ( - hasattr(_logging_obj, "litellm_params") - and _logging_obj.litellm_params - ): + if hasattr(_logging_obj, "litellm_params") and _logging_obj.litellm_params: # First check direct model_info path (set by router.py with selected deployment) model_info = _logging_obj.litellm_params.get("model_info") or {} model_id = model_info.get("id", None) diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 4768ec42ff6..8f5f182f429 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -1,11 +1,13 @@ import copy +from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock import pytest -from fastapi import Request, status +from fastapi import Request, Response, status from fastapi.responses import StreamingResponse import litellm +import litellm.proxy.common_request_processing as common_request_processing from litellm._uuid import uuid from litellm.integrations.opentelemetry import UserAPIKeyAuth from litellm.proxy.common_request_processing import ( @@ -75,6 +77,101 @@ class TestProxyBaseLLMRequestProcessing: pytest.fail("litellm_call_id is not a valid UUID") assert data_passed["litellm_call_id"] == returned_data["litellm_call_id"] + @pytest.mark.asyncio + async def test_base_process_llm_request_prefers_guardrail_mock_response( + self, monkeypatch + ): + processing_obj = ProxyBaseLLMRequestProcessing( + data={ + "messages": [], + "metadata": {}, + "litellm_metadata": {"model_info": {"id": "fallback-model"}}, + } + ) + + guardrail_response = litellm.ModelResponse( + model="bedrock-guardrail", + hidden_params={"model_id": "guardrail-model"}, + ) + llm_response = litellm.ModelResponse( + model="real-model", + hidden_params={"model_id": "real-model"}, + ) + + async def mock_common_processing(self, *args, **kwargs): + logging_obj = SimpleNamespace(litellm_call_id="test-call-id") + self.data["litellm_call_id"] = "test-call-id" + self.data["litellm_logging_obj"] = logging_obj + return self.data, logging_obj + + monkeypatch.setattr( + ProxyBaseLLMRequestProcessing, + "common_processing_pre_call_logic", + mock_common_processing, + ) + + async def mock_route_request(*args, **kwargs): + async def _inner(): + return llm_response + + return _inner() + + monkeypatch.setattr( + common_request_processing, + "route_request", + mock_route_request, + ) + + check_response_size_is_safe_mock = AsyncMock() + monkeypatch.setattr( + common_request_processing, + "check_response_size_is_safe", + check_response_size_is_safe_mock, + ) + + async def mock_during_call_hook(*args, **kwargs): + kwargs["data"]["mock_response"] = guardrail_response + + proxy_logging_obj = MagicMock(spec=ProxyLogging) + proxy_logging_obj.during_call_hook = AsyncMock( + side_effect=mock_during_call_hook + ) + proxy_logging_obj.update_request_status = AsyncMock(return_value=None) + proxy_logging_obj.post_call_success_hook = AsyncMock( + return_value=guardrail_response + ) + + user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) + user_api_key_dict.tpm_limit = None + user_api_key_dict.rpm_limit = None + user_api_key_dict.max_budget = None + user_api_key_dict.spend = 0 + user_api_key_dict.allowed_model_region = None + + fastapi_response = Response() + proxy_config = MagicMock(spec=ProxyConfig) + + result = await processing_obj.base_process_llm_request( + request=MagicMock(spec=Request), + fastapi_response=fastapi_response, + user_api_key_dict=user_api_key_dict, + route_type="acompletion", + proxy_logging_obj=proxy_logging_obj, + general_settings={}, + proxy_config=proxy_config, + select_data_generator=lambda **kwargs: None, + ) + + assert result is guardrail_response + assert ( + proxy_logging_obj.post_call_success_hook.await_args.kwargs["response"] + is guardrail_response + ) + assert ( + check_response_size_is_safe_mock.await_args.kwargs["response"] + is guardrail_response + ) + @pytest.mark.asyncio async def test_stream_timeout_header_processing(self): """ From 7808a610f8a95ddb4449eae1b0af67d4e5b2e50d Mon Sep 17 00:00:00 2001 From: orgersh92 Date: Mon, 1 Dec 2025 20:03:51 +0200 Subject: [PATCH 217/349] Fix session consistency, move Lasso API version away from source code (#17316) * store and fetch lasso-conversation id from cache * include gateway/v# in the baseUrl to allow simpler version migrations in the future * add tests for cached conversation ID --- .../docs/proxy/guardrails/lasso_security.md | 4 +- .../guardrails/guardrail_hooks/lasso/lasso.py | 68 +++++++------------ .../guardrails/guardrail_hooks/test_lasso.py | 29 +++++--- 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/docs/my-website/docs/proxy/guardrails/lasso_security.md b/docs/my-website/docs/proxy/guardrails/lasso_security.md index 21528790afe..113e3f8974a 100644 --- a/docs/my-website/docs/proxy/guardrails/lasso_security.md +++ b/docs/my-website/docs/proxy/guardrails/lasso_security.md @@ -35,7 +35,7 @@ guardrails: guardrail: lasso mode: "pre_call" api_key: os.environ/LASSO_API_KEY - api_base: "https://server.lasso.security" + api_base: "https://server.lasso.security/gateway/v3" - guardrail_name: "lasso-post-guard" litellm_params: guardrail: lasso @@ -228,7 +228,7 @@ Expected response: ## PII Masking with Lasso -Lasso supports automatic PII detection and masking using the `/gateway/v1/classifix` endpoint. When enabled, sensitive information like emails, phone numbers, and other PII will be automatically masked with appropriate placeholders. +Lasso supports automatic PII detection and masking using the `/classifix` endpoint. When enabled, sensitive information like emails, phone numbers, and other PII will be automatically masked with appropriate placeholders. ### Enabling PII Masking diff --git a/litellm/proxy/guardrails/guardrail_hooks/lasso/lasso.py b/litellm/proxy/guardrails/guardrail_hooks/lasso/lasso.py index 99d2b82400f..ea8f1b0a97f 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/lasso/lasso.py +++ b/litellm/proxy/guardrails/guardrail_hooks/lasso/lasso.py @@ -33,6 +33,8 @@ from litellm.integrations.custom_guardrail import ( CustomGuardrail, log_guardrail_information, ) +from litellm.integrations.custom_guardrail import dc as global_cache + from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, @@ -100,7 +102,7 @@ class LassoGuardrail(CustomGuardrail): ) self.api_base = ( - api_base or os.getenv("LASSO_API_BASE") or "https://server.lasso.security" + api_base or os.getenv("LASSO_API_BASE") or "https://server.lasso.security/gateway/v3" ) verbose_proxy_logger.debug( @@ -125,7 +127,7 @@ class LassoGuardrail(CustomGuardrail): async def async_pre_call_hook( self, user_api_key_dict: UserAPIKeyAuth, - cache: DualCache, + cache: DualCache, # Deprecated, use global_cache instead (kept to align with CustomGuardrail interface) data: dict, call_type: Literal[ "completion", @@ -150,10 +152,10 @@ class LassoGuardrail(CustomGuardrail): return data # Get or generate conversation_id and store it in data for post-call consistency - conversation_id = self._get_or_generate_conversation_id(data, cache) - data.setdefault("_lasso_internal", {})["conversation_id"] = conversation_id + # The conversation_id is being stored in the cache so it can be used by the post_call hook + self._get_or_generate_conversation_id(data, global_cache) - return await self._run_lasso_guardrail(data, cache, message_type="PROMPT") + return await self._run_lasso_guardrail(data, global_cache, message_type="PROMPT") @log_guardrail_information async def async_moderation_hook( @@ -213,17 +215,12 @@ class LassoGuardrail(CustomGuardrail): "litellm_call_id": data.get("litellm_call_id"), } - # Copy stored conversation_id from pre-call hook - if data.get("_lasso_internal", {}).get("conversation_id") and isinstance(response_data, dict): - response_data.setdefault("_lasso_internal", {})["conversation_id"] = data["_lasso_internal"][ - "conversation_id" - ] # Handle masking for post-call if self.mask: - headers = self._prepare_headers(response_data) - payload = self._prepare_payload(response_messages, "COMPLETION", response_data) - api_url = f"{self.api_base}/gateway/v3/classifix" + headers = self._prepare_headers(response_data, global_cache) + payload = self._prepare_payload(response_messages, response_data, global_cache, "COMPLETION") + api_url = f"{self.api_base}/classifix" try: lasso_response = await self._call_lasso_api(headers=headers, payload=payload, api_url=api_url) @@ -241,7 +238,7 @@ class LassoGuardrail(CustomGuardrail): raise LassoGuardrailAPIError(f"Failed to apply post-call masking: {str(e)}") else: # Use the same data for conversation_id consistency (no cache access needed) - await self._run_lasso_guardrail(response_data, cache=None, message_type="COMPLETION") + await self._run_lasso_guardrail(response_data, cache=global_cache, message_type="COMPLETION") verbose_proxy_logger.debug("Post-call Lasso validation completed") else: verbose_proxy_logger.warning("No response messages found to validate") @@ -306,7 +303,7 @@ class LassoGuardrail(CustomGuardrail): async def _run_lasso_guardrail( self, data: dict, - cache: Optional[DualCache], + cache: DualCache, message_type: Literal["PROMPT", "COMPLETION"] = "PROMPT", ): """ @@ -345,14 +342,14 @@ class LassoGuardrail(CustomGuardrail): async def _handle_classification( self, data: dict, - cache: Optional[DualCache], + cache: DualCache, message_type: Literal["PROMPT", "COMPLETION"], messages: List[Dict[str, str]], ) -> dict: """Handle classification without masking.""" try: headers = self._prepare_headers(data, cache) - payload = self._prepare_payload(messages, message_type, data, cache) + payload = self._prepare_payload(messages, data, cache, message_type) response = await self._call_lasso_api(headers=headers, payload=payload) self._process_lasso_response(response) return data @@ -363,15 +360,15 @@ class LassoGuardrail(CustomGuardrail): async def _handle_masking( self, data: dict, - cache: Optional[DualCache], + cache: DualCache, message_type: Literal["PROMPT", "COMPLETION"], messages: List[Dict[str, str]], ) -> dict: """Handle masking with classifix endpoint.""" try: headers = self._prepare_headers(data, cache) - payload = self._prepare_payload(messages, message_type, data, cache) - api_url = f"{self.api_base}/gateway/v3/classifix" + payload = self._prepare_payload(messages, data, cache, message_type) + api_url = f"{self.api_base}/classifix" response = await self._call_lasso_api(headers=headers, payload=payload, api_url=api_url) self._process_lasso_response(response) @@ -437,7 +434,7 @@ class LassoGuardrail(CustomGuardrail): }, ) - def _prepare_headers(self, data: dict, cache: Optional[DualCache] = None) -> Dict[str, str]: + def _prepare_headers(self, data: dict, cache: DualCache) -> Dict[str, str]: """Prepare headers for the Lasso API request.""" if not self.lasso_api_key: raise LassoGuardrailMissingSecrets( @@ -455,13 +452,7 @@ class LassoGuardrail(CustomGuardrail): headers["lasso-user-id"] = self.user_id # Always include conversation_id (generated or provided) - if cache is not None: - conversation_id = self._get_or_generate_conversation_id(data, cache) - else: - # For post-call hook, use stored conversation_id or generate a new one - conversation_id = ( - data.get("_lasso_internal", {}).get("conversation_id") or self.conversation_id or self._generate_ulid() - ) + conversation_id = self._get_or_generate_conversation_id(data, cache) headers["lasso-conversation-id"] = conversation_id @@ -470,9 +461,9 @@ class LassoGuardrail(CustomGuardrail): def _prepare_payload( self, messages: List[Dict[str, str]], + data: dict, + cache: DualCache, message_type: Literal["PROMPT", "COMPLETION"] = "PROMPT", - data: Optional[dict] = None, - cache: Optional[DualCache] = None, ) -> Dict[str, Any]: """ Prepare the payload for the Lasso API request. @@ -490,20 +481,9 @@ class LassoGuardrail(CustomGuardrail): payload["userId"] = self.user_id # Always include sessionId (conversation_id - generated or provided) - if data is not None: - if cache is not None: - conversation_id = self._get_or_generate_conversation_id(data, cache) - else: - # For post-call hook, use stored conversation_id or fallback - conversation_id = ( - data.get("_lasso_internal", {}).get("conversation_id") - or self.conversation_id - or self._generate_ulid() - ) + conversation_id = self._get_or_generate_conversation_id(data, cache) - payload["sessionId"] = conversation_id - elif self.conversation_id: - payload["sessionId"] = self.conversation_id + payload["sessionId"] = conversation_id return payload @@ -514,7 +494,7 @@ class LassoGuardrail(CustomGuardrail): api_url: Optional[str] = None, ) -> LassoResponse: """Call the Lasso API and return the response.""" - url = api_url or f"{self.api_base}/gateway/v3/classify" + url = api_url or f"{self.api_base}/classify" verbose_proxy_logger.debug(f"Calling Lasso API with messageType: {payload.get('messageType')}") response = await self.async_handler.post( url=url, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_lasso.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_lasso.py index c63974ac3f2..87542c974a5 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_lasso.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_lasso.py @@ -1,6 +1,7 @@ import os import sys import pytest +import uuid from unittest.mock import patch, MagicMock from httpx import Response, Request from fastapi import HTTPException @@ -77,10 +78,11 @@ class TestLassoGuardrail: assert guardrail.lasso_api_key == "test-api-key" assert guardrail.user_id == "test-user" assert guardrail.conversation_id == "test-conversation" - assert guardrail.api_base == "https://server.lasso.security" + assert guardrail.api_base == "https://server.lasso.security/gateway/v3" @pytest.mark.asyncio async def test_pre_call_no_violations(self): + from litellm.integrations.custom_guardrail import dc as global_cache """Test pre-call hook with no violations detected.""" # Setup guardrail guardrail = LassoGuardrail( @@ -90,12 +92,16 @@ class TestLassoGuardrail: default_on=True ) + test_call_id = str(uuid.uuid4()) + assert global_cache.get_cache(f"lasso_conversation_id:{test_call_id}") is None + # Test data data = { "messages": [ {"role": "user", "content": "Hello, how are you?"} ], - "metadata": {} + "metadata": {}, + "litellm_call_id": test_call_id } # Mock successful API response with no violations @@ -118,13 +124,14 @@ class TestLassoGuardrail: request=Request(method="POST", url="https://server.lasso.security/gateway/v3/classify"), ) + local_cache = DualCache() with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", return_value=mock_response ): result = await guardrail.async_pre_call_hook( user_api_key_dict=UserAPIKeyAuth(), - cache=DualCache(), + cache=local_cache, data=data, call_type="completion" ) @@ -132,6 +139,11 @@ class TestLassoGuardrail: # Should return original data when no violations detected assert result == data + # Verify that the conversation_id is stored in the global cache but not the local cache + cache_key = f"lasso_conversation_id:{test_call_id}" + assert global_cache.get_cache(cache_key) is not None + assert local_cache.get_cache(cache_key) is None + @pytest.mark.asyncio async def test_pre_call_with_violations(self): """Test pre-call hook with violations detected.""" @@ -466,9 +478,10 @@ class TestLassoGuardrail: ) messages = [{"role": "user", "content": "Test message"}] + cache = DualCache() # Test PROMPT payload - prompt_payload = guardrail._prepare_payload(messages, "PROMPT") + prompt_payload = guardrail._prepare_payload(messages, {}, cache, "PROMPT") assert prompt_payload["messageType"] == "PROMPT" assert prompt_payload["messages"] == messages assert prompt_payload["userId"] == "test-user" @@ -476,7 +489,7 @@ class TestLassoGuardrail: # Test COMPLETION payload completion_messages = [{"role": "assistant", "content": "Test response"}] - completion_payload = guardrail._prepare_payload(completion_messages, "COMPLETION") + completion_payload = guardrail._prepare_payload(completion_messages, {}, cache, "COMPLETION") assert completion_payload["messageType"] == "COMPLETION" assert completion_payload["messages"] == completion_messages assert completion_payload["userId"] == "test-user" @@ -489,9 +502,9 @@ class TestLassoGuardrail: user_id="test-user", conversation_id="test-conversation" ) - + cache = DualCache() data = {"litellm_call_id": "test-call-id"} - headers = guardrail._prepare_headers(data) + headers = guardrail._prepare_headers(data, cache) assert headers["lasso-api-key"] == "test-api-key" assert headers["Content-Type"] == "application/json" assert headers["lasso-user-id"] == "test-user" @@ -499,7 +512,7 @@ class TestLassoGuardrail: # Test without optional fields guardrail_minimal = LassoGuardrail(lasso_api_key="test-api-key") - headers_minimal = guardrail_minimal._prepare_headers(data) + headers_minimal = guardrail_minimal._prepare_headers(data, cache) assert headers_minimal["lasso-api-key"] == "test-api-key" assert headers_minimal["Content-Type"] == "application/json" assert "lasso-user-id" not in headers_minimal From 625b2fd54930a46d4647b906157ea94566fe5d0e Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 1 Dec 2025 10:18:38 -0800 Subject: [PATCH 218/349] Await cred delete refresh + migrate to reusable delete modal --- .../components/model_add/credentials.test.tsx | 46 ++++++++-- .../src/components/model_add/credentials.tsx | 92 +++++++++++-------- 2 files changed, 92 insertions(+), 46 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx index 8e356baa12b..fab74a39d2e 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx @@ -1,5 +1,5 @@ import { CredentialItem } from "@/components/networking"; -import { render, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { UploadProps } from "antd/es/upload"; import { describe, expect, it, vi } from "vitest"; import CredentialsPanel from "./credentials"; @@ -7,10 +7,25 @@ import CredentialsPanel from "./credentials"; const DEFAULT_UPLOAD_PROPS = {} as UploadProps; describe("CredentialsPanel", () => { - it("renders without crashing and fetches credentials when token exists", async () => { + it("should render", () => { const fetchCredentials = vi.fn(() => Promise.resolve()); - const { getByRole, getByText } = render( + render( + , + ); + + expect(screen.getByRole("button", { name: /add credential/i })).toBeInTheDocument(); + }); + + it("should call fetchCredentials when accessToken exists", async () => { + const fetchCredentials = vi.fn(() => Promise.resolve()); + + render( { ); await waitFor(() => { - expect(getByRole("button", { name: /add credential/i })).toBeInTheDocument(); - expect(getByText("Credential Name")).toBeInTheDocument(); - expect(getByText("Provider")).toBeInTheDocument(); + expect(fetchCredentials).toHaveBeenCalledWith("test-token"); }); }); - it("displays provided credentials and still calls the fetch helper", async () => { + it("should display provided credentials", () => { const fetchCredentials = vi.fn(() => Promise.resolve()); const credentials: CredentialItem[] = [ { @@ -36,7 +49,7 @@ describe("CredentialsPanel", () => { }, ]; - const { getByText } = render( + render( { />, ); - await waitFor(() => expect(getByText("openai-key")).toBeInTheDocument()); + expect(screen.getByText("openai-key")).toBeInTheDocument(); + }); + + it("should display empty state when no credentials are provided", () => { + const fetchCredentials = vi.fn(() => Promise.resolve()); + + render( + , + ); + + expect(screen.getByText("No credentials configured")).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.tsx index e36a759294b..cf6a5cb9eef 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.tsx @@ -1,28 +1,28 @@ -import React, { useState, useEffect } from "react"; import { + credentialCreateCall, + credentialDeleteCall, + CredentialItem, + credentialUpdateCall, +} from "@/components/networking"; // Assume this is your networking function +import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline"; +import { + Badge, + Button, + Card, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, - Card, Text, - Badge, - Button, } from "@tremor/react"; -import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline"; -import { UploadProps } from "antd/es/upload"; -import { - credentialCreateCall, - credentialDeleteCall, - credentialUpdateCall, - CredentialItem, -} from "@/components/networking"; // Assume this is your networking function -import AddCredentialsTab from "./add_credentials_tab"; -import CredentialDeleteModal from "./CredentialDeleteModal"; import { Form } from "antd"; +import { UploadProps } from "antd/es/upload"; +import React, { useEffect, useState } from "react"; +import DeleteResourceModal from "../common_components/DeleteResourceModal"; import NotificationsManager from "../molecules/notifications_manager"; +import AddCredentialsTab from "./add_credentials_tab"; interface CredentialsPanelProps { accessToken: string | null; uploadProps: UploadProps; @@ -39,7 +39,9 @@ const CredentialsPanel: React.FC = ({ const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false); const [selectedCredential, setSelectedCredential] = useState(null); - const [credentialToDelete, setCredentialToDelete] = useState(null); + const [credentialToDelete, setCredentialToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isCredentialDeleting, setIsCredentialDeleting] = useState(false); const [form] = Form.useForm(); const restrictedFields = ["credential_name", "custom_llm_provider"]; @@ -113,27 +115,38 @@ const CredentialsPanel: React.FC = ({ ); }; - const handleDeleteCredential = async (credentialName: string) => { - if (!accessToken) { + const handleDeleteCredential = async () => { + if (!accessToken || !credentialToDelete) { return; } - const response = await credentialDeleteCall(accessToken, credentialName); - NotificationsManager.success("Credential deleted successfully"); - setCredentialToDelete(null); - fetchCredentials(accessToken); + setIsCredentialDeleting(true); + try { + await credentialDeleteCall(accessToken, credentialToDelete.credential_name); + NotificationsManager.success("Credential deleted successfully"); + await fetchCredentials(accessToken); + } catch (error) { + NotificationsManager.error("Failed to delete credential"); + } finally { + setCredentialToDelete(null); + setIsDeleteModalOpen(false); + setIsCredentialDeleting(false); + } }; - const openDeleteModal = (credentialName: string) => { - setCredentialToDelete(credentialName); + const openDeleteModal = (credential: CredentialItem) => { + setCredentialToDelete(credential); + setIsDeleteModalOpen(true); }; const closeDeleteModal = () => { setCredentialToDelete(null); + setIsDeleteModalOpen(false); }; return ( - - Select the models you want to be visible on the public model hub. Users will still require a valid API key to - use these models. + Select the models you want to be visible on the public model hub. Users will still require a valid Virtual Key + to use these models. {/* Filters */} diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx index 4b4f1ab676b..5a012c1fc5c 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx @@ -220,12 +220,12 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] } - title="API Key Setup" - description="Configure your LiteLLM Proxy API key for authentication" + title="Virtual Key Setup" + description="Configure your LiteLLM Proxy Virtual Key for authentication" >
- Get your API key from your LiteLLM Proxy dashboard or contact your administrator + Get your Virtual Key from your LiteLLM Proxy dashboard or contact your administrator
@@ -249,7 +249,7 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] = ({ currentServerAccessGroups = [] "server_url": "${proxyBaseUrl}/mcp", "require_approval": "never", "headers": { - "x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY", + "x-litellm-api-key": "Bearer YOUR_LITELLM_VIRTUAL_KEY", "x-mcp-servers": ["Zapier_MCP,dev"] } } diff --git a/ui/litellm-dashboard/src/components/new_usage.test.tsx b/ui/litellm-dashboard/src/components/new_usage.test.tsx index a4969124f1f..a06045137d7 100644 --- a/ui/litellm-dashboard/src/components/new_usage.test.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.test.tsx @@ -239,7 +239,7 @@ describe("NewUsage", () => { // Check for chart titles expect(screen.getByText("Daily Spend")).toBeInTheDocument(); - expect(screen.getByText("Top API Keys")).toBeInTheDocument(); + expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument(); }); it("should switch between tabs correctly", async () => { diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx index 4794a7f091d..a8d30885493 100644 --- a/ui/litellm-dashboard/src/components/new_usage.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -580,7 +580,7 @@ const NewUsagePage: React.FC = ({ {/* Top API Keys */} - Top API Keys + Top Virtual Keys = ({ setApiKey(response["key"]); setSoftBudget(response["soft_budget"]); - NotificationsManager.success("API Key Created"); + NotificationsManager.success("Virtual Key Created"); form.resetFields(); localStorage.removeItem("userData" + userID); } catch (error) { @@ -415,7 +415,7 @@ const CreateKey: React.FC = ({ }; const handleCopy = () => { - NotificationsManager.success("API Key copied to clipboard"); + NotificationsManager.success("Virtual Key copied to clipboard"); }; useEffect(() => { @@ -505,7 +505,7 @@ const CreateKey: React.FC = ({ label={ Owned By{" "} - + @@ -594,8 +594,8 @@ const CreateKey: React.FC = ({ {isFormDisabled && (
- Please select a team to continue configuring your API key. If you do not see any teams, please contact - your Proxy Admin to either provide you with access to models or to add you to a team. + Please select a team to continue configuring your Virtual Key. If you do not see any teams, please + contact your Proxy Admin to either provide you with access to models or to add you to a team.
)} @@ -1277,7 +1277,7 @@ const CreateKey: React.FC = ({ {apiKey != null ? (
- API Key: + Virtual Key:
= ({
- + {/*
- New API Key: + New Virtual Key:
{regeneratedKey}
NotificationManager.success("API Key copied to clipboard")} + onCopy={() => NotificationManager.success("Virtual Key copied to clipboard")} > - + diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx index 90c43df0dc4..c2924c048c3 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx @@ -691,7 +691,7 @@ const ChatUI: React.FC = ({ const effectiveApiKey = apiKeySource === "session" ? accessToken : apiKey; if (!effectiveApiKey) { - NotificationsManager.fromBackend("Please provide an API key or select Current UI Session"); + NotificationsManager.fromBackend("Please provide a Virtual Key or select Current UI Session"); return; } @@ -1003,7 +1003,7 @@ const ChatUI: React.FC = ({
- API Key Source + Virtual Key Source setApiKeySource(value as "session" | "custom")} @@ -567,7 +567,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }: setCustomApiKey(event.target.value)} - placeholder="Enter API key" + placeholder="Enter Virtual Key" className="w-56" /> )} diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx index 47941bce2ca..3f8c90424c6 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx @@ -20,7 +20,7 @@ export async function makeAnthropicMessagesRequest( selectedMCPTools?: string[], ) { if (!accessToken) { - throw new Error("API key is required"); + throw new Error("Virtual Key is required"); } const isLocal = process.env.NODE_ENV === "development"; diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/embeddings_api.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/embeddings_api.tsx index 832d29bb852..d0939c00437 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/embeddings_api.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/embeddings_api.tsx @@ -9,7 +9,7 @@ export async function makeOpenAIEmbeddingsRequest( tags?: string[], ) { if (!accessToken) { - throw new Error("API key is required"); + throw new Error("Virtual Key is required"); } // Base URL should be the current base_url diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx index 8461f8e20a0..46b0621a0b1 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx @@ -24,7 +24,7 @@ export async function makeOpenAIResponsesRequest( onMCPEvent?: (event: MCPEvent) => void, ) { if (!accessToken) { - throw new Error("API key is required"); + throw new Error("Virtual Key is required"); } // Base URL should be the current base_url diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx index f58e392a58f..9897bb4d47a 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx @@ -245,7 +245,7 @@ import KeyInfoView from "./key_info_view"; const baseKeyData = { token_id: "tok_123", token: "tok_123", - key_alias: "My API Key", + key_alias: "My Virtual Key", key_name: "sk-xxxx", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index ec2b294d9de..dbbb195a1f8 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -297,7 +297,7 @@ export default function KeyInfoView({ - {currentKeyData.key_alias || "API Key"} + {currentKeyData.key_alias || "Virtual Key"}
@@ -381,7 +381,7 @@ export default function KeyInfoView({ {/* Delete Confirmation Modal */} {isDeleteModalOpen && (() => { - const keyName = currentKeyData?.key_alias || currentKeyData?.token_id || "API Key"; + const keyName = currentKeyData?.key_alias || currentKeyData?.token_id || "Virtual Key"; const isValid = deleteConfirmInput === keyName; return (
@@ -415,7 +415,7 @@ export default function KeyInfoView({

- Warning: You are about to delete this API key. + Warning: You are about to delete this Virtual Key.

This action is irreversible and will immediately revoke access for any applications using this @@ -423,7 +423,7 @@ export default function KeyInfoView({

-

Are you sure you want to delete this API key?

+

Are you sure you want to delete this Virtual Key?

- Warning: You are about to delete this API key. + Warning: You are about to delete this Virtual Key.

This action is irreversible and will immediately revoke access for any applications using this @@ -374,7 +374,7 @@ const ViewKeyTable: React.FC = ({

-

Are you sure you want to delete this API key?

+

Are you sure you want to delete this Virtual Key?

@@ -417,7 +417,7 @@ const ViewKeyTable: React.FC = ({ {/* Regenerate Key Form Modal */} { setRegenerateDialogVisible(false); @@ -516,7 +516,7 @@ const ViewKeyTable: React.FC = ({ {selectedToken?.key_alias || "No alias set"}
- New API Key: + New Virtual Key:
= ({
NotificationManager.success({ description: "API Key copied to clipboard" })} + onCopy={() => NotificationManager.success({ description: "Virtual Key copied to clipboard" })} > - + diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 88b1e3c3fb7..0900a0a9cc1 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -615,7 +615,7 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use - Top API Keys + Top Virtual Keys ( {row.original.key_count > 0 ? ( - {row.original.key_count} Keys + {row.original.key_count} {row.original.key_count === 1 ? "Key" : "Keys"} ) : ( diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx index 456f07d1882..2caae7d861f 100644 --- a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx +++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx @@ -320,9 +320,11 @@ export default function UserInfoView({ - API Keys + Virtual Keys
- {userData.keys?.length || 0} keys + + {userData.keys?.length || 0} {userData.keys?.length === 1 ? "Key" : "Keys"} +
@@ -467,7 +469,7 @@ export default function UserInfoView({
-
+
+ +
Configured credentials for different AI providers. Add and manage your API credentials.
@@ -143,6 +156,7 @@ const CredentialsPanel: React.FC = ({ Credential Name Provider + Actions @@ -173,7 +187,8 @@ const CredentialsPanel: React.FC = ({ icon={TrashIcon} variant="light" size="sm" - onClick={() => openDeleteModal(credential.credential_name)} + onClick={() => openDeleteModal(credential)} + className="ml-2" /> @@ -182,9 +197,6 @@ const CredentialsPanel: React.FC = ({ - {isAddModalOpen && ( = ({ /> )} - {credentialToDelete && ( - handleDeleteCredential(credentialToDelete)} - credentialName={credentialToDelete} - /> - )} +
); }; From c588e7854d7a8d03363b1b203af8650d37ab9b57 Mon Sep 17 00:00:00 2001 From: Colin Lin Date: Thu, 27 Nov 2025 16:36:04 -0500 Subject: [PATCH 219/349] use kwargs --- litellm/llms/custom_httpx/llm_http_handler.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index fdd504e2f57..10353c68b97 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -1804,15 +1804,23 @@ class BaseLLMHTTPHandler: Optional[litellm.types.utils.ProviderSpecificHeader], kwargs.get("provider_specific_header", None), ) - extra_headers = ProviderSpecificHeaderUtils.get_provider_specific_headers( + provider_specific_headers = ProviderSpecificHeaderUtils.get_provider_specific_headers( provider_specific_header=provider_specific_header, custom_llm_provider=custom_llm_provider, ) forwarded_headers = kwargs.get("headers", None) - if forwarded_headers and extra_headers: - merged_headers = {**forwarded_headers, **extra_headers} - else: - merged_headers = forwarded_headers or extra_headers + # Also check for extra_headers in kwargs (from config or direct calls) + extra_headers_from_kwargs = kwargs.get("extra_headers", None) + print("extra_headers_from_kwargs", extra_headers_from_kwargs) + print("provider_specific_headers", provider_specific_headers) + # Merge all header sources: forwarded < extra_headers < provider_specific + merged_headers = {} + if forwarded_headers: + merged_headers.update(forwarded_headers) + if extra_headers_from_kwargs: + merged_headers.update(extra_headers_from_kwargs) + if provider_specific_headers: + merged_headers.update(provider_specific_headers) ( headers, api_base, From 2a5082e6cf2e00c64faea984e587c314fac2731d Mon Sep 17 00:00:00 2001 From: Colin Lin Date: Thu, 27 Nov 2025 16:58:32 -0500 Subject: [PATCH 220/349] remove logs --- litellm/llms/custom_httpx/llm_http_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 10353c68b97..701cefb771e 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -1811,8 +1811,6 @@ class BaseLLMHTTPHandler: forwarded_headers = kwargs.get("headers", None) # Also check for extra_headers in kwargs (from config or direct calls) extra_headers_from_kwargs = kwargs.get("extra_headers", None) - print("extra_headers_from_kwargs", extra_headers_from_kwargs) - print("provider_specific_headers", provider_specific_headers) # Merge all header sources: forwarded < extra_headers < provider_specific merged_headers = {} if forwarded_headers: From e420b633a1ac8eaaf33fc2024d4ad70e7b8d688a Mon Sep 17 00:00:00 2001 From: Colin Lin Date: Fri, 28 Nov 2025 17:06:10 -0500 Subject: [PATCH 221/349] add tests --- .../custom_httpx/test_llm_http_handler.py | 150 +++++++++++++++++- 1 file changed, 145 insertions(+), 5 deletions(-) diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index 26fc18de16d..17b4243da1d 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -1,17 +1,14 @@ -import io import os -import pathlib -import ssl import sys -from unittest.mock import MagicMock +from unittest.mock import AsyncMock, Mock, patch import pytest sys.path.insert( 0, os.path.abspath("../../../..") ) # Adds the parent directory to the system path -import litellm from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler +from litellm.types.router import GenericLiteLLMParams def test_prepare_fake_stream_request(): @@ -75,3 +72,146 @@ def test_prepare_fake_stream_request(): assert "stream" not in result_data assert result_data["model"] == "gpt-4" assert result_data["messages"] == [{"role": "user", "content": "Hello"}] + + +@pytest.mark.asyncio +async def test_async_anthropic_messages_handler_extra_headers(): + """ + Test that async_anthropic_messages_handler correctly extracts and merges + extra_headers from kwargs with proper priority. + """ + handler = BaseLLMHTTPHandler() + + # Mock the config + mock_config = Mock() + mock_config.validate_anthropic_messages_environment = Mock( + return_value=({"x-api-key": "test-key"}, "https://api.anthropic.com") + ) + mock_config.transform_anthropic_messages_request = Mock( + return_value={"model": "claude-3-opus-20240229", "messages": []} + ) + + # Mock the client + mock_client = AsyncMock() + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "id": "msg_123", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "Hello!"}], + "model": "claude-3-opus-20240229", + "stop_reason": "end_turn", + } + mock_client.post = AsyncMock(return_value=mock_response) + + # Mock logging object + mock_logging_obj = Mock() + mock_logging_obj.update_environment_variables = Mock() + mock_logging_obj.model_call_details = {} + mock_logging_obj.stream = False + + # Test case 1: Only extra_headers in kwargs + kwargs = { + "extra_headers": { + "X-Custom-Header": "from-kwargs", + "X-Auth-Token": "token123", + } + } + + with patch( + "litellm.litellm_core_utils.get_provider_specific_headers.ProviderSpecificHeaderUtils.get_provider_specific_headers" + ) as mock_provider_headers: + mock_provider_headers.return_value = None + + # Capture what headers are passed to validate_anthropic_messages_environment + captured_headers = {} + def capture_validate(*args, **kwargs): + captured_headers.update(kwargs.get("headers", {})) + return ({"x-api-key": "test-key"}, "https://api.anthropic.com") + + mock_config.validate_anthropic_messages_environment = capture_validate + + try: + await handler.async_anthropic_messages_handler( + model="claude-3-opus-20240229", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=mock_config, + anthropic_messages_optional_request_params={}, + custom_llm_provider="anthropic", + litellm_params=GenericLiteLLMParams(), + logging_obj=mock_logging_obj, + client=mock_client, + kwargs=kwargs, + ) + except Exception: + pass # We're testing header extraction, not the full flow + + # Verify extra_headers were extracted and merged + assert "X-Custom-Header" in captured_headers + assert captured_headers["X-Custom-Header"] == "from-kwargs" + assert "X-Auth-Token" in captured_headers + assert captured_headers["X-Auth-Token"] == "token123" + + +@pytest.mark.asyncio +async def test_async_anthropic_messages_handler_header_priority(): + """ + Test that async_anthropic_messages_handler respects header priority: + forwarded < extra_headers < provider_specific + """ + handler = BaseLLMHTTPHandler() + + # Mock the config + mock_config = Mock() + mock_client = AsyncMock() + mock_logging_obj = Mock() + mock_logging_obj.update_environment_variables = Mock() + mock_logging_obj.model_call_details = {} + mock_logging_obj.stream = False + + # Test with all three header sources + kwargs = { + "headers": {"X-Priority": "forwarded", "X-Forwarded-Only": "keep"}, + "extra_headers": {"X-Priority": "extra", "X-Extra-Only": "also-keep"}, + } + + with patch( + "litellm.litellm_core_utils.get_provider_specific_headers.ProviderSpecificHeaderUtils.get_provider_specific_headers" + ) as mock_provider_headers: + mock_provider_headers.return_value = { + "X-Priority": "provider", + "X-Provider-Only": "keep-this-too" + } + + captured_headers = {} + def capture_validate(*args, **kwargs): + captured_headers.update(kwargs.get("headers", {})) + return ({"x-api-key": "test-key"}, "https://api.anthropic.com") + + mock_config.validate_anthropic_messages_environment = capture_validate + mock_config.transform_anthropic_messages_request = Mock( + return_value={"model": "claude-3-opus-20240229", "messages": []} + ) + + try: + await handler.async_anthropic_messages_handler( + model="claude-3-opus-20240229", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=mock_config, + anthropic_messages_optional_request_params={}, + custom_llm_provider="anthropic", + litellm_params=GenericLiteLLMParams(), + logging_obj=mock_logging_obj, + client=mock_client, + kwargs=kwargs, + ) + except Exception: + pass + + # Verify priority: provider_specific should win + assert captured_headers["X-Priority"] == "provider" + # Verify all unique headers from different sources are present + assert captured_headers["X-Forwarded-Only"] == "keep" + assert captured_headers["X-Extra-Only"] == "also-keep" + assert captured_headers["X-Provider-Only"] == "keep-this-too" From 661bccbc3984396b13900ee9069754dca244e83d Mon Sep 17 00:00:00 2001 From: Colin Lin Date: Mon, 1 Dec 2025 14:15:54 -0500 Subject: [PATCH 222/349] fixed flaky test by sorting list --- .../llms/bedrock/test_anthropic_beta_support.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py index bd64670517c..7de2294954c 100644 --- a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py +++ b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py @@ -80,7 +80,8 @@ class TestAnthropicBetaHeaderSupport: assert "additionalModelRequestFields" in result additional_fields = result["additionalModelRequestFields"] assert "anthropic_beta" in additional_fields - assert additional_fields["anthropic_beta"] == ["context-1m-2025-08-07", "interleaved-thinking-2025-05-14"] + # Sort both arrays before comparing to avoid flakiness from ordering differences + assert sorted(additional_fields["anthropic_beta"]) == sorted(["context-1m-2025-08-07", "interleaved-thinking-2025-05-14"]) def test_messages_transformation_anthropic_beta(self): """Test that Messages API transformation includes anthropic_beta in request.""" @@ -96,7 +97,8 @@ class TestAnthropicBetaHeaderSupport: ) assert "anthropic_beta" in result - assert result["anthropic_beta"] == ["output-128k-2025-02-19"] + # Sort both arrays before comparing to avoid flakiness from ordering differences + assert sorted(result["anthropic_beta"]) == sorted(["output-128k-2025-02-19"]) def test_converse_computer_use_compatibility(self): """Test that user anthropic_beta headers work with computer use tools.""" @@ -287,4 +289,4 @@ class TestAnthropicBetaHeaderSupport: assert "prompt-caching-2024-07-31" not in result["anthropic_beta"] else: # If no beta headers, that's also fine - assert True \ No newline at end of file + assert True From 69a6c25a5ff0c04799b597c602e2853f011d7819 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 1 Dec 2025 12:28:53 -0800 Subject: [PATCH 223/349] Add user alias to user table --- .../src/components/view_users/columns.tsx | 6 + .../src/components/view_users/table.test.tsx | 187 ++++++------------ .../src/components/view_users/types.ts | 1 + 3 files changed, 68 insertions(+), 126 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_users/columns.tsx b/ui/litellm-dashboard/src/components/view_users/columns.tsx index 20df4fc246e..48895c69def 100644 --- a/ui/litellm-dashboard/src/components/view_users/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_users/columns.tsx @@ -46,6 +46,12 @@ export const columns = ( enableSorting: true, cell: ({ row }) => {possibleUIRoles?.[row.original.user_role]?.ui_label || "-"}, }, + { + header: "User Alias", + accessorKey: "user_alias", + enableSorting: false, + cell: ({ row }) => {row.original.user_alias || "-"}, + }, { header: "Spend (USD)", accessorKey: "spend", diff --git a/ui/litellm-dashboard/src/components/view_users/table.test.tsx b/ui/litellm-dashboard/src/components/view_users/table.test.tsx index 8ef887932cc..c688d5749d0 100644 --- a/ui/litellm-dashboard/src/components/view_users/table.test.tsx +++ b/ui/litellm-dashboard/src/components/view_users/table.test.tsx @@ -1,63 +1,52 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; - import { UserDataTable } from "./table"; +const defaultFilters = { + email: "", + user_id: "", + user_role: "", + sso_user_id: "", + team: "", + model: "", + min_spend: null, + max_spend: null, + sort_by: "", + sort_order: "asc" as const, +}; + +const getDefaultProps = () => ({ + data: [] as any[], + columns: [] as any[], + accessToken: null, + userRole: "Admin", + possibleUIRoles: null as Record> | null, + filters: defaultFilters, + updateFilters: vi.fn(), + initialFilters: defaultFilters, + teams: [] as any[], + handleEdit: vi.fn(), + handleDelete: vi.fn(), + handleResetPassword: vi.fn(), + userListResponse: { users: [], total: 0, page: 1, page_size: 25, total_pages: 1 }, + currentPage: 1, + handlePageChange: vi.fn(), +}); + describe("UserDataTable", () => { it("should render the UserDataTable component", () => { - const filters = { - email: "", - user_id: "", - user_role: "", - sso_user_id: "", - team: "", - model: "", - min_spend: null, - max_spend: null, - sort_by: "", - sort_order: "asc" as const, - }; - - const updateFilters = vi.fn(); - - render( - , - ); + render(); expect(screen.getByText("Filters")).toBeInTheDocument(); }); it("should call onSortChange when clicking a sortable header", () => { const filters = { - email: "", - user_id: "", - user_role: "", - sso_user_id: "", - team: "", - model: "", - min_spend: null, - max_spend: null, + ...defaultFilters, sort_by: "created_at", sort_order: "desc" as const, }; - const updateFilters = vi.fn(); const onSortChange = vi.fn(); const possibleUIRoles = { @@ -67,21 +56,10 @@ describe("UserDataTable", () => { render( , @@ -96,41 +74,7 @@ describe("UserDataTable", () => { }); it("should show skeleton loaders when isLoading is true", () => { - const filters = { - email: "", - user_id: "", - user_role: "", - sso_user_id: "", - team: "", - model: "", - min_spend: null, - max_spend: null, - sort_by: "", - sort_order: "asc" as const, - }; - - const updateFilters = vi.fn(); - - render( - , - ); + render(); expect(screen.queryByText(/Showing/i)).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: /Previous/i })).not.toBeInTheDocument(); @@ -138,44 +82,35 @@ describe("UserDataTable", () => { }); it("should show actual content when isLoading is false", () => { - const filters = { - email: "", - user_id: "", - user_role: "", - sso_user_id: "", - team: "", - model: "", - min_spend: null, - max_spend: null, - sort_by: "", - sort_order: "asc" as const, - }; - - const updateFilters = vi.fn(); - - render( - , - ); + render(); expect(screen.getByText(/Showing/i)).toBeInTheDocument(); expect(screen.getByRole("button", { name: /Previous/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /Next/i })).toBeInTheDocument(); }); + + it("should render all column headers", () => { + const possibleUIRoles = { + admin: { ui_label: "Admin" }, + user: { ui_label: "User" }, + }; + + render(); + + [ + "User ID", + "Email", + "Global Proxy Role", + "User Alias", + "Spend (USD)", + "Budget (USD)", + "SSO ID", + "API Keys", + "Created At", + "Updated At", + "Actions", + ].forEach((header) => { + expect(screen.getByRole("columnheader", { name: header })).toBeInTheDocument(); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/view_users/types.ts b/ui/litellm-dashboard/src/components/view_users/types.ts index d976d46ebc1..d674db5c7db 100644 --- a/ui/litellm-dashboard/src/components/view_users/types.ts +++ b/ui/litellm-dashboard/src/components/view_users/types.ts @@ -1,6 +1,7 @@ export interface UserInfo { user_id: string; user_email: string; + user_alias: string | null; user_role: string; spend: number; max_budget: number | null; From a73bd751fcc6895fa27b801d61b11acb03f91e65 Mon Sep 17 00:00:00 2001 From: YutaSaito <36355491+uc4w6c@users.noreply.github.com> Date: Tue, 2 Dec 2025 05:38:49 +0900 Subject: [PATCH 224/349] doc: add images for tool permission guardrail (#17322) --- .../img/create_guard_tool_permission.png | Bin 0 -> 51115 bytes .../img/create_rule_tool_permission.png | Bin 0 -> 76256 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/my-website/img/create_guard_tool_permission.png create mode 100644 docs/my-website/img/create_rule_tool_permission.png diff --git a/docs/my-website/img/create_guard_tool_permission.png b/docs/my-website/img/create_guard_tool_permission.png new file mode 100644 index 0000000000000000000000000000000000000000..f6e0e77b1aa8c64447170b1db78bbde4ee4714a7 GIT binary patch literal 51115 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⁢iQJIr|hfF_P-@E@2gTV8yAc)*w^!Iq_C&1j(fVx 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�Xi|ViYoZ*4fAou1UpoI<2L}v5U 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 Date: Mon, 1 Dec 2025 12:55:47 -0800 Subject: [PATCH 225/349] [Feat] WatsonX - allow passing zen_api_key dynamically (#16655) * test_watsonx_zen_api_key_from_client * zen api key * docs using zen api key --- .../docs/providers/watsonx/index.md | 53 ++++ litellm/llms/anthropic/skills/readme.md | 286 +++++++++++++++++- litellm/llms/watsonx/common_utils.py | 6 +- .../test_litellm/llms/watsonx/test_watsonx.py | 90 ++++++ 4 files changed, 422 insertions(+), 13 deletions(-) diff --git a/docs/my-website/docs/providers/watsonx/index.md b/docs/my-website/docs/providers/watsonx/index.md index 279d2d1024e..14e0c07c081 100644 --- a/docs/my-website/docs/providers/watsonx/index.md +++ b/docs/my-website/docs/providers/watsonx/index.md @@ -175,3 +175,56 @@ For all available models, see [watsonx.ai documentation](https://dataplatform.cl For all available embedding models, see [watsonx.ai embedding documentation](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx). + +## Advanced + +### Using Zen API Key + +You can use a Zen API key for long-term authentication instead of generating IAM tokens. Pass it either as an environment variable or as a parameter: + +```python +import os +from litellm import completion + +# Option 1: Set as environment variable +os.environ["WATSONX_ZENAPIKEY"] = "your-zen-api-key" + +response = completion( + model="watsonx/ibm/granite-13b-chat-v2", + messages=[{"content": "What is your favorite color?", "role": "user"}], + project_id="your-project-id" +) + +# Option 2: Pass as parameter +response = completion( + model="watsonx/ibm/granite-13b-chat-v2", + messages=[{"content": "What is your favorite color?", "role": "user"}], + zen_api_key="your-zen-api-key", + project_id="your-project-id" +) +``` + +**Using with LiteLLM Proxy via OpenAI client:** + +```python +import openai + +client = openai.OpenAI( + api_key="sk-1234", # LiteLLM proxy key + base_url="http://0.0.0.0:4000" +) + +response = client.chat.completions.create( + model="watsonx/ibm/granite-3-3-8b-instruct", + messages=[{"role": "user", "content": "What is your favorite color?"}], + max_tokens=2048, + extra_body={ + "project_id": "your-project-id", + "zen_api_key": "your-zen-api-key" + } +) +``` + +See [IBM documentation](https://www.ibm.com/docs/en/watsonx/w-and-w/2.2.0?topic=keys-generating-zenapikey-authorization-tokens) for more information on generating Zen API keys. + + diff --git a/litellm/llms/anthropic/skills/readme.md b/litellm/llms/anthropic/skills/readme.md index 898639cd44b..0602272256c 100644 --- a/litellm/llms/anthropic/skills/readme.md +++ b/litellm/llms/anthropic/skills/readme.md @@ -1,17 +1,279 @@ -# Anthropic Skills API +# Anthropic Skills API Integration -This folder maintains the integration for the Anthropic Skills API. +This module provides comprehensive support for the Anthropic Skills API through LiteLLM. -You can do the following with the Anthropic Skills API: +## Features -1. Create a new skill -2. List all skills -3. Get a skill -4. Delete a skill +The Skills API allows you to: +- **Create skills**: Define reusable AI capabilities +- **List skills**: Browse all available skills +- **Get skills**: Retrieve detailed information about a specific skill +- **Delete skills**: Remove skills that are no longer needed +## Quick Start -Versions: - - Create Skill Version - - List Skill Versions - - Get Skill Version - - Delete Skill Version \ No newline at end of file +### Prerequisites + +Set your Anthropic API key: +```python +import os +os.environ["ANTHROPIC_API_KEY"] = "your-api-key-here" +``` + +### Basic Usage + +#### Create a Skill + +```python +import litellm + +# Create a skill with files +# Note: All files must be in the same top-level directory +# and must include a SKILL.md file at the root +skill = litellm.create_skill( + files=[ + # List of file objects to upload + # Must include SKILL.md + ], + display_title="Python Code Generator", + custom_llm_provider="anthropic" +) +print(f"Created skill: {skill.id}") + +# Asynchronous version +skill = await litellm.acreate_skill( + files=[...], # Your files here + display_title="Python Code Generator", + custom_llm_provider="anthropic" +) +``` + +#### List Skills + +```python +# List all skills +skills = litellm.list_skills( + custom_llm_provider="anthropic" +) + +for skill in skills.data: + print(f"{skill.display_title}: {skill.id}") + +# With pagination and filtering +skills = litellm.list_skills( + limit=20, + source="custom", # Filter by 'custom' or 'anthropic' + custom_llm_provider="anthropic" +) + +# Get next page if available +if skills.has_more: + next_page = litellm.list_skills( + page=skills.next_page, + custom_llm_provider="anthropic" + ) +``` + +#### Get a Skill + +```python +skill = litellm.get_skill( + skill_id="skill_abc123", + custom_llm_provider="anthropic" +) + +print(f"Skill: {skill.display_title}") +print(f"Created: {skill.created_at}") +print(f"Latest version: {skill.latest_version}") +print(f"Source: {skill.source}") +``` + +#### Delete a Skill + +```python +result = litellm.delete_skill( + skill_id="skill_abc123", + custom_llm_provider="anthropic" +) + +print(f"Deleted skill {result.id}, type: {result.type}") +``` + +## API Reference + +### `create_skill()` + +Create a new skill. + +**Parameters:** +- `files` (List[Any], optional): Files to upload for the skill. All files must be in the same top-level directory and must include a SKILL.md file at the root. +- `display_title` (str, optional): Display title for the skill +- `custom_llm_provider` (str, optional): Provider name (default: "anthropic") +- `extra_headers` (dict, optional): Additional HTTP headers +- `timeout` (float, optional): Request timeout + +**Returns:** +- `Skill`: The created skill object + +**Async version:** `acreate_skill()` + +### `list_skills()` + +List all skills. + +**Parameters:** +- `limit` (int, optional): Number of results to return per page (max 100, default 20) +- `page` (str, optional): Pagination token for fetching a specific page of results +- `source` (str, optional): Filter skills by source ('custom' or 'anthropic') +- `custom_llm_provider` (str, optional): Provider name (default: "anthropic") +- `extra_headers` (dict, optional): Additional HTTP headers +- `timeout` (float, optional): Request timeout + +**Returns:** +- `ListSkillsResponse`: Object containing a list of skills and pagination info + +**Async version:** `alist_skills()` + +### `get_skill()` + +Get a specific skill by ID. + +**Parameters:** +- `skill_id` (str, required): The skill ID +- `custom_llm_provider` (str, optional): Provider name (default: "anthropic") +- `extra_headers` (dict, optional): Additional HTTP headers +- `timeout` (float, optional): Request timeout + +**Returns:** +- `Skill`: The requested skill object + +**Async version:** `aget_skill()` + +### `delete_skill()` + +Delete a skill. + +**Parameters:** +- `skill_id` (str, required): The skill ID to delete +- `custom_llm_provider` (str, optional): Provider name (default: "anthropic") +- `extra_headers` (dict, optional): Additional HTTP headers +- `timeout` (float, optional): Request timeout + +**Returns:** +- `DeleteSkillResponse`: Object with `id` and `type` fields + +**Async version:** `adelete_skill()` + +## Response Types + +### `Skill` + +Represents a skill from the Anthropic Skills API. + +**Fields:** +- `id` (str): Unique identifier +- `created_at` (str): ISO 8601 timestamp +- `display_title` (str, optional): Display title +- `latest_version` (str, optional): Latest version identifier +- `source` (str): Source ("custom" or "anthropic") +- `type` (str): Object type (always "skill") +- `updated_at` (str): ISO 8601 timestamp + +### `ListSkillsResponse` + +Response from listing skills. + +**Fields:** +- `data` (List[Skill]): List of skills +- `next_page` (str, optional): Pagination token for the next page +- `has_more` (bool): Whether more skills are available + +### `DeleteSkillResponse` + +Response from deleting a skill. + +**Fields:** +- `id` (str): The deleted skill ID +- `type` (str): Deleted object type (always "skill_deleted") + +## Architecture + +The Skills API implementation follows LiteLLM's standard patterns: + +1. **Type Definitions** (`litellm/types/llms/anthropic_skills.py`) + - Pydantic models for request/response types + - TypedDict definitions for request parameters + +2. **Base Configuration** (`litellm/llms/base_llm/skills/transformation.py`) + - Abstract base class `BaseSkillsAPIConfig` + - Defines transformation interface for provider-specific implementations + +3. **Provider Implementation** (`litellm/llms/anthropic/skills/transformation.py`) + - `AnthropicSkillsConfig` - Anthropic-specific transformations + - Handles API authentication, URL construction, and response mapping + +4. **Main Handler** (`litellm/skills/main.py`) + - Public API functions (sync and async) + - Request validation and routing + - Error handling + +5. **HTTP Handlers** (`litellm/llms/custom_httpx/llm_http_handler.py`) + - Low-level HTTP request/response handling + - Connection pooling and retry logic + +## Beta API Support + +The Skills API is in beta. The beta header (`skills-2025-10-02`) is automatically added by the Anthropic provider configuration. You can customize it if needed: + +```python +skill = litellm.create_skill( + display_title="My Skill", + extra_headers={ + "anthropic-beta": "skills-2025-10-02" # Or any other beta version + }, + custom_llm_provider="anthropic" +) +``` + +The default beta version is configured in `litellm.constants.ANTHROPIC_SKILLS_API_BETA_VERSION`. + +## Error Handling + +All Skills API functions follow LiteLLM's standard error handling: + +```python +import litellm + +try: + skill = litellm.create_skill( + display_title="My Skill", + custom_llm_provider="anthropic" + ) +except litellm.exceptions.AuthenticationError as e: + print(f"Authentication failed: {e}") +except litellm.exceptions.RateLimitError as e: + print(f"Rate limit exceeded: {e}") +except litellm.exceptions.APIError as e: + print(f"API error: {e}") +``` + +## Contributing + +To add support for Skills API to a new provider: + +1. Create provider-specific configuration class inheriting from `BaseSkillsAPIConfig` +2. Implement all abstract methods for request/response transformations +3. Register the config in `ProviderConfigManager.get_provider_skills_api_config()` +4. Add appropriate tests + +## Related Documentation + +- [Anthropic Skills API Documentation](https://platform.claude.com/docs/en/api/beta/skills/create) +- [LiteLLM Responses API](../../../responses/) +- [Provider Configuration System](../../base_llm/) + +## Support + +For issues or questions: +- GitHub Issues: https://github.com/BerriAI/litellm/issues +- Discord: https://discord.gg/wuPM9dRgDw diff --git a/litellm/llms/watsonx/common_utils.py b/litellm/llms/watsonx/common_utils.py index 58b33097cbd..0207020534c 100644 --- a/litellm/llms/watsonx/common_utils.py +++ b/litellm/llms/watsonx/common_utils.py @@ -252,9 +252,13 @@ class IBMWatsonXMixin: Optional[str], optional_params.get("token") or get_secret_str("WATSONX_TOKEN"), ) + zen_api_key = cast( + Optional[str], + optional_params.pop("zen_api_key", None) or get_secret_str("WATSONX_ZENAPIKEY"), + ) if token: headers["Authorization"] = f"Bearer {token}" - elif zen_api_key := get_secret_str("WATSONX_ZENAPIKEY"): + elif zen_api_key: headers["Authorization"] = f"ZenApiKey {zen_api_key}" else: token = _generate_watsonx_token(api_key=api_key, token=token) diff --git a/tests/test_litellm/llms/watsonx/test_watsonx.py b/tests/test_litellm/llms/watsonx/test_watsonx.py index a41316bb47e..fc45a13c2c1 100644 --- a/tests/test_litellm/llms/watsonx/test_watsonx.py +++ b/tests/test_litellm/llms/watsonx/test_watsonx.py @@ -414,3 +414,93 @@ def test_watsonx_chat_completion_with_reasoning_effort(monkeypatch): assert ( json_data["reasoning_effort"] == "low" ), "The value of 'reasoning_effort' should be 'low'." + + +def test_watsonx_zen_api_key_from_client(monkeypatch, watsonx_chat_completion_call): + """ + Test that zen_api_key can be passed from client code and is used in Authorization header. + """ + monkeypatch.setenv("WATSONX_PROJECT_ID", "test-project-id") + monkeypatch.setenv("WATSONX_API_BASE", "https://test-api.watsonx.ai") + + model = "watsonx/ibm/granite-3-3-8b-instruct" + messages = [{"role": "user", "content": "What is your favorite color?"}] + + client = HTTPHandler() + + zen_api_key = "U1ZDLWQo=" + + # No need to patch token call since zen_api_key should skip token generation + with patch.object(client, "post") as mock_post: + try: + completion( + model=model, + messages=messages, + api_key="test_api_key", + client=client, + zen_api_key=zen_api_key, + ) + except Exception as e: + print(f"Caught expected exception: {e}") + + # Verify the request was made + assert mock_post.call_count == 1, "The completion endpoint should have been called once." + + # Get the headers sent in the POST request + request_kwargs = mock_post.call_args.kwargs + headers = request_kwargs["headers"] + + print("\nHeaders sent to WatsonX API:") + print(json.dumps(dict(headers), indent=2)) + + # Verify Authorization header uses ZenApiKey format + assert "Authorization" in headers, "Authorization header should be present." + assert headers["Authorization"] == f"ZenApiKey {zen_api_key}", ( + f"Authorization header should use ZenApiKey format. " + f"Expected: 'ZenApiKey {zen_api_key}', Got: '{headers['Authorization']}'" + ) + + +def test_watsonx_zen_api_key_from_env(monkeypatch, watsonx_chat_completion_call): + """ + Test that zen_api_key from environment variable is used in Authorization header. + """ + monkeypatch.setenv("WATSONX_PROJECT_ID", "test-project-id") + monkeypatch.setenv("WATSONX_API_BASE", "https://test-api.watsonx.ai") + + zen_api_key = "U1ZDLWxpdG--===" + monkeypatch.setenv("WATSONX_ZENAPIKEY", zen_api_key) + + model = "watsonx/ibm/granite-3-3-8b-instruct" + messages = [{"role": "user", "content": "What is your favorite color?"}] + + client = HTTPHandler() + + # No need to patch token call since zen_api_key should skip token generation + with patch.object(client, "post") as mock_post: + try: + completion( + model=model, + messages=messages, + api_key="test_api_key", + client=client, + ) + except Exception as e: + print(f"Caught expected exception: {e}") + + # Verify the request was made + assert mock_post.call_count == 1, "The completion endpoint should have been called once." + + # Get the headers sent in the POST request + request_kwargs = mock_post.call_args.kwargs + headers = request_kwargs["headers"] + + print("\nHeaders sent to WatsonX API:") + print(json.dumps(dict(headers), indent=2)) + + # Verify Authorization header uses ZenApiKey format + assert "Authorization" in headers, "Authorization header should be present." + assert headers["Authorization"] == f"ZenApiKey {zen_api_key}", ( + f"Authorization header should use ZenApiKey format. " + f"Expected: 'ZenApiKey {zen_api_key}', Got: '{headers['Authorization']}'" + ) From 21baa354cc17b6f706f3bf93c9c8ba024a72bdb0 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 1 Dec 2025 13:13:39 -0800 Subject: [PATCH 226/349] Standardize API Key vs Virtual Key in UI --- .../src/components/activity_metrics.tsx | 7 ++- .../components/bulk_create_users_button.tsx | 8 +-- .../src/components/cache_dashboard.tsx | 12 ++--- .../PassThroughSecuritySection.tsx | 16 ++---- .../src/components/dashboard_default_team.tsx | 3 +- .../src/components/entity_usage.tsx | 2 +- .../src/components/make_agent_public_form.tsx | 16 ++---- .../src/components/make_mcp_public_form.tsx | 49 ++++++++----------- .../src/components/make_model_public_form.tsx | 4 +- .../src/components/mcp_tools/mcp_connect.tsx | 10 ++-- .../src/components/new_usage.test.tsx | 2 +- .../src/components/new_usage.tsx | 2 +- .../organisms/create_key_button.tsx | 14 +++--- .../organisms/regenerate_key_modal.tsx | 10 ++-- .../components/playground/chat_ui/ChatUI.tsx | 6 +-- .../playground/compareUI/CompareUI.tsx | 6 +-- .../llm_calls/anthropic_messages.tsx | 2 +- .../playground/llm_calls/embeddings_api.tsx | 2 +- .../playground/llm_calls/responses_api.tsx | 2 +- .../KeyInfoView.handleKeyUpdate.test.tsx | 2 +- .../components/templates/key_info_view.tsx | 8 +-- .../components/templates/view_key_table.tsx | 20 ++++---- ui/litellm-dashboard/src/components/usage.tsx | 2 +- .../src/components/view_users/columns.tsx | 4 +- .../components/view_users/user_info_view.tsx | 10 ++-- 25 files changed, 99 insertions(+), 120 deletions(-) diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx index 1878e1364d0..a791ece9bb7 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx @@ -38,10 +38,9 @@ const ModelSection = ({ modelName, metrics }: { modelName: string; metrics: Mode - {/* Top API Keys Section */} {metrics.top_api_keys && metrics.top_api_keys.length > 0 && ( - Top API Keys by Spend + Top Virtual Keys by Spend ); @@ -267,8 +261,8 @@ const MakeMCPPublicForm: React.FC = ({
- Total: {selectedServers.size} MCP server{selectedServers.size !== 1 ? "s" : ""} will be made - public + Total: {selectedServers.size} MCP server{selectedServers.size !== 1 ? "s" : ""} will be + made public
{metrics.top_api_keys.map((keyData, index) => ( @@ -384,12 +383,12 @@ export const processActivityData = ( }); }); - // Process API key breakdowns for each metric (skip if key is 'api_keys' to avoid duplication) + // Process Virtual Key breakdowns for each metric (skip if key is 'api_keys' to avoid duplication) if (key !== "api_keys") { Object.entries(modelMetrics).forEach(([model, _]) => { const apiKeyBreakdown: Record = {}; - // Aggregate API key data across all days + // Aggregate Virtual Key data across all days dailyActivity.results.forEach((day) => { const modelData = day.breakdown[key]?.[model]; if (modelData && "api_key_breakdown" in modelData) { diff --git a/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx b/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx index d34b14ceabf..a8046d146a8 100644 --- a/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx +++ b/ui/litellm-dashboard/src/components/bulk_create_users_button.tsx @@ -569,7 +569,7 @@ const BulkCreateUsersButton: React.FC = ({
  • Download our CSV template
  • Add your users' information to the spreadsheet
  • Save the file and upload it here
  • -
  • After creation, download the results file containing the API keys for each user
  • +
  • After creation, download the results file containing the Virtual Keys for each user
  • @@ -809,9 +809,9 @@ const BulkCreateUsersButton: React.FC = ({
    User creation complete - Next step: Download the credentials file containing API - keys and invitation links. Users will need these API keys to make LLM requests through - LiteLLM. + Next step: Download the credentials file containing + Virtual Keys and invitation links. Users will need these Virtual Keys to make LLM requests + through LiteLLM.
    diff --git a/ui/litellm-dashboard/src/components/cache_dashboard.tsx b/ui/litellm-dashboard/src/components/cache_dashboard.tsx index a1c0cb0a664..38c0f1a8f41 100644 --- a/ui/litellm-dashboard/src/components/cache_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/cache_dashboard.tsx @@ -293,7 +293,11 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole - + {uniqueApiKeys.map((key) => ( {key} @@ -388,11 +392,7 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole /> - + diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx index c42094abb55..c63770d3c85 100644 --- a/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx @@ -21,7 +21,7 @@ const PassThroughSecuritySection: React.FC = ({ Security - When enabled, requests to this endpoint will require a valid LiteLLM API key + When enabled, requests to this endpoint will require a valid LiteLLM Virtual Key {premiumUser ? ( @@ -35,22 +35,13 @@ const PassThroughSecuritySection: React.FC = ({ ) : (
    - + Authentication (Premium)
    Setting authentication for pass-through endpoints is a LiteLLM Enterprise feature. Get a trial key{" "} - + here . @@ -63,4 +54,3 @@ const PassThroughSecuritySection: React.FC = ({ }; export default PassThroughSecuritySection; - diff --git a/ui/litellm-dashboard/src/components/dashboard_default_team.tsx b/ui/litellm-dashboard/src/components/dashboard_default_team.tsx index 36805b8912a..6506e1a60a5 100644 --- a/ui/litellm-dashboard/src/components/dashboard_default_team.tsx +++ b/ui/litellm-dashboard/src/components/dashboard_default_team.tsx @@ -69,7 +69,8 @@ const DashboardTeam: React.FC = ({ Select Team - If you belong to multiple teams, this setting controls which team is used by default when creating new API Keys. + If you belong to multiple teams, this setting controls which team is used by default when creating new Virtual + Keys. Default Team: If no team_id is set for a key, it will be grouped under here. diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index a5789b7dbac..501eac7124b 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -550,7 +550,7 @@ const EntityUsage: React.FC = ({ {/* Top API Keys */} - Top API Keys + Top Virtual Keys = ({ setLoading(true); try { const agentIdsToMakePublic = Array.from(selectedAgents); - + // Make batch API call for all agents await makeAgentsPublicCall(accessToken, agentIdsToMakePublic); @@ -127,8 +127,8 @@ const MakeAgentPublicForm: React.FC = ({
    - Select the agents you want to be visible on the public model hub. Users will still require a valid API key to - use these agents. + Select the agents you want to be visible on the public model hub. Users will still require a valid Virtual Key + to use these agents.
    @@ -141,10 +141,7 @@ const MakeAgentPublicForm: React.FC = ({ agentHubData.map((agent) => { const agentId = agent.agent_id || agent.name; return ( -
    +
    handleAgentSelection(agentId, e.target.checked)} @@ -217,9 +214,7 @@ const MakeAgentPublicForm: React.FC = ({ )}
    - {agent?.description && ( - {agent.description} - )} + {agent?.description && {agent.description}}
    ); @@ -296,4 +291,3 @@ const MakeAgentPublicForm: React.FC = ({ }; export default MakeAgentPublicForm; - diff --git a/ui/litellm-dashboard/src/components/make_mcp_public_form.tsx b/ui/litellm-dashboard/src/components/make_mcp_public_form.tsx index 29f866f8bc6..f7bba175800 100644 --- a/ui/litellm-dashboard/src/components/make_mcp_public_form.tsx +++ b/ui/litellm-dashboard/src/components/make_mcp_public_form.tsx @@ -76,7 +76,7 @@ const MakeMCPPublicForm: React.FC = ({ const publicServerIds = mcpHubData .filter((server) => server.mcp_info?.is_public === true) .map((server) => server.server_id); - + // Preselect servers that are already public setSelectedServers(new Set(publicServerIds)); } @@ -91,7 +91,7 @@ const MakeMCPPublicForm: React.FC = ({ setLoading(true); try { const serverIdsToMakePublic = Array.from(selectedServers); - + // Make batch API call for all servers await makeMCPPublicCall(accessToken, serverIdsToMakePublic); @@ -128,8 +128,8 @@ const MakeMCPPublicForm: React.FC = ({
    - Select the MCP servers you want to be visible on the public model hub. Users will still require a valid API key to - use these servers. + Select the MCP servers you want to be visible on the public model hub. Users will still require a valid + Virtual Key to use these servers.
    @@ -161,22 +161,20 @@ const MakeMCPPublicForm: React.FC = ({ {server.transport} - {server.status || "unknown"}
    - - {server.description || server.url} - + {server.description || server.url} {server.allowed_tools && server.allowed_tools.length > 0 && (
    {server.allowed_tools.slice(0, 3).map((tool, idx) => ( @@ -236,14 +234,14 @@ const MakeMCPPublicForm: React.FC = ({ {server.transport} - {server.status || "unknown"} @@ -251,12 +249,8 @@ const MakeMCPPublicForm: React.FC = ({ )}
    - {server?.description && ( - {server.description} - )} - {server?.url && ( - {server.url} - )} + {server?.description && {server.description}} + {server?.url && {server.url}}
    @@ -333,4 +327,3 @@ const MakeMCPPublicForm: React.FC = ({ }; export default MakeMCPPublicForm; - diff --git a/ui/litellm-dashboard/src/components/make_model_public_form.tsx b/ui/litellm-dashboard/src/components/make_model_public_form.tsx index e67d60fb33b..750bdc24eeb 100644 --- a/ui/litellm-dashboard/src/components/make_model_public_form.tsx +++ b/ui/litellm-dashboard/src/components/make_model_public_form.tsx @@ -152,8 +152,8 @@ const MakeModelPublicForm: React.FC = ({
    - API Keys + Virtual Keys
    {userData.keys?.length && userData.keys?.length > 0 ? ( userData.keys.map((key, index) => ( @@ -476,7 +478,7 @@ export default function UserInfoView({ )) ) : ( - No API keys + No Virtual Keys )}
    From 24f847b84c947e13b765edaf172f646fd1b06297 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Dec 2025 13:59:00 -0800 Subject: [PATCH 227/349] [Feat] JWT Auth - AI Gateway, allow using regular OIDC flow with user info endpoints (#17324) * feat: allow fetching OIDC user info * test: use test_auth_builder_with_oidc_userinfo_enabled gets user info when enabled * fix tool permission doc * docs fix diagram --- .../docs/proxy/guardrails/tool_permission.md | 5 - docs/my-website/docs/proxy/token_auth.md | 66 +++++ litellm/proxy/_types.py | 18 ++ litellm/proxy/auth/handle_jwt.py | 76 +++++- .../proxy/auth/test_handle_jwt.py | 227 +++++++++++++++++- 5 files changed, 385 insertions(+), 7 deletions(-) diff --git a/docs/my-website/docs/proxy/guardrails/tool_permission.md b/docs/my-website/docs/proxy/guardrails/tool_permission.md index 897c31d9dab..1827333654f 100644 --- a/docs/my-website/docs/proxy/guardrails/tool_permission.md +++ b/docs/my-website/docs/proxy/guardrails/tool_permission.md @@ -1,4 +1,3 @@ -import Image from '@theme/IdealImage'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -14,8 +13,6 @@ LiteLLM provides the LiteLLM Tool Permission Guardrail that lets you control whi Open the LiteLLM Dashboard, click **Add New Guardrail**, and choose **LiteLLM Tool Permission Guardrail**. This loads the rule builder UI. -Configure tool permission guardrail in LiteLLM UI - #### Step 2: Define Regex Rules 1. Click **Add Rule**. @@ -24,8 +21,6 @@ Open the LiteLLM Dashboard, click **Add New Guardrail**, and choose **LiteLLM To 4. Optionally add a regex for tool type (e.g., `^function$`). 5. Pick **Allow** or **Deny**. -Configure tool permission guardrail in LiteLLM UI - #### Step 3: Restrict Tool Arguments (Optional) Select **+ Restrict tool arguments** to attach regex validations to nested paths (dot + `[]` notation). This enforces that sensitive parameters (such as `arguments.to[]`) conform to pre-approved formats. diff --git a/docs/my-website/docs/proxy/token_auth.md b/docs/my-website/docs/proxy/token_auth.md index c2a88010d79..c465c1022e4 100644 --- a/docs/my-website/docs/proxy/token_auth.md +++ b/docs/my-website/docs/proxy/token_auth.md @@ -407,6 +407,72 @@ general_settings: user_id_upsert: true # 👈 upserts the user to db, if valid email but not in db ``` +## OIDC UserInfo Endpoint + +Use this when your JWT/access token doesn't contain user-identifying information. LiteLLM will call your identity provider's UserInfo endpoint to fetch user details. + +### When to Use + +- Your JWT is opaque (not self-contained) or lacks user claims +- You need to fetch fresh user information from your identity provider +- Your access tokens don't include email, roles, or other identifying data + +### Configuration + +```yaml title="config.yaml" showLineNumbers +general_settings: + enable_jwt_auth: True + litellm_jwtauth: + # Enable OIDC UserInfo endpoint + oidc_userinfo_enabled: true + oidc_userinfo_endpoint: "https://your-idp.com/oauth2/userinfo" + oidc_userinfo_cache_ttl: 300 # Cache for 5 minutes (default: 300) + + # Map fields from UserInfo response + user_id_jwt_field: "sub" + user_email_jwt_field: "email" + user_roles_jwt_field: "roles" +``` + +### Flow Diagram + +```mermaid +sequenceDiagram + participant Client + participant LiteLLM + participant IdP as Identity Provider + + Client->>LiteLLM: Request with Bearer token + Note over LiteLLM: Check cache for UserInfo + + LiteLLM->>IdP: GET /userinfo (if not cached)
    Authorization: Bearer {token} + IdP-->>LiteLLM: User data (sub, email, roles) + + Note over LiteLLM: Cache response (TTL: 5min)
    Extract user_id, email, roles
    Perform RBAC checks + + LiteLLM-->>Client: Authorized/Denied +``` + +### Example: Azure AD + +```yaml title="config.yaml" showLineNumbers +litellm_jwtauth: + oidc_userinfo_enabled: true + oidc_userinfo_endpoint: "https://graph.microsoft.com/oidc/userinfo" + user_id_jwt_field: "sub" + user_email_jwt_field: "email" +``` + +### Example: Keycloak + +```yaml title="config.yaml" showLineNumbers +litellm_jwtauth: + oidc_userinfo_enabled: true + oidc_userinfo_endpoint: "https://keycloak.example.com/realms/your-realm/protocol/openid-connect/userinfo" + user_id_jwt_field: "sub" + user_roles_jwt_field: "resource_access.your-client.roles" +``` + ## [BETA] Control Access with OIDC Roles Allow JWT tokens with supported roles to access the proxy. diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index fe87a70b244..b5b0bd80602 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3422,6 +3422,9 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): - public_allowed_routes: list of allowed routes for authenticated but unknown litellm role jwt tokens. - enforce_rbac: If true, enforce RBAC for all routes. - custom_validate: A custom function to validates the JWT token. + - oidc_userinfo_endpoint: OIDC UserInfo endpoint URL. When set along with oidc_userinfo_enabled, LiteLLM will call this endpoint with the access token to retrieve user identity information. + - oidc_userinfo_enabled: Enable fetching user info from OIDC UserInfo endpoint instead of just decoding JWT token. Default: False. + - oidc_userinfo_cache_ttl: TTL (in seconds) for caching UserInfo responses. Default: 300s (5 minutes). See `auth_checks.py` for the specific routes """ @@ -3472,6 +3475,21 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): jwt_litellm_role_map: Optional[List[JWTLiteLLMRoleMap]] = None sync_user_role_and_teams: bool = False ######################################################### + ######################################################### + # OIDC UserInfo Endpoint Configuration + oidc_userinfo_endpoint: Optional[str] = Field( + default=None, + description="OIDC UserInfo endpoint URL. If set, LiteLLM will call this endpoint with the access token to retrieve user identity information.", + ) + oidc_userinfo_enabled: bool = Field( + default=False, + description="Enable fetching user info from OIDC UserInfo endpoint instead of just decoding JWT token.", + ) + oidc_userinfo_cache_ttl: float = Field( + default=300, + description="TTL (in seconds) for caching UserInfo responses. Default: 300s (5 minutes).", + ) + ######################################################### def __init__(self, **kwargs: Any) -> None: # get the attribute names for this Pydantic model diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 3e18db2d025..ed6877d1469 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -480,6 +480,71 @@ class JWTHandler: else: return False + async def get_oidc_userinfo(self, token: str) -> dict: + """ + Fetch user information from OIDC UserInfo endpoint. + + This follows the OpenID Connect protocol where an access token + is sent to the identity provider's UserInfo endpoint to retrieve + user identity information. + + Args: + token: The access token to use for authentication + + Returns: + dict: User information from the UserInfo endpoint + + Raises: + Exception: If UserInfo endpoint is not configured or request fails + """ + if not self.litellm_jwtauth.oidc_userinfo_endpoint: + raise Exception( + "OIDC UserInfo endpoint not configured. Set 'oidc_userinfo_endpoint' in JWT auth config." + ) + + # Check cache first + cache_key = f"oidc_userinfo_{token[:20]}" # Use first 20 chars of token as cache key + cached_userinfo = await self.user_api_key_cache.async_get_cache(cache_key) + + if cached_userinfo is not None: + verbose_proxy_logger.debug("Returning cached OIDC UserInfo") + return cached_userinfo + + verbose_proxy_logger.debug( + f"Calling OIDC UserInfo endpoint: {self.litellm_jwtauth.oidc_userinfo_endpoint}" + ) + + try: + # Call the UserInfo endpoint with the access token + response = await self.http_handler.get( + url=self.litellm_jwtauth.oidc_userinfo_endpoint, + headers={ + "Authorization": f"Bearer {token}", + "Accept": "application/json", + }, + ) + + if response.status_code != 200: + raise Exception( + f"OIDC UserInfo endpoint returned status {response.status_code}: {response.text}" + ) + + userinfo = response.json() + verbose_proxy_logger.debug(f"Received OIDC UserInfo: {userinfo}") + + # Cache the userinfo response + await self.user_api_key_cache.async_set_cache( + key=cache_key, + value=userinfo, + ttl=self.litellm_jwtauth.oidc_userinfo_cache_ttl, + ) + + return userinfo + + except Exception as e: + verbose_proxy_logger.error(f"Error fetching OIDC UserInfo: {str(e)}") + raise Exception(f"Failed to fetch OIDC UserInfo: {str(e)}") + async def auth_jwt(self, token: str) -> dict: # Supported algos: https://pyjwt.readthedocs.io/en/stable/algorithms.html # "Warning: Make sure not to mix symmetric and asymmetric algorithms that interpret @@ -1077,7 +1142,16 @@ class JWTAuthManager: proxy_logging_obj: ProxyLogging, ) -> JWTAuthBuilderResult: """Main authentication and authorization builder""" - jwt_valid_token: dict = await jwt_handler.auth_jwt(token=api_key) + # Check if OIDC UserInfo endpoint is enabled + if jwt_handler.litellm_jwtauth.oidc_userinfo_enabled: + verbose_proxy_logger.debug( + "OIDC UserInfo is enabled. Fetching user info from UserInfo endpoint." + ) + # Use the access token to fetch user info from OIDC UserInfo endpoint + jwt_valid_token: dict = await jwt_handler.get_oidc_userinfo(token=api_key) + else: + # Default behavior: decode and validate the JWT token + jwt_valid_token = await jwt_handler.auth_jwt(token=api_key) # Check custom validate if jwt_handler.litellm_jwtauth.custom_validate: diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 8f8f3ced074..603a6928f88 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -846,4 +846,229 @@ async def test_auth_builder_returns_team_membership_object(): assert result["team_membership"].user_id == _user_id, "team_membership user_id should match" assert result["team_membership"].team_id == _team_id, "team_membership team_id should match" assert result["team_membership"].budget_id == "budget_123", "team_membership budget_id should match" - assert result["team_membership"].spend == 10.5, "team_membership spend should match" \ No newline at end of file + assert result["team_membership"].spend == 10.5, "team_membership spend should match" + + +@pytest.mark.asyncio +async def test_auth_builder_with_oidc_userinfo_enabled(): + """Test that auth_builder uses OIDC UserInfo endpoint when enabled""" + from unittest.mock import MagicMock + + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + + # Setup test data + api_key = "test_access_token" + request_data = {"model": "gpt-4"} + general_settings = {"enforce_rbac": False} + route = "/chat/completions" + + user_object = LiteLLM_UserTable( + user_id="test_user_1", user_role=LitellmUserRoles.INTERNAL_USER + ) + + # Create JWT handler with OIDC UserInfo enabled + jwt_handler = JWTHandler() + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=user_api_key_cache, + litellm_jwtauth=LiteLLM_JWTAuth( + oidc_userinfo_enabled=True, + oidc_userinfo_endpoint="https://example.com/oauth2/userinfo", + user_id_jwt_field="sub", + user_email_jwt_field="email", + ), + ) + + # Mock OIDC UserInfo response + userinfo_response = { + "sub": "test_user_1", + "email": "test@example.com", + "scope": "", + } + + # Mock all the dependencies + with patch.object( + jwt_handler, "get_oidc_userinfo", new_callable=AsyncMock + ) as mock_get_userinfo, patch.object( + jwt_handler, "auth_jwt", new_callable=AsyncMock + ) as mock_auth_jwt, patch.object( + JWTAuthManager, "check_rbac_role", new_callable=AsyncMock + ) as mock_check_rbac, patch.object( + jwt_handler, "get_rbac_role", return_value=None + ) as mock_get_rbac, patch.object( + jwt_handler, "get_scopes", return_value=[] + ) as mock_get_scopes, patch.object( + jwt_handler, "get_object_id", return_value=None + ) as mock_get_object_id, patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=("test_user_1", "test@example.com", True), + ) as mock_get_user_info, patch.object( + jwt_handler, "get_org_id", return_value=None + ) as mock_get_org_id, patch.object( + jwt_handler, "get_end_user_id", return_value=None + ) as mock_get_end_user_id, patch.object( + JWTAuthManager, "check_admin_access", new_callable=AsyncMock, return_value=None + ) as mock_check_admin, patch.object( + JWTAuthManager, + "find_and_validate_specific_team_id", + new_callable=AsyncMock, + return_value=(None, None), + ) as mock_find_team, patch.object( + JWTAuthManager, "get_all_team_ids", return_value=set() + ) as mock_get_all_team_ids, patch.object( + JWTAuthManager, + "find_team_with_model_access", + new_callable=AsyncMock, + return_value=(None, None), + ) as mock_find_team_access, patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None), + ) as mock_get_objects, patch.object( + JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock + ) as mock_map_user, patch.object( + JWTAuthManager, "validate_object_id", return_value=True + ) as mock_validate_object, patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ) as mock_sync_user: + # Set up mock return values + mock_get_userinfo.return_value = userinfo_response + + # Call auth_builder + result = await JWTAuthManager.auth_builder( + api_key=api_key, + jwt_handler=jwt_handler, + request_data=request_data, + general_settings=general_settings, + route=route, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + # Verify that get_oidc_userinfo was called instead of auth_jwt + mock_get_userinfo.assert_called_once_with(token=api_key) + mock_auth_jwt.assert_not_called() # Should not be called when OIDC is enabled + + # Verify the result + assert result["user_id"] == "test_user_1" + assert result["user_object"] == user_object + + +@pytest.mark.asyncio +async def test_auth_builder_with_oidc_userinfo_disabled(): + """Test that auth_builder uses JWT validation when OIDC UserInfo is disabled""" + from unittest.mock import MagicMock + + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + + # Setup test data + api_key = "test_jwt_token" + request_data = {"model": "gpt-4"} + general_settings = {"enforce_rbac": False} + route = "/chat/completions" + + user_object = LiteLLM_UserTable( + user_id="test_user_1", user_role=LitellmUserRoles.INTERNAL_USER + ) + + # Create JWT handler with OIDC UserInfo disabled + jwt_handler = JWTHandler() + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=user_api_key_cache, + litellm_jwtauth=LiteLLM_JWTAuth( + oidc_userinfo_enabled=False, # Disabled + user_id_jwt_field="sub", + ), + ) + + # Mock JWT validation response + jwt_response = { + "sub": "test_user_1", + "scope": "", + } + + # Mock all the dependencies + with patch.object( + jwt_handler, "get_oidc_userinfo", new_callable=AsyncMock + ) as mock_get_userinfo, patch.object( + jwt_handler, "auth_jwt", new_callable=AsyncMock + ) as mock_auth_jwt, patch.object( + JWTAuthManager, "check_rbac_role", new_callable=AsyncMock + ) as mock_check_rbac, patch.object( + jwt_handler, "get_rbac_role", return_value=None + ) as mock_get_rbac, patch.object( + jwt_handler, "get_scopes", return_value=[] + ) as mock_get_scopes, patch.object( + jwt_handler, "get_object_id", return_value=None + ) as mock_get_object_id, patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=("test_user_1", None, None), + ) as mock_get_user_info, patch.object( + jwt_handler, "get_org_id", return_value=None + ) as mock_get_org_id, patch.object( + jwt_handler, "get_end_user_id", return_value=None + ) as mock_get_end_user_id, patch.object( + JWTAuthManager, "check_admin_access", new_callable=AsyncMock, return_value=None + ) as mock_check_admin, patch.object( + JWTAuthManager, + "find_and_validate_specific_team_id", + new_callable=AsyncMock, + return_value=(None, None), + ) as mock_find_team, patch.object( + JWTAuthManager, "get_all_team_ids", return_value=set() + ) as mock_get_all_team_ids, patch.object( + JWTAuthManager, + "find_team_with_model_access", + new_callable=AsyncMock, + return_value=(None, None), + ) as mock_find_team_access, patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None), + ) as mock_get_objects, patch.object( + JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock + ) as mock_map_user, patch.object( + JWTAuthManager, "validate_object_id", return_value=True + ) as mock_validate_object, patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ) as mock_sync_user: + # Set up mock return values + mock_auth_jwt.return_value = jwt_response + + # Call auth_builder + result = await JWTAuthManager.auth_builder( + api_key=api_key, + jwt_handler=jwt_handler, + request_data=request_data, + general_settings=general_settings, + route=route, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + # Verify that auth_jwt was called instead of get_oidc_userinfo + mock_auth_jwt.assert_called_once_with(token=api_key) + mock_get_userinfo.assert_not_called() # Should not be called when OIDC is disabled + + # Verify the result + assert result["user_id"] == "test_user_1" + assert result["user_object"] == user_object \ No newline at end of file From 7a46f3a0830c1f8a5635a6635f02dc7556ce0d30 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Dec 2025 14:05:54 -0800 Subject: [PATCH 228/349] docs: document azure ai provider for anthropic --- .../blog/anthropic_opus_4_5_and_advanced_features/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index be2c0b5dc5d..1e5f968b2ca 100644 --- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -33,7 +33,7 @@ This guide covers Anthropic's latest model (Claude Opus 4.5) and its advanced fe | Input Examples | Claude Opus 4.5, Sonnet 4.5 | | Effort Parameter | Claude Opus 4.5 only | -Supported Providers: [Anthropic](../../docs/providers/anthropic), [Bedrock](../../docs/providers/bedrock), [Vertex AI](../../docs/providers/vertex_partner#vertex-ai---anthropic-claude). +Supported Providers: [Anthropic](../../docs/providers/anthropic), [Bedrock](../../docs/providers/bedrock), [Vertex AI](../../docs/providers/vertex_partner#vertex-ai---anthropic-claude), [Azure AI](../../docs/providers/azure_ai). ## Usage From c9afb869940ae2de27846ed5263ab27a4461d2b7 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Dec 2025 14:06:31 -0800 Subject: [PATCH 229/349] docs(azure_ai.md): document anthropic model usage on azure ai --- docs/my-website/docs/providers/azure_ai.md | 79 +++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/azure_ai.md b/docs/my-website/docs/providers/azure_ai.md index b1b5de5bb34..68e2df676e6 100644 --- a/docs/my-website/docs/providers/azure_ai.md +++ b/docs/my-website/docs/providers/azure_ai.md @@ -312,6 +312,82 @@ LiteLLM supports **ALL** azure ai models. Here's a few examples: | mistral-large-latest | `completion(model="azure_ai/mistral-large-latest", messages)` | | AI21-Jamba-Instruct | `completion(model="azure_ai/ai21-jamba-instruct", messages)` | +## Usage - Azure Anthropic (Azure Foundry Claude) + +LiteLLM funnels Azure Claude deployments through the `azure_ai/` provider so Claude Opus models on Azure Foundry keep working with Tool Search, Effort, streaming, and the rest of the advanced feature set. Point `AZURE_AI_API_BASE` to `https://.services.ai.azure.com/anthropic` (LiteLLM appends `/v1/messages` automatically) and authenticate with `AZURE_AI_API_KEY` or an Azure AD token. + + + + +```python +import os +from litellm import completion + +# Configure Azure credentials +os.environ["AZURE_AI_API_KEY"] = "your-azure-ai-api-key" +os.environ["AZURE_AI_API_BASE"] = "https://my-resource.services.ai.azure.com/anthropic" + +response = completion( + model="azure_ai/claude-opus-4-1", + messages=[{"role": "user", "content": "Explain how Azure Anthropic hosts Claude Opus differently from the public Anthropic API."}], + max_tokens=1200, + temperature=0.7, + stream=True, +) + +for chunk in response: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="", flush=True) +``` + + + + +**1. Set environment variables** + +```bash +export AZURE_AI_API_KEY="your-azure-ai-api-key" +export AZURE_AI_API_BASE="https://my-resource.services.ai.azure.com/anthropic" +``` + +**2. Configure the proxy** + +```yaml +model_list: + - model_name: claude-4-azure + litellm_params: + model: azure_ai/claude-opus-4-1 + api_key: os.environ/AZURE_AI_API_KEY + api_base: os.environ/AZURE_AI_API_BASE +``` + +**3. Start LiteLLM** + +```bash +litellm --config /path/to/config.yaml +``` + +**4. Test the Azure Claude route** + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer $LITELLM_KEY' \ + --data '{ + "model": "claude-4-azure", + "messages": [ + { + "role": "user", + "content": "How do I use Claude Opus 4 via Azure Anthropic in LiteLLM?" + } + ], + "max_tokens": 1024 + }' +``` + + + + ## Rerank Endpoint @@ -397,4 +473,5 @@ curl http://0.0.0.0:4000/rerank \ ``` - \ No newline at end of file + + From f434ca61ec0c637ba676fb90336ff154a992793b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Dec 2025 14:14:41 -0800 Subject: [PATCH 230/349] add kimi-k2-instruct-0905 (#17328) --- litellm/model_prices_and_context_window_backup.json | 13 +++++++++++++ model_prices_and_context_window.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f4f6b94fd18..af63d1e2592 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -10198,6 +10198,19 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/kimi-k2-instruct-0905": { + "input_cost_per_token": 6e-07, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 262144, + "max_output_tokens": 32768, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://app.fireworks.ai/models/fireworks/kimi-k2-instruct-0905", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "fireworks_ai/accounts/fireworks/models/kimi-k2-thinking": { "input_cost_per_token": 6e-07, "litellm_provider": "fireworks_ai", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f4f6b94fd18..af63d1e2592 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -10198,6 +10198,19 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/kimi-k2-instruct-0905": { + "input_cost_per_token": 6e-07, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 262144, + "max_output_tokens": 32768, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://app.fireworks.ai/models/fireworks/kimi-k2-instruct-0905", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "fireworks_ai/accounts/fireworks/models/kimi-k2-thinking": { "input_cost_per_token": 6e-07, "litellm_provider": "fireworks_ai", From b6d6f834e059e1cb0d9062f99189c4f521fe3e1e Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Mon, 1 Dec 2025 14:29:52 -0800 Subject: [PATCH 231/349] (feat) Generic Guardrail API - allows guardrail providers to add INSTANT support for LiteLLM w/out PR to repo (#17175) * feat(generic_guardrail_api.py): new generic api for guardrails Allows guardrail providers to work with litellm for guardrails without needing to make a PR to LiteLLM * docs(generic_guardrail_api.md): document new generic guardrail api * Fix: Improve PII detection and guardrail API integration Co-authored-by: krrishdholakia * feat: correctly extract raw request from guardrail api * docs(generic_guardrail_api.md): document this is a beta feature --------- Co-authored-by: Cursor Agent --- .../mock_bedrock_guardrail_server.py | 564 ++++++++++++++++++ .../adding_provider/generic_guardrail_api.md | 160 +++++ docs/my-website/sidebars.js | 1 + litellm/proxy/_new_secret_config.yaml | 2 +- .../generic_guardrail_api/__init__.py | 37 ++ .../generic_guardrail_api/example_config.yaml | 52 ++ .../generic_guardrail_api.py | 235 ++++++++ litellm/types/guardrails.py | 11 +- .../guardrail_hooks/generic_guardrail_api.py | 29 + 9 files changed, 1089 insertions(+), 2 deletions(-) create mode 100644 cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py create mode 100644 docs/my-website/docs/adding_provider/generic_guardrail_api.md create mode 100644 litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/__init__.py create mode 100644 litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/example_config.yaml create mode 100644 litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py create mode 100644 litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py diff --git a/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py new file mode 100644 index 00000000000..9cfbb11feb5 --- /dev/null +++ b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py @@ -0,0 +1,564 @@ +#!/usr/bin/env python3 +""" +Mock Bedrock Guardrail API Server + +This is a FastAPI server that mimics the AWS Bedrock Guardrail API for testing purposes. +It follows the same API spec as the real Bedrock guardrail endpoint. + +Usage: + python mock_bedrock_guardrail_server.py + +The server will start on http://localhost:8080 +""" + +import os +import re +from typing import Any, Dict, List, Literal, Optional + +from fastapi import Depends, FastAPI, Header, HTTPException, status +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field + +# ============================================================================ +# Request/Response Models (matching Bedrock API spec) +# ============================================================================ + + +class BedrockTextContent(BaseModel): + text: str + + +class BedrockContentItem(BaseModel): + text: BedrockTextContent + + +class BedrockRequest(BaseModel): + source: Literal["INPUT", "OUTPUT"] + content: List[BedrockContentItem] = Field(default_factory=list) + + +class BedrockGuardrailOutput(BaseModel): + text: Optional[str] = None + + +class TopicPolicyItem(BaseModel): + name: str + type: str + action: Literal["BLOCKED", "NONE"] + + +class TopicPolicy(BaseModel): + topics: List[TopicPolicyItem] = Field(default_factory=list) + + +class ContentFilterItem(BaseModel): + type: str + confidence: str + action: Literal["BLOCKED", "NONE"] + + +class ContentPolicy(BaseModel): + filters: List[ContentFilterItem] = Field(default_factory=list) + + +class CustomWord(BaseModel): + match: str + action: Literal["BLOCKED", "NONE"] + + +class WordPolicy(BaseModel): + customWords: List[CustomWord] = Field(default_factory=list) + managedWordLists: List[Dict[str, Any]] = Field(default_factory=list) + + +class PiiEntity(BaseModel): + type: str + match: str + action: Literal["BLOCKED", "ANONYMIZED", "NONE"] + + +class RegexMatch(BaseModel): + name: str + match: str + regex: str + action: Literal["BLOCKED", "ANONYMIZED", "NONE"] + + +class SensitiveInformationPolicy(BaseModel): + piiEntities: List[PiiEntity] = Field(default_factory=list) + regexes: List[RegexMatch] = Field(default_factory=list) + + +class ContextualGroundingFilter(BaseModel): + type: str + threshold: float + score: float + action: Literal["BLOCKED", "NONE"] + + +class ContextualGroundingPolicy(BaseModel): + filters: List[ContextualGroundingFilter] = Field(default_factory=list) + + +class Assessment(BaseModel): + topicPolicy: Optional[TopicPolicy] = None + contentPolicy: Optional[ContentPolicy] = None + wordPolicy: Optional[WordPolicy] = None + sensitiveInformationPolicy: Optional[SensitiveInformationPolicy] = None + contextualGroundingPolicy: Optional[ContextualGroundingPolicy] = None + + +class BedrockGuardrailResponse(BaseModel): + usage: Dict[str, int] = Field( + default_factory=lambda: {"topicPolicyUnits": 1, "contentPolicyUnits": 1} + ) + action: Literal["NONE", "GUARDRAIL_INTERVENED"] = "NONE" + outputs: List[BedrockGuardrailOutput] = Field(default_factory=list) + assessments: List[Assessment] = Field(default_factory=list) + + +# ============================================================================ +# Mock Guardrail Configuration +# ============================================================================ + + +class GuardrailConfig(BaseModel): + """Configuration for mock guardrail behavior""" + + blocked_words: List[str] = Field( + default_factory=lambda: ["offensive", "inappropriate", "badword"] + ) + blocked_topics: List[str] = Field(default_factory=lambda: ["violence", "illegal"]) + pii_patterns: Dict[str, str] = Field( + default_factory=lambda: { + "EMAIL": r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "PHONE": r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", + "SSN": r"\b\d{3}-\d{2}-\d{4}\b", + "CREDIT_CARD": r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", + } + ) + anonymize_pii: bool = True # If True, ANONYMIZE PII; if False, BLOCK it + bearer_token: str = "mock-bedrock-token-12345" + + +# Global config +GUARDRAIL_CONFIG = GuardrailConfig() + +# ============================================================================ +# FastAPI App Setup +# ============================================================================ + +app = FastAPI( + title="Mock Bedrock Guardrail API", + description="Mock server mimicking AWS Bedrock Guardrail API", + version="1.0.0", +) + + +# ============================================================================ +# Authentication +# ============================================================================ + + +async def verify_bearer_token(authorization: Optional[str] = Header(None)) -> str: + """ + Verify the Bearer token from the Authorization header. + + Args: + authorization: The Authorization header value + + Returns: + The token if valid + + Raises: + HTTPException: If token is missing or invalid + """ + if authorization is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Missing Authorization header", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Check if it's a Bearer token + parts = authorization.split() + print(f"parts: {parts}") + if len(parts) != 2 or parts[0].lower() != "bearer": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid Authorization header format. Expected: Bearer ", + headers={"WWW-Authenticate": "Bearer"}, + ) + + token = parts[1] + + # Verify token + if token != GUARDRAIL_CONFIG.bearer_token: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Invalid bearer token", + ) + + return token + + +# ============================================================================ +# Guardrail Logic +# ============================================================================ + + +def check_blocked_words(text: str) -> Optional[WordPolicy]: + """Check if text contains blocked words""" + found_words = [] + text_lower = text.lower() + + for word in GUARDRAIL_CONFIG.blocked_words: + if word.lower() in text_lower: + found_words.append(CustomWord(match=word, action="BLOCKED")) + + if found_words: + return WordPolicy(customWords=found_words) + return None + + +def check_blocked_topics(text: str) -> Optional[TopicPolicy]: + """Check if text contains blocked topics""" + found_topics = [] + text_lower = text.lower() + + for topic in GUARDRAIL_CONFIG.blocked_topics: + if topic.lower() in text_lower: + found_topics.append( + TopicPolicyItem(name=topic, type=topic.upper(), action="BLOCKED") + ) + + if found_topics: + return TopicPolicy(topics=found_topics) + return None + + +def check_pii(text: str) -> tuple[Optional[SensitiveInformationPolicy], str]: + """ + Check for PII in text and return policy + anonymized text + + Returns: + Tuple of (SensitiveInformationPolicy or None, anonymized_text) + """ + pii_entities = [] + anonymized_text = text + action = "ANONYMIZED" if GUARDRAIL_CONFIG.anonymize_pii else "BLOCKED" + + for pii_type, pattern in GUARDRAIL_CONFIG.pii_patterns.items(): + try: + # Compile the regex pattern with a timeout to prevent ReDoS attacks + compiled_pattern = re.compile(pattern) + matches = compiled_pattern.finditer(text) + for match in matches: + matched_text = match.group() + pii_entities.append( + PiiEntity(type=pii_type, match=matched_text, action=action) + ) + + # Anonymize the text if configured + if GUARDRAIL_CONFIG.anonymize_pii: + anonymized_text = anonymized_text.replace( + matched_text, f"[{pii_type}_REDACTED]" + ) + except re.error: + # Invalid regex pattern - skip it and log a warning + print(f"Warning: Invalid regex pattern for PII type {pii_type}: {pattern}") + continue + + if pii_entities: + return SensitiveInformationPolicy(piiEntities=pii_entities), anonymized_text + + return None, text + + +def process_guardrail_request( + request: BedrockRequest, +) -> tuple[BedrockGuardrailResponse, List[str]]: + """ + Process a guardrail request and return the response. + + Returns: + Tuple of (response, list of output texts) + """ + all_text_content = [] + output_texts = [] + + # Extract all text from content items + for content_item in request.content: + if content_item.text and content_item.text.text: + all_text_content.append(content_item.text.text) + + # Combine all text for analysis + combined_text = " ".join(all_text_content) + + # Initialize response + response = BedrockGuardrailResponse() + assessment = Assessment() + has_intervention = False + + # Check for blocked words + word_policy = check_blocked_words(combined_text) + if word_policy: + assessment.wordPolicy = word_policy + has_intervention = True + + # Check for blocked topics + topic_policy = check_blocked_topics(combined_text) + if topic_policy: + assessment.topicPolicy = topic_policy + has_intervention = True + + # Check for PII + for text in all_text_content: + pii_policy, anonymized_text = check_pii(text) + if pii_policy: + assessment.sensitiveInformationPolicy = pii_policy + if GUARDRAIL_CONFIG.anonymize_pii: + # If anonymizing, we don't block, we modify the text + output_texts.append(anonymized_text) + has_intervention = True + else: + # If not anonymizing PII, we block it + output_texts.append(text) + has_intervention = True + else: + output_texts.append(text) + + # Build response + if has_intervention: + response.action = "GUARDRAIL_INTERVENED" + # Only add assessment if there were interventions + response.assessments = [assessment] + + # Add outputs (modified or original text) + response.outputs = [BedrockGuardrailOutput(text=txt) for txt in output_texts] + + return response, output_texts + + +# ============================================================================ +# API Endpoints +# ============================================================================ + + +@app.get("/") +async def root(): + """Health check endpoint""" + return { + "service": "Mock Bedrock Guardrail API", + "status": "running", + "endpoint_format": "/guardrail/{guardrailIdentifier}/version/{guardrailVersion}/apply", + } + + +@app.get("/health") +async def health(): + """Health check endpoint""" + return {"status": "healthy"} + + +@app.post( + "/guardrail/{guardrailIdentifier}/version/{guardrailVersion}/apply", + response_model=BedrockGuardrailResponse, +) +async def apply_guardrail( + guardrailIdentifier: str, + guardrailVersion: str, + request: BedrockRequest, + token: str = Depends(verify_bearer_token), +) -> BedrockGuardrailResponse: + """ + Apply guardrail to input or output content. + + This endpoint mimics the AWS Bedrock ApplyGuardrail API. + + Args: + guardrailIdentifier: The guardrail ID + guardrailVersion: The guardrail version + request: The guardrail request containing content to analyze + token: Bearer token (verified by dependency) + + Returns: + BedrockGuardrailResponse with analysis results + """ + # Process the request + response, output_texts = process_guardrail_request(request) + + # Log the request (optional, for debugging) + print(f"Guardrail applied: {guardrailIdentifier} v{guardrailVersion}") + print(f"Source: {request.source}") + print(f"Action: {response.action}") + + return response + + +""" +LiteLLM exposes a basic guardrail API with the text extracted from the request and sent to the guardrail API, as well as the received request body for any further processing. + +This works across all LiteLLM endpoints (completion, anthropic /v1/messages, responses api, image generation, embedding, etc.) + +This makes it easy to support your own guardrail API without having to make a PR to LiteLLM. + +LiteLLM supports passing any provider specific params from LiteLLM config.yaml to the guardrail API. + +Example: + +```yaml +guardrails: + - guardrail_name: "bedrock-content-guard" + litellm_params: + guardrail: generic_guardrail_api + mode: "pre_call" + api_key: os.environ/GUARDRAIL_API_KEY + api_base: os.environ/GUARDRAIL_API_BASE + additional_provider_specific_params: + api_version: os.environ/GUARDRAIL_API_VERSION # additional provider specific params +``` + +This is a beta API. Please help us improve it. +""" + + +class LitellmBasicGuardrailRequest(BaseModel): + text: str + request_body: Dict[str, Any] = Field(default_factory=dict) + additional_provider_specific_params: Dict[str, Any] = Field(default_factory=dict) + + +class LitellmBasicGuardrailResponse(BaseModel): + action: Literal[ + "BLOCKED", "NONE", "GUARDRAIL_INTERVENED" + ] # BLOCKED = litellm will raise an error, NONE = litellm will continue, GUARDRAIL_INTERVENED = litellm will continue, but the text was modified by the guardrail + blocked_reason: Optional[str] = None # only if action is BLOCKED, otherwise None + text: Optional[str] = None + + +@app.post( + "/beta/litellm_basic_guardrail_api", + response_model=LitellmBasicGuardrailResponse, +) +async def beta_litellm_basic_guardrail_api( + request: LitellmBasicGuardrailRequest, +) -> LitellmBasicGuardrailResponse: + """ + Apply guardrail to input or output content. + + This endpoint mimics the AWS Bedrock ApplyGuardrail API. + + Args: + request: The guardrail request containing content to analyze + token: Bearer token (verified by dependency) + + Returns: + LitellmBasicGuardrailResponse with analysis results + """ + print(f"request: {request}") + if "ishaan" in request.text.lower(): + return LitellmBasicGuardrailResponse( + action="BLOCKED", blocked_reason="Ishaan is not allowed" + ) + elif "pii_value" in request.text: + return LitellmBasicGuardrailResponse( + action="GUARDRAIL_INTERVENED", + text=request.text.replace("pii_value", "pii_value_redacted"), + ) + return LitellmBasicGuardrailResponse(action="NONE") + + +@app.post("/config/update") +async def update_config( + config: GuardrailConfig, token: str = Depends(verify_bearer_token) +): + """ + Update the guardrail configuration. + + This is a testing endpoint to modify the mock guardrail behavior. + + Args: + config: New guardrail configuration + token: Bearer token (verified by dependency) + + Returns: + Updated configuration + """ + global GUARDRAIL_CONFIG + GUARDRAIL_CONFIG = config + return {"status": "updated", "config": GUARDRAIL_CONFIG} + + +@app.get("/config") +async def get_config(token: str = Depends(verify_bearer_token)): + """ + Get the current guardrail configuration. + + Args: + token: Bearer token (verified by dependency) + + Returns: + Current configuration + """ + return GUARDRAIL_CONFIG + + +# ============================================================================ +# Error Handlers +# ============================================================================ + + +@app.exception_handler(HTTPException) +async def http_exception_handler(request, exc: HTTPException): + """Custom error handler for HTTP exceptions""" + return JSONResponse( + status_code=exc.status_code, + content={"error": exc.detail}, + headers=exc.headers, + ) + + +# ============================================================================ +# Main +# ============================================================================ + +if __name__ == "__main__": + import uvicorn + + # Get configuration from environment + host = os.getenv("MOCK_BEDROCK_HOST", "0.0.0.0") + port = int(os.getenv("MOCK_BEDROCK_PORT", "8080")) + bearer_token = os.getenv("MOCK_BEDROCK_TOKEN", "mock-bedrock-token-12345") + + # Update config with environment token + GUARDRAIL_CONFIG.bearer_token = bearer_token + + print("=" * 80) + print("Mock Bedrock Guardrail API Server") + print("=" * 80) + print(f"Server starting on: http://{host}:{port}") + print(f"Bearer Token: {bearer_token}") + print(f"Endpoint: POST /guardrail/{{id}}/version/{{version}}/apply") + print("=" * 80) + print("\nExample curl command:") + print( + f""" +curl -X POST "http://{host}:{port}/guardrail/test-guardrail/version/1/apply" \\ + -H "Authorization: Bearer {bearer_token}" \\ + -H "Content-Type: application/json" \\ + -d '{{ + "source": "INPUT", + "content": [ + {{ + "text": {{ + "text": "Hello, my email is test@example.com" + }} + }} + ] + }}' + """ + ) + print("=" * 80) + + uvicorn.run(app, host=host, port=port) diff --git a/docs/my-website/docs/adding_provider/generic_guardrail_api.md b/docs/my-website/docs/adding_provider/generic_guardrail_api.md new file mode 100644 index 00000000000..70b39d3c397 --- /dev/null +++ b/docs/my-website/docs/adding_provider/generic_guardrail_api.md @@ -0,0 +1,160 @@ +# [BETA] Generic Guardrail API - Integrate Without a PR + +## The Problem + +As a guardrail provider, integrating with LiteLLM traditionally requires: +- Making a PR to the LiteLLM repository +- Waiting for review and merge +- Maintaining provider-specific code in LiteLLM's codebase +- Updating the integration for changes to your API + +## The Solution + +The **Generic Guardrail API** lets you integrate with LiteLLM **instantly** by implementing a simple API endpoint. No PR required. + +### Key Benefits + +1. **No PR Needed** - Deploy and integrate immediately +2. **Universal Support** - Works across ALL LiteLLM endpoints (chat, embeddings, image generation, etc.) +3. **Simple Contract** - One endpoint, three response types +4. **Custom Parameters** - Pass provider-specific params via config +5. **Full Control** - You own and maintain your guardrail API + +## How It Works + +1. LiteLLM extracts text from any request (chat messages, embeddings, image prompts, etc.) +2. Sends extracted text + original request to your API endpoint +3. Your API responds with: `BLOCKED`, `NONE`, or `GUARDRAIL_INTERVENED` +4. LiteLLM enforces the decision + +## API Contract + +### Endpoint + +Implement `POST /beta/litellm_basic_guardrail_api` + +### Request Format + +```json +{ + "text": "extracted text from the request", + "request_body": {}, // full original request for context + "additional_provider_specific_params": { + // your custom params from config + } +} +``` + +### Response Format + +```json +{ + "action": "BLOCKED" | "NONE" | "GUARDRAIL_INTERVENED", + "blocked_reason": "why content was blocked", // required if action=BLOCKED + "text": "modified text" // required if action=GUARDRAIL_INTERVENED +} +``` + +**Actions:** +- `BLOCKED` - LiteLLM raises error and blocks request +- `NONE` - Request proceeds unchanged +- `GUARDRAIL_INTERVENED` - Request proceeds with modified text + +## LiteLLM Configuration + +Add to `config.yaml`: + +```yaml +litellm_settings: + guardrails: + - guardrail_name: "my-guardrail" + litellm_params: + guardrail: generic_guardrail_api + mode: pre_call # or post_call, during_call + api_base: https://your-guardrail-api.com + api_key: os.environ/YOUR_GUARDRAIL_API_KEY # optional + additional_provider_specific_params: + # your custom parameters + threshold: 0.8 + language: "en" +``` + +## Usage + +Users apply your guardrail by name: + +```python +response = client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "hello"}], + guardrails=["my-guardrail"] +) +``` + +Or with dynamic parameters: + +```python +response = client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "hello"}], + guardrails=[{ + "my-guardrail": { + "extra_body": { + "custom_threshold": 0.9 + } + } + }] +) +``` + +## Implementation Example + +See [mock_bedrock_guardrail_server.py](https://github.com/BerriAI/litellm/blob/main/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py) for a complete reference implementation. + +**Minimal FastAPI example:** + +```python +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class GuardrailRequest(BaseModel): + text: str + request_body: dict + additional_provider_specific_params: dict + +class GuardrailResponse(BaseModel): + action: str # BLOCKED, NONE, or GUARDRAIL_INTERVENED + blocked_reason: str | None = None + text: str | None = None + +@app.post("/beta/litellm_basic_guardrail_api") +async def apply_guardrail(request: GuardrailRequest): + # Your guardrail logic here + if "badword" in request.text.lower(): + return GuardrailResponse( + action="BLOCKED", + blocked_reason="Content contains prohibited terms" + ) + + return GuardrailResponse(action="NONE") +``` + +## When to Use This + +✅ **Use Generic Guardrail API when:** +- You want instant integration without waiting for PRs +- You maintain your own guardrail service +- You need full control over updates and features +- You want to support all LiteLLM endpoints automatically + +❌ **Make a PR when:** +- You want deeper integration with LiteLLM internals +- Your guardrail requires complex LiteLLM-specific logic +- You want to be featured as a built-in provider + +## Questions? + +This is a **beta API**. We're actively improving it based on feedback. Open an issue or PR if you need additional capabilities. + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 802ffdd5bb1..2039d01186c 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -45,6 +45,7 @@ const sidebars = { type: "category", "label": "Contributing to Guardrails", items: [ + "adding_provider/generic_guardrail_api", "adding_provider/simple_guardrail_tutorial", "adding_provider/adding_guardrail_support", ] diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 68761524794..c11848a8623 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -16,4 +16,4 @@ callback_settings: callback_type: generic_api endpoint: https://webhook.site/efc57707-9018-478c-bdf1-2ffaabb2b315 headers: - Authorization: Bearer sk-1234 \ No newline at end of file + Authorization: Bearer sk-1234 diff --git a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/__init__.py new file mode 100644 index 00000000000..c762f0cbfc6 --- /dev/null +++ b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/__init__.py @@ -0,0 +1,37 @@ +from typing import TYPE_CHECKING + +from litellm.types.guardrails import SupportedGuardrailIntegrations + +from .generic_guardrail_api import GenericGuardrailAPI + +if TYPE_CHECKING: + from litellm.types.guardrails import Guardrail, LitellmParams + + +def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"): + import litellm + + _generic_guardrail_api_callback = GenericGuardrailAPI( + api_base=litellm_params.api_base, + headers=getattr(litellm_params, "headers", None), + additional_provider_specific_params=getattr( + litellm_params, "additional_provider_specific_params", {} + ), + guardrail_name=guardrail.get("guardrail_name", ""), + event_hook=litellm_params.mode, + default_on=litellm_params.default_on, + ) + + litellm.logging_callback_manager.add_litellm_callback( + _generic_guardrail_api_callback + ) + return _generic_guardrail_api_callback + + +guardrail_initializer_registry = { + SupportedGuardrailIntegrations.GENERIC_GUARDRAIL_API.value: initialize_guardrail, +} + +guardrail_class_registry = { + SupportedGuardrailIntegrations.GENERIC_GUARDRAIL_API.value: GenericGuardrailAPI, +} diff --git a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/example_config.yaml b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/example_config.yaml new file mode 100644 index 00000000000..7ad33b24608 --- /dev/null +++ b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/example_config.yaml @@ -0,0 +1,52 @@ +# Example configuration for Generic Guardrail API + +model_list: + - model_name: gpt-4 + litellm_params: + model: gpt-4 + api_key: os.environ/OPENAI_API_KEY + +litellm_settings: + guardrails: + - guardrail_name: "my-generic-guardrail" + litellm_params: + guardrail: generic_guardrail_api + mode: pre_call # Options: pre_call, post_call, during_call, [pre_call, post_call] + api_key: os.environ/GENERIC_GUARDRAIL_API_KEY # Optional if using Bearer auth + api_base: http://localhost:8080 # Required. Endpoint /beta/litellm_basic_guardrail_api is automatically appended + default_on: false # Set to true to apply to all requests by default + additional_provider_specific_params: + # Any additional parameters your guardrail API needs + api_version: "v1" + custom_param: "value" + +# Usage examples: + +# 1. Apply guardrail to a specific request: +# curl --location 'http://localhost:4000/chat/completions' \ +# --header 'Authorization: Bearer sk-1234' \ +# --header 'Content-Type: application/json' \ +# --data '{ +# "model": "gpt-4", +# "messages": [{"role": "user", "content": "Test message"}], +# "guardrails": ["my-generic-guardrail"] +# }' + +# 2. Apply guardrail with dynamic parameters: +# curl --location 'http://localhost:4000/chat/completions' \ +# --header 'Authorization: Bearer sk-1234' \ +# --header 'Content-Type: application/json' \ +# --data '{ +# "model": "gpt-4", +# "messages": [{"role": "user", "content": "Test message"}], +# "guardrails": [ +# { +# "my-generic-guardrail": { +# "extra_body": { +# "custom_threshold": 0.8 +# } +# } +# } +# ] +# }' + diff --git a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py new file mode 100644 index 00000000000..e94306e172e --- /dev/null +++ b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py @@ -0,0 +1,235 @@ +# +-------------------------------------------------------------+ +# +# Use Generic Guardrail API for your LLM calls +# +# +-------------------------------------------------------------+ +# Thank you users! We ❤️ you! - Krrish & Ishaan + +import os +from typing import Any, Dict, List, Optional + +from litellm._logging import verbose_proxy_logger +from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) +from litellm.types.guardrails import GuardrailEventHooks + +GUARDRAIL_NAME = "generic_guardrail_api" + + +class GenericGuardrailAPIRequest: + """Request model for the Generic Guardrail API""" + + def __init__( + self, + text: str, + request_body: Dict[str, Any], + additional_provider_specific_params: Optional[Dict[str, Any]] = None, + ): + self.text = text + self.request_body = request_body + self.additional_provider_specific_params = ( + additional_provider_specific_params or {} + ) + + def to_dict(self) -> dict: + return { + "text": self.text, + "request_body": self.request_body, + "additional_provider_specific_params": self.additional_provider_specific_params, + } + + +class GenericGuardrailAPIResponse: + """Response model for the Generic Guardrail API""" + + def __init__( + self, + action: str, + blocked_reason: Optional[str] = None, + text: Optional[str] = None, + ): + self.action = action + self.blocked_reason = blocked_reason + self.text = text + + @classmethod + def from_dict(cls, data: dict) -> "GenericGuardrailAPIResponse": + return cls( + action=data.get("action", "NONE"), + blocked_reason=data.get("blocked_reason"), + text=data.get("text"), + ) + + +class GenericGuardrailAPI(CustomGuardrail): + """ + Generic Guardrail API integration for LiteLLM. + + This integration allows you to use any guardrail API that follows the + LiteLLM Basic Guardrail API spec without needing to write custom integration code. + + The API should accept a POST request with: + { + "text": str, + "request_body": dict, + "additional_provider_specific_params": dict + } + + And return: + { + "action": "BLOCKED" | "NONE" | "GUARDRAIL_INTERVENED", + "blocked_reason": str (optional, only if action is BLOCKED), + "text": str (optional, modified text if action is GUARDRAIL_INTERVENED) + } + """ + + def __init__( + self, + headers: Optional[Dict[str, Any]] = None, + api_base: Optional[str] = None, + additional_provider_specific_params: Optional[Dict[str, Any]] = None, + **kwargs, + ): + self.async_handler = get_async_httpx_client( + llm_provider=httpxSpecialProvider.GuardrailCallback + ) + self.headers = headers or {} + base_url = api_base or os.environ.get("GENERIC_GUARDRAIL_API_BASE") + + if not base_url: + raise ValueError( + "api_base is required for Generic Guardrail API. " + "Set GENERIC_GUARDRAIL_API_BASE environment variable or pass it in litellm_params" + ) + + # Append the endpoint path if not already present + if not base_url.endswith("/beta/litellm_basic_guardrail_api"): + base_url = base_url.rstrip("/") + self.api_base = f"{base_url}/beta/litellm_basic_guardrail_api" + else: + self.api_base = base_url + + self.additional_provider_specific_params = ( + additional_provider_specific_params or {} + ) + + # Set supported event hooks + if "supported_event_hooks" not in kwargs: + kwargs["supported_event_hooks"] = [ + GuardrailEventHooks.pre_call, + GuardrailEventHooks.post_call, + GuardrailEventHooks.during_call, + ] + + super().__init__(**kwargs) + + verbose_proxy_logger.debug( + "Generic Guardrail API initialized with api_base: %s", self.api_base + ) + + async def apply_guardrail( + self, + text: str, + language: Optional[str] = None, + entities: Optional[List] = None, + request_data: Optional[dict] = None, + ) -> str: + """ + Apply the Generic Guardrail API to the given text. + + This is the main method that gets called by the framework. + + Args: + text: The text to check + language: Optional language parameter (not used by Generic API) + entities: Optional entities parameter (not used by Generic API) + request_data: Optional request data dictionary for logging metadata + + Returns: + The processed text (original or modified) + + Raises: + Exception: If the guardrail blocks the request + """ + verbose_proxy_logger.debug("Generic Guardrail API: Applying guardrail to text") + + # Use provided request_data or create an empty dict + if request_data is None: + request_data = {} + + request_body = request_data.get("body") or {} + + # Merge additional provider specific params from config and dynamic params + additional_params = {**self.additional_provider_specific_params} + + # Get dynamic params from request if available + dynamic_params = self.get_guardrail_dynamic_request_body_params(request_body) + if dynamic_params: + additional_params.update(dynamic_params) + + # Create request payload + guardrail_request = GenericGuardrailAPIRequest( + text=text, + request_body=request_body, + additional_provider_specific_params=additional_params, + ) + + # Prepare headers + headers = {"Content-Type": "application/json"} + if self.headers: + headers.update(self.headers) + + verbose_proxy_logger.debug( + "Generic Guardrail API request to %s: %s", + self.api_base, + {"text_length": len(text), "has_request_body": bool(request_data)}, + ) + + try: + # Make the API request + response = await self.async_handler.post( + url=self.api_base, + json=guardrail_request.to_dict(), + headers=headers, + ) + + response.raise_for_status() + response_json = response.json() + + verbose_proxy_logger.debug( + "Generic Guardrail API response: %s", response_json + ) + + guardrail_response = GenericGuardrailAPIResponse.from_dict(response_json) + + # Handle the response + if guardrail_response.action == "BLOCKED": + # Block the request + error_message = ( + guardrail_response.blocked_reason or "Content violates policy" + ) + verbose_proxy_logger.warning( + "Generic Guardrail API blocked request: %s", error_message + ) + raise Exception(f"Content blocked by guardrail: {error_message}") + + elif guardrail_response.action == "GUARDRAIL_INTERVENED": + # Content was modified by the guardrail + if guardrail_response.text: + verbose_proxy_logger.debug("Generic Guardrail API modified text") + return guardrail_response.text + + # Action is NONE or no modifications needed + return text + + except Exception as e: + # Check if it's already an exception we raised + if "Content blocked by guardrail" in str(e): + raise + verbose_proxy_logger.error( + "Generic Guardrail API: failed to make request: %s", str(e) + ) + raise Exception(f"Generic Guardrail API failed: {str(e)}") diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index 24a235def59..31e301ed4de 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -8,6 +8,9 @@ from typing_extensions import Required, TypedDict from litellm.types.proxy.guardrails.guardrail_hooks.enkryptai import ( EnkryptAIGuardrailConfigs, ) +from litellm.types.proxy.guardrails.guardrail_hooks.generic_guardrail_api import ( + GenericGuardrailAPIOptionalParams, +) from litellm.types.proxy.guardrails.guardrail_hooks.grayswan import ( GraySwanGuardrailConfigModel, ) @@ -18,7 +21,6 @@ from litellm.types.proxy.guardrails.guardrail_hooks.tool_permission import ( ToolPermissionGuardrailConfigModel, ) - """ Pydantic object defining how to set guardrails on litellm proxy @@ -59,6 +61,7 @@ class SupportedGuardrailIntegrations(Enum): IBM_GUARDRAILS = "ibm_guardrails" LITELLM_CONTENT_FILTER = "litellm_content_filter" PROMPT_SECURITY = "prompt_security" + GENERIC_GUARDRAIL_API = "generic_guardrail_api" class Role(Enum): @@ -590,6 +593,12 @@ class BaseLitellmParams(BaseModel): # works for new and patch update guardrails description="Whether to fail the request if Model Armor encounters an error", ) + # Generic Guardrail API params + additional_provider_specific_params: Optional[Dict[str, Any]] = Field( + default=None, + description="Additional provider-specific parameters for generic guardrail APIs", + ) + model_config = ConfigDict(extra="allow", protected_namespaces=()) diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py new file mode 100644 index 00000000000..a00fe76a0f0 --- /dev/null +++ b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py @@ -0,0 +1,29 @@ +from typing import Any, Dict, Literal, Optional + +from pydantic import BaseModel, Field + +from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel + + +class GenericGuardrailAPIOptionalParams(BaseModel): + """Optional parameters for the Generic Guardrail API""" + + additional_provider_specific_params: Optional[Dict[str, Any]] = Field( + default=None, + description="Additional provider-specific parameters to send with the guardrail request", + ) + + +class GenericGuardrailAPIConfigModel( + GuardrailConfigModel[GenericGuardrailAPIOptionalParams], +): + """Configuration parameters for the Generic Guardrail API guardrail""" + + optional_params: Optional[GenericGuardrailAPIOptionalParams] = Field( + default_factory=GenericGuardrailAPIOptionalParams, + description="Optional parameters for the Generic Guardrail API guardrail", + ) + + @staticmethod + def ui_friendly_name() -> str: + return "Generic Guardrail API" From 1eb06f803101d7e82761a3b3a36d6a61de22fc6f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Mon, 1 Dec 2025 15:40:28 -0800 Subject: [PATCH 232/349] =?UTF-8?q?Revert=20"fix:=20respect=20guardrail=20?= =?UTF-8?q?mock=5Fresponse=20during=20during=5Fcall=20to=20return=20blo?= =?UTF-8?q?=E2=80=A6"=20(#17332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 6de610767340cadd6df1c5508325128045c8fae5. --- litellm/proxy/common_request_processing.py | 23 ++--- .../proxy/test_common_request_processing.py | 99 +------------------ 2 files changed, 11 insertions(+), 111 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index ed4c451f8d3..d2b04410026 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -536,11 +536,7 @@ class ProxyBaseLLMRequestProcessing: responses = await llm_responses - # Guardrails (pre/during-call) can inject a mock response to short-circuit the LLM call. - # Prefer it when present so blocked/filtered output is returned instead of the model response. - response = self.data.get("mock_response") - if response is None: - response = responses[1] + response = responses[1] hidden_params = getattr(response, "_hidden_params", {}) or {} model_id = hidden_params.get("model_id", None) or "" @@ -808,7 +804,7 @@ class ProxyBaseLLMRequestProcessing: # This matches the original behavior before the refactor in commit 511d435f6f error_body = await e.response.aread() error_text = error_body.decode("utf-8") - + raise HTTPException( status_code=e.response.status_code, detail={"error": error_text}, @@ -1076,9 +1072,9 @@ class ProxyBaseLLMRequestProcessing: # Add cache-related fields to **params (handled by Usage.__init__) if cache_creation_input_tokens is not None: - usage_kwargs[ - "cache_creation_input_tokens" - ] = cache_creation_input_tokens + usage_kwargs["cache_creation_input_tokens"] = ( + cache_creation_input_tokens + ) if cache_read_input_tokens is not None: usage_kwargs["cache_read_input_tokens"] = cache_read_input_tokens @@ -1097,9 +1093,7 @@ class ProxyBaseLLMRequestProcessing: return obj return None - def maybe_get_model_id( - self, _logging_obj: Optional[LiteLLMLoggingObj] - ) -> Optional[str]: + def maybe_get_model_id(self, _logging_obj: Optional[LiteLLMLoggingObj]) -> Optional[str]: """ Get model_id from logging object or request metadata. @@ -1109,7 +1103,10 @@ class ProxyBaseLLMRequestProcessing: model_id = None if _logging_obj: # 1. Try getting from litellm_params (updated during call) - if hasattr(_logging_obj, "litellm_params") and _logging_obj.litellm_params: + if ( + hasattr(_logging_obj, "litellm_params") + and _logging_obj.litellm_params + ): # First check direct model_info path (set by router.py with selected deployment) model_info = _logging_obj.litellm_params.get("model_info") or {} model_id = model_info.get("id", None) diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 8f5f182f429..4768ec42ff6 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -1,13 +1,11 @@ import copy -from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock import pytest -from fastapi import Request, Response, status +from fastapi import Request, status from fastapi.responses import StreamingResponse import litellm -import litellm.proxy.common_request_processing as common_request_processing from litellm._uuid import uuid from litellm.integrations.opentelemetry import UserAPIKeyAuth from litellm.proxy.common_request_processing import ( @@ -77,101 +75,6 @@ class TestProxyBaseLLMRequestProcessing: pytest.fail("litellm_call_id is not a valid UUID") assert data_passed["litellm_call_id"] == returned_data["litellm_call_id"] - @pytest.mark.asyncio - async def test_base_process_llm_request_prefers_guardrail_mock_response( - self, monkeypatch - ): - processing_obj = ProxyBaseLLMRequestProcessing( - data={ - "messages": [], - "metadata": {}, - "litellm_metadata": {"model_info": {"id": "fallback-model"}}, - } - ) - - guardrail_response = litellm.ModelResponse( - model="bedrock-guardrail", - hidden_params={"model_id": "guardrail-model"}, - ) - llm_response = litellm.ModelResponse( - model="real-model", - hidden_params={"model_id": "real-model"}, - ) - - async def mock_common_processing(self, *args, **kwargs): - logging_obj = SimpleNamespace(litellm_call_id="test-call-id") - self.data["litellm_call_id"] = "test-call-id" - self.data["litellm_logging_obj"] = logging_obj - return self.data, logging_obj - - monkeypatch.setattr( - ProxyBaseLLMRequestProcessing, - "common_processing_pre_call_logic", - mock_common_processing, - ) - - async def mock_route_request(*args, **kwargs): - async def _inner(): - return llm_response - - return _inner() - - monkeypatch.setattr( - common_request_processing, - "route_request", - mock_route_request, - ) - - check_response_size_is_safe_mock = AsyncMock() - monkeypatch.setattr( - common_request_processing, - "check_response_size_is_safe", - check_response_size_is_safe_mock, - ) - - async def mock_during_call_hook(*args, **kwargs): - kwargs["data"]["mock_response"] = guardrail_response - - proxy_logging_obj = MagicMock(spec=ProxyLogging) - proxy_logging_obj.during_call_hook = AsyncMock( - side_effect=mock_during_call_hook - ) - proxy_logging_obj.update_request_status = AsyncMock(return_value=None) - proxy_logging_obj.post_call_success_hook = AsyncMock( - return_value=guardrail_response - ) - - user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) - user_api_key_dict.tpm_limit = None - user_api_key_dict.rpm_limit = None - user_api_key_dict.max_budget = None - user_api_key_dict.spend = 0 - user_api_key_dict.allowed_model_region = None - - fastapi_response = Response() - proxy_config = MagicMock(spec=ProxyConfig) - - result = await processing_obj.base_process_llm_request( - request=MagicMock(spec=Request), - fastapi_response=fastapi_response, - user_api_key_dict=user_api_key_dict, - route_type="acompletion", - proxy_logging_obj=proxy_logging_obj, - general_settings={}, - proxy_config=proxy_config, - select_data_generator=lambda **kwargs: None, - ) - - assert result is guardrail_response - assert ( - proxy_logging_obj.post_call_success_hook.await_args.kwargs["response"] - is guardrail_response - ) - assert ( - check_response_size_is_safe_mock.await_args.kwargs["response"] - is guardrail_response - ) - @pytest.mark.asyncio async def test_stream_timeout_header_processing(self): """ From be920d75d361519b61f43809b771bc7b107eaf85 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Tue, 2 Dec 2025 04:25:26 +0200 Subject: [PATCH 233/349] Add `claude-opus-4-5` alias (#17313) Similar to `claude-sonnet-4-5`. --- model_prices_and_context_window.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index af63d1e2592..6b9b8beed80 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -6717,6 +6717,33 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "claude-opus-4-5": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_creation_input_token_cost_above_1hr": 1e-05, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "claude-sonnet-4-20250514": { "deprecation_date": "2026-05-14", "cache_creation_input_token_cost": 3.75e-06, From 37ecb03d4f0b8bd9695126c8f0beb68ed978f7d1 Mon Sep 17 00:00:00 2001 From: Elias <55650958+eliasto@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:26:39 -0500 Subject: [PATCH 234/349] Add support of audio transcription for OVHcloud (#17305) --- docs/my-website/docs/audio_transcription.md | 3 +- docs/my-website/docs/providers/ovhcloud.md | 15 ++ .../get_supported_openai_params.py | 9 + .../audio_transcription/transformation.py | 156 ++++++++++++++++++ litellm/utils.py | 6 + provider_endpoints_support.json | 2 +- ...loud_audio_transcription_transformation.py | 59 +++++++ 7 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 litellm/llms/ovhcloud/audio_transcription/transformation.py create mode 100644 tests/test_litellm/llms/ovhcloud/test_ovhcloud_audio_transcription_transformation.py diff --git a/docs/my-website/docs/audio_transcription.md b/docs/my-website/docs/audio_transcription.md index fd55cc66e92..5853b5c1872 100644 --- a/docs/my-website/docs/audio_transcription.md +++ b/docs/my-website/docs/audio_transcription.md @@ -13,7 +13,7 @@ import TabItem from '@theme/TabItem'; | Fallbacks | ✅ | Works between supported models | | Loadbalancing | ✅ | Works between supported models | | Guardrails | ✅ | Applies to output transcribed text (non-streaming only) | -| Supported Providers | `openai`, `azure`, `vertex_ai`, `gemini`, `deepgram`, `groq`, `fireworks_ai` | | +| Supported Providers | `openai`, `azure`, `vertex_ai`, `gemini`, `deepgram`, `groq`, `fireworks_ai`, `ovhcloud` | | ## Quick Start @@ -126,6 +126,7 @@ transcript = client.audio.transcriptions.create( - [Fireworks AI](./providers/fireworks_ai.md#audio-transcription) - [Groq](./providers/groq.md#speech-to-text---whisper) - [Deepgram](./providers/deepgram.md) +- [OVHcloud AI Endpoints](./providers/ovhcloud.md) --- diff --git a/docs/my-website/docs/providers/ovhcloud.md b/docs/my-website/docs/providers/ovhcloud.md index 6c42208f2cc..94625b0f2ed 100644 --- a/docs/my-website/docs/providers/ovhcloud.md +++ b/docs/my-website/docs/providers/ovhcloud.md @@ -311,6 +311,21 @@ response = embedding( print(response.data) ``` +### Audio Transcription + +```python +from litellm import transcription + +audio_file = open("path/to/your/audio.wav", "rb") + +response = transcription( + model="ovhcloud/whisper-large-v3-turbo", + file=audio_file +) + +print(response.text) +``` + ## Usage with LiteLLM Proxy Server Here's how to call a OVHCloud AI Endpoints model with the LiteLLM Proxy Server diff --git a/litellm/litellm_core_utils/get_supported_openai_params.py b/litellm/litellm_core_utils/get_supported_openai_params.py index 06e650f938d..19b52d2dace 100644 --- a/litellm/litellm_core_utils/get_supported_openai_params.py +++ b/litellm/litellm_core_utils/get_supported_openai_params.py @@ -266,6 +266,15 @@ def get_supported_openai_params( # noqa: PLR0915 model=model ) ) + elif custom_llm_provider == "ovhcloud": + if request_type == "transcription": + from litellm.llms.ovhcloud.audio_transcription.transformation import ( + OVHCloudAudioTranscriptionConfig, + ) + + return OVHCloudAudioTranscriptionConfig().get_supported_openai_params( + model=model + ) elif custom_llm_provider == "elevenlabs": if request_type == "transcription": from litellm.llms.elevenlabs.audio_transcription.transformation import ( diff --git a/litellm/llms/ovhcloud/audio_transcription/transformation.py b/litellm/llms/ovhcloud/audio_transcription/transformation.py new file mode 100644 index 00000000000..7233d911b07 --- /dev/null +++ b/litellm/llms/ovhcloud/audio_transcription/transformation.py @@ -0,0 +1,156 @@ +""" +Support for OVHCloud AI Endpoints `/v1/audio/transcriptions` endpoint. + +Our unified API follows the OpenAI standard. +More information on our website: https://endpoints.ai.cloud.ovh.net +""" + +from typing import List, Optional, Union + +import httpx + +from litellm.litellm_core_utils.audio_utils.utils import process_audio_file +from litellm.llms.base_llm.audio_transcription.transformation import ( + AudioTranscriptionRequestData, + BaseAudioTranscriptionConfig, +) +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ( + AllMessageValues, + OpenAIAudioTranscriptionOptionalParams, +) +from litellm.types.utils import FileTypes, TranscriptionResponse + +from ..utils import OVHCloudException + + +class OVHCloudAudioTranscriptionConfig(BaseAudioTranscriptionConfig): + def get_supported_openai_params( + self, model: str + ) -> List[OpenAIAudioTranscriptionOptionalParams]: + # OVHCloud implements the OpenAI-compatible Whisper interface. + # We pass through the same optional params as the OpenAI Whisper API. + return ["language", "prompt", "response_format", "timestamp_granularities", "temperature"] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_params = self.get_supported_openai_params(model) + for k, v in non_default_params.items(): + if k in supported_params: + optional_params[k] = v + return optional_params + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + api_base = ( + "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" + if api_base is None + else api_base.rstrip("/") + ) + complete_url = f"{api_base}/audio/transcriptions" + return complete_url + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] + ) -> BaseLLMException: + return OVHCloudException( + message=error_message, + status_code=status_code, + headers=headers, + ) + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + if api_key is None: + api_key = get_secret_str("OVHCLOUD_API_KEY") + + default_headers = { + "Authorization": f"Bearer {api_key}", + "accept": "application/json", + } + + # Caller can override / extend headers if needed + default_headers.update(headers or {}) + return default_headers + + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> AudioTranscriptionRequestData: + """ + Transform the audio transcription request into OpenAI-compatible form-data. + + OVHCloud follows OpenAI's `/audio/transcriptions` format, so we: + - Build a multipart form-data body with `file`, `model`, and optional params + - Let the shared HTTP handler set the proper content-type boundary + """ + processed_audio = process_audio_file(audio_file) + + # Base form fields: model + OpenAI-compatible optional params + form_fields: dict = { + "model": model, + } + + # Include OpenAI-compatible optional params + for key in self.get_supported_openai_params(model): + value = optional_params.get(key) + if value is not None: + form_fields[key] = value + + files = { + "file": ( + processed_audio.filename, + processed_audio.file_content, + processed_audio.content_type, + ) + } + + return AudioTranscriptionRequestData(data=form_fields, files=files) + + def transform_audio_transcription_response( + self, + raw_response: httpx.Response, + ) -> TranscriptionResponse: + """ + Transform OVHCloud audio transcription response to OpenAI-compatible TranscriptionResponse. + """ + try: + response_json = raw_response.json() + except Exception: + raise OVHCloudException( + message=raw_response.text, + status_code=raw_response.status_code, + headers=raw_response.headers, + ) + + text = response_json.get("text") or response_json.get("transcript") or "" + response = TranscriptionResponse(text=text) + + response._hidden_params = response_json + return response + + diff --git a/litellm/utils.py b/litellm/utils.py index f74c3aa0693..37a71b43476 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7384,6 +7384,12 @@ class ProviderConfigManager: ) return IBMWatsonXAudioTranscriptionConfig() + elif litellm.LlmProviders.OVHCLOUD == provider: + from litellm.llms.ovhcloud.audio_transcription.transformation import ( + OVHCloudAudioTranscriptionConfig, + ) + + return OVHCloudAudioTranscriptionConfig() return None @staticmethod diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 5eab130cdd3..b5bde3e5ce4 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -1272,7 +1272,7 @@ "responses": true, "embeddings": false, "image_generations": false, - "audio_transcriptions": false, + "audio_transcriptions": true, "audio_speech": false, "moderations": false, "batches": false, diff --git a/tests/test_litellm/llms/ovhcloud/test_ovhcloud_audio_transcription_transformation.py b/tests/test_litellm/llms/ovhcloud/test_ovhcloud_audio_transcription_transformation.py new file mode 100644 index 00000000000..fc5e310e71b --- /dev/null +++ b/tests/test_litellm/llms/ovhcloud/test_ovhcloud_audio_transcription_transformation.py @@ -0,0 +1,59 @@ +import os +from typing import Dict + +import litellm +import pytest + +from litellm.llms.base_llm.audio_transcription.transformation import ( + BaseAudioTranscriptionConfig, +) +from litellm.utils import ProviderConfigManager +from tests.llm_translation.base_audio_transcription_unit_tests import ( + BaseLLMAudioTranscriptionTest, +) + + +@pytest.mark.skipif( + not os.getenv("OVHCLOUD_API_KEY"), + reason="OVHCLOUD_API_KEY not set, skipping OVHCloud audio transcription tests", +) +class TestOVHCloudAudioTranscription(BaseLLMAudioTranscriptionTest): + def get_base_audio_transcription_call_args(self) -> Dict: + return { + "model": "ovhcloud/whisper-large-v3-turbo", + } + + def get_custom_llm_provider(self) -> litellm.LlmProviders: + return litellm.LlmProviders.OVHCLOUD + + # Override the async base test with a sync no-op to avoid + # 'async def functions are not natively supported' failures when + # running this file in isolation without pytest-asyncio. + def test_audio_transcription_async(self): # type: ignore[override] + pytest.skip( + "Async audio transcription test for OVHCloud is skipped in this suite; " + "async test plugins (e.g. pytest-asyncio/anyio) are not configured here." + ) + + +@pytest.mark.skipif( + not os.getenv("OVHCLOUD_API_KEY"), + reason="OVHCLOUD_API_KEY not set, skipping OVHCloud audio transcription config test", +) +def test_ovhcloud_audio_transcription_config_installed(): + """ + Ensure OVHCloud audio transcription config is registered with ProviderConfigManager. + """ + model = "ovhcloud/whisper-large-v3-turbo" + provider = litellm.LlmProviders.OVHCLOUD + + config = ProviderConfigManager.get_provider_audio_transcription_config( + model=model, + provider=provider, + ) + + assert config is not None + assert isinstance(config, BaseAudioTranscriptionConfig) + + + From 860cdc81d3a540c64d17cc6112ac577f1f9dd926 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Dec 2025 18:26:56 -0800 Subject: [PATCH 235/349] [Fix] Fix Watsonx Audio Transcription API (#17326) * """ add * fix transform_audio_transcription_request * fix tests * test_watsonx_transcription_request_body --- .../audio_transcription/transformation.py | 78 ++++++++++++++++--- litellm/types/llms/watsonx.py | 36 ++++++++- ...sonx_audio_transcription_transformation.py | 35 ++++++++- 3 files changed, 131 insertions(+), 18 deletions(-) diff --git a/litellm/llms/watsonx/audio_transcription/transformation.py b/litellm/llms/watsonx/audio_transcription/transformation.py index 8c8324cb72d..8fe8b4a4248 100644 --- a/litellm/llms/watsonx/audio_transcription/transformation.py +++ b/litellm/llms/watsonx/audio_transcription/transformation.py @@ -4,11 +4,17 @@ Translates from OpenAI's `/v1/audio/transcriptions` to IBM WatsonX's `/ml/v1/aud WatsonX follows the OpenAI spec for audio transcription. """ -from typing import List, Optional +from typing import Any, Dict, List, Optional import litellm +from litellm.litellm_core_utils.audio_utils.utils import process_audio_file from litellm.types.llms.openai import OpenAIAudioTranscriptionOptionalParams +from litellm.types.llms.watsonx import WatsonXAudioTranscriptionRequestBody +from litellm.types.utils import FileTypes +from ...base_llm.audio_transcription.transformation import ( + AudioTranscriptionRequestData, +) from ...openai.transcriptions.whisper_transformation import ( OpenAIWhisperAudioTranscriptionConfig, ) @@ -40,6 +46,60 @@ class IBMWatsonXAudioTranscriptionConfig( "timestamp_granularities", ] + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> AudioTranscriptionRequestData: + """ + Transform the audio transcription request for WatsonX. + + WatsonX expects multipart/form-data with: + - file: the audio file + - model: the model name (without watsonx/ prefix) + - project_id: the project ID (as form field, not query param) + - other optional params + """ + # Use common utility to process the audio file + processed_audio = process_audio_file(audio_file) + + # Get API params to extract project_id + api_params = _get_api_params(params=optional_params.copy()) + + # Initialize form data with required fields + form_data: WatsonXAudioTranscriptionRequestBody = { + "model": model, + "project_id": api_params.get("project_id", ""), + } + + # Add supported OpenAI params to form data + supported_params = self.get_supported_openai_params(model) + for key, value in optional_params.items(): + if key in supported_params and value is not None: + form_data[key] = value # type: ignore + + # Set default response_format for cost calculation + if "response_format" not in form_data or ( + form_data.get("response_format") in ["text", "json"] + ): + form_data["response_format"] = "verbose_json" + + # Prepare files dict with the audio file + files = { + "file": ( + processed_audio.filename, + processed_audio.file_content, + processed_audio.content_type, + ) + } + + # Convert TypedDict to regular dict for AudioTranscriptionRequestData + form_data_dict: Dict[str, Any] = dict(form_data) + + return AudioTranscriptionRequestData(data=form_data_dict, files=files) + def get_complete_url( self, api_base: Optional[str], @@ -52,7 +112,9 @@ class IBMWatsonXAudioTranscriptionConfig( """ Construct the complete URL for WatsonX audio transcription. - URL format: {api_base}/ml/v1/audio/transcriptions?version={version}&project_id={project_id} + URL format: {api_base}/ml/v1/audio/transcriptions?version={version} + + Note: project_id is sent as form data, not as a query parameter """ # Get base URL url = self._get_base_url(api_base=api_base) @@ -61,18 +123,10 @@ class IBMWatsonXAudioTranscriptionConfig( # Add the audio transcription endpoint url = f"{url}/ml/v1/audio/transcriptions" - # Get API params for project_id - api_params = _get_api_params(params=optional_params.copy()) - - # Add version parameter - api_version = optional_params.pop( + # Add version parameter (only version in query string, not project_id) + api_version = optional_params.get( "api_version", None ) or litellm.WATSONX_DEFAULT_API_VERSION url = f"{url}?version={api_version}" - # Add project_id parameter - project_id = api_params.get("project_id") - if project_id: - url = f"{url}&project_id={project_id}" - return url diff --git a/litellm/types/llms/watsonx.py b/litellm/types/llms/watsonx.py index 4eb2f2531a0..6c42c3ecea0 100644 --- a/litellm/types/llms/watsonx.py +++ b/litellm/types/llms/watsonx.py @@ -1,9 +1,7 @@ -import json from enum import Enum -from typing import Any, List, Optional, Union +from typing import List, Optional -from pydantic import BaseModel -from typing_extensions import TypedDict +from typing_extensions import NotRequired, TypedDict class WatsonXAPIParams(TypedDict): @@ -18,6 +16,36 @@ class WatsonXCredentials(TypedDict): token: Optional[str] +class WatsonXAudioTranscriptionRequestBody(TypedDict): + """ + WatsonX Audio Transcription API request body. + + Follows multipart/form-data format for WatsonX Whisper models. + See: https://cloud.ibm.com/apidocs/watsonx-ai + """ + + model: str + """Model name (e.g., 'whisper-large-v3-turbo')""" + + project_id: str + """WatsonX project ID (required)""" + + language: NotRequired[str] + """Language code (e.g., 'en', 'es')""" + + prompt: NotRequired[str] + """Optional prompt to guide transcription""" + + response_format: NotRequired[str] + """Response format: 'json', 'text', 'srt', 'verbose_json', 'vtt'""" + + temperature: NotRequired[float] + """Sampling temperature (0-1)""" + + timestamp_granularities: NotRequired[List[str]] + """Timestamp granularities: ['word', 'segment']""" + + class WatsonXAIEndpoint(str, Enum): TEXT_GENERATION = "/ml/v1/text/generation" TEXT_GENERATION_STREAM = "/ml/v1/text/generation_stream" diff --git a/tests/test_litellm/llms/watsonx/audio_transcription/test_watsonx_audio_transcription_transformation.py b/tests/test_litellm/llms/watsonx/audio_transcription/test_watsonx_audio_transcription_transformation.py index 84a9d25d98e..1286c2d4fe6 100644 --- a/tests/test_litellm/llms/watsonx/audio_transcription/test_watsonx_audio_transcription_transformation.py +++ b/tests/test_litellm/llms/watsonx/audio_transcription/test_watsonx_audio_transcription_transformation.py @@ -4,6 +4,7 @@ Tests for IBM WatsonX Audio Transcription. Validates that litellm.transcription transforms requests correctly for WatsonX. """ +import json import os import sys from unittest.mock import AsyncMock, MagicMock, patch @@ -29,6 +30,7 @@ class TestWatsonXAudioTranscription: captured_request["url"] = str(kwargs.get("url", args[0] if args else None)) captured_request["headers"] = kwargs.get("headers", {}) captured_request["data"] = kwargs.get("data", {}) + captured_request["files"] = kwargs.get("files", {}) mock_response = MagicMock() mock_response.json.return_value = { @@ -54,16 +56,30 @@ class TestWatsonXAudioTranscription: # Validate URL contains WatsonX audio transcription endpoint assert "/ml/v1/audio/transcriptions" in captured_request["url"] assert "version=" in captured_request["url"] - assert "project_id=test-project-123" in captured_request["url"] + # project_id should NOT be in URL (it should be in form data instead) + assert "project_id=test-project-123" not in captured_request["url"] # Validate headers contain WatsonX auth assert "Authorization" in captured_request["headers"] assert "Bearer test-bearer-token" in captured_request["headers"]["Authorization"] + + # Validate project_id is in form data, not URL + assert captured_request["data"].get("project_id") == "test-project-123" + + # Validate file is in files dict + assert "file" in captured_request["files"] @pytest.mark.asyncio async def test_watsonx_transcription_request_body(self): """ Test that litellm.transcription sends correct request body for WatsonX. + + Validates that: + - Request uses multipart/form-data (data + files) + - Model name has watsonx/ prefix removed + - project_id is in form data, not URL + - Audio file is in files dict + - OpenAI params are included in form data """ captured_request = {} @@ -94,9 +110,24 @@ class TestWatsonXAudioTranscription: except Exception: pass # We just want to capture the request - # Validate request body contains expected fields + # Validate form data contains expected fields data = captured_request.get("data", {}) + + print("JSON DUMPS captured_request:") + print(json.dumps(captured_request, indent=4, default=str)) + + # Model name should NOT have watsonx/ prefix assert data.get("model") == "whisper-large-v3-turbo" + + # project_id should be in form data + assert data.get("project_id") == "test-project-123" + + # OpenAI params should be in form data assert data.get("language") == "en" assert data.get("temperature") == 0.5 assert data.get("response_format") == "verbose_json" # Default for cost calculation + + # Validate file is in files dict (multipart/form-data) + files = captured_request.get("files", {}) + assert "file" in files + assert isinstance(files["file"], tuple) # Should be (filename, content, content_type) From 1cdfb3da8fb81c293ed94a8628ce9dafbc703542 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Dec 2025 19:14:12 -0800 Subject: [PATCH 236/349] [Bug Fix] - Fix `litellm_enterprise` ensure imported routes exist (#17337) * test_enterprise_routes.py * test_enterprise_routes_all_imports_exist --- .../proxy/enterprise_routes.py | 4 - .../test_litellm/enterprise/proxy/__init__.py | 0 .../proxy/test_enterprise_routes.py | 78 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 tests/test_litellm/enterprise/proxy/__init__.py create mode 100644 tests/test_litellm/enterprise/proxy/test_enterprise_routes.py diff --git a/enterprise/litellm_enterprise/proxy/enterprise_routes.py b/enterprise/litellm_enterprise/proxy/enterprise_routes.py index f3227892bbd..e28d8b8a4c6 100644 --- a/enterprise/litellm_enterprise/proxy/enterprise_routes.py +++ b/enterprise/litellm_enterprise/proxy/enterprise_routes.py @@ -5,14 +5,10 @@ from litellm_enterprise.enterprise_callbacks.send_emails.endpoints import ( ) from .audit_logging_endpoints import router as audit_logging_router -from .guardrails.endpoints import router as guardrails_router from .management_endpoints import management_endpoints_router from .utils import _should_block_robots -from .vector_stores.endpoints import router as vector_stores_router router = APIRouter() -router.include_router(vector_stores_router) -router.include_router(guardrails_router) router.include_router(email_events_router) router.include_router(audit_logging_router) router.include_router(management_endpoints_router) diff --git a/tests/test_litellm/enterprise/proxy/__init__.py b/tests/test_litellm/enterprise/proxy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/enterprise/proxy/test_enterprise_routes.py b/tests/test_litellm/enterprise/proxy/test_enterprise_routes.py new file mode 100644 index 00000000000..a9bf33a21ac --- /dev/null +++ b/tests/test_litellm/enterprise/proxy/test_enterprise_routes.py @@ -0,0 +1,78 @@ +""" +Test enterprise_routes imports work correctly + +This validates that all imports can be resolved to prevent broken imports +from breaking the enterprise proxy initialization. +""" + +import ast +import os + +import pytest + + +def test_enterprise_routes_all_imports_exist(): + """ + Validate that all relative imports in enterprise_routes.py exist in the filesystem. + + This catches any import errors from moved/deleted modules without hardcoding + specific module names. Works by checking that imported files actually exist. + """ + # Path to the enterprise_routes.py source file + enterprise_routes_path = os.path.join( + os.path.dirname(__file__), + "..", "..", "..", "..", + "enterprise", "litellm_enterprise", "proxy", "enterprise_routes.py" + ) + + enterprise_routes_path = os.path.normpath(enterprise_routes_path) + enterprise_proxy_dir = os.path.dirname(enterprise_routes_path) + + if not os.path.exists(enterprise_routes_path): + pytest.skip(f"Enterprise routes file not found at {enterprise_routes_path}") + + # Read and parse the source file + with open(enterprise_routes_path, "r") as f: + source_code = f.read() + + try: + tree = ast.parse(source_code) + except SyntaxError as e: + pytest.fail(f"Syntax error in enterprise_routes.py: {e}") + + # Check all relative imports + missing_imports = [] + + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom): + # level > 0 means it's a relative import (. or .. etc) + if node.level and node.level > 0: + module = node.module or "" + + # Convert relative import to file path + # e.g., "audit_logging_endpoints" -> "audit_logging_endpoints.py" + # e.g., "vector_stores.endpoints" -> "vector_stores/endpoints.py" + module_path = module.replace(".", os.sep) if module else "" + + # Check both .py file and package directory + file_path = os.path.join(enterprise_proxy_dir, module_path + ".py") if module_path else None + package_path = os.path.join(enterprise_proxy_dir, module_path, "__init__.py") if module_path else None + + # If module is empty (e.g., "from . import something"), skip check + if not module: + continue + + file_exists = file_path and os.path.exists(file_path) + package_exists = package_path and os.path.exists(package_path) + + if not file_exists and not package_exists: + missing_imports.append( + f"Line {node.lineno}: Cannot find '.{module}' " + f"(checked: {file_path} and {package_path})" + ) + + if missing_imports: + error_msg = "Found imports in enterprise_routes.py that don't exist:\n" + error_msg += "\n".join(missing_imports) + error_msg += "\n\nThis usually means a module was moved or deleted but the import wasn't updated." + pytest.fail(error_msg) From 70126d91302233bdb4e4b6aecf5d81b462a94527 Mon Sep 17 00:00:00 2001 From: rioiart Date: Tue, 2 Dec 2025 04:51:42 +0100 Subject: [PATCH 237/349] Fix/new org team validate against org (#17333) * fix: skip user budget/model validation for org-scoped teams When creating a team with organization_id, budget and model constraints should be validated against the organization's limits, not the user's personal limits. This allows org admins with restrictive personal budgets to create teams within their organization's more generous limits. Adds 4 unit tests to verify: - Org-scoped teams bypass user budget validation - Org-scoped teams bypass user model validation - Standalone teams still validate against user limits * fix: enforce user budget/model limits for standalone teams in update_team - Add user-level budget and model validation to update_team endpoint for standalone teams, matching the existing pattern in new_team - Org-scoped teams correctly bypass user validation and use organization limits instead - Add 5 new comprehensive tests covering standalone/org team budget/model validation * fix: Add direct TPM/RPM org limit validation and consolidate user team limit checks - Add direct TPM/RPM comparison against org limits in _check_org_team_limits() - Consolidate budget/models/TPM/RPM user validation into _check_user_team_limits() helper - Ensure user limits only apply to standalone teams (organization_id=None) - Org-scoped teams now validate TPM/RPM against org limits (not user limits) - Add 8 tests for TPM/RPM validation scenarios (org and user limits) - Reduce code duplication between new_team() and update_team() --- .../management_endpoints/team_endpoints.py | 496 +++-- .../test_team_endpoints.py | 1602 +++++++++++++++++ 2 files changed, 1901 insertions(+), 197 deletions(-) diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 6d4faae5fd8..b697e01a6ef 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -461,11 +461,65 @@ async def _check_org_team_limits( prisma_client: PrismaClient, ) -> None: """ - Check if the organization team is allocating guaranteed throughput limits. If so, raise an error if we're overallocating. - - Only runs check if tpm_limit_type or rpm_limit_type is "guaranteed_throughput" + Check organization team limits including: + - Team budget vs organization's max_budget + - Team models vs organization's allowed models + - Guaranteed throughput limits (tpm/rpm) if applicable """ + # Validate team budget against organization's max_budget + if ( + data.max_budget is not None + and org_table.litellm_budget_table is not None + and org_table.litellm_budget_table.max_budget is not None + and data.max_budget > org_table.litellm_budget_table.max_budget + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"Team max_budget ({data.max_budget}) exceeds organization's max_budget ({org_table.litellm_budget_table.max_budget}). Organization: {org_table.organization_id}" + }, + ) + + # Validate team models against organization's allowed models + if data.models is not None and len(org_table.models) > 0: + for m in data.models: + if m not in org_table.models: + raise HTTPException( + status_code=400, + detail={ + "error": f"Model '{m}' not in organization's allowed models. Organization allowed models={org_table.models}. Organization: {org_table.organization_id}" + }, + ) + + # Validate team TPM/RPM against organization's TPM/RPM limits (direct comparison) + if ( + data.tpm_limit is not None + and org_table.litellm_budget_table is not None + and org_table.litellm_budget_table.tpm_limit is not None + and data.tpm_limit > org_table.litellm_budget_table.tpm_limit + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"Team tpm_limit ({data.tpm_limit}) exceeds organization's tpm_limit ({org_table.litellm_budget_table.tpm_limit}). Organization: {org_table.organization_id}" + }, + ) + + if ( + data.rpm_limit is not None + and org_table.litellm_budget_table is not None + and org_table.litellm_budget_table.rpm_limit is not None + and data.rpm_limit > org_table.litellm_budget_table.rpm_limit + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"Team rpm_limit ({data.rpm_limit}) exceeds organization's rpm_limit ({org_table.litellm_budget_table.rpm_limit}). Organization: {org_table.organization_id}" + }, + ) + + # Check guaranteed throughput limits (only if applicable) rpm_limit_type = getattr(data, "rpm_limit_type", None) or ( data.metadata.get("rpm_limit_type", None) if data.metadata else None ) @@ -503,6 +557,80 @@ async def _check_org_team_limits( ) +async def _check_user_team_limits( + data: Union[NewTeamRequest, UpdateTeamRequest], + user_api_key_dict: UserAPIKeyAuth, + prisma_client: PrismaClient, + user_api_key_cache: Any, +) -> None: + """ + Check user team limits for standalone teams (not org-scoped). + + This validates: + - Team budget vs user's max_budget + - Team models vs user's allowed models + + Should only be called for standalone teams (when organization_id is None). + For org-scoped teams, use _check_org_team_limits() instead. + """ + # Validate team budget against user's max_budget + if data.max_budget is not None and user_api_key_dict.user_id is not None: + user_obj = await get_user_object( + user_id=user_api_key_dict.user_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + user_id_upsert=False, + ) + + if ( + user_obj is not None + and user_obj.max_budget is not None + and data.max_budget > user_obj.max_budget + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"max budget higher than user max. User max budget={user_obj.max_budget}. User role={user_api_key_dict.user_role}" + }, + ) + + # Validate team models against user's allowed models + if data.models is not None and len(user_api_key_dict.models) > 0: + for m in data.models: + if m not in user_api_key_dict.models: + raise HTTPException( + status_code=400, + detail={ + "error": f"Model not in allowed user models. User allowed models={user_api_key_dict.models}. User id={user_api_key_dict.user_id}" + }, + ) + + # Validate team TPM/RPM against user's TPM/RPM limits + if ( + data.tpm_limit is not None + and user_api_key_dict.tpm_limit is not None + and data.tpm_limit > user_api_key_dict.tpm_limit + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"tpm limit higher than user max. User tpm limit={user_api_key_dict.tpm_limit}. User role={user_api_key_dict.user_role}" + }, + ) + + if ( + data.rpm_limit is not None + and user_api_key_dict.rpm_limit is not None + and data.rpm_limit > user_api_key_dict.rpm_limit + ): + raise HTTPException( + status_code=400, + detail={ + "error": f"rpm limit higher than user max. User rpm limit={user_api_key_dict.rpm_limit}. User role={user_api_key_dict.user_role}" + }, + ) + + #### TEAM MANAGEMENT #### @router.post( "/team/new", @@ -665,61 +793,16 @@ async def new_team( # noqa: PLR0915 user_api_key_dict.user_role is None or user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN ): # don't restrict proxy admin - if ( - data.tpm_limit is not None - and user_api_key_dict.tpm_limit is not None - and data.tpm_limit > user_api_key_dict.tpm_limit - ): - raise HTTPException( - status_code=400, - detail={ - "error": f"tpm limit higher than user max. User tpm limit={user_api_key_dict.tpm_limit}. User role={user_api_key_dict.user_role}" - }, - ) - - if ( - data.rpm_limit is not None - and user_api_key_dict.rpm_limit is not None - and data.rpm_limit > user_api_key_dict.rpm_limit - ): - raise HTTPException( - status_code=400, - detail={ - "error": f"rpm limit higher than user max. User rpm limit={user_api_key_dict.rpm_limit}. User role={user_api_key_dict.user_role}" - }, - ) - - if data.max_budget is not None and user_api_key_dict.user_id is not None: - # Fetch user object to get max_budget - user_obj = await get_user_object( - user_id=user_api_key_dict.user_id, + # Only validate user budget/models/tpm/rpm for standalone teams (not org-scoped) + # For org-scoped teams, validation is done by _check_org_team_limits() + if data.organization_id is None: + await _check_user_team_limits( + data=data, + user_api_key_dict=user_api_key_dict, prisma_client=prisma_client, user_api_key_cache=user_api_key_cache, - user_id_upsert=False, ) - if ( - user_obj is not None - and user_obj.max_budget is not None - and data.max_budget > user_obj.max_budget - ): - raise HTTPException( - status_code=400, - detail={ - "error": f"max budget higher than user max. User max budget={user_obj.max_budget}. User role={user_api_key_dict.user_role}" - }, - ) - - if data.models is not None and len(user_api_key_dict.models) > 0: - for m in data.models: - if m not in user_api_key_dict.models: - raise HTTPException( - status_code=400, - detail={ - "error": f"Model not in allowed user models. User allowed models={user_api_key_dict.models}. User id={user_api_key_dict.user_id}" - }, - ) - if user_api_key_dict.user_id is not None: creating_user_in_list = False for member in data.members_with_roles: @@ -1151,168 +1234,187 @@ async def update_team( }' ``` """ - from litellm.proxy.auth.auth_checks import _cache_team_object - from litellm.proxy.proxy_server import ( - litellm_proxy_admin_name, - llm_router, - prisma_client, - proxy_logging_obj, - user_api_key_cache, - ) - - if prisma_client is None: - raise HTTPException( - status_code=500, - detail={"error": CommonProxyErrors.db_not_connected_error.value}, + try: + from litellm.proxy.auth.auth_checks import _cache_team_object + from litellm.proxy.proxy_server import ( + litellm_proxy_admin_name, + llm_router, + prisma_client, + proxy_logging_obj, + user_api_key_cache, ) - if data.team_id is None: - raise HTTPException(status_code=400, detail={"error": "No team id passed in"}) - verbose_proxy_logger.debug("/team/update - %s", data) - - existing_team_row = await prisma_client.db.litellm_teamtable.find_unique( - where={"team_id": data.team_id} - ) - - if existing_team_row is None: - raise HTTPException( - status_code=404, - detail={"error": f"Team not found, passed team_id={data.team_id}"}, - ) - - if ( - data.organization_id is not None and len(data.organization_id) > 0 - ): # allow unsetting the organization_id - await fetch_and_validate_organization( - organization_id=data.organization_id, - existing_team_row=existing_team_row, - llm_router=llm_router, - prisma_client=prisma_client, - ) - elif data.organization_id is not None and len(data.organization_id) == 0: - # unsetting the organization_id - data.organization_id = None - - # check org team limits - if updating team that belongs to an org - org_id_to_check = ( - data.organization_id - if data.organization_id is not None - else existing_team_row.organization_id - ) - if ( - org_id_to_check is not None - and isinstance(org_id_to_check, str) - and prisma_client is not None - ): - org_table = await get_org_object( - org_id=org_id_to_check, - user_api_key_cache=user_api_key_cache, - prisma_client=prisma_client, - ) - if org_table is not None: - await _check_org_team_limits( - org_table=org_table, - data=data, - prisma_client=prisma_client, + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, ) - updated_kv = data.json(exclude_unset=True) + if data.team_id is None: + raise HTTPException(status_code=400, detail={"error": "No team id passed in"}) + verbose_proxy_logger.debug("/team/update - %s", data) - # Check budget_duration and budget_reset_at - if data.budget_duration is not None: - from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time + existing_team_row = await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": data.team_id} + ) - reset_at = get_budget_reset_time(budget_duration=data.budget_duration) + if existing_team_row is None: + raise HTTPException( + status_code=404, + detail={"error": f"Team not found, passed team_id={data.team_id}"}, + ) - # set the budget_reset_at in DB - updated_kv["budget_reset_at"] = reset_at + if ( + data.organization_id is not None and len(data.organization_id) > 0 + ): # allow unsetting the organization_id + await fetch_and_validate_organization( + organization_id=data.organization_id, + existing_team_row=existing_team_row, + llm_router=llm_router, + prisma_client=prisma_client, + ) + elif data.organization_id is not None and len(data.organization_id) == 0: + # unsetting the organization_id + data.organization_id = None - if TeamMemberBudgetHandler.should_create_budget( - team_member_budget=data.team_member_budget, - team_member_rpm_limit=data.team_member_rpm_limit, - team_member_tpm_limit=data.team_member_tpm_limit, - ): - updated_kv = await TeamMemberBudgetHandler.upsert_team_member_budget_table( - team_table=existing_team_row, - user_api_key_dict=user_api_key_dict, - updated_kv=updated_kv, + # check org team limits - if updating team that belongs to an org + org_id_to_check = ( + data.organization_id + if data.organization_id is not None + else existing_team_row.organization_id + ) + if ( + org_id_to_check is not None + and isinstance(org_id_to_check, str) + and prisma_client is not None + ): + org_table = await get_org_object( + org_id=org_id_to_check, + user_api_key_cache=user_api_key_cache, + prisma_client=prisma_client, + ) + if org_table is not None: + await _check_org_team_limits( + org_table=org_table, + data=data, + prisma_client=prisma_client, + ) + + # Check user limits for standalone teams (not org-scoped) + # Skip for PROXY_ADMIN users + if ( + user_api_key_dict.user_role is None + or user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN + ): + # Only validate user budget/models for standalone teams + # For org-scoped teams, validation is done by _check_org_team_limits() above + if org_id_to_check is None: + await _check_user_team_limits( + data=data, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + ) + + updated_kv = data.json(exclude_unset=True) + + # Check budget_duration and budget_reset_at + if data.budget_duration is not None: + from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time + + reset_at = get_budget_reset_time(budget_duration=data.budget_duration) + + # set the budget_reset_at in DB + updated_kv["budget_reset_at"] = reset_at + + if TeamMemberBudgetHandler.should_create_budget( team_member_budget=data.team_member_budget, team_member_rpm_limit=data.team_member_rpm_limit, team_member_tpm_limit=data.team_member_tpm_limit, - ) - else: - TeamMemberBudgetHandler._clean_team_member_fields(updated_kv) - - # Check object permission - if data.object_permission is not None: - updated_kv = await handle_update_object_permission( - data_json=updated_kv, - existing_team_row=existing_team_row, - ) - - # update team metadata fields - _team_metadata_fields = LiteLLM_ManagementEndpoint_MetadataFields_Premium - for field in _team_metadata_fields: - if field in updated_kv and updated_kv[field] is not None: - _update_metadata_field( + ): + updated_kv = await TeamMemberBudgetHandler.upsert_team_member_budget_table( + team_table=existing_team_row, + user_api_key_dict=user_api_key_dict, updated_kv=updated_kv, - field_name=field, + team_member_budget=data.team_member_budget, + team_member_rpm_limit=data.team_member_rpm_limit, + team_member_tpm_limit=data.team_member_tpm_limit, + ) + else: + TeamMemberBudgetHandler._clean_team_member_fields(updated_kv) + + # Check object permission + if data.object_permission is not None: + updated_kv = await handle_update_object_permission( + data_json=updated_kv, + existing_team_row=existing_team_row, ) - for field in LiteLLM_ManagementEndpoint_MetadataFields: - if field in updated_kv and updated_kv[field] is not None: - _update_metadata_field( - updated_kv=updated_kv, - field_name=field, + # update team metadata fields + _team_metadata_fields = LiteLLM_ManagementEndpoint_MetadataFields_Premium + for field in _team_metadata_fields: + if field in updated_kv and updated_kv[field] is not None: + _update_metadata_field( + updated_kv=updated_kv, + field_name=field, + ) + + for field in LiteLLM_ManagementEndpoint_MetadataFields: + if field in updated_kv and updated_kv[field] is not None: + _update_metadata_field( + updated_kv=updated_kv, + field_name=field, + ) + + if "model_aliases" in updated_kv: + updated_kv.pop("model_aliases") + _model_id = await _update_model_table( + data=data, + model_id=existing_team_row.model_id, + prisma_client=prisma_client, + user_api_key_dict=user_api_key_dict, + litellm_proxy_admin_name=litellm_proxy_admin_name, + ) + if _model_id is not None: + updated_kv["model_id"] = _model_id + + updated_kv = prisma_client.jsonify_team_object(db_data=updated_kv) + team_row: Optional[LiteLLM_TeamTable] = ( + await prisma_client.db.litellm_teamtable.update( + where={"team_id": data.team_id}, + data=updated_kv, + include={"litellm_model_table": True}, # type: ignore + ) + ) + + if team_row is None or team_row.team_id is None: + raise HTTPException( + status_code=400, + detail={"error": "Team doesn't exist. Got={}".format(team_row)}, ) - if "model_aliases" in updated_kv: - updated_kv.pop("model_aliases") - _model_id = await _update_model_table( - data=data, - model_id=existing_team_row.model_id, - prisma_client=prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_proxy_admin_name=litellm_proxy_admin_name, - ) - if _model_id is not None: - updated_kv["model_id"] = _model_id - - updated_kv = prisma_client.jsonify_team_object(db_data=updated_kv) - team_row: Optional[LiteLLM_TeamTable] = ( - await prisma_client.db.litellm_teamtable.update( - where={"team_id": data.team_id}, - data=updated_kv, - include={"litellm_model_table": True}, # type: ignore - ) - ) - - if team_row is None or team_row.team_id is None: - raise HTTPException( - status_code=400, - detail={"error": "Team doesn't exist. Got={}".format(team_row)}, + verbose_proxy_logger.info("Successfully updated team - %s, info", team_row.team_id) + await _cache_team_object( + team_id=team_row.team_id, + team_table=LiteLLM_TeamTableCachedObj(**team_row.model_dump()), + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, ) - verbose_proxy_logger.info("Successfully updated team - %s, info", team_row.team_id) - await _cache_team_object( - team_id=team_row.team_id, - team_table=LiteLLM_TeamTableCachedObj(**team_row.model_dump()), - user_api_key_cache=user_api_key_cache, - proxy_logging_obj=proxy_logging_obj, - ) + # Enterprise Feature - Audit Logging. Enable with litellm.store_audit_logs = True + if litellm.store_audit_logs is True: + await _create_team_update_audit_log( + existing_team_row=existing_team_row, + updated_kv=updated_kv, + team_id=data.team_id, + litellm_changed_by=litellm_changed_by, + user_api_key_dict=user_api_key_dict, + litellm_proxy_admin_name=litellm_proxy_admin_name, + ) - # Enterprise Feature - Audit Logging. Enable with litellm.store_audit_logs = True - if litellm.store_audit_logs is True: - await _create_team_update_audit_log( - existing_team_row=existing_team_row, - updated_kv=updated_kv, - team_id=data.team_id, - litellm_changed_by=litellm_changed_by, - user_api_key_dict=user_api_key_dict, - litellm_proxy_admin_name=litellm_proxy_admin_name, - ) - - return {"team_id": team_row.team_id, "data": team_row} + return {"team_id": team_row.team_id, "data": team_row} + except Exception as e: + raise handle_exception_on_proxy(e) async def handle_update_object_permission( diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py index 86b23c98ba5..06ec71a84f8 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -2015,3 +2015,1605 @@ async def test_new_team_max_budget_within_user_limit(): assert result is not None assert result["team_id"] == "team-within-budget-789" assert result["max_budget"] == 50.0 + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_budget_bypasses_user_limit(): + """ + Test that /team/new with organization_id does NOT validate budget against user's personal max_budget. + + This is the bug fix for: When an org admin creates an org-scoped team, the team's budget should + be validated against the organization's limits, not the user's personal limits. + + Scenario: + - Organization has max_budget=$100 + - User (org admin) has personal max_budget=$3 + - Team is created with organization_id and max_budget=$50 + - Expected: Should succeed (within org's $100 limit) + - Bug behavior: Would fail with "max budget higher than user max. User max budget=3.0" + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, UserAPIKeyAuth, LiteLLM_UserTable, LiteLLM_OrganizationTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create non-admin user with very restrictive personal budget ($3) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-user-123", + user_max_budget=3.0, # Restrictive personal budget + models=[], # Empty models list to bypass model validation + ) + + # Create team request with budget ($50) that's within org's limit but exceeds user's personal limit + team_request = NewTeamRequest( + team_alias="org-scoped-team", + max_budget=50.0, # Within org's $100 limit, but exceeds user's $3 limit + organization_id="test-org-123", # This makes it an org-scoped team + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object" + ) as mock_get_org: + + # Setup mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.jsonify_team_object = lambda db_data: db_data + mock_prisma.get_data = AsyncMock(return_value=None) + mock_prisma.update_data = AsyncMock() + + # Mock organization with $100 budget + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-123" + mock_org.max_budget = 100.0 + mock_org.models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] + mock_org.litellm_budget_table = None # No budget table for this test + mock_get_org.return_value = mock_org + + # Mock user cache to return user with restrictive personal budget + mock_user_obj = LiteLLM_UserTable( + user_id="org-admin-user-123", + max_budget=3.0, # Restrictive personal budget + ) + mock_cache.async_get_cache = AsyncMock(return_value=mock_user_obj) + + # Mock team creation + mock_created_team = MagicMock() + mock_created_team.team_id = "team-org-scoped-789" + mock_created_team.team_alias = "org-scoped-team" + mock_created_team.max_budget = 50.0 + mock_created_team.organization_id = "test-org-123" + mock_created_team.members_with_roles = [] + mock_created_team.metadata = None + mock_created_team.model_dump.return_value = { + "team_id": "team-org-scoped-789", + "team_alias": "org-scoped-team", + "max_budget": 50.0, + "organization_id": "test-org-123", + "members_with_roles": [], + } + mock_prisma.db.litellm_teamtable.create = AsyncMock(return_value=mock_created_team) + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_created_team) + + # Mock model table + mock_prisma.db.litellm_modeltable = MagicMock() + mock_prisma.db.litellm_modeltable.create = AsyncMock(return_value=MagicMock(id="model123")) + + # Mock user table operations + mock_user = MagicMock() + mock_user.user_id = "org-admin-user-123" + mock_user.model_dump.return_value = {"user_id": "org-admin-user-123", "teams": ["team-org-scoped-789"]} + mock_prisma.db.litellm_usertable = MagicMock() + mock_prisma.db.litellm_usertable.upsert = AsyncMock(return_value=mock_user) + mock_prisma.db.litellm_usertable.update = AsyncMock(return_value=mock_user) + + # Mock team membership table + mock_membership = MagicMock() + mock_membership.model_dump.return_value = { + "team_id": "team-org-scoped-789", + "user_id": "org-admin-user-123", + "budget_id": None, + } + mock_prisma.db.litellm_teammembership = MagicMock() + mock_prisma.db.litellm_teammembership.create = AsyncMock(return_value=mock_membership) + + # Should NOT raise an exception - the fix should bypass user budget validation for org-scoped teams + result = await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify the team was created successfully with the higher budget + assert result is not None + assert result["team_id"] == "team-org-scoped-789" + assert result["max_budget"] == 50.0 + assert result["organization_id"] == "test-org-123" + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_models_bypasses_user_limit(): + """ + Test that /team/new with organization_id does NOT validate models against user's personal models. + + This is the bug fix for: When an org admin creates an org-scoped team, the team's models should + be validated against the organization's models, not the user's personal models. + + Scenario: + - Organization has models=['gpt-4', 'gpt-3.5-turbo', 'claude-3-opus'] + - User (org admin) has personal models=['no-default-models'] + - Team is created with organization_id and models=['gpt-4'] + - Expected: Should succeed (within org's allowed models) + - Bug behavior: Would fail with "Model not in allowed user models" + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, UserAPIKeyAuth, LiteLLM_UserTable, LiteLLM_OrganizationTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create non-admin user with restrictive personal models + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-user-456", + user_max_budget=None, # No budget restriction for this test + models=["no-default-models"], # Restrictive personal models + ) + + # Create team request with models that are within org's allowed models but not user's + team_request = NewTeamRequest( + team_alias="org-scoped-models-team", + models=["gpt-4"], # Within org's allowed models, but not in user's personal models + organization_id="test-org-456", # This makes it an org-scoped team + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object" + ) as mock_get_org: + + # Setup mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.jsonify_team_object = lambda db_data: db_data + mock_prisma.get_data = AsyncMock(return_value=None) + mock_prisma.update_data = AsyncMock() + + # Mock organization with allowed models + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-456" + mock_org.max_budget = 100.0 + mock_org.models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] + mock_org.litellm_budget_table = None + mock_get_org.return_value = mock_org + + # Mock user cache + mock_user_obj = LiteLLM_UserTable( + user_id="org-admin-user-456", + max_budget=None, + ) + mock_cache.async_get_cache = AsyncMock(return_value=mock_user_obj) + + # Mock team creation + mock_created_team = MagicMock() + mock_created_team.team_id = "team-org-scoped-models-789" + mock_created_team.team_alias = "org-scoped-models-team" + mock_created_team.max_budget = None + mock_created_team.organization_id = "test-org-456" + mock_created_team.models = ["gpt-4"] + mock_created_team.members_with_roles = [] + mock_created_team.metadata = None + mock_created_team.model_dump.return_value = { + "team_id": "team-org-scoped-models-789", + "team_alias": "org-scoped-models-team", + "max_budget": None, + "organization_id": "test-org-456", + "models": ["gpt-4"], + "members_with_roles": [], + } + mock_prisma.db.litellm_teamtable.create = AsyncMock(return_value=mock_created_team) + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_created_team) + + # Mock model table + mock_prisma.db.litellm_modeltable = MagicMock() + mock_prisma.db.litellm_modeltable.create = AsyncMock(return_value=MagicMock(id="model123")) + + # Mock user table operations + mock_user = MagicMock() + mock_user.user_id = "org-admin-user-456" + mock_user.model_dump.return_value = {"user_id": "org-admin-user-456", "teams": ["team-org-scoped-models-789"]} + mock_prisma.db.litellm_usertable = MagicMock() + mock_prisma.db.litellm_usertable.upsert = AsyncMock(return_value=mock_user) + mock_prisma.db.litellm_usertable.update = AsyncMock(return_value=mock_user) + + # Mock team membership table + mock_membership = MagicMock() + mock_membership.model_dump.return_value = { + "team_id": "team-org-scoped-models-789", + "user_id": "org-admin-user-456", + "budget_id": None, + } + mock_prisma.db.litellm_teammembership = MagicMock() + mock_prisma.db.litellm_teammembership.create = AsyncMock(return_value=mock_membership) + + # Should NOT raise an exception - the fix should bypass user model validation for org-scoped teams + result = await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify the team was created successfully with the org's models + assert result is not None + assert result["team_id"] == "team-org-scoped-models-789" + assert result["models"] == ["gpt-4"] + assert result["organization_id"] == "test-org-456" + + +@pytest.mark.asyncio +async def test_new_team_standalone_validates_against_user_models(): + """ + Test that /team/new WITHOUT organization_id still validates models against user's personal models. + + This ensures that standalone teams (not org-scoped) still use user-level validation. + + Scenario: + - User has personal models=['no-default-models'] + - Team is created WITHOUT organization_id and models=['gpt-4'] + - Expected: Should fail with "Model not in allowed user models" + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create non-admin user with restrictive personal models + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="non-admin-user-789", + user_max_budget=None, + models=["no-default-models"], # Restrictive personal models + ) + + # Create standalone team request (no organization_id) with models not in user's list + team_request = NewTeamRequest( + team_alias="standalone-team", + models=["gpt-4"], # Not in user's allowed models + # Note: No organization_id - this is a standalone team + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit: + # Setup basic mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.get_data = AsyncMock(return_value=None) + + # Should raise ProxyException because gpt-4 is not in user's allowed models + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "Model not in allowed user models" in str(exc_info.value.message) + assert "no-default-models" in str(exc_info.value.message) + + +@pytest.mark.asyncio +async def test_new_team_standalone_validates_against_user_budget(): + """ + Test that /team/new WITHOUT organization_id still validates budget against user's personal max_budget. + + This ensures that standalone teams (not org-scoped) still use user-level validation. + This is essentially the same as test_new_team_max_budget_exceeds_user_max_budget but + explicitly showing the contrast with org-scoped teams. + + Scenario: + - User has personal max_budget=$3 + - Team is created WITHOUT organization_id and max_budget=$50 + - Expected: Should fail with "max budget higher than user max" + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_UserTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create non-admin user with restrictive personal budget + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="non-admin-user-budget-789", + user_max_budget=100.0, # This is for key auth, actual budget is from user object + models=[], # Empty models list to bypass model validation + ) + + # Create standalone team request (no organization_id) with budget exceeding user's limit + team_request = NewTeamRequest( + team_alias="standalone-budget-team", + max_budget=50.0, # Exceeds user's personal budget + # Note: No organization_id - this is a standalone team + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit: + # Setup basic mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.get_data = AsyncMock(return_value=None) + + # Mock user cache to return user with restrictive personal budget ($3) + mock_user_obj = LiteLLM_UserTable( + user_id="non-admin-user-budget-789", + max_budget=3.0, # Restrictive personal budget + ) + mock_cache.async_get_cache = AsyncMock(return_value=mock_user_obj) + + # Should raise ProxyException because budget exceeds user's max_budget + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "max budget higher than user max" in str(exc_info.value.message) + assert "3.0" in str(exc_info.value.message) # User's max_budget should be mentioned + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_budget_exceeds_org_limit(): + """ + Test that /team/new with organization_id fails when team budget exceeds organization's max_budget. + + Scenario: + - Organization has max_budget=$100 + - Team is created with organization_id and max_budget=$150 + - Expected: Should fail with error about exceeding org budget + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create user (org admin) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-user-budget-test", + models=[], + ) + + # Create team request with budget ($150) that exceeds org's limit ($100) + team_request = NewTeamRequest( + team_alias="org-team-exceeds-budget", + max_budget=150.0, # Exceeds org's $100 limit + organization_id="test-org-budget-limit", + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object" + ) as mock_get_org: + + # Setup mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.get_data = AsyncMock(return_value=None) + + # Mock organization with $100 budget limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.max_budget = 100.0 + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-budget-limit" + mock_org.models = ["gpt-4", "gpt-3.5-turbo"] + mock_org.litellm_budget_table = mock_budget_table + mock_get_org.return_value = mock_org + + # Should raise ProxyException because team budget exceeds org budget + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "exceeds organization" in str(exc_info.value.message).lower() or "organization" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_models_not_in_org_models(): + """ + Test that /team/new with organization_id fails when team models are not in organization's allowed models. + + Scenario: + - Organization has models=['gpt-4', 'gpt-3.5-turbo'] + - Team is created with organization_id and models=['claude-3-opus'] + - Expected: Should fail with error about model not in org's allowed models + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create user (org admin) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-user-models-test", + models=[], + ) + + # Create team request with model not in org's allowed list + team_request = NewTeamRequest( + team_alias="org-team-invalid-model", + models=["claude-3-opus"], # Not in org's allowed models + organization_id="test-org-models-limit", + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object" + ) as mock_get_org: + + # Setup mocks + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.get_data = AsyncMock(return_value=None) + + # Mock organization with specific allowed models (not including claude-3-opus) + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-models-limit" + mock_org.models = ["gpt-4", "gpt-3.5-turbo"] # claude-3-opus is NOT allowed + mock_org.litellm_budget_table = None + mock_get_org.return_value = mock_org + + # Should raise ProxyException because claude-3-opus is not in org's allowed models + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "claude-3-opus" in str(exc_info.value.message) or "organization" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_standalone_budget_exceeds_user_limit(): + """ + Test that /team/update for a standalone team fails when new budget exceeds user's max_budget. + + Scenario: + - User has personal max_budget=$50 + - Standalone team exists (no organization_id) + - User tries to update team budget to $100 + - Expected: Should fail with error about exceeding user budget + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_UserTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create non-admin user with restrictive personal budget + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="non-admin-update-test", + models=[], + ) + + # Create update request with budget exceeding user's limit + update_request = UpdateTeamRequest( + team_id="standalone-team-123", + max_budget=100.0, # Exceeds user's $50 limit + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit: + + # Mock existing standalone team (no organization_id) + mock_existing_team = MagicMock() + mock_existing_team.team_id = "standalone-team-123" + mock_existing_team.organization_id = None # Standalone team + mock_existing_team.max_budget = 30.0 + mock_existing_team.model_dump.return_value = { + "team_id": "standalone-team-123", + "organization_id": None, + "max_budget": 30.0, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Mock user cache to return user with restrictive budget + mock_user_obj = LiteLLM_UserTable( + user_id="non-admin-update-test", + max_budget=50.0, # User's budget limit + ) + mock_cache.async_get_cache = AsyncMock(return_value=mock_user_obj) + + # Should raise ProxyException because new budget exceeds user's max_budget + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "budget" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_budget_exceeds_org_limit(): + """ + Test that /team/update for an org-scoped team fails when new budget exceeds organization's max_budget. + + Scenario: + - Organization has max_budget=$100 + - Org-scoped team exists + - User tries to update team budget to $150 + - Expected: Should fail with error about exceeding org budget + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user (org admin) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-test", + models=[], + ) + + # Create update request with budget exceeding org's limit + update_request = UpdateTeamRequest( + team_id="org-team-456", + max_budget=150.0, # Exceeds org's $100 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with $100 budget limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.max_budget = 100.0 + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ) as mock_get_org: + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-456" + mock_existing_team.organization_id = "test-org-update" + mock_existing_team.max_budget = 80.0 + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-456", + "organization_id": "test-org-update", + "max_budget": 80.0, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because new budget exceeds org's max_budget + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "organization" in str(exc_info.value.message).lower() or "budget" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_standalone_models_exceeds_user_limit(): + """ + Test that /team/update for a standalone team fails when models are not in user's allowed models. + + Scenario: + - User has personal models=['gpt-3.5-turbo'] + - Standalone team exists (no organization_id) + - User tries to update team models to ['gpt-4'] (not in user's allowed models) + - Expected: Should fail with error about model not in user's allowed models + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create non-admin user with restrictive personal models + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="non-admin-update-models-test", + models=["gpt-3.5-turbo"], # Restrictive model list + ) + + # Create update request with model not in user's allowed list + update_request = UpdateTeamRequest( + team_id="standalone-team-models-123", + models=["gpt-4"], # Not in user's allowed models + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit: + + # Mock existing standalone team (no organization_id) + mock_existing_team = MagicMock() + mock_existing_team.team_id = "standalone-team-models-123" + mock_existing_team.organization_id = None # Standalone team + mock_existing_team.models = ["gpt-3.5-turbo"] + mock_existing_team.model_dump.return_value = { + "team_id": "standalone-team-models-123", + "organization_id": None, + "models": ["gpt-3.5-turbo"], + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because model not in user's allowed models + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "model" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_budget_bypasses_user_limit(): + """ + Test that /team/update for an org-scoped team does NOT validate budget against user's personal max_budget. + + Scenario: + - Organization has max_budget=$100 + - User (org admin) has personal max_budget=$3 + - Org-scoped team exists with current budget=$30 + - User tries to update team budget to $50 (within org limit, exceeds user limit) + - Expected: Should succeed (validated against org, not user) + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, UserAPIKeyAuth, LiteLLM_UserTable, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user with very restrictive personal budget ($3) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-budget-test", + models=[], + ) + + # Create update request with budget within org limit but exceeding user limit + update_request = UpdateTeamRequest( + team_id="org-team-update-budget-123", + max_budget=50.0, # Within org's $100 limit, exceeds user's $3 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with $100 budget limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.max_budget = 100.0 + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-budget" + mock_org.models = ["gpt-4", "gpt-3.5-turbo"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ) as mock_get_org: + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-budget-123" + mock_existing_team.organization_id = "test-org-update-budget" + mock_existing_team.max_budget = 30.0 + mock_existing_team.model_id = None + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-budget-123", + "organization_id": "test-org-update-budget", + "max_budget": 30.0, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + mock_prisma.jsonify_team_object = lambda db_data: db_data + + # Mock user cache to return user with restrictive budget + mock_user_obj = LiteLLM_UserTable( + user_id="org-admin-update-budget-test", + max_budget=3.0, # Restrictive personal budget + ) + mock_cache.async_get_cache = AsyncMock(return_value=mock_user_obj) + mock_cache.async_set_cache = AsyncMock() # Mock cache set for _cache_team_object + + # Mock team update + mock_updated_team = MagicMock() + mock_updated_team.team_id = "org-team-update-budget-123" + mock_updated_team.organization_id = "test-org-update-budget" + mock_updated_team.max_budget = 50.0 + mock_updated_team.litellm_model_table = None + mock_updated_team.model_dump.return_value = { + "team_id": "org-team-update-budget-123", + "organization_id": "test-org-update-budget", + "max_budget": 50.0, + } + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_updated_team) + + # Should NOT raise an exception - bypass user budget validation for org-scoped teams + result = await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify the team was updated successfully with the higher budget + assert result is not None + assert result["data"].max_budget == 50.0 + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_models_bypasses_user_limit(): + """ + Test that /team/update for an org-scoped team does NOT validate models against user's personal models. + + Scenario: + - Organization has models=['gpt-4', 'gpt-3.5-turbo', 'claude-3-opus'] + - User (org admin) has personal models=['no-default-models'] + - Org-scoped team exists + - User tries to update team models to ['gpt-4'] (in org's allowed, not in user's) + - Expected: Should succeed (validated against org, not user) + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, UserAPIKeyAuth, LiteLLM_OrganizationTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user with very restrictive personal models + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-models-test", + models=["no-default-models"], # Restrictive model list + ) + + # Create update request with models in org's allowed but not in user's + update_request = UpdateTeamRequest( + team_id="org-team-update-models-123", + models=["gpt-4"], # In org's allowed, not in user's + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with generous model list + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-models" + mock_org.models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] + mock_org.litellm_budget_table = None + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ) as mock_get_org: + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-models-123" + mock_existing_team.organization_id = "test-org-update-models" + mock_existing_team.models = ["gpt-3.5-turbo"] + mock_existing_team.model_id = None + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-models-123", + "organization_id": "test-org-update-models", + "models": ["gpt-3.5-turbo"], + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + mock_prisma.jsonify_team_object = lambda db_data: db_data + mock_cache.async_set_cache = AsyncMock() # Mock cache set for _cache_team_object + + # Mock team update + mock_updated_team = MagicMock() + mock_updated_team.team_id = "org-team-update-models-123" + mock_updated_team.organization_id = "test-org-update-models" + mock_updated_team.models = ["gpt-4"] + mock_updated_team.litellm_model_table = None + mock_updated_team.model_dump.return_value = { + "team_id": "org-team-update-models-123", + "organization_id": "test-org-update-models", + "models": ["gpt-4"], + } + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_updated_team) + + # Should NOT raise an exception - bypass user models validation for org-scoped teams + result = await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify the team was updated successfully with the new models + assert result is not None + assert result["data"].models == ["gpt-4"] + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_models_not_in_org_models(): + """ + Test that /team/update for an org-scoped team fails when models are not in organization's allowed models. + + Scenario: + - Organization has models=['gpt-4', 'gpt-3.5-turbo'] + - Org-scoped team exists + - User tries to update team models to ['claude-3-opus'] (not in org's allowed models) + - Expected: Should fail with error about model not in org's allowed models + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user (org admin) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-models-fail-test", + models=[], + ) + + # Create update request with model not in org's allowed list + update_request = UpdateTeamRequest( + team_id="org-team-update-models-fail-123", + models=["claude-3-opus"], # Not in org's allowed models + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with restricted model list (no claude-3-opus) + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-models-fail" + mock_org.models = ["gpt-4", "gpt-3.5-turbo"] # claude-3-opus is NOT allowed + mock_org.litellm_budget_table = None + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ) as mock_get_org: + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-models-fail-123" + mock_existing_team.organization_id = "test-org-update-models-fail" + mock_existing_team.models = ["gpt-4"] + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-models-fail-123", + "organization_id": "test-org-update-models-fail", + "models": ["gpt-4"], + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because claude-3-opus is not in org's allowed models + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "claude-3-opus" in str(exc_info.value.message) or "organization" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_tpm_limit_exceeds_user_limit(): + """ + Test that /team/update fails when TPM limit exceeds user's TPM limit. + + Scenario: + - User has tpm_limit=1000 + - User tries to update team with tpm_limit=5000 + - Expected: Should fail with error about exceeding user TPM limit + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create non-admin user with TPM limit + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="tpm-limit-user", + models=[], + tpm_limit=1000, # User's TPM limit + ) + + # Create update request with TPM exceeding user's limit + update_request = UpdateTeamRequest( + team_id="team-tpm-test-123", + tpm_limit=5000, # Exceeds user's 1000 limit + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ): + + # Mock existing standalone team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "team-tpm-test-123" + mock_existing_team.organization_id = None + mock_existing_team.tpm_limit = 500 + mock_existing_team.model_dump.return_value = { + "team_id": "team-tpm-test-123", + "organization_id": None, + "tpm_limit": 500, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because new TPM exceeds user's limit + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "tpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_rpm_limit_exceeds_user_limit(): + """ + Test that /team/update fails when RPM limit exceeds user's RPM limit. + + Scenario: + - User has rpm_limit=100 + - User tries to update team with rpm_limit=500 + - Expected: Should fail with error about exceeding user RPM limit + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create non-admin user with RPM limit + non_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="rpm-limit-user", + models=[], + rpm_limit=100, # User's RPM limit + ) + + # Create update request with RPM exceeding user's limit + update_request = UpdateTeamRequest( + team_id="team-rpm-test-123", + rpm_limit=500, # Exceeds user's 100 limit + ) + + dummy_request = MagicMock(spec=Request) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ): + + # Mock existing standalone team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "team-rpm-test-123" + mock_existing_team.organization_id = None + mock_existing_team.rpm_limit = 50 + mock_existing_team.model_dump.return_value = { + "team_id": "team-rpm-test-123", + "organization_id": None, + "rpm_limit": 50, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because new RPM exceeds user's limit + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=non_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "rpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_tpm_exceeds_org_limit(): + """ + Test that /team/new for an org-scoped team fails when TPM exceeds organization's TPM limit. + + Scenario: + - Organization has tpm_limit=10000 + - User tries to create org-scoped team with tpm_limit=20000 + - Expected: Should fail with error about exceeding org TPM limit + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create user (with restrictive personal TPM limit that should be bypassed) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-tpm-test", + models=[], + tpm_limit=1000, # User's personal limit (should be bypassed for org teams) + ) + + # Create team request with TPM exceeding org's limit + team_request = NewTeamRequest( + team_alias="org-tpm-test-team", + organization_id="test-org-tpm", + tpm_limit=20000, # Exceeds org's 10000 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with TPM limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = 10000 # Org's TPM limit + mock_budget_table.rpm_limit = None + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-tpm" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ): + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_prisma.get_data = AsyncMock(return_value=None) + + # Should raise ProxyException because TPM exceeds org limit + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "tpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_rpm_exceeds_org_limit(): + """ + Test that /team/new for an org-scoped team fails when RPM exceeds organization's RPM limit. + + Scenario: + - Organization has rpm_limit=1000 + - User tries to create org-scoped team with rpm_limit=2000 + - Expected: Should fail with error about exceeding org RPM limit + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create user (with restrictive personal RPM limit that should be bypassed) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-rpm-test", + models=[], + rpm_limit=100, # User's personal limit (should be bypassed for org teams) + ) + + # Create team request with RPM exceeding org's limit + team_request = NewTeamRequest( + team_alias="org-rpm-test-team", + organization_id="test-org-rpm", + rpm_limit=2000, # Exceeds org's 1000 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with RPM limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = None + mock_budget_table.rpm_limit = 1000 # Org's RPM limit + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-rpm" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ): + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_prisma.get_data = AsyncMock(return_value=None) + + # Should raise ProxyException because RPM exceeds org limit + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "rpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_new_team_org_scoped_tpm_rpm_bypasses_user_limit(): + """ + Test that /team/new for an org-scoped team bypasses user's TPM/RPM limits. + + Scenario: + - User has tpm_limit=1000, rpm_limit=100 + - Organization has tpm_limit=50000, rpm_limit=5000 + - User creates org-scoped team with tpm_limit=10000, rpm_limit=1000 + - Expected: Should succeed (bypasses user limits, within org limits) + """ + from fastapi import Request + + from litellm.proxy._types import NewTeamRequest, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable, LiteLLM_TeamTable + from litellm.proxy.management_endpoints.team_endpoints import new_team + + # Create user with restrictive personal limits + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-bypass-test", + models=[], + tpm_limit=1000, # Restrictive user TPM limit + rpm_limit=100, # Restrictive user RPM limit + ) + + # Create team request exceeding user limits but within org limits + team_request = NewTeamRequest( + team_alias="org-bypass-test-team", + organization_id="test-org-bypass", + tpm_limit=10000, # Exceeds user's 1000 but within org's 50000 + rpm_limit=1000, # Exceeds user's 100 but within org's 5000 + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with generous limits + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = 50000 # Generous org TPM limit + mock_budget_table.rpm_limit = 5000 # Generous org RPM limit + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-bypass" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server._license_check" + ) as mock_license, patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ), patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ), patch( + "litellm.proxy.management_endpoints.team_endpoints._add_team_members_to_team", + new=AsyncMock() + ): + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_prisma.get_data = AsyncMock(return_value=None) + + # Mock team creation + mock_created_team = MagicMock(spec=LiteLLM_TeamTable) + mock_created_team.team_id = "new-bypass-team-id" + mock_created_team.team_alias = "org-bypass-test-team" + mock_created_team.tpm_limit = 10000 + mock_created_team.rpm_limit = 1000 + mock_created_team.metadata = None + mock_created_team.members_with_roles = [] + mock_created_team.model_dump.return_value = { + "team_id": "new-bypass-team-id", + "team_alias": "org-bypass-test-team", + "tpm_limit": 10000, + "rpm_limit": 1000, + "metadata": None, + "members_with_roles": [], + } + mock_prisma.db.litellm_teamtable.create = AsyncMock(return_value=mock_created_team) + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_created_team) + mock_prisma.jsonify_team_object = MagicMock(side_effect=lambda db_data: db_data) + + # Should succeed - bypasses user limits since org-scoped + result = await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify team was created + assert result["team_id"] == "new-bypass-team-id" + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_tpm_exceeds_org_limit(): + """ + Test that /team/update for an org-scoped team fails when TPM exceeds organization's TPM limit. + + Scenario: + - Organization has tpm_limit=10000 + - User tries to update org-scoped team with tpm_limit=20000 + - Expected: Should fail with error about exceeding org TPM limit + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user (with restrictive personal TPM limit that should be bypassed) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-tpm-test", + models=[], + tpm_limit=1000, # User's personal limit (should be bypassed for org teams) + ) + + # Create update request with TPM exceeding org's limit + update_request = UpdateTeamRequest( + team_id="org-team-update-tpm-123", + tpm_limit=20000, # Exceeds org's 10000 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with TPM limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = 10000 # Org's TPM limit + mock_budget_table.rpm_limit = None + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-tpm" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ): + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-tpm-123" + mock_existing_team.organization_id = "test-org-update-tpm" + mock_existing_team.tpm_limit = 5000 + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-tpm-123", + "organization_id": "test-org-update-tpm", + "tpm_limit": 5000, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because TPM exceeds org limit + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "tpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_rpm_exceeds_org_limit(): + """ + Test that /team/update for an org-scoped team fails when RPM exceeds organization's RPM limit. + + Scenario: + - Organization has rpm_limit=1000 + - User tries to update org-scoped team with rpm_limit=2000 + - Expected: Should fail with error about exceeding org RPM limit + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, ProxyException, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user (with restrictive personal RPM limit that should be bypassed) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-rpm-test", + models=[], + rpm_limit=100, # User's personal limit (should be bypassed for org teams) + ) + + # Create update request with RPM exceeding org's limit + update_request = UpdateTeamRequest( + team_id="org-team-update-rpm-123", + rpm_limit=2000, # Exceeds org's 1000 limit + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with RPM limit + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = None + mock_budget_table.rpm_limit = 1000 # Org's RPM limit + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-rpm" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ): + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-rpm-123" + mock_existing_team.organization_id = "test-org-update-rpm" + mock_existing_team.rpm_limit = 500 + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-rpm-123", + "organization_id": "test-org-update-rpm", + "rpm_limit": 500, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + + # Should raise ProxyException because RPM exceeds org limit + with pytest.raises(ProxyException) as exc_info: + await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify exception details + assert exc_info.value.code == '400' + assert "rpm" in str(exc_info.value.message).lower() + + +@pytest.mark.asyncio +async def test_update_team_org_scoped_tpm_rpm_bypasses_user_limit(): + """ + Test that /team/update for an org-scoped team bypasses user's TPM/RPM limits. + + Scenario: + - User has tpm_limit=1000, rpm_limit=100 + - Organization has tpm_limit=50000, rpm_limit=5000 + - User updates org-scoped team with tpm_limit=10000, rpm_limit=1000 + - Expected: Should succeed (bypasses user limits, within org limits) + """ + from fastapi import Request + + from litellm.proxy._types import UpdateTeamRequest, UserAPIKeyAuth, LiteLLM_OrganizationTable, LiteLLM_BudgetTable, LiteLLM_TeamTable + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user with restrictive personal limits + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-update-bypass-test", + models=[], + tpm_limit=1000, # Restrictive user TPM limit + rpm_limit=100, # Restrictive user RPM limit + ) + + # Create update request exceeding user limits but within org limits + update_request = UpdateTeamRequest( + team_id="org-team-update-bypass-123", + tpm_limit=10000, # Exceeds user's 1000 but within org's 50000 + rpm_limit=1000, # Exceeds user's 100 but within org's 5000 + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with generous limits + mock_budget_table = MagicMock(spec=LiteLLM_BudgetTable) + mock_budget_table.tpm_limit = 50000 # Generous org TPM limit + mock_budget_table.rpm_limit = 5000 # Generous org RPM limit + mock_budget_table.max_budget = None + + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-update-bypass" + mock_org.models = ["gpt-4"] + mock_org.litellm_budget_table = mock_budget_table + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.proxy_logging_obj" + ) as mock_logging, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ): + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-update-bypass-123" + mock_existing_team.organization_id = "test-org-update-bypass" + mock_existing_team.tpm_limit = 5000 + mock_existing_team.rpm_limit = 500 + mock_existing_team.model_id = None + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-update-bypass-123", + "organization_id": "test-org-update-bypass", + "tpm_limit": 5000, + "rpm_limit": 500, + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + mock_cache.async_set_cache = AsyncMock() + + # Mock team update + mock_updated_team = MagicMock(spec=LiteLLM_TeamTable) + mock_updated_team.team_id = "org-team-update-bypass-123" + mock_updated_team.tpm_limit = 10000 + mock_updated_team.rpm_limit = 1000 + mock_updated_team.model_dump.return_value = { + "team_id": "org-team-update-bypass-123", + "tpm_limit": 10000, + "rpm_limit": 1000, + } + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_updated_team) + mock_prisma.jsonify_team_object = MagicMock(side_effect=lambda db_data: db_data) + + # Should succeed - bypasses user limits since org-scoped + result = await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify team was updated + assert result["team_id"] == "org-team-update-bypass-123" \ No newline at end of file From 98a244450e1d14649f9edbd43c4ace5962d605c7 Mon Sep 17 00:00:00 2001 From: rioiart Date: Tue, 2 Dec 2025 04:53:30 +0100 Subject: [PATCH 238/349] Fix sso users not added to entra synced team (#17331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add failing tests for SSO user not added to Entra-synced teams bug Adds tests reproducing the bug where new SSO users with teams=None (from NewUserResponse) are not added to Entra ID synced teams because add_missing_team_member() returns early when teams is None. Tests demonstrate: - NewUserResponse with teams=None fails to add user to teams (bug) - LiteLLM_UserTable with teams=[] correctly adds user to teams (control) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: treat None as empty list in add_missing_team_member for new SSO users Fixed bug where new SSO users logging in via Microsoft SSO were not added to their Entra-synced teams. The issue was an early return when user_info.teams is None (default for NewUserResponse). Now treats None as an empty list so new users are properly added to all their SSO teams. Location: litellm/proxy/management_endpoints/ui_sso.py:438-440 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- litellm/proxy/management_endpoints/ui_sso.py | 6 +- .../proxy/management_endpoints/test_ui_sso.py | 209 +++++++++++++++++- 2 files changed, 211 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index a033e2cf5f4..59a93f3c486 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -435,9 +435,9 @@ async def add_missing_team_member( - Get missing teams (diff b/w user_info.team_ids and sso_teams) - Add missing user to missing teams """ - if user_info.teams is None: - return - missing_teams = set(sso_teams) - set(user_info.teams) + # Handle None as empty list for new users + user_teams = user_info.teams if user_info.teams is not None else [] + missing_teams = set(sso_teams) - set(user_teams) missing_teams_list = list(missing_teams) tasks = [] tasks = [ diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py index f01813fa587..8d7aa51fa0f 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py +++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py @@ -16,7 +16,7 @@ sys.path.insert( ) # Adds the parent directory to the system path import litellm -from litellm.proxy._types import NewTeamRequest +from litellm.proxy._types import LiteLLM_UserTable, NewTeamRequest, NewUserResponse from litellm.proxy.auth.handle_jwt import JWTHandler from litellm.proxy.management_endpoints.types import CustomOpenID from litellm.proxy.management_endpoints.ui_sso import ( @@ -2573,3 +2573,210 @@ class TestPKCEFunctionality: assert "code_challenge=" in updated_location assert "code_challenge_method=S256" in updated_location assert f"state={test_state}" in updated_location + + +# Tests for SSO user team assignment bug (Issue: SSO Users Not Added to Entra-Synced Teams on First Login) +class TestAddMissingTeamMember: + """Tests for the add_missing_team_member function""" + + @pytest.mark.asyncio + async def test_add_missing_team_member_with_new_user_response_teams_none(self): + """ + Bug reproduction: When a NewUserResponse has teams=None (new SSO user), + add_missing_team_member() should still add the user to the SSO teams. + + Currently FAILS: The function returns early when teams is None. + """ + from litellm.proxy._types import NewUserResponse + from litellm.proxy.management_endpoints.ui_sso import add_missing_team_member + + # Simulate a new SSO user - NewUserResponse has teams=None by default + new_user = NewUserResponse( + user_id="new-sso-user-123", + key="sk-xxxxx", + teams=None, # This is the default for NewUserResponse + ) + + sso_teams = ["team-from-entra-1", "team-from-entra-2"] + + with patch( + "litellm.proxy.management_endpoints.ui_sso.create_team_member_add_task" + ) as mock_add_task: + mock_add_task.return_value = AsyncMock() + + await add_missing_team_member(user_info=new_user, sso_teams=sso_teams) + + # Bug: This assertion currently FAILS - no teams are added + # because function returns early when teams is None + assert ( + mock_add_task.call_count == 2 + ), f"Expected 2 calls to add user to teams, but got {mock_add_task.call_count}" + called_team_ids = [call.args[0] for call in mock_add_task.call_args_list] + assert set(called_team_ids) == { + "team-from-entra-1", + "team-from-entra-2", + } + + @pytest.mark.asyncio + async def test_add_missing_team_member_with_litellm_user_table_empty_teams(self): + """ + Control test: When a LiteLLM_UserTable has teams=[] (existing user, no teams), + add_missing_team_member() should add the user to SSO teams. + + This test PASSES because LiteLLM_UserTable defaults teams to [] not None. + """ + from litellm.proxy._types import LiteLLM_UserTable + from litellm.proxy.management_endpoints.ui_sso import add_missing_team_member + + # Existing user has teams=[] by default (not None) + existing_user = LiteLLM_UserTable( + user_id="existing-user-456", + teams=[], # Empty list, not None + ) + + sso_teams = ["team-from-entra-1", "team-from-entra-2"] + + with patch( + "litellm.proxy.management_endpoints.ui_sso.create_team_member_add_task" + ) as mock_add_task: + mock_add_task.return_value = AsyncMock() + + await add_missing_team_member(user_info=existing_user, sso_teams=sso_teams) + + # This PASSES - teams are added because teams=[] not None + assert mock_add_task.call_count == 2 + + @pytest.mark.asyncio + async def test_add_user_to_teams_from_sso_response_new_user(self): + """ + Integration test: Simulates the SSO response handler with a new user + that has teams=None from NewUserResponse. + """ + from litellm.proxy._types import NewUserResponse + from litellm.proxy.management_endpoints.types import CustomOpenID + from litellm.proxy.management_endpoints.ui_sso import ( + SSOAuthenticationHandler, + ) + + # SSO response with team_ids from Entra ID + sso_result = CustomOpenID( + id="new-sso-user-id", + email="newuser@example.com", + team_ids=["entra-group-1", "entra-group-2"], + ) + + # New user response (simulates what new_user() returns) + new_user_info = NewUserResponse( + user_id="new-sso-user-id", + key="sk-xxxxx", + teams=None, # Bug: NewUserResponse defaults to None + ) + + with patch( + "litellm.proxy.management_endpoints.ui_sso.add_missing_team_member" + ) as mock_add_member: + await SSOAuthenticationHandler.add_user_to_teams_from_sso_response( + result=sso_result, + user_info=new_user_info, + ) + + # Verify add_missing_team_member was called with correct args + mock_add_member.assert_called_once_with( + user_info=new_user_info, sso_teams=["entra-group-1", "entra-group-2"] + ) + + @pytest.mark.asyncio + async def test_sso_first_login_full_flow_adds_user_to_teams(self): + """ + End-to-end test: Simulates complete first-time SSO login with Entra groups. + Verifies teams are created AND user is added as a member. + """ + from litellm.proxy._types import NewUserResponse + from litellm.proxy.management_endpoints.ui_sso import add_missing_team_member + + team_member_calls = [] + + async def track_team_member_add(team_id, user_info): + team_member_calls.append( + {"team_id": team_id, "user_id": user_info.user_id} + ) + + # New SSO user with Entra groups + new_user = NewUserResponse( + user_id="first-time-sso-user", + key="sk-xxxxx", + teams=None, # The problematic default + ) + + sso_teams = ["entra-team-alpha", "entra-team-beta"] + + with patch( + "litellm.proxy.management_endpoints.ui_sso.create_team_member_add_task", + side_effect=track_team_member_add, + ): + await add_missing_team_member(user_info=new_user, sso_teams=sso_teams) + + # Bug: With current code, team_member_calls will be empty + # After fix: Should have 2 entries + assert ( + len(team_member_calls) == 2 + ), f"Expected 2 teams to be added, but got {len(team_member_calls)}" + assert {c["team_id"] for c in team_member_calls} == { + "entra-team-alpha", + "entra-team-beta", + } + assert all(c["user_id"] == "first-time-sso-user" for c in team_member_calls) + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "user_info_factory,teams_value,expected_teams_added", + [ + # Bug case: NewUserResponse with teams=None + pytest.param( + lambda uid: NewUserResponse(user_id=uid, key="sk-xxx", teams=None), + None, + ["team-1", "team-2"], # Should still add teams + id="new_user_teams_none", + ), + # Working case: LiteLLM_UserTable with teams=[] + pytest.param( + lambda uid: LiteLLM_UserTable(user_id=uid, teams=[]), + [], + ["team-1", "team-2"], + id="existing_user_empty_teams", + ), + # Existing user with some teams already + pytest.param( + lambda uid: LiteLLM_UserTable(user_id=uid, teams=["team-1"]), + ["team-1"], + ["team-2"], # Only missing team should be added + id="existing_user_partial_teams", + ), + ], + ) + async def test_add_missing_team_member_handles_all_user_types( + self, user_info_factory, teams_value, expected_teams_added + ): + """ + Parametrized test ensuring add_missing_team_member works for all user types. + """ + from litellm.proxy._types import LiteLLM_UserTable + from litellm.proxy.management_endpoints.ui_sso import add_missing_team_member + + user_info = user_info_factory("test-user-id") + sso_teams = ["team-1", "team-2"] + + added_teams = [] + + async def mock_create_task(team_id, user): + added_teams.append(team_id) + + with patch( + "litellm.proxy.management_endpoints.ui_sso.create_team_member_add_task", + side_effect=mock_create_task, + ): + await add_missing_team_member(user_info=user_info, sso_teams=sso_teams) + + assert set(added_teams) == set( + expected_teams_added + ), f"Expected teams {expected_teams_added}, but got {added_teams}" From 71efcb71151aedb216e411815ce871376605da55 Mon Sep 17 00:00:00 2001 From: idola9 Date: Tue, 2 Dec 2025 05:56:14 +0200 Subject: [PATCH 239/349] Refactor Noma guardrail to use shared Responses transformation and include system instructions (#17315) * Support system prompts in noma guardrails * Use litellm util to covert chat completions to responses api --- .../transformation.py | 6 +- .../guardrails/guardrail_hooks/noma/noma.py | 78 ++-- .../guardrails/guardrail_hooks/test_noma.py | 335 ++++++++++++------ 3 files changed, 263 insertions(+), 156 deletions(-) diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 07d9de5a016..2045836387f 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -148,7 +148,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if role == "system": # Extract system message as instructions if isinstance(content, str): - instructions = content + if instructions: + # Concatenate multiple system prompts with a space + instructions = f"{instructions} {content}" + else: + instructions = content else: input_items.append( { diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py index 3ae2d519c45..a0ea90ccf21 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py @@ -28,6 +28,9 @@ from fastapi import HTTPException import litellm from litellm import DualCache, ModelResponse from litellm._logging import verbose_proxy_logger +from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, +) from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.llms.base_llm.base_model_iterator import MockResponseIterator from litellm.llms.custom_httpx.http_handler import ( @@ -111,6 +114,7 @@ class NomaGuardrail(CustomGuardrail): self.async_handler = get_async_httpx_client( llm_provider=httpxSpecialProvider.GuardrailCallback ) + self._responses_transform_handler = LiteLLMResponsesTransformationHandler() self.api_key = api_key or os.environ.get("NOMA_API_KEY") self.api_base = api_base or os.environ.get( "NOMA_API_BASE", NomaGuardrail._DEFAULT_API_BASE @@ -164,13 +168,28 @@ class NomaGuardrail(CustomGuardrail): start_time = datetime.now() extra_data = self.get_guardrail_dynamic_request_body_params(request_data) - user_message = await self._extract_user_message(request_data) - if not user_message: + messages = request_data.get("messages") or [] + if not messages: return None - payload = { - "input": [{"type": "message", "role": "user", "content": user_message}] - } + input_items, instructions = self._responses_transform_handler.convert_chat_completion_messages_to_responses_api( # type: ignore[arg-type] + messages + ) + + if instructions: + system_message = { + "type": "message", + "role": "system", + "content": [ + {"type": "input_text", "text": instructions}, + ], + } + input_items.insert(0, system_message) + + if not input_items: + return None + + payload = {"input": input_items} response_json = await self._call_noma_api( payload=payload, llm_request_id=None, @@ -198,9 +217,9 @@ class NomaGuardrail(CustomGuardrail): if self.monitor_mode: await self._handle_verdict_background( - USER_ROLE, json.dumps(user_message), response_json + USER_ROLE, json.dumps(input_items), response_json ) - return json.dumps(user_message) + return json.dumps(input_items) # Check if we should anonymize content if self._should_anonymize(response_json, USER_ROLE): @@ -215,8 +234,8 @@ class NomaGuardrail(CustomGuardrail): ) return anonymized_content - await self._check_verdict(USER_ROLE, json.dumps(user_message), response_json) - return json.dumps(user_message) + await self._check_verdict(USER_ROLE, json.dumps(input_items), response_json) + return json.dumps(input_items) async def _process_llm_response_check( self, @@ -732,47 +751,6 @@ class NomaGuardrail(CustomGuardrail): return response - async def _extract_user_message(self, data: dict) -> Optional[List[dict]]: - """Extract the last user message from request data""" - messages = data.get("messages", []) - if not messages: - return None - - # Get the last user message - user_messages = [msg for msg in messages if msg.get("role") == USER_ROLE] - if not user_messages: - return None - - last_user_message = user_messages[-1].get("content", "") - if isinstance(last_user_message, str): - return [{"type": "input_text", "text": last_user_message}] - elif isinstance(last_user_message, list): - converted_messages = [] - for message in last_user_message: - converted_message = self._convert_single_user_message_to_payload( - message - ) - if converted_message is not None: - converted_messages.append(converted_message) - return converted_messages - else: - return None - - def _convert_single_user_message_to_payload( - self, user_message: Any - ) -> Optional[dict]: - if isinstance(user_message, str): - return {"type": "input_text", "text": user_message} - elif user_message.get("type", "") == "image_url": - return { - "type": "input_image", - "image_url": user_message.get("image_url", {}).get("url", ""), - } - elif user_message.get("type", "") == "text": - return {"type": "input_text", "text": user_message.get("text", "")} - else: - return None - async def _call_noma_api( self, payload: dict, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py index 94cb831a30c..f1ac6ef14b1 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py @@ -1,5 +1,6 @@ import copy import os +from typing import cast from unittest.mock import AsyncMock, MagicMock, patch import httpx @@ -14,6 +15,7 @@ from litellm.proxy.guardrails.guardrail_hooks.noma import ( ) from litellm.proxy.guardrails.guardrail_hooks.noma.noma import NomaBlockedMessage from litellm.proxy.guardrails.init_guardrails import init_guardrails_v2 +from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import Choices, Message @@ -413,7 +415,7 @@ class TestNomaGuardrailHooks: # Verify API call details call_args = mock_post.call_args - # Verify the URL endpoint + # Verify the URL endpoint assert call_args.args[0].endswith("/ai-dr/v2/prompt/scan") # Verify headers and JSON payload if "headers" in call_args.kwargs: @@ -426,6 +428,130 @@ class TestNomaGuardrailHooks: assert "x-noma-context" in json_payload assert json_payload["x-noma-context"]["applicationId"] == "test-app" + @pytest.mark.asyncio + async def test_pre_call_hook_with_system_prompt( + self, noma_guardrail, mock_user_api_key_dict + ): + """Test pre-call hook includes system prompt in Noma API request""" + request_data = { + "messages": [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "Hello, how are you?"}, + ], + "litellm_call_id": "test-call-id", + "metadata": {"requester_ip_address": "192.168.1.1"}, + } + + mock_response = MagicMock() + mock_response.json.return_value = { + "aggregatedScanResult": False, # False means safe + "scanResult": [ + { + "role": "system", + "type": "message", + "results": {} + }, + { + "role": "user", + "type": "message", + "results": {} + } + ] + } + mock_response.raise_for_status = MagicMock() + + with patch.object( + noma_guardrail.async_handler, "post", return_value=mock_response + ) as mock_post: + result = await noma_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + assert result == request_data + mock_post.assert_called_once() + + # Verify the payload includes both system and user messages + call_args = mock_post.call_args + json_payload = call_args.kwargs["json"] + assert "input" in json_payload + messages = json_payload["input"] + + # Should have 2 messages: system and user + assert len(messages) == 2 + + # First message should be system + assert messages[0]["type"] == "message" + assert messages[0]["role"] == "system" + assert messages[0]["content"][0]["type"] == "input_text" + assert messages[0]["content"][0]["text"] == "You are a helpful assistant" + + # Second message should be user + assert messages[1]["type"] == "message" + assert messages[1]["role"] == "user" + assert messages[1]["content"][0]["type"] == "input_text" + assert messages[1]["content"][0]["text"] == "Hello, how are you?" + + @pytest.mark.asyncio + async def test_pre_call_hook_with_multiple_system_prompts( + self, noma_guardrail, mock_user_api_key_dict + ): + """Test pre-call hook combines multiple system prompts into single message""" + request_data = { + "messages": [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "system", "content": "You should be polite and respectful"}, + {"role": "user", "content": "Hello, how are you?"}, + ], + "litellm_call_id": "test-call-id", + } + + mock_response = MagicMock() + mock_response.json.return_value = { + "aggregatedScanResult": False, + "scanResult": [ + {"role": "system", "type": "message", "results": {}}, + {"role": "user", "type": "message", "results": {}} + ] + } + mock_response.raise_for_status = MagicMock() + + with patch.object( + noma_guardrail.async_handler, "post", return_value=mock_response + ) as mock_post: + result = await noma_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + assert result == request_data + mock_post.assert_called_once() + + # Verify the payload combines system prompts into single message + call_args = mock_post.call_args + json_payload = call_args.kwargs["json"] + messages = json_payload["input"] + + # Should have 2 messages: 1 combined system and 1 user + assert len(messages) == 2 + + # First message should be system with combined content + assert messages[0]["role"] == "system" + assert messages[0]["content"][0]["type"] == "input_text" + assert ( + messages[0]["content"][0]["text"] + == "You are a helpful assistant You should be polite and respectful" + ) + + # Second message should be user + assert messages[1]["role"] == "user" + assert messages[1]["content"][0]["type"] == "input_text" + assert messages[1]["content"][0]["text"] == "Hello, how are you?" + @pytest.mark.asyncio async def test_pre_call_hook_blocked( self, noma_guardrail, mock_user_api_key_dict, mock_request_data @@ -644,34 +770,6 @@ class TestNomaGuardrailHooks: assert result == mock_request_data - def test_extract_user_message(self, noma_guardrail): - data = { - "messages": [ - {"role": "system", "content": "System prompt"}, - {"role": "user", "content": "First user message"}, - {"role": "assistant", "content": "Assistant response"}, - {"role": "user", "content": "Second user message"}, - ] - } - - import asyncio - - message = asyncio.run(noma_guardrail._extract_user_message(data)) - assert message == [{"type": "input_text", "text": "Second user message"}] - - data = {"messages": [{"role": "system", "content": "System prompt"}]} - message = asyncio.run(noma_guardrail._extract_user_message(data)) - assert message is None - - data = {"messages": []} - message = asyncio.run(noma_guardrail._extract_user_message(data)) - assert message is None - - data = {} - message = asyncio.run(noma_guardrail._extract_user_message(data)) - assert message is None - - class TestBackgroundProcessing: """Test the new background processing functionality""" @@ -1025,57 +1123,66 @@ class TestNomaImageProcessing: metadata={}, ) - def test_extract_user_message_with_image_url(self, noma_guardrail): - """Test extracting user message with image_url content""" - import asyncio - - data = { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "image_url", - "image_url": { - "url": "https://example.com/image.jpg" - } - } - ] - } - ] - } + def test_extract_user_message_with_image_url(self): + """User message with only image_url becomes a single input_image content item.""" + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, + ) - message = asyncio.run(noma_guardrail._extract_user_message(data)) + handler = LiteLLMResponsesTransformationHandler() + messages: list[AllMessageValues] = [ + { + "role": "user", + "content": [ + { + "type": "image_url", + "image_url": { + "url": "https://example.com/image.jpg" + } + } + ] + } + ] + + input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages) + + assert len(input_items) == 1 + message = input_items[0]["content"] assert message is not None assert len(message) == 1 assert message[0]["type"] == "input_image" assert message[0]["image_url"] == "https://example.com/image.jpg" - def test_extract_user_message_with_mixed_content(self, noma_guardrail): - """Test extracting user message with mixed text and image content""" - import asyncio - - data = { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "What's in this image?" - }, - { - "type": "image_url", - "image_url": { - "url": "https://example.com/image.jpg" - } - } - ] - } - ] - } + def test_extract_user_message_with_mixed_content(self): + """User message with text + image becomes input_text then input_image in content list.""" + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, + ) - message = asyncio.run(noma_guardrail._extract_user_message(data)) + handler = LiteLLMResponsesTransformationHandler() + messages: list[AllMessageValues] = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What's in this image?", + }, + { + "type": "image_url", + "image_url": { + "url": "https://example.com/image.jpg" + } + } + ] + } + ] + + input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages) + + # Match the original assertions: `message` is the content list + assert len(input_items) == 1 + message = input_items[0]["content"] assert message is not None assert len(message) == 2 # First item should be text @@ -1085,37 +1192,43 @@ class TestNomaImageProcessing: assert message[1]["type"] == "input_image" assert message[1]["image_url"] == "https://example.com/image.jpg" - def test_extract_user_message_with_multiple_images(self, noma_guardrail): - """Test extracting user message with multiple images""" - import asyncio - - data = { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "Compare these images" - }, - { - "type": "image_url", - "image_url": { - "url": "https://example.com/image1.jpg" - } - }, - { - "type": "image_url", - "image_url": { - "url": "https://example.com/image2.jpg" - } - } - ] - } - ] - } + def test_extract_user_message_with_multiple_images(self): + """User message with multiple images becomes multiple input_image items.""" + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, + ) - message = asyncio.run(noma_guardrail._extract_user_message(data)) + handler = LiteLLMResponsesTransformationHandler() + + messages: list[AllMessageValues] = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Compare these images", + }, + { + "type": "image_url", + "image_url": { + "url": "https://example.com/image1.jpg" + } + }, + { + "type": "image_url", + "image_url": { + "url": "https://example.com/image2.jpg" + } + } + ] + } + ] + + input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages) + + # Match the original assertions + assert len(input_items) == 1 + message = input_items[0]["content"] assert message is not None assert len(message) == 3 assert message[0]["type"] == "input_text" @@ -1301,8 +1414,14 @@ class TestNomaImageProcessing: assert exc_info.value.status_code == 400 @pytest.mark.asyncio - async def test_image_with_base64_data(self, noma_guardrail): + async def test_image_with_base64_data( + self, noma_guardrail, mock_user_api_key_dict + ): """Test extracting image with base64 data URL""" + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, + ) + data = { "messages": [ { @@ -1319,7 +1438,13 @@ class TestNomaImageProcessing: ] } - message = await noma_guardrail._extract_user_message(data) + handler = LiteLLMResponsesTransformationHandler() + messages = cast(list[AllMessageValues], data["messages"]) + + input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages) + + assert len(input_items) == 1 + message = input_items[0]["content"] assert message is not None assert len(message) == 1 assert message[0]["type"] == "input_image" From 965406c643077dc5375a2c7bcd28c801caf807a6 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:56:47 -0300 Subject: [PATCH 240/349] feat(provider): add Z.AI (Zhipu AI) as built-in provider (#17307) * feat(provider): add Z.AI (Zhipu AI) as built-in provider Add support for Z.AI GLM models as a native OpenAI-compatible provider. - Add "zai" to openai_compatible_providers list - Add ZAI enum to LlmProviders - Add provider URL resolution for https://api.z.ai/api/paas/v4 - Add 8 GLM models with pricing to model cost maps: - glm-4.6 (200K context, $0.6/$2.2 per 1M tokens) - glm-4.5, glm-4.5v, glm-4.5-x, glm-4.5-air, glm-4.5-airx - glm-4-32b-0414-128k - glm-4.5-flash (free tier) - Add unit tests for provider integration Closes #17289 * docs: add Z.AI provider documentation - Add zai.md with usage examples, model list, and pricing - Add to sidebars.js navigation --- docs/my-website/docs/providers/zai.md | 135 ++++++++++++++++ docs/my-website/sidebars.js | 1 + litellm/constants.py | 1 + .../get_llm_provider_logic.py | 7 + ...odel_prices_and_context_window_backup.json | 89 +++++++++++ litellm/types/utils.py | 1 + model_prices_and_context_window.json | 89 +++++++++++ .../llms/zai/test_zai_provider.py | 144 ++++++++++++++++++ 8 files changed, 467 insertions(+) create mode 100644 docs/my-website/docs/providers/zai.md create mode 100644 tests/test_litellm/llms/zai/test_zai_provider.py diff --git a/docs/my-website/docs/providers/zai.md b/docs/my-website/docs/providers/zai.md new file mode 100644 index 00000000000..5055d0c1cdd --- /dev/null +++ b/docs/my-website/docs/providers/zai.md @@ -0,0 +1,135 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Z.AI (Zhipu AI) +https://z.ai/ + +**We support Z.AI GLM text/chat models, just set `zai/` as a prefix when sending completion requests** + +## API Key +```python +# env variable +os.environ['ZAI_API_KEY'] +``` + +## Sample Usage +```python +from litellm import completion +import os + +os.environ['ZAI_API_KEY'] = "" +response = completion( + model="zai/glm-4.6", + messages=[ + {"role": "user", "content": "hello from litellm"} + ], +) +print(response) +``` + +## Sample Usage - Streaming +```python +from litellm import completion +import os + +os.environ['ZAI_API_KEY'] = "" +response = completion( + model="zai/glm-4.6", + messages=[ + {"role": "user", "content": "hello from litellm"} + ], + stream=True +) + +for chunk in response: + print(chunk) +``` + +## Supported Models + +We support ALL Z.AI GLM models, just set `zai/` as a prefix when sending completion requests. + +| Model Name | Function Call | Notes | +|------------|---------------|-------| +| glm-4.6 | `completion(model="zai/glm-4.6", messages)` | Latest flagship model, 200K context | +| glm-4.5 | `completion(model="zai/glm-4.5", messages)` | 128K context | +| glm-4.5v | `completion(model="zai/glm-4.5v", messages)` | Vision model | +| glm-4.5-x | `completion(model="zai/glm-4.5-x", messages)` | Premium tier | +| glm-4.5-air | `completion(model="zai/glm-4.5-air", messages)` | Lightweight | +| glm-4.5-airx | `completion(model="zai/glm-4.5-airx", messages)` | Fast lightweight | +| glm-4-32b-0414-128k | `completion(model="zai/glm-4-32b-0414-128k", messages)` | 32B parameter model | +| glm-4.5-flash | `completion(model="zai/glm-4.5-flash", messages)` | **FREE tier** | + +## Model Pricing + +| Model | Input ($/1M tokens) | Output ($/1M tokens) | Context Window | +|-------|---------------------|----------------------|----------------| +| glm-4.6 | $0.60 | $2.20 | 200K | +| glm-4.5 | $0.60 | $2.20 | 128K | +| glm-4.5v | $0.60 | $1.80 | 128K | +| glm-4.5-x | $2.20 | $8.90 | 128K | +| glm-4.5-air | $0.20 | $1.10 | 128K | +| glm-4.5-airx | $1.10 | $4.50 | 128K | +| glm-4-32b-0414-128k | $0.10 | $0.10 | 128K | +| glm-4.5-flash | **FREE** | **FREE** | 128K | + +## Using with LiteLLM Proxy + + + + +```python +from litellm import completion +import os + +os.environ['ZAI_API_KEY'] = "" +response = completion( + model="zai/glm-4.6", + messages=[{"role": "user", "content": "Hello, how are you?"}], +) + +print(response.choices[0].message.content) +``` + + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: glm-4.6 + litellm_params: + model: zai/glm-4.6 + api_key: os.environ/ZAI_API_KEY + - model_name: glm-4.5-flash # Free tier + litellm_params: + model: zai/glm-4.5-flash + api_key: os.environ/ZAI_API_KEY +``` + +2. Run proxy + +```bash +litellm --config config.yaml +``` + +3. Test it! + +```bash +curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ + "model": "glm-4.6", + "messages": [ + { + "role": "user", + "content": "Hello, how are you?" + } + ] +}' +``` + + + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 2039d01186c..e467711b59d 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -656,6 +656,7 @@ const sidebars = { }, "providers/xai", "providers/xinference", + "providers/zai", ], }, { diff --git a/litellm/constants.py b/litellm/constants.py index 65de5d7b555..e3de7368c8a 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -555,6 +555,7 @@ openai_compatible_providers: List = [ "perplexity", "xinference", "xai", + "zai", "together_ai", "fireworks_ai", "empower", diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 4d29a74ddbc..b10011befcd 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -662,6 +662,13 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915 ) = litellm.XAIChatConfig()._get_openai_compatible_provider_info( api_base, api_key ) + elif custom_llm_provider == "zai": + api_base = ( + api_base + or get_secret_str("ZAI_API_BASE") + or "https://api.z.ai/api/paas/v4" + ) + dynamic_api_key = api_key or get_secret_str("ZAI_API_KEY") elif custom_llm_provider == "together_ai": api_base = ( api_base diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index af63d1e2592..9fdc1704f41 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -26855,6 +26855,95 @@ "supports_vision": true, "supports_web_search": true }, + "zai/glm-4.6": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.2e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.2e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5v": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 1.8e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-x": { + "input_cost_per_token": 2.2e-06, + "output_cost_per_token": 8.9e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-air": { + "input_cost_per_token": 2e-07, + "output_cost_per_token": 1.1e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-airx": { + "input_cost_per_token": 1.1e-06, + "output_cost_per_token": 4.5e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4-32b-0414-128k": { + "input_cost_per_token": 1e-07, + "output_cost_per_token": 1e-07, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-flash": { + "input_cost_per_token": 0, + "output_cost_per_token": 0, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, "vertex_ai/search_api": { "input_cost_per_query": 1.5e-03, "litellm_provider": "vertex_ai", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 2456c87044c..58267fdfea9 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2553,6 +2553,7 @@ class LlmProviders(str, Enum): OPENAI_LIKE = "openai_like" # embedding only JINA_AI = "jina_ai" XAI = "xai" + ZAI = "zai" CUSTOM_OPENAI = "custom_openai" TEXT_COMPLETION_OPENAI = "text-completion-openai" COHERE = "cohere" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 6b9b8beed80..1f8f1c7511e 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -26882,6 +26882,95 @@ "supports_vision": true, "supports_web_search": true }, + "zai/glm-4.6": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.2e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.2e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5v": { + "input_cost_per_token": 6e-07, + "output_cost_per_token": 1.8e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-x": { + "input_cost_per_token": 2.2e-06, + "output_cost_per_token": 8.9e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-air": { + "input_cost_per_token": 2e-07, + "output_cost_per_token": 1.1e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-airx": { + "input_cost_per_token": 1.1e-06, + "output_cost_per_token": 4.5e-06, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4-32b-0414-128k": { + "input_cost_per_token": 1e-07, + "output_cost_per_token": 1e-07, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, + "zai/glm-4.5-flash": { + "input_cost_per_token": 0, + "output_cost_per_token": 0, + "litellm_provider": "zai", + "max_input_tokens": 128000, + "max_output_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing" + }, "vertex_ai/search_api": { "input_cost_per_query": 1.5e-03, "litellm_provider": "vertex_ai", diff --git a/tests/test_litellm/llms/zai/test_zai_provider.py b/tests/test_litellm/llms/zai/test_zai_provider.py new file mode 100644 index 00000000000..a3d47d666bc --- /dev/null +++ b/tests/test_litellm/llms/zai/test_zai_provider.py @@ -0,0 +1,144 @@ +""" +Tests for Z.AI (Zhipu AI) provider - GLM models +""" +import json +import math + +import pytest +import respx + +import litellm +from litellm import completion +from litellm.cost_calculator import cost_per_token + + +@pytest.fixture +def zai_response(): + """Mock response from Z.AI API""" + return { + "id": "chatcmpl-zai-123", + "object": "chat.completion", + "created": 1677652288, + "model": "glm-4.6", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello! How can I help you today?"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 10, "completion_tokens": 15, "total_tokens": 25}, + } + + +def test_get_llm_provider_zai(): + """Test that get_llm_provider correctly identifies zai provider""" + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, api_key, api_base = get_llm_provider("zai/glm-4.6") + assert model == "glm-4.6" + assert provider == "zai" + assert api_base == "https://api.z.ai/api/paas/v4" + + +def test_zai_in_provider_lists(): + """Test that zai is registered in all necessary provider lists""" + assert "zai" in litellm.openai_compatible_providers + assert "zai" in litellm.provider_list + + +def test_zai_models_in_model_cost(): + """Test that ZAI models are in the model cost map""" + import os + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + zai_models = [ + "zai/glm-4.6", + "zai/glm-4.5", + "zai/glm-4.5v", + "zai/glm-4.5-x", + "zai/glm-4.5-air", + "zai/glm-4.5-airx", + "zai/glm-4-32b-0414-128k", + "zai/glm-4.5-flash", + ] + + for model in zai_models: + assert model in litellm.model_cost, f"Model {model} not found in model_cost" + assert litellm.model_cost[model]["litellm_provider"] == "zai" + + +def test_zai_glm46_cost_calculation(): + """Test the cost calculation for glm-4.6""" + import os + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + key = "zai/glm-4.6" + info = litellm.model_cost[key] + + prompt_cost, completion_cost = cost_per_token( + model="zai/glm-4.6", + prompt_tokens=1000000, # 1M tokens + completion_tokens=1000000, + ) + + # GLM-4.6: $0.6/M input, $2.2/M output + assert math.isclose(prompt_cost, 0.6, rel_tol=1e-6) + assert math.isclose(completion_cost, 2.2, rel_tol=1e-6) + + +def test_zai_flash_model_is_free(): + """Test that glm-4.5-flash has zero cost""" + import os + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + key = "zai/glm-4.5-flash" + info = litellm.model_cost[key] + + assert info["input_cost_per_token"] == 0 + assert info["output_cost_per_token"] == 0 + + +@pytest.mark.asyncio +async def test_zai_completion_call(respx_mock, zai_response, monkeypatch): + """Test completion call with zai provider using mocked response""" + monkeypatch.setenv("ZAI_API_KEY", "test-api-key") + litellm.disable_aiohttp_transport = True + + respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(json=zai_response) + + response = await litellm.acompletion( + model="zai/glm-4.6", + messages=[{"role": "user", "content": "Hello"}], + max_tokens=20, + ) + + assert response.choices[0].message.content == "Hello! How can I help you today?" + assert response.usage.total_tokens == 25 + + assert len(respx_mock.calls) == 1 + request = respx_mock.calls[0].request + assert request.method == "POST" + assert "api.z.ai" in str(request.url) + assert "Authorization" in request.headers + assert request.headers["Authorization"] == "Bearer test-api-key" + + +def test_zai_sync_completion(respx_mock, zai_response, monkeypatch): + """Test synchronous completion call""" + monkeypatch.setenv("ZAI_API_KEY", "test-api-key") + litellm.disable_aiohttp_transport = True + + respx_mock.post("https://api.z.ai/api/paas/v4/chat/completions").respond(json=zai_response) + + response = completion( + model="zai/glm-4.6", + messages=[{"role": "user", "content": "Hello"}], + max_tokens=20, + ) + + assert response.choices[0].message.content == "Hello! How can I help you today?" + assert response.usage.total_tokens == 25 From 01dfc3561acb1baf60209786fc24e19d77384b08 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:58:27 -0300 Subject: [PATCH 241/349] Fix AttributeError when metadata is null in request body (#17263) (#17306) Handle the case where metadata is explicitly set to null/None in the request body. This was causing a 401 error with "'NoneType' object has no attribute 'get'" when calling /v1/batches with metadata: null. The fix uses `or {}` instead of a default dict value since the key exists but has a None value. --- .../proxy/common_utils/http_parsing_utils.py | 2 +- .../common_utils/test_http_parsing_utils.py | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 59b3ec20b4a..259755f5ef9 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -309,7 +309,7 @@ def get_tags_from_request_body(request_body: dict) -> List[str]: List of tag names (strings), empty list if no valid tags found """ metadata_variable_name = get_metadata_variable_name_from_kwargs(request_body) - metadata = request_body.get(metadata_variable_name, {}) + metadata = request_body.get(metadata_variable_name) or {} tags_in_metadata: Any = metadata.get("tags", []) tags_in_request_body: Any = request_body.get("tags", []) combined_tags: List[str] = [] diff --git a/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py b/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py index 85858866dda..2361decc5af 100644 --- a/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py +++ b/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py @@ -606,8 +606,27 @@ def test_get_tags_from_request_body_with_dict_tags(): } } } - + result = get_tags_from_request_body(request_body=request_body) - + + assert result == [] + assert isinstance(result, list) + + +def test_get_tags_from_request_body_with_null_metadata(): + """ + Test that function handles null metadata gracefully without crashing. + + This is a regression test for https://github.com/BerriAI/litellm/issues/17263 + When metadata is explicitly set to null/None, the function should return + an empty list instead of raising AttributeError. + """ + request_body = { + "model": "gpt-4", + "metadata": None # OpenAI API accepts metadata: null + } + + result = get_tags_from_request_body(request_body=request_body) + assert result == [] assert isinstance(result, list) From 860270a7927b0d13319bead2fdff9a4c00e3d00f Mon Sep 17 00:00:00 2001 From: Saar wintrov Date: Tue, 2 Dec 2025 06:01:36 +0200 Subject: [PATCH 242/349] SSO: Clear sso integration for all users (#17287) --- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../static/chunks/1518-21c80a799b5c426e.js | 1 - .../static/chunks/1518-4475f8385da5ac78.js | 1 + ...6050ee3518d4.js => 1529-59ce29afdf8ccc9b.js} | 2 +- ...971a192714f2.js => 1674-de8248fbd0c554ba.js} | 2 +- .../static/chunks/1973-26a414084f96c69b.js | 1 + .../static/chunks/1994-6637a121c9ee1602.js | 1 - .../static/chunks/1994-a4d0b99849c16b62.js | 1 + .../static/chunks/2004-294ce010a90069b4.js | 1 + .../static/chunks/2004-8b1ad3d8c195646a.js | 1 - .../static/chunks/2012-9200c205d5b0405a.js | 1 - .../static/chunks/2012-c09fa25a9cbf6028.js | 1 + .../static/chunks/2019-15183fcc4c29249f.js | 1 - .../static/chunks/2249-01a36f26b1cecba3.js | 1 + .../static/chunks/2249-3e3c0a9e241e35dc.js | 1 - ...0eb77e9f4fa7.js => 3250-6c57da6c11f342fa.js} | 2 +- .../static/chunks/3325-4a3c766c7d12465e.js | 1 - .../static/chunks/3341-852c4599adcc0f2b.js | 1 + ...9f5df18d8716.js => 3705-124a560b74decaa8.js} | 2 +- .../static/chunks/3801-9878b21c4f9ae250.js | 1 - .../static/chunks/3801-ff2404f6d0c38247.js | 1 + .../static/chunks/4182-1ec11708566c0483.js | 1 - .../static/chunks/4267-eb59bdfbffb79a80.js | 1 - .../static/chunks/4292-28669d6dfecbbf62.js | 1 + .../static/chunks/4292-913ecd28879b76a8.js | 1 - .../static/chunks/4612-06e9d10957e990c0.js | 1 + .../_next/static/chunks/475-3985fee235e827f8.js | 1 + .../static/chunks/4865-c1c0885a93c327fa.js | 1 - .../static/chunks/5074-51f1824c21869900.js | 1 - .../static/chunks/5096-d9222b69b30b3d56.js | 1 + ...9ffa75db75f8.js => 5170-eddf033da66a3d25.js} | 2 +- .../_next/static/chunks/544-3d98fdc8d64554e8.js | 1 - .../static/chunks/5572-9290ae3dc2551207.js | 1 - .../static/chunks/5572-d4f8dc9b2bf09618.js | 1 + .../static/chunks/5830-30dbbe6913297258.js | 1 + ...68ba6ad0ce0c.js => 5869-99bf8c2997f4811f.js} | 2 +- .../static/chunks/5945-8b3b7713d7f416a2.js | 1 + .../_next/static/chunks/605-102c0e6d8bb7517c.js | 1 + .../static/chunks/6062-89f63f71675c6a08.js | 1 - .../static/chunks/6264-a48a17494c2e1d26.js | 1 - .../_next/static/chunks/630-1e0342aa26bb0fe8.js | 1 - .../_next/static/chunks/630-f305780b75c36612.js | 1 + ...e6266dea9539.js => 6600-1c55511ad9da9e4d.js} | 2 +- .../static/chunks/6609-3e081758ffbe3786.js | 1 - .../static/chunks/6609-d93906f43161f066.js | 1 + .../_next/static/chunks/667-213a9fbd82e0ada7.js | 1 + .../static/chunks/6843-98abf1271c25c6e0.js | 1 + .../static/chunks/6843-b8ebdf2bb4fe5c67.js | 1 - .../static/chunks/7155-1a3e4c5a6aefae2b.js | 1 - .../static/chunks/7155-459bc53437553b96.js | 1 + .../static/chunks/7164-8de9ea967cd5d031.js | 1 + .../static/chunks/7164-b089dfb991cc1d8c.js | 1 - .../static/chunks/7187-d4c57193fb558148.js | 1 + ...1f347ea9707a.js => 7526-9d5ec51e0920ffc6.js} | 2 +- .../static/chunks/7641-f70830b7a61a3f9c.js | 1 - .../static/chunks/7641-fa9cc1f68c670e1c.js | 1 + ...579c5c97ecaba.js => 773-b02e89f4d1193982.js} | 2 +- ...16ddcb35e063.js => 7975-d5ed9d0e73f8f3a9.js} | 2 +- .../static/chunks/8008-851877152eb2be38.js | 1 - .../static/chunks/8541-04c822145b2301f8.js | 1 + .../static/chunks/8661-1cf4178f6bffc981.js | 1 - .../static/chunks/9028-2bfc9f09930a0d61.js | 1 - .../static/chunks/9111-3cb8240098962e8a.js | 1 - .../static/chunks/9111-9b9192c9fb4809ff.js | 1 + ...dfb8fa3d2ed7.js => 9611-8bd2ffcee22edc34.js} | 2 +- .../static/chunks/9798-a47f1a4423863a8a.js | 1 + .../static/chunks/9877-f58702e3cb433729.js | 1 - .../static/chunks/9877-ff2a01b39a318119.js | 1 + .../api-reference/page-6ead8448e1510439.js | 1 - .../api-reference/page-efca3b67652c1db6.js | 1 + .../api-playground/page-8047d2cef33b9999.js | 1 - .../api-playground/page-e66957ea53741305.js | 1 + ...cab0cb418464.js => page-349dab403faa8586.js} | 2 +- ...6b7e7489b565.js => page-29593a3a38ff72cd.js} | 2 +- ...6697f4d6f550.js => page-392368af0265ebf3.js} | 2 +- .../prompts/page-843a18f5283af912.js | 1 - .../prompts/page-a188489df21ffc96.js | 1 + ...5395c754c862.js => page-04b44e5847f0e275.js} | 2 +- ...350eb16ca3ba.js => page-df254f7363ecac47.js} | 2 +- .../app/(dashboard)/layout-a0258e2243643336.js | 1 + .../app/(dashboard)/layout-a928c135835301f0.js | 1 - .../(dashboard)/logs/page-24f7ccafa5658895.js | 1 + .../(dashboard)/logs/page-974be1d69803befc.js | 1 - ...b2d51a5f5567.js => page-cb5b5c184df1920f.js} | 2 +- .../page-7526ca663daec9bf.js | 1 + .../page-a11b969ee66b82c0.js | 1 - ...977ecb7e4aea.js => page-c5c54ec599dda90a.js} | 2 +- ...e021acdc06f3.js => page-f66c8c75efc80fa3.js} | 2 +- .../admin-settings/page-41bcefda7b19fcbe.js | 1 - .../admin-settings/page-b14017f2434341b6.js | 1 + ...c91c28de94ff.js => page-73e2aa132fcafea5.js} | 2 +- .../router-settings/page-7e77ec8e3ff58278.js | 1 - .../router-settings/page-ce416427bf19a1dc.js | 1 + ...0a8b43105b4e.js => page-e723d4c81fc7d9a9.js} | 2 +- ...d0b0d541c84f.js => page-464d4ef166df7211.js} | 2 +- ...2c9c481d0375.js => page-4dd219948b528c92.js} | 2 +- ...df83dfce71fa.js => page-4a1119ecd30d2b39.js} | 2 +- ...498724555fa1.js => page-c4aed80b18ca0651.js} | 2 +- ...feee0752e151.js => page-2098a2b6e214223c.js} | 2 +- .../(dashboard)/users/page-607b92cfac56e9f9.js | 1 - .../(dashboard)/users/page-80eaf816a6ca5c75.js | 1 + .../virtual-keys/page-52c22b525906afcf.js | 1 + .../virtual-keys/page-681e2e7643e3068c.js | 1 - .../chunks/app/layout-4e0c2c971ccc1e6d.js | 1 - .../chunks/app/layout-5681449b28aa197a.js | 1 + ...c0d632ab220d.js => page-e50863ece139886b.js} | 2 +- ...17915c7f9cff.js => page-ca976de28014d49a.js} | 2 +- ...536062f9ecd9.js => page-623abbf7f2315887.js} | 2 +- .../app/onboarding/page-6f2572027a406495.js | 1 + .../app/onboarding/page-7cc24917468a90ab.js | 1 - .../static/chunks/app/page-28eb040917ca1710.js | 1 + .../static/chunks/app/page-dda848d817541095.js | 1 - ...04ee9adf.js => main-app-ce1f29ef0860719b.js} | 2 +- ...ed3b4a921.js => webpack-134f5d194761e240.js} | 2 +- .../out/_next/static/css/0fc668a8750043fe.css | 1 + .../proxy/_experimental/out/api-reference.html | 1 - .../proxy/_experimental/out/api-reference.txt | 17 ++++++++--------- .../_experimental/out/api-reference/index.html | 1 + .../out/experimental/api-playground.html | 2 +- .../out/experimental/api-playground.txt | 17 ++++++++--------- .../_experimental/out/experimental/budgets.html | 2 +- .../_experimental/out/experimental/budgets.txt | 17 ++++++++--------- .../_experimental/out/experimental/caching.html | 2 +- .../_experimental/out/experimental/caching.txt | 17 ++++++++--------- .../out/experimental/old-usage.html | 2 +- .../out/experimental/old-usage.txt | 17 ++++++++--------- .../_experimental/out/experimental/prompts.html | 2 +- .../_experimental/out/experimental/prompts.txt | 17 ++++++++--------- .../out/experimental/tag-management.html | 2 +- .../out/experimental/tag-management.txt | 17 ++++++++--------- litellm/proxy/_experimental/out/guardrails.html | 1 - litellm/proxy/_experimental/out/guardrails.txt | 17 ++++++++--------- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 11 +++++------ litellm/proxy/_experimental/out/logs.html | 1 - litellm/proxy/_experimental/out/logs.txt | 17 ++++++++--------- litellm/proxy/_experimental/out/logs/index.html | 1 + .../_experimental/out/mcp/oauth/callback.html | 2 +- .../_experimental/out/mcp/oauth/callback.txt | 7 +++---- litellm/proxy/_experimental/out/model-hub.html | 1 - litellm/proxy/_experimental/out/model-hub.txt | 17 ++++++++--------- .../_experimental/out/model-hub/index.html | 1 + litellm/proxy/_experimental/out/model_hub.txt | 7 +++---- .../_experimental/out/model_hub_table.html | 1 - .../proxy/_experimental/out/model_hub_table.txt | 7 +++---- .../out/model_hub_table/index.html | 1 + .../_experimental/out/models-and-endpoints.html | 1 - .../_experimental/out/models-and-endpoints.txt | 17 ++++++++--------- .../out/models-and-endpoints/index.html | 1 + litellm/proxy/_experimental/out/onboarding.html | 1 - litellm/proxy/_experimental/out/onboarding.txt | 7 +++---- .../proxy/_experimental/out/organizations.html | 1 - .../proxy/_experimental/out/organizations.txt | 17 ++++++++--------- .../_experimental/out/organizations/index.html | 1 + litellm/proxy/_experimental/out/playground.html | 1 - litellm/proxy/_experimental/out/playground.txt | 17 ++++++++--------- .../_experimental/out/playground/index.html | 1 + .../out/settings/admin-settings.html | 2 +- .../out/settings/admin-settings.txt | 17 ++++++++--------- .../out/settings/logging-and-alerts.html | 2 +- .../out/settings/logging-and-alerts.txt | 17 ++++++++--------- .../out/settings/router-settings.html | 2 +- .../out/settings/router-settings.txt | 17 ++++++++--------- .../_experimental/out/settings/ui-theme.html | 2 +- .../_experimental/out/settings/ui-theme.txt | 17 ++++++++--------- litellm/proxy/_experimental/out/teams.html | 1 - litellm/proxy/_experimental/out/teams.txt | 17 ++++++++--------- .../proxy/_experimental/out/teams/index.html | 1 + litellm/proxy/_experimental/out/test-key.html | 1 - litellm/proxy/_experimental/out/test-key.txt | 17 ++++++++--------- .../proxy/_experimental/out/test-key/index.html | 1 + .../_experimental/out/tools/mcp-servers.html | 2 +- .../_experimental/out/tools/mcp-servers.txt | 17 ++++++++--------- .../_experimental/out/tools/vector-stores.html | 2 +- .../_experimental/out/tools/vector-stores.txt | 17 ++++++++--------- litellm/proxy/_experimental/out/usage.html | 1 - litellm/proxy/_experimental/out/usage.txt | 17 ++++++++--------- .../proxy/_experimental/out/usage/index.html | 1 + litellm/proxy/_experimental/out/users.html | 1 - litellm/proxy/_experimental/out/users.txt | 17 ++++++++--------- .../proxy/_experimental/out/users/index.html | 1 + .../proxy/_experimental/out/virtual-keys.html | 1 - .../proxy/_experimental/out/virtual-keys.txt | 17 ++++++++--------- .../_experimental/out/virtual-keys/index.html | 1 + ui/litellm-dashboard/src/components/admins.tsx | 2 +- 186 files changed, 309 insertions(+), 339 deletions(-) rename litellm/proxy/_experimental/out/_next/static/{V73dwfVXi9kkAaHXHHR5u => 6DVsIIQxhiSKdAYpN-pIf}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{V73dwfVXi9kkAaHXHHR5u => 6DVsIIQxhiSKdAYpN-pIf}/_ssgManifest.js (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1518-4475f8385da5ac78.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1529-aa686050ee3518d4.js => 1529-59ce29afdf8ccc9b.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{1674-475a971a192714f2.js => 1674-de8248fbd0c554ba.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1973-26a414084f96c69b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2004-294ce010a90069b4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2004-8b1ad3d8c195646a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2012-c09fa25a9cbf6028.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2019-15183fcc4c29249f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3250-d3d70eb77e9f4fa7.js => 3250-6c57da6c11f342fa.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3341-852c4599adcc0f2b.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3705-05649f5df18d8716.js => 3705-124a560b74decaa8.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3801-9878b21c4f9ae250.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3801-ff2404f6d0c38247.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4182-1ec11708566c0483.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4267-eb59bdfbffb79a80.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4292-28669d6dfecbbf62.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4292-913ecd28879b76a8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4612-06e9d10957e990c0.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/475-3985fee235e827f8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4865-c1c0885a93c327fa.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5074-51f1824c21869900.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5096-d9222b69b30b3d56.js rename litellm/proxy/_experimental/out/_next/static/chunks/{5170-56859ffa75db75f8.js => 5170-eddf033da66a3d25.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/544-3d98fdc8d64554e8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5572-9290ae3dc2551207.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5572-d4f8dc9b2bf09618.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5830-30dbbe6913297258.js rename litellm/proxy/_experimental/out/_next/static/chunks/{5869-426268ba6ad0ce0c.js => 5869-99bf8c2997f4811f.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5945-8b3b7713d7f416a2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/605-102c0e6d8bb7517c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6062-89f63f71675c6a08.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6264-a48a17494c2e1d26.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/630-1e0342aa26bb0fe8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/630-f305780b75c36612.js rename litellm/proxy/_experimental/out/_next/static/chunks/{6600-3c16e6266dea9539.js => 6600-1c55511ad9da9e4d.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6609-3e081758ffbe3786.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6609-d93906f43161f066.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/667-213a9fbd82e0ada7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6843-98abf1271c25c6e0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6843-b8ebdf2bb4fe5c67.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7155-1a3e4c5a6aefae2b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7155-459bc53437553b96.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7164-8de9ea967cd5d031.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7164-b089dfb991cc1d8c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7187-d4c57193fb558148.js rename litellm/proxy/_experimental/out/_next/static/chunks/{7526-e29a1f347ea9707a.js => 7526-9d5ec51e0920ffc6.js} (51%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7641-f70830b7a61a3f9c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7641-fa9cc1f68c670e1c.js rename litellm/proxy/_experimental/out/_next/static/chunks/{773-870579c5c97ecaba.js => 773-b02e89f4d1193982.js} (74%) rename litellm/proxy/_experimental/out/_next/static/chunks/{7975-afe816ddcb35e063.js => 7975-d5ed9d0e73f8f3a9.js} (65%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8008-851877152eb2be38.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8541-04c822145b2301f8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8661-1cf4178f6bffc981.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9028-2bfc9f09930a0d61.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9111-3cb8240098962e8a.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9111-9b9192c9fb4809ff.js rename litellm/proxy/_experimental/out/_next/static/chunks/{9611-e0c4dfb8fa3d2ed7.js => 9611-8bd2ffcee22edc34.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9798-a47f1a4423863a8a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9877-f58702e3cb433729.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9877-ff2a01b39a318119.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/api-reference/page-6ead8448e1510439.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/api-reference/page-efca3b67652c1db6.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-8047d2cef33b9999.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-e66957ea53741305.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/{page-3234cab0cb418464.js => page-349dab403faa8586.js} (96%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/{page-0a286b7e7489b565.js => page-29593a3a38ff72cd.js} (92%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/{page-bdfb6697f4d6f550.js => page-392368af0265ebf3.js} (98%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-843a18f5283af912.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-a188489df21ffc96.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/{page-e5395395c754c862.js => page-04b44e5847f0e275.js} (96%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/{page-fbd5350eb16ca3ba.js => page-df254f7363ecac47.js} (93%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-a0258e2243643336.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-a928c135835301f0.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/page-24f7ccafa5658895.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/page-974be1d69803befc.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/{page-a4e1b2d51a5f5567.js => page-cb5b5c184df1920f.js} (95%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-7526ca663daec9bf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-a11b969ee66b82c0.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/{page-c9a9977ecb7e4aea.js => page-c5c54ec599dda90a.js} (97%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/{page-6729e021acdc06f3.js => page-f66c8c75efc80fa3.js} (97%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-41bcefda7b19fcbe.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-b14017f2434341b6.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/{page-2470c91c28de94ff.js => page-73e2aa132fcafea5.js} (97%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-7e77ec8e3ff58278.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-ce416427bf19a1dc.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/{page-ee5d0a8b43105b4e.js => page-e723d4c81fc7d9a9.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/{page-866cd0b0d541c84f.js => page-464d4ef166df7211.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/{page-69022c9c481d0375.js => page-4dd219948b528c92.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/{page-9474df83dfce71fa.js => page-4a1119ecd30d2b39.js} (93%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/{page-6ddf498724555fa1.js => page-c4aed80b18ca0651.js} (96%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/usage/{page-6882feee0752e151.js => page-2098a2b6e214223c.js} (95%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-607b92cfac56e9f9.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-80eaf816a6ca5c75.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/page-52c22b525906afcf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/page-681e2e7643e3068c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-4e0c2c971ccc1e6d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-5681449b28aa197a.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/mcp/oauth/callback/{page-4cdcc0d632ab220d.js => page-e50863ece139886b.js} (89%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/{page-16d517915c7f9cff.js => page-ca976de28014d49a.js} (84%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/{page-e60a536062f9ecd9.js => page-623abbf7f2315887.js} (95%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-6f2572027a406495.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-7cc24917468a90ab.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-28eb040917ca1710.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-dda848d817541095.js rename litellm/proxy/_experimental/out/_next/static/chunks/{main-app-77a6ca3c04ee9adf.js => main-app-ce1f29ef0860719b.js} (81%) rename litellm/proxy/_experimental/out/_next/static/chunks/{webpack-db32e14ed3b4a921.js => webpack-134f5d194761e240.js} (77%) create mode 100644 litellm/proxy/_experimental/out/_next/static/css/0fc668a8750043fe.css delete mode 100644 litellm/proxy/_experimental/out/api-reference.html create mode 100644 litellm/proxy/_experimental/out/api-reference/index.html delete mode 100644 litellm/proxy/_experimental/out/guardrails.html delete mode 100644 litellm/proxy/_experimental/out/logs.html create mode 100644 litellm/proxy/_experimental/out/logs/index.html delete mode 100644 litellm/proxy/_experimental/out/model-hub.html create mode 100644 litellm/proxy/_experimental/out/model-hub/index.html delete mode 100644 litellm/proxy/_experimental/out/model_hub_table.html create mode 100644 litellm/proxy/_experimental/out/model_hub_table/index.html delete mode 100644 litellm/proxy/_experimental/out/models-and-endpoints.html create mode 100644 litellm/proxy/_experimental/out/models-and-endpoints/index.html delete mode 100644 litellm/proxy/_experimental/out/onboarding.html delete mode 100644 litellm/proxy/_experimental/out/organizations.html create mode 100644 litellm/proxy/_experimental/out/organizations/index.html delete mode 100644 litellm/proxy/_experimental/out/playground.html create mode 100644 litellm/proxy/_experimental/out/playground/index.html delete mode 100644 litellm/proxy/_experimental/out/teams.html create mode 100644 litellm/proxy/_experimental/out/teams/index.html delete mode 100644 litellm/proxy/_experimental/out/test-key.html create mode 100644 litellm/proxy/_experimental/out/test-key/index.html delete mode 100644 litellm/proxy/_experimental/out/usage.html create mode 100644 litellm/proxy/_experimental/out/usage/index.html delete mode 100644 litellm/proxy/_experimental/out/users.html create mode 100644 litellm/proxy/_experimental/out/users/index.html delete mode 100644 litellm/proxy/_experimental/out/virtual-keys.html create mode 100644 litellm/proxy/_experimental/out/virtual-keys/index.html diff --git a/litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/6DVsIIQxhiSKdAYpN-pIf/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/6DVsIIQxhiSKdAYpN-pIf/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/6DVsIIQxhiSKdAYpN-pIf/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/V73dwfVXi9kkAaHXHHR5u/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/6DVsIIQxhiSKdAYpN-pIf/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js b/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js deleted file mode 100644 index 52544b97059..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1518-21c80a799b5c426e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1518],{81518:function(e,s,a){a.r(s),a.d(s,{default:function(){return Y}});var t=a(57437),r=a(2265),n=a(85572),l=a(93837),i=a(37592),o=a(4260),d=a(99981),c=a(5545),m=a(26430),u=a(96473),x=a(9114),h=a(10703),p=a(95459),g=a(32489),f=a(98728),v=a(62831),j=a(17906),y=a(94263),b=a(79862),N=a(82222),k=a(51817),w=a(94331),A=a(38398),S=a(33152);function C(e){let{messages:s,isLoading:a}=e;if(0===s.length)return(0,t.jsx)("div",{className:"h-full"});let r=[],n=0;for(;n(0,t.jsx)("div",{className:"whitespace-pre-wrap break-words",style:{wordWrap:"break-word",overflowWrap:"break-word",wordBreak:"break-word",hyphens:"auto"},children:(0,t.jsx)(v.UG,{components:{code(e){let{node:s,inline:a,className:r,children:n,...l}=e,i=/language-(\w+)/.exec(r||"");return!a&&i?(0,t.jsx)(j.Z,{style:y.Z,language:i[1],PreTag:"div",className:"rounded-md my-2",wrapLines:!0,wrapLongLines:!0,...l,children:String(n).replace(/\n$/,"")}):(0,t.jsx)("code",{className:"".concat(r," px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono"),...l,children:n})},pre:e=>{let{node:s,...a}=e;return(0,t.jsx)("pre",{style:{overflowX:"auto",maxWidth:"100%"},...a})}},children:"string"==typeof e.content?e.content:""})});return(0,t.jsxs)("div",{className:"flex flex-col gap-6 min-w-0 w-full p-4",children:[r.map((e,s)=>{let n=e.assistant,i=(null==n?void 0:n.model)||"Assistant";return(0,t.jsxs)("div",{className:"space-y-4",children:[e.user&&(0,t.jsxs)("div",{className:"space-y-2 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 text-blue-600",children:(0,t.jsx)(b.Z,{size:16})}),(0,t.jsx)("div",{className:"text-sm font-semibold text-gray-700",children:"You"})]}),l(e.user)]}),(0,t.jsx)("div",{className:"border-t border-gray-200"}),n?(0,t.jsxs)("div",{className:"space-y-3 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600",children:(0,t.jsx)(N.Z,{size:16})}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-semibold text-gray-700",children:i}),n.toolName&&(0,t.jsx)("span",{className:"rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600",children:n.toolName})]})]}),n.reasoningContent&&(0,t.jsx)(w.Z,{reasoningContent:n.reasoningContent}),n.searchResults&&(0,t.jsx)(S.J,{searchResults:n.searchResults}),l(n),(n.timeToFirstToken||n.totalLatency||n.usage)&&(0,t.jsx)(A.Z,{timeToFirstToken:n.timeToFirstToken,totalLatency:n.totalLatency,usage:n.usage,toolName:n.toolName})]}):a&&s===r.length-1?(0,t.jsxs)("div",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]}):(0,t.jsx)("div",{className:"text-sm text-gray-500",children:"Waiting for a response..."})]},s)}),a&&0===r.length&&(0,t.jsxs)("div",{className:"flex items-center gap-2 text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]})]})}var T=a(31283);function P(e){let{value:s,onChange:a,models:n,loading:l,disabled:o}=e,[d,c]=(0,r.useState)(!1),[m,u]=(0,r.useState)(""),x=(0,r.useMemo)(()=>Array.from(new Set(n)).sort(),[n]),h=(0,r.useMemo)(()=>s&&!x.includes(s)?[s,...x]:x,[x,s]),p=d?"__custom__":s||void 0,g=()=>{let e=m.trim();if(!e){c(!1),u("");return}a(e),c(!1),u("")};return(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsxs)(i.default,{value:p,onChange:e=>{if("__custom__"===e){c(!0),s&&!x.includes(s)?u(s):u("");return}c(!1),u(""),a(e)},disabled:o,loading:l,placeholder:l?"Loading models...":"Select a model",className:"w-full rounded-md",showSearch:!0,optionFilterProp:"children",children:[h.map(e=>(0,t.jsx)(i.default.Option,{value:e,children:e},e)),(0,t.jsx)(i.default.Option,{value:"__custom__",children:"+ Add custom model"})]}),d&&(0,t.jsx)(T.o,{className:"mt-2",placeholder:"Custom Model Name (Enter to add)",value:m,onValueChange:u,onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),g())},onBlur:g,autoFocus:!0})]})}var Z=a(99020),_=a(97415),L=a(67479),E=a(4156),M=a(23496),I=a(85847),O=a(79326);function R(e){let{comparison:s,onUpdate:a,onRemove:n,canRemove:l,modelOptions:i,isLoadingModels:o,apiKey:d}=e,[c,m]=(0,r.useState)(!1),u=e=>{e?a({applyAcrossModels:!0,temperature:s.temperature,maxTokens:s.maxTokens,tags:[...s.tags],vectorStores:[...s.vectorStores],guardrails:[...s.guardrails],useAdvancedParams:s.useAdvancedParams},{applyToAll:!0,keysToApply:["temperature","maxTokens","tags","vectorStores","guardrails","useAdvancedParams"]}):a({applyAcrossModels:!1})},x=e=>{a({useAdvancedParams:e},s.applyAcrossModels?{applyToAll:!0,keysToApply:["useAdvancedParams"]}:void 0)},h=(e,t)=>{a({[e]:t},s.applyAcrossModels?{applyToAll:!0,keysToApply:[e]}:void 0)},p=s.useAdvancedParams?1:.4,v=s.useAdvancedParams?"text-gray-700":"text-gray-400",j=()=>{m(e=>!e)},y=(0,t.jsxs)("div",{className:"w-[300px] max-h-[65vh] overflow-y-auto relative",children:[(0,t.jsx)("button",{onClick:()=>{m(!1)},className:"absolute top-0 right-0 p-1 hover:bg-gray-100 rounded transition-colors text-gray-500 hover:text-gray-700 z-10",children:(0,t.jsx)(g.Z,{size:14})}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(E.Z,{checked:s.applyAcrossModels,onChange:e=>u(e.target.checked),children:(0,t.jsx)("span",{className:"text-xs font-medium",children:"Sync Settings Across Models"})})}),(0,t.jsx)(M.Z,{className:"border-gray-200"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"General Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Tags"}),(0,t.jsx)(Z.Z,{value:s.tags,onChange:e=>h("tags",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Vector Stores"}),(0,t.jsx)(_.Z,{value:s.vectorStores,onChange:e=>h("vectorStores",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Guardrails"}),(0,t.jsx)(L.Z,{value:s.guardrails,onChange:e=>h("guardrails",e),accessToken:d})]})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"Advanced Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2 pb-1",children:(0,t.jsx)(E.Z,{checked:s.useAdvancedParams,onChange:e=>x(e.target.checked),children:(0,t.jsx)("span",{className:"text-sm font-medium",children:"Use Advanced Parameters"})})}),(0,t.jsxs)("div",{className:"space-y-2 transition-opacity duration-200",style:{opacity:p},children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Temperature"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.temperature.toFixed(2)})]}),(0,t.jsx)(I.Z,{min:0,max:2,step:.01,value:s.temperature,onChange:e=>{h("temperature",Math.min(2,Math.max(0,Number((Array.isArray(e)?e[0]:e).toFixed(2)))))},disabled:!s.useAdvancedParams})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Max Tokens"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.maxTokens})]}),(0,t.jsx)(I.Z,{min:1,max:32768,step:1,value:s.maxTokens,onChange:e=>{h("maxTokens",Math.min(32768,Math.max(1,Math.round(Array.isArray(e)?e[0]:e))))},disabled:!s.useAdvancedParams})]})]})]})]})]})]});return(0,t.jsxs)("div",{className:"bg-white first:border-l-0 border-l border-gray-200 flex flex-col min-h-0",children:[(0,t.jsxs)("div",{className:"border-b flex items-center justify-between gap-3 px-4 py-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 flex-1",children:[(0,t.jsx)(P,{value:s.model,models:i,loading:o,onChange:e=>a({model:e})}),(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(O.Z,{content:y,trigger:[],open:c,onOpenChange:()=>{},placement:"bottomRight",destroyTooltipOnHide:!1,children:(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),j()},className:"p-2 rounded-lg transition-colors ".concat(c?"bg-gray-200 text-gray-700":"hover:bg-gray-100 text-gray-600"),children:(0,t.jsx)(f.Z,{size:18})})})})]}),l&&(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),n()},className:"p-2 hover:bg-red-50 text-red-600 rounded-lg transition-colors",children:(0,t.jsx)(g.Z,{size:18})})]}),(0,t.jsx)("div",{className:"relative flex-1 flex flex-col min-h-0",children:(0,t.jsx)("div",{className:"flex-1 max-h-[calc(100vh-385px)] overflow-auto rounded-b-2xl",children:(0,t.jsx)(C,{messages:s.messages,isLoading:s.isLoading})})})]})}var z=a(79276);let{TextArea:U}=o.default;function K(e){let{value:s,onChange:a,onSend:r,disabled:n}=e;return(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsxs)("div",{className:"flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]",children:[(0,t.jsx)(U,{value:s,onChange:e=>a(e.target.value),onKeyDown:e=>{"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),!n&&s.trim()&&r())},placeholder:"Type your message... (Shift+Enter for new line)",disabled:n,className:"flex-1",autoSize:{minRows:1,maxRows:4},style:{resize:"none",border:"none",boxShadow:"none",background:"transparent",padding:"4px 0",fontSize:"14px",lineHeight:"20px"}}),(0,t.jsx)(c.ZP,{onClick:r,disabled:n||!s.trim(),icon:(0,t.jsx)(z.Z,{}),shape:"circle"})]})})}let B=["Can you summarize the key points?","What assumptions did you make?","What are the next steps?"],D=["Write me a poem","Explain quantum computing","Draft a polite email requesting a meeting"],F="/v1/chat/completions";function W(e){let{accessToken:s,disabledPersonalKeyCreation:a}=e,[n,g]=(0,r.useState)([{id:"1",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1},{id:"2",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1}]),[f,v]=(0,r.useState)([]),[j,y]=(0,r.useState)(!1),[b,N]=(0,r.useState)(""),[k,w]=(0,r.useState)(a?"custom":"session"),[A,S]=(0,r.useState)(""),[C,T]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=setTimeout(()=>{T(A)},300);return()=>clearTimeout(e)},[A]);let P=(0,r.useMemo)(()=>"session"===k?s||"":C.trim(),[k,s,C]),Z=(0,r.useMemo)(()=>n.length>0&&n.every(e=>!e.isLoading&&e.messages.some(e=>"assistant"===e.role)),[n]);(0,r.useEffect)(()=>{let e=!0;return(async()=>{if(!P){v([]);return}y(!0);try{let s=await (0,h.p)(P);if(!e)return;let a=Array.from(new Set(s.map(e=>e.model_group)));v(a)}catch(s){console.error("CompareUI: failed to fetch models",s),e&&v([])}finally{e&&y(!1)}})(),()=>{e=!1}},[P]),(0,r.useEffect)(()=>{0!==f.length&&g(e=>e.map((e,s)=>{var a,t,r,n,l;return{...e,temperature:null!==(a=e.temperature)&&void 0!==a?a:1,maxTokens:null!==(t=e.maxTokens)&&void 0!==t?t:2048,applyAcrossModels:null!==(r=e.applyAcrossModels)&&void 0!==r&&r,useAdvancedParams:null!==(n=e.useAdvancedParams)&&void 0!==n&&n,...e.model?{}:{model:null!==(l=f[s%f.length])&&void 0!==l?l:""}}}))},[f]);let _=e=>{n.length>1&&g(s=>s.filter(s=>s.id!==e))},L=(e,s,a)=>{g(t=>{var r;if((null==a?void 0:a.applyToAll)&&(null===(r=a.keysToApply)||void 0===r?void 0:r.length)){let r={};a.keysToApply.forEach(e=>{let a=s[e];void 0!==a&&(r[e]=Array.isArray(a)?[...a]:a)});let n=Object.keys(r).length>0;return t.map(a=>a.id===e?{...a,...s}:n?{...a,...r}:a)}return t.map(a=>a.id===e?{...a,...s}:a)})},E=(e,s,a)=>{s&&g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];if(n&&"assistant"===n.role){var l;let e="string"==typeof n.content?n.content:"";r[r.length-1]={...n,content:e+s,model:null!==(l=n.model)&&void 0!==l?l:a}}else r.push({role:"assistant",content:s,model:a});return{...t,messages:r}}))},M=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,reasoningContent:(r.reasoningContent||"")+s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",reasoningContent:s}),{...a,messages:t}}))},I=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,timeToFirstToken:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",timeToFirstToken:s}),{...a,messages:t}}))},O=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,totalLatency:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",totalLatency:s}),{...a,messages:t}}))},z=(e,s,a)=>{g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];return n&&"assistant"===n.role&&(r[r.length-1]={...n,usage:s,toolName:a}),{...t,messages:r}}))},U=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role&&(t[t.length-1]={...r,searchResults:s}),{...a,messages:t}}))},W=!!s,G=e=>{let s=e.trim();if(!s)return;if(!P){x.Z.fromBackend("Please provide an API key or select Current UI Session");return}if(0===n.length)return;if(n.some(e=>!e.model)){x.Z.fromBackend("Select a model before sending a message.");return}let a=new Map;n.forEach(e=>{var t;let r=null!==(t=e.traceId)&&void 0!==t?t:(0,l.Z)();a.set(e.id,{id:e.id,model:e.model,traceId:r,tags:e.tags,vectorStores:e.vectorStores,guardrails:e.guardrails,temperature:e.temperature,maxTokens:e.maxTokens,messages:[...e.messages,{role:"user",content:s}]})}),0!==a.size&&(g(e=>e.map(e=>{let s=a.get(e.id);return s?{...e,traceId:s.traceId,messages:s.messages,isLoading:!0}:e})),a.forEach(e=>{var s;let a=e.messages.map(e=>{let{role:s,content:a}=e;return{role:s,content:"string"==typeof a?a:""}}),t=e.tags.length>0?e.tags:void 0,r=e.vectorStores.length>0?e.vectorStores:void 0,l=e.guardrails.length>0?e.guardrails:void 0,i=n.find(s=>s.id===e.id),o=null!==(s=null==i?void 0:i.useAdvancedParams)&&void 0!==s&&s;(0,p.n)(a,(s,a)=>E(e.id,s,a),e.model,P,t,void 0,s=>M(e.id,s),s=>I(e.id,s),s=>z(e.id,s),e.traceId,r,l,void 0,void 0,s=>U(e.id,s),o?e.temperature:void 0,o?e.maxTokens:void 0,s=>O(e.id,s)).catch(s=>{let a=s instanceof Error?s.message:String(s);console.error("CompareUI: failed to fetch response",s),x.Z.fromBackend(a),g(s=>s.map(s=>{if(s.id!==e.id)return s;let t=[...s.messages],r=t[t.length-1],n=r&&"assistant"===r.role&&"string"==typeof r.content?r.content:"";return r&&"assistant"===r.role?t[t.length-1]={...r,content:n?"".concat(n,"\nError fetching response: ").concat(a):"Error fetching response: ".concat(a)}:t.push({role:"assistant",content:"Error fetching response: ".concat(a)}),{...s,messages:t}}))}).finally(()=>{g(s=>s.map(s=>s.id===e.id?{...s,isLoading:!1}:s))})}))},V=e=>{N(e)},X=n.some(e=>e.messages.length>0),Y=n.some(e=>e.isLoading);return(0,t.jsx)("div",{className:"w-full h-full p-4 bg-white",children:(0,t.jsxs)("div",{className:"rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-140px)] flex flex-col",children:[(0,t.jsx)("div",{className:"border-b px-4 py-2",children:(0,t.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"API Key Source"}),(0,t.jsxs)(i.default,{value:k,onChange:e=>w(e),disabled:a,className:"w-48",children:[(0,t.jsx)(i.default.Option,{value:"session",disabled:!W,children:"Current UI Session"}),(0,t.jsx)(i.default.Option,{value:"custom",children:"Virtual Key"})]}),"custom"===k&&(0,t.jsx)(o.default.Password,{value:A,onChange:e=>S(e.target.value),placeholder:"Enter API key",className:"w-56"})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"Endpoint"}),(0,t.jsx)(d.Z,{title:"Other endpoints will be available soon",children:(0,t.jsx)(i.default,{value:F,disabled:!0,className:"w-56",children:(0,t.jsx)(i.default.Option,{value:F,children:F})})})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(c.ZP,{onClick:()=>{g(e=>e.map(e=>({...e,messages:[],traceId:void 0,isLoading:!1}))),N("")},disabled:!X,icon:(0,t.jsx)(m.Z,{}),children:"Clear All Chats"}),(0,t.jsx)(d.Z,{title:n.length>=3?"Compare up to 3 models at a time":"Add another comparison",children:(0,t.jsx)(c.ZP,{onClick:()=>{var e;if(n.length>=3)return;let s=null!==(e=f[n.length%(f.length||1)])&&void 0!==e?e:"",a={id:Date.now().toString(),model:s,messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1};g(e=>[...e,a])},disabled:n.length>=3,icon:(0,t.jsx)(u.Z,{}),children:"Add Comparison"})})]})]})}),(0,t.jsx)("div",{className:"grid flex-1 min-h-0 auto-rows-[minmax(0,1fr)]",style:{gridTemplateColumns:"repeat(".concat(n.length,", minmax(0, 1fr))")},children:n.map(e=>(0,t.jsx)(R,{comparison:e,onUpdate:(s,a)=>L(e.id,s,a),onRemove:()=>_(e.id),canRemove:n.length>1,modelOptions:f,isLoadingModels:j,apiKey:P},e.id))}),(0,t.jsx)("div",{className:"flex justify-center pb-4",children:(0,t.jsx)("div",{className:"w-full max-w-3xl px-4",children:(0,t.jsxs)("div",{className:"border border-gray-200 shadow-lg rounded-xl bg-white p-4",children:[(0,t.jsx)("div",{className:"flex items-center justify-between gap-4 mb-3 min-h-8",children:X||Y?Z?(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:B.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))}):Y?(0,t.jsxs)("span",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)("span",{className:"h-2 w-2 rounded-full bg-blue-500 animate-pulse","aria-hidden":!0}),"Gathering responses from all models..."]}):(0,t.jsx)("span",{className:"text-sm text-gray-500",children:"Send a prompt to compare models"}):(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:D.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))})}),(0,t.jsx)(K,{value:b,onChange:e=>{N(e)},onSend:()=>{G(b),N("")},disabled:0===n.length||n.every(e=>e.isLoading)})]})})})]})})}var G=a(58643),V=a(80443),X=a(91624);function Y(){let{accessToken:e,userRole:s,userId:a,disabledPersonalKeyCreation:l,token:i}=(0,V.Z)(),[o,d]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{(async()=>{if(e){let s=await (0,X.C)(e);s&&d({PROXY_BASE_URL:s.PROXY_BASE_URL,LITELLM_UI_API_DOC_BASE_URL:s.LITELLM_UI_API_DOC_BASE_URL})}})()},[e]),(0,t.jsxs)(G.v0,{className:"h-full w-full",children:[(0,t.jsxs)(G.td,{className:"mb-0",children:[(0,t.jsx)(G.OK,{children:"Chat"}),(0,t.jsx)(G.OK,{children:"Compare"})]}),(0,t.jsxs)(G.nP,{className:"h-full",children:[(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(n.Z,{accessToken:e,token:i,userRole:s,userID:a,disabledPersonalKeyCreation:l,proxySettings:o})}),(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(W,{accessToken:e,disabledPersonalKeyCreation:l})})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1518-4475f8385da5ac78.js b/litellm/proxy/_experimental/out/_next/static/chunks/1518-4475f8385da5ac78.js new file mode 100644 index 00000000000..476fabcb02f --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1518-4475f8385da5ac78.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1518],{81518:function(e,s,a){a.r(s),a.d(s,{default:function(){return Y}});var t=a(57437),r=a(2265),n=a(85572),l=a(93837),i=a(37592),o=a(4260),d=a(99981),c=a(5545),m=a(26430),u=a(96473),x=a(9114),h=a(10703),p=a(95459),g=a(32489),f=a(98728),v=a(62831),j=a(17906),y=a(94263),b=a(79862),N=a(82222),k=a(51817),w=a(94331),A=a(38398),S=a(33152);function C(e){let{messages:s,isLoading:a}=e;if(0===s.length)return(0,t.jsx)("div",{className:"h-full"});let r=[],n=0;for(;n(0,t.jsx)("div",{className:"whitespace-pre-wrap break-words",style:{wordWrap:"break-word",overflowWrap:"break-word",wordBreak:"break-word",hyphens:"auto"},children:(0,t.jsx)(v.UG,{components:{code(e){let{node:s,inline:a,className:r,children:n,...l}=e,i=/language-(\w+)/.exec(r||"");return!a&&i?(0,t.jsx)(j.Z,{style:y.Z,language:i[1],PreTag:"div",className:"rounded-md my-2",wrapLines:!0,wrapLongLines:!0,...l,children:String(n).replace(/\n$/,"")}):(0,t.jsx)("code",{className:"".concat(r," px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono"),...l,children:n})},pre:e=>{let{node:s,...a}=e;return(0,t.jsx)("pre",{style:{overflowX:"auto",maxWidth:"100%"},...a})}},children:"string"==typeof e.content?e.content:""})});return(0,t.jsxs)("div",{className:"flex flex-col gap-6 min-w-0 w-full p-4",children:[r.map((e,s)=>{let n=e.assistant,i=(null==n?void 0:n.model)||"Assistant";return(0,t.jsxs)("div",{className:"space-y-4",children:[e.user&&(0,t.jsxs)("div",{className:"space-y-2 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 text-blue-600",children:(0,t.jsx)(b.Z,{size:16})}),(0,t.jsx)("div",{className:"text-sm font-semibold text-gray-700",children:"You"})]}),l(e.user)]}),(0,t.jsx)("div",{className:"border-t border-gray-200"}),n?(0,t.jsxs)("div",{className:"space-y-3 min-w-0",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600",children:(0,t.jsx)(N.Z,{size:16})}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-semibold text-gray-700",children:i}),n.toolName&&(0,t.jsx)("span",{className:"rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600",children:n.toolName})]})]}),n.reasoningContent&&(0,t.jsx)(w.Z,{reasoningContent:n.reasoningContent}),n.searchResults&&(0,t.jsx)(S.J,{searchResults:n.searchResults}),l(n),(n.timeToFirstToken||n.totalLatency||n.usage)&&(0,t.jsx)(A.Z,{timeToFirstToken:n.timeToFirstToken,totalLatency:n.totalLatency,usage:n.usage,toolName:n.toolName})]}):a&&s===r.length-1?(0,t.jsxs)("div",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]}):(0,t.jsx)("div",{className:"text-sm text-gray-500",children:"Waiting for a response..."})]},s)}),a&&0===r.length&&(0,t.jsxs)("div",{className:"flex items-center gap-2 text-gray-500",children:[(0,t.jsx)(k.Z,{size:18,className:"animate-spin"}),(0,t.jsx)("span",{children:"Generating response..."})]})]})}var T=a(31283);function P(e){let{value:s,onChange:a,models:n,loading:l,disabled:o}=e,[d,c]=(0,r.useState)(!1),[m,u]=(0,r.useState)(""),x=(0,r.useMemo)(()=>Array.from(new Set(n)).sort(),[n]),h=(0,r.useMemo)(()=>s&&!x.includes(s)?[s,...x]:x,[x,s]),p=d?"__custom__":s||void 0,g=()=>{let e=m.trim();if(!e){c(!1),u("");return}a(e),c(!1),u("")};return(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsxs)(i.default,{value:p,onChange:e=>{if("__custom__"===e){c(!0),s&&!x.includes(s)?u(s):u("");return}c(!1),u(""),a(e)},disabled:o,loading:l,placeholder:l?"Loading models...":"Select a model",className:"w-full rounded-md",showSearch:!0,optionFilterProp:"children",children:[h.map(e=>(0,t.jsx)(i.default.Option,{value:e,children:e},e)),(0,t.jsx)(i.default.Option,{value:"__custom__",children:"+ Add custom model"})]}),d&&(0,t.jsx)(T.o,{className:"mt-2",placeholder:"Custom Model Name (Enter to add)",value:m,onValueChange:u,onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),g())},onBlur:g,autoFocus:!0})]})}var Z=a(99020),_=a(97415),L=a(67479),E=a(61994),M=a(23496),I=a(85847),O=a(79326);function R(e){let{comparison:s,onUpdate:a,onRemove:n,canRemove:l,modelOptions:i,isLoadingModels:o,apiKey:d}=e,[c,m]=(0,r.useState)(!1),u=e=>{e?a({applyAcrossModels:!0,temperature:s.temperature,maxTokens:s.maxTokens,tags:[...s.tags],vectorStores:[...s.vectorStores],guardrails:[...s.guardrails],useAdvancedParams:s.useAdvancedParams},{applyToAll:!0,keysToApply:["temperature","maxTokens","tags","vectorStores","guardrails","useAdvancedParams"]}):a({applyAcrossModels:!1})},x=e=>{a({useAdvancedParams:e},s.applyAcrossModels?{applyToAll:!0,keysToApply:["useAdvancedParams"]}:void 0)},h=(e,t)=>{a({[e]:t},s.applyAcrossModels?{applyToAll:!0,keysToApply:[e]}:void 0)},p=s.useAdvancedParams?1:.4,v=s.useAdvancedParams?"text-gray-700":"text-gray-400",j=()=>{m(e=>!e)},y=(0,t.jsxs)("div",{className:"w-[300px] max-h-[65vh] overflow-y-auto relative",children:[(0,t.jsx)("button",{onClick:()=>{m(!1)},className:"absolute top-0 right-0 p-1 hover:bg-gray-100 rounded transition-colors text-gray-500 hover:text-gray-700 z-10",children:(0,t.jsx)(g.Z,{size:14})}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(E.Z,{checked:s.applyAcrossModels,onChange:e=>u(e.target.checked),children:(0,t.jsx)("span",{className:"text-xs font-medium",children:"Sync Settings Across Models"})})}),(0,t.jsx)(M.Z,{className:"border-gray-200"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"General Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Tags"}),(0,t.jsx)(Z.Z,{value:s.tags,onChange:e=>h("tags",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Vector Stores"}),(0,t.jsx)(_.Z,{value:s.vectorStores,onChange:e=>h("vectorStores",e),accessToken:d})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"text-xs font-medium text-gray-600 block mb-0.5",children:"Guardrails"}),(0,t.jsx)(L.Z,{value:s.guardrails,onChange:e=>h("guardrails",e),accessToken:d})]})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-xs font-semibold text-gray-700 mb-1.5 uppercase tracking-wide",children:"Advanced Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("div",{className:"flex items-center gap-2 pb-1",children:(0,t.jsx)(E.Z,{checked:s.useAdvancedParams,onChange:e=>x(e.target.checked),children:(0,t.jsx)("span",{className:"text-sm font-medium",children:"Use Advanced Parameters"})})}),(0,t.jsxs)("div",{className:"space-y-2 transition-opacity duration-200",style:{opacity:p},children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Temperature"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.temperature.toFixed(2)})]}),(0,t.jsx)(I.Z,{min:0,max:2,step:.01,value:s.temperature,onChange:e=>{h("temperature",Math.min(2,Math.max(0,Number((Array.isArray(e)?e[0]:e).toFixed(2)))))},disabled:!s.useAdvancedParams})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,t.jsx)("label",{className:"text-xs font-medium ".concat(v),children:"Max Tokens"}),(0,t.jsx)("span",{className:"text-xs ".concat(v),children:s.maxTokens})]}),(0,t.jsx)(I.Z,{min:1,max:32768,step:1,value:s.maxTokens,onChange:e=>{h("maxTokens",Math.min(32768,Math.max(1,Math.round(Array.isArray(e)?e[0]:e))))},disabled:!s.useAdvancedParams})]})]})]})]})]})]});return(0,t.jsxs)("div",{className:"bg-white first:border-l-0 border-l border-gray-200 flex flex-col min-h-0",children:[(0,t.jsxs)("div",{className:"border-b flex items-center justify-between gap-3 px-4 py-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 flex-1",children:[(0,t.jsx)(P,{value:s.model,models:i,loading:o,onChange:e=>a({model:e})}),(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)(O.Z,{content:y,trigger:[],open:c,onOpenChange:()=>{},placement:"bottomRight",destroyTooltipOnHide:!1,children:(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),j()},className:"p-2 rounded-lg transition-colors ".concat(c?"bg-gray-200 text-gray-700":"hover:bg-gray-100 text-gray-600"),children:(0,t.jsx)(f.Z,{size:18})})})})]}),l&&(0,t.jsx)("button",{onClick:e=>{e.stopPropagation(),n()},className:"p-2 hover:bg-red-50 text-red-600 rounded-lg transition-colors",children:(0,t.jsx)(g.Z,{size:18})})]}),(0,t.jsx)("div",{className:"relative flex-1 flex flex-col min-h-0",children:(0,t.jsx)("div",{className:"flex-1 max-h-[calc(100vh-385px)] overflow-auto rounded-b-2xl",children:(0,t.jsx)(C,{messages:s.messages,isLoading:s.isLoading})})})]})}var z=a(79276);let{TextArea:U}=o.default;function K(e){let{value:s,onChange:a,onSend:r,disabled:n}=e;return(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsxs)("div",{className:"flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]",children:[(0,t.jsx)(U,{value:s,onChange:e=>a(e.target.value),onKeyDown:e=>{"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),!n&&s.trim()&&r())},placeholder:"Type your message... (Shift+Enter for new line)",disabled:n,className:"flex-1",autoSize:{minRows:1,maxRows:4},style:{resize:"none",border:"none",boxShadow:"none",background:"transparent",padding:"4px 0",fontSize:"14px",lineHeight:"20px"}}),(0,t.jsx)(c.ZP,{onClick:r,disabled:n||!s.trim(),icon:(0,t.jsx)(z.Z,{}),shape:"circle"})]})})}let B=["Can you summarize the key points?","What assumptions did you make?","What are the next steps?"],D=["Write me a poem","Explain quantum computing","Draft a polite email requesting a meeting"],F="/v1/chat/completions";function W(e){let{accessToken:s,disabledPersonalKeyCreation:a}=e,[n,g]=(0,r.useState)([{id:"1",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1},{id:"2",model:"",messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1}]),[f,v]=(0,r.useState)([]),[j,y]=(0,r.useState)(!1),[b,N]=(0,r.useState)(""),[k,w]=(0,r.useState)(a?"custom":"session"),[A,S]=(0,r.useState)(""),[C,T]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=setTimeout(()=>{T(A)},300);return()=>clearTimeout(e)},[A]);let P=(0,r.useMemo)(()=>"session"===k?s||"":C.trim(),[k,s,C]),Z=(0,r.useMemo)(()=>n.length>0&&n.every(e=>!e.isLoading&&e.messages.some(e=>"assistant"===e.role)),[n]);(0,r.useEffect)(()=>{let e=!0;return(async()=>{if(!P){v([]);return}y(!0);try{let s=await (0,h.p)(P);if(!e)return;let a=Array.from(new Set(s.map(e=>e.model_group)));v(a)}catch(s){console.error("CompareUI: failed to fetch models",s),e&&v([])}finally{e&&y(!1)}})(),()=>{e=!1}},[P]),(0,r.useEffect)(()=>{0!==f.length&&g(e=>e.map((e,s)=>{var a,t,r,n,l;return{...e,temperature:null!==(a=e.temperature)&&void 0!==a?a:1,maxTokens:null!==(t=e.maxTokens)&&void 0!==t?t:2048,applyAcrossModels:null!==(r=e.applyAcrossModels)&&void 0!==r&&r,useAdvancedParams:null!==(n=e.useAdvancedParams)&&void 0!==n&&n,...e.model?{}:{model:null!==(l=f[s%f.length])&&void 0!==l?l:""}}}))},[f]);let _=e=>{n.length>1&&g(s=>s.filter(s=>s.id!==e))},L=(e,s,a)=>{g(t=>{var r;if((null==a?void 0:a.applyToAll)&&(null===(r=a.keysToApply)||void 0===r?void 0:r.length)){let r={};a.keysToApply.forEach(e=>{let a=s[e];void 0!==a&&(r[e]=Array.isArray(a)?[...a]:a)});let n=Object.keys(r).length>0;return t.map(a=>a.id===e?{...a,...s}:n?{...a,...r}:a)}return t.map(a=>a.id===e?{...a,...s}:a)})},E=(e,s,a)=>{s&&g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];if(n&&"assistant"===n.role){var l;let e="string"==typeof n.content?n.content:"";r[r.length-1]={...n,content:e+s,model:null!==(l=n.model)&&void 0!==l?l:a}}else r.push({role:"assistant",content:s,model:a});return{...t,messages:r}}))},M=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,reasoningContent:(r.reasoningContent||"")+s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",reasoningContent:s}),{...a,messages:t}}))},I=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,timeToFirstToken:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",timeToFirstToken:s}),{...a,messages:t}}))},O=(e,s)=>{g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role?t[t.length-1]={...r,totalLatency:s}:r&&"user"===r.role&&t.push({role:"assistant",content:"",totalLatency:s}),{...a,messages:t}}))},z=(e,s,a)=>{g(t=>t.map(t=>{if(t.id!==e)return t;let r=[...t.messages],n=r[r.length-1];return n&&"assistant"===n.role&&(r[r.length-1]={...n,usage:s,toolName:a}),{...t,messages:r}}))},U=(e,s)=>{s&&g(a=>a.map(a=>{if(a.id!==e)return a;let t=[...a.messages],r=t[t.length-1];return r&&"assistant"===r.role&&(t[t.length-1]={...r,searchResults:s}),{...a,messages:t}}))},W=!!s,G=e=>{let s=e.trim();if(!s)return;if(!P){x.Z.fromBackend("Please provide an API key or select Current UI Session");return}if(0===n.length)return;if(n.some(e=>!e.model)){x.Z.fromBackend("Select a model before sending a message.");return}let a=new Map;n.forEach(e=>{var t;let r=null!==(t=e.traceId)&&void 0!==t?t:(0,l.Z)();a.set(e.id,{id:e.id,model:e.model,traceId:r,tags:e.tags,vectorStores:e.vectorStores,guardrails:e.guardrails,temperature:e.temperature,maxTokens:e.maxTokens,messages:[...e.messages,{role:"user",content:s}]})}),0!==a.size&&(g(e=>e.map(e=>{let s=a.get(e.id);return s?{...e,traceId:s.traceId,messages:s.messages,isLoading:!0}:e})),a.forEach(e=>{var s;let a=e.messages.map(e=>{let{role:s,content:a}=e;return{role:s,content:"string"==typeof a?a:""}}),t=e.tags.length>0?e.tags:void 0,r=e.vectorStores.length>0?e.vectorStores:void 0,l=e.guardrails.length>0?e.guardrails:void 0,i=n.find(s=>s.id===e.id),o=null!==(s=null==i?void 0:i.useAdvancedParams)&&void 0!==s&&s;(0,p.n)(a,(s,a)=>E(e.id,s,a),e.model,P,t,void 0,s=>M(e.id,s),s=>I(e.id,s),s=>z(e.id,s),e.traceId,r,l,void 0,void 0,s=>U(e.id,s),o?e.temperature:void 0,o?e.maxTokens:void 0,s=>O(e.id,s)).catch(s=>{let a=s instanceof Error?s.message:String(s);console.error("CompareUI: failed to fetch response",s),x.Z.fromBackend(a),g(s=>s.map(s=>{if(s.id!==e.id)return s;let t=[...s.messages],r=t[t.length-1],n=r&&"assistant"===r.role&&"string"==typeof r.content?r.content:"";return r&&"assistant"===r.role?t[t.length-1]={...r,content:n?"".concat(n,"\nError fetching response: ").concat(a):"Error fetching response: ".concat(a)}:t.push({role:"assistant",content:"Error fetching response: ".concat(a)}),{...s,messages:t}}))}).finally(()=>{g(s=>s.map(s=>s.id===e.id?{...s,isLoading:!1}:s))})}))},V=e=>{N(e)},X=n.some(e=>e.messages.length>0),Y=n.some(e=>e.isLoading);return(0,t.jsx)("div",{className:"w-full h-full p-4 bg-white",children:(0,t.jsxs)("div",{className:"rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-140px)] flex flex-col",children:[(0,t.jsx)("div",{className:"border-b px-4 py-2",children:(0,t.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"API Key Source"}),(0,t.jsxs)(i.default,{value:k,onChange:e=>w(e),disabled:a,className:"w-48",children:[(0,t.jsx)(i.default.Option,{value:"session",disabled:!W,children:"Current UI Session"}),(0,t.jsx)(i.default.Option,{value:"custom",children:"Virtual Key"})]}),"custom"===k&&(0,t.jsx)(o.default.Password,{value:A,onChange:e=>S(e.target.value),placeholder:"Enter API key",className:"w-56"})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-600",children:"Endpoint"}),(0,t.jsx)(d.Z,{title:"Other endpoints will be available soon",children:(0,t.jsx)(i.default,{value:F,disabled:!0,className:"w-56",children:(0,t.jsx)(i.default.Option,{value:F,children:F})})})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(c.ZP,{onClick:()=>{g(e=>e.map(e=>({...e,messages:[],traceId:void 0,isLoading:!1}))),N("")},disabled:!X,icon:(0,t.jsx)(m.Z,{}),children:"Clear All Chats"}),(0,t.jsx)(d.Z,{title:n.length>=3?"Compare up to 3 models at a time":"Add another comparison",children:(0,t.jsx)(c.ZP,{onClick:()=>{var e;if(n.length>=3)return;let s=null!==(e=f[n.length%(f.length||1)])&&void 0!==e?e:"",a={id:Date.now().toString(),model:s,messages:[],isLoading:!1,tags:[],mcpTools:[],vectorStores:[],guardrails:[],temperature:1,maxTokens:2048,applyAcrossModels:!1,useAdvancedParams:!1};g(e=>[...e,a])},disabled:n.length>=3,icon:(0,t.jsx)(u.Z,{}),children:"Add Comparison"})})]})]})}),(0,t.jsx)("div",{className:"grid flex-1 min-h-0 auto-rows-[minmax(0,1fr)]",style:{gridTemplateColumns:"repeat(".concat(n.length,", minmax(0, 1fr))")},children:n.map(e=>(0,t.jsx)(R,{comparison:e,onUpdate:(s,a)=>L(e.id,s,a),onRemove:()=>_(e.id),canRemove:n.length>1,modelOptions:f,isLoadingModels:j,apiKey:P},e.id))}),(0,t.jsx)("div",{className:"flex justify-center pb-4",children:(0,t.jsx)("div",{className:"w-full max-w-3xl px-4",children:(0,t.jsxs)("div",{className:"border border-gray-200 shadow-lg rounded-xl bg-white p-4",children:[(0,t.jsx)("div",{className:"flex items-center justify-between gap-4 mb-3 min-h-8",children:X||Y?Z?(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:B.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))}):Y?(0,t.jsxs)("span",{className:"flex items-center gap-2 text-sm text-gray-500",children:[(0,t.jsx)("span",{className:"h-2 w-2 rounded-full bg-blue-500 animate-pulse","aria-hidden":!0}),"Gathering responses from all models..."]}):(0,t.jsx)("span",{className:"text-sm text-gray-500",children:"Send a prompt to compare models"}):(0,t.jsx)("div",{className:"flex items-center gap-2 overflow-x-auto",children:D.map(e=>(0,t.jsx)("button",{type:"button",onClick:()=>V(e),className:"shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 cursor-pointer",children:e},e))})}),(0,t.jsx)(K,{value:b,onChange:e=>{N(e)},onSend:()=>{G(b),N("")},disabled:0===n.length||n.every(e=>e.isLoading)})]})})})]})})}var G=a(58643),V=a(39760),X=a(91624);function Y(){let{accessToken:e,userRole:s,userId:a,disabledPersonalKeyCreation:l,token:i}=(0,V.Z)(),[o,d]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{(async()=>{if(e){let s=await (0,X.C)(e);s&&d({PROXY_BASE_URL:s.PROXY_BASE_URL,LITELLM_UI_API_DOC_BASE_URL:s.LITELLM_UI_API_DOC_BASE_URL})}})()},[e]),(0,t.jsxs)(G.v0,{className:"h-full w-full",children:[(0,t.jsxs)(G.td,{className:"mb-0",children:[(0,t.jsx)(G.OK,{children:"Chat"}),(0,t.jsx)(G.OK,{children:"Compare"})]}),(0,t.jsxs)(G.nP,{className:"h-full",children:[(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(n.Z,{accessToken:e,token:i,userRole:s,userID:a,disabledPersonalKeyCreation:l,proxySettings:o})}),(0,t.jsx)(G.x4,{className:"h-full",children:(0,t.jsx)(W,{accessToken:e,disabledPersonalKeyCreation:l})})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1529-aa686050ee3518d4.js b/litellm/proxy/_experimental/out/_next/static/chunks/1529-59ce29afdf8ccc9b.js similarity index 99% rename from litellm/proxy/_experimental/out/_next/static/chunks/1529-aa686050ee3518d4.js rename to litellm/proxy/_experimental/out/_next/static/chunks/1529-59ce29afdf8ccc9b.js index e0d9b0cf48c..c98ed86dee5 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1529-aa686050ee3518d4.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1529-59ce29afdf8ccc9b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1529],{39760:function(e,n,t){t.d(n,{Z:function(){return a}});var r=t(1119),o=t(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"ellipsis",theme:"outlined"},l=t(55015),a=o.forwardRef(function(e,n){return o.createElement(l.Z,(0,r.Z)({},e,{ref:n,icon:i}))})},77565:function(e,n,t){t.d(n,{Z:function(){return a}});var r=t(1119),o=t(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"},l=t(55015),a=o.forwardRef(function(e,n){return o.createElement(l.Z,(0,r.Z)({},e,{ref:n,icon:i}))})},71030:function(e,n,t){t.d(n,{Z:function(){return C}});var r=t(1119),o=t(11993),i=t(26365),l=t(6989),a=t(97821),u=t(36760),c=t.n(u),s=t(28791),f=t(2265),d=t(95814),p=t(53346),v=d.Z.ESC,m=d.Z.TAB,b=(0,f.forwardRef)(function(e,n){var t=e.overlay,r=e.arrow,o=e.prefixCls,i=(0,f.useMemo)(function(){return"function"==typeof t?t():t},[t]),l=(0,s.sQ)(n,(0,s.C4)(i));return f.createElement(f.Fragment,null,r&&f.createElement("div",{className:"".concat(o,"-arrow")}),f.cloneElement(i,{ref:(0,s.Yr)(i)?l:void 0}))}),y={adjustX:1,adjustY:1},h=[0,0],g={topLeft:{points:["bl","tl"],overflow:y,offset:[0,-4],targetOffset:h},top:{points:["bc","tc"],overflow:y,offset:[0,-4],targetOffset:h},topRight:{points:["br","tr"],overflow:y,offset:[0,-4],targetOffset:h},bottomLeft:{points:["tl","bl"],overflow:y,offset:[0,4],targetOffset:h},bottom:{points:["tc","bc"],overflow:y,offset:[0,4],targetOffset:h},bottomRight:{points:["tr","br"],overflow:y,offset:[0,4],targetOffset:h}},Z=["arrow","prefixCls","transitionName","animation","align","placement","placements","getPopupContainer","showAction","hideAction","overlayClassName","overlayStyle","visible","trigger","autoFocus","overlay","children","onVisibleChange"],C=f.forwardRef(function(e,n){var t,u,d,y,h,C,E,w,k,M,R,x,N,P,S=e.arrow,I=void 0!==S&&S,K=e.prefixCls,A=void 0===K?"rc-dropdown":K,O=e.transitionName,T=e.animation,L=e.align,D=e.placement,_=e.placements,V=e.getPopupContainer,z=e.showAction,F=e.hideAction,j=e.overlayClassName,B=e.overlayStyle,W=e.visible,H=e.trigger,Y=void 0===H?["hover"]:H,q=e.autoFocus,X=e.overlay,G=e.children,Q=e.onVisibleChange,U=(0,l.Z)(e,Z),J=f.useState(),$=(0,i.Z)(J,2),ee=$[0],en=$[1],et="visible"in e?W:ee,er=f.useRef(null),eo=f.useRef(null),ei=f.useRef(null);f.useImperativeHandle(n,function(){return er.current});var el=function(e){en(e),null==Q||Q(e)};u=(t={visible:et,triggerRef:ei,onVisibleChange:el,autoFocus:q,overlayRef:eo}).visible,d=t.triggerRef,y=t.onVisibleChange,h=t.autoFocus,C=t.overlayRef,E=f.useRef(!1),w=function(){if(u){var e,n;null===(e=d.current)||void 0===e||null===(n=e.focus)||void 0===n||n.call(e),null==y||y(!1)}},k=function(){var e;return null!==(e=C.current)&&void 0!==e&&!!e.focus&&(C.current.focus(),E.current=!0,!0)},M=function(e){switch(e.keyCode){case v:w();break;case m:var n=!1;E.current||(n=k()),n?e.preventDefault():w()}},f.useEffect(function(){return u?(window.addEventListener("keydown",M),h&&(0,p.Z)(k,3),function(){window.removeEventListener("keydown",M),E.current=!1}):function(){E.current=!1}},[u]);var ea=function(){return f.createElement(b,{ref:eo,overlay:X,prefixCls:A,arrow:I})},eu=f.cloneElement(G,{className:c()(null===(P=G.props)||void 0===P?void 0:P.className,et&&(void 0!==(R=e.openClassName)?R:"".concat(A,"-open"))),ref:(0,s.Yr)(G)?(0,s.sQ)(ei,(0,s.C4)(G)):void 0}),ec=F;return ec||-1===Y.indexOf("contextMenu")||(ec=["click"]),f.createElement(a.Z,(0,r.Z)({builtinPlacements:void 0===_?g:_},U,{prefixCls:A,ref:er,popupClassName:c()(j,(0,o.Z)({},"".concat(A,"-show-arrow"),I)),popupStyle:B,action:Y,showAction:z,hideAction:ec,popupPlacement:void 0===D?"bottomLeft":D,popupAlign:L,popupTransitionName:O,popupAnimation:T,popupVisible:et,stretch:(x=e.minOverlayWidthMatchTrigger,N=e.alignPoint,"minOverlayWidthMatchTrigger"in e?x:!N)?"minWidth":"",popup:"function"==typeof X?ea:ea(),onPopupVisibleChange:el,onPopupClick:function(n){var t=e.onOverlayClick;en(!1),t&&t(n)},getPopupContainer:V}),eu)})},33082:function(e,n,t){t.d(n,{iz:function(){return eA},ck:function(){return ev},BW:function(){return eL},sN:function(){return ev},Wd:function(){return eI},ZP:function(){return ej},Xl:function(){return x}});var r=t(1119),o=t(11993),i=t(31686),l=t(83145),a=t(26365),u=t(6989),c=t(36760),s=t.n(c),f=t(1699),d=t(50506),p=t(16671),v=t(32559),m=t(2265),b=t(54887),y=m.createContext(null);function h(e,n){return void 0===e?null:"".concat(e,"-").concat(n)}function g(e){return h(m.useContext(y),e)}var Z=t(6397),C=["children","locked"],E=m.createContext(null);function w(e){var n=e.children,t=e.locked,r=(0,u.Z)(e,C),o=m.useContext(E),l=(0,Z.Z)(function(){var e;return e=(0,i.Z)({},o),Object.keys(r).forEach(function(n){var t=r[n];void 0!==t&&(e[n]=t)}),e},[o,r],function(e,n){return!t&&(e[0]!==n[0]||!(0,p.Z)(e[1],n[1],!0))});return m.createElement(E.Provider,{value:l},n)}var k=m.createContext(null);function M(){return m.useContext(k)}var R=m.createContext([]);function x(e){var n=m.useContext(R);return m.useMemo(function(){return void 0!==e?[].concat((0,l.Z)(n),[e]):n},[n,e])}var N=m.createContext(null),P=m.createContext({}),S=t(2857);function I(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if((0,S.Z)(e)){var t=e.nodeName.toLowerCase(),r=["input","select","textarea","button"].includes(t)||e.isContentEditable||"a"===t&&!!e.getAttribute("href"),o=e.getAttribute("tabindex"),i=Number(o),l=null;return o&&!Number.isNaN(i)?l=i:r&&null===l&&(l=0),r&&e.disabled&&(l=null),null!==l&&(l>=0||n&&l<0)}return!1}var K=t(95814),A=t(53346),O=K.Z.LEFT,T=K.Z.RIGHT,L=K.Z.UP,D=K.Z.DOWN,_=K.Z.ENTER,V=K.Z.ESC,z=K.Z.HOME,F=K.Z.END,j=[L,D,O,T];function B(e,n){return(function(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],t=(0,l.Z)(e.querySelectorAll("*")).filter(function(e){return I(e,n)});return I(e,n)&&t.unshift(e),t})(e,!0).filter(function(e){return n.has(e)})}function W(e,n,t){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;if(!e)return null;var o=B(e,n),i=o.length,l=o.findIndex(function(e){return t===e});return r<0?-1===l?l=i-1:l-=1:r>0&&(l+=1),o[l=(l+i)%i]}var H=function(e,n){var t=new Set,r=new Map,o=new Map;return e.forEach(function(e){var i=document.querySelector("[data-menu-id='".concat(h(n,e),"']"));i&&(t.add(i),o.set(i,e),r.set(e,i))}),{elements:t,key2element:r,element2key:o}},Y="__RC_UTIL_PATH_SPLIT__",q=function(e){return e.join(Y)},X="rc-menu-more";function G(e){var n=m.useRef(e);n.current=e;var t=m.useCallback(function(){for(var e,t=arguments.length,r=Array(t),o=0;o1&&(k.motionAppear=!1);var M=k.onVisibleChanged;return(k.onVisibleChanged=function(e){return b.current||e||Z(!0),null==M?void 0:M(e)},g)?null:m.createElement(w,{mode:u,locked:!b.current},m.createElement(eR.ZP,(0,r.Z)({visible:C},k,{forceRender:f,removeOnLeave:!1,leavedClassName:"".concat(s,"-hidden")}),function(e){var t=e.className,r=e.style;return m.createElement(eb,{id:n,className:t,style:r},l)}))}var eN=["style","className","title","eventKey","warnKey","disabled","internalPopupClose","children","itemIcon","expandIcon","popupClassName","popupOffset","popupStyle","onClick","onMouseEnter","onMouseLeave","onTitleClick","onTitleMouseEnter","onTitleMouseLeave"],eP=["active"],eS=m.forwardRef(function(e,n){var t=e.style,l=e.className,c=e.title,d=e.eventKey,p=(e.warnKey,e.disabled),v=e.internalPopupClose,b=e.children,y=e.itemIcon,h=e.expandIcon,Z=e.popupClassName,C=e.popupOffset,k=e.popupStyle,M=e.onClick,R=e.onMouseEnter,S=e.onMouseLeave,I=e.onTitleClick,K=e.onTitleMouseEnter,A=e.onTitleMouseLeave,O=(0,u.Z)(e,eN),T=g(d),L=m.useContext(E),D=L.prefixCls,_=L.mode,V=L.openKeys,z=L.disabled,F=L.overflowDisabled,j=L.activeKey,B=L.selectedKeys,W=L.itemIcon,H=L.expandIcon,Y=L.onItemClick,q=L.onOpenChange,X=L.onActive,Q=m.useContext(P)._internalRenderSubMenuItem,U=m.useContext(N).isSubPathKey,J=x(),$="".concat(D,"-submenu"),ee=z||p,en=m.useRef(),et=m.useRef(),er=null!=h?h:H,ea=V.includes(d),ec=!F&&ea,es=U(B,d),ef=eo(d,ee,K,A),ed=ef.active,ep=(0,u.Z)(ef,eP),ev=m.useState(!1),em=(0,a.Z)(ev,2),ey=em[0],eh=em[1],eg=function(e){ee||eh(e)},eZ=m.useMemo(function(){return ed||"inline"!==_&&(ey||U([j],d))},[_,ed,j,ey,d,U]),eC=ei(J.length),eE=G(function(e){null==M||M(eu(e)),Y(e)}),ew=T&&"".concat(T,"-popup"),ek=m.useMemo(function(){return m.createElement(el,{icon:"horizontal"!==_?er:void 0,props:(0,i.Z)((0,i.Z)({},e),{},{isOpen:ec,isSubMenu:!0})},m.createElement("i",{className:"".concat($,"-arrow")}))},[_,er,e,ec,$]),eR=m.createElement("div",(0,r.Z)({role:"menuitem",style:eC,className:"".concat($,"-title"),tabIndex:ee?null:-1,ref:en,title:"string"==typeof c?c:null,"data-menu-id":F&&T?null:T,"aria-expanded":ec,"aria-haspopup":!0,"aria-controls":ew,"aria-disabled":ee,onClick:function(e){ee||(null==I||I({key:d,domEvent:e}),"inline"===_&&q(d,!ea))},onFocus:function(){X(d)}},ep),c,ek),eS=m.useRef(_);if("inline"!==_&&J.length>1?eS.current="vertical":eS.current=_,!F){var eI=eS.current;eR=m.createElement(eM,{mode:eI,prefixCls:$,visible:!v&&ec&&"inline"!==_,popupClassName:Z,popupOffset:C,popupStyle:k,popup:m.createElement(w,{mode:"horizontal"===eI?"vertical":eI},m.createElement(eb,{id:ew,ref:et},b)),disabled:ee,onVisibleChange:function(e){"inline"!==_&&q(d,e)}},eR)}var eK=m.createElement(f.Z.Item,(0,r.Z)({ref:n,role:"none"},O,{component:"li",style:t,className:s()($,"".concat($,"-").concat(_),l,(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},"".concat($,"-open"),ec),"".concat($,"-active"),eZ),"".concat($,"-selected"),es),"".concat($,"-disabled"),ee)),onMouseEnter:function(e){eg(!0),null==R||R({key:d,domEvent:e})},onMouseLeave:function(e){eg(!1),null==S||S({key:d,domEvent:e})}}),eR,!F&&m.createElement(ex,{id:ew,open:ec,keyPath:J},b));return Q&&(eK=Q(eK,e,{selected:es,active:eZ,open:ec,disabled:ee})),m.createElement(w,{onItemClick:eE,mode:"horizontal"===_?"vertical":_,itemIcon:null!=y?y:W,expandIcon:er},eK)}),eI=m.forwardRef(function(e,n){var t,o=e.eventKey,i=e.children,l=x(o),a=eh(i,l),u=M();return m.useEffect(function(){if(u)return u.registerPath(o,l),function(){u.unregisterPath(o,l)}},[l]),t=u?a:m.createElement(eS,(0,r.Z)({ref:n},e),a),m.createElement(R.Provider,{value:l},t)}),eK=t(41154);function eA(e){var n=e.className,t=e.style,r=m.useContext(E).prefixCls;return M()?null:m.createElement("li",{role:"separator",className:s()("".concat(r,"-item-divider"),n),style:t})}var eO=["className","title","eventKey","children"],eT=m.forwardRef(function(e,n){var t=e.className,o=e.title,i=(e.eventKey,e.children),l=(0,u.Z)(e,eO),a=m.useContext(E).prefixCls,c="".concat(a,"-item-group");return m.createElement("li",(0,r.Z)({ref:n,role:"presentation"},l,{onClick:function(e){return e.stopPropagation()},className:s()(c,t)}),m.createElement("div",{role:"presentation",className:"".concat(c,"-title"),title:"string"==typeof o?o:void 0},o),m.createElement("ul",{role:"group",className:"".concat(c,"-list")},i))}),eL=m.forwardRef(function(e,n){var t=e.eventKey,o=eh(e.children,x(t));return M()?o:m.createElement(eT,(0,r.Z)({ref:n},(0,et.Z)(e,["warnKey"])),o)}),eD=["label","children","key","type","extra"];function e_(e,n,t,o,l){var a=e,c=(0,i.Z)({divider:eA,item:ev,group:eL,submenu:eI},o);return n&&(a=function e(n,t,o){var i=t.item,l=t.group,a=t.submenu,c=t.divider;return(n||[]).map(function(n,s){if(n&&"object"===(0,eK.Z)(n)){var f=n.label,d=n.children,p=n.key,v=n.type,b=n.extra,y=(0,u.Z)(n,eD),h=null!=p?p:"tmp-".concat(s);return d||"group"===v?"group"===v?m.createElement(l,(0,r.Z)({key:h},y,{title:f}),e(d,t,o)):m.createElement(a,(0,r.Z)({key:h},y,{title:f}),e(d,t,o)):"divider"===v?m.createElement(c,(0,r.Z)({key:h},y)):m.createElement(i,(0,r.Z)({key:h},y,{extra:b}),f,(!!b||0===b)&&m.createElement("span",{className:"".concat(o,"-item-extra")},b))}return null}).filter(function(e){return e})}(n,c,l)),eh(a,t)}var eV=["prefixCls","rootClassName","style","className","tabIndex","items","children","direction","id","mode","inlineCollapsed","disabled","disabledOverflow","subMenuOpenDelay","subMenuCloseDelay","forceSubMenuRender","defaultOpenKeys","openKeys","activeKey","defaultActiveFirst","selectable","multiple","defaultSelectedKeys","selectedKeys","onSelect","onDeselect","inlineIndent","motion","defaultMotions","triggerSubMenuAction","builtinPlacements","itemIcon","expandIcon","overflowedIndicator","overflowedIndicatorPopupClassName","getPopupContainer","onClick","onOpenChange","onKeyDown","openAnimation","openTransitionName","_internalRenderMenuItem","_internalRenderSubMenuItem","_internalComponents"],ez=[],eF=m.forwardRef(function(e,n){var t,c,v,h,g,Z,C,E,M,R,x,S,I,K,J,$,ee,en,et,er,eo,ei,el,ea,ec,es,ef=e.prefixCls,ed=void 0===ef?"rc-menu":ef,ep=e.rootClassName,em=e.style,eb=e.className,ey=e.tabIndex,eh=e.items,eg=e.children,eZ=e.direction,eC=e.id,eE=e.mode,ew=void 0===eE?"vertical":eE,ek=e.inlineCollapsed,eM=e.disabled,eR=e.disabledOverflow,ex=e.subMenuOpenDelay,eN=e.subMenuCloseDelay,eP=e.forceSubMenuRender,eS=e.defaultOpenKeys,eK=e.openKeys,eA=e.activeKey,eO=e.defaultActiveFirst,eT=e.selectable,eL=void 0===eT||eT,eD=e.multiple,eF=void 0!==eD&&eD,ej=e.defaultSelectedKeys,eB=e.selectedKeys,eW=e.onSelect,eH=e.onDeselect,eY=e.inlineIndent,eq=e.motion,eX=e.defaultMotions,eG=e.triggerSubMenuAction,eQ=e.builtinPlacements,eU=e.itemIcon,eJ=e.expandIcon,e$=e.overflowedIndicator,e0=void 0===e$?"...":e$,e1=e.overflowedIndicatorPopupClassName,e6=e.getPopupContainer,e2=e.onClick,e5=e.onOpenChange,e9=e.onKeyDown,e3=(e.openAnimation,e.openTransitionName,e._internalRenderMenuItem),e4=e._internalRenderSubMenuItem,e7=e._internalComponents,e8=(0,u.Z)(e,eV),ne=m.useMemo(function(){return[e_(eg,eh,ez,e7,ed),e_(eg,eh,ez,{},ed)]},[eg,eh,e7]),nn=(0,a.Z)(ne,2),nt=nn[0],nr=nn[1],no=m.useState(!1),ni=(0,a.Z)(no,2),nl=ni[0],na=ni[1],nu=m.useRef(),nc=(t=(0,d.Z)(eC,{value:eC}),v=(c=(0,a.Z)(t,2))[0],h=c[1],m.useEffect(function(){U+=1;var e="".concat(Q,"-").concat(U);h("rc-menu-uuid-".concat(e))},[]),v),ns="rtl"===eZ,nf=(0,d.Z)(eS,{value:eK,postState:function(e){return e||ez}}),nd=(0,a.Z)(nf,2),np=nd[0],nv=nd[1],nm=function(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];function t(){nv(e),null==e5||e5(e)}n?(0,b.flushSync)(t):t()},nb=m.useState(np),ny=(0,a.Z)(nb,2),nh=ny[0],ng=ny[1],nZ=m.useRef(!1),nC=m.useMemo(function(){return("inline"===ew||"vertical"===ew)&&ek?["vertical",ek]:[ew,!1]},[ew,ek]),nE=(0,a.Z)(nC,2),nw=nE[0],nk=nE[1],nM="inline"===nw,nR=m.useState(nw),nx=(0,a.Z)(nR,2),nN=nx[0],nP=nx[1],nS=m.useState(nk),nI=(0,a.Z)(nS,2),nK=nI[0],nA=nI[1];m.useEffect(function(){nP(nw),nA(nk),nZ.current&&(nM?nv(nh):nm(ez))},[nw,nk]);var nO=m.useState(0),nT=(0,a.Z)(nO,2),nL=nT[0],nD=nT[1],n_=nL>=nt.length-1||"horizontal"!==nN||eR;m.useEffect(function(){nM&&ng(np)},[np]),m.useEffect(function(){return nZ.current=!0,function(){nZ.current=!1}},[]);var nV=(g=m.useState({}),Z=(0,a.Z)(g,2)[1],C=(0,m.useRef)(new Map),E=(0,m.useRef)(new Map),M=m.useState([]),x=(R=(0,a.Z)(M,2))[0],S=R[1],I=(0,m.useRef)(0),K=(0,m.useRef)(!1),J=function(){K.current||Z({})},$=(0,m.useCallback)(function(e,n){var t,r=q(n);E.current.set(r,e),C.current.set(e,r),I.current+=1;var o=I.current;t=function(){o===I.current&&J()},Promise.resolve().then(t)},[]),ee=(0,m.useCallback)(function(e,n){var t=q(n);E.current.delete(t),C.current.delete(e)},[]),en=(0,m.useCallback)(function(e){S(e)},[]),et=(0,m.useCallback)(function(e,n){var t=(C.current.get(e)||"").split(Y);return n&&x.includes(t[0])&&t.unshift(X),t},[x]),er=(0,m.useCallback)(function(e,n){return e.filter(function(e){return void 0!==e}).some(function(e){return et(e,!0).includes(n)})},[et]),eo=(0,m.useCallback)(function(e){var n="".concat(C.current.get(e)).concat(Y),t=new Set;return(0,l.Z)(E.current.keys()).forEach(function(e){e.startsWith(n)&&t.add(E.current.get(e))}),t},[]),m.useEffect(function(){return function(){K.current=!0}},[]),{registerPath:$,unregisterPath:ee,refreshOverflowKeys:en,isSubPathKey:er,getKeyPath:et,getKeys:function(){var e=(0,l.Z)(C.current.keys());return x.length&&e.push(X),e},getSubPathKeys:eo}),nz=nV.registerPath,nF=nV.unregisterPath,nj=nV.refreshOverflowKeys,nB=nV.isSubPathKey,nW=nV.getKeyPath,nH=nV.getKeys,nY=nV.getSubPathKeys,nq=m.useMemo(function(){return{registerPath:nz,unregisterPath:nF}},[nz,nF]),nX=m.useMemo(function(){return{isSubPathKey:nB}},[nB]);m.useEffect(function(){nj(n_?ez:nt.slice(nL+1).map(function(e){return e.key}))},[nL,n_]);var nG=(0,d.Z)(eA||eO&&(null===(es=nt[0])||void 0===es?void 0:es.key),{value:eA}),nQ=(0,a.Z)(nG,2),nU=nQ[0],nJ=nQ[1],n$=G(function(e){nJ(e)}),n0=G(function(){nJ(void 0)});(0,m.useImperativeHandle)(n,function(){return{list:nu.current,focus:function(e){var n,t,r=H(nH(),nc),o=r.elements,i=r.key2element,l=r.element2key,a=B(nu.current,o),u=null!=nU?nU:a[0]?l.get(a[0]):null===(n=nt.find(function(e){return!e.props.disabled}))||void 0===n?void 0:n.key,c=i.get(u);u&&c&&(null==c||null===(t=c.focus)||void 0===t||t.call(c,e))}}});var n1=(0,d.Z)(ej||[],{value:eB,postState:function(e){return Array.isArray(e)?e:null==e?ez:[e]}}),n6=(0,a.Z)(n1,2),n2=n6[0],n5=n6[1],n9=function(e){if(eL){var n,t=e.key,r=n2.includes(t);n5(n=eF?r?n2.filter(function(e){return e!==t}):[].concat((0,l.Z)(n2),[t]):[t]);var o=(0,i.Z)((0,i.Z)({},e),{},{selectedKeys:n});r?null==eH||eH(o):null==eW||eW(o)}!eF&&np.length&&"inline"!==nN&&nm(ez)},n3=G(function(e){null==e2||e2(eu(e)),n9(e)}),n4=G(function(e,n){var t=np.filter(function(n){return n!==e});if(n)t.push(e);else if("inline"!==nN){var r=nY(e);t=t.filter(function(e){return!r.has(e)})}(0,p.Z)(np,t,!0)||nm(t,!0)}),n7=(ei=function(e,n){var t=null!=n?n:!np.includes(e);n4(e,t)},el=m.useRef(),(ea=m.useRef()).current=nU,ec=function(){A.Z.cancel(el.current)},m.useEffect(function(){return function(){ec()}},[]),function(e){var n=e.which;if([].concat(j,[_,V,z,F]).includes(n)){var t=nH(),r=H(t,nc),i=r,l=i.elements,a=i.key2element,u=i.element2key,c=function(e,n){for(var t=e||document.activeElement;t;){if(n.has(t))return t;t=t.parentElement}return null}(a.get(nU),l),s=u.get(c),f=function(e,n,t,r){var i,l="prev",a="next",u="children",c="parent";if("inline"===e&&r===_)return{inlineTrigger:!0};var s=(0,o.Z)((0,o.Z)({},L,l),D,a),f=(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},O,t?a:l),T,t?l:a),D,u),_,u),d=(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},L,l),D,a),_,u),V,c),O,t?u:c),T,t?c:u);switch(null===(i=({inline:s,horizontal:f,vertical:d,inlineSub:s,horizontalSub:d,verticalSub:d})["".concat(e).concat(n?"":"Sub")])||void 0===i?void 0:i[r]){case l:return{offset:-1,sibling:!0};case a:return{offset:1,sibling:!0};case c:return{offset:-1,sibling:!1};case u:return{offset:1,sibling:!1};default:return null}}(nN,1===nW(s,!0).length,ns,n);if(!f&&n!==z&&n!==F)return;(j.includes(n)||[z,F].includes(n))&&e.preventDefault();var d=function(e){if(e){var n=e,t=e.querySelector("a");null!=t&&t.getAttribute("href")&&(n=t);var r=u.get(e);nJ(r),ec(),el.current=(0,A.Z)(function(){ea.current===r&&n.focus()})}};if([z,F].includes(n)||f.sibling||!c){var p,v=B(p=c&&"inline"!==nN?function(e){for(var n=e;n;){if(n.getAttribute("data-menu-list"))return n;n=n.parentElement}return null}(c):nu.current,l);d(n===z?v[0]:n===F?v[v.length-1]:W(p,l,c,f.offset))}else if(f.inlineTrigger)ei(s);else if(f.offset>0)ei(s,!0),ec(),el.current=(0,A.Z)(function(){r=H(t,nc);var e=c.getAttribute("aria-controls");d(W(document.getElementById(e),r.elements))},5);else if(f.offset<0){var m=nW(s,!0),b=m[m.length-2],y=a.get(b);ei(b,!1),d(y)}}null==e9||e9(e)});m.useEffect(function(){na(!0)},[]);var n8=m.useMemo(function(){return{_internalRenderMenuItem:e3,_internalRenderSubMenuItem:e4}},[e3,e4]),te="horizontal"!==nN||eR?nt:nt.map(function(e,n){return m.createElement(w,{key:e.key,overflowDisabled:n>nL},e)}),tn=m.createElement(f.Z,(0,r.Z)({id:eC,ref:nu,prefixCls:"".concat(ed,"-overflow"),component:"ul",itemComponent:ev,className:s()(ed,"".concat(ed,"-root"),"".concat(ed,"-").concat(nN),eb,(0,o.Z)((0,o.Z)({},"".concat(ed,"-inline-collapsed"),nK),"".concat(ed,"-rtl"),ns),ep),dir:eZ,style:em,role:"menu",tabIndex:void 0===ey?0:ey,data:te,renderRawItem:function(e){return e},renderRawRest:function(e){var n=e.length,t=n?nt.slice(-n):null;return m.createElement(eI,{eventKey:X,title:e0,disabled:n_,internalPopupClose:0===n,popupClassName:e1},t)},maxCount:"horizontal"!==nN||eR?f.Z.INVALIDATE:f.Z.RESPONSIVE,ssr:"full","data-menu-list":!0,onVisibleChange:function(e){nD(e)},onKeyDown:n7},e8));return m.createElement(P.Provider,{value:n8},m.createElement(y.Provider,{value:nc},m.createElement(w,{prefixCls:ed,rootClassName:ep,mode:nN,openKeys:np,rtl:ns,disabled:eM,motion:nl?eq:null,defaultMotions:nl?eX:null,activeKey:nU,onActive:n$,onInactive:n0,selectedKeys:n2,inlineIndent:void 0===eY?24:eY,subMenuOpenDelay:void 0===ex?.1:ex,subMenuCloseDelay:void 0===eN?.1:eN,forceSubMenuRender:eP,builtinPlacements:eQ,triggerSubMenuAction:void 0===eG?"hover":eG,getPopupContainer:e6,itemIcon:eU,expandIcon:eJ,onItemClick:n3,onOpenChange:n4},m.createElement(N.Provider,{value:nX},tn),m.createElement("div",{style:{display:"none"},"aria-hidden":!0},m.createElement(k.Provider,{value:nq},nr)))))});eF.Item=ev,eF.SubMenu=eI,eF.ItemGroup=eL,eF.Divider=eA;var ej=eF}}]); \ No newline at end of file +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1529],{60440:function(e,n,t){t.d(n,{Z:function(){return a}});var r=t(1119),o=t(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"ellipsis",theme:"outlined"},l=t(55015),a=o.forwardRef(function(e,n){return o.createElement(l.Z,(0,r.Z)({},e,{ref:n,icon:i}))})},77565:function(e,n,t){t.d(n,{Z:function(){return a}});var r=t(1119),o=t(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"},l=t(55015),a=o.forwardRef(function(e,n){return o.createElement(l.Z,(0,r.Z)({},e,{ref:n,icon:i}))})},71030:function(e,n,t){t.d(n,{Z:function(){return C}});var r=t(1119),o=t(11993),i=t(26365),l=t(6989),a=t(97821),u=t(36760),c=t.n(u),s=t(28791),f=t(2265),d=t(95814),p=t(53346),v=d.Z.ESC,m=d.Z.TAB,b=(0,f.forwardRef)(function(e,n){var t=e.overlay,r=e.arrow,o=e.prefixCls,i=(0,f.useMemo)(function(){return"function"==typeof t?t():t},[t]),l=(0,s.sQ)(n,(0,s.C4)(i));return f.createElement(f.Fragment,null,r&&f.createElement("div",{className:"".concat(o,"-arrow")}),f.cloneElement(i,{ref:(0,s.Yr)(i)?l:void 0}))}),y={adjustX:1,adjustY:1},h=[0,0],g={topLeft:{points:["bl","tl"],overflow:y,offset:[0,-4],targetOffset:h},top:{points:["bc","tc"],overflow:y,offset:[0,-4],targetOffset:h},topRight:{points:["br","tr"],overflow:y,offset:[0,-4],targetOffset:h},bottomLeft:{points:["tl","bl"],overflow:y,offset:[0,4],targetOffset:h},bottom:{points:["tc","bc"],overflow:y,offset:[0,4],targetOffset:h},bottomRight:{points:["tr","br"],overflow:y,offset:[0,4],targetOffset:h}},Z=["arrow","prefixCls","transitionName","animation","align","placement","placements","getPopupContainer","showAction","hideAction","overlayClassName","overlayStyle","visible","trigger","autoFocus","overlay","children","onVisibleChange"],C=f.forwardRef(function(e,n){var t,u,d,y,h,C,E,w,k,M,R,x,N,P,S=e.arrow,I=void 0!==S&&S,K=e.prefixCls,A=void 0===K?"rc-dropdown":K,O=e.transitionName,T=e.animation,L=e.align,D=e.placement,_=e.placements,V=e.getPopupContainer,z=e.showAction,F=e.hideAction,j=e.overlayClassName,B=e.overlayStyle,W=e.visible,H=e.trigger,Y=void 0===H?["hover"]:H,q=e.autoFocus,X=e.overlay,G=e.children,Q=e.onVisibleChange,U=(0,l.Z)(e,Z),J=f.useState(),$=(0,i.Z)(J,2),ee=$[0],en=$[1],et="visible"in e?W:ee,er=f.useRef(null),eo=f.useRef(null),ei=f.useRef(null);f.useImperativeHandle(n,function(){return er.current});var el=function(e){en(e),null==Q||Q(e)};u=(t={visible:et,triggerRef:ei,onVisibleChange:el,autoFocus:q,overlayRef:eo}).visible,d=t.triggerRef,y=t.onVisibleChange,h=t.autoFocus,C=t.overlayRef,E=f.useRef(!1),w=function(){if(u){var e,n;null===(e=d.current)||void 0===e||null===(n=e.focus)||void 0===n||n.call(e),null==y||y(!1)}},k=function(){var e;return null!==(e=C.current)&&void 0!==e&&!!e.focus&&(C.current.focus(),E.current=!0,!0)},M=function(e){switch(e.keyCode){case v:w();break;case m:var n=!1;E.current||(n=k()),n?e.preventDefault():w()}},f.useEffect(function(){return u?(window.addEventListener("keydown",M),h&&(0,p.Z)(k,3),function(){window.removeEventListener("keydown",M),E.current=!1}):function(){E.current=!1}},[u]);var ea=function(){return f.createElement(b,{ref:eo,overlay:X,prefixCls:A,arrow:I})},eu=f.cloneElement(G,{className:c()(null===(P=G.props)||void 0===P?void 0:P.className,et&&(void 0!==(R=e.openClassName)?R:"".concat(A,"-open"))),ref:(0,s.Yr)(G)?(0,s.sQ)(ei,(0,s.C4)(G)):void 0}),ec=F;return ec||-1===Y.indexOf("contextMenu")||(ec=["click"]),f.createElement(a.Z,(0,r.Z)({builtinPlacements:void 0===_?g:_},U,{prefixCls:A,ref:er,popupClassName:c()(j,(0,o.Z)({},"".concat(A,"-show-arrow"),I)),popupStyle:B,action:Y,showAction:z,hideAction:ec,popupPlacement:void 0===D?"bottomLeft":D,popupAlign:L,popupTransitionName:O,popupAnimation:T,popupVisible:et,stretch:(x=e.minOverlayWidthMatchTrigger,N=e.alignPoint,"minOverlayWidthMatchTrigger"in e?x:!N)?"minWidth":"",popup:"function"==typeof X?ea:ea(),onPopupVisibleChange:el,onPopupClick:function(n){var t=e.onOverlayClick;en(!1),t&&t(n)},getPopupContainer:V}),eu)})},33082:function(e,n,t){t.d(n,{iz:function(){return eA},ck:function(){return ev},BW:function(){return eL},sN:function(){return ev},Wd:function(){return eI},ZP:function(){return ej},Xl:function(){return x}});var r=t(1119),o=t(11993),i=t(31686),l=t(83145),a=t(26365),u=t(6989),c=t(36760),s=t.n(c),f=t(1699),d=t(50506),p=t(16671),v=t(32559),m=t(2265),b=t(54887),y=m.createContext(null);function h(e,n){return void 0===e?null:"".concat(e,"-").concat(n)}function g(e){return h(m.useContext(y),e)}var Z=t(6397),C=["children","locked"],E=m.createContext(null);function w(e){var n=e.children,t=e.locked,r=(0,u.Z)(e,C),o=m.useContext(E),l=(0,Z.Z)(function(){var e;return e=(0,i.Z)({},o),Object.keys(r).forEach(function(n){var t=r[n];void 0!==t&&(e[n]=t)}),e},[o,r],function(e,n){return!t&&(e[0]!==n[0]||!(0,p.Z)(e[1],n[1],!0))});return m.createElement(E.Provider,{value:l},n)}var k=m.createContext(null);function M(){return m.useContext(k)}var R=m.createContext([]);function x(e){var n=m.useContext(R);return m.useMemo(function(){return void 0!==e?[].concat((0,l.Z)(n),[e]):n},[n,e])}var N=m.createContext(null),P=m.createContext({}),S=t(2857);function I(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if((0,S.Z)(e)){var t=e.nodeName.toLowerCase(),r=["input","select","textarea","button"].includes(t)||e.isContentEditable||"a"===t&&!!e.getAttribute("href"),o=e.getAttribute("tabindex"),i=Number(o),l=null;return o&&!Number.isNaN(i)?l=i:r&&null===l&&(l=0),r&&e.disabled&&(l=null),null!==l&&(l>=0||n&&l<0)}return!1}var K=t(95814),A=t(53346),O=K.Z.LEFT,T=K.Z.RIGHT,L=K.Z.UP,D=K.Z.DOWN,_=K.Z.ENTER,V=K.Z.ESC,z=K.Z.HOME,F=K.Z.END,j=[L,D,O,T];function B(e,n){return(function(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],t=(0,l.Z)(e.querySelectorAll("*")).filter(function(e){return I(e,n)});return I(e,n)&&t.unshift(e),t})(e,!0).filter(function(e){return n.has(e)})}function W(e,n,t){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;if(!e)return null;var o=B(e,n),i=o.length,l=o.findIndex(function(e){return t===e});return r<0?-1===l?l=i-1:l-=1:r>0&&(l+=1),o[l=(l+i)%i]}var H=function(e,n){var t=new Set,r=new Map,o=new Map;return e.forEach(function(e){var i=document.querySelector("[data-menu-id='".concat(h(n,e),"']"));i&&(t.add(i),o.set(i,e),r.set(e,i))}),{elements:t,key2element:r,element2key:o}},Y="__RC_UTIL_PATH_SPLIT__",q=function(e){return e.join(Y)},X="rc-menu-more";function G(e){var n=m.useRef(e);n.current=e;var t=m.useCallback(function(){for(var e,t=arguments.length,r=Array(t),o=0;o1&&(k.motionAppear=!1);var M=k.onVisibleChanged;return(k.onVisibleChanged=function(e){return b.current||e||Z(!0),null==M?void 0:M(e)},g)?null:m.createElement(w,{mode:u,locked:!b.current},m.createElement(eR.ZP,(0,r.Z)({visible:C},k,{forceRender:f,removeOnLeave:!1,leavedClassName:"".concat(s,"-hidden")}),function(e){var t=e.className,r=e.style;return m.createElement(eb,{id:n,className:t,style:r},l)}))}var eN=["style","className","title","eventKey","warnKey","disabled","internalPopupClose","children","itemIcon","expandIcon","popupClassName","popupOffset","popupStyle","onClick","onMouseEnter","onMouseLeave","onTitleClick","onTitleMouseEnter","onTitleMouseLeave"],eP=["active"],eS=m.forwardRef(function(e,n){var t=e.style,l=e.className,c=e.title,d=e.eventKey,p=(e.warnKey,e.disabled),v=e.internalPopupClose,b=e.children,y=e.itemIcon,h=e.expandIcon,Z=e.popupClassName,C=e.popupOffset,k=e.popupStyle,M=e.onClick,R=e.onMouseEnter,S=e.onMouseLeave,I=e.onTitleClick,K=e.onTitleMouseEnter,A=e.onTitleMouseLeave,O=(0,u.Z)(e,eN),T=g(d),L=m.useContext(E),D=L.prefixCls,_=L.mode,V=L.openKeys,z=L.disabled,F=L.overflowDisabled,j=L.activeKey,B=L.selectedKeys,W=L.itemIcon,H=L.expandIcon,Y=L.onItemClick,q=L.onOpenChange,X=L.onActive,Q=m.useContext(P)._internalRenderSubMenuItem,U=m.useContext(N).isSubPathKey,J=x(),$="".concat(D,"-submenu"),ee=z||p,en=m.useRef(),et=m.useRef(),er=null!=h?h:H,ea=V.includes(d),ec=!F&&ea,es=U(B,d),ef=eo(d,ee,K,A),ed=ef.active,ep=(0,u.Z)(ef,eP),ev=m.useState(!1),em=(0,a.Z)(ev,2),ey=em[0],eh=em[1],eg=function(e){ee||eh(e)},eZ=m.useMemo(function(){return ed||"inline"!==_&&(ey||U([j],d))},[_,ed,j,ey,d,U]),eC=ei(J.length),eE=G(function(e){null==M||M(eu(e)),Y(e)}),ew=T&&"".concat(T,"-popup"),ek=m.useMemo(function(){return m.createElement(el,{icon:"horizontal"!==_?er:void 0,props:(0,i.Z)((0,i.Z)({},e),{},{isOpen:ec,isSubMenu:!0})},m.createElement("i",{className:"".concat($,"-arrow")}))},[_,er,e,ec,$]),eR=m.createElement("div",(0,r.Z)({role:"menuitem",style:eC,className:"".concat($,"-title"),tabIndex:ee?null:-1,ref:en,title:"string"==typeof c?c:null,"data-menu-id":F&&T?null:T,"aria-expanded":ec,"aria-haspopup":!0,"aria-controls":ew,"aria-disabled":ee,onClick:function(e){ee||(null==I||I({key:d,domEvent:e}),"inline"===_&&q(d,!ea))},onFocus:function(){X(d)}},ep),c,ek),eS=m.useRef(_);if("inline"!==_&&J.length>1?eS.current="vertical":eS.current=_,!F){var eI=eS.current;eR=m.createElement(eM,{mode:eI,prefixCls:$,visible:!v&&ec&&"inline"!==_,popupClassName:Z,popupOffset:C,popupStyle:k,popup:m.createElement(w,{mode:"horizontal"===eI?"vertical":eI},m.createElement(eb,{id:ew,ref:et},b)),disabled:ee,onVisibleChange:function(e){"inline"!==_&&q(d,e)}},eR)}var eK=m.createElement(f.Z.Item,(0,r.Z)({ref:n,role:"none"},O,{component:"li",style:t,className:s()($,"".concat($,"-").concat(_),l,(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},"".concat($,"-open"),ec),"".concat($,"-active"),eZ),"".concat($,"-selected"),es),"".concat($,"-disabled"),ee)),onMouseEnter:function(e){eg(!0),null==R||R({key:d,domEvent:e})},onMouseLeave:function(e){eg(!1),null==S||S({key:d,domEvent:e})}}),eR,!F&&m.createElement(ex,{id:ew,open:ec,keyPath:J},b));return Q&&(eK=Q(eK,e,{selected:es,active:eZ,open:ec,disabled:ee})),m.createElement(w,{onItemClick:eE,mode:"horizontal"===_?"vertical":_,itemIcon:null!=y?y:W,expandIcon:er},eK)}),eI=m.forwardRef(function(e,n){var t,o=e.eventKey,i=e.children,l=x(o),a=eh(i,l),u=M();return m.useEffect(function(){if(u)return u.registerPath(o,l),function(){u.unregisterPath(o,l)}},[l]),t=u?a:m.createElement(eS,(0,r.Z)({ref:n},e),a),m.createElement(R.Provider,{value:l},t)}),eK=t(41154);function eA(e){var n=e.className,t=e.style,r=m.useContext(E).prefixCls;return M()?null:m.createElement("li",{role:"separator",className:s()("".concat(r,"-item-divider"),n),style:t})}var eO=["className","title","eventKey","children"],eT=m.forwardRef(function(e,n){var t=e.className,o=e.title,i=(e.eventKey,e.children),l=(0,u.Z)(e,eO),a=m.useContext(E).prefixCls,c="".concat(a,"-item-group");return m.createElement("li",(0,r.Z)({ref:n,role:"presentation"},l,{onClick:function(e){return e.stopPropagation()},className:s()(c,t)}),m.createElement("div",{role:"presentation",className:"".concat(c,"-title"),title:"string"==typeof o?o:void 0},o),m.createElement("ul",{role:"group",className:"".concat(c,"-list")},i))}),eL=m.forwardRef(function(e,n){var t=e.eventKey,o=eh(e.children,x(t));return M()?o:m.createElement(eT,(0,r.Z)({ref:n},(0,et.Z)(e,["warnKey"])),o)}),eD=["label","children","key","type","extra"];function e_(e,n,t,o,l){var a=e,c=(0,i.Z)({divider:eA,item:ev,group:eL,submenu:eI},o);return n&&(a=function e(n,t,o){var i=t.item,l=t.group,a=t.submenu,c=t.divider;return(n||[]).map(function(n,s){if(n&&"object"===(0,eK.Z)(n)){var f=n.label,d=n.children,p=n.key,v=n.type,b=n.extra,y=(0,u.Z)(n,eD),h=null!=p?p:"tmp-".concat(s);return d||"group"===v?"group"===v?m.createElement(l,(0,r.Z)({key:h},y,{title:f}),e(d,t,o)):m.createElement(a,(0,r.Z)({key:h},y,{title:f}),e(d,t,o)):"divider"===v?m.createElement(c,(0,r.Z)({key:h},y)):m.createElement(i,(0,r.Z)({key:h},y,{extra:b}),f,(!!b||0===b)&&m.createElement("span",{className:"".concat(o,"-item-extra")},b))}return null}).filter(function(e){return e})}(n,c,l)),eh(a,t)}var eV=["prefixCls","rootClassName","style","className","tabIndex","items","children","direction","id","mode","inlineCollapsed","disabled","disabledOverflow","subMenuOpenDelay","subMenuCloseDelay","forceSubMenuRender","defaultOpenKeys","openKeys","activeKey","defaultActiveFirst","selectable","multiple","defaultSelectedKeys","selectedKeys","onSelect","onDeselect","inlineIndent","motion","defaultMotions","triggerSubMenuAction","builtinPlacements","itemIcon","expandIcon","overflowedIndicator","overflowedIndicatorPopupClassName","getPopupContainer","onClick","onOpenChange","onKeyDown","openAnimation","openTransitionName","_internalRenderMenuItem","_internalRenderSubMenuItem","_internalComponents"],ez=[],eF=m.forwardRef(function(e,n){var t,c,v,h,g,Z,C,E,M,R,x,S,I,K,J,$,ee,en,et,er,eo,ei,el,ea,ec,es,ef=e.prefixCls,ed=void 0===ef?"rc-menu":ef,ep=e.rootClassName,em=e.style,eb=e.className,ey=e.tabIndex,eh=e.items,eg=e.children,eZ=e.direction,eC=e.id,eE=e.mode,ew=void 0===eE?"vertical":eE,ek=e.inlineCollapsed,eM=e.disabled,eR=e.disabledOverflow,ex=e.subMenuOpenDelay,eN=e.subMenuCloseDelay,eP=e.forceSubMenuRender,eS=e.defaultOpenKeys,eK=e.openKeys,eA=e.activeKey,eO=e.defaultActiveFirst,eT=e.selectable,eL=void 0===eT||eT,eD=e.multiple,eF=void 0!==eD&&eD,ej=e.defaultSelectedKeys,eB=e.selectedKeys,eW=e.onSelect,eH=e.onDeselect,eY=e.inlineIndent,eq=e.motion,eX=e.defaultMotions,eG=e.triggerSubMenuAction,eQ=e.builtinPlacements,eU=e.itemIcon,eJ=e.expandIcon,e$=e.overflowedIndicator,e0=void 0===e$?"...":e$,e1=e.overflowedIndicatorPopupClassName,e6=e.getPopupContainer,e2=e.onClick,e5=e.onOpenChange,e9=e.onKeyDown,e3=(e.openAnimation,e.openTransitionName,e._internalRenderMenuItem),e4=e._internalRenderSubMenuItem,e7=e._internalComponents,e8=(0,u.Z)(e,eV),ne=m.useMemo(function(){return[e_(eg,eh,ez,e7,ed),e_(eg,eh,ez,{},ed)]},[eg,eh,e7]),nn=(0,a.Z)(ne,2),nt=nn[0],nr=nn[1],no=m.useState(!1),ni=(0,a.Z)(no,2),nl=ni[0],na=ni[1],nu=m.useRef(),nc=(t=(0,d.Z)(eC,{value:eC}),v=(c=(0,a.Z)(t,2))[0],h=c[1],m.useEffect(function(){U+=1;var e="".concat(Q,"-").concat(U);h("rc-menu-uuid-".concat(e))},[]),v),ns="rtl"===eZ,nf=(0,d.Z)(eS,{value:eK,postState:function(e){return e||ez}}),nd=(0,a.Z)(nf,2),np=nd[0],nv=nd[1],nm=function(e){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];function t(){nv(e),null==e5||e5(e)}n?(0,b.flushSync)(t):t()},nb=m.useState(np),ny=(0,a.Z)(nb,2),nh=ny[0],ng=ny[1],nZ=m.useRef(!1),nC=m.useMemo(function(){return("inline"===ew||"vertical"===ew)&&ek?["vertical",ek]:[ew,!1]},[ew,ek]),nE=(0,a.Z)(nC,2),nw=nE[0],nk=nE[1],nM="inline"===nw,nR=m.useState(nw),nx=(0,a.Z)(nR,2),nN=nx[0],nP=nx[1],nS=m.useState(nk),nI=(0,a.Z)(nS,2),nK=nI[0],nA=nI[1];m.useEffect(function(){nP(nw),nA(nk),nZ.current&&(nM?nv(nh):nm(ez))},[nw,nk]);var nO=m.useState(0),nT=(0,a.Z)(nO,2),nL=nT[0],nD=nT[1],n_=nL>=nt.length-1||"horizontal"!==nN||eR;m.useEffect(function(){nM&&ng(np)},[np]),m.useEffect(function(){return nZ.current=!0,function(){nZ.current=!1}},[]);var nV=(g=m.useState({}),Z=(0,a.Z)(g,2)[1],C=(0,m.useRef)(new Map),E=(0,m.useRef)(new Map),M=m.useState([]),x=(R=(0,a.Z)(M,2))[0],S=R[1],I=(0,m.useRef)(0),K=(0,m.useRef)(!1),J=function(){K.current||Z({})},$=(0,m.useCallback)(function(e,n){var t,r=q(n);E.current.set(r,e),C.current.set(e,r),I.current+=1;var o=I.current;t=function(){o===I.current&&J()},Promise.resolve().then(t)},[]),ee=(0,m.useCallback)(function(e,n){var t=q(n);E.current.delete(t),C.current.delete(e)},[]),en=(0,m.useCallback)(function(e){S(e)},[]),et=(0,m.useCallback)(function(e,n){var t=(C.current.get(e)||"").split(Y);return n&&x.includes(t[0])&&t.unshift(X),t},[x]),er=(0,m.useCallback)(function(e,n){return e.filter(function(e){return void 0!==e}).some(function(e){return et(e,!0).includes(n)})},[et]),eo=(0,m.useCallback)(function(e){var n="".concat(C.current.get(e)).concat(Y),t=new Set;return(0,l.Z)(E.current.keys()).forEach(function(e){e.startsWith(n)&&t.add(E.current.get(e))}),t},[]),m.useEffect(function(){return function(){K.current=!0}},[]),{registerPath:$,unregisterPath:ee,refreshOverflowKeys:en,isSubPathKey:er,getKeyPath:et,getKeys:function(){var e=(0,l.Z)(C.current.keys());return x.length&&e.push(X),e},getSubPathKeys:eo}),nz=nV.registerPath,nF=nV.unregisterPath,nj=nV.refreshOverflowKeys,nB=nV.isSubPathKey,nW=nV.getKeyPath,nH=nV.getKeys,nY=nV.getSubPathKeys,nq=m.useMemo(function(){return{registerPath:nz,unregisterPath:nF}},[nz,nF]),nX=m.useMemo(function(){return{isSubPathKey:nB}},[nB]);m.useEffect(function(){nj(n_?ez:nt.slice(nL+1).map(function(e){return e.key}))},[nL,n_]);var nG=(0,d.Z)(eA||eO&&(null===(es=nt[0])||void 0===es?void 0:es.key),{value:eA}),nQ=(0,a.Z)(nG,2),nU=nQ[0],nJ=nQ[1],n$=G(function(e){nJ(e)}),n0=G(function(){nJ(void 0)});(0,m.useImperativeHandle)(n,function(){return{list:nu.current,focus:function(e){var n,t,r=H(nH(),nc),o=r.elements,i=r.key2element,l=r.element2key,a=B(nu.current,o),u=null!=nU?nU:a[0]?l.get(a[0]):null===(n=nt.find(function(e){return!e.props.disabled}))||void 0===n?void 0:n.key,c=i.get(u);u&&c&&(null==c||null===(t=c.focus)||void 0===t||t.call(c,e))}}});var n1=(0,d.Z)(ej||[],{value:eB,postState:function(e){return Array.isArray(e)?e:null==e?ez:[e]}}),n6=(0,a.Z)(n1,2),n2=n6[0],n5=n6[1],n9=function(e){if(eL){var n,t=e.key,r=n2.includes(t);n5(n=eF?r?n2.filter(function(e){return e!==t}):[].concat((0,l.Z)(n2),[t]):[t]);var o=(0,i.Z)((0,i.Z)({},e),{},{selectedKeys:n});r?null==eH||eH(o):null==eW||eW(o)}!eF&&np.length&&"inline"!==nN&&nm(ez)},n3=G(function(e){null==e2||e2(eu(e)),n9(e)}),n4=G(function(e,n){var t=np.filter(function(n){return n!==e});if(n)t.push(e);else if("inline"!==nN){var r=nY(e);t=t.filter(function(e){return!r.has(e)})}(0,p.Z)(np,t,!0)||nm(t,!0)}),n7=(ei=function(e,n){var t=null!=n?n:!np.includes(e);n4(e,t)},el=m.useRef(),(ea=m.useRef()).current=nU,ec=function(){A.Z.cancel(el.current)},m.useEffect(function(){return function(){ec()}},[]),function(e){var n=e.which;if([].concat(j,[_,V,z,F]).includes(n)){var t=nH(),r=H(t,nc),i=r,l=i.elements,a=i.key2element,u=i.element2key,c=function(e,n){for(var t=e||document.activeElement;t;){if(n.has(t))return t;t=t.parentElement}return null}(a.get(nU),l),s=u.get(c),f=function(e,n,t,r){var i,l="prev",a="next",u="children",c="parent";if("inline"===e&&r===_)return{inlineTrigger:!0};var s=(0,o.Z)((0,o.Z)({},L,l),D,a),f=(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},O,t?a:l),T,t?l:a),D,u),_,u),d=(0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)((0,o.Z)({},L,l),D,a),_,u),V,c),O,t?u:c),T,t?c:u);switch(null===(i=({inline:s,horizontal:f,vertical:d,inlineSub:s,horizontalSub:d,verticalSub:d})["".concat(e).concat(n?"":"Sub")])||void 0===i?void 0:i[r]){case l:return{offset:-1,sibling:!0};case a:return{offset:1,sibling:!0};case c:return{offset:-1,sibling:!1};case u:return{offset:1,sibling:!1};default:return null}}(nN,1===nW(s,!0).length,ns,n);if(!f&&n!==z&&n!==F)return;(j.includes(n)||[z,F].includes(n))&&e.preventDefault();var d=function(e){if(e){var n=e,t=e.querySelector("a");null!=t&&t.getAttribute("href")&&(n=t);var r=u.get(e);nJ(r),ec(),el.current=(0,A.Z)(function(){ea.current===r&&n.focus()})}};if([z,F].includes(n)||f.sibling||!c){var p,v=B(p=c&&"inline"!==nN?function(e){for(var n=e;n;){if(n.getAttribute("data-menu-list"))return n;n=n.parentElement}return null}(c):nu.current,l);d(n===z?v[0]:n===F?v[v.length-1]:W(p,l,c,f.offset))}else if(f.inlineTrigger)ei(s);else if(f.offset>0)ei(s,!0),ec(),el.current=(0,A.Z)(function(){r=H(t,nc);var e=c.getAttribute("aria-controls");d(W(document.getElementById(e),r.elements))},5);else if(f.offset<0){var m=nW(s,!0),b=m[m.length-2],y=a.get(b);ei(b,!1),d(y)}}null==e9||e9(e)});m.useEffect(function(){na(!0)},[]);var n8=m.useMemo(function(){return{_internalRenderMenuItem:e3,_internalRenderSubMenuItem:e4}},[e3,e4]),te="horizontal"!==nN||eR?nt:nt.map(function(e,n){return m.createElement(w,{key:e.key,overflowDisabled:n>nL},e)}),tn=m.createElement(f.Z,(0,r.Z)({id:eC,ref:nu,prefixCls:"".concat(ed,"-overflow"),component:"ul",itemComponent:ev,className:s()(ed,"".concat(ed,"-root"),"".concat(ed,"-").concat(nN),eb,(0,o.Z)((0,o.Z)({},"".concat(ed,"-inline-collapsed"),nK),"".concat(ed,"-rtl"),ns),ep),dir:eZ,style:em,role:"menu",tabIndex:void 0===ey?0:ey,data:te,renderRawItem:function(e){return e},renderRawRest:function(e){var n=e.length,t=n?nt.slice(-n):null;return m.createElement(eI,{eventKey:X,title:e0,disabled:n_,internalPopupClose:0===n,popupClassName:e1},t)},maxCount:"horizontal"!==nN||eR?f.Z.INVALIDATE:f.Z.RESPONSIVE,ssr:"full","data-menu-list":!0,onVisibleChange:function(e){nD(e)},onKeyDown:n7},e8));return m.createElement(P.Provider,{value:n8},m.createElement(y.Provider,{value:nc},m.createElement(w,{prefixCls:ed,rootClassName:ep,mode:nN,openKeys:np,rtl:ns,disabled:eM,motion:nl?eq:null,defaultMotions:nl?eX:null,activeKey:nU,onActive:n$,onInactive:n0,selectedKeys:n2,inlineIndent:void 0===eY?24:eY,subMenuOpenDelay:void 0===ex?.1:ex,subMenuCloseDelay:void 0===eN?.1:eN,forceSubMenuRender:eP,builtinPlacements:eQ,triggerSubMenuAction:void 0===eG?"hover":eG,getPopupContainer:e6,itemIcon:eU,expandIcon:eJ,onItemClick:n3,onOpenChange:n4},m.createElement(N.Provider,{value:nX},tn),m.createElement("div",{style:{display:"none"},"aria-hidden":!0},m.createElement(k.Provider,{value:nq},nr)))))});eF.Item=ev,eF.SubMenu=eI,eF.ItemGroup=eL,eF.Divider=eA;var ej=eF}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js b/litellm/proxy/_experimental/out/_next/static/chunks/1674-de8248fbd0c554ba.js similarity index 99% rename from litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js rename to litellm/proxy/_experimental/out/_next/static/chunks/1674-de8248fbd0c554ba.js index cd70c451170..e8fa6e80e42 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1674-475a971a192714f2.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1674-de8248fbd0c554ba.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1674],{14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return eM}});var r=n(5853),o=n(7084),i=n(26898),a=n(13241),c=n(1153),l=n(2265),s=n(60474),u=n(47625),p=n(93765),f=n(86757),d=n.n(f),y=n(61994),m=n(9841),v=n(81889),h=n(82944),b=["points","className","baseLinePoints","connectNulls"];function g(){return(g=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){k(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),k(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},j=function(e,t){var n=x(e);t&&(n=[n.reduce(function(e,t){return[].concat(A(e),A(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},w=function(e,t,n){var r=j(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(j(t.reverse(),n).slice(1))},P=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,b);if(!t||!t.length)return null;var a=(0,y.Z)("recharts-polygon",n);if(r&&r.length){var c=i.stroke&&"none"!==i.stroke,s=w(t,r,o);return l.createElement("g",{className:a},l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(t,o)})):null,c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(r,o)})):null)}var u=j(t,o);return l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},E=n(58811),S=n(41637),T=n(39206);function L(e){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(){return(R=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=I(I({},(0,h.L6)(this.props,!1)),{},{fill:"none"},(0,h.L6)(o,!1));if("circle"===i)return l.createElement(v.o,R({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var c=this.props.ticks.map(function(e){return(0,T.op)(t,n,r,e.coordinate)});return l.createElement(P,R({className:"recharts-polar-angle-axis-line"},a,{points:c}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,o=t.tick,i=t.tickLine,a=t.tickFormatter,c=t.stroke,s=(0,h.L6)(this.props,!1),u=(0,h.L6)(o,!1),p=I(I({},s),{},{fill:"none"},(0,h.L6)(i,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),d=I(I(I({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:c},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return l.createElement(m.m,R({className:(0,y.Z)("recharts-polar-angle-axis-tick",(0,T.$S)(o)),key:"tick-".concat(t.coordinate)},(0,S.bw)(e.props,t,n)),i&&l.createElement("line",R({className:"recharts-polar-angle-axis-tick-line"},p,f)),o&&r.renderTickItem(o,d,a?a(t.value,n):t.value))});return l.createElement(m.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?l.createElement(m.m,{className:(0,y.Z)("recharts-polar-angle-axis",this.props.className)},r&&this.renderAxisLine(),this.renderTicks()):null}}],n=[{key:"renderTickItem",value:function(e,t,n){return l.isValidElement(e)?l.cloneElement(e,t):d()(e)?e(t):l.createElement(E.x,R({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],t&&C(r.prototype,t),n&&C(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(l.PureComponent);Z(M,"displayName","PolarAngleAxis"),Z(M,"axisType","angleAxis"),Z(M,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var V=n(35802),$=n.n(V),q=n(37891),z=n.n(q),G=n(26680),W=["cx","cy","angle","ticks","axisLine"],Y=["ticks","tick","angle","tickFormatter","stroke"];function H(e){return(H="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function U(){return(U=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ee(e,t){for(var n=0;n0?es()(e,"paddingAngle",0):0;if(n){var c=(0,eb.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),l=ew(ew({},e),{},{startAngle:i+a,endAngle:i+c(r)+a});o.push(l),i=l.endAngle}else{var s=e.endAngle,p=e.startAngle,f=(0,eb.k4)(0,s-p)(r),d=ew(ew({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(d),i=d.endAngle}}),l.createElement(m.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!ep()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,c=t.cy,s=t.innerRadius,u=t.outerRadius,p=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eb.hj)(a)||!(0,eb.hj)(c)||!(0,eb.hj)(s)||!(0,eb.hj)(u))return null;var d=(0,y.Z)("recharts-pie",o);return l.createElement(m.m,{tabIndex:this.props.rootTabIndex,className:d,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),G._.renderCallByParent(this.props,null,!1),(!p||f)&&em.e.renderCallByParent(this.props,r,!1))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?A:A-1)*u,k=a.reduce(function(e,t){var n=(0,eg.F$)(t,g,0);return e+((0,eb.hj)(n)?n:0)},0);return k>0&&(t=a.map(function(e,t){var r,o=(0,eg.F$)(e,g,0),i=(0,eg.F$)(e,f,t),a=((0,eb.hj)(o)?o:0)/k,s=(r=t?n.endAngle+(0,eb.uY)(h)*u*(0!==o?1:0):l)+(0,eb.uY)(h)*((0!==o?m:0)+a*O),p=(r+s)/2,d=(v.innerRadius+v.outerRadius)/2,b=[{name:i,value:o,payload:e,dataKey:g,type:y}],A=(0,T.op)(v.cx,v.cy,d,p);return n=ew(ew(ew({percent:a,cornerRadius:c,name:i,tooltipPayload:b,midAngle:p,middleRadius:d,tooltipPosition:A},e),v),{},{value:(0,eg.F$)(e,g),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,eb.uY)(h)*u})})),ew(ew({},v),{},{sectors:t,data:a})});var eI=(0,p.z)({chartName:"PieChart",GraphicalChild:eN,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:M},{axisType:"radiusAxis",AxisComp:ea}],formatAxisMap:T.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eC=n(8147),eD=n(92666),eF=n(98593);let e_=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return l.createElement(eF.$B,null,l.createElement("div",{className:(0,a.q)("px-4 py-2")},l.createElement(eF.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eZ=(e,t)=>e.map((e,n)=>{let r=ne||t((0,c.vP)(n.map(e=>e[r]))),eK=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c}=e;return l.createElement("g",null,l.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c,fill:"",opacity:.3,style:{outline:"none"}}))},eM=l.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:p="name",colors:f=i.s,variant:d="donut",valueFormatter:y=c.Cj,label:m,showLabel:v=!0,animationDuration:h=900,showAnimation:b=!1,showTooltip:g=!0,noDataText:A,onValueChange:O,customTooltip:k,className:x}=e,j=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),w="donut"==d,P=eB(m,y,n,s),[E,S]=l.useState(void 0),T=!!O;return(0,l.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[E]),l.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",x)},j),l.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?l.createElement(eI,{onClick:T&&E?()=>{S(void 0),null==O||O(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},v&&w?l.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},P):null,l.createElement(eN,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",O?"cursor-pointer":"cursor-default"),data:eZ(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:w?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:p,isAnimationActive:b,animationDuration:h,onClick:function(e,t,n){n.stopPropagation(),T&&(E===t?(S(void 0),null==O||O(null)):(S(t),null==O||O(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:E,inactiveShape:eK,style:{outline:"none"}}),l.createElement(eC.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:g?e=>{var t;let{active:n,payload:r}=e;return k?l.createElement(k,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):l.createElement(e_,{active:n,payload:r,valueFormatter:y})}:l.createElement(l.Fragment,null)})):l.createElement(eD.Z,{noDataText:A})))});eM.displayName="DonutChart"},35802:function(e,t,n){var r=n(67646),o=n(58905),i=n(88157);e.exports=function(e,t){return e&&e.length?r(e,i(t,2),o):void 0}},37891:function(e,t,n){var r=n(67646),o=n(88157),i=n(20121);e.exports=function(e,t){return e&&e.length?r(e,o(t,2),i):void 0}},44633:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});t.Z=o},58710:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=o},92668:function(e,t,n){"use strict";n.d(t,{I:function(){return c}});var r=n(59121),o=n(31091),i=n(63497),a=n(99649);function c(e,t){let{years:n=0,months:c=0,weeks:l=0,days:s=0,hours:u=0,minutes:p=0,seconds:f=0}=t,d=(0,a.Q)(e),y=c||n?(0,o.z)(d,c+12*n):d,m=s||l?(0,r.E)(y,s+7*l):y;return(0,i.L)(e,m.getTime()+1e3*(f+60*(p+60*u)))}}}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1674],{14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return eM}});var r=n(5853),o=n(7084),i=n(26898),a=n(13241),c=n(1153),l=n(2265),s=n(60474),u=n(47625),p=n(93765),f=n(86757),d=n.n(f),y=n(87602),m=n(9841),v=n(81889),h=n(82944),b=["points","className","baseLinePoints","connectNulls"];function g(){return(g=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){k(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),k(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},j=function(e,t){var n=x(e);t&&(n=[n.reduce(function(e,t){return[].concat(A(e),A(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},w=function(e,t,n){var r=j(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(j(t.reverse(),n).slice(1))},P=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,b);if(!t||!t.length)return null;var a=(0,y.Z)("recharts-polygon",n);if(r&&r.length){var c=i.stroke&&"none"!==i.stroke,s=w(t,r,o);return l.createElement("g",{className:a},l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(t,o)})):null,c?l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"none",d:j(r,o)})):null)}var u=j(t,o);return l.createElement("path",g({},(0,h.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},E=n(58811),S=n(41637),T=n(39206);function L(e){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(){return(R=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=I(I({},(0,h.L6)(this.props,!1)),{},{fill:"none"},(0,h.L6)(o,!1));if("circle"===i)return l.createElement(v.o,R({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var c=this.props.ticks.map(function(e){return(0,T.op)(t,n,r,e.coordinate)});return l.createElement(P,R({className:"recharts-polar-angle-axis-line"},a,{points:c}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,o=t.tick,i=t.tickLine,a=t.tickFormatter,c=t.stroke,s=(0,h.L6)(this.props,!1),u=(0,h.L6)(o,!1),p=I(I({},s),{},{fill:"none"},(0,h.L6)(i,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),d=I(I(I({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:c},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return l.createElement(m.m,R({className:(0,y.Z)("recharts-polar-angle-axis-tick",(0,T.$S)(o)),key:"tick-".concat(t.coordinate)},(0,S.bw)(e.props,t,n)),i&&l.createElement("line",R({className:"recharts-polar-angle-axis-tick-line"},p,f)),o&&r.renderTickItem(o,d,a?a(t.value,n):t.value))});return l.createElement(m.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?l.createElement(m.m,{className:(0,y.Z)("recharts-polar-angle-axis",this.props.className)},r&&this.renderAxisLine(),this.renderTicks()):null}}],n=[{key:"renderTickItem",value:function(e,t,n){return l.isValidElement(e)?l.cloneElement(e,t):d()(e)?e(t):l.createElement(E.x,R({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],t&&C(r.prototype,t),n&&C(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(l.PureComponent);Z(M,"displayName","PolarAngleAxis"),Z(M,"axisType","angleAxis"),Z(M,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var V=n(35802),$=n.n(V),q=n(37891),z=n.n(q),G=n(26680),W=["cx","cy","angle","ticks","axisLine"],Y=["ticks","tick","angle","tickFormatter","stroke"];function H(e){return(H="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function U(){return(U=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ee(e,t){for(var n=0;n0?es()(e,"paddingAngle",0):0;if(n){var c=(0,eb.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),l=ew(ew({},e),{},{startAngle:i+a,endAngle:i+c(r)+a});o.push(l),i=l.endAngle}else{var s=e.endAngle,p=e.startAngle,f=(0,eb.k4)(0,s-p)(r),d=ew(ew({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(d),i=d.endAngle}}),l.createElement(m.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!ep()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,c=t.cy,s=t.innerRadius,u=t.outerRadius,p=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eb.hj)(a)||!(0,eb.hj)(c)||!(0,eb.hj)(s)||!(0,eb.hj)(u))return null;var d=(0,y.Z)("recharts-pie",o);return l.createElement(m.m,{tabIndex:this.props.rootTabIndex,className:d,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),G._.renderCallByParent(this.props,null,!1),(!p||f)&&em.e.renderCallByParent(this.props,r,!1))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?A:A-1)*u,k=a.reduce(function(e,t){var n=(0,eg.F$)(t,g,0);return e+((0,eb.hj)(n)?n:0)},0);return k>0&&(t=a.map(function(e,t){var r,o=(0,eg.F$)(e,g,0),i=(0,eg.F$)(e,f,t),a=((0,eb.hj)(o)?o:0)/k,s=(r=t?n.endAngle+(0,eb.uY)(h)*u*(0!==o?1:0):l)+(0,eb.uY)(h)*((0!==o?m:0)+a*O),p=(r+s)/2,d=(v.innerRadius+v.outerRadius)/2,b=[{name:i,value:o,payload:e,dataKey:g,type:y}],A=(0,T.op)(v.cx,v.cy,d,p);return n=ew(ew(ew({percent:a,cornerRadius:c,name:i,tooltipPayload:b,midAngle:p,middleRadius:d,tooltipPosition:A},e),v),{},{value:(0,eg.F$)(e,g),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,eb.uY)(h)*u})})),ew(ew({},v),{},{sectors:t,data:a})});var eI=(0,p.z)({chartName:"PieChart",GraphicalChild:eN,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:M},{axisType:"radiusAxis",AxisComp:ea}],formatAxisMap:T.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eC=n(8147),eD=n(92666),eF=n(98593);let e_=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return l.createElement(eF.$B,null,l.createElement("div",{className:(0,a.q)("px-4 py-2")},l.createElement(eF.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eZ=(e,t)=>e.map((e,n)=>{let r=ne||t((0,c.vP)(n.map(e=>e[r]))),eK=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c}=e;return l.createElement("g",null,l.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:c,fill:"",opacity:.3,style:{outline:"none"}}))},eM=l.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:p="name",colors:f=i.s,variant:d="donut",valueFormatter:y=c.Cj,label:m,showLabel:v=!0,animationDuration:h=900,showAnimation:b=!1,showTooltip:g=!0,noDataText:A,onValueChange:O,customTooltip:k,className:x}=e,j=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),w="donut"==d,P=eB(m,y,n,s),[E,S]=l.useState(void 0),T=!!O;return(0,l.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[E]),l.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",x)},j),l.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?l.createElement(eI,{onClick:T&&E?()=>{S(void 0),null==O||O(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},v&&w?l.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},P):null,l.createElement(eN,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",O?"cursor-pointer":"cursor-default"),data:eZ(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:w?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:p,isAnimationActive:b,animationDuration:h,onClick:function(e,t,n){n.stopPropagation(),T&&(E===t?(S(void 0),null==O||O(null)):(S(t),null==O||O(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:E,inactiveShape:eK,style:{outline:"none"}}),l.createElement(eC.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:g?e=>{var t;let{active:n,payload:r}=e;return k?l.createElement(k,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):l.createElement(e_,{active:n,payload:r,valueFormatter:y})}:l.createElement(l.Fragment,null)})):l.createElement(eD.Z,{noDataText:A})))});eM.displayName="DonutChart"},35802:function(e,t,n){var r=n(67646),o=n(58905),i=n(88157);e.exports=function(e,t){return e&&e.length?r(e,i(t,2),o):void 0}},37891:function(e,t,n){var r=n(67646),o=n(88157),i=n(20121);e.exports=function(e,t){return e&&e.length?r(e,o(t,2),i):void 0}},44633:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});t.Z=o},58710:function(e,t,n){"use strict";var r=n(2265);let o=r.forwardRef(function(e,t){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=o},92668:function(e,t,n){"use strict";n.d(t,{I:function(){return c}});var r=n(59121),o=n(31091),i=n(63497),a=n(99649);function c(e,t){let{years:n=0,months:c=0,weeks:l=0,days:s=0,hours:u=0,minutes:p=0,seconds:f=0}=t,d=(0,a.Q)(e),y=c||n?(0,o.z)(d,c+12*n):d,m=s||l?(0,r.E)(y,s+7*l):y;return(0,i.L)(e,m.getTime()+1e3*(f+60*(p+60*u)))}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1973-26a414084f96c69b.js b/litellm/proxy/_experimental/out/_next/static/chunks/1973-26a414084f96c69b.js new file mode 100644 index 00000000000..8ba0b21bc05 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1973-26a414084f96c69b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1973],{83669:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},5540:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"}}]},name:"clock-circle",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},41169:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 472a40 40 0 1080 0 40 40 0 10-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 01-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z"}}]},name:"experiment",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},10798:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z"}}]},name:"filter",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},8881:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z"}}]},name:"stop",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},3632:function(e,t,n){n.d(t,{Z:function(){return l}});var a=n(1119),c=n(2265),o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"},r=n(55015),l=c.forwardRef(function(e,t){return c.createElement(r.Z,(0,a.Z)({},e,{ref:t,icon:o}))})},30150:function(e,t,n){n.d(t,{Z:function(){return u}});var a=n(5853),c=n(2265);let o=e=>{var t=(0,a._T)(e,[]);return c.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),c.createElement("path",{d:"M12 4v16m8-8H4"}))},r=e=>{var t=(0,a._T)(e,[]);return c.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),c.createElement("path",{d:"M20 12H4"}))};var l=n(13241),i=n(1153),s=n(69262);let d="flex mx-auto text-tremor-content-subtle dark:text-dark-tremor-content-subtle",m="cursor-pointer hover:text-tremor-content dark:hover:text-dark-tremor-content",u=c.forwardRef((e,t)=>{let{onSubmit:n,enableStepper:u=!0,disabled:g,onValueChange:p,onChange:f}=e,h=(0,a._T)(e,["onSubmit","enableStepper","disabled","onValueChange","onChange"]),v=(0,c.useRef)(null),[b,x]=c.useState(!1),y=c.useCallback(()=>{x(!0)},[]),k=c.useCallback(()=>{x(!1)},[]),[S,w]=c.useState(!1),E=c.useCallback(()=>{w(!0)},[]),C=c.useCallback(()=>{w(!1)},[]);return c.createElement(s.Z,Object.assign({type:"number",ref:(0,i.lq)([v,t]),disabled:g,makeInputClassName:(0,i.fn)("NumberInput"),onKeyDown:e=>{var t;if("Enter"===e.key&&!e.ctrlKey&&!e.altKey&&!e.shiftKey){let e=null===(t=v.current)||void 0===t?void 0:t.value;null==n||n(parseFloat(null!=e?e:""))}"ArrowDown"===e.key&&y(),"ArrowUp"===e.key&&E()},onKeyUp:e=>{"ArrowDown"===e.key&&k(),"ArrowUp"===e.key&&C()},onChange:e=>{g||(null==p||p(parseFloat(e.target.value)),null==f||f(e))},stepper:u?c.createElement("div",{className:(0,l.q)("flex justify-center align-middle")},c.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;g||(null===(e=v.current)||void 0===e||e.stepDown(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!g&&m,d,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},c.createElement(r,{"data-testid":"step-down",className:(b?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"})),c.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;g||(null===(e=v.current)||void 0===e||e.stepUp(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!g&&m,d,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},c.createElement(o,{"data-testid":"step-up",className:(S?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"}))):null},h))});u.displayName="NumberInput"},16853:function(e,t,n){n.d(t,{Z:function(){return d}});var a=n(5853),c=n(96398),o=n(44140),r=n(2265),l=n(13241),i=n(1153);let s=(0,i.fn)("Textarea"),d=r.forwardRef((e,t)=>{let{value:n,defaultValue:d="",placeholder:m="Type...",error:u=!1,errorMessage:g,disabled:p=!1,className:f,onChange:h,onValueChange:v,autoHeight:b=!1}=e,x=(0,a._T)(e,["value","defaultValue","placeholder","error","errorMessage","disabled","className","onChange","onValueChange","autoHeight"]),[y,k]=(0,o.Z)(d,n),S=(0,r.useRef)(null),w=(0,c.Uh)(y);return(0,r.useEffect)(()=>{let e=S.current;if(b&&e){e.style.height="60px";let t=e.scrollHeight;e.style.height=t+"px"}},[b,S,y]),r.createElement(r.Fragment,null,r.createElement("textarea",Object.assign({ref:(0,i.lq)([S,t]),value:y,placeholder:m,disabled:p,className:(0,l.q)(s("Textarea"),"w-full flex items-center outline-none rounded-tremor-default px-3 py-2 text-tremor-default focus:ring-2 transition duration-100 border","shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:shadow-dark-tremor-input focus:dark:border-dark-tremor-brand-subtle focus:dark:ring-dark-tremor-brand-muted",(0,c.um)(w,p,u),p?"placeholder:text-tremor-content-subtle dark:placeholder:text-dark-tremor-content-subtle":"placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content",f),"data-testid":"text-area",onChange:e=>{null==h||h(e),k(e.target.value),null==v||v(e.target.value)}},x)),u&&g?r.createElement("p",{className:(0,l.q)(s("errorMessage"),"text-sm text-red-500 mt-1")},g):null)});d.displayName="Textarea"},67101:function(e,t,n){n.d(t,{Z:function(){return d}});var a=n(5853),c=n(13241),o=n(1153),r=n(2265),l=n(9496);let i=(0,o.fn)("Grid"),s=(e,t)=>e&&Object.keys(t).includes(String(e))?t[e]:"",d=r.forwardRef((e,t)=>{let{numItems:n=1,numItemsSm:o,numItemsMd:d,numItemsLg:m,children:u,className:g}=e,p=(0,a._T)(e,["numItems","numItemsSm","numItemsMd","numItemsLg","children","className"]),f=s(n,l._m),h=s(o,l.LH),v=s(d,l.l5),b=s(m,l.N4),x=(0,c.q)(f,h,v,b);return r.createElement("div",Object.assign({ref:t,className:(0,c.q)(i("root"),"grid",x,g)},p),u)});d.displayName="Grid"},9496:function(e,t,n){n.d(t,{LH:function(){return c},N4:function(){return r},PT:function(){return l},SP:function(){return i},VS:function(){return s},_m:function(){return a},_w:function(){return d},l5:function(){return o}});let a={0:"grid-cols-none",1:"grid-cols-1",2:"grid-cols-2",3:"grid-cols-3",4:"grid-cols-4",5:"grid-cols-5",6:"grid-cols-6",7:"grid-cols-7",8:"grid-cols-8",9:"grid-cols-9",10:"grid-cols-10",11:"grid-cols-11",12:"grid-cols-12"},c={0:"sm:grid-cols-none",1:"sm:grid-cols-1",2:"sm:grid-cols-2",3:"sm:grid-cols-3",4:"sm:grid-cols-4",5:"sm:grid-cols-5",6:"sm:grid-cols-6",7:"sm:grid-cols-7",8:"sm:grid-cols-8",9:"sm:grid-cols-9",10:"sm:grid-cols-10",11:"sm:grid-cols-11",12:"sm:grid-cols-12"},o={0:"md:grid-cols-none",1:"md:grid-cols-1",2:"md:grid-cols-2",3:"md:grid-cols-3",4:"md:grid-cols-4",5:"md:grid-cols-5",6:"md:grid-cols-6",7:"md:grid-cols-7",8:"md:grid-cols-8",9:"md:grid-cols-9",10:"md:grid-cols-10",11:"md:grid-cols-11",12:"md:grid-cols-12"},r={0:"lg:grid-cols-none",1:"lg:grid-cols-1",2:"lg:grid-cols-2",3:"lg:grid-cols-3",4:"lg:grid-cols-4",5:"lg:grid-cols-5",6:"lg:grid-cols-6",7:"lg:grid-cols-7",8:"lg:grid-cols-8",9:"lg:grid-cols-9",10:"lg:grid-cols-10",11:"lg:grid-cols-11",12:"lg:grid-cols-12"},l={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12",13:"col-span-13"},i={1:"sm:col-span-1",2:"sm:col-span-2",3:"sm:col-span-3",4:"sm:col-span-4",5:"sm:col-span-5",6:"sm:col-span-6",7:"sm:col-span-7",8:"sm:col-span-8",9:"sm:col-span-9",10:"sm:col-span-10",11:"sm:col-span-11",12:"sm:col-span-12",13:"sm:col-span-13"},s={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12",13:"md:col-span-13"},d={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12",13:"lg:col-span-13"}},44140:function(e,t,n){n.d(t,{Z:function(){return c}});var a=n(2265);let c=(e,t)=>{let n=void 0!==t,[c,o]=(0,a.useState)(e);return[n?t:c,e=>{n||o(e)}]}},35631:function(e,t,n){n.d(t,{Z:function(){return I}});var a=n(83145),c=n(2265),o=n(36760),r=n.n(o),l=n(53253),i=n(6543),s=n(71744),d=n(91086),m=n(33759),u=n(77774),g=n(28617),p=n(40049),f=n(10353);let h=c.createContext({});h.Consumer;var v=n(19722),b=n(54998),x=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 c=0,a=Object.getOwnPropertySymbols(e);ct.indexOf(a[c])&&Object.prototype.propertyIsEnumerable.call(e,a[c])&&(n[a[c]]=e[a[c]]);return n};let y=c.forwardRef((e,t)=>{let n;let{prefixCls:a,children:o,actions:l,extra:i,styles:d,className:m,classNames:u,colStyle:g}=e,p=x(e,["prefixCls","children","actions","extra","styles","className","classNames","colStyle"]),{grid:f,itemLayout:y}=(0,c.useContext)(h),{getPrefixCls:k,list:S}=(0,c.useContext)(s.E_),w=e=>{var t,n;return r()(null===(n=null===(t=null==S?void 0:S.item)||void 0===t?void 0:t.classNames)||void 0===n?void 0:n[e],null==u?void 0:u[e])},E=e=>{var t,n;return Object.assign(Object.assign({},null===(n=null===(t=null==S?void 0:S.item)||void 0===t?void 0:t.styles)||void 0===n?void 0:n[e]),null==d?void 0:d[e])},C=k("list",a),N=l&&l.length>0&&c.createElement("ul",{className:r()("".concat(C,"-item-action"),w("actions")),key:"actions",style:E("actions")},l.map((e,t)=>c.createElement("li",{key:"".concat(C,"-item-action-").concat(t)},e,t!==l.length-1&&c.createElement("em",{className:"".concat(C,"-item-action-split")})))),z=c.createElement(f?"div":"li",Object.assign({},p,f?{}:{ref:t},{className:r()("".concat(C,"-item"),{["".concat(C,"-item-no-flex")]:!("vertical"===y?!!i:(n=!1,c.Children.forEach(o,e=>{"string"==typeof e&&(n=!0)}),!(n&&c.Children.count(o)>1)))},m)}),"vertical"===y&&i?[c.createElement("div",{className:"".concat(C,"-item-main"),key:"content"},o,N),c.createElement("div",{className:r()("".concat(C,"-item-extra"),w("extra")),key:"extra",style:E("extra")},i)]:[o,N,(0,v.Tm)(i,{key:"extra"})]);return f?c.createElement(b.Z,{ref:t,flex:1,style:g},z):z});y.Meta=e=>{var{prefixCls:t,className:n,avatar:a,title:o,description:l}=e,i=x(e,["prefixCls","className","avatar","title","description"]);let{getPrefixCls:d}=(0,c.useContext)(s.E_),m=d("list",t),u=r()("".concat(m,"-item-meta"),n),g=c.createElement("div",{className:"".concat(m,"-item-meta-content")},o&&c.createElement("h4",{className:"".concat(m,"-item-meta-title")},o),l&&c.createElement("div",{className:"".concat(m,"-item-meta-description")},l));return c.createElement("div",Object.assign({},i,{className:u}),a&&c.createElement("div",{className:"".concat(m,"-item-meta-avatar")},a),(o||l)&&g)};var k=n(93463),S=n(12918),w=n(99320),E=n(71140);let C=e=>{let{listBorderedCls:t,componentCls:n,paddingLG:a,margin:c,itemPaddingSM:o,itemPaddingLG:r,marginLG:l,borderRadiusLG:i}=e,s=(0,k.bf)(e.calc(i).sub(e.lineWidth).equal());return{[t]:{border:"".concat((0,k.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:i,["".concat(n,"-header")]:{borderRadius:"".concat(s," ").concat(s," 0 0")},["".concat(n,"-footer")]:{borderRadius:"0 0 ".concat(s," ").concat(s)},["".concat(n,"-header,").concat(n,"-footer,").concat(n,"-item")]:{paddingInline:a},["".concat(n,"-pagination")]:{margin:"".concat((0,k.bf)(c)," ").concat((0,k.bf)(l))}},["".concat(t).concat(n,"-sm")]:{["".concat(n,"-item,").concat(n,"-header,").concat(n,"-footer")]:{padding:o}},["".concat(t).concat(n,"-lg")]:{["".concat(n,"-item,").concat(n,"-header,").concat(n,"-footer")]:{padding:r}}}},N=e=>{let{componentCls:t,screenSM:n,screenMD:a,marginLG:c,marginSM:o,margin:r}=e;return{["@media screen and (max-width:".concat(a,"px)")]:{[t]:{["".concat(t,"-item")]:{["".concat(t,"-item-action")]:{marginInlineStart:c}}},["".concat(t,"-vertical")]:{["".concat(t,"-item")]:{["".concat(t,"-item-extra")]:{marginInlineStart:c}}}},["@media screen and (max-width: ".concat(n,"px)")]:{[t]:{["".concat(t,"-item")]:{flexWrap:"wrap",["".concat(t,"-action")]:{marginInlineStart:o}}},["".concat(t,"-vertical")]:{["".concat(t,"-item")]:{flexWrap:"wrap-reverse",["".concat(t,"-item-main")]:{minWidth:e.contentWidth},["".concat(t,"-item-extra")]:{margin:"auto auto ".concat((0,k.bf)(r))}}}}}},z=e=>{let{componentCls:t,antCls:n,controlHeight:a,minHeight:c,paddingSM:o,marginLG:r,padding:l,itemPadding:i,colorPrimary:s,itemPaddingSM:d,itemPaddingLG:m,paddingXS:u,margin:g,colorText:p,colorTextDescription:f,motionDurationSlow:h,lineWidth:v,headerBg:b,footerBg:x,emptyTextPadding:y,metaMarginBottom:w,avatarMarginRight:E,titleMarginBottom:C,descriptionFontSize:N}=e;return{[t]:Object.assign(Object.assign({},(0,S.Wf)(e)),{position:"relative","--rc-virtual-list-scrollbar-bg":e.colorSplit,"*":{outline:"none"},["".concat(t,"-header")]:{background:b},["".concat(t,"-footer")]:{background:x},["".concat(t,"-header, ").concat(t,"-footer")]:{paddingBlock:o},["".concat(t,"-pagination")]:{marginBlockStart:r,["".concat(n,"-pagination-options")]:{textAlign:"start"}},["".concat(t,"-spin")]:{minHeight:c,textAlign:"center"},["".concat(t,"-items")]:{margin:0,padding:0,listStyle:"none"},["".concat(t,"-item")]:{display:"flex",alignItems:"center",justifyContent:"space-between",padding:i,color:p,["".concat(t,"-item-meta")]:{display:"flex",flex:1,alignItems:"flex-start",maxWidth:"100%",["".concat(t,"-item-meta-avatar")]:{marginInlineEnd:E},["".concat(t,"-item-meta-content")]:{flex:"1 0",width:0,color:p},["".concat(t,"-item-meta-title")]:{margin:"0 0 ".concat((0,k.bf)(e.marginXXS)," 0"),color:p,fontSize:e.fontSize,lineHeight:e.lineHeight,"> a":{color:p,transition:"all ".concat(h),"&:hover":{color:s}}},["".concat(t,"-item-meta-description")]:{color:f,fontSize:N,lineHeight:e.lineHeight}},["".concat(t,"-item-action")]:{flex:"0 0 auto",marginInlineStart:e.marginXXL,padding:0,fontSize:0,listStyle:"none","& > li":{position:"relative",display:"inline-block",padding:"0 ".concat((0,k.bf)(u)),color:f,fontSize:e.fontSize,lineHeight:e.lineHeight,textAlign:"center","&:first-child":{paddingInlineStart:0}},["".concat(t,"-item-action-split")]:{position:"absolute",insetBlockStart:"50%",insetInlineEnd:0,width:v,height:e.calc(e.fontHeight).sub(e.calc(e.marginXXS).mul(2)).equal(),transform:"translateY(-50%)",backgroundColor:e.colorSplit}}},["".concat(t,"-empty")]:{padding:"".concat((0,k.bf)(l)," 0"),color:f,fontSize:e.fontSizeSM,textAlign:"center"},["".concat(t,"-empty-text")]:{padding:y,color:e.colorTextDisabled,fontSize:e.fontSize,textAlign:"center"},["".concat(t,"-item-no-flex")]:{display:"block"}}),["".concat(t,"-grid ").concat(n,"-col > ").concat(t,"-item")]:{display:"block",maxWidth:"100%",marginBlockEnd:g,paddingBlock:0,borderBlockEnd:"none"},["".concat(t,"-vertical ").concat(t,"-item")]:{alignItems:"initial",["".concat(t,"-item-main")]:{display:"block",flex:1},["".concat(t,"-item-extra")]:{marginInlineStart:r},["".concat(t,"-item-meta")]:{marginBlockEnd:w,["".concat(t,"-item-meta-title")]:{marginBlockStart:0,marginBlockEnd:C,color:p,fontSize:e.fontSizeLG,lineHeight:e.lineHeightLG}},["".concat(t,"-item-action")]:{marginBlockStart:l,marginInlineStart:"auto","> li":{padding:"0 ".concat((0,k.bf)(l)),"&:first-child":{paddingInlineStart:0}}}},["".concat(t,"-split ").concat(t,"-item")]:{borderBlockEnd:"".concat((0,k.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit),"&:last-child":{borderBlockEnd:"none"}},["".concat(t,"-split ").concat(t,"-header")]:{borderBlockEnd:"".concat((0,k.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit)},["".concat(t,"-split").concat(t,"-empty ").concat(t,"-footer")]:{borderTop:"".concat((0,k.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit)},["".concat(t,"-loading ").concat(t,"-spin-nested-loading")]:{minHeight:a},["".concat(t,"-split").concat(t,"-something-after-last-item ").concat(n,"-spin-container > ").concat(t,"-items > ").concat(t,"-item:last-child")]:{borderBlockEnd:"".concat((0,k.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit)},["".concat(t,"-lg ").concat(t,"-item")]:{padding:m},["".concat(t,"-sm ").concat(t,"-item")]:{padding:d},["".concat(t,":not(").concat(t,"-vertical)")]:{["".concat(t,"-item-no-flex")]:{["".concat(t,"-item-action")]:{float:"right"}}}}};var M=(0,w.I$)("List",e=>{let t=(0,E.IX)(e,{listBorderedCls:"".concat(e.componentCls,"-bordered"),minHeight:e.controlHeightLG});return[z(t),C(t),N(t)]},e=>({contentWidth:220,itemPadding:"".concat((0,k.bf)(e.paddingContentVertical)," 0"),itemPaddingSM:"".concat((0,k.bf)(e.paddingContentVerticalSM)," ").concat((0,k.bf)(e.paddingContentHorizontal)),itemPaddingLG:"".concat((0,k.bf)(e.paddingContentVerticalLG)," ").concat((0,k.bf)(e.paddingContentHorizontalLG)),headerBg:"transparent",footerBg:"transparent",emptyTextPadding:e.padding,metaMarginBottom:e.padding,avatarMarginRight:e.padding,titleMarginBottom:e.paddingSM,descriptionFontSize:e.fontSize})),O=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 c=0,a=Object.getOwnPropertySymbols(e);ct.indexOf(a[c])&&Object.prototype.propertyIsEnumerable.call(e,a[c])&&(n[a[c]]=e[a[c]]);return n};let Z=c.forwardRef(function(e,t){let{pagination:n=!1,prefixCls:o,bordered:v=!1,split:b=!0,className:x,rootClassName:y,style:k,children:S,itemLayout:w,loadMore:E,grid:C,dataSource:N=[],size:z,header:Z,footer:I,loading:j=!1,rowKey:H,renderItem:B,locale:L}=e,T=O(e,["pagination","prefixCls","bordered","split","className","rootClassName","style","children","itemLayout","loadMore","grid","dataSource","size","header","footer","loading","rowKey","renderItem","locale"]),V=n&&"object"==typeof n?n:{},[R,W]=c.useState(V.defaultCurrent||1),[_,P]=c.useState(V.defaultPageSize||10),{getPrefixCls:D,direction:q,className:A,style:G}=(0,s.dj)("list"),{renderEmpty:U}=c.useContext(s.E_),X=e=>(t,a)=>{var c;W(t),P(a),n&&(null===(c=null==n?void 0:n[e])||void 0===c||c.call(n,t,a))},K=X("onChange"),F=X("onShowSizeChange"),J=!!(E||n||I),Y=D("list",o),[$,Q,ee]=M(Y),et=j;"boolean"==typeof et&&(et={spinning:et});let en=!!(null==et?void 0:et.spinning),ea=(0,m.Z)(z),ec="";switch(ea){case"large":ec="lg";break;case"small":ec="sm"}let eo=r()(Y,{["".concat(Y,"-vertical")]:"vertical"===w,["".concat(Y,"-").concat(ec)]:ec,["".concat(Y,"-split")]:b,["".concat(Y,"-bordered")]:v,["".concat(Y,"-loading")]:en,["".concat(Y,"-grid")]:!!C,["".concat(Y,"-something-after-last-item")]:J,["".concat(Y,"-rtl")]:"rtl"===q},A,x,y,Q,ee),er=(0,l.Z)({current:1,total:0,position:"bottom"},{total:N.length,current:R,pageSize:_},n||{}),el=Math.ceil(er.total/er.pageSize);er.current=Math.min(er.current,el);let ei=n&&c.createElement("div",{className:r()("".concat(Y,"-pagination"))},c.createElement(p.Z,Object.assign({align:"end"},er,{onChange:K,onShowSizeChange:F}))),es=(0,a.Z)(N);n&&N.length>(er.current-1)*er.pageSize&&(es=(0,a.Z)(N).splice((er.current-1)*er.pageSize,er.pageSize));let ed=Object.keys(C||{}).some(e=>["xs","sm","md","lg","xl","xxl"].includes(e)),em=(0,g.Z)(ed),eu=c.useMemo(()=>{for(let e=0;e{if(!C)return;let e=eu&&C[eu]?C[eu]:C.column;if(e)return{width:"".concat(100/e,"%"),maxWidth:"".concat(100/e,"%")}},[JSON.stringify(C),eu]),ep=en&&c.createElement("div",{style:{minHeight:53}});if(es.length>0){let e=es.map((e,t)=>{let n;return B?((n="function"==typeof H?H(e):H?e[H]:e.key)||(n="list-item-".concat(t)),c.createElement(c.Fragment,{key:n},B(e,t))):null});ep=C?c.createElement(u.Z,{gutter:C.gutter},c.Children.map(e,e=>c.createElement("div",{key:null==e?void 0:e.key,style:eg},e))):c.createElement("ul",{className:"".concat(Y,"-items")},e)}else S||en||(ep=c.createElement("div",{className:"".concat(Y,"-empty-text")},(null==L?void 0:L.emptyText)||(null==U?void 0:U("List"))||c.createElement(d.Z,{componentName:"List"})));let ef=er.position,eh=c.useMemo(()=>({grid:C,itemLayout:w}),[JSON.stringify(C),w]);return $(c.createElement(h.Provider,{value:eh},c.createElement("div",Object.assign({ref:t,style:Object.assign(Object.assign({},G),k),className:eo},T),("top"===ef||"both"===ef)&&ei,Z&&c.createElement("div",{className:"".concat(Y,"-header")},Z),c.createElement(f.Z,Object.assign({},et),ep,S),I&&c.createElement("div",{className:"".concat(Y,"-footer")},I),E||("bottom"===ef||"both"===ef)&&ei)))});Z.Item=y;var I=Z},30401:function(e,t,n){n.d(t,{Z:function(){return a}});let a=(0,n(79205).Z)("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]])},10900:function(e,t,n){var a=n(2265);let c=a.forwardRef(function(e,t){return a.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),a.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"}))});t.Z=c}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js b/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js deleted file mode 100644 index 90f29480d63..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1994],{4156:function(e,n,t){t.d(n,{Z:function(){return O}});var o=t(2265),a=t(36760),r=t.n(a),c=t(20873),l=t(28791),i=t(6694),s=t(34709),u=t(71744),d=t(86586),b=t(64024),p=t(39109);let f=o.createContext(null);var v=t(23159),m=t(66531),h=function(e,n){var t={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>n.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let g=o.forwardRef((e,n)=>{var t;let{prefixCls:a,className:g,rootClassName:C,children:y,indeterminate:k=!1,style:x,onMouseEnter:O,onMouseLeave:E,skipGroup:S=!1,disabled:w}=e,Z=h(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:P,direction:N,checkbox:j}=o.useContext(u.E_),I=o.useContext(f),{isFormItemInput:R}=o.useContext(p.aM),z=o.useContext(d.Z),B=null!==(t=(null==I?void 0:I.disabled)||w)&&void 0!==t?t:z,D=o.useRef(Z.value),M=o.useRef(null),_=(0,l.sQ)(n,M);o.useEffect(()=>{null==I||I.registerValue(Z.value)},[]),o.useEffect(()=>{if(!S)return Z.value!==D.current&&(null==I||I.cancelValue(D.current),null==I||I.registerValue(Z.value),D.current=Z.value),()=>null==I?void 0:I.cancelValue(Z.value)},[Z.value]),o.useEffect(()=>{var e;(null===(e=M.current)||void 0===e?void 0:e.input)&&(M.current.input.indeterminate=k)},[k]);let W=P("checkbox",a),q=(0,b.Z)(W),[H,T,G]=(0,v.ZP)(W,q),V=Object.assign({},Z);I&&!S&&(V.onChange=function(){for(var e=arguments.length,n=Array(e),t=0;tn.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let x=o.forwardRef((e,n)=>{let{defaultValue:t,children:a,options:c=[],prefixCls:l,className:i,rootClassName:s,style:d,onChange:p}=e,m=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:x}=o.useContext(u.E_),[O,E]=o.useState(m.value||t||[]),[S,w]=o.useState([]);o.useEffect(()=>{"value"in m&&E(m.value||[])},[m.value]);let Z=o.useMemo(()=>c.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[c]),P=e=>{w(n=>n.filter(n=>n!==e))},N=e=>{w(n=>[].concat((0,C.Z)(n),[e]))},j=e=>{let n=O.indexOf(e.value),t=(0,C.Z)(O);-1===n?t.push(e.value):t.splice(n,1),"value"in m||E(t),null==p||p(t.filter(e=>S.includes(e)).sort((e,n)=>Z.findIndex(n=>n.value===e)-Z.findIndex(e=>e.value===n)))},I=h("checkbox",l),R="".concat(I,"-group"),z=(0,b.Z)(I),[B,D,M]=(0,v.ZP)(I,z),_=(0,y.Z)(m,["value","disabled"]),W=c.length?Z.map(e=>o.createElement(g,{prefixCls:I,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:O.includes(e.value),onChange:e.onChange,className:r()("".concat(R,"-item"),e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):a,q=o.useMemo(()=>({toggleOption:j,value:O,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:P}),[j,O,m.disabled,m.name,N,P]),H=r()(R,{["".concat(R,"-rtl")]:"rtl"===x},i,s,M,z,D);return B(o.createElement("div",Object.assign({className:H,style:d},_,{ref:n}),o.createElement(f.Provider,{value:q},W)))});g.Group=x,g.__ANT_CHECKBOX=!0;var O=g},23159:function(e,n,t){t.d(n,{C2:function(){return i}});var o=t(93463),a=t(12918),r=t(71140),c=t(99320);let l=e=>{let{checkboxCls:n}=e,t="".concat(n,"-wrapper");return[{["".concat(n,"-group")]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,["> ".concat(e.antCls,"-row")]:{flex:1}}),[t]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},["& + ".concat(t)]:{marginInlineStart:0},["&".concat(t,"-in-form-item")]:{'input[type="checkbox"]':{width:14,height:14}}}),[n]:Object.assign(Object.assign({},(0,a.Wf)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",["".concat(n,"-input")]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,["&:focus-visible + ".concat(n,"-inner")]:(0,a.oN)(e)},["".concat(n,"-inner")]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:"".concat((0,o.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:"all ".concat(e.motionDurationSlow),"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:"".concat((0,o.bf)(e.lineWidthBold)," solid ").concat(e.colorWhite),borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:"all ".concat(e.motionDurationFast," ").concat(e.motionEaseInBack,", opacity ").concat(e.motionDurationFast)}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{["\n ".concat(t,":not(").concat(t,"-disabled),\n ").concat(n,":not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{borderColor:e.colorPrimary}},["".concat(t,":not(").concat(t,"-disabled)")]:{["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled) ").concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled):after")]:{borderColor:e.colorPrimaryHover}}},{["".concat(n,"-checked")]:{["".concat(n,"-inner")]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:"all ".concat(e.motionDurationMid," ").concat(e.motionEaseOutBack," ").concat(e.motionDurationFast)}}},["\n ".concat(t,"-checked:not(").concat(t,"-disabled),\n ").concat(n,"-checked:not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[n]:{"&-indeterminate":{"&":{["".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorBorder),"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},["&:hover ".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorPrimary)}}}}},{["".concat(t,"-disabled")]:{cursor:"not-allowed"},["".concat(n,"-disabled")]:{["&, ".concat(n,"-input")]:{cursor:"not-allowed",pointerEvents:"none"},["".concat(n,"-inner")]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},["&".concat(n,"-indeterminate ").concat(n,"-inner::after")]:{background:e.colorTextDisabled}}}]};function i(e,n){return l((0,r.IX)(n,{checkboxCls:".".concat(e),checkboxSize:n.controlInteractiveSize}))}n.ZP=(0,c.I$)("Checkbox",(e,n)=>{let{prefixCls:t}=n;return[i(t,e)]})},66531:function(e,n,t){t.d(n,{Z:function(){return r}});var o=t(2265),a=t(53346);function r(e){let n=o.useRef(null),t=()=>{a.Z.cancel(n.current),n.current=null};return[()=>{t(),n.current=(0,a.Z)(()=>{n.current=null})},o=>{n.current&&(o.stopPropagation(),t()),null==e||e(o)}]}},20873:function(e,n,t){var o=t(1119),a=t(31686),r=t(11993),c=t(26365),l=t(6989),i=t(36760),s=t.n(i),u=t(50506),d=t(2265),b=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],p=(0,d.forwardRef)(function(e,n){var t=e.prefixCls,i=void 0===t?"rc-checkbox":t,p=e.className,f=e.style,v=e.checked,m=e.disabled,h=e.defaultChecked,g=e.type,C=void 0===g?"checkbox":g,y=e.title,k=e.onChange,x=(0,l.Z)(e,b),O=(0,d.useRef)(null),E=(0,d.useRef)(null),S=(0,u.Z)(void 0!==h&&h,{value:v}),w=(0,c.Z)(S,2),Z=w[0],P=w[1];(0,d.useImperativeHandle)(n,function(){return{focus:function(e){var n;null===(n=O.current)||void 0===n||n.focus(e)},blur:function(){var e;null===(e=O.current)||void 0===e||e.blur()},input:O.current,nativeElement:E.current}});var N=s()(i,p,(0,r.Z)((0,r.Z)({},"".concat(i,"-checked"),Z),"".concat(i,"-disabled"),m));return d.createElement("span",{className:N,title:y,style:f,ref:E},d.createElement("input",(0,o.Z)({},x,{className:"".concat(i,"-input"),ref:O,onChange:function(n){m||("checked"in e||P(n.target.checked),null==k||k({target:(0,a.Z)((0,a.Z)({},e),{},{type:C,checked:n.target.checked}),stopPropagation:function(){n.stopPropagation()},preventDefault:function(){n.preventDefault()},nativeEvent:n.nativeEvent}))},disabled:m,checked:!!Z,type:C})),d.createElement("span",{className:"".concat(i,"-inner")}))});n.Z=p}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js b/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js new file mode 100644 index 00000000000..3211472683d --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1994-a4d0b99849c16b62.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1994],{61994:function(e,n,t){t.d(n,{Z:function(){return O}});var o=t(2265),a=t(36760),r=t.n(a),c=t(20873),l=t(28791),i=t(6694),s=t(34709),u=t(71744),d=t(86586),b=t(64024),p=t(39109);let f=o.createContext(null);var v=t(23159),m=t(66531),h=function(e,n){var t={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>n.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let g=o.forwardRef((e,n)=>{var t;let{prefixCls:a,className:g,rootClassName:C,children:y,indeterminate:k=!1,style:x,onMouseEnter:O,onMouseLeave:E,skipGroup:S=!1,disabled:w}=e,Z=h(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:P,direction:N,checkbox:j}=o.useContext(u.E_),I=o.useContext(f),{isFormItemInput:R}=o.useContext(p.aM),z=o.useContext(d.Z),B=null!==(t=(null==I?void 0:I.disabled)||w)&&void 0!==t?t:z,D=o.useRef(Z.value),M=o.useRef(null),_=(0,l.sQ)(n,M);o.useEffect(()=>{null==I||I.registerValue(Z.value)},[]),o.useEffect(()=>{if(!S)return Z.value!==D.current&&(null==I||I.cancelValue(D.current),null==I||I.registerValue(Z.value),D.current=Z.value),()=>null==I?void 0:I.cancelValue(Z.value)},[Z.value]),o.useEffect(()=>{var e;(null===(e=M.current)||void 0===e?void 0:e.input)&&(M.current.input.indeterminate=k)},[k]);let W=P("checkbox",a),q=(0,b.Z)(W),[H,T,G]=(0,v.ZP)(W,q),V=Object.assign({},Z);I&&!S&&(V.onChange=function(){for(var e=arguments.length,n=Array(e),t=0;tn.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let x=o.forwardRef((e,n)=>{let{defaultValue:t,children:a,options:c=[],prefixCls:l,className:i,rootClassName:s,style:d,onChange:p}=e,m=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:x}=o.useContext(u.E_),[O,E]=o.useState(m.value||t||[]),[S,w]=o.useState([]);o.useEffect(()=>{"value"in m&&E(m.value||[])},[m.value]);let Z=o.useMemo(()=>c.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[c]),P=e=>{w(n=>n.filter(n=>n!==e))},N=e=>{w(n=>[].concat((0,C.Z)(n),[e]))},j=e=>{let n=O.indexOf(e.value),t=(0,C.Z)(O);-1===n?t.push(e.value):t.splice(n,1),"value"in m||E(t),null==p||p(t.filter(e=>S.includes(e)).sort((e,n)=>Z.findIndex(n=>n.value===e)-Z.findIndex(e=>e.value===n)))},I=h("checkbox",l),R="".concat(I,"-group"),z=(0,b.Z)(I),[B,D,M]=(0,v.ZP)(I,z),_=(0,y.Z)(m,["value","disabled"]),W=c.length?Z.map(e=>o.createElement(g,{prefixCls:I,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:O.includes(e.value),onChange:e.onChange,className:r()("".concat(R,"-item"),e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):a,q=o.useMemo(()=>({toggleOption:j,value:O,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:P}),[j,O,m.disabled,m.name,N,P]),H=r()(R,{["".concat(R,"-rtl")]:"rtl"===x},i,s,M,z,D);return B(o.createElement("div",Object.assign({className:H,style:d},_,{ref:n}),o.createElement(f.Provider,{value:q},W)))});g.Group=x,g.__ANT_CHECKBOX=!0;var O=g},23159:function(e,n,t){t.d(n,{C2:function(){return i}});var o=t(93463),a=t(12918),r=t(71140),c=t(99320);let l=e=>{let{checkboxCls:n}=e,t="".concat(n,"-wrapper");return[{["".concat(n,"-group")]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,["> ".concat(e.antCls,"-row")]:{flex:1}}),[t]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},["& + ".concat(t)]:{marginInlineStart:0},["&".concat(t,"-in-form-item")]:{'input[type="checkbox"]':{width:14,height:14}}}),[n]:Object.assign(Object.assign({},(0,a.Wf)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",["".concat(n,"-input")]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,["&:focus-visible + ".concat(n,"-inner")]:(0,a.oN)(e)},["".concat(n,"-inner")]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:"".concat((0,o.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:"all ".concat(e.motionDurationSlow),"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:"".concat((0,o.bf)(e.lineWidthBold)," solid ").concat(e.colorWhite),borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:"all ".concat(e.motionDurationFast," ").concat(e.motionEaseInBack,", opacity ").concat(e.motionDurationFast)}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{["\n ".concat(t,":not(").concat(t,"-disabled),\n ").concat(n,":not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{borderColor:e.colorPrimary}},["".concat(t,":not(").concat(t,"-disabled)")]:{["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled) ").concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled):after")]:{borderColor:e.colorPrimaryHover}}},{["".concat(n,"-checked")]:{["".concat(n,"-inner")]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:"all ".concat(e.motionDurationMid," ").concat(e.motionEaseOutBack," ").concat(e.motionDurationFast)}}},["\n ".concat(t,"-checked:not(").concat(t,"-disabled),\n ").concat(n,"-checked:not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[n]:{"&-indeterminate":{"&":{["".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorBorder),"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},["&:hover ".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorPrimary)}}}}},{["".concat(t,"-disabled")]:{cursor:"not-allowed"},["".concat(n,"-disabled")]:{["&, ".concat(n,"-input")]:{cursor:"not-allowed",pointerEvents:"none"},["".concat(n,"-inner")]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},["&".concat(n,"-indeterminate ").concat(n,"-inner::after")]:{background:e.colorTextDisabled}}}]};function i(e,n){return l((0,r.IX)(n,{checkboxCls:".".concat(e),checkboxSize:n.controlInteractiveSize}))}n.ZP=(0,c.I$)("Checkbox",(e,n)=>{let{prefixCls:t}=n;return[i(t,e)]})},66531:function(e,n,t){t.d(n,{Z:function(){return r}});var o=t(2265),a=t(53346);function r(e){let n=o.useRef(null),t=()=>{a.Z.cancel(n.current),n.current=null};return[()=>{t(),n.current=(0,a.Z)(()=>{n.current=null})},o=>{n.current&&(o.stopPropagation(),t()),null==e||e(o)}]}},20873:function(e,n,t){var o=t(1119),a=t(31686),r=t(11993),c=t(26365),l=t(6989),i=t(36760),s=t.n(i),u=t(50506),d=t(2265),b=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],p=(0,d.forwardRef)(function(e,n){var t=e.prefixCls,i=void 0===t?"rc-checkbox":t,p=e.className,f=e.style,v=e.checked,m=e.disabled,h=e.defaultChecked,g=e.type,C=void 0===g?"checkbox":g,y=e.title,k=e.onChange,x=(0,l.Z)(e,b),O=(0,d.useRef)(null),E=(0,d.useRef)(null),S=(0,u.Z)(void 0!==h&&h,{value:v}),w=(0,c.Z)(S,2),Z=w[0],P=w[1];(0,d.useImperativeHandle)(n,function(){return{focus:function(e){var n;null===(n=O.current)||void 0===n||n.focus(e)},blur:function(){var e;null===(e=O.current)||void 0===e||e.blur()},input:O.current,nativeElement:E.current}});var N=s()(i,p,(0,r.Z)((0,r.Z)({},"".concat(i,"-checked"),Z),"".concat(i,"-disabled"),m));return d.createElement("span",{className:N,title:y,style:f,ref:E},d.createElement("input",(0,o.Z)({},x,{className:"".concat(i,"-input"),ref:O,onChange:function(n){m||("checked"in e||P(n.target.checked),null==k||k({target:(0,a.Z)((0,a.Z)({},e),{},{type:C,checked:n.target.checked}),stopPropagation:function(){n.stopPropagation()},preventDefault:function(){n.preventDefault()},nativeEvent:n.nativeEvent}))},disabled:m,checked:!!Z,type:C})),d.createElement("span",{className:"".concat(i,"-inner")}))});n.Z=p}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2004-294ce010a90069b4.js b/litellm/proxy/_experimental/out/_next/static/chunks/2004-294ce010a90069b4.js new file mode 100644 index 00000000000..280fafea373 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2004-294ce010a90069b4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2004],{22004:function(e,l,s){s.d(l,{Z:function(){return ee},g:function(){return X}});var i=s(57437),a=s(2265),r=s(41649),t=s(78489),n=s(12514),o=s(49804),d=s(67101),c=s(47323),m=s(12485),u=s(18135),x=s(35242),h=s(29706),_=s(77991),g=s(21626),j=s(97214),p=s(28241),v=s(58834),Z=s(69552),b=s(71876),f=s(84264),w=s(24199),z=s(4260),y=s(10032),N=s(99981),C=s(22116),S=s(37592),O=s(15424),M=s(23628),k=s(86462),I=s(47686),P=s(53410),A=s(74998),T=s(31283),D=s(46468),F=s(59872),L=s(10900),R=s(49566),U=s(96761),E=s(5545),B=s(30401),V=s(78867),q=s(33860),G=s(95920),W=s(9114),$=s(19250),J=s(98015),Q=s(10901),Y=s(97415),H=e=>{var l,s,o,N,C;let{organizationId:O,onClose:M,accessToken:k,is_org_admin:I,is_proxy_admin:T,userModels:H,editOrg:K}=e,[X,ee]=(0,a.useState)(null),[el,es]=(0,a.useState)(!0),[ei]=y.Z.useForm(),[ea,er]=(0,a.useState)(!1),[et,en]=(0,a.useState)(!1),[eo,ed]=(0,a.useState)(!1),[ec,em]=(0,a.useState)(null),[eu,ex]=(0,a.useState)({}),[eh,e_]=(0,a.useState)(!1),eg=I||T,ej=async()=>{try{if(es(!0),!k)return;let e=await (0,$.organizationInfoCall)(k,O);ee(e)}catch(e){W.Z.fromBackend("Failed to load organization information"),console.error("Error fetching organization info:",e)}finally{es(!1)}};(0,a.useEffect)(()=>{ej()},[O,k]);let ep=async e=>{try{if(null==k)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,$.organizationMemberAddCall)(k,O,l),W.Z.success("Organization member added successfully"),en(!1),ei.resetFields(),ej()}catch(e){W.Z.fromBackend("Failed to add organization member"),console.error("Error adding organization member:",e)}},ev=async e=>{try{if(!k)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,$.organizationMemberUpdateCall)(k,O,l),W.Z.success("Organization member updated successfully"),ed(!1),ei.resetFields(),ej()}catch(e){W.Z.fromBackend("Failed to update organization member"),console.error("Error updating organization member:",e)}},eZ=async e=>{try{if(!k)return;await (0,$.organizationMemberDeleteCall)(k,O,e.user_id),W.Z.success("Organization member deleted successfully"),ed(!1),ei.resetFields(),ej()}catch(e){W.Z.fromBackend("Failed to delete organization member"),console.error("Error deleting organization member:",e)}},eb=async e=>{try{if(!k)return;e_(!0);let l={organization_id:O,organization_alias:e.organization_alias,models:e.models,litellm_budget_table:{tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit,max_budget:e.max_budget,budget_duration:e.budget_duration},metadata:e.metadata?JSON.parse(e.metadata):null};if((void 0!==e.vector_stores||void 0!==e.mcp_servers_and_groups)&&(l.object_permission={...null==X?void 0:X.object_permission,vector_stores:e.vector_stores||[]},void 0!==e.mcp_servers_and_groups)){let{servers:s,accessGroups:i}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]};s&&s.length>0&&(l.object_permission.mcp_servers=s),i&&i.length>0&&(l.object_permission.mcp_access_groups=i)}await (0,$.organizationUpdateCall)(k,l),W.Z.success("Organization settings updated successfully"),er(!1),ej()}catch(e){W.Z.fromBackend("Failed to update organization settings"),console.error("Error updating organization:",e)}finally{e_(!1)}};if(el)return(0,i.jsx)("div",{className:"p-4",children:"Loading..."});if(!X)return(0,i.jsx)("div",{className:"p-4",children:"Organization not found"});let ef=async(e,l)=>{await (0,F.vQ)(e)&&(ex(e=>({...e,[l]:!0})),setTimeout(()=>{ex(e=>({...e,[l]:!1}))},2e3))};return(0,i.jsxs)("div",{className:"w-full h-screen p-4 bg-white",children:[(0,i.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,i.jsxs)("div",{children:[(0,i.jsx)(t.Z,{icon:L.Z,onClick:M,variant:"light",className:"mb-4",children:"Back to Organizations"}),(0,i.jsx)(U.Z,{children:X.organization_alias}),(0,i.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,i.jsx)(f.Z,{className:"text-gray-500 font-mono",children:X.organization_id}),(0,i.jsx)(E.ZP,{type:"text",size:"small",icon:eu["org-id"]?(0,i.jsx)(B.Z,{size:12}):(0,i.jsx)(V.Z,{size:12}),onClick:()=>ef(X.organization_id,"org-id"),className:"left-2 z-10 transition-all duration-200 ".concat(eu["org-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,i.jsxs)(u.Z,{defaultIndex:K?2:0,children:[(0,i.jsxs)(x.Z,{className:"mb-4",children:[(0,i.jsx)(m.Z,{children:"Overview"}),(0,i.jsx)(m.Z,{children:"Members"}),(0,i.jsx)(m.Z,{children:"Settings"})]}),(0,i.jsxs)(_.Z,{children:[(0,i.jsx)(h.Z,{children:(0,i.jsxs)(d.Z,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Organization Details"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(f.Z,{children:["Created: ",new Date(X.created_at).toLocaleDateString()]}),(0,i.jsxs)(f.Z,{children:["Updated: ",new Date(X.updated_at).toLocaleDateString()]}),(0,i.jsxs)(f.Z,{children:["Created By: ",X.created_by]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Budget Status"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(U.Z,{children:["$",(0,F.pw)(X.spend,4)]}),(0,i.jsxs)(f.Z,{children:["of"," ",null===X.litellm_budget_table.max_budget?"Unlimited":"$".concat((0,F.pw)(X.litellm_budget_table.max_budget,4))]}),X.litellm_budget_table.budget_duration&&(0,i.jsxs)(f.Z,{className:"text-gray-500",children:["Reset: ",X.litellm_budget_table.budget_duration]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Rate Limits"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(f.Z,{children:["TPM: ",X.litellm_budget_table.tpm_limit||"Unlimited"]}),(0,i.jsxs)(f.Z,{children:["RPM: ",X.litellm_budget_table.rpm_limit||"Unlimited"]}),X.litellm_budget_table.max_parallel_requests&&(0,i.jsxs)(f.Z,{children:["Max Parallel Requests: ",X.litellm_budget_table.max_parallel_requests]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Models"}),(0,i.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===X.models.length?(0,i.jsx)(r.Z,{color:"red",children:"All proxy models"}):X.models.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e},l))})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Teams"}),(0,i.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:null===(l=X.teams)||void 0===l?void 0:l.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e.team_id},l))})]}),(0,i.jsx)(J.Z,{objectPermission:X.object_permission,variant:"card",accessToken:k})]})}),(0,i.jsx)(h.Z,{children:(0,i.jsxs)("div",{className:"space-y-4",children:[(0,i.jsx)(n.Z,{className:"w-full mx-auto flex-auto overflow-y-auto max-h-[75vh]",children:(0,i.jsxs)(g.Z,{children:[(0,i.jsx)(v.Z,{children:(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(Z.Z,{children:"User ID"}),(0,i.jsx)(Z.Z,{children:"Role"}),(0,i.jsx)(Z.Z,{children:"Spend"}),(0,i.jsx)(Z.Z,{children:"Created At"}),(0,i.jsx)(Z.Z,{})]})}),(0,i.jsx)(j.Z,{children:null===(s=X.members)||void 0===s?void 0:s.map((e,l)=>(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{className:"font-mono",children:e.user_id})}),(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{className:"font-mono",children:e.user_role})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:["$",(0,F.pw)(e.spend,4)]})}),(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{children:new Date(e.created_at).toLocaleString()})}),(0,i.jsx)(p.Z,{children:eg&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(c.Z,{icon:P.Z,size:"sm",onClick:()=>{em({role:e.user_role,user_email:e.user_email,user_id:e.user_id}),ed(!0)}}),(0,i.jsx)(c.Z,{icon:A.Z,size:"sm",onClick:()=>{eZ(e)}})]})})]},l))})]})}),eg&&(0,i.jsx)(t.Z,{onClick:()=>{en(!0)},children:"Add Member"})]})}),(0,i.jsx)(h.Z,{children:(0,i.jsxs)(n.Z,{className:"overflow-y-auto max-h-[65vh]",children:[(0,i.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,i.jsx)(U.Z,{children:"Organization Settings"}),eg&&!ea&&(0,i.jsx)(t.Z,{onClick:()=>er(!0),children:"Edit Settings"})]}),ea?(0,i.jsxs)(y.Z,{form:ei,onFinish:eb,initialValues:{organization_alias:X.organization_alias,models:X.models,tpm_limit:X.litellm_budget_table.tpm_limit,rpm_limit:X.litellm_budget_table.rpm_limit,max_budget:X.litellm_budget_table.max_budget,budget_duration:X.litellm_budget_table.budget_duration,metadata:X.metadata?JSON.stringify(X.metadata,null,2):"",vector_stores:(null===(o=X.object_permission)||void 0===o?void 0:o.vector_stores)||[],mcp_servers_and_groups:{servers:(null===(N=X.object_permission)||void 0===N?void 0:N.mcp_servers)||[],accessGroups:(null===(C=X.object_permission)||void 0===C?void 0:C.mcp_access_groups)||[]}},layout:"vertical",children:[(0,i.jsx)(y.Z.Item,{label:"Organization Name",name:"organization_alias",rules:[{required:!0,message:"Please input an organization name"}],children:(0,i.jsx)(R.Z,{})}),(0,i.jsx)(y.Z.Item,{label:"Models",name:"models",children:(0,i.jsxs)(S.default,{mode:"multiple",placeholder:"Select models",children:[(0,i.jsx)(S.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),H.map(e=>(0,i.jsx)(S.default.Option,{value:e,children:(0,D.W0)(e)},e))]})}),(0,i.jsx)(y.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,i.jsx)(w.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,i.jsxs)(S.default,{placeholder:"n/a",children:[(0,i.jsx)(S.default.Option,{value:"24h",children:"daily"}),(0,i.jsx)(S.default.Option,{value:"7d",children:"weekly"}),(0,i.jsx)(S.default.Option,{value:"30d",children:"monthly"})]})}),(0,i.jsx)(y.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,i.jsx)(w.Z,{step:1,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,i.jsx)(w.Z,{step:1,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,i.jsx)(Y.Z,{onChange:e=>ei.setFieldValue("vector_stores",e),value:ei.getFieldValue("vector_stores"),accessToken:k||"",placeholder:"Select vector stores"})}),(0,i.jsx)(y.Z.Item,{label:"MCP Servers & Access Groups",name:"mcp_servers_and_groups",children:(0,i.jsx)(G.Z,{onChange:e=>ei.setFieldValue("mcp_servers_and_groups",e),value:ei.getFieldValue("mcp_servers_and_groups"),accessToken:k||"",placeholder:"Select MCP servers and access groups"})}),(0,i.jsx)(y.Z.Item,{label:"Metadata",name:"metadata",children:(0,i.jsx)(z.default.TextArea,{rows:4})}),(0,i.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,i.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,i.jsx)(t.Z,{variant:"secondary",onClick:()=>er(!1),disabled:eh,children:"Cancel"}),(0,i.jsx)(t.Z,{type:"submit",loading:eh,children:"Save Changes"})]})})]}):(0,i.jsxs)("div",{className:"space-y-4",children:[(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Organization Name"}),(0,i.jsx)("div",{children:X.organization_alias})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Organization ID"}),(0,i.jsx)("div",{className:"font-mono",children:X.organization_id})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Created At"}),(0,i.jsx)("div",{children:new Date(X.created_at).toLocaleString()})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Models"}),(0,i.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:X.models.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e},l))})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Rate Limits"}),(0,i.jsxs)("div",{children:["TPM: ",X.litellm_budget_table.tpm_limit||"Unlimited"]}),(0,i.jsxs)("div",{children:["RPM: ",X.litellm_budget_table.rpm_limit||"Unlimited"]})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Budget"}),(0,i.jsxs)("div",{children:["Max:"," ",null!==X.litellm_budget_table.max_budget?"$".concat((0,F.pw)(X.litellm_budget_table.max_budget,4)):"No Limit"]}),(0,i.jsxs)("div",{children:["Reset: ",X.litellm_budget_table.budget_duration||"Never"]})]}),(0,i.jsx)(J.Z,{objectPermission:X.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:k})]})]})})]})]}),(0,i.jsx)(q.Z,{isVisible:et,onCancel:()=>en(!1),onSubmit:ep,accessToken:k,title:"Add Organization Member",roles:[{label:"org_admin",value:"org_admin",description:"Can add and remove members, and change their roles."},{label:"internal_user",value:"internal_user",description:"Can view/create keys for themselves within organization."},{label:"internal_user_viewer",value:"internal_user_viewer",description:"Can only view their keys within organization."}],defaultRole:"internal_user"}),(0,i.jsx)(Q.Z,{visible:eo,onCancel:()=>ed(!1),onSubmit:ev,initialData:ec,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Org Admin",value:"org_admin"},{label:"Internal User",value:"internal_user"},{label:"Internal User Viewer",value:"internal_user_viewer"}]}})]})},K=s(21609);let X=async(e,l)=>{l(await (0,$.organizationListCall)(e))};var ee=e=>{let{organizations:l,userRole:s,userModels:L,accessToken:R,lastRefreshed:U,handleRefreshClick:E,currentOrg:B,guardrailsList:V=[],setOrganizations:q,premiumUser:J}=e,[Q,ee]=(0,a.useState)(null),[el,es]=(0,a.useState)(!1),[ei,ea]=(0,a.useState)(!1),[er,et]=(0,a.useState)(null),[en,eo]=(0,a.useState)(!1),[ed,ec]=(0,a.useState)(!1),[em]=y.Z.useForm(),[eu,ex]=(0,a.useState)({});(0,a.useEffect)(()=>{R&&X(R,q)},[R]);let eh=e=>{e&&(et(e),ea(!0))},e_=async()=>{if(er&&R)try{eo(!0),await (0,$.organizationDeleteCall)(R,er),W.Z.success("Organization deleted successfully"),ea(!1),et(null),await X(R,q)}catch(e){console.error("Error deleting organization:",e)}finally{eo(!1)}},eg=async e=>{try{var l,s,i,a;if(!R)return;console.log("values in organizations new create call: ".concat(JSON.stringify(e))),(e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0||e.allowed_mcp_servers_and_groups&&((null===(l=e.allowed_mcp_servers_and_groups.servers)||void 0===l?void 0:l.length)>0||(null===(s=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===s?void 0:s.length)>0))&&(e.object_permission={},e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0&&(e.object_permission.vector_stores=e.allowed_vector_store_ids,delete e.allowed_vector_store_ids),e.allowed_mcp_servers_and_groups&&((null===(i=e.allowed_mcp_servers_and_groups.servers)||void 0===i?void 0:i.length)>0&&(e.object_permission.mcp_servers=e.allowed_mcp_servers_and_groups.servers),(null===(a=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===a?void 0:a.length)>0&&(e.object_permission.mcp_access_groups=e.allowed_mcp_servers_and_groups.accessGroups),delete e.allowed_mcp_servers_and_groups)),await (0,$.organizationCreateCall)(R,e),W.Z.success("Organization created successfully"),ec(!1),em.resetFields(),X(R,q)}catch(e){console.error("Error creating organization:",e)}};return J?(0,i.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[(0,i.jsx)(d.Z,{numItems:1,className:"gap-2 p-8 w-full mt-2",children:(0,i.jsxs)(o.Z,{numColSpan:1,className:"flex flex-col gap-2",children:[("Admin"===s||"Org Admin"===s)&&(0,i.jsx)(t.Z,{className:"w-fit",onClick:()=>ec(!0),children:"+ Create New Organization"}),Q?(0,i.jsx)(H,{organizationId:Q,onClose:()=>{ee(null),es(!1)},accessToken:R,is_org_admin:!0,is_proxy_admin:"Admin"===s,userModels:L,editOrg:el}):(0,i.jsxs)(u.Z,{className:"gap-2 h-[75vh] w-full",children:[(0,i.jsxs)(x.Z,{className:"flex justify-between mt-2 w-full items-center",children:[(0,i.jsx)("div",{className:"flex",children:(0,i.jsx)(m.Z,{children:"Your Organizations"})}),(0,i.jsxs)("div",{className:"flex items-center space-x-2",children:[U&&(0,i.jsxs)(f.Z,{children:["Last Refreshed: ",U]}),(0,i.jsx)(c.Z,{icon:M.Z,variant:"shadow",size:"xs",className:"self-center",onClick:E})]})]}),(0,i.jsx)(_.Z,{children:(0,i.jsxs)(h.Z,{children:[(0,i.jsx)(f.Z,{children:"Click on “Organization ID” to view organization details."}),(0,i.jsx)(d.Z,{numItems:1,className:"gap-2 pt-2 pb-2 h-[75vh] w-full mt-2",children:(0,i.jsx)(o.Z,{numColSpan:1,children:(0,i.jsx)(n.Z,{className:"w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]",children:(0,i.jsxs)(g.Z,{children:[(0,i.jsx)(v.Z,{children:(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(Z.Z,{children:"Organization ID"}),(0,i.jsx)(Z.Z,{children:"Organization Name"}),(0,i.jsx)(Z.Z,{children:"Created"}),(0,i.jsx)(Z.Z,{children:"Spend (USD)"}),(0,i.jsx)(Z.Z,{children:"Budget (USD)"}),(0,i.jsx)(Z.Z,{children:"Models"}),(0,i.jsx)(Z.Z,{children:"TPM / RPM Limits"}),(0,i.jsx)(Z.Z,{children:"Info"}),(0,i.jsx)(Z.Z,{children:"Actions"})]})}),(0,i.jsx)(j.Z,{children:l&&l.length>0?l.sort((e,l)=>new Date(l.created_at).getTime()-new Date(e.created_at).getTime()).map(e=>{var l,a,n,o,d,m,u,x,h;return(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(p.Z,{children:(0,i.jsx)("div",{className:"overflow-hidden",children:(0,i.jsx)(N.Z,{title:e.organization_id,children:(0,i.jsxs)(t.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]",onClick:()=>ee(e.organization_id),children:[null===(l=e.organization_id)||void 0===l?void 0:l.slice(0,7),"..."]})})})}),(0,i.jsx)(p.Z,{children:e.organization_alias}),(0,i.jsx)(p.Z,{children:e.created_at?new Date(e.created_at).toLocaleDateString():"N/A"}),(0,i.jsx)(p.Z,{children:(0,F.pw)(e.spend,4)}),(0,i.jsx)(p.Z,{children:(null===(a=e.litellm_budget_table)||void 0===a?void 0:a.max_budget)!==null&&(null===(n=e.litellm_budget_table)||void 0===n?void 0:n.max_budget)!==void 0?null===(o=e.litellm_budget_table)||void 0===o?void 0:o.max_budget:"No limit"}),(0,i.jsx)(p.Z,{style:{maxWidth:"8-x",whiteSpace:"pre-wrap",overflow:"hidden"},className:e.models.length>3?"px-0":"",children:(0,i.jsx)("div",{className:"flex flex-col",children:Array.isArray(e.models)?(0,i.jsx)("div",{className:"flex flex-col",children:0===e.models.length?(0,i.jsx)(r.Z,{size:"xs",className:"mb-1",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})}):(0,i.jsx)(i.Fragment,{children:(0,i.jsxs)("div",{className:"flex items-start",children:[e.models.length>3&&(0,i.jsx)("div",{children:(0,i.jsx)(c.Z,{icon:eu[e.organization_id||""]?k.Z:I.Z,className:"cursor-pointer",size:"xs",onClick:()=>{ex(l=>({...l,[e.organization_id||""]:!l[e.organization_id||""]}))}})}),(0,i.jsxs)("div",{className:"flex flex-wrap gap-1",children:[e.models.slice(0,3).map((e,l)=>"all-proxy-models"===e?(0,i.jsx)(r.Z,{size:"xs",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})},l):(0,i.jsx)(r.Z,{size:"xs",color:"blue",children:(0,i.jsx)(f.Z,{children:e.length>30?"".concat((0,D.W0)(e).slice(0,30),"..."):(0,D.W0)(e)})},l)),e.models.length>3&&!eu[e.organization_id||""]&&(0,i.jsx)(r.Z,{size:"xs",color:"gray",className:"cursor-pointer",children:(0,i.jsxs)(f.Z,{children:["+",e.models.length-3," ",e.models.length-3==1?"more model":"more models"]})}),eu[e.organization_id||""]&&(0,i.jsx)("div",{className:"flex flex-wrap gap-1",children:e.models.slice(3).map((e,l)=>"all-proxy-models"===e?(0,i.jsx)(r.Z,{size:"xs",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})},l+3):(0,i.jsx)(r.Z,{size:"xs",color:"blue",children:(0,i.jsx)(f.Z,{children:e.length>30?"".concat((0,D.W0)(e).slice(0,30),"..."):(0,D.W0)(e)})},l+3))})]})]})})}):null})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:["TPM:"," ",(null===(d=e.litellm_budget_table)||void 0===d?void 0:d.tpm_limit)?null===(m=e.litellm_budget_table)||void 0===m?void 0:m.tpm_limit:"Unlimited",(0,i.jsx)("br",{}),"RPM:"," ",(null===(u=e.litellm_budget_table)||void 0===u?void 0:u.rpm_limit)?null===(x=e.litellm_budget_table)||void 0===x?void 0:x.rpm_limit:"Unlimited"]})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:[(null===(h=e.members)||void 0===h?void 0:h.length)||0," Members"]})}),(0,i.jsx)(p.Z,{children:"Admin"===s&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(N.Z,{title:"Edit organization",children:[" ",(0,i.jsx)(c.Z,{icon:P.Z,size:"sm",className:"cursor-pointer hover:text-blue-600",onClick:()=>{ee(e.organization_id),es(!0)}})]}),(0,i.jsxs)(N.Z,{title:"Delete organization",children:[" ",(0,i.jsx)(c.Z,{onClick:()=>eh(e.organization_id),icon:A.Z,size:"sm",className:"cursor-pointer hover:text-red-600"})]})]})})]},e.organization_id)}):null})]})})})})]})})]})]})}),(0,i.jsx)(C.Z,{title:"Create Organization",visible:ed,width:800,footer:null,onCancel:()=>{ec(!1),em.resetFields()},children:(0,i.jsxs)(y.Z,{form:em,onFinish:eg,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,i.jsx)(y.Z.Item,{label:"Organization Name",name:"organization_alias",rules:[{required:!0,message:"Please input an organization name"}],children:(0,i.jsx)(T.o,{placeholder:""})}),(0,i.jsx)(y.Z.Item,{label:"Models",name:"models",children:(0,i.jsxs)(S.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},children:[(0,i.jsx)(S.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),L&&L.length>0&&L.map(e=>(0,i.jsx)(S.default.Option,{value:e,children:(0,D.W0)(e)},e))]})}),(0,i.jsx)(y.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,i.jsx)(w.Z,{step:.01,precision:2,width:200})}),(0,i.jsx)(y.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,i.jsxs)(S.default,{defaultValue:null,placeholder:"n/a",children:[(0,i.jsx)(S.default.Option,{value:"24h",children:"daily"}),(0,i.jsx)(S.default.Option,{value:"7d",children:"weekly"}),(0,i.jsx)(S.default.Option,{value:"30d",children:"monthly"})]})}),(0,i.jsx)(y.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,i.jsx)(w.Z,{step:1,width:400})}),(0,i.jsx)(y.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,i.jsx)(w.Z,{step:1,width:400})}),(0,i.jsx)(y.Z.Item,{label:(0,i.jsxs)("span",{children:["Allowed Vector Stores"," ",(0,i.jsx)(N.Z,{title:"Select which vector stores this organization can access by default. Leave empty for access to all vector stores",children:(0,i.jsx)(O.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_vector_store_ids",className:"mt-4",help:"Select vector stores this organization can access. Leave empty for access to all vector stores",children:(0,i.jsx)(Y.Z,{onChange:e=>em.setFieldValue("allowed_vector_store_ids",e),value:em.getFieldValue("allowed_vector_store_ids"),accessToken:R||"",placeholder:"Select vector stores (optional)"})}),(0,i.jsx)(y.Z.Item,{label:(0,i.jsxs)("span",{children:["Allowed MCP Servers"," ",(0,i.jsx)(N.Z,{title:"Select which MCP servers and access groups this organization can access by default.",children:(0,i.jsx)(O.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_mcp_servers_and_groups",className:"mt-4",help:"Select MCP servers and access groups this organization can access.",children:(0,i.jsx)(G.Z,{onChange:e=>em.setFieldValue("allowed_mcp_servers_and_groups",e),value:em.getFieldValue("allowed_mcp_servers_and_groups"),accessToken:R||"",placeholder:"Select MCP servers and access groups (optional)"})}),(0,i.jsx)(y.Z.Item,{label:"Metadata",name:"metadata",children:(0,i.jsx)(z.default.TextArea,{rows:4})}),(0,i.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,i.jsx)(t.Z,{type:"submit",children:"Create Organization"})})]})}),(0,i.jsx)(K.Z,{isOpen:ei,title:"Delete Organization?",message:"Are you sure you want to delete this organization? This action cannot be undone.",resourceInformationTitle:"Organization Information",resourceInformation:[{label:"Organization ID",value:er,code:!0}],onCancel:()=>{ea(!1),et(null)},onOk:e_,confirmLoading:en})]}):(0,i.jsx)("div",{children:(0,i.jsxs)(f.Z,{children:["This is a LiteLLM Enterprise feature, and requires a valid key to use. Get a trial key"," ",(0,i.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",children:"here"}),"."]})})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2004-8b1ad3d8c195646a.js b/litellm/proxy/_experimental/out/_next/static/chunks/2004-8b1ad3d8c195646a.js deleted file mode 100644 index 59075bdbc32..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2004-8b1ad3d8c195646a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2004],{22004:function(e,l,s){s.d(l,{Z:function(){return ee},g:function(){return X}});var i=s(57437),a=s(2265),r=s(41649),t=s(78489),n=s(12514),o=s(49804),d=s(67101),c=s(47323),m=s(12485),u=s(18135),x=s(35242),h=s(29706),_=s(77991),g=s(21626),j=s(97214),p=s(28241),v=s(58834),Z=s(69552),b=s(71876),f=s(84264),w=s(24199),z=s(4260),y=s(10032),N=s(99981),C=s(22116),S=s(37592),O=s(15424),M=s(23628),k=s(86462),I=s(47686),P=s(53410),A=s(74998),T=s(31283),D=s(46468),F=s(59872),L=s(10900),R=s(49566),U=s(96761),E=s(5545),B=s(30401),V=s(78867),q=s(33860),G=s(95920),W=s(9114),$=s(19250),J=s(98015),Q=s(10901),Y=s(97415),H=e=>{var l,s,o,N,C;let{organizationId:O,onClose:M,accessToken:k,is_org_admin:I,is_proxy_admin:T,userModels:H,editOrg:K}=e,[X,ee]=(0,a.useState)(null),[el,es]=(0,a.useState)(!0),[ei]=y.Z.useForm(),[ea,er]=(0,a.useState)(!1),[et,en]=(0,a.useState)(!1),[eo,ed]=(0,a.useState)(!1),[ec,em]=(0,a.useState)(null),[eu,ex]=(0,a.useState)({}),eh=I||T,e_=async()=>{try{if(es(!0),!k)return;let e=await (0,$.organizationInfoCall)(k,O);ee(e)}catch(e){W.Z.fromBackend("Failed to load organization information"),console.error("Error fetching organization info:",e)}finally{es(!1)}};(0,a.useEffect)(()=>{e_()},[O,k]);let eg=async e=>{try{if(null==k)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,$.organizationMemberAddCall)(k,O,l),W.Z.success("Organization member added successfully"),en(!1),ei.resetFields(),e_()}catch(e){W.Z.fromBackend("Failed to add organization member"),console.error("Error adding organization member:",e)}},ej=async e=>{try{if(!k)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,$.organizationMemberUpdateCall)(k,O,l),W.Z.success("Organization member updated successfully"),ed(!1),ei.resetFields(),e_()}catch(e){W.Z.fromBackend("Failed to update organization member"),console.error("Error updating organization member:",e)}},ep=async e=>{try{if(!k)return;await (0,$.organizationMemberDeleteCall)(k,O,e.user_id),W.Z.success("Organization member deleted successfully"),ed(!1),ei.resetFields(),e_()}catch(e){W.Z.fromBackend("Failed to delete organization member"),console.error("Error deleting organization member:",e)}},ev=async e=>{try{if(!k)return;let l={organization_id:O,organization_alias:e.organization_alias,models:e.models,litellm_budget_table:{tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit,max_budget:e.max_budget,budget_duration:e.budget_duration},metadata:e.metadata?JSON.parse(e.metadata):null};if((void 0!==e.vector_stores||void 0!==e.mcp_servers_and_groups)&&(l.object_permission={...null==X?void 0:X.object_permission,vector_stores:e.vector_stores||[]},void 0!==e.mcp_servers_and_groups)){let{servers:s,accessGroups:i}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]};s&&s.length>0&&(l.object_permission.mcp_servers=s),i&&i.length>0&&(l.object_permission.mcp_access_groups=i)}await (0,$.organizationUpdateCall)(k,l),W.Z.success("Organization settings updated successfully"),er(!1),e_()}catch(e){W.Z.fromBackend("Failed to update organization settings"),console.error("Error updating organization:",e)}};if(el)return(0,i.jsx)("div",{className:"p-4",children:"Loading..."});if(!X)return(0,i.jsx)("div",{className:"p-4",children:"Organization not found"});let eZ=async(e,l)=>{await (0,F.vQ)(e)&&(ex(e=>({...e,[l]:!0})),setTimeout(()=>{ex(e=>({...e,[l]:!1}))},2e3))};return(0,i.jsxs)("div",{className:"w-full h-screen p-4 bg-white",children:[(0,i.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,i.jsxs)("div",{children:[(0,i.jsx)(t.Z,{icon:L.Z,onClick:M,variant:"light",className:"mb-4",children:"Back to Organizations"}),(0,i.jsx)(U.Z,{children:X.organization_alias}),(0,i.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,i.jsx)(f.Z,{className:"text-gray-500 font-mono",children:X.organization_id}),(0,i.jsx)(E.ZP,{type:"text",size:"small",icon:eu["org-id"]?(0,i.jsx)(B.Z,{size:12}):(0,i.jsx)(V.Z,{size:12}),onClick:()=>eZ(X.organization_id,"org-id"),className:"left-2 z-10 transition-all duration-200 ".concat(eu["org-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,i.jsxs)(u.Z,{defaultIndex:K?2:0,children:[(0,i.jsxs)(x.Z,{className:"mb-4",children:[(0,i.jsx)(m.Z,{children:"Overview"}),(0,i.jsx)(m.Z,{children:"Members"}),(0,i.jsx)(m.Z,{children:"Settings"})]}),(0,i.jsxs)(_.Z,{children:[(0,i.jsx)(h.Z,{children:(0,i.jsxs)(d.Z,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Organization Details"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(f.Z,{children:["Created: ",new Date(X.created_at).toLocaleDateString()]}),(0,i.jsxs)(f.Z,{children:["Updated: ",new Date(X.updated_at).toLocaleDateString()]}),(0,i.jsxs)(f.Z,{children:["Created By: ",X.created_by]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Budget Status"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(U.Z,{children:["$",(0,F.pw)(X.spend,4)]}),(0,i.jsxs)(f.Z,{children:["of"," ",null===X.litellm_budget_table.max_budget?"Unlimited":"$".concat((0,F.pw)(X.litellm_budget_table.max_budget,4))]}),X.litellm_budget_table.budget_duration&&(0,i.jsxs)(f.Z,{className:"text-gray-500",children:["Reset: ",X.litellm_budget_table.budget_duration]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Rate Limits"}),(0,i.jsxs)("div",{className:"mt-2",children:[(0,i.jsxs)(f.Z,{children:["TPM: ",X.litellm_budget_table.tpm_limit||"Unlimited"]}),(0,i.jsxs)(f.Z,{children:["RPM: ",X.litellm_budget_table.rpm_limit||"Unlimited"]}),X.litellm_budget_table.max_parallel_requests&&(0,i.jsxs)(f.Z,{children:["Max Parallel Requests: ",X.litellm_budget_table.max_parallel_requests]})]})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Models"}),(0,i.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===X.models.length?(0,i.jsx)(r.Z,{color:"red",children:"All proxy models"}):X.models.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e},l))})]}),(0,i.jsxs)(n.Z,{children:[(0,i.jsx)(f.Z,{children:"Teams"}),(0,i.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:null===(l=X.teams)||void 0===l?void 0:l.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e.team_id},l))})]}),(0,i.jsx)(J.Z,{objectPermission:X.object_permission,variant:"card",accessToken:k})]})}),(0,i.jsx)(h.Z,{children:(0,i.jsxs)("div",{className:"space-y-4",children:[(0,i.jsx)(n.Z,{className:"w-full mx-auto flex-auto overflow-y-auto max-h-[75vh]",children:(0,i.jsxs)(g.Z,{children:[(0,i.jsx)(v.Z,{children:(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(Z.Z,{children:"User ID"}),(0,i.jsx)(Z.Z,{children:"Role"}),(0,i.jsx)(Z.Z,{children:"Spend"}),(0,i.jsx)(Z.Z,{children:"Created At"}),(0,i.jsx)(Z.Z,{})]})}),(0,i.jsx)(j.Z,{children:null===(s=X.members)||void 0===s?void 0:s.map((e,l)=>(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{className:"font-mono",children:e.user_id})}),(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{className:"font-mono",children:e.user_role})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:["$",(0,F.pw)(e.spend,4)]})}),(0,i.jsx)(p.Z,{children:(0,i.jsx)(f.Z,{children:new Date(e.created_at).toLocaleString()})}),(0,i.jsx)(p.Z,{children:eh&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(c.Z,{icon:P.Z,size:"sm",onClick:()=>{em({role:e.user_role,user_email:e.user_email,user_id:e.user_id}),ed(!0)}}),(0,i.jsx)(c.Z,{icon:A.Z,size:"sm",onClick:()=>{ep(e)}})]})})]},l))})]})}),eh&&(0,i.jsx)(t.Z,{onClick:()=>{en(!0)},children:"Add Member"})]})}),(0,i.jsx)(h.Z,{children:(0,i.jsxs)(n.Z,{className:"overflow-y-auto max-h-[65vh]",children:[(0,i.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,i.jsx)(U.Z,{children:"Organization Settings"}),eh&&!ea&&(0,i.jsx)(t.Z,{onClick:()=>er(!0),children:"Edit Settings"})]}),ea?(0,i.jsxs)(y.Z,{form:ei,onFinish:ev,initialValues:{organization_alias:X.organization_alias,models:X.models,tpm_limit:X.litellm_budget_table.tpm_limit,rpm_limit:X.litellm_budget_table.rpm_limit,max_budget:X.litellm_budget_table.max_budget,budget_duration:X.litellm_budget_table.budget_duration,metadata:X.metadata?JSON.stringify(X.metadata,null,2):"",vector_stores:(null===(o=X.object_permission)||void 0===o?void 0:o.vector_stores)||[],mcp_servers_and_groups:{servers:(null===(N=X.object_permission)||void 0===N?void 0:N.mcp_servers)||[],accessGroups:(null===(C=X.object_permission)||void 0===C?void 0:C.mcp_access_groups)||[]}},layout:"vertical",children:[(0,i.jsx)(y.Z.Item,{label:"Organization Name",name:"organization_alias",rules:[{required:!0,message:"Please input an organization name"}],children:(0,i.jsx)(R.Z,{})}),(0,i.jsx)(y.Z.Item,{label:"Models",name:"models",children:(0,i.jsxs)(S.default,{mode:"multiple",placeholder:"Select models",children:[(0,i.jsx)(S.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),H.map(e=>(0,i.jsx)(S.default.Option,{value:e,children:(0,D.W0)(e)},e))]})}),(0,i.jsx)(y.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,i.jsx)(w.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,i.jsxs)(S.default,{placeholder:"n/a",children:[(0,i.jsx)(S.default.Option,{value:"24h",children:"daily"}),(0,i.jsx)(S.default.Option,{value:"7d",children:"weekly"}),(0,i.jsx)(S.default.Option,{value:"30d",children:"monthly"})]})}),(0,i.jsx)(y.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,i.jsx)(w.Z,{step:1,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,i.jsx)(w.Z,{step:1,style:{width:"100%"}})}),(0,i.jsx)(y.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,i.jsx)(Y.Z,{onChange:e=>ei.setFieldValue("vector_stores",e),value:ei.getFieldValue("vector_stores"),accessToken:k||"",placeholder:"Select vector stores"})}),(0,i.jsx)(y.Z.Item,{label:"MCP Servers & Access Groups",name:"mcp_servers_and_groups",children:(0,i.jsx)(G.Z,{onChange:e=>ei.setFieldValue("mcp_servers_and_groups",e),value:ei.getFieldValue("mcp_servers_and_groups"),accessToken:k||"",placeholder:"Select MCP servers and access groups"})}),(0,i.jsx)(y.Z.Item,{label:"Metadata",name:"metadata",children:(0,i.jsx)(z.default.TextArea,{rows:4})}),(0,i.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,i.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,i.jsx)(t.Z,{variant:"secondary",onClick:()=>er(!1),children:"Cancel"}),(0,i.jsx)(t.Z,{type:"submit",children:"Save Changes"})]})})]}):(0,i.jsxs)("div",{className:"space-y-4",children:[(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Organization Name"}),(0,i.jsx)("div",{children:X.organization_alias})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Organization ID"}),(0,i.jsx)("div",{className:"font-mono",children:X.organization_id})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Created At"}),(0,i.jsx)("div",{children:new Date(X.created_at).toLocaleString()})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Models"}),(0,i.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:X.models.map((e,l)=>(0,i.jsx)(r.Z,{color:"red",children:e},l))})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Rate Limits"}),(0,i.jsxs)("div",{children:["TPM: ",X.litellm_budget_table.tpm_limit||"Unlimited"]}),(0,i.jsxs)("div",{children:["RPM: ",X.litellm_budget_table.rpm_limit||"Unlimited"]})]}),(0,i.jsxs)("div",{children:[(0,i.jsx)(f.Z,{className:"font-medium",children:"Budget"}),(0,i.jsxs)("div",{children:["Max:"," ",null!==X.litellm_budget_table.max_budget?"$".concat((0,F.pw)(X.litellm_budget_table.max_budget,4)):"No Limit"]}),(0,i.jsxs)("div",{children:["Reset: ",X.litellm_budget_table.budget_duration||"Never"]})]}),(0,i.jsx)(J.Z,{objectPermission:X.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:k})]})]})})]})]}),(0,i.jsx)(q.Z,{isVisible:et,onCancel:()=>en(!1),onSubmit:eg,accessToken:k,title:"Add Organization Member",roles:[{label:"org_admin",value:"org_admin",description:"Can add and remove members, and change their roles."},{label:"internal_user",value:"internal_user",description:"Can view/create keys for themselves within organization."},{label:"internal_user_viewer",value:"internal_user_viewer",description:"Can only view their keys within organization."}],defaultRole:"internal_user"}),(0,i.jsx)(Q.Z,{visible:eo,onCancel:()=>ed(!1),onSubmit:ej,initialData:ec,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Org Admin",value:"org_admin"},{label:"Internal User",value:"internal_user"},{label:"Internal User Viewer",value:"internal_user_viewer"}]}})]})},K=s(21609);let X=async(e,l)=>{l(await (0,$.organizationListCall)(e))};var ee=e=>{let{organizations:l,userRole:s,userModels:L,accessToken:R,lastRefreshed:U,handleRefreshClick:E,currentOrg:B,guardrailsList:V=[],setOrganizations:q,premiumUser:J}=e,[Q,ee]=(0,a.useState)(null),[el,es]=(0,a.useState)(!1),[ei,ea]=(0,a.useState)(!1),[er,et]=(0,a.useState)(null),[en,eo]=(0,a.useState)(!1),[ed,ec]=(0,a.useState)(!1),[em]=y.Z.useForm(),[eu,ex]=(0,a.useState)({});(0,a.useEffect)(()=>{R&&X(R,q)},[R]);let eh=e=>{e&&(et(e),ea(!0))},e_=async()=>{if(er&&R)try{eo(!0),await (0,$.organizationDeleteCall)(R,er),W.Z.success("Organization deleted successfully"),ea(!1),et(null),await X(R,q)}catch(e){console.error("Error deleting organization:",e)}finally{eo(!1)}},eg=async e=>{try{var l,s,i,a;if(!R)return;console.log("values in organizations new create call: ".concat(JSON.stringify(e))),(e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0||e.allowed_mcp_servers_and_groups&&((null===(l=e.allowed_mcp_servers_and_groups.servers)||void 0===l?void 0:l.length)>0||(null===(s=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===s?void 0:s.length)>0))&&(e.object_permission={},e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0&&(e.object_permission.vector_stores=e.allowed_vector_store_ids,delete e.allowed_vector_store_ids),e.allowed_mcp_servers_and_groups&&((null===(i=e.allowed_mcp_servers_and_groups.servers)||void 0===i?void 0:i.length)>0&&(e.object_permission.mcp_servers=e.allowed_mcp_servers_and_groups.servers),(null===(a=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===a?void 0:a.length)>0&&(e.object_permission.mcp_access_groups=e.allowed_mcp_servers_and_groups.accessGroups),delete e.allowed_mcp_servers_and_groups)),await (0,$.organizationCreateCall)(R,e),W.Z.success("Organization created successfully"),ec(!1),em.resetFields(),X(R,q)}catch(e){console.error("Error creating organization:",e)}};return J?(0,i.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[(0,i.jsx)(d.Z,{numItems:1,className:"gap-2 p-8 w-full mt-2",children:(0,i.jsxs)(o.Z,{numColSpan:1,className:"flex flex-col gap-2",children:[("Admin"===s||"Org Admin"===s)&&(0,i.jsx)(t.Z,{className:"w-fit",onClick:()=>ec(!0),children:"+ Create New Organization"}),Q?(0,i.jsx)(H,{organizationId:Q,onClose:()=>{ee(null),es(!1)},accessToken:R,is_org_admin:!0,is_proxy_admin:"Admin"===s,userModels:L,editOrg:el}):(0,i.jsxs)(u.Z,{className:"gap-2 h-[75vh] w-full",children:[(0,i.jsxs)(x.Z,{className:"flex justify-between mt-2 w-full items-center",children:[(0,i.jsx)("div",{className:"flex",children:(0,i.jsx)(m.Z,{children:"Your Organizations"})}),(0,i.jsxs)("div",{className:"flex items-center space-x-2",children:[U&&(0,i.jsxs)(f.Z,{children:["Last Refreshed: ",U]}),(0,i.jsx)(c.Z,{icon:M.Z,variant:"shadow",size:"xs",className:"self-center",onClick:E})]})]}),(0,i.jsx)(_.Z,{children:(0,i.jsxs)(h.Z,{children:[(0,i.jsx)(f.Z,{children:"Click on “Organization ID” to view organization details."}),(0,i.jsx)(d.Z,{numItems:1,className:"gap-2 pt-2 pb-2 h-[75vh] w-full mt-2",children:(0,i.jsx)(o.Z,{numColSpan:1,children:(0,i.jsx)(n.Z,{className:"w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]",children:(0,i.jsxs)(g.Z,{children:[(0,i.jsx)(v.Z,{children:(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(Z.Z,{children:"Organization ID"}),(0,i.jsx)(Z.Z,{children:"Organization Name"}),(0,i.jsx)(Z.Z,{children:"Created"}),(0,i.jsx)(Z.Z,{children:"Spend (USD)"}),(0,i.jsx)(Z.Z,{children:"Budget (USD)"}),(0,i.jsx)(Z.Z,{children:"Models"}),(0,i.jsx)(Z.Z,{children:"TPM / RPM Limits"}),(0,i.jsx)(Z.Z,{children:"Info"}),(0,i.jsx)(Z.Z,{children:"Actions"})]})}),(0,i.jsx)(j.Z,{children:l&&l.length>0?l.sort((e,l)=>new Date(l.created_at).getTime()-new Date(e.created_at).getTime()).map(e=>{var l,a,n,o,d,m,u,x,h;return(0,i.jsxs)(b.Z,{children:[(0,i.jsx)(p.Z,{children:(0,i.jsx)("div",{className:"overflow-hidden",children:(0,i.jsx)(N.Z,{title:e.organization_id,children:(0,i.jsxs)(t.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]",onClick:()=>ee(e.organization_id),children:[null===(l=e.organization_id)||void 0===l?void 0:l.slice(0,7),"..."]})})})}),(0,i.jsx)(p.Z,{children:e.organization_alias}),(0,i.jsx)(p.Z,{children:e.created_at?new Date(e.created_at).toLocaleDateString():"N/A"}),(0,i.jsx)(p.Z,{children:(0,F.pw)(e.spend,4)}),(0,i.jsx)(p.Z,{children:(null===(a=e.litellm_budget_table)||void 0===a?void 0:a.max_budget)!==null&&(null===(n=e.litellm_budget_table)||void 0===n?void 0:n.max_budget)!==void 0?null===(o=e.litellm_budget_table)||void 0===o?void 0:o.max_budget:"No limit"}),(0,i.jsx)(p.Z,{style:{maxWidth:"8-x",whiteSpace:"pre-wrap",overflow:"hidden"},className:e.models.length>3?"px-0":"",children:(0,i.jsx)("div",{className:"flex flex-col",children:Array.isArray(e.models)?(0,i.jsx)("div",{className:"flex flex-col",children:0===e.models.length?(0,i.jsx)(r.Z,{size:"xs",className:"mb-1",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})}):(0,i.jsx)(i.Fragment,{children:(0,i.jsxs)("div",{className:"flex items-start",children:[e.models.length>3&&(0,i.jsx)("div",{children:(0,i.jsx)(c.Z,{icon:eu[e.organization_id||""]?k.Z:I.Z,className:"cursor-pointer",size:"xs",onClick:()=>{ex(l=>({...l,[e.organization_id||""]:!l[e.organization_id||""]}))}})}),(0,i.jsxs)("div",{className:"flex flex-wrap gap-1",children:[e.models.slice(0,3).map((e,l)=>"all-proxy-models"===e?(0,i.jsx)(r.Z,{size:"xs",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})},l):(0,i.jsx)(r.Z,{size:"xs",color:"blue",children:(0,i.jsx)(f.Z,{children:e.length>30?"".concat((0,D.W0)(e).slice(0,30),"..."):(0,D.W0)(e)})},l)),e.models.length>3&&!eu[e.organization_id||""]&&(0,i.jsx)(r.Z,{size:"xs",color:"gray",className:"cursor-pointer",children:(0,i.jsxs)(f.Z,{children:["+",e.models.length-3," ",e.models.length-3==1?"more model":"more models"]})}),eu[e.organization_id||""]&&(0,i.jsx)("div",{className:"flex flex-wrap gap-1",children:e.models.slice(3).map((e,l)=>"all-proxy-models"===e?(0,i.jsx)(r.Z,{size:"xs",color:"red",children:(0,i.jsx)(f.Z,{children:"All Proxy Models"})},l+3):(0,i.jsx)(r.Z,{size:"xs",color:"blue",children:(0,i.jsx)(f.Z,{children:e.length>30?"".concat((0,D.W0)(e).slice(0,30),"..."):(0,D.W0)(e)})},l+3))})]})]})})}):null})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:["TPM:"," ",(null===(d=e.litellm_budget_table)||void 0===d?void 0:d.tpm_limit)?null===(m=e.litellm_budget_table)||void 0===m?void 0:m.tpm_limit:"Unlimited",(0,i.jsx)("br",{}),"RPM:"," ",(null===(u=e.litellm_budget_table)||void 0===u?void 0:u.rpm_limit)?null===(x=e.litellm_budget_table)||void 0===x?void 0:x.rpm_limit:"Unlimited"]})}),(0,i.jsx)(p.Z,{children:(0,i.jsxs)(f.Z,{children:[(null===(h=e.members)||void 0===h?void 0:h.length)||0," Members"]})}),(0,i.jsx)(p.Z,{children:"Admin"===s&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(N.Z,{title:"Edit organization",children:[" ",(0,i.jsx)(c.Z,{icon:P.Z,size:"sm",className:"cursor-pointer hover:text-blue-600",onClick:()=>{ee(e.organization_id),es(!0)}})]}),(0,i.jsxs)(N.Z,{title:"Delete organization",children:[" ",(0,i.jsx)(c.Z,{onClick:()=>eh(e.organization_id),icon:A.Z,size:"sm",className:"cursor-pointer hover:text-red-600"})]})]})})]},e.organization_id)}):null})]})})})})]})})]})]})}),(0,i.jsx)(C.Z,{title:"Create Organization",visible:ed,width:800,footer:null,onCancel:()=>{ec(!1),em.resetFields()},children:(0,i.jsxs)(y.Z,{form:em,onFinish:eg,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,i.jsx)(y.Z.Item,{label:"Organization Name",name:"organization_alias",rules:[{required:!0,message:"Please input an organization name"}],children:(0,i.jsx)(T.o,{placeholder:""})}),(0,i.jsx)(y.Z.Item,{label:"Models",name:"models",children:(0,i.jsxs)(S.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},children:[(0,i.jsx)(S.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),L&&L.length>0&&L.map(e=>(0,i.jsx)(S.default.Option,{value:e,children:(0,D.W0)(e)},e))]})}),(0,i.jsx)(y.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,i.jsx)(w.Z,{step:.01,precision:2,width:200})}),(0,i.jsx)(y.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,i.jsxs)(S.default,{defaultValue:null,placeholder:"n/a",children:[(0,i.jsx)(S.default.Option,{value:"24h",children:"daily"}),(0,i.jsx)(S.default.Option,{value:"7d",children:"weekly"}),(0,i.jsx)(S.default.Option,{value:"30d",children:"monthly"})]})}),(0,i.jsx)(y.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,i.jsx)(w.Z,{step:1,width:400})}),(0,i.jsx)(y.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,i.jsx)(w.Z,{step:1,width:400})}),(0,i.jsx)(y.Z.Item,{label:(0,i.jsxs)("span",{children:["Allowed Vector Stores"," ",(0,i.jsx)(N.Z,{title:"Select which vector stores this organization can access by default. Leave empty for access to all vector stores",children:(0,i.jsx)(O.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_vector_store_ids",className:"mt-4",help:"Select vector stores this organization can access. Leave empty for access to all vector stores",children:(0,i.jsx)(Y.Z,{onChange:e=>em.setFieldValue("allowed_vector_store_ids",e),value:em.getFieldValue("allowed_vector_store_ids"),accessToken:R||"",placeholder:"Select vector stores (optional)"})}),(0,i.jsx)(y.Z.Item,{label:(0,i.jsxs)("span",{children:["Allowed MCP Servers"," ",(0,i.jsx)(N.Z,{title:"Select which MCP servers and access groups this organization can access by default.",children:(0,i.jsx)(O.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_mcp_servers_and_groups",className:"mt-4",help:"Select MCP servers and access groups this organization can access.",children:(0,i.jsx)(G.Z,{onChange:e=>em.setFieldValue("allowed_mcp_servers_and_groups",e),value:em.getFieldValue("allowed_mcp_servers_and_groups"),accessToken:R||"",placeholder:"Select MCP servers and access groups (optional)"})}),(0,i.jsx)(y.Z.Item,{label:"Metadata",name:"metadata",children:(0,i.jsx)(z.default.TextArea,{rows:4})}),(0,i.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,i.jsx)(t.Z,{type:"submit",children:"Create Organization"})})]})}),(0,i.jsx)(K.Z,{isOpen:ei,title:"Delete Organization?",message:"Are you sure you want to delete this organization? This action cannot be undone.",resourceInformationTitle:"Organization Information",resourceInformation:[{label:"Organization ID",value:er,code:!0}],onCancel:()=>{ea(!1),et(null)},onOk:e_,confirmLoading:en})]}):(0,i.jsx)("div",{children:(0,i.jsxs)(f.Z,{children:["This is a LiteLLM Enterprise feature, and requires a valid key to use. Get a trial key"," ",(0,i.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",children:"here"}),"."]})})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js b/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js deleted file mode 100644 index 600ee05936e..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2012-9200c205d5b0405a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2012],{26210:function(e,l,s){s.d(l,{UQ:function(){return t.Z},X1:function(){return i.Z},_m:function(){return a.Z},oi:function(){return n.Z},xv:function(){return r.Z}});var t=s(87452),i=s(88829),a=s(72208),r=s(84264),n=s(49566)},30078:function(e,l,s){s.d(l,{Ct:function(){return t.Z},Dx:function(){return x.Z},OK:function(){return n.Z},Zb:function(){return a.Z},nP:function(){return c.Z},oi:function(){return h.Z},rj:function(){return r.Z},td:function(){return d.Z},v0:function(){return m.Z},x4:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(67101),n=s(12485),m=s(18135),d=s(35242),o=s(29706),c=s(77991),u=s(84264),h=s(49566),x=s(96761)},62490:function(e,l,s){s.d(l,{Ct:function(){return t.Z},RM:function(){return n.Z},SC:function(){return c.Z},Zb:function(){return a.Z},iA:function(){return r.Z},pj:function(){return m.Z},ss:function(){return d.Z},xs:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(21626),n=s(97214),m=s(28241),d=s(58834),o=s(69552),c=s(71876),u=s(84264)},11318:function(e,l,s){s.d(l,{Z:function(){return n}});var t=s(2265),i=s(80443),a=s(19250);let r=async(e,l,s,t)=>"Admin"!=s&&"Admin Viewer"!=s?await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null,l):await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null);var n=()=>{let[e,l]=(0,t.useState)([]),{accessToken:s,userId:a,userRole:n}=(0,i.Z)();return(0,t.useEffect)(()=>{(async()=>{l(await r(s,a,n,null))})()},[s,a,n]),{teams:e,setTeams:l}}},21609:function(e,l,s){s.d(l,{Z:function(){return o}});var t=s(57437),i=s(57840),a=s(22116),r=s(51653),n=s(76188),m=s(4260),d=s(2265);function o(e){let{isOpen:l,title:s,alertMessage:o,message:c,resourceInformationTitle:u,resourceInformation:h,onCancel:x,onOk:b,confirmLoading:p,requiredConfirmation:g}=e,{Title:_,Text:v}=i.default,[j,f]=(0,d.useState)("");return(0,d.useEffect)(()=>{l&&f("")},[l]),(0,t.jsx)(a.Z,{title:s,open:l,onOk:b,onCancel:x,confirmLoading:p,okText:p?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!g&&j!==g||p},cancelButtonProps:{disabled:p},children:(0,t.jsxs)("div",{className:"space-y-4",children:[o&&(0,t.jsx)(r.Z,{message:o,type:"warning"}),(0,t.jsxs)("div",{className:"mt-4 p-4 bg-red-50 rounded-lg border border-red-200",children:[(0,t.jsx)(_,{level:5,className:"mb-3 text-gray-900",children:u}),(0,t.jsx)(n.Z,{column:1,size:"small",children:h&&h.map(e=>{let{label:l,value:s,...i}=e;return(0,t.jsx)(n.Z.Item,{label:(0,t.jsx)("span",{className:"font-semibold text-gray-700",children:l}),children:(0,t.jsx)(v,{...i,children:null!=s?s:"-"})},l)})})]}),(0,t.jsx)("div",{children:(0,t.jsx)(v,{children:c})}),g&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200",children:[(0,t.jsxs)(v,{className:"block text-base font-medium text-gray-700 mb-2",children:[(0,t.jsx)(v,{children:"Type "}),(0,t.jsx)(v,{strong:!0,type:"danger",children:g}),(0,t.jsx)(v,{children:" to confirm deletion:"})]}),(0,t.jsx)(m.default,{value:j,onChange:e=>f(e.target.value),placeholder:g,className:"rounded-md text-base border-gray-200",autoFocus:!0})]})]})})}},33860:function(e,l,s){var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(37592),m=s(99981),d=s(5545),o=s(7310),c=s.n(o),u=s(19250);l.Z=e=>{let{isVisible:l,onCancel:s,onSubmit:o,accessToken:h,title:x="Add Team Member",roles:b=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:p="user"}=e,[g]=a.Z.useForm(),[_,v]=(0,i.useState)([]),[j,f]=(0,i.useState)(!1),[Z,y]=(0,i.useState)("user_email"),N=async(e,l)=>{if(!e){v([]);return}f(!0);try{let s=new URLSearchParams;if(s.append(l,e),null==h)return;let t=(await (0,u.userFilterUICall)(h,s)).map(e=>({label:"user_email"===l?"".concat(e.user_email):"".concat(e.user_id),value:"user_email"===l?e.user_email:e.user_id,user:e}));v(t)}catch(e){console.error("Error fetching users:",e)}finally{f(!1)}},w=(0,i.useCallback)(c()((e,l)=>N(e,l),300),[]),k=(e,l)=>{y(l),w(e,l)},M=(e,l)=>{let s=l.user;g.setFieldsValue({user_email:s.user_email,user_id:s.user_id,role:g.getFieldValue("role")})};return(0,t.jsx)(r.Z,{title:x,open:l,onCancel:()=>{g.resetFields(),v([]),s()},footer:null,width:800,children:(0,t.jsxs)(a.Z,{form:g,onFinish:o,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:p},children:[(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>k(e,"user_email"),onSelect:(e,l)=>M(e,l),options:"user_email"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>k(e,"user_id"),onSelect:(e,l)=>M(e,l),options:"user_id"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)(a.Z.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(n.default,{defaultValue:p,children:b.map(e=>(0,t.jsx)(n.default.Option,{value:e.value,children:(0,t.jsxs)(m.Z,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(d.ZP,{type:"default",htmlType:"submit",children:"Add Member"})})]})})}},10901:function(e,l,s){s.d(l,{Z:function(){return h}});var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(5545),m=s(27281),d=s(57365),o=s(49566),c=s(92280),u=s(24199),h=e=>{var l,s,h;let{visible:x,onCancel:b,onSubmit:p,initialData:g,mode:_,config:v}=e,[j]=a.Z.useForm();console.log("Initial Data:",g),(0,i.useEffect)(()=>{if(x){if("edit"===_&&g){let e={...g,role:g.role||v.defaultRole,max_budget_in_team:g.max_budget_in_team||null,tpm_limit:g.tpm_limit||null,rpm_limit:g.rpm_limit||null};console.log("Setting form values:",e),j.setFieldsValue(e)}else{var e;j.resetFields(),j.setFieldsValue({role:v.defaultRole||(null===(e=v.roleOptions[0])||void 0===e?void 0:e.value)})}}},[x,g,_,j,v.defaultRole,v.roleOptions]);let f=async e=>{try{let l=Object.entries(e).reduce((e,l)=>{let[s,t]=l;if("string"==typeof t){let l=t.trim();return""===l&&("max_budget_in_team"===s||"tpm_limit"===s||"rpm_limit"===s)?{...e,[s]:null}:{...e,[s]:l}}return{...e,[s]:t}},{});console.log("Submitting form data:",l),p(l),j.resetFields()}catch(e){console.error("Form submission error:",e)}},Z=e=>{switch(e.type){case"input":return(0,t.jsx)(o.Z,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(u.Z,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":var l;return(0,t.jsx)(m.Z,{children:null===(l=e.options)||void 0===l?void 0:l.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))});default:return null}};return(0,t.jsx)(r.Z,{title:v.title||("add"===_?"Add Member":"Edit Member"),open:x,width:1e3,footer:null,onCancel:b,children:(0,t.jsxs)(a.Z,{form:j,onFinish:f,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[v.showEmail&&(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(o.Z,{placeholder:"user@example.com"})}),v.showEmail&&v.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(c.x,{children:"OR"})}),v.showUserId&&(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(o.Z,{placeholder:"user_123"})}),(0,t.jsx)(a.Z.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===_&&g&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(s=g.role,(null===(h=v.roleOptions.find(e=>e.value===s))||void 0===h?void 0:h.label)||s),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(m.Z,{children:"edit"===_&&g?[...v.roleOptions.filter(e=>e.value===g.role),...v.roleOptions.filter(e=>e.value!==g.role)].map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value)):v.roleOptions.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))})}),null===(l=v.additionalFields)||void 0===l?void 0:l.map(e=>(0,t.jsx)(a.Z.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:Z(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(n.ZP,{onClick:b,className:"mr-2",children:"Cancel"}),(0,t.jsx)(n.ZP,{type:"default",htmlType:"submit",children:"add"===_?"Add Member":"Save Changes"})]})]})})}},33293:function(e,l,s){s.d(l,{Z:function(){return el}});var t=s(57437),i=s(33860),a=s(19250),r=s(59872),n=s(33304),m=s(15424),d=s(10900),o=s(30078),c=s(10032),u=s(42264),h=s(5545),x=s(4260),b=s(37592),p=s(99981),g=s(63709),_=s(30401),v=s(78867),j=s(2265),f=s(21609),Z=s(95096),y=s(46468),N=s(27799),w=s(95920),k=s(68473),M=s(9114),C=s(98015),S=s(24199),T=s(97415),I=s(10901),P=s(21425),L=s(78489),F=s(12514),E=s(21626),O=s(97214),D=s(28241),A=s(58834),R=s(69552),U=s(71876),z=s(84264),B=s(96761),V=s(4156),q=s(85180),G=s(89245),K=s(78355);let $={"/key/generate":"Member can generate a virtual key for this team","/key/service-account/generate":"Member can generate a service account key (not belonging to any user) for this team","/key/update":"Member can update a virtual key belonging to this team","/key/delete":"Member can delete a virtual key belonging to this team","/key/info":"Member can get info about a virtual key belonging to this team","/key/regenerate":"Member can regenerate a virtual key belonging to this team","/key/{key_id}/regenerate":"Member can regenerate a virtual key belonging to this team","/key/list":"Member can list virtual keys belonging to this team","/key/block":"Member can block a virtual key belonging to this team","/key/unblock":"Member can unblock a virtual key belonging to this team"},J=e=>e.includes("/info")||e.includes("/list")?"GET":"POST",Q=e=>{let l=J(e),s=$[e];if(!s){for(let[l,t]of Object.entries($))if(e.includes(l)){s=t;break}}return s||(s="Access ".concat(e)),{method:l,endpoint:e,description:s,route:e}};var W=e=>{let{teamId:l,accessToken:s,canEditTeam:i}=e,[r,n]=(0,j.useState)([]),[m,d]=(0,j.useState)([]),[o,c]=(0,j.useState)(!0),[u,x]=(0,j.useState)(!1),[b,p]=(0,j.useState)(!1),g=async()=>{try{if(c(!0),!s)return;let e=await (0,a.getTeamPermissionsCall)(s,l),t=e.all_available_permissions||[];n(t);let i=e.team_member_permissions||[];d(i),p(!1)}catch(e){M.Z.fromBackend("Failed to load permissions"),console.error("Error fetching permissions:",e)}finally{c(!1)}};(0,j.useEffect)(()=>{g()},[l,s]);let _=(e,l)=>{d(l?[...m,e]:m.filter(l=>l!==e)),p(!0)},v=async()=>{try{if(!s)return;x(!0),await (0,a.teamPermissionsUpdateCall)(s,l,m),M.Z.success("Permissions updated successfully"),p(!1)}catch(e){M.Z.fromBackend("Failed to update permissions"),console.error("Error updating permissions:",e)}finally{x(!1)}};if(o)return(0,t.jsx)("div",{className:"p-6 text-center",children:"Loading permissions..."});let f=r.length>0;return(0,t.jsxs)(F.Z,{className:"bg-white shadow-md rounded-md p-6",children:[(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row justify-between items-start sm:items-center border-b pb-4 mb-6",children:[(0,t.jsx)(B.Z,{className:"mb-2 sm:mb-0",children:"Member Permissions"}),i&&b&&(0,t.jsxs)("div",{className:"flex gap-3",children:[(0,t.jsx)(h.ZP,{icon:(0,t.jsx)(G.Z,{}),onClick:()=>{g()},children:"Reset"}),(0,t.jsxs)(L.Z,{onClick:v,loading:u,className:"flex items-center gap-2",children:[(0,t.jsx)(K.Z,{})," Save Changes"]})]})]}),(0,t.jsx)(z.Z,{className:"mb-6 text-gray-600",children:"Control what team members can do when they are not team admins."}),f?(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:" min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"Method"}),(0,t.jsx)(R.Z,{children:"Endpoint"}),(0,t.jsx)(R.Z,{children:"Description"}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:"Allow Access"})]})}),(0,t.jsx)(O.Z,{children:r.map(e=>{let l=Q(e);return(0,t.jsxs)(U.Z,{className:"hover:bg-gray-50 transition-colors",children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"px-2 py-1 rounded text-xs font-medium ".concat("GET"===l.method?"bg-blue-100 text-blue-800":"bg-green-100 text-green-800"),children:l.method})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"font-mono text-sm text-gray-800",children:l.endpoint})}),(0,t.jsx)(D.Z,{className:"text-gray-700",children:l.description}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:(0,t.jsx)(V.Z,{checked:m.includes(e),onChange:l=>_(e,l.target.checked),disabled:!i})})]},e)})})]})}):(0,t.jsx)("div",{className:"py-12",children:(0,t.jsx)(q.Z,{description:"No permissions available"})})]})},X=s(47323),Y=s(53410),H=s(74998),ee=e=>{let{teamData:l,canEditTeam:s,handleMemberDelete:i,setSelectedEditMember:a,setIsEditMemberModalVisible:n,setIsAddMemberModalVisible:d}=e,o=e=>{if(null==e)return"0";if("number"==typeof e){let l=Number(e);return l===Math.floor(l)?l.toString():(0,r.pw)(l,8).replace(/\.?0+$/,"")}return"0"},c=e=>{if(!e)return 0;let s=l.team_memberships.find(l=>l.user_id===e);return(null==s?void 0:s.spend)||0},u=e=>{var s;if(!e)return null;let t=l.team_memberships.find(l=>l.user_id===e);console.log("membership=".concat(t));let i=null==t?void 0:null===(s=t.litellm_budget_table)||void 0===s?void 0:s.max_budget;return null==i?null:o(i)},h=e=>{var s,t;if(!e)return"No Limits";let i=l.team_memberships.find(l=>l.user_id===e),a=null==i?void 0:null===(s=i.litellm_budget_table)||void 0===s?void 0:s.rpm_limit,r=null==i?void 0:null===(t=i.litellm_budget_table)||void 0===t?void 0:t.tpm_limit,n=[a?"".concat(o(a)," RPM"):null,r?"".concat(o(r)," TPM"):null].filter(Boolean);return n.length>0?n.join(" / "):"No Limits"};return(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)(F.Z,{className:"w-full mx-auto flex-auto overflow-auto max-h-[50vh]",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:"min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"User ID"}),(0,t.jsx)(R.Z,{children:"User Email"}),(0,t.jsx)(R.Z,{children:"Role"}),(0,t.jsxs)(R.Z,{children:["Team Member Spend (USD)"," ",(0,t.jsx)(p.Z,{title:"This is the amount spent by a user in the team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{children:"Team Member Budget (USD)"}),(0,t.jsxs)(R.Z,{children:["Team Member Rate Limits"," ",(0,t.jsx)(p.Z,{title:"Rate limits for this member's usage within this team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:"Actions"})]})}),(0,t.jsx)(O.Z,{children:l.team_info.members_with_roles.map((e,m)=>(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_id})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_email?e.user_email:"No Email"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.role})}),(0,t.jsx)(D.Z,{children:(0,t.jsxs)(z.Z,{className:"font-mono",children:["$",(0,r.pw)(c(e.user_id),4)]})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:u(e.user_id)?"$".concat((0,r.pw)(Number(u(e.user_id)),4)):"No Limit"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:h(e.user_id)})}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:s&&(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(X.Z,{icon:Y.Z,size:"sm",onClick:()=>{var s,t,i;let r=l.team_memberships.find(l=>l.user_id===e.user_id);a({...e,max_budget_in_team:(null==r?void 0:null===(s=r.litellm_budget_table)||void 0===s?void 0:s.max_budget)||null,tpm_limit:(null==r?void 0:null===(t=r.litellm_budget_table)||void 0===t?void 0:t.tpm_limit)||null,rpm_limit:(null==r?void 0:null===(i=r.litellm_budget_table)||void 0===i?void 0:i.rpm_limit)||null}),n(!0)},className:"cursor-pointer hover:text-blue-600"}),(0,t.jsx)(X.Z,{icon:H.Z,size:"sm",onClick:()=>i(e),className:"cursor-pointer hover:text-red-600"})]})})]},m))})]})})}),(0,t.jsx)(L.Z,{onClick:()=>d(!0),children:"Add Member"})]})},el=e=>{var l,s,L,F,E,O,D,A,R,U,z,B,V,q,G,K,$,J;let{teamId:Q,onClose:X,accessToken:Y,is_team_admin:H,is_proxy_admin:el,userModels:es,editTeam:et,premiumUser:ei=!1,onUpdate:ea}=e,[er,en]=(0,j.useState)(null),[em,ed]=(0,j.useState)(!0),[eo,ec]=(0,j.useState)(!1),[eu]=c.Z.useForm(),[eh,ex]=(0,j.useState)(!1),[eb,ep]=(0,j.useState)(null),[eg,e_]=(0,j.useState)(!1),[ev,ej]=(0,j.useState)([]),[ef,eZ]=(0,j.useState)(!1),[ey,eN]=(0,j.useState)({}),[ew,ek]=(0,j.useState)([]),[eM,eC]=(0,j.useState)(null),[eS,eT]=(0,j.useState)(!1),[eI,eP]=(0,j.useState)(!1);console.log("userModels in team info",es);let eL=H||el,eF=async()=>{try{if(ed(!0),!Y)return;let e=await (0,a.teamInfoCall)(Y,Q);en(e)}catch(e){M.Z.fromBackend("Failed to load team information"),console.error("Error fetching team info:",e)}finally{ed(!1)}};(0,j.useEffect)(()=>{eF()},[Q,Y]),(0,j.useEffect)(()=>{(async()=>{try{if(!Y)return;let e=(await (0,a.getGuardrailsList)(Y)).guardrails.map(e=>e.guardrail_name);ek(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})()},[Y]);let eE=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,a.teamMemberAddCall)(Y,Q,l),M.Z.success("Team member added successfully"),ec(!1),eu.resetFields();let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(i){var l,s,t;let e="Failed to add team member";(null==i?void 0:null===(t=i.raw)||void 0===t?void 0:null===(s=t.detail)||void 0===s?void 0:null===(l=s.error)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==i?void 0:i.message)&&(e=i.message),M.Z.fromBackend(e),console.error("Error adding team member:",i)}},eO=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role,max_budget_in_team:e.max_budget_in_team,tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit};console.log("Updating member with values:",l),u.ZP.destroy(),await (0,a.teamMemberUpdateCall)(Y,Q,l),M.Z.success("Team member updated successfully"),ex(!1);let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(t){var l,s;let e="Failed to update team member";(null==t?void 0:null===(s=t.raw)||void 0===s?void 0:null===(l=s.detail)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==t?void 0:t.message)&&(e=t.message),ex(!1),u.ZP.destroy(),M.Z.fromBackend(e),console.error("Error updating team member:",t)}},eD=async()=>{if(eM&&Y){eP(!0);try{await (0,a.teamMemberDeleteCall)(Y,Q,eM),M.Z.success("Team member removed successfully");let e=await (0,a.teamInfoCall)(Y,Q);en(e),ea(e)}catch(e){M.Z.fromBackend("Failed to remove team member"),console.error("Error removing team member:",e)}finally{eP(!1),eT(!1),eC(null)}}},eA=async e=>{try{if(!Y)return;let l={};try{l=e.metadata?JSON.parse(e.metadata):{}}catch(e){M.Z.fromBackend("Invalid JSON in metadata field");return}let s=e=>null==e||"string"==typeof e&&""===e.trim()||"number"==typeof e&&Number.isNaN(e)?null:e,t={team_id:Q,team_alias:e.team_alias,models:e.models,tpm_limit:s(e.tpm_limit),rpm_limit:s(e.rpm_limit),max_budget:e.max_budget,budget_duration:e.budget_duration,metadata:{...l,guardrails:e.guardrails||[],logging:e.logging_settings||[]},organization_id:e.organization_id};t.max_budget=(0,n.C)(t.max_budget),void 0!==e.team_member_budget&&(t.team_member_budget=Number(e.team_member_budget)),void 0!==e.team_member_key_duration&&(t.team_member_key_duration=e.team_member_key_duration),(void 0!==e.team_member_tpm_limit||void 0!==e.team_member_rpm_limit)&&(t.team_member_tpm_limit=s(e.team_member_tpm_limit),t.team_member_rpm_limit=s(e.team_member_rpm_limit));let{servers:i,accessGroups:r}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]},m=new Set(i||[]),d=Object.fromEntries(Object.entries(e.mcp_tool_permissions||{}).filter(e=>{let[l]=e;return m.has(l)}));t.object_permission={},i&&(t.object_permission.mcp_servers=i),r&&(t.object_permission.mcp_access_groups=r),d&&(t.object_permission.mcp_tool_permissions=d),delete e.mcp_servers_and_groups,delete e.mcp_tool_permissions,await (0,a.teamUpdateCall)(Y,t),M.Z.success("Team settings updated successfully"),e_(!1),eF()}catch(e){console.error("Error updating team:",e)}};if(em)return(0,t.jsx)("div",{className:"p-4",children:"Loading..."});if(!(null==er?void 0:er.team_info))return(0,t.jsx)("div",{className:"p-4",children:"Team not found"});let{team_info:eR}=er,eU=async(e,l)=>{await (0,r.vQ)(e)&&(eN(e=>({...e,[l]:!0})),setTimeout(()=>{eN(e=>({...e,[l]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)(o.zx,{icon:d.Z,variant:"light",onClick:X,className:"mb-4",children:"Back to Teams"}),(0,t.jsx)(o.Dx,{children:eR.team_alias}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(o.xv,{className:"text-gray-500 font-mono",children:eR.team_id}),(0,t.jsx)(h.ZP,{type:"text",size:"small",icon:ey["team-id"]?(0,t.jsx)(_.Z,{size:12}):(0,t.jsx)(v.Z,{size:12}),onClick:()=>eU(eR.team_id,"team-id"),className:"left-2 z-10 transition-all duration-200 ".concat(ey["team-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,t.jsxs)(o.v0,{defaultIndex:et?3:0,children:[(0,t.jsx)(o.td,{className:"mb-4",children:[(0,t.jsx)(o.OK,{children:"Overview"},"overview"),...eL?[(0,t.jsx)(o.OK,{children:"Members"},"members"),(0,t.jsx)(o.OK,{children:"Member Permissions"},"member-permissions"),(0,t.jsx)(o.OK,{children:"Settings"},"settings")]:[]]}),(0,t.jsxs)(o.nP,{children:[(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Budget Status"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.Dx,{children:["$",(0,r.pw)(eR.spend,4)]}),(0,t.jsxs)(o.xv,{children:["of ",null===eR.max_budget?"Unlimited":"$".concat((0,r.pw)(eR.max_budget,4))]}),eR.budget_duration&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Reset: ",eR.budget_duration]}),(0,t.jsx)("br",{}),eR.team_member_budget_table&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Team Member Budget: $",(0,r.pw)(eR.team_member_budget_table.max_budget,4)]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Rate Limits"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)(o.xv,{children:["RPM: ",eR.rpm_limit||"Unlimited"]}),eR.max_parallel_requests&&(0,t.jsxs)(o.xv,{children:["Max Parallel Requests: ",eR.max_parallel_requests]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Models"}),(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===eR.models.length?(0,t.jsx)(o.Ct,{color:"red",children:"All proxy models"}):eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{className:"font-semibold text-gray-900",children:"Virtual Keys"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["User Keys: ",er.keys.filter(e=>e.user_id).length]}),(0,t.jsxs)(o.xv,{children:["Service Account Keys: ",er.keys.filter(e=>!e.user_id).length]}),(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Total: ",er.keys.length]})]})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"card",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(l=eR.metadata)||void 0===l?void 0:l.logging)||[],disabledCallbacks:[],variant:"card"})]})}),(0,t.jsx)(o.x4,{children:(0,t.jsx)(ee,{teamData:er,canEditTeam:eL,handleMemberDelete:e=>{eC(e),eT(!0)},setSelectedEditMember:ep,setIsEditMemberModalVisible:ex,setIsAddMemberModalVisible:ec})}),eL&&(0,t.jsx)(o.x4,{children:(0,t.jsx)(W,{teamId:Q,accessToken:Y,canEditTeam:eL})}),(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.Zb,{className:"overflow-y-auto max-h-[65vh]",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(o.Dx,{children:"Team Settings"}),eL&&!eg&&(0,t.jsx)(o.zx,{onClick:()=>e_(!0),children:"Edit Settings"})]}),eg?(0,t.jsxs)(c.Z,{form:eu,onFinish:eA,initialValues:{...eR,team_alias:eR.team_alias,models:eR.models,tpm_limit:eR.tpm_limit,rpm_limit:eR.rpm_limit,max_budget:eR.max_budget,budget_duration:eR.budget_duration,team_member_tpm_limit:null===(s=eR.team_member_budget_table)||void 0===s?void 0:s.tpm_limit,team_member_rpm_limit:null===(L=eR.team_member_budget_table)||void 0===L?void 0:L.rpm_limit,guardrails:(null===(F=eR.metadata)||void 0===F?void 0:F.guardrails)||[],disable_global_guardrails:(null===(E=eR.metadata)||void 0===E?void 0:E.disable_global_guardrails)||!1,metadata:eR.metadata?JSON.stringify((e=>{let{logging:l,...s}=e;return s})(eR.metadata),null,2):"",logging_settings:(null===(O=eR.metadata)||void 0===O?void 0:O.logging)||[],organization_id:eR.organization_id,vector_stores:(null===(D=eR.object_permission)||void 0===D?void 0:D.vector_stores)||[],mcp_servers:(null===(A=eR.object_permission)||void 0===A?void 0:A.mcp_servers)||[],mcp_access_groups:(null===(R=eR.object_permission)||void 0===R?void 0:R.mcp_access_groups)||[],mcp_servers_and_groups:{servers:(null===(U=eR.object_permission)||void 0===U?void 0:U.mcp_servers)||[],accessGroups:(null===(z=eR.object_permission)||void 0===z?void 0:z.mcp_access_groups)||[]},mcp_tool_permissions:(null===(B=eR.object_permission)||void 0===B?void 0:B.mcp_tool_permissions)||{}},layout:"vertical",children:[(0,t.jsx)(c.Z.Item,{label:"Team Name",name:"team_alias",rules:[{required:!0,message:"Please input a team name"}],children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Please select at least one model"}],children:(0,t.jsxs)(b.default,{mode:"multiple",placeholder:"Select models",children:[(el||es.includes("all-proxy-models"))&&(0,t.jsx)(b.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,t.jsx)(b.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),Array.from(new Set(es)).map((e,l)=>(0,t.jsx)(b.default.Option,{value:e,children:(0,y.W0)(e)},l))]})}),(0,t.jsx)(c.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Budget (USD)",name:"team_member_budget",tooltip:"This is the individual budget for a user in the team.",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Key Duration (eg: 1d, 1mo)",name:"team_member_key_duration",tooltip:"Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",children:(0,t.jsx)(o.oi,{placeholder:"e.g., 30d"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member TPM Limit",name:"team_member_tpm_limit",tooltip:"Default tokens per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 1000"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member RPM Limit",name:"team_member_rpm_limit",tooltip:"Default requests per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 100"})}),(0,t.jsx)(c.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsxs)(b.default,{placeholder:"n/a",children:[(0,t.jsx)(b.default.Option,{value:"24h",children:"daily"}),(0,t.jsx)(b.default.Option,{value:"7d",children:"weekly"}),(0,t.jsx)(b.default.Option,{value:"30d",children:"monthly"})]})}),(0,t.jsx)(c.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(p.Z,{title:"Setup your first guardrail",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",help:"Select existing guardrails or enter new ones",children:(0,t.jsx)(b.default,{mode:"tags",placeholder:"Select or enter guardrails",options:ew.map(e=>({value:e,label:e}))})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,t.jsx)(p.Z,{title:"When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),name:"disable_global_guardrails",valuePropName:"checked",help:"Bypass global guardrails for this team",children:(0,t.jsx)(g.Z,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(c.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,t.jsx)(T.Z,{onChange:e=>eu.setFieldValue("vector_stores",e),value:eu.getFieldValue("vector_stores"),accessToken:Y||"",placeholder:"Select vector stores"})}),(0,t.jsx)(c.Z.Item,{label:"Allowed Pass Through Routes",name:"allowed_passthrough_routes",children:(0,t.jsx)(Z.Z,{onChange:e=>eu.setFieldValue("allowed_passthrough_routes",e),value:eu.getFieldValue("allowed_passthrough_routes"),accessToken:Y||"",placeholder:"Select pass through routes"})}),(0,t.jsx)(c.Z.Item,{label:"MCP Servers / Access Groups",name:"mcp_servers_and_groups",children:(0,t.jsx)(w.Z,{onChange:e=>eu.setFieldValue("mcp_servers_and_groups",e),value:eu.getFieldValue("mcp_servers_and_groups"),accessToken:Y||"",placeholder:"Select MCP servers or access groups (optional)"})}),(0,t.jsx)(c.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(x.default,{type:"hidden"})}),(0,t.jsx)(c.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.mcp_servers_and_groups!==l.mcp_servers_and_groups||e.mcp_tool_permissions!==l.mcp_tool_permissions,children:()=>{var e;return(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(k.Z,{accessToken:Y||"",selectedServers:(null===(e=eu.getFieldValue("mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:eu.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eu.setFieldsValue({mcp_tool_permissions:e})})})}}),(0,t.jsx)(c.Z.Item,{label:"Organization ID",name:"organization_id",children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Logging Settings",name:"logging_settings",children:(0,t.jsx)(P.Z,{value:eu.getFieldValue("logging_settings"),onChange:e=>eu.setFieldValue("logging_settings",e)})}),(0,t.jsx)(c.Z.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(x.default.TextArea,{rows:10})}),(0,t.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,t.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,t.jsx)(o.zx,{variant:"secondary",onClick:()=>e_(!1),children:"Cancel"}),(0,t.jsx)(o.zx,{type:"submit",children:"Save Changes"})]})})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Name"}),(0,t.jsx)("div",{children:eR.team_alias})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team ID"}),(0,t.jsx)("div",{className:"font-mono",children:eR.team_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Created At"}),(0,t.jsx)("div",{children:new Date(eR.created_at).toLocaleString()})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:eR.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Rate Limits"}),(0,t.jsxs)("div",{children:["TPM: ",eR.tpm_limit||"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",eR.rpm_limit||"Unlimited"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Budget"}),(0,t.jsxs)("div",{children:["Max Budget:"," ",null!==eR.max_budget?"$".concat((0,r.pw)(eR.max_budget,4)):"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Reset: ",eR.budget_duration||"Never"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)(o.xv,{className:"font-medium",children:["Team Member Settings"," ",(0,t.jsx)(p.Z,{title:"These are limits on individual team members",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),(0,t.jsxs)("div",{children:["Max Budget: ",(null===(V=eR.team_member_budget_table)||void 0===V?void 0:V.max_budget)||"No Limit"]}),(0,t.jsxs)("div",{children:["Key Duration: ",(null===(q=eR.metadata)||void 0===q?void 0:q.team_member_key_duration)||"No Limit"]}),(0,t.jsxs)("div",{children:["TPM Limit: ",(null===(G=eR.team_member_budget_table)||void 0===G?void 0:G.tpm_limit)||"No Limit"]}),(0,t.jsxs)("div",{children:["RPM Limit: ",(null===(K=eR.team_member_budget_table)||void 0===K?void 0:K.rpm_limit)||"No Limit"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Organization ID"}),(0,t.jsx)("div",{children:eR.organization_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Status"}),(0,t.jsx)(o.Ct,{color:eR.blocked?"red":"green",children:eR.blocked?"Blocked":"Active"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Disable Global Guardrails"}),(0,t.jsx)("div",{children:(null===($=eR.metadata)||void 0===$?void 0:$.disable_global_guardrails)===!0?(0,t.jsx)(o.Ct,{color:"yellow",children:"Enabled - Global guardrails bypassed"}):(0,t.jsx)(o.Ct,{color:"green",children:"Disabled - Global guardrails active"})})]}),(0,t.jsx)(C.Z,{objectPermission:eR.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(J=eR.metadata)||void 0===J?void 0:J.logging)||[],disabledCallbacks:[],variant:"inline",className:"pt-4 border-t border-gray-200"})]})]})})]})]}),(0,t.jsx)(I.Z,{visible:eh,onCancel:()=>ex(!1),onSubmit:eO,initialData:eb,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Admin",value:"admin"},{label:"User",value:"user"}],additionalFields:[{name:"max_budget_in_team",label:(0,t.jsxs)("span",{children:["Team Member Budget (USD)"," ",(0,t.jsx)(p.Z,{title:"Maximum amount in USD this member can spend within this team. This is separate from any global user budget limits",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:.01,min:0,placeholder:"Budget limit for this member within this team"},{name:"tpm_limit",label:(0,t.jsxs)("span",{children:["Team Member TPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum tokens per minute this member can use within this team. This is separate from any global user TPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Tokens per minute limit for this member in this team"},{name:"rpm_limit",label:(0,t.jsxs)("span",{children:["Team Member RPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum requests per minute this member can make within this team. This is separate from any global user RPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Requests per minute limit for this member in this team"}]}}),(0,t.jsx)(i.Z,{isVisible:eo,onCancel:()=>ec(!1),onSubmit:eE,accessToken:Y}),(0,t.jsx)(f.Z,{isOpen:eS,title:"Delete Team Member",alertMessage:"Removing team members will also delete any keys created by or created for this member.",message:"Are you sure you want to remove this member from the team? This action cannot be undone.",resourceInformationTitle:"Team Member Information",resourceInformation:[{label:"User ID",value:null==eM?void 0:eM.user_id,code:!0},{label:"Email",value:null==eM?void 0:eM.user_email},{label:"Role",value:null==eM?void 0:eM.role}],onCancel:()=>{eT(!1),eC(null)},onOk:eD,confirmLoading:eI})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2012-c09fa25a9cbf6028.js b/litellm/proxy/_experimental/out/_next/static/chunks/2012-c09fa25a9cbf6028.js new file mode 100644 index 00000000000..fc87e2ebf09 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2012-c09fa25a9cbf6028.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2012],{26210:function(e,l,s){s.d(l,{UQ:function(){return t.Z},X1:function(){return i.Z},_m:function(){return a.Z},oi:function(){return n.Z},xv:function(){return r.Z}});var t=s(87452),i=s(88829),a=s(72208),r=s(84264),n=s(49566)},30078:function(e,l,s){s.d(l,{Ct:function(){return t.Z},Dx:function(){return x.Z},OK:function(){return n.Z},Zb:function(){return a.Z},nP:function(){return c.Z},oi:function(){return h.Z},rj:function(){return r.Z},td:function(){return d.Z},v0:function(){return m.Z},x4:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(67101),n=s(12485),m=s(18135),d=s(35242),o=s(29706),c=s(77991),u=s(84264),h=s(49566),x=s(96761)},62490:function(e,l,s){s.d(l,{Ct:function(){return t.Z},RM:function(){return n.Z},SC:function(){return c.Z},Zb:function(){return a.Z},iA:function(){return r.Z},pj:function(){return m.Z},ss:function(){return d.Z},xs:function(){return o.Z},xv:function(){return u.Z},zx:function(){return i.Z}});var t=s(41649),i=s(78489),a=s(12514),r=s(21626),n=s(97214),m=s(28241),d=s(58834),o=s(69552),c=s(71876),u=s(84264)},11318:function(e,l,s){s.d(l,{Z:function(){return n}});var t=s(2265),i=s(39760),a=s(19250);let r=async(e,l,s,t)=>"Admin"!=s&&"Admin Viewer"!=s?await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null,l):await (0,a.teamListCall)(e,(null==t?void 0:t.organization_id)||null);var n=()=>{let[e,l]=(0,t.useState)([]),{accessToken:s,userId:a,userRole:n}=(0,i.Z)();return(0,t.useEffect)(()=>{(async()=>{l(await r(s,a,n,null))})()},[s,a,n]),{teams:e,setTeams:l}}},21609:function(e,l,s){s.d(l,{Z:function(){return o}});var t=s(57437),i=s(57840),a=s(22116),r=s(51653),n=s(76188),m=s(4260),d=s(2265);function o(e){let{isOpen:l,title:s,alertMessage:o,message:c,resourceInformationTitle:u,resourceInformation:h,onCancel:x,onOk:b,confirmLoading:p,requiredConfirmation:g}=e,{Title:_,Text:v}=i.default,[j,f]=(0,d.useState)("");return(0,d.useEffect)(()=>{l&&f("")},[l]),(0,t.jsx)(a.Z,{title:s,open:l,onOk:b,onCancel:x,confirmLoading:p,okText:p?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!g&&j!==g||p},cancelButtonProps:{disabled:p},children:(0,t.jsxs)("div",{className:"space-y-4",children:[o&&(0,t.jsx)(r.Z,{message:o,type:"warning"}),(0,t.jsxs)("div",{className:"mt-4 p-4 bg-red-50 rounded-lg border border-red-200",children:[(0,t.jsx)(_,{level:5,className:"mb-3 text-gray-900",children:u}),(0,t.jsx)(n.Z,{column:1,size:"small",children:h&&h.map(e=>{let{label:l,value:s,...i}=e;return(0,t.jsx)(n.Z.Item,{label:(0,t.jsx)("span",{className:"font-semibold text-gray-700",children:l}),children:(0,t.jsx)(v,{...i,children:null!=s?s:"-"})},l)})})]}),(0,t.jsx)("div",{children:(0,t.jsx)(v,{children:c})}),g&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200",children:[(0,t.jsxs)(v,{className:"block text-base font-medium text-gray-700 mb-2",children:[(0,t.jsx)(v,{children:"Type "}),(0,t.jsx)(v,{strong:!0,type:"danger",children:g}),(0,t.jsx)(v,{children:" to confirm deletion:"})]}),(0,t.jsx)(m.default,{value:j,onChange:e=>f(e.target.value),placeholder:g,className:"rounded-md text-base border-gray-200",autoFocus:!0})]})]})})}},33860:function(e,l,s){var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(37592),m=s(99981),d=s(5545),o=s(7310),c=s.n(o),u=s(19250);l.Z=e=>{let{isVisible:l,onCancel:s,onSubmit:o,accessToken:h,title:x="Add Team Member",roles:b=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:p="user"}=e,[g]=a.Z.useForm(),[_,v]=(0,i.useState)([]),[j,f]=(0,i.useState)(!1),[Z,y]=(0,i.useState)("user_email"),N=async(e,l)=>{if(!e){v([]);return}f(!0);try{let s=new URLSearchParams;if(s.append(l,e),null==h)return;let t=(await (0,u.userFilterUICall)(h,s)).map(e=>({label:"user_email"===l?"".concat(e.user_email):"".concat(e.user_id),value:"user_email"===l?e.user_email:e.user_id,user:e}));v(t)}catch(e){console.error("Error fetching users:",e)}finally{f(!1)}},w=(0,i.useCallback)(c()((e,l)=>N(e,l),300),[]),k=(e,l)=>{y(l),w(e,l)},M=(e,l)=>{let s=l.user;g.setFieldsValue({user_email:s.user_email,user_id:s.user_id,role:g.getFieldValue("role")})};return(0,t.jsx)(r.Z,{title:x,open:l,onCancel:()=>{g.resetFields(),v([]),s()},footer:null,width:800,children:(0,t.jsxs)(a.Z,{form:g,onFinish:o,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:p},children:[(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>k(e,"user_email"),onSelect:(e,l)=>M(e,l),options:"user_email"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>k(e,"user_id"),onSelect:(e,l)=>M(e,l),options:"user_id"===Z?_:[],loading:j,allowClear:!0})}),(0,t.jsx)(a.Z.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(n.default,{defaultValue:p,children:b.map(e=>(0,t.jsx)(n.default.Option,{value:e.value,children:(0,t.jsxs)(m.Z,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(d.ZP,{type:"default",htmlType:"submit",children:"Add Member"})})]})})}},10901:function(e,l,s){s.d(l,{Z:function(){return h}});var t=s(57437),i=s(2265),a=s(10032),r=s(22116),n=s(5545),m=s(27281),d=s(57365),o=s(49566),c=s(92280),u=s(24199),h=e=>{var l,s,h;let{visible:x,onCancel:b,onSubmit:p,initialData:g,mode:_,config:v}=e,[j]=a.Z.useForm();console.log("Initial Data:",g),(0,i.useEffect)(()=>{if(x){if("edit"===_&&g){let e={...g,role:g.role||v.defaultRole,max_budget_in_team:g.max_budget_in_team||null,tpm_limit:g.tpm_limit||null,rpm_limit:g.rpm_limit||null};console.log("Setting form values:",e),j.setFieldsValue(e)}else{var e;j.resetFields(),j.setFieldsValue({role:v.defaultRole||(null===(e=v.roleOptions[0])||void 0===e?void 0:e.value)})}}},[x,g,_,j,v.defaultRole,v.roleOptions]);let f=async e=>{try{let l=Object.entries(e).reduce((e,l)=>{let[s,t]=l;if("string"==typeof t){let l=t.trim();return""===l&&("max_budget_in_team"===s||"tpm_limit"===s||"rpm_limit"===s)?{...e,[s]:null}:{...e,[s]:l}}return{...e,[s]:t}},{});console.log("Submitting form data:",l),p(l),j.resetFields()}catch(e){console.error("Form submission error:",e)}},Z=e=>{switch(e.type){case"input":return(0,t.jsx)(o.Z,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(u.Z,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":var l;return(0,t.jsx)(m.Z,{children:null===(l=e.options)||void 0===l?void 0:l.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))});default:return null}};return(0,t.jsx)(r.Z,{title:v.title||("add"===_?"Add Member":"Edit Member"),open:x,width:1e3,footer:null,onCancel:b,children:(0,t.jsxs)(a.Z,{form:j,onFinish:f,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[v.showEmail&&(0,t.jsx)(a.Z.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(o.Z,{placeholder:"user@example.com"})}),v.showEmail&&v.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(c.x,{children:"OR"})}),v.showUserId&&(0,t.jsx)(a.Z.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(o.Z,{placeholder:"user_123"})}),(0,t.jsx)(a.Z.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===_&&g&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(s=g.role,(null===(h=v.roleOptions.find(e=>e.value===s))||void 0===h?void 0:h.label)||s),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(m.Z,{children:"edit"===_&&g?[...v.roleOptions.filter(e=>e.value===g.role),...v.roleOptions.filter(e=>e.value!==g.role)].map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value)):v.roleOptions.map(e=>(0,t.jsx)(d.Z,{value:e.value,children:e.label},e.value))})}),null===(l=v.additionalFields)||void 0===l?void 0:l.map(e=>(0,t.jsx)(a.Z.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:Z(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(n.ZP,{onClick:b,className:"mr-2",children:"Cancel"}),(0,t.jsx)(n.ZP,{type:"default",htmlType:"submit",children:"add"===_?"Add Member":"Save Changes"})]})]})})}},33293:function(e,l,s){s.d(l,{Z:function(){return el}});var t=s(57437),i=s(33860),a=s(19250),r=s(59872),n=s(33304),m=s(15424),d=s(10900),o=s(30078),c=s(10032),u=s(42264),h=s(5545),x=s(4260),b=s(37592),p=s(99981),g=s(63709),_=s(30401),v=s(78867),j=s(2265),f=s(21609),Z=s(95096),y=s(46468),N=s(27799),w=s(95920),k=s(68473),M=s(9114),C=s(98015),S=s(24199),T=s(97415),I=s(10901),P=s(21425),L=s(78489),F=s(12514),E=s(21626),O=s(97214),D=s(28241),A=s(58834),R=s(69552),U=s(71876),z=s(84264),B=s(96761),V=s(61994),q=s(85180),G=s(89245),K=s(78355);let $={"/key/generate":"Member can generate a virtual key for this team","/key/service-account/generate":"Member can generate a service account key (not belonging to any user) for this team","/key/update":"Member can update a virtual key belonging to this team","/key/delete":"Member can delete a virtual key belonging to this team","/key/info":"Member can get info about a virtual key belonging to this team","/key/regenerate":"Member can regenerate a virtual key belonging to this team","/key/{key_id}/regenerate":"Member can regenerate a virtual key belonging to this team","/key/list":"Member can list virtual keys belonging to this team","/key/block":"Member can block a virtual key belonging to this team","/key/unblock":"Member can unblock a virtual key belonging to this team"},J=e=>e.includes("/info")||e.includes("/list")?"GET":"POST",Q=e=>{let l=J(e),s=$[e];if(!s){for(let[l,t]of Object.entries($))if(e.includes(l)){s=t;break}}return s||(s="Access ".concat(e)),{method:l,endpoint:e,description:s,route:e}};var W=e=>{let{teamId:l,accessToken:s,canEditTeam:i}=e,[r,n]=(0,j.useState)([]),[m,d]=(0,j.useState)([]),[o,c]=(0,j.useState)(!0),[u,x]=(0,j.useState)(!1),[b,p]=(0,j.useState)(!1),g=async()=>{try{if(c(!0),!s)return;let e=await (0,a.getTeamPermissionsCall)(s,l),t=e.all_available_permissions||[];n(t);let i=e.team_member_permissions||[];d(i),p(!1)}catch(e){M.Z.fromBackend("Failed to load permissions"),console.error("Error fetching permissions:",e)}finally{c(!1)}};(0,j.useEffect)(()=>{g()},[l,s]);let _=(e,l)=>{d(l?[...m,e]:m.filter(l=>l!==e)),p(!0)},v=async()=>{try{if(!s)return;x(!0),await (0,a.teamPermissionsUpdateCall)(s,l,m),M.Z.success("Permissions updated successfully"),p(!1)}catch(e){M.Z.fromBackend("Failed to update permissions"),console.error("Error updating permissions:",e)}finally{x(!1)}};if(o)return(0,t.jsx)("div",{className:"p-6 text-center",children:"Loading permissions..."});let f=r.length>0;return(0,t.jsxs)(F.Z,{className:"bg-white shadow-md rounded-md p-6",children:[(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row justify-between items-start sm:items-center border-b pb-4 mb-6",children:[(0,t.jsx)(B.Z,{className:"mb-2 sm:mb-0",children:"Member Permissions"}),i&&b&&(0,t.jsxs)("div",{className:"flex gap-3",children:[(0,t.jsx)(h.ZP,{icon:(0,t.jsx)(G.Z,{}),onClick:()=>{g()},children:"Reset"}),(0,t.jsxs)(L.Z,{onClick:v,loading:u,className:"flex items-center gap-2",children:[(0,t.jsx)(K.Z,{})," Save Changes"]})]})]}),(0,t.jsx)(z.Z,{className:"mb-6 text-gray-600",children:"Control what team members can do when they are not team admins."}),f?(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:" min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"Method"}),(0,t.jsx)(R.Z,{children:"Endpoint"}),(0,t.jsx)(R.Z,{children:"Description"}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:"Allow Access"})]})}),(0,t.jsx)(O.Z,{children:r.map(e=>{let l=Q(e);return(0,t.jsxs)(U.Z,{className:"hover:bg-gray-50 transition-colors",children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"px-2 py-1 rounded text-xs font-medium ".concat("GET"===l.method?"bg-blue-100 text-blue-800":"bg-green-100 text-green-800"),children:l.method})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)("span",{className:"font-mono text-sm text-gray-800",children:l.endpoint})}),(0,t.jsx)(D.Z,{className:"text-gray-700",children:l.description}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:(0,t.jsx)(V.Z,{checked:m.includes(e),onChange:l=>_(e,l.target.checked),disabled:!i})})]},e)})})]})}):(0,t.jsx)("div",{className:"py-12",children:(0,t.jsx)(q.Z,{description:"No permissions available"})})]})},X=s(47323),Y=s(53410),H=s(74998),ee=e=>{let{teamData:l,canEditTeam:s,handleMemberDelete:i,setSelectedEditMember:a,setIsEditMemberModalVisible:n,setIsAddMemberModalVisible:d}=e,o=e=>{if(null==e)return"0";if("number"==typeof e){let l=Number(e);return l===Math.floor(l)?l.toString():(0,r.pw)(l,8).replace(/\.?0+$/,"")}return"0"},c=e=>{if(!e)return 0;let s=l.team_memberships.find(l=>l.user_id===e);return(null==s?void 0:s.spend)||0},u=e=>{var s;if(!e)return null;let t=l.team_memberships.find(l=>l.user_id===e);console.log("membership=".concat(t));let i=null==t?void 0:null===(s=t.litellm_budget_table)||void 0===s?void 0:s.max_budget;return null==i?null:o(i)},h=e=>{var s,t;if(!e)return"No Limits";let i=l.team_memberships.find(l=>l.user_id===e),a=null==i?void 0:null===(s=i.litellm_budget_table)||void 0===s?void 0:s.rpm_limit,r=null==i?void 0:null===(t=i.litellm_budget_table)||void 0===t?void 0:t.tpm_limit,n=[a?"".concat(o(a)," RPM"):null,r?"".concat(o(r)," TPM"):null].filter(Boolean);return n.length>0?n.join(" / "):"No Limits"};return(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)(F.Z,{className:"w-full mx-auto flex-auto overflow-auto max-h-[50vh]",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(E.Z,{className:"min-w-full",children:[(0,t.jsx)(A.Z,{children:(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(R.Z,{children:"User ID"}),(0,t.jsx)(R.Z,{children:"User Email"}),(0,t.jsx)(R.Z,{children:"Role"}),(0,t.jsxs)(R.Z,{children:["Team Member Spend (USD)"," ",(0,t.jsx)(p.Z,{title:"This is the amount spent by a user in the team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{children:"Team Member Budget (USD)"}),(0,t.jsxs)(R.Z,{children:["Team Member Rate Limits"," ",(0,t.jsx)(p.Z,{title:"Rate limits for this member's usage within this team.",children:(0,t.jsx)(m.Z,{})})]}),(0,t.jsx)(R.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:"Actions"})]})}),(0,t.jsx)(O.Z,{children:l.team_info.members_with_roles.map((e,m)=>(0,t.jsxs)(U.Z,{children:[(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_id})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.user_email?e.user_email:"No Email"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:e.role})}),(0,t.jsx)(D.Z,{children:(0,t.jsxs)(z.Z,{className:"font-mono",children:["$",(0,r.pw)(c(e.user_id),4)]})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:u(e.user_id)?"$".concat((0,r.pw)(Number(u(e.user_id)),4)):"No Limit"})}),(0,t.jsx)(D.Z,{children:(0,t.jsx)(z.Z,{className:"font-mono",children:h(e.user_id)})}),(0,t.jsx)(D.Z,{className:"sticky right-0 bg-white z-10 border-l border-gray-200",children:s&&(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(X.Z,{icon:Y.Z,size:"sm",onClick:()=>{var s,t,i;let r=l.team_memberships.find(l=>l.user_id===e.user_id);a({...e,max_budget_in_team:(null==r?void 0:null===(s=r.litellm_budget_table)||void 0===s?void 0:s.max_budget)||null,tpm_limit:(null==r?void 0:null===(t=r.litellm_budget_table)||void 0===t?void 0:t.tpm_limit)||null,rpm_limit:(null==r?void 0:null===(i=r.litellm_budget_table)||void 0===i?void 0:i.rpm_limit)||null}),n(!0)},className:"cursor-pointer hover:text-blue-600"}),(0,t.jsx)(X.Z,{icon:H.Z,size:"sm",onClick:()=>i(e),className:"cursor-pointer hover:text-red-600"})]})})]},m))})]})})}),(0,t.jsx)(L.Z,{onClick:()=>d(!0),children:"Add Member"})]})},el=e=>{var l,s,L,F,E,O,D,A,R,U,z,B,V,q,G,K,$,J;let{teamId:Q,onClose:X,accessToken:Y,is_team_admin:H,is_proxy_admin:el,userModels:es,editTeam:et,premiumUser:ei=!1,onUpdate:ea}=e,[er,en]=(0,j.useState)(null),[em,ed]=(0,j.useState)(!0),[eo,ec]=(0,j.useState)(!1),[eu]=c.Z.useForm(),[eh,ex]=(0,j.useState)(!1),[eb,ep]=(0,j.useState)(null),[eg,e_]=(0,j.useState)(!1),[ev,ej]=(0,j.useState)([]),[ef,eZ]=(0,j.useState)(!1),[ey,eN]=(0,j.useState)({}),[ew,ek]=(0,j.useState)([]),[eM,eC]=(0,j.useState)(null),[eS,eT]=(0,j.useState)(!1),[eI,eP]=(0,j.useState)(!1),[eL,eF]=(0,j.useState)(!1);console.log("userModels in team info",es);let eE=H||el,eO=async()=>{try{if(ed(!0),!Y)return;let e=await (0,a.teamInfoCall)(Y,Q);en(e)}catch(e){M.Z.fromBackend("Failed to load team information"),console.error("Error fetching team info:",e)}finally{ed(!1)}};(0,j.useEffect)(()=>{eO()},[Q,Y]),(0,j.useEffect)(()=>{(async()=>{try{if(!Y)return;let e=(await (0,a.getGuardrailsList)(Y)).guardrails.map(e=>e.guardrail_name);ek(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})()},[Y]);let eD=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role};await (0,a.teamMemberAddCall)(Y,Q,l),M.Z.success("Team member added successfully"),ec(!1),eu.resetFields();let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(i){var l,s,t;let e="Failed to add team member";(null==i?void 0:null===(t=i.raw)||void 0===t?void 0:null===(s=t.detail)||void 0===s?void 0:null===(l=s.error)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==i?void 0:i.message)&&(e=i.message),M.Z.fromBackend(e),console.error("Error adding team member:",i)}},eA=async e=>{try{if(null==Y)return;let l={user_email:e.user_email,user_id:e.user_id,role:e.role,max_budget_in_team:e.max_budget_in_team,tpm_limit:e.tpm_limit,rpm_limit:e.rpm_limit};console.log("Updating member with values:",l),u.ZP.destroy(),await (0,a.teamMemberUpdateCall)(Y,Q,l),M.Z.success("Team member updated successfully"),ex(!1);let s=await (0,a.teamInfoCall)(Y,Q);en(s),ea(s)}catch(t){var l,s;let e="Failed to update team member";(null==t?void 0:null===(s=t.raw)||void 0===s?void 0:null===(l=s.detail)||void 0===l?void 0:l.includes("Assigning team admins is a premium feature"))?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":(null==t?void 0:t.message)&&(e=t.message),ex(!1),u.ZP.destroy(),M.Z.fromBackend(e),console.error("Error updating team member:",t)}},eR=async()=>{if(eM&&Y){eP(!0);try{await (0,a.teamMemberDeleteCall)(Y,Q,eM),M.Z.success("Team member removed successfully");let e=await (0,a.teamInfoCall)(Y,Q);en(e),ea(e)}catch(e){M.Z.fromBackend("Failed to remove team member"),console.error("Error removing team member:",e)}finally{eP(!1),eT(!1),eC(null)}}},eU=async e=>{try{if(!Y)return;eF(!0);let l={};try{l=e.metadata?JSON.parse(e.metadata):{}}catch(e){M.Z.fromBackend("Invalid JSON in metadata field");return}let s=e=>null==e||"string"==typeof e&&""===e.trim()||"number"==typeof e&&Number.isNaN(e)?null:e,t={team_id:Q,team_alias:e.team_alias,models:e.models,tpm_limit:s(e.tpm_limit),rpm_limit:s(e.rpm_limit),max_budget:e.max_budget,budget_duration:e.budget_duration,metadata:{...l,guardrails:e.guardrails||[],logging:e.logging_settings||[]},organization_id:e.organization_id};t.max_budget=(0,n.C)(t.max_budget),void 0!==e.team_member_budget&&(t.team_member_budget=Number(e.team_member_budget)),void 0!==e.team_member_key_duration&&(t.team_member_key_duration=e.team_member_key_duration),(void 0!==e.team_member_tpm_limit||void 0!==e.team_member_rpm_limit)&&(t.team_member_tpm_limit=s(e.team_member_tpm_limit),t.team_member_rpm_limit=s(e.team_member_rpm_limit));let{servers:i,accessGroups:r}=e.mcp_servers_and_groups||{servers:[],accessGroups:[]},m=new Set(i||[]),d=Object.fromEntries(Object.entries(e.mcp_tool_permissions||{}).filter(e=>{let[l]=e;return m.has(l)}));t.object_permission={},i&&(t.object_permission.mcp_servers=i),r&&(t.object_permission.mcp_access_groups=r),d&&(t.object_permission.mcp_tool_permissions=d),delete e.mcp_servers_and_groups,delete e.mcp_tool_permissions,await (0,a.teamUpdateCall)(Y,t),M.Z.success("Team settings updated successfully"),e_(!1),eO()}catch(e){console.error("Error updating team:",e)}finally{eF(!1)}};if(em)return(0,t.jsx)("div",{className:"p-4",children:"Loading..."});if(!(null==er?void 0:er.team_info))return(0,t.jsx)("div",{className:"p-4",children:"Team not found"});let{team_info:ez}=er,eB=async(e,l)=>{await (0,r.vQ)(e)&&(eN(e=>({...e,[l]:!0})),setTimeout(()=>{eN(e=>({...e,[l]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)(o.zx,{icon:d.Z,variant:"light",onClick:X,className:"mb-4",children:"Back to Teams"}),(0,t.jsx)(o.Dx,{children:ez.team_alias}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(o.xv,{className:"text-gray-500 font-mono",children:ez.team_id}),(0,t.jsx)(h.ZP,{type:"text",size:"small",icon:ey["team-id"]?(0,t.jsx)(_.Z,{size:12}):(0,t.jsx)(v.Z,{size:12}),onClick:()=>eB(ez.team_id,"team-id"),className:"left-2 z-10 transition-all duration-200 ".concat(ey["team-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]})}),(0,t.jsxs)(o.v0,{defaultIndex:et?3:0,children:[(0,t.jsx)(o.td,{className:"mb-4",children:[(0,t.jsx)(o.OK,{children:"Overview"},"overview"),...eE?[(0,t.jsx)(o.OK,{children:"Members"},"members"),(0,t.jsx)(o.OK,{children:"Member Permissions"},"member-permissions"),(0,t.jsx)(o.OK,{children:"Settings"},"settings")]:[]]}),(0,t.jsxs)(o.nP,{children:[(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Budget Status"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.Dx,{children:["$",(0,r.pw)(ez.spend,4)]}),(0,t.jsxs)(o.xv,{children:["of ",null===ez.max_budget?"Unlimited":"$".concat((0,r.pw)(ez.max_budget,4))]}),ez.budget_duration&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Reset: ",ez.budget_duration]}),(0,t.jsx)("br",{}),ez.team_member_budget_table&&(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Team Member Budget: $",(0,r.pw)(ez.team_member_budget_table.max_budget,4)]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Rate Limits"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["TPM: ",ez.tpm_limit||"Unlimited"]}),(0,t.jsxs)(o.xv,{children:["RPM: ",ez.rpm_limit||"Unlimited"]}),ez.max_parallel_requests&&(0,t.jsxs)(o.xv,{children:["Max Parallel Requests: ",ez.max_parallel_requests]})]})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{children:"Models"}),(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===ez.models.length?(0,t.jsx)(o.Ct,{color:"red",children:"All proxy models"}):ez.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)(o.Zb,{children:[(0,t.jsx)(o.xv,{className:"font-semibold text-gray-900",children:"Virtual Keys"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(o.xv,{children:["User Keys: ",er.keys.filter(e=>e.user_id).length]}),(0,t.jsxs)(o.xv,{children:["Service Account Keys: ",er.keys.filter(e=>!e.user_id).length]}),(0,t.jsxs)(o.xv,{className:"text-gray-500",children:["Total: ",er.keys.length]})]})]}),(0,t.jsx)(C.Z,{objectPermission:ez.object_permission,variant:"card",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(l=ez.metadata)||void 0===l?void 0:l.logging)||[],disabledCallbacks:[],variant:"card"})]})}),(0,t.jsx)(o.x4,{children:(0,t.jsx)(ee,{teamData:er,canEditTeam:eE,handleMemberDelete:e=>{eC(e),eT(!0)},setSelectedEditMember:ep,setIsEditMemberModalVisible:ex,setIsAddMemberModalVisible:ec})}),eE&&(0,t.jsx)(o.x4,{children:(0,t.jsx)(W,{teamId:Q,accessToken:Y,canEditTeam:eE})}),(0,t.jsx)(o.x4,{children:(0,t.jsxs)(o.Zb,{className:"overflow-y-auto max-h-[65vh]",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(o.Dx,{children:"Team Settings"}),eE&&!eg&&(0,t.jsx)(o.zx,{onClick:()=>e_(!0),children:"Edit Settings"})]}),eg?(0,t.jsxs)(c.Z,{form:eu,onFinish:eU,initialValues:{...ez,team_alias:ez.team_alias,models:ez.models,tpm_limit:ez.tpm_limit,rpm_limit:ez.rpm_limit,max_budget:ez.max_budget,budget_duration:ez.budget_duration,team_member_tpm_limit:null===(s=ez.team_member_budget_table)||void 0===s?void 0:s.tpm_limit,team_member_rpm_limit:null===(L=ez.team_member_budget_table)||void 0===L?void 0:L.rpm_limit,guardrails:(null===(F=ez.metadata)||void 0===F?void 0:F.guardrails)||[],disable_global_guardrails:(null===(E=ez.metadata)||void 0===E?void 0:E.disable_global_guardrails)||!1,metadata:ez.metadata?JSON.stringify((e=>{let{logging:l,...s}=e;return s})(ez.metadata),null,2):"",logging_settings:(null===(O=ez.metadata)||void 0===O?void 0:O.logging)||[],organization_id:ez.organization_id,vector_stores:(null===(D=ez.object_permission)||void 0===D?void 0:D.vector_stores)||[],mcp_servers:(null===(A=ez.object_permission)||void 0===A?void 0:A.mcp_servers)||[],mcp_access_groups:(null===(R=ez.object_permission)||void 0===R?void 0:R.mcp_access_groups)||[],mcp_servers_and_groups:{servers:(null===(U=ez.object_permission)||void 0===U?void 0:U.mcp_servers)||[],accessGroups:(null===(z=ez.object_permission)||void 0===z?void 0:z.mcp_access_groups)||[]},mcp_tool_permissions:(null===(B=ez.object_permission)||void 0===B?void 0:B.mcp_tool_permissions)||{}},layout:"vertical",children:[(0,t.jsx)(c.Z.Item,{label:"Team Name",name:"team_alias",rules:[{required:!0,message:"Please input a team name"}],children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Please select at least one model"}],children:(0,t.jsxs)(b.default,{mode:"multiple",placeholder:"Select models",children:[(el||es.includes("all-proxy-models"))&&(0,t.jsx)(b.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,t.jsx)(b.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),Array.from(new Set(es)).map((e,l)=>(0,t.jsx)(b.default.Option,{value:e,children:(0,y.W0)(e)},l))]})}),(0,t.jsx)(c.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Budget (USD)",name:"team_member_budget",tooltip:"This is the individual budget for a user in the team.",children:(0,t.jsx)(S.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Team Member Key Duration (eg: 1d, 1mo)",name:"team_member_key_duration",tooltip:"Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",children:(0,t.jsx)(o.oi,{placeholder:"e.g., 30d"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member TPM Limit",name:"team_member_tpm_limit",tooltip:"Default tokens per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 1000"})}),(0,t.jsx)(c.Z.Item,{label:"Team Member RPM Limit",name:"team_member_rpm_limit",tooltip:"Default requests per minute limit for an individual team member. This limit applies to all requests the user makes within this team. Can be overridden per member.",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"},placeholder:"e.g., 100"})}),(0,t.jsx)(c.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsxs)(b.default,{placeholder:"n/a",children:[(0,t.jsx)(b.default.Option,{value:"24h",children:"daily"}),(0,t.jsx)(b.default.Option,{value:"7d",children:"weekly"}),(0,t.jsx)(b.default.Option,{value:"30d",children:"monthly"})]})}),(0,t.jsx)(c.Z.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,t.jsx)(S.Z,{step:1,style:{width:"100%"}})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(p.Z,{title:"Setup your first guardrail",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",help:"Select existing guardrails or enter new ones",children:(0,t.jsx)(b.default,{mode:"tags",placeholder:"Select or enter guardrails",options:ew.map(e=>({value:e,label:e}))})}),(0,t.jsx)(c.Z.Item,{label:(0,t.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,t.jsx)(p.Z,{title:"When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),name:"disable_global_guardrails",valuePropName:"checked",help:"Bypass global guardrails for this team",children:(0,t.jsx)(g.Z,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(c.Z.Item,{label:"Vector Stores",name:"vector_stores",children:(0,t.jsx)(T.Z,{onChange:e=>eu.setFieldValue("vector_stores",e),value:eu.getFieldValue("vector_stores"),accessToken:Y||"",placeholder:"Select vector stores"})}),(0,t.jsx)(c.Z.Item,{label:"Allowed Pass Through Routes",name:"allowed_passthrough_routes",children:(0,t.jsx)(Z.Z,{onChange:e=>eu.setFieldValue("allowed_passthrough_routes",e),value:eu.getFieldValue("allowed_passthrough_routes"),accessToken:Y||"",placeholder:"Select pass through routes"})}),(0,t.jsx)(c.Z.Item,{label:"MCP Servers / Access Groups",name:"mcp_servers_and_groups",children:(0,t.jsx)(w.Z,{onChange:e=>eu.setFieldValue("mcp_servers_and_groups",e),value:eu.getFieldValue("mcp_servers_and_groups"),accessToken:Y||"",placeholder:"Select MCP servers or access groups (optional)"})}),(0,t.jsx)(c.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(x.default,{type:"hidden"})}),(0,t.jsx)(c.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.mcp_servers_and_groups!==l.mcp_servers_and_groups||e.mcp_tool_permissions!==l.mcp_tool_permissions,children:()=>{var e;return(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(k.Z,{accessToken:Y||"",selectedServers:(null===(e=eu.getFieldValue("mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:eu.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eu.setFieldsValue({mcp_tool_permissions:e})})})}}),(0,t.jsx)(c.Z.Item,{label:"Organization ID",name:"organization_id",children:(0,t.jsx)(x.default,{type:""})}),(0,t.jsx)(c.Z.Item,{label:"Logging Settings",name:"logging_settings",children:(0,t.jsx)(P.Z,{value:eu.getFieldValue("logging_settings"),onChange:e=>eu.setFieldValue("logging_settings",e)})}),(0,t.jsx)(c.Z.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(x.default.TextArea,{rows:10})}),(0,t.jsx)("div",{className:"sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]",children:(0,t.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,t.jsx)(o.zx,{variant:"secondary",onClick:()=>e_(!1),disabled:eL,children:"Cancel"}),(0,t.jsx)(o.zx,{type:"submit",loading:eL,children:"Save Changes"})]})})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Name"}),(0,t.jsx)("div",{children:ez.team_alias})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team ID"}),(0,t.jsx)("div",{className:"font-mono",children:ez.team_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Created At"}),(0,t.jsx)("div",{children:new Date(ez.created_at).toLocaleString()})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:ez.models.map((e,l)=>(0,t.jsx)(o.Ct,{color:"red",children:e},l))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Rate Limits"}),(0,t.jsxs)("div",{children:["TPM: ",ez.tpm_limit||"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",ez.rpm_limit||"Unlimited"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Team Budget"}),(0,t.jsxs)("div",{children:["Max Budget:"," ",null!==ez.max_budget?"$".concat((0,r.pw)(ez.max_budget,4)):"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Reset: ",ez.budget_duration||"Never"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)(o.xv,{className:"font-medium",children:["Team Member Settings"," ",(0,t.jsx)(p.Z,{title:"These are limits on individual team members",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),(0,t.jsxs)("div",{children:["Max Budget: ",(null===(V=ez.team_member_budget_table)||void 0===V?void 0:V.max_budget)||"No Limit"]}),(0,t.jsxs)("div",{children:["Key Duration: ",(null===(q=ez.metadata)||void 0===q?void 0:q.team_member_key_duration)||"No Limit"]}),(0,t.jsxs)("div",{children:["TPM Limit: ",(null===(G=ez.team_member_budget_table)||void 0===G?void 0:G.tpm_limit)||"No Limit"]}),(0,t.jsxs)("div",{children:["RPM Limit: ",(null===(K=ez.team_member_budget_table)||void 0===K?void 0:K.rpm_limit)||"No Limit"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Organization ID"}),(0,t.jsx)("div",{children:ez.organization_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Status"}),(0,t.jsx)(o.Ct,{color:ez.blocked?"red":"green",children:ez.blocked?"Blocked":"Active"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(o.xv,{className:"font-medium",children:"Disable Global Guardrails"}),(0,t.jsx)("div",{children:(null===($=ez.metadata)||void 0===$?void 0:$.disable_global_guardrails)===!0?(0,t.jsx)(o.Ct,{color:"yellow",children:"Enabled - Global guardrails bypassed"}):(0,t.jsx)(o.Ct,{color:"green",children:"Disabled - Global guardrails active"})})]}),(0,t.jsx)(C.Z,{objectPermission:ez.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:Y}),(0,t.jsx)(N.Z,{loggingConfigs:(null===(J=ez.metadata)||void 0===J?void 0:J.logging)||[],disabledCallbacks:[],variant:"inline",className:"pt-4 border-t border-gray-200"})]})]})})]})]}),(0,t.jsx)(I.Z,{visible:eh,onCancel:()=>ex(!1),onSubmit:eA,initialData:eb,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Admin",value:"admin"},{label:"User",value:"user"}],additionalFields:[{name:"max_budget_in_team",label:(0,t.jsxs)("span",{children:["Team Member Budget (USD)"," ",(0,t.jsx)(p.Z,{title:"Maximum amount in USD this member can spend within this team. This is separate from any global user budget limits",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:.01,min:0,placeholder:"Budget limit for this member within this team"},{name:"tpm_limit",label:(0,t.jsxs)("span",{children:["Team Member TPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum tokens per minute this member can use within this team. This is separate from any global user TPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Tokens per minute limit for this member in this team"},{name:"rpm_limit",label:(0,t.jsxs)("span",{children:["Team Member RPM Limit"," ",(0,t.jsx)(p.Z,{title:"Maximum requests per minute this member can make within this team. This is separate from any global user RPM limit",children:(0,t.jsx)(m.Z,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Requests per minute limit for this member in this team"}]}}),(0,t.jsx)(i.Z,{isVisible:eo,onCancel:()=>ec(!1),onSubmit:eD,accessToken:Y}),(0,t.jsx)(f.Z,{isOpen:eS,title:"Delete Team Member",alertMessage:"Removing team members will also delete any keys created by or created for this member.",message:"Are you sure you want to remove this member from the team? This action cannot be undone.",resourceInformationTitle:"Team Member Information",resourceInformation:[{label:"User ID",value:null==eM?void 0:eM.user_id,code:!0},{label:"Email",value:null==eM?void 0:eM.user_email},{label:"Role",value:null==eM?void 0:eM.role}],onCancel:()=>{eT(!1),eC(null)},onOk:eR,confirmLoading:eI})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2019-15183fcc4c29249f.js b/litellm/proxy/_experimental/out/_next/static/chunks/2019-15183fcc4c29249f.js deleted file mode 100644 index 742a5c6e359..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2019-15183fcc4c29249f.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2019],{92019:function(e,s,t){var a=t(57437),r=t(13817),l=t(18310),i=t(60985),n=t(92403),o=t(28595),c=t(68208),d=t(9775),m=t(41361),g=t(37527),u=t(15883),x=t(12660),y=t(88009),h=t(48231),p=t(57400),f=t(58630),b=t(44625),j=t(41169),N=t(38434),v=t(71891),L=t(55322),w=t(2265),k=t(99376),Z=t(20347),S=t(79262),_=t(19250);let{Sider:z}=r.default,O=()=>{let e="ui/".replace(/^\/+|\/+$/g,""),s=e?"/".concat(e,"/"):"/";if(_.serverRootPath&&"/"!==_.serverRootPath){let e=_.serverRootPath.replace(/\/+$/,""),t=s.replace(/^\/+/,"");return"".concat(e,"/").concat(t)}return s},P=e=>{switch(e){case"api-keys":return"virtual-keys";case"llm-playground":return"test-key";case"models":return"models-and-endpoints";case"new_usage":return"usage";case"teams":return"teams";case"organizations":return"organizations";case"users":return"users";case"api_ref":return"api-reference";case"model-hub-table":return"model-hub";case"logs":return"logs";case"guardrails":return"guardrails";case"mcp-servers":return"tools/mcp-servers";case"vector-stores":return"tools/vector-stores";case"caching":return"experimental/caching";case"prompts":return"experimental/prompts";case"budgets":return"experimental/budgets";case"transform-request":return"experimental/api-playground";case"tag-management":return"experimental/tag-management";case"usage":return"experimental/old-usage";case"general-settings":return"settings/router-settings";case"settings":return"settings/logging-and-alerts";case"admin-panel":return"settings/admin-settings";case"ui-theme":return"settings/ui-theme";default:return e.replace(/^\/+/,"")}},M=e=>{let s=O(),t=P(e).replace(/^\/+|\/+$/g,"");return"".concat(s).concat(t)},C=[{key:"1",page:"api-keys",label:"Virtual Keys",icon:(0,a.jsx)(n.Z,{style:{fontSize:18}})},{key:"3",page:"llm-playground",label:"Test Key",icon:(0,a.jsx)(o.Z,{style:{fontSize:18}}),roles:Z.LQ},{key:"2",page:"models",label:"Models + Endpoints",icon:(0,a.jsx)(c.Z,{style:{fontSize:18}}),roles:Z.LQ},{key:"12",page:"new_usage",label:"Usage",icon:(0,a.jsx)(d.Z,{style:{fontSize:18}}),roles:[...Z.ZL,...Z.lo]},{key:"6",page:"teams",label:"Teams",icon:(0,a.jsx)(m.Z,{style:{fontSize:18}})},{key:"17",page:"organizations",label:"Organizations",icon:(0,a.jsx)(g.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"5",page:"users",label:"Internal Users",icon:(0,a.jsx)(u.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"14",page:"api_ref",label:"API Reference",icon:(0,a.jsx)(x.Z,{style:{fontSize:18}})},{key:"16",page:"model-hub-table",label:"Model Hub",icon:(0,a.jsx)(y.Z,{style:{fontSize:18}})},{key:"15",page:"logs",label:"Logs",icon:(0,a.jsx)(h.Z,{style:{fontSize:18}})},{key:"11",page:"guardrails",label:"Guardrails",icon:(0,a.jsx)(p.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"26",page:"tools",label:"Tools",icon:(0,a.jsx)(f.Z,{style:{fontSize:18}}),children:[{key:"18",page:"mcp-servers",label:"MCP Servers",icon:(0,a.jsx)(f.Z,{style:{fontSize:18}})},{key:"21",page:"vector-stores",label:"Vector Stores",icon:(0,a.jsx)(b.Z,{style:{fontSize:18}}),roles:Z.ZL}]},{key:"experimental",page:"experimental",label:"Experimental",icon:(0,a.jsx)(j.Z,{style:{fontSize:18}}),children:[{key:"9",page:"caching",label:"Caching",icon:(0,a.jsx)(b.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"25",page:"prompts",label:"Prompts",icon:(0,a.jsx)(N.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"10",page:"budgets",label:"Budgets",icon:(0,a.jsx)(g.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"20",page:"transform-request",label:"API Playground",icon:(0,a.jsx)(x.Z,{style:{fontSize:18}}),roles:[...Z.ZL,...Z.lo]},{key:"19",page:"tag-management",label:"Tag Management",icon:(0,a.jsx)(v.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"4",page:"usage",label:"Old Usage",icon:(0,a.jsx)(d.Z,{style:{fontSize:18}})}]},{key:"settings",page:"settings",label:"Settings",icon:(0,a.jsx)(L.Z,{style:{fontSize:18}}),roles:Z.ZL,children:[{key:"11",page:"general-settings",label:"Router Settings",icon:(0,a.jsx)(L.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"8",page:"settings",label:"Logging & Alerts",icon:(0,a.jsx)(L.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"13",page:"admin-panel",label:"Admin Settings",icon:(0,a.jsx)(L.Z,{style:{fontSize:18}}),roles:Z.ZL},{key:"14",page:"ui-theme",label:"UI Theme",icon:(0,a.jsx)(L.Z,{style:{fontSize:18}}),roles:Z.ZL}]}];s.Z=e=>{let{accessToken:s,userRole:t,defaultSelectedKey:n,collapsed:o=!1}=e,c=(0,k.useRouter)(),d=(0,k.usePathname)()||"/",m=w.useMemo(()=>C.filter(e=>!e.roles||e.roles.includes(t)).map(e=>({...e,children:e.children?e.children.filter(e=>!e.roles||e.roles.includes(t)):void 0})),[t]),g=w.useMemo(()=>{var e,s;let t=O(),a=(d.startsWith(t)?d.slice(t.length):d.replace(/^\/+/,"")).toLowerCase(),r=e=>{let s=P(e).toLowerCase();return a===s||a.startsWith("".concat(s,"/"))};for(let e of m){if(!e.children&&r(e.page))return e.key;if(e.children){for(let s of e.children)if(r(s.page))return s.key}}let l=null===(e=m.find(e=>e.page===n))||void 0===e?void 0:e.key;if(l)return l;for(let e of m)if(null===(s=e.children)||void 0===s?void 0:s.some(e=>e.page===n))return e.children.find(e=>e.page===n).key;return"1"},[d,m,n]),u=e=>{let s=M(e);c.push(s)};return(0,a.jsx)(r.default,{style:{minHeight:"100vh"},children:(0,a.jsxs)(z,{theme:"light",width:220,collapsed:o,collapsedWidth:80,collapsible:!0,trigger:null,style:{transition:"all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",position:"relative"},children:[(0,a.jsx)(l.ZP,{theme:{components:{Menu:{iconSize:18,fontSize:14}}},children:(0,a.jsx)(i.Z,{mode:"inline",selectedKeys:[g],defaultOpenKeys:o?[]:["llm-tools"],inlineCollapsed:o,className:"custom-sidebar-menu",style:{borderRight:0,backgroundColor:"transparent",fontSize:"14px"},items:m.map(e=>{var s;return{key:e.key,icon:e.icon,label:e.label,children:null===(s=e.children)||void 0===s?void 0:s.map(e=>({key:e.key,icon:e.icon,label:e.label,onClick:()=>u(e.page)})),onClick:e.children?void 0:()=>u(e.page)}})})}),(0,Z.tY)(t)&&!o&&(0,a.jsx)(S.Z,{accessToken:s,width:220})]})})}},79262:function(e,s,t){t.d(s,{Z:function(){return u}});var a=t(57437);t(1309);var r=t(76865),l=t(70525),i=t(95805),n=t(51817),o=t(21047);t(22135),t(40875);var c=t(49663),d=t(2265),m=t(19250);let g=function(){for(var e=arguments.length,s=Array(e),t=0;t{(async()=>{if(s){j(!0),v(null);try{let e=await (0,m.getRemainingUsers)(s);f(e)}catch(e){console.error("Failed to fetch usage data:",e),v("Failed to load usage data")}finally{j(!1)}}})()},[s]);let{isOverLimit:L,isNearLimit:w,usagePercentage:k,userMetrics:Z,teamMetrics:S}=(e=>{if(!e)return{isOverLimit:!1,isNearLimit:!1,usagePercentage:0,userMetrics:{isOverLimit:!1,isNearLimit:!1,usagePercentage:0},teamMetrics:{isOverLimit:!1,isNearLimit:!1,usagePercentage:0}};let s=e.total_users?e.total_users_used/e.total_users*100:0,t=s>100,a=s>=80&&s<=100,r=e.total_teams?e.total_teams_used/e.total_teams*100:0,l=r>100,i=r>=80&&r<=100,n=t||l;return{isOverLimit:n,isNearLimit:(a||i)&&!n,usagePercentage:Math.max(s,r),userMetrics:{isOverLimit:t,isNearLimit:a,usagePercentage:s},teamMetrics:{isOverLimit:l,isNearLimit:i,usagePercentage:r}}})(p),_=()=>L?(0,a.jsx)(r.Z,{className:"h-3 w-3"}):w?(0,a.jsx)(l.Z,{className:"h-3 w-3"}):null;return s&&((null==p?void 0:p.total_users)!==null||(null==p?void 0:p.total_teams)!==null)?(0,a.jsx)("div",{className:"fixed bottom-4 left-4 z-50",style:{width:"".concat(Math.min(t,220),"px")},children:(0,a.jsx)(()=>y?(0,a.jsx)("button",{onClick:()=>h(!1),className:g("bg-white border border-gray-200 rounded-lg shadow-sm p-3 hover:shadow-md transition-all w-full"),title:"Show usage details",children:(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)(i.Z,{className:"h-4 w-4 flex-shrink-0"}),(L||w)&&(0,a.jsx)("span",{className:"flex-shrink-0",children:_()}),(0,a.jsxs)("div",{className:"flex items-center gap-2 text-sm font-medium truncate",children:[p&&null!==p.total_users&&(0,a.jsxs)("span",{className:g("flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",Z.isOverLimit&&"bg-red-50 text-red-700 border-red-200",Z.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!Z.isOverLimit&&!Z.isNearLimit&&"bg-gray-50 text-gray-700 border-gray-200"),children:["U: ",p.total_users_used,"/",p.total_users]}),p&&null!==p.total_teams&&(0,a.jsxs)("span",{className:g("flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",S.isOverLimit&&"bg-red-50 text-red-700 border-red-200",S.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!S.isOverLimit&&!S.isNearLimit&&"bg-gray-50 text-gray-700 border-gray-200"),children:["T: ",p.total_teams_used,"/",p.total_teams]}),!p||null===p.total_users&&null===p.total_teams&&(0,a.jsx)("span",{className:"truncate",children:"Usage"})]})]})}):b?(0,a.jsx)("div",{className:"bg-white border border-gray-200 rounded-lg shadow-sm p-4 w-full",children:(0,a.jsxs)("div",{className:"flex items-center justify-center gap-2 py-2",children:[(0,a.jsx)(n.Z,{className:"h-4 w-4 animate-spin"}),(0,a.jsx)("span",{className:"text-sm text-gray-500 truncate",children:"Loading..."})]})}):N||!p?(0,a.jsx)("div",{className:"bg-white border border-gray-200 rounded-lg shadow-sm p-4 group w-full",children:(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,a.jsx)("div",{className:"flex-1 min-w-0",children:(0,a.jsx)("span",{className:"text-sm text-gray-500 truncate block",children:N||"No data"})}),(0,a.jsx)("button",{onClick:()=>h(!0),className:"opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0",title:"Minimize",children:(0,a.jsx)(o.Z,{className:"h-3 w-3 text-gray-400"})})]})}):(0,a.jsxs)("div",{className:g("bg-white border rounded-lg shadow-sm p-3 transition-all duration-200 group w-full"),children:[(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2 mb-3",children:[(0,a.jsxs)("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[(0,a.jsx)(i.Z,{className:"h-4 w-4 flex-shrink-0"}),(0,a.jsx)("span",{className:"font-medium text-sm truncate",children:"Usage"})]}),(0,a.jsx)("button",{onClick:()=>h(!0),className:"opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0",title:"Minimize",children:(0,a.jsx)(o.Z,{className:"h-3 w-3 text-gray-400"})})]}),(0,a.jsxs)("div",{className:"space-y-3 text-sm",children:[null!==p.total_users&&(0,a.jsxs)("div",{className:g("space-y-1 border rounded-md p-2",Z.isOverLimit&&"border-red-200 bg-red-50",Z.isNearLimit&&"border-yellow-200 bg-yellow-50"),children:[(0,a.jsxs)("div",{className:"flex items-center gap-2 text-xs text-gray-600 mb-1",children:[(0,a.jsx)(i.Z,{className:"h-3 w-3"}),(0,a.jsx)("span",{className:"font-medium",children:"Users"}),(0,a.jsx)("span",{className:g("ml-1 px-1.5 py-0.5 rounded border",Z.isOverLimit&&"bg-red-50 text-red-700 border-red-200",Z.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!Z.isOverLimit&&!Z.isNearLimit&&"bg-gray-50 text-gray-600 border-gray-200"),children:Z.isOverLimit?"Over limit":Z.isNearLimit?"Near limit":"OK"})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Used:"}),(0,a.jsxs)("span",{className:"font-medium text-right",children:[p.total_users_used,"/",p.total_users]})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Remaining:"}),(0,a.jsx)("span",{className:g("font-medium text-right",Z.isOverLimit&&"text-red-600",Z.isNearLimit&&"text-yellow-600"),children:p.total_users_remaining})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Usage:"}),(0,a.jsxs)("span",{className:"font-medium text-right",children:[Math.round(Z.usagePercentage),"%"]})]}),(0,a.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,a.jsx)("div",{className:g("h-2 rounded-full transition-all duration-300",Z.isOverLimit&&"bg-red-500",Z.isNearLimit&&"bg-yellow-500",!Z.isOverLimit&&!Z.isNearLimit&&"bg-green-500"),style:{width:"".concat(Math.min(Z.usagePercentage,100),"%")}})})]}),null!==p.total_teams&&(0,a.jsxs)("div",{className:g("space-y-1 border rounded-md p-2",S.isOverLimit&&"border-red-200 bg-red-50",S.isNearLimit&&"border-yellow-200 bg-yellow-50"),children:[(0,a.jsxs)("div",{className:"flex items-center gap-2 text-xs text-gray-600 mb-1",children:[(0,a.jsx)(c.Z,{className:"h-3 w-3"}),(0,a.jsx)("span",{className:"font-medium",children:"Teams"}),(0,a.jsx)("span",{className:g("ml-1 px-1.5 py-0.5 rounded border",S.isOverLimit&&"bg-red-50 text-red-700 border-red-200",S.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!S.isOverLimit&&!S.isNearLimit&&"bg-gray-50 text-gray-600 border-gray-200"),children:S.isOverLimit?"Over limit":S.isNearLimit?"Near limit":"OK"})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Used:"}),(0,a.jsxs)("span",{className:"font-medium text-right",children:[p.total_teams_used,"/",p.total_teams]})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Remaining:"}),(0,a.jsx)("span",{className:g("font-medium text-right",S.isOverLimit&&"text-red-600",S.isNearLimit&&"text-yellow-600"),children:p.total_teams_remaining})]}),(0,a.jsxs)("div",{className:"flex justify-between items-center",children:[(0,a.jsx)("span",{className:"text-gray-600 text-xs",children:"Usage:"}),(0,a.jsxs)("span",{className:"font-medium text-right",children:[Math.round(S.usagePercentage),"%"]})]}),(0,a.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,a.jsx)("div",{className:g("h-2 rounded-full transition-all duration-300",S.isOverLimit&&"bg-red-500",S.isNearLimit&&"bg-yellow-500",!S.isOverLimit&&!S.isNearLimit&&"bg-green-500"),style:{width:"".concat(Math.min(S.usagePercentage,100),"%")}})})]})]})]}),{})}):null}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js b/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js new file mode 100644 index 00000000000..b21241beec0 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2249-01a36f26b1cecba3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2249],{64748:function(e,s,l){l.d(s,{Ct:function(){return a.Z},Dx:function(){return m.Z},OK:function(){return n.Z},Zb:function(){return r.Z},nP:function(){return o.Z},td:function(){return c.Z},v0:function(){return i.Z},x4:function(){return d.Z},xv:function(){return x.Z},zx:function(){return t.Z}});var a=l(41649),t=l(78489),r=l(12514),n=l(12485),i=l(18135),c=l(35242),d=l(29706),o=l(77991),x=l(84264),m=l(96761)},78801:function(e,s,l){l.d(s,{Z:function(){return a.Z},x:function(){return t.Z}});var a=l(12514),t=l(84264)},92249:function(e,s,l){l.d(s,{Z:function(){return W}});var a=l(57437),t=l(23639),r=l(64748),n=l(22116),i=l(78867),c=l(99376),d=l(2265),o=l(17906),x=l(20347),m=l(41649),h=l(78489),u=l(84264),p=l(99981),g=l(3810),j=l(15424),v=l(15690),b=l(10032),N=l(61994),f=l(5545),y=l(96761),_=l(19250),k=l(9114);let{Step:w}=v.default;var Z=e=>{let{visible:s,onClose:l,accessToken:t,agentHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),Z=()=>{o(0),h(new Set),j.resetFields(),l()},C=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.agent_id||e.name))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>!0===e.is_public).map(e=>e.agent_id||e.name)))},[s,r]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeAgentsPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," agent(s) public!")),Z(),i()}catch(e){console.error("Error making agents public:",e),k.Z.fromBackend("Failed to make agents public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.agent_id||e.name)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Agents to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the agents you want to be visible on the public model hub. Users will still require a valid API key to use these agents."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No agents available."})}):r.map(e=>{let s=e.agent_id||e.name;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(s),onChange:e=>C(s,e.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.name}),(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",e.version]})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description}),e.skills&&e.skills.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.skills.slice(0,3).map(e=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e.name},e.id)),e.skills.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.skills.length-3," more"]})]})]})]},s)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Agents Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these agents public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Agents to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>(s.agent_id||s.name)===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.name)||e}),s&&(0,a.jsxs)(m.Z,{color:"blue",size:"xs",children:["v",s.version]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Agents Public",open:s,onCancel:Z,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(w,{title:"Select Agents"}),(0,a.jsx)(w,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?Z:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})};let{Step:C}=v.default;var S=e=>{let{visible:s,onClose:l,accessToken:t,mcpHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),w=()=>{o(0),h(new Set),j.resetFields(),l()},Z=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.server_id))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>{var s;return(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0}).map(e=>e.server_id)))},[s]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeMCPPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," MCP server(s) public!")),w(),i()}catch(e){console.error("Error making MCP servers public:",e),k.Z.fromBackend("Failed to make MCP servers public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.server_id)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select MCP Servers to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the MCP servers you want to be visible on the public model hub. Users will still require a valid API key to use these servers."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No MCP servers available."})}):r.map(e=>{var s;let l=(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.server_id),onChange:s=>Z(e.server_id,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.server_name}),l&&(0,a.jsx)(m.Z,{color:"emerald",size:"sm",children:"Public"}),(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:e.transport}),(0,a.jsx)(m.Z,{color:"active"===e.status||"healthy"===e.status?"green":"inactive"===e.status||"unhealthy"===e.status?"red":"gray",size:"sm",children:e.status||"unknown"})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description||e.url}),e.allowed_tools&&e.allowed_tools.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.allowed_tools.slice(0,3).map((e,s)=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e},s)),e.allowed_tools.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.allowed_tools.length-3," more"]})]})]})]},e.server_id)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making MCP Servers Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these MCP servers public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"MCP Servers to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.server_id===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.server_name)||e}),s&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:s.transport}),(0,a.jsx)(m.Z,{color:"active"===s.status||"healthy"===s.status?"green":"inactive"===s.status||"unhealthy"===s.status?"red":"gray",size:"xs",children:s.status||"unknown"})]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description}),(null==s?void 0:s.url)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-500 mt-1",children:s.url})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make MCP Servers Public",open:s,onCancel:w,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(C,{title:"Select Servers"}),(0,a.jsx)(C,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?w:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})},P=l(78801),M=e=>{let{modelHubData:s,onFilteredDataChange:l,showFiltersCard:t=!0,className:r=""}=e,[n,i]=(0,d.useState)(""),[c,o]=(0,d.useState)(""),[x,m]=(0,d.useState)(""),[h,u]=(0,d.useState)(""),p=(0,d.useRef)([]),g=(0,d.useMemo)(()=>(null==s?void 0:s.filter(e=>{let s=e.model_group.toLowerCase().includes(n.toLowerCase()),l=""===c||e.providers.includes(c),a=""===x||e.mode===x,t=""===h||Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).some(e=>{let[s]=e;return s.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")===h});return s&&l&&a&&t}))||[],[s,n,c,x,h]);(0,d.useEffect)(()=>{(g.length!==p.current.length||g.some((e,s)=>{var l;return e.model_group!==(null===(l=p.current[s])||void 0===l?void 0:l.model_group)}))&&(p.current=g,l(g))},[g,l]);let j=(0,a.jsxs)("div",{className:"flex flex-wrap gap-4 items-center",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Search Models:"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names...",value:n,onChange:e=>i(e.target.value),className:"border rounded px-3 py-2 w-64 h-10 text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Provider:"}),(0,a.jsxs)("select",{value:c,onChange:e=>o(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Providers"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.providers.forEach(e=>s.add(e))}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Mode:"}),(0,a.jsxs)("select",{value:x,onChange:e=>m(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Modes"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.mode&&s.add(e.mode)}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Features:"}),(0,a.jsxs)("select",{value:h,onChange:e=>u(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Features"}),s&&(e=>{let s=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).forEach(e=>{let[l]=e,a=l.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");s.add(a)})}),Array.from(s).sort()})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(n||c||x||h)&&(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsx)("button",{onClick:()=>{i(""),o(""),m(""),u("")},className:"text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center",children:"Clear Filters"})})]});return t?(0,a.jsx)(P.Z,{className:"mb-6 ".concat(r),children:j}):(0,a.jsx)("div",{className:r,children:j})};let{Step:z}=v.default;var A=e=>{let{visible:s,onClose:l,accessToken:t,modelHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)([]),[j,w]=(0,d.useState)(!1),[Z]=b.Z.useForm(),C=()=>{o(0),h(new Set),g([]),Z.resetFields(),l()},S=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},P=e=>{e?h(new Set(p.map(e=>e.model_group))):h(new Set)},A=(0,d.useCallback)(e=>{g(e)},[]);(0,d.useEffect)(()=>{s&&r.length>0&&(g(r),h(new Set(r.filter(e=>!0===e.is_public_model_group).map(e=>e.model_group))))},[s,r]);let F=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}w(!0);try{let e=Array.from(x);await (0,_.makeModelGroupPublic)(t,e),k.Z.success("Successfully made ".concat(e.length," model group(s) public!")),C(),i()}catch(e){console.error("Error making model groups public:",e),k.Z.fromBackend("Failed to make model groups public. Please try again.")}finally{w(!1)}},L=()=>{let e=p.length>0&&p.every(e=>x.has(e.model_group)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Models to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>P(e.target.checked),disabled:0===p.length,children:["Select All ",p.length>0&&"(".concat(p.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the models you want to be visible on the public model hub. Users will still require a valid API key to use these models."}),(0,a.jsx)(M,{modelHubData:r,onFilteredDataChange:A,showFiltersCard:!1,className:"border rounded-lg p-4 bg-gray-50"}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===p.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No models match the current filters."})}):p.map(e=>(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.model_group),onChange:s=>S(e.model_group,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.model_group}),e.mode&&(0,a.jsx)(m.Z,{color:"green",size:"sm",children:e.mode})]}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})]},e.model_group))})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," selected"]})})]})},D=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Models Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these models public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Models to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.model_group===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e}),s&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:s.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Models Public",open:s,onCancel:C,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:Z,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(z,{title:"Select Models"}),(0,a.jsx)(z,{title:"Confirm"})]}),(()=>{switch(c){case 0:return L();case 1:return D();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?C:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:F,loading:j,children:"Make Public"})]})]})]})})},F=l(8048);let L=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),D=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),O=e=>"$".concat((1e6*e).toFixed(2)),U=e=>e>=1e6?"".concat((e/1e6).toFixed(1),"M"):e>=1e3?"".concat((e/1e3).toFixed(1),"K"):e.toString(),E=function(e,s){let l=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=[{header:"Public Model Name",accessorKey:"model_group",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.model_group}),(0,a.jsx)(p.Z,{title:"Copy model name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.providers.join(", ")})})]})}},{header:"Provider",accessorKey:"providers",enableSorting:!0,sortingFn:(e,s)=>{let l=e.original.providers.join(", "),a=s.original.providers.join(", ");return l.localeCompare(a)},cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.providers.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"blue",className:"text-xs",children:e},e)),l.providers.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.providers.length-2]})]})},meta:{className:"hidden md:table-cell"}},{header:"Mode",accessorKey:"mode",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return l.mode?(0,a.jsx)(m.Z,{color:"green",size:"sm",children:l.mode}):(0,a.jsx)(u.Z,{className:"text-gray-500",children:"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Tokens",accessorKey:"max_input_tokens",enableSorting:!0,sortingFn:(e,s)=>(e.original.max_input_tokens||0)+(e.original.max_output_tokens||0)-((s.original.max_input_tokens||0)+(s.original.max_output_tokens||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)("div",{className:"space-y-1",children:(0,a.jsxs)(u.Z,{className:"text-xs",children:[l.max_input_tokens?U(l.max_input_tokens):"-"," /"," ",l.max_output_tokens?U(l.max_output_tokens):"-"]})})},meta:{className:"hidden lg:table-cell"}},{header:"Cost/1M",accessorKey:"input_cost_per_token",enableSorting:!0,sortingFn:(e,s)=>(e.original.input_cost_per_token||0)+(e.original.output_cost_per_token||0)-((s.original.input_cost_per_token||0)+(s.original.output_cost_per_token||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs",children:l.input_cost_per_token?O(l.input_cost_per_token):"-"}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-500",children:l.output_cost_per_token?O(l.output_cost_per_token):"-"})]})}},{header:"Features",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=D(s.original),t=["green","blue","purple","orange","red","yellow"];return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map((e,s)=>(0,a.jsx)(m.Z,{color:t[s%t.length],size:"xs",children:L(e)},e))})}},{header:"Public",accessorKey:"is_public_model_group",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public_model_group?1:0)-(!0===s.original.is_public_model_group?1:0),cell:e=>{let{row:s}=e;return!0===s.original.is_public_model_group?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}];return l?r.filter(e=>!("accessorKey"in e)||"is_public_model_group"!==e.accessorKey):r};var K=l(87526),T=l(86462),H=l(47686),I=l(77355),R=l(93416),B=l(74998),Y=l(95704),V=e=>{let{accessToken:s,userRole:l}=e,[t,r]=(0,d.useState)([]),[i,c]=(0,d.useState)({url:"",displayName:""}),[o,m]=(0,d.useState)(null),[h,u]=(0,d.useState)(!1),[p,g]=(0,d.useState)(!0),j=async()=>{if(s)try{u(!0);let e=await (0,_.getPublicModelHubInfo)();if(e&&e.useful_links){let s=e.useful_links||{},l=Object.entries(s).map((e,s)=>{let[l,a]=e;return{id:"".concat(s,"-").concat(l),displayName:l,url:a}});r(l)}else r([])}catch(e){console.error("Error fetching useful links:",e),r([])}finally{u(!1)}};if((0,d.useEffect)(()=>{j()},[s]),!(0,x.tY)(l||""))return null;let v=async e=>{if(!s)return!1;try{let l={};return e.forEach(e=>{l[e.displayName]=e.url}),await (0,_.updateUsefulLinksCall)(s,l),n.Z.success({title:"Links Saved Successfully",content:(0,a.jsxs)("div",{className:"py-4",children:[(0,a.jsx)("p",{className:"text-gray-600 mb-4",children:"Your useful links have been saved and are now visible on the public model hub."}),(0,a.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4",children:[(0,a.jsx)("p",{className:"text-sm text-blue-800 mb-2 font-medium",children:"View your updated model hub:"}),(0,a.jsx)("a",{href:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table"),target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center text-blue-600 hover:text-blue-800 underline text-sm font-medium",children:"Open Public Model Hub →"})]})]}),width:500,okText:"Close",maskClosable:!0,keyboard:!0}),!0}catch(e){return console.error("Error saving links:",e),k.Z.fromBackend("Failed to save links - ".concat(e)),!1}},b=async()=>{if(!i.url||!i.displayName)return;try{new URL(i.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.displayName===i.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=[...t,{id:"".concat(Date.now(),"-").concat(i.displayName),displayName:i.displayName,url:i.url}];await v(e)&&(r(e),c({url:"",displayName:""}),k.Z.success("Link added successfully"))},N=e=>{m({...e})},f=async()=>{if(!o)return;try{new URL(o.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.id!==o.id&&e.displayName===o.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=t.map(e=>e.id===o.id?o:e);await v(e)&&(r(e),m(null),k.Z.success("Link updated successfully"))},y=()=>{m(null)},w=async e=>{let s=t.filter(s=>s.id!==e);await v(s)&&(r(s),k.Z.success("Link deleted successfully"))},Z=e=>{window.open(e,"_blank")};return(0,a.jsxs)(Y.Zb,{className:"mb-6",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between cursor-pointer",onClick:()=>g(!p),children:[(0,a.jsxs)("div",{className:"flex flex-col",children:[(0,a.jsx)(Y.Dx,{className:"mb-0",children:"Link Management"}),(0,a.jsx)("p",{className:"text-sm text-gray-500",children:"Manage the links that are displayed under 'Useful Links' on the public model hub."})]}),(0,a.jsx)("div",{className:"flex items-center",children:p?(0,a.jsx)(T.Z,{className:"w-5 h-5 text-gray-500"}):(0,a.jsx)(H.Z,{className:"w-5 h-5 text-gray-500"})})]}),p&&(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Link"}),(0,a.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"URL"}),(0,a.jsx)("input",{type:"text",value:i.url,onChange:e=>c({...i,url:e.target.value}),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Display Name"}),(0,a.jsx)("input",{type:"text",value:i.displayName,onChange:e=>c({...i,displayName:e.target.value}),placeholder:"Friendly name",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsxs)("button",{onClick:b,disabled:!i.url||!i.displayName,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(i.url&&i.displayName?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,a.jsx)(I.Z,{className:"w-4 h-4 mr-1"}),"Add Link"]})})]})]}),(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Links"}),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(Y.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(Y.ss,{children:(0,a.jsxs)(Y.SC,{children:[(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Display Name"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"URL"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,a.jsxs)(Y.RM,{children:[t.map(e=>(0,a.jsx)(Y.SC,{className:"h-8",children:o&&o.id===e.id?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.displayName,onChange:e=>m({...o,displayName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.url,onChange:e=>m({...o,url:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:f,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,a.jsx)("button",{onClick:y,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-900",children:e.displayName}),(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-500",children:e.url}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:()=>Z(e.url),className:"text-xs bg-green-50 text-green-600 px-2 py-1 rounded hover:bg-green-100",children:"Use"}),(0,a.jsx)("button",{onClick:()=>N(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,a.jsx)(R.Z,{className:"w-3 h-3"})}),(0,a.jsx)("button",{onClick:()=>w(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,a.jsx)(B.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===t.length&&(0,a.jsx)(Y.SC,{children:(0,a.jsx)(Y.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No links added yet. Add a new link above."})})]})]})})})]})]})},W=e=>{var s,l,v,b;let{accessToken:N,publicPage:f,premiumUser:y,userRole:w}=e,[C,P]=(0,d.useState)(!1),[z,L]=(0,d.useState)(null),[D,O]=(0,d.useState)(!0),[U,T]=(0,d.useState)(!1),[H,I]=(0,d.useState)(!1),[R,B]=(0,d.useState)(null),[Y,W]=(0,d.useState)([]),[q,G]=(0,d.useState)(!1),[J,$]=(0,d.useState)(null),[Q,X]=(0,d.useState)(!1),[ee,es]=(0,d.useState)(!0),[el,ea]=(0,d.useState)(null),[et,er]=(0,d.useState)(!1),[en,ei]=(0,d.useState)(null),[ec,ed]=(0,d.useState)(!0),[eo,ex]=(0,d.useState)(null),[em,eh]=(0,d.useState)(!1),[eu,ep]=(0,d.useState)(!1),eg=(0,c.useRouter)(),ej=(0,d.useRef)(null),ev=(0,d.useRef)(null),eb=(0,d.useRef)(null);(0,d.useEffect)(()=>{let e=async e=>{try{O(!0);let s=await (0,_.modelHubCall)(e);console.log("ModelHubData:",s),L(s.data),(0,_.getConfigFieldSetting)(e,"enable_public_model_hub").then(e=>{console.log("data: ".concat(JSON.stringify(e))),!0==e.field_value&&P(!0)}).catch(e=>{})}catch(e){console.error("There was an error fetching the model data",e)}finally{O(!1)}},s=async()=>{try{var e,s;O(!0),await (0,_.getUiConfig)();let l=await (0,_.modelHubPublicModelsCall)();console.log("ModelHubData:",l),console.log("First model structure:",l[0]),console.log("Model has model_group?",null===(e=l[0])||void 0===e?void 0:e.model_group),console.log("Model has providers?",null===(s=l[0])||void 0===s?void 0:s.providers),L(l),P(!0)}catch(e){console.error("There was an error fetching the public model data",e)}finally{O(!1)}};N?e(N):f&&s()},[N,f]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{es(!0);let e=await (0,_.getAgentsList)(N);console.log("AgentHubData:",e);let s=e.agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.litellm_params.is_public}));$(s)}catch(e){console.error("There was an error fetching the agent data",e)}finally{es(!1)}};f||e()},[f,N]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{ed(!0);let e=await (0,_.fetchMCPServers)(N);console.log("MCPHubData:",e),ei(e)}catch(e){console.error("There was an error fetching the MCP server data",e)}finally{ed(!1)}};f||e()},[f,N]);let eN=()=>{N&&G(!0)},ef=()=>{N&&X(!0)},ey=()=>{N&&ep(!0)},e_=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ek=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ew=e=>{navigator.clipboard.writeText(e),k.Z.success("Copied to clipboard!")},eZ=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),eC=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),eS=e=>"$".concat((1e6*e).toFixed(2)),eP=(0,d.useCallback)(e=>{W(e)},[]);return(console.log("publicPage: ",f),console.log("publicPageAllowed: ",C),f&&C)?(0,a.jsx)(K.Z,{accessToken:N}):(0,a.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[!1==f?(0,a.jsxs)("div",{className:"w-full m-2 mt-2 p-8",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,a.jsxs)("div",{className:"flex flex-col items-start",children:[(0,a.jsx)(r.Dx,{className:"text-center",children:"AI Hub"}),(0,x.tY)(w||"")?(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"Make models, agents, and MCP servers public for developers to know what's available."}):(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"A list of all public model names personally available to you."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,a.jsx)(r.xv,{children:"Model Hub URL:"}),(0,a.jsxs)("div",{className:"flex items-center bg-gray-200 px-2 py-1 rounded",children:[(0,a.jsx)(r.xv,{className:"mr-2",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")}),(0,a.jsx)("button",{onClick:()=>ew("".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")),className:"p-1 hover:bg-gray-300 rounded transition-colors",title:"Copy URL",children:(0,a.jsx)(i.Z,{size:16,className:"text-gray-600"})})]})]})]}),(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"mt-8 mb-2",children:(0,a.jsx)(V,{accessToken:N,userRole:w})}),(0,a.jsxs)(r.v0,{children:[(0,a.jsxs)(r.td,{className:"mb-4",children:[(0,a.jsx)(r.OK,{children:"Model Hub"}),(0,a.jsx)(r.OK,{children:"Agent Hub"}),(0,a.jsx)(r.OK,{children:"MCP Hub"})]}),(0,a.jsxs)(r.nP,{children:[(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>eN(),children:"Select Models to Make Public"})}),(0,a.jsx)(M,{modelHubData:z||[],onFilteredDataChange:eP}),(0,a.jsx)(F.C,{columns:E(e=>{B(e),T(!0)},ew,f),data:Y,isLoading:D,table:ej,defaultSorting:[{id:"model_group",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",Y.length," of ",(null==z?void 0:z.length)||0," models"]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ef(),children:"Select Agents to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Agent Name",accessorKey:"name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.name}),(0,a.jsx)(p.Z,{title:"Copy agent name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"Version",accessorKey:"version",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",l.version]})},meta:{className:"hidden lg:table-cell"}},{header:"Protocol",accessorKey:"protocolVersion",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.protocolVersion||"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Skills",accessorKey:"skills",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.skills||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs font-medium",children:[l.length," skill",1!==l.length?"s":""]}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e.name},e.id)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})}},{header:"Capabilities",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=Object.entries(s.original.capabilities||{}).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return s});return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map(e=>(0,a.jsx)(m.Z,{color:"green",size:"xs",children:e},e))})}},{header:"I/O Modes",accessorKey:"defaultInputModes",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original,t=l.defaultInputModes||[],r=l.defaultOutputModes||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"In:"})," ",t.join(", ")||"-"]}),(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"Out:"})," ",r.join(", ")||"-"]})]})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"is_public",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public?1:0)-(!0===s.original.is_public?1:0),cell:e=>{let{row:s}=e;return console.log("CHECKPOINT 1: ".concat(JSON.stringify(s.original))),!0===s.original.is_public?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ea(e),er(!0)},ew,f),data:J||[],isLoading:ee,table:ev,defaultSorting:[{id:"name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==J?void 0:J.length)||0," agent",(null==J?void 0:J.length)!==1?"s":""]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ey(),children:"Select MCP Servers to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Server Name",accessorKey:"server_name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.server_name}),(0,a.jsx)(p.Z,{title:"Copy server name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.server_name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description||"-"})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"URL",accessorKey:"url",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"text-xs truncate max-w-xs",children:r.url}),(0,a.jsx)(p.Z,{title:"Copy URL",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs flex-shrink-0"})})]})},meta:{className:"hidden lg:table-cell"}},{header:"Transport",accessorKey:"transport",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:l.transport})},meta:{className:"hidden md:table-cell"}},{header:"Auth Type",accessorKey:"auth_type",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t="none"===l.auth_type?"gray":"green";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.auth_type})},meta:{className:"hidden md:table-cell"}},{header:"Status",accessorKey:"status",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t={active:"green",inactive:"red",unknown:"gray",healthy:"green",unhealthy:"red"}[l.status]||"gray";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.status||"unknown"})}},{header:"Tools",accessorKey:"allowed_tools",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.allowed_tools||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs font-medium",children:l.length>0?"".concat(l.length," tool").concat(1!==l.length?"s":""):"All tools"}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map((e,s)=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e},s)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})},meta:{className:"hidden lg:table-cell"}},{header:"Created By",accessorKey:"created_by",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.created_by||"-"})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"mcp_info.is_public",enableSorting:!0,sortingFn:(e,s)=>{var l,a;return((null===(l=e.original.mcp_info)||void 0===l?void 0:l.is_public)===!0?1:0)-((null===(a=s.original.mcp_info)||void 0===a?void 0:a.is_public)===!0?1:0)},cell:e=>{var s;let{row:l}=e;return(null===(s=l.original.mcp_info)||void 0===s?void 0:s.is_public)===!0?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ex(e),eh(!0)},ew,f),data:en||[],isLoading:ec,table:eb,defaultSorting:[{id:"server_name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==en?void 0:en.length)||0," MCP server",(null==en?void 0:en.length)!==1?"s":""]})})]})]})]})]}):(0,a.jsxs)(r.Zb,{className:"mx-auto max-w-xl mt-10",children:[(0,a.jsx)(r.xv,{className:"text-xl text-center mb-2 text-black",children:"Public Model Hub not enabled."}),(0,a.jsx)("p",{className:"text-base text-center text-slate-800",children:"Ask your proxy admin to enable this on their Admin UI."})]}),(0,a.jsx)(n.Z,{title:"Public Model Hub",width:600,visible:H,footer:null,onOk:e_,onCancel:ek,children:(0,a.jsxs)("div",{className:"pt-5 pb-5",children:[(0,a.jsxs)("div",{className:"flex justify-between mb-4",children:[(0,a.jsx)(r.xv,{className:"text-base mr-2",children:"Shareable Link:"}),(0,a.jsx)(r.xv,{className:"max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")})]}),(0,a.jsx)("div",{className:"flex justify-end",children:(0,a.jsx)(r.zx,{onClick:()=>{eg.replace("/model_hub_table?key=".concat(N))},children:"See Page"})})]})}),(0,a.jsx)(n.Z,{title:(null==R?void 0:R.model_group)||"Model Details",width:1e3,visible:U,footer:null,onOk:e_,onCancel:ek,children:R&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Model Group:"}),(0,a.jsx)(r.xv,{children:R.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(r.xv,{children:R.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:R.providers.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e))})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(s=R.max_input_tokens)||void 0===s?void 0:s.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(l=R.max_output_tokens)||void 0===l?void 0:l.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.input_cost_per_token?eS(R.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.output_cost_per_token?eS(R.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=eC(R),s=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(r.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,l)=>(0,a.jsx)(r.Ct,{color:s[l%s.length],children:eZ(e)},e))})()})]}),(R.tpm||R.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[R.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(r.xv,{children:R.tpm.toLocaleString()})]}),R.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(r.xv,{children:R.rpm.toLocaleString()})]})]})]}),R.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:R.supported_openai_params.map(e=>(0,a.jsx)(r.Ct,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'import openai\n\nclient = openai.OpenAI(\n api_key="your_api_key",\n base_url="http://0.0.0.0:4000" # Your LiteLLM Proxy URL\n)\n\nresponse = client.chat.completions.create(\n model="'.concat(R.model_group,'",\n messages=[\n {\n "role": "user",\n "content": "Hello, how are you?"\n }\n ]\n)\n\nprint(response.choices[0].message.content)')})]})]})}),(0,a.jsx)(n.Z,{title:(null==el?void 0:el.name)||"Agent Details",width:1e3,visible:et,footer:null,onOk:e_,onCancel:ek,children:el&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Agent Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Name:"}),(0,a.jsx)(r.xv,{children:el.name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Version:"}),(0,a.jsxs)(r.Ct,{color:"blue",children:["v",el.version]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Protocol Version:"}),(0,a.jsx)(r.xv,{children:el.protocolVersion})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"truncate",children:el.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(el.url),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:el.description})]})]}),el.capabilities&&Object.keys(el.capabilities).length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(el.capabilities).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return(0,a.jsx)(r.Ct,{color:"green",children:s},s)})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Input/Output Modes"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(v=el.defaultInputModes)||void 0===v?void 0:v.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(b=el.defaultOutputModes)||void 0===b?void 0:b.map(e=>(0,a.jsx)(r.Ct,{color:"purple",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]})]})]}),el.skills&&el.skills.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Skills"}),(0,a.jsx)("div",{className:"space-y-4",children:el.skills.map(e=>(0,a.jsxs)("div",{className:"border border-gray-200 rounded p-4",children:[(0,a.jsxs)("div",{className:"flex justify-between items-start mb-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium text-base",children:e.name}),(0,a.jsxs)(r.xv,{className:"text-xs text-gray-500",children:["ID: ",e.id]})]}),e.tags&&e.tags.length>0&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:e.tags.map(e=>(0,a.jsx)(r.Ct,{color:"purple",size:"xs",children:e},e))})]}),(0,a.jsx)(r.xv,{className:"text-sm mb-2",children:e.description}),e.examples&&e.examples.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-xs font-medium text-gray-700",children:"Examples:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.examples.map((e,s)=>(0,a.jsx)(r.Ct,{color:"gray",size:"xs",children:e},s))})]})]},e.id))})]}),el.supportsAuthenticatedExtendedCard&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Additional Features"}),(0,a.jsx)(r.Ct,{color:"green",children:"Supports Authenticated Extended Card"})]})]})}),(0,a.jsx)(n.Z,{title:(null==eo?void 0:eo.server_name)||"MCP Server Details",width:1e3,visible:em,footer:null,onOk:e_,onCancel:ek,children:eo&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Server Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server Name:"}),(0,a.jsx)(r.xv,{children:eo.server_name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server ID:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"text-xs truncate",children:eo.server_id}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.server_id),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]}),eo.alias&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Alias:"}),(0,a.jsx)(r.xv,{children:eo.alias})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Transport:"}),(0,a.jsx)(r.Ct,{color:"blue",children:eo.transport})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Auth Type:"}),(0,a.jsx)(r.Ct,{color:"none"===eo.auth_type?"gray":"green",children:eo.auth_type})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Status:"}),(0,a.jsx)(r.Ct,{color:"active"===eo.status||"healthy"===eo.status?"green":"inactive"===eo.status||"unhealthy"===eo.status?"red":"gray",children:eo.status||"unknown"})]})]}),eo.description&&(0,a.jsxs)("div",{className:"mt-2",children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:eo.description})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Connection Details"}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2 mt-1",children:[(0,a.jsx)(r.xv,{className:"text-sm break-all bg-gray-100 p-2 rounded flex-1",children:eo.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0"})]})]}),eo.command&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Command:"}),(0,a.jsx)(r.xv,{className:"text-sm bg-gray-100 p-2 rounded mt-1 font-mono",children:eo.command})]})]})]}),eo.allowed_tools&&eo.allowed_tools.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Allowed Tools"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.allowed_tools.map((e,s)=>(0,a.jsx)(r.Ct,{color:"purple",children:e},s))})]}),eo.teams&&eo.teams.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Teams"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.teams.map((e,s)=>(0,a.jsx)(r.Ct,{color:"blue",children:e},s))})]}),eo.mcp_access_groups&&eo.mcp_access_groups.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Access Groups"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.mcp_access_groups.map((e,s)=>(0,a.jsx)(r.Ct,{color:"green",children:e},s))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Metadata"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created By:"}),(0,a.jsx)(r.xv,{children:eo.created_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated By:"}),(0,a.jsx)(r.xv,{children:eo.updated_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.created_at).toLocaleString()})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.updated_at).toLocaleString()})]}),eo.last_health_check&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Last Health Check:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.last_health_check).toLocaleString()})]})]}),eo.health_check_error&&(0,a.jsxs)("div",{className:"mt-2 p-2 bg-red-50 rounded",children:[(0,a.jsx)(r.xv,{className:"font-medium text-red-700",children:"Health Check Error:"}),(0,a.jsx)(r.xv,{className:"text-sm text-red-600 mt-1",children:eo.health_check_error})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'from fastmcp import Client\nimport asyncio\n\n# Standard MCP configuration\nconfig = {\n "mcpServers": {\n "'.concat(eo.server_name,'": {\n "url": "http://localhost:4000/').concat(eo.server_name,'/mcp",\n "headers": {\n "x-litellm-api-key": "Bearer sk-1234"\n }\n }\n }\n}\n\n# Create a client that connects to the server\nclient = Client(config)\n\nasync def main():\n async with client:\n # List available tools\n tools = await client.list_tools()\n print(f"Available tools: {[tool.name for tool in tools]}")\n\n # Call a tool\n response = await client.call_tool(\n name="tool_name", \n arguments={"arg": "value"}\n )\n print(f"Response: {response}")\n\nif __name__ == "__main__":\n asyncio.run(main())')})]})]})}),(0,a.jsx)(A,{visible:q,onClose:()=>G(!1),accessToken:N||"",modelHubData:z||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.modelHubCall)(N);L(e.data)}catch(e){console.error("Error refreshing model data:",e)}})()}}),(0,a.jsx)(Z,{visible:Q,onClose:()=>X(!1),accessToken:N||"",agentHubData:J||[],onSuccess:()=>{N&&(async()=>{try{let e=(await (0,_.getAgentsList)(N)).agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.is_public}));$(e)}catch(e){console.error("Error refreshing agent data:",e)}})()}}),(0,a.jsx)(S,{visible:eu,onClose:()=>ep(!1),accessToken:N||"",mcpHubData:en||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.fetchMCPServers)(N);ei(e)}catch(e){console.error("Error refreshing MCP server data:",e)}})()}})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js b/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js deleted file mode 100644 index 1c6e112db48..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2249-3e3c0a9e241e35dc.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2249],{64748:function(e,s,l){l.d(s,{Ct:function(){return a.Z},Dx:function(){return m.Z},OK:function(){return n.Z},Zb:function(){return r.Z},nP:function(){return o.Z},td:function(){return c.Z},v0:function(){return i.Z},x4:function(){return d.Z},xv:function(){return x.Z},zx:function(){return t.Z}});var a=l(41649),t=l(78489),r=l(12514),n=l(12485),i=l(18135),c=l(35242),d=l(29706),o=l(77991),x=l(84264),m=l(96761)},78801:function(e,s,l){l.d(s,{Z:function(){return a.Z},x:function(){return t.Z}});var a=l(12514),t=l(84264)},92249:function(e,s,l){l.d(s,{Z:function(){return W}});var a=l(57437),t=l(23639),r=l(64748),n=l(22116),i=l(78867),c=l(99376),d=l(2265),o=l(17906),x=l(20347),m=l(41649),h=l(78489),u=l(84264),p=l(99981),g=l(3810),j=l(15424),v=l(15690),b=l(10032),N=l(4156),f=l(5545),y=l(96761),_=l(19250),k=l(9114);let{Step:w}=v.default;var Z=e=>{let{visible:s,onClose:l,accessToken:t,agentHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),Z=()=>{o(0),h(new Set),j.resetFields(),l()},C=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.agent_id||e.name))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>!0===e.is_public).map(e=>e.agent_id||e.name)))},[s,r]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeAgentsPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," agent(s) public!")),Z(),i()}catch(e){console.error("Error making agents public:",e),k.Z.fromBackend("Failed to make agents public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.agent_id||e.name)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Agents to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the agents you want to be visible on the public model hub. Users will still require a valid API key to use these agents."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No agents available."})}):r.map(e=>{let s=e.agent_id||e.name;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(s),onChange:e=>C(s,e.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.name}),(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",e.version]})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description}),e.skills&&e.skills.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.skills.slice(0,3).map(e=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e.name},e.id)),e.skills.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.skills.length-3," more"]})]})]})]},s)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Agents Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these agents public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Agents to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>(s.agent_id||s.name)===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.name)||e}),s&&(0,a.jsxs)(m.Z,{color:"blue",size:"xs",children:["v",s.version]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," agent",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Agents Public",open:s,onCancel:Z,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(w,{title:"Select Agents"}),(0,a.jsx)(w,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?Z:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one agent to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})};let{Step:C}=v.default;var S=e=>{let{visible:s,onClose:l,accessToken:t,mcpHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)(!1),[j]=b.Z.useForm(),w=()=>{o(0),h(new Set),j.resetFields(),l()},Z=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},S=e=>{e?h(new Set(r.map(e=>e.server_id))):h(new Set)};(0,d.useEffect)(()=>{s&&r.length>0&&h(new Set(r.filter(e=>{var s;return(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0}).map(e=>e.server_id)))},[s]);let P=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}g(!0);try{let e=Array.from(x);await (0,_.makeMCPPublicCall)(t,e),k.Z.success("Successfully made ".concat(e.length," MCP server(s) public!")),w(),i()}catch(e){console.error("Error making MCP servers public:",e),k.Z.fromBackend("Failed to make MCP servers public. Please try again.")}finally{g(!1)}},M=()=>{let e=r.length>0&&r.every(e=>x.has(e.server_id)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select MCP Servers to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>S(e.target.checked),disabled:0===r.length,children:["Select All ",r.length>0&&"(".concat(r.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the MCP servers you want to be visible on the public model hub. Users will still require a valid API key to use these servers."}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===r.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No MCP servers available."})}):r.map(e=>{var s;let l=(null===(s=e.mcp_info)||void 0===s?void 0:s.is_public)===!0;return(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.server_id),onChange:s=>Z(e.server_id,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.server_name}),l&&(0,a.jsx)(m.Z,{color:"emerald",size:"sm",children:"Public"}),(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:e.transport}),(0,a.jsx)(m.Z,{color:"active"===e.status||"healthy"===e.status?"green":"inactive"===e.status||"unhealthy"===e.status?"red":"gray",size:"sm",children:e.status||"unknown"})]}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:e.description||e.url}),e.allowed_tools&&e.allowed_tools.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1 mt-1",children:[e.allowed_tools.slice(0,3).map((e,s)=>(0,a.jsx)(m.Z,{color:"purple",size:"xs",children:e},s)),e.allowed_tools.length>3&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",e.allowed_tools.length-3," more"]})]})]})]},e.server_id)})})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," selected"]})})]})},z=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making MCP Servers Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these MCP servers public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"MCP Servers to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.server_id===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:(null==s?void 0:s.server_name)||e}),s&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:s.transport}),(0,a.jsx)(m.Z,{color:"active"===s.status||"healthy"===s.status?"green":"inactive"===s.status||"unhealthy"===s.status?"red":"gray",size:"xs",children:s.status||"unknown"})]})]}),(null==s?void 0:s.description)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-600 mt-1",children:s.description}),(null==s?void 0:s.url)&&(0,a.jsx)(u.Z,{className:"text-xs text-gray-500 mt-1",children:s.url})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," MCP server",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make MCP Servers Public",open:s,onCancel:w,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:j,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(C,{title:"Select Servers"}),(0,a.jsx)(C,{title:"Confirm"})]}),(()=>{switch(c){case 0:return M();case 1:return z();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?w:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one MCP server to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:P,loading:p,children:"Make Public"})]})]})]})})},P=l(78801),M=e=>{let{modelHubData:s,onFilteredDataChange:l,showFiltersCard:t=!0,className:r=""}=e,[n,i]=(0,d.useState)(""),[c,o]=(0,d.useState)(""),[x,m]=(0,d.useState)(""),[h,u]=(0,d.useState)(""),p=(0,d.useRef)([]),g=(0,d.useMemo)(()=>(null==s?void 0:s.filter(e=>{let s=e.model_group.toLowerCase().includes(n.toLowerCase()),l=""===c||e.providers.includes(c),a=""===x||e.mode===x,t=""===h||Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).some(e=>{let[s]=e;return s.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")===h});return s&&l&&a&&t}))||[],[s,n,c,x,h]);(0,d.useEffect)(()=>{(g.length!==p.current.length||g.some((e,s)=>{var l;return e.model_group!==(null===(l=p.current[s])||void 0===l?void 0:l.model_group)}))&&(p.current=g,l(g))},[g,l]);let j=(0,a.jsxs)("div",{className:"flex flex-wrap gap-4 items-center",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Search Models:"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names...",value:n,onChange:e=>i(e.target.value),className:"border rounded px-3 py-2 w-64 h-10 text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Provider:"}),(0,a.jsxs)("select",{value:c,onChange:e=>o(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Providers"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.providers.forEach(e=>s.add(e))}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Mode:"}),(0,a.jsxs)("select",{value:x,onChange:e=>m(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Modes"}),s&&(e=>{let s=new Set;return e.forEach(e=>{e.mode&&s.add(e.mode)}),Array.from(s)})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(P.x,{className:"text-sm font-medium mb-2",children:"Features:"}),(0,a.jsxs)("select",{value:h,onChange:e=>u(e.target.value),className:"border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10",children:[(0,a.jsx)("option",{value:"",className:"text-sm text-gray-600",children:"All Features"}),s&&(e=>{let s=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).forEach(e=>{let[l]=e,a=l.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");s.add(a)})}),Array.from(s).sort()})(s).map(e=>(0,a.jsx)("option",{value:e,className:"text-sm text-gray-800",children:e},e))]})]}),(n||c||x||h)&&(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsx)("button",{onClick:()=>{i(""),o(""),m(""),u("")},className:"text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center",children:"Clear Filters"})})]});return t?(0,a.jsx)(P.Z,{className:"mb-6 ".concat(r),children:j}):(0,a.jsx)("div",{className:r,children:j})};let{Step:z}=v.default;var A=e=>{let{visible:s,onClose:l,accessToken:t,modelHubData:r,onSuccess:i}=e,[c,o]=(0,d.useState)(0),[x,h]=(0,d.useState)(new Set),[p,g]=(0,d.useState)([]),[j,w]=(0,d.useState)(!1),[Z]=b.Z.useForm(),C=()=>{o(0),h(new Set),g([]),Z.resetFields(),l()},S=(e,s)=>{let l=new Set(x);s?l.add(e):l.delete(e),h(l)},P=e=>{e?h(new Set(p.map(e=>e.model_group))):h(new Set)},A=(0,d.useCallback)(e=>{g(e)},[]);(0,d.useEffect)(()=>{s&&r.length>0&&(g(r),h(new Set(r.filter(e=>!0===e.is_public_model_group).map(e=>e.model_group))))},[s,r]);let F=async()=>{if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}w(!0);try{let e=Array.from(x);await (0,_.makeModelGroupPublic)(t,e),k.Z.success("Successfully made ".concat(e.length," model group(s) public!")),C(),i()}catch(e){console.error("Error making model groups public:",e),k.Z.fromBackend("Failed to make model groups public. Please try again.")}finally{w(!1)}},L=()=>{let e=p.length>0&&p.every(e=>x.has(e.model_group)),s=x.size>0&&!e;return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsx)(y.Z,{children:"Select Models to Make Public"}),(0,a.jsx)("div",{className:"flex items-center space-x-2",children:(0,a.jsxs)(N.Z,{checked:e,indeterminate:s,onChange:e=>P(e.target.checked),disabled:0===p.length,children:["Select All ",p.length>0&&"(".concat(p.length,")")]})})]}),(0,a.jsx)(u.Z,{className:"text-sm text-gray-600",children:"Select the models you want to be visible on the public model hub. Users will still require a valid API key to use these models."}),(0,a.jsx)(M,{modelHubData:r,onFilteredDataChange:A,showFiltersCard:!1,className:"border rounded-lg p-4 bg-gray-50"}),(0,a.jsx)("div",{className:"max-h-96 overflow-y-auto border rounded-lg p-4",children:(0,a.jsx)("div",{className:"space-y-3",children:0===p.length?(0,a.jsx)("div",{className:"text-center py-8 text-gray-500",children:(0,a.jsx)(u.Z,{children:"No models match the current filters."})}):p.map(e=>(0,a.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50",children:[(0,a.jsx)(N.Z,{checked:x.has(e.model_group),onChange:s=>S(e.model_group,s.target.checked)}),(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e.model_group}),e.mode&&(0,a.jsx)(m.Z,{color:"green",size:"sm",children:e.mode})]}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})]},e.model_group))})}),x.size>0&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:[(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," selected"]})})]})},D=()=>(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)(y.Z,{children:"Confirm Making Models Public"}),(0,a.jsx)("div",{className:"bg-yellow-50 border border-yellow-200 rounded-lg p-4",children:(0,a.jsxs)(u.Z,{className:"text-sm text-yellow-800",children:[(0,a.jsx)("strong",{children:"Warning:"})," Once you make these models public, anyone who can go to the"," ",(0,a.jsx)("code",{children:"/ui/model_hub_table"})," will be able to know they exist on the proxy."]})}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(u.Z,{className:"font-medium",children:"Models to be made public:"}),(0,a.jsx)("div",{className:"max-h-48 overflow-y-auto border rounded-lg p-3",children:(0,a.jsx)("div",{className:"space-y-2",children:Array.from(x).map(e=>{let s=r.find(s=>s.model_group===e);return(0,a.jsx)("div",{className:"flex items-center justify-between p-2 bg-gray-50 rounded",children:(0,a.jsxs)("div",{children:[(0,a.jsx)(u.Z,{className:"font-medium",children:e}),s&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:s.providers.map(e=>(0,a.jsx)(m.Z,{color:"blue",size:"xs",children:e},e))})]})},e)})})})]}),(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-3",children:(0,a.jsxs)(u.Z,{className:"text-sm text-blue-800",children:["Total: ",(0,a.jsx)("strong",{children:x.size})," model",1!==x.size?"s":""," will be made public"]})})]});return(0,a.jsx)(n.Z,{title:"Make Models Public",open:s,onCancel:C,footer:null,width:1200,maskClosable:!1,children:(0,a.jsxs)(b.Z,{form:Z,layout:"vertical",children:[(0,a.jsxs)(v.default,{current:c,className:"mb-6",children:[(0,a.jsx)(z,{title:"Select Models"}),(0,a.jsx)(z,{title:"Confirm"})]}),(()=>{switch(c){case 0:return L();case 1:return D();default:return null}})(),(0,a.jsxs)("div",{className:"flex justify-between mt-6",children:[(0,a.jsx)(f.ZP,{onClick:0===c?C:()=>{1===c&&o(0)},children:0===c?"Cancel":"Previous"}),(0,a.jsxs)("div",{className:"flex space-x-2",children:[0===c&&(0,a.jsx)(f.ZP,{onClick:()=>{if(0===c){if(0===x.size){k.Z.fromBackend("Please select at least one model to make public");return}o(1)}},disabled:0===x.size,children:"Next"}),1===c&&(0,a.jsx)(f.ZP,{onClick:F,loading:j,children:"Make Public"})]})]})]})})},F=l(8048);let L=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),D=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),O=e=>"$".concat((1e6*e).toFixed(2)),U=e=>e>=1e6?"".concat((e/1e6).toFixed(1),"M"):e>=1e3?"".concat((e/1e3).toFixed(1),"K"):e.toString(),E=function(e,s){let l=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=[{header:"Public Model Name",accessorKey:"model_group",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.model_group}),(0,a.jsx)(p.Z,{title:"Copy model name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.providers.join(", ")})})]})}},{header:"Provider",accessorKey:"providers",enableSorting:!0,sortingFn:(e,s)=>{let l=e.original.providers.join(", "),a=s.original.providers.join(", ");return l.localeCompare(a)},cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.providers.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"blue",className:"text-xs",children:e},e)),l.providers.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.providers.length-2]})]})},meta:{className:"hidden md:table-cell"}},{header:"Mode",accessorKey:"mode",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return l.mode?(0,a.jsx)(m.Z,{color:"green",size:"sm",children:l.mode}):(0,a.jsx)(u.Z,{className:"text-gray-500",children:"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Tokens",accessorKey:"max_input_tokens",enableSorting:!0,sortingFn:(e,s)=>(e.original.max_input_tokens||0)+(e.original.max_output_tokens||0)-((s.original.max_input_tokens||0)+(s.original.max_output_tokens||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)("div",{className:"space-y-1",children:(0,a.jsxs)(u.Z,{className:"text-xs",children:[l.max_input_tokens?U(l.max_input_tokens):"-"," /"," ",l.max_output_tokens?U(l.max_output_tokens):"-"]})})},meta:{className:"hidden lg:table-cell"}},{header:"Cost/1M",accessorKey:"input_cost_per_token",enableSorting:!0,sortingFn:(e,s)=>(e.original.input_cost_per_token||0)+(e.original.output_cost_per_token||0)-((s.original.input_cost_per_token||0)+(s.original.output_cost_per_token||0)),cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs",children:l.input_cost_per_token?O(l.input_cost_per_token):"-"}),(0,a.jsx)(u.Z,{className:"text-xs text-gray-500",children:l.output_cost_per_token?O(l.output_cost_per_token):"-"})]})}},{header:"Features",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=D(s.original),t=["green","blue","purple","orange","red","yellow"];return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map((e,s)=>(0,a.jsx)(m.Z,{color:t[s%t.length],size:"xs",children:L(e)},e))})}},{header:"Public",accessorKey:"is_public_model_group",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public_model_group?1:0)-(!0===s.original.is_public_model_group?1:0),cell:e=>{let{row:s}=e;return!0===s.original.is_public_model_group?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}];return l?r.filter(e=>!("accessorKey"in e)||"is_public_model_group"!==e.accessorKey):r};var K=l(87526),T=l(86462),H=l(47686),I=l(77355),R=l(93416),B=l(74998),Y=l(95704),V=e=>{let{accessToken:s,userRole:l}=e,[t,r]=(0,d.useState)([]),[i,c]=(0,d.useState)({url:"",displayName:""}),[o,m]=(0,d.useState)(null),[h,u]=(0,d.useState)(!1),[p,g]=(0,d.useState)(!0),j=async()=>{if(s)try{u(!0);let e=await (0,_.getPublicModelHubInfo)();if(e&&e.useful_links){let s=e.useful_links||{},l=Object.entries(s).map((e,s)=>{let[l,a]=e;return{id:"".concat(s,"-").concat(l),displayName:l,url:a}});r(l)}else r([])}catch(e){console.error("Error fetching useful links:",e),r([])}finally{u(!1)}};if((0,d.useEffect)(()=>{j()},[s]),!(0,x.tY)(l||""))return null;let v=async e=>{if(!s)return!1;try{let l={};return e.forEach(e=>{l[e.displayName]=e.url}),await (0,_.updateUsefulLinksCall)(s,l),n.Z.success({title:"Links Saved Successfully",content:(0,a.jsxs)("div",{className:"py-4",children:[(0,a.jsx)("p",{className:"text-gray-600 mb-4",children:"Your useful links have been saved and are now visible on the public model hub."}),(0,a.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4",children:[(0,a.jsx)("p",{className:"text-sm text-blue-800 mb-2 font-medium",children:"View your updated model hub:"}),(0,a.jsx)("a",{href:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table"),target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center text-blue-600 hover:text-blue-800 underline text-sm font-medium",children:"Open Public Model Hub →"})]})]}),width:500,okText:"Close",maskClosable:!0,keyboard:!0}),!0}catch(e){return console.error("Error saving links:",e),k.Z.fromBackend("Failed to save links - ".concat(e)),!1}},b=async()=>{if(!i.url||!i.displayName)return;try{new URL(i.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.displayName===i.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=[...t,{id:"".concat(Date.now(),"-").concat(i.displayName),displayName:i.displayName,url:i.url}];await v(e)&&(r(e),c({url:"",displayName:""}),k.Z.success("Link added successfully"))},N=e=>{m({...e})},f=async()=>{if(!o)return;try{new URL(o.url)}catch(e){k.Z.fromBackend("Please enter a valid URL");return}if(t.some(e=>e.id!==o.id&&e.displayName===o.displayName)){k.Z.fromBackend("A link with this display name already exists");return}let e=t.map(e=>e.id===o.id?o:e);await v(e)&&(r(e),m(null),k.Z.success("Link updated successfully"))},y=()=>{m(null)},w=async e=>{let s=t.filter(s=>s.id!==e);await v(s)&&(r(s),k.Z.success("Link deleted successfully"))},Z=e=>{window.open(e,"_blank")};return(0,a.jsxs)(Y.Zb,{className:"mb-6",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between cursor-pointer",onClick:()=>g(!p),children:[(0,a.jsxs)("div",{className:"flex flex-col",children:[(0,a.jsx)(Y.Dx,{className:"mb-0",children:"Link Management"}),(0,a.jsx)("p",{className:"text-sm text-gray-500",children:"Manage the links that are displayed under 'Useful Links' on the public model hub."})]}),(0,a.jsx)("div",{className:"flex items-center",children:p?(0,a.jsx)(T.Z,{className:"w-5 h-5 text-gray-500"}):(0,a.jsx)(H.Z,{className:"w-5 h-5 text-gray-500"})})]}),p&&(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Link"}),(0,a.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"URL"}),(0,a.jsx)("input",{type:"text",value:i.url,onChange:e=>c({...i,url:e.target.value}),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Display Name"}),(0,a.jsx)("input",{type:"text",value:i.displayName,onChange:e=>c({...i,displayName:e.target.value}),placeholder:"Friendly name",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsxs)("button",{onClick:b,disabled:!i.url||!i.displayName,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(i.url&&i.displayName?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,a.jsx)(I.Z,{className:"w-4 h-4 mr-1"}),"Add Link"]})})]})]}),(0,a.jsx)(Y.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Links"}),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(Y.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(Y.ss,{children:(0,a.jsxs)(Y.SC,{children:[(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Display Name"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"URL"}),(0,a.jsx)(Y.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,a.jsxs)(Y.RM,{children:[t.map(e=>(0,a.jsx)(Y.SC,{className:"h-8",children:o&&o.id===e.id?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.displayName,onChange:e=>m({...o,displayName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:o.url,onChange:e=>m({...o,url:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:f,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,a.jsx)("button",{onClick:y,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-900",children:e.displayName}),(0,a.jsx)(Y.pj,{className:"py-0.5 text-sm text-gray-500",children:e.url}),(0,a.jsx)(Y.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:()=>Z(e.url),className:"text-xs bg-green-50 text-green-600 px-2 py-1 rounded hover:bg-green-100",children:"Use"}),(0,a.jsx)("button",{onClick:()=>N(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,a.jsx)(R.Z,{className:"w-3 h-3"})}),(0,a.jsx)("button",{onClick:()=>w(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,a.jsx)(B.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===t.length&&(0,a.jsx)(Y.SC,{children:(0,a.jsx)(Y.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No links added yet. Add a new link above."})})]})]})})})]})]})},W=e=>{var s,l,v,b;let{accessToken:N,publicPage:f,premiumUser:y,userRole:w}=e,[C,P]=(0,d.useState)(!1),[z,L]=(0,d.useState)(null),[D,O]=(0,d.useState)(!0),[U,T]=(0,d.useState)(!1),[H,I]=(0,d.useState)(!1),[R,B]=(0,d.useState)(null),[Y,W]=(0,d.useState)([]),[q,G]=(0,d.useState)(!1),[J,$]=(0,d.useState)(null),[Q,X]=(0,d.useState)(!1),[ee,es]=(0,d.useState)(!0),[el,ea]=(0,d.useState)(null),[et,er]=(0,d.useState)(!1),[en,ei]=(0,d.useState)(null),[ec,ed]=(0,d.useState)(!0),[eo,ex]=(0,d.useState)(null),[em,eh]=(0,d.useState)(!1),[eu,ep]=(0,d.useState)(!1),eg=(0,c.useRouter)(),ej=(0,d.useRef)(null),ev=(0,d.useRef)(null),eb=(0,d.useRef)(null);(0,d.useEffect)(()=>{let e=async e=>{try{O(!0);let s=await (0,_.modelHubCall)(e);console.log("ModelHubData:",s),L(s.data),(0,_.getConfigFieldSetting)(e,"enable_public_model_hub").then(e=>{console.log("data: ".concat(JSON.stringify(e))),!0==e.field_value&&P(!0)}).catch(e=>{})}catch(e){console.error("There was an error fetching the model data",e)}finally{O(!1)}},s=async()=>{try{var e,s;O(!0),await (0,_.getUiConfig)();let l=await (0,_.modelHubPublicModelsCall)();console.log("ModelHubData:",l),console.log("First model structure:",l[0]),console.log("Model has model_group?",null===(e=l[0])||void 0===e?void 0:e.model_group),console.log("Model has providers?",null===(s=l[0])||void 0===s?void 0:s.providers),L(l),P(!0)}catch(e){console.error("There was an error fetching the public model data",e)}finally{O(!1)}};N?e(N):f&&s()},[N,f]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{es(!0);let e=await (0,_.getAgentsList)(N);console.log("AgentHubData:",e);let s=e.agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.litellm_params.is_public}));$(s)}catch(e){console.error("There was an error fetching the agent data",e)}finally{es(!1)}};f||e()},[f,N]),(0,d.useEffect)(()=>{let e=async()=>{if(N)try{ed(!0);let e=await (0,_.fetchMCPServers)(N);console.log("MCPHubData:",e),ei(e)}catch(e){console.error("There was an error fetching the MCP server data",e)}finally{ed(!1)}};f||e()},[f,N]);let eN=()=>{N&&G(!0)},ef=()=>{N&&X(!0)},ey=()=>{N&&ep(!0)},e_=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ek=()=>{T(!1),I(!1),B(null),er(!1),ea(null),eh(!1),ex(null)},ew=e=>{navigator.clipboard.writeText(e),k.Z.success("Copied to clipboard!")},eZ=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),eC=e=>Object.entries(e).filter(e=>{let[s,l]=e;return s.startsWith("supports_")&&!0===l}).map(e=>{let[s]=e;return s}),eS=e=>"$".concat((1e6*e).toFixed(2)),eP=(0,d.useCallback)(e=>{W(e)},[]);return(console.log("publicPage: ",f),console.log("publicPageAllowed: ",C),f&&C)?(0,a.jsx)(K.Z,{accessToken:N}):(0,a.jsxs)("div",{className:"w-full mx-4 h-[75vh]",children:[!1==f?(0,a.jsxs)("div",{className:"w-full m-2 mt-2 p-8",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,a.jsxs)("div",{className:"flex flex-col items-start",children:[(0,a.jsx)(r.Dx,{className:"text-center",children:"AI Hub"}),(0,x.tY)(w||"")?(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"Make models, agents, and MCP servers public for developers to know what's available."}):(0,a.jsx)("p",{className:"text-sm text-gray-600",children:"A list of all public model names personally available to you."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,a.jsx)(r.xv,{children:"Model Hub URL:"}),(0,a.jsxs)("div",{className:"flex items-center bg-gray-200 px-2 py-1 rounded",children:[(0,a.jsx)(r.xv,{className:"mr-2",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")}),(0,a.jsx)("button",{onClick:()=>ew("".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")),className:"p-1 hover:bg-gray-300 rounded transition-colors",title:"Copy URL",children:(0,a.jsx)(i.Z,{size:16,className:"text-gray-600"})})]})]})]}),(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"mt-8 mb-2",children:(0,a.jsx)(V,{accessToken:N,userRole:w})}),(0,a.jsxs)(r.v0,{children:[(0,a.jsxs)(r.td,{className:"mb-4",children:[(0,a.jsx)(r.OK,{children:"Model Hub"}),(0,a.jsx)(r.OK,{children:"Agent Hub"}),(0,a.jsx)(r.OK,{children:"MCP Hub"})]}),(0,a.jsxs)(r.nP,{children:[(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>eN(),children:"Select Models to Make Public"})}),(0,a.jsx)(M,{modelHubData:z||[],onFilteredDataChange:eP}),(0,a.jsx)(F.C,{columns:E(e=>{B(e),T(!0)},ew,f),data:Y,isLoading:D,table:ej,defaultSorting:[{id:"model_group",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",Y.length," of ",(null==z?void 0:z.length)||0," models"]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ef(),children:"Select Agents to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Agent Name",accessorKey:"name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.name}),(0,a.jsx)(p.Z,{title:"Copy agent name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"Version",accessorKey:"version",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsxs)(m.Z,{color:"blue",size:"sm",children:["v",l.version]})},meta:{className:"hidden lg:table-cell"}},{header:"Protocol",accessorKey:"protocolVersion",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.protocolVersion||"-"})},meta:{className:"hidden lg:table-cell"}},{header:"Skills",accessorKey:"skills",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.skills||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs font-medium",children:[l.length," skill",1!==l.length?"s":""]}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map(e=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e.name},e.id)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})}},{header:"Capabilities",accessorKey:"capabilities",enableSorting:!1,cell:e=>{let{row:s}=e,l=Object.entries(s.original.capabilities||{}).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return s});return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:0===l.length?(0,a.jsx)(u.Z,{className:"text-gray-500 text-xs",children:"-"}):l.map(e=>(0,a.jsx)(m.Z,{color:"green",size:"xs",children:e},e))})}},{header:"I/O Modes",accessorKey:"defaultInputModes",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original,t=l.defaultInputModes||[],r=l.defaultOutputModes||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"In:"})," ",t.join(", ")||"-"]}),(0,a.jsxs)(u.Z,{className:"text-xs",children:[(0,a.jsx)("span",{className:"font-medium",children:"Out:"})," ",r.join(", ")||"-"]})]})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"is_public",enableSorting:!0,sortingFn:(e,s)=>(!0===e.original.is_public?1:0)-(!0===s.original.is_public?1:0),cell:e=>{let{row:s}=e;return console.log("CHECKPOINT 1: ".concat(JSON.stringify(s.original))),!0===s.original.is_public?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ea(e),er(!0)},ew,f),data:J||[],isLoading:ee,table:ev,defaultSorting:[{id:"name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==J?void 0:J.length)||0," agent",(null==J?void 0:J.length)!==1?"s":""]})})]}),(0,a.jsxs)(r.x4,{children:[(0,a.jsxs)(r.Zb,{children:[!1==f&&(0,x.tY)(w||"")&&(0,a.jsx)("div",{className:"flex justify-end mb-4",children:(0,a.jsx)(r.zx,{onClick:()=>ey(),children:"Select MCP Servers to Make Public"})}),(0,a.jsx)(F.C,{columns:function(e,s){return arguments.length>2&&void 0!==arguments[2]&&arguments[2],[{header:"Server Name",accessorKey:"server_name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"font-medium text-sm",children:r.server_name}),(0,a.jsx)(p.Z,{title:"Copy server name",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.server_name),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]}),(0,a.jsx)("div",{className:"md:hidden",children:(0,a.jsx)(u.Z,{className:"text-xs text-gray-600",children:r.description||"-"})})]})}},{header:"Description",accessorKey:"description",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs line-clamp-2",children:l.description||"-"})},meta:{className:"hidden md:table-cell"}},{header:"URL",accessorKey:"url",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,r=l.original;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(u.Z,{className:"text-xs truncate max-w-xs",children:r.url}),(0,a.jsx)(p.Z,{title:"Copy URL",children:(0,a.jsx)(t.Z,{onClick:()=>s(r.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs flex-shrink-0"})})]})},meta:{className:"hidden lg:table-cell"}},{header:"Transport",accessorKey:"transport",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(m.Z,{color:"blue",size:"sm",children:l.transport})},meta:{className:"hidden md:table-cell"}},{header:"Auth Type",accessorKey:"auth_type",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t="none"===l.auth_type?"gray":"green";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.auth_type})},meta:{className:"hidden md:table-cell"}},{header:"Status",accessorKey:"status",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original,t={active:"green",inactive:"red",unknown:"gray",healthy:"green",unhealthy:"red"}[l.status]||"gray";return(0,a.jsx)(m.Z,{color:t,size:"sm",children:l.status||"unknown"})}},{header:"Tools",accessorKey:"allowed_tools",enableSorting:!1,cell:e=>{let{row:s}=e,l=s.original.allowed_tools||[];return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(u.Z,{className:"text-xs font-medium",children:l.length>0?"".concat(l.length," tool").concat(1!==l.length?"s":""):"All tools"}),l.length>0&&(0,a.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,2).map((e,s)=>(0,a.jsx)(g.Z,{color:"purple",className:"text-xs",children:e},s)),l.length>2&&(0,a.jsxs)(u.Z,{className:"text-xs text-gray-500",children:["+",l.length-2]})]})]})},meta:{className:"hidden lg:table-cell"}},{header:"Created By",accessorKey:"created_by",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:s}=e,l=s.original;return(0,a.jsx)(u.Z,{className:"text-xs",children:l.created_by||"-"})},meta:{className:"hidden xl:table-cell"}},{header:"Public",accessorKey:"mcp_info.is_public",enableSorting:!0,sortingFn:(e,s)=>{var l,a;return((null===(l=e.original.mcp_info)||void 0===l?void 0:l.is_public)===!0?1:0)-((null===(a=s.original.mcp_info)||void 0===a?void 0:a.is_public)===!0?1:0)},cell:e=>{var s;let{row:l}=e;return(null===(s=l.original.mcp_info)||void 0===s?void 0:s.is_public)===!0?(0,a.jsx)(m.Z,{color:"green",size:"xs",children:"Yes"}):(0,a.jsx)(m.Z,{color:"gray",size:"xs",children:"No"})},meta:{className:"hidden md:table-cell"}},{header:"Details",id:"details",enableSorting:!1,cell:s=>{let{row:l}=s,t=l.original;return(0,a.jsxs)(h.Z,{size:"xs",variant:"secondary",onClick:()=>e(t),icon:j.Z,children:[(0,a.jsx)("span",{className:"hidden lg:inline",children:"Details"}),(0,a.jsx)("span",{className:"lg:hidden",children:"Info"})]})}}]}(e=>{ex(e),eh(!0)},ew,f),data:en||[],isLoading:ec,table:eb,defaultSorting:[{id:"server_name",desc:!1}]})]}),(0,a.jsx)("div",{className:"mt-4 text-center space-y-2",children:(0,a.jsxs)(r.xv,{className:"text-sm text-gray-600",children:["Showing ",(null==en?void 0:en.length)||0," MCP server",(null==en?void 0:en.length)!==1?"s":""]})})]})]})]})]}):(0,a.jsxs)(r.Zb,{className:"mx-auto max-w-xl mt-10",children:[(0,a.jsx)(r.xv,{className:"text-xl text-center mb-2 text-black",children:"Public Model Hub not enabled."}),(0,a.jsx)("p",{className:"text-base text-center text-slate-800",children:"Ask your proxy admin to enable this on their Admin UI."})]}),(0,a.jsx)(n.Z,{title:"Public Model Hub",width:600,visible:H,footer:null,onOk:e_,onCancel:ek,children:(0,a.jsxs)("div",{className:"pt-5 pb-5",children:[(0,a.jsxs)("div",{className:"flex justify-between mb-4",children:[(0,a.jsx)(r.xv,{className:"text-base mr-2",children:"Shareable Link:"}),(0,a.jsx)(r.xv,{className:"max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded",children:"".concat((0,_.getProxyBaseUrl)(),"/ui/model_hub_table")})]}),(0,a.jsx)("div",{className:"flex justify-end",children:(0,a.jsx)(r.zx,{onClick:()=>{eg.replace("/model_hub_table?key=".concat(N))},children:"See Page"})})]})}),(0,a.jsx)(n.Z,{title:(null==R?void 0:R.model_group)||"Model Details",width:1e3,visible:U,footer:null,onOk:e_,onCancel:ek,children:R&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Model Group:"}),(0,a.jsx)(r.xv,{children:R.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(r.xv,{children:R.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:R.providers.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e))})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(s=R.max_input_tokens)||void 0===s?void 0:s.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(r.xv,{children:(null===(l=R.max_output_tokens)||void 0===l?void 0:l.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.input_cost_per_token?eS(R.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(r.xv,{children:R.output_cost_per_token?eS(R.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=eC(R),s=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(r.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,l)=>(0,a.jsx)(r.Ct,{color:s[l%s.length],children:eZ(e)},e))})()})]}),(R.tpm||R.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[R.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(r.xv,{children:R.tpm.toLocaleString()})]}),R.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(r.xv,{children:R.rpm.toLocaleString()})]})]})]}),R.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:R.supported_openai_params.map(e=>(0,a.jsx)(r.Ct,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'import openai\n\nclient = openai.OpenAI(\n api_key="your_api_key",\n base_url="http://0.0.0.0:4000" # Your LiteLLM Proxy URL\n)\n\nresponse = client.chat.completions.create(\n model="'.concat(R.model_group,'",\n messages=[\n {\n "role": "user",\n "content": "Hello, how are you?"\n }\n ]\n)\n\nprint(response.choices[0].message.content)')})]})]})}),(0,a.jsx)(n.Z,{title:(null==el?void 0:el.name)||"Agent Details",width:1e3,visible:et,footer:null,onOk:e_,onCancel:ek,children:el&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Agent Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Name:"}),(0,a.jsx)(r.xv,{children:el.name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Version:"}),(0,a.jsxs)(r.Ct,{color:"blue",children:["v",el.version]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Protocol Version:"}),(0,a.jsx)(r.xv,{children:el.protocolVersion})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"truncate",children:el.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(el.url),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:el.description})]})]}),el.capabilities&&Object.keys(el.capabilities).length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(el.capabilities).filter(e=>{let[s,l]=e;return!0===l}).map(e=>{let[s]=e;return(0,a.jsx)(r.Ct,{color:"green",children:s},s)})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Input/Output Modes"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Input Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(v=el.defaultInputModes)||void 0===v?void 0:v.map(e=>(0,a.jsx)(r.Ct,{color:"blue",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Output Modes:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:(null===(b=el.defaultOutputModes)||void 0===b?void 0:b.map(e=>(0,a.jsx)(r.Ct,{color:"purple",children:e},e)))||(0,a.jsx)(r.xv,{children:"Not specified"})})]})]})]}),el.skills&&el.skills.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Skills"}),(0,a.jsx)("div",{className:"space-y-4",children:el.skills.map(e=>(0,a.jsxs)("div",{className:"border border-gray-200 rounded p-4",children:[(0,a.jsxs)("div",{className:"flex justify-between items-start mb-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium text-base",children:e.name}),(0,a.jsxs)(r.xv,{className:"text-xs text-gray-500",children:["ID: ",e.id]})]}),e.tags&&e.tags.length>0&&(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:e.tags.map(e=>(0,a.jsx)(r.Ct,{color:"purple",size:"xs",children:e},e))})]}),(0,a.jsx)(r.xv,{className:"text-sm mb-2",children:e.description}),e.examples&&e.examples.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-xs font-medium text-gray-700",children:"Examples:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:e.examples.map((e,s)=>(0,a.jsx)(r.Ct,{color:"gray",size:"xs",children:e},s))})]})]},e.id))})]}),el.supportsAuthenticatedExtendedCard&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Additional Features"}),(0,a.jsx)(r.Ct,{color:"green",children:"Supports Authenticated Extended Card"})]})]})}),(0,a.jsx)(n.Z,{title:(null==eo?void 0:eo.server_name)||"MCP Server Details",width:1e3,visible:em,footer:null,onOk:e_,onCancel:ek,children:eo&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Server Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server Name:"}),(0,a.jsx)(r.xv,{children:eo.server_name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Server ID:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)(r.xv,{className:"text-xs truncate",children:eo.server_id}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.server_id),className:"cursor-pointer text-gray-500 hover:text-blue-500"})]})]}),eo.alias&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Alias:"}),(0,a.jsx)(r.xv,{children:eo.alias})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Transport:"}),(0,a.jsx)(r.Ct,{color:"blue",children:eo.transport})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Auth Type:"}),(0,a.jsx)(r.Ct,{color:"none"===eo.auth_type?"gray":"green",children:eo.auth_type})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Status:"}),(0,a.jsx)(r.Ct,{color:"active"===eo.status||"healthy"===eo.status?"green":"inactive"===eo.status||"unhealthy"===eo.status?"red":"gray",children:eo.status||"unknown"})]})]}),eo.description&&(0,a.jsxs)("div",{className:"mt-2",children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Description:"}),(0,a.jsx)(r.xv,{className:"mt-1",children:eo.description})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Connection Details"}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"URL:"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2 mt-1",children:[(0,a.jsx)(r.xv,{className:"text-sm break-all bg-gray-100 p-2 rounded flex-1",children:eo.url}),(0,a.jsx)(t.Z,{onClick:()=>ew(eo.url),className:"cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0"})]})]}),eo.command&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Command:"}),(0,a.jsx)(r.xv,{className:"text-sm bg-gray-100 p-2 rounded mt-1 font-mono",children:eo.command})]})]})]}),eo.allowed_tools&&eo.allowed_tools.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Allowed Tools"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.allowed_tools.map((e,s)=>(0,a.jsx)(r.Ct,{color:"purple",children:e},s))})]}),eo.teams&&eo.teams.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Teams"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.teams.map((e,s)=>(0,a.jsx)(r.Ct,{color:"blue",children:e},s))})]}),eo.mcp_access_groups&&eo.mcp_access_groups.length>0&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Access Groups"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:eo.mcp_access_groups.map((e,s)=>(0,a.jsx)(r.Ct,{color:"green",children:e},s))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Metadata"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created By:"}),(0,a.jsx)(r.xv,{children:eo.created_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated By:"}),(0,a.jsx)(r.xv,{children:eo.updated_by})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Created At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.created_at).toLocaleString()})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Updated At:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.updated_at).toLocaleString()})]}),eo.last_health_check&&(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"font-medium",children:"Last Health Check:"}),(0,a.jsx)(r.xv,{className:"text-sm",children:new Date(eo.last_health_check).toLocaleString()})]})]}),eo.health_check_error&&(0,a.jsxs)("div",{className:"mt-2 p-2 bg-red-50 rounded",children:[(0,a.jsx)(r.xv,{className:"font-medium text-red-700",children:"Health Check Error:"}),(0,a.jsx)(r.xv,{className:"text-sm text-red-600 mt-1",children:eo.health_check_error})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(r.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)(o.Z,{language:"python",className:"text-sm",children:'from fastmcp import Client\nimport asyncio\n\n# Standard MCP configuration\nconfig = {\n "mcpServers": {\n "'.concat(eo.server_name,'": {\n "url": "http://localhost:4000/').concat(eo.server_name,'/mcp",\n "headers": {\n "x-litellm-api-key": "Bearer sk-1234"\n }\n }\n }\n}\n\n# Create a client that connects to the server\nclient = Client(config)\n\nasync def main():\n async with client:\n # List available tools\n tools = await client.list_tools()\n print(f"Available tools: {[tool.name for tool in tools]}")\n\n # Call a tool\n response = await client.call_tool(\n name="tool_name", \n arguments={"arg": "value"}\n )\n print(f"Response: {response}")\n\nif __name__ == "__main__":\n asyncio.run(main())')})]})]})}),(0,a.jsx)(A,{visible:q,onClose:()=>G(!1),accessToken:N||"",modelHubData:z||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.modelHubCall)(N);L(e.data)}catch(e){console.error("Error refreshing model data:",e)}})()}}),(0,a.jsx)(Z,{visible:Q,onClose:()=>X(!1),accessToken:N||"",agentHubData:J||[],onSuccess:()=>{N&&(async()=>{try{let e=(await (0,_.getAgentsList)(N)).agents.map(e=>({agent_id:e.agent_id,...e.agent_card_params,is_public:e.is_public}));$(e)}catch(e){console.error("Error refreshing agent data:",e)}})()}}),(0,a.jsx)(S,{visible:eu,onClose:()=>ep(!1),accessToken:N||"",mcpHubData:en||[],onSuccess:()=>{N&&(async()=>{try{let e=await (0,_.fetchMCPServers)(N);ei(e)}catch(e){console.error("Error refreshing MCP server data:",e)}})()}})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js b/litellm/proxy/_experimental/out/_next/static/chunks/3250-6c57da6c11f342fa.js similarity index 99% rename from litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js rename to litellm/proxy/_experimental/out/_next/static/chunks/3250-6c57da6c11f342fa.js index 0416de210bc..c8c793ed69b 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/3250-d3d70eb77e9f4fa7.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/3250-6c57da6c11f342fa.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3250],{49634:function(e,r,o){o.d(r,{Z:function(){return s}});var t=o(1119),l=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"}}]},name:"calendar",theme:"outlined"},n=o(55015),s=l.forwardRef(function(e,r){return l.createElement(n.Z,(0,t.Z)({},e,{ref:r,icon:a}))})},94789:function(e,r,o){o.d(r,{Z:function(){return d}});var t=o(5853),l=o(2265),a=o(26898),n=o(13241),s=o(1153);let i=(0,s.fn)("Callout"),d=l.forwardRef((e,r)=>{let{title:o,icon:d,color:c,className:m,children:p}=e,u=(0,t._T)(e,["title","icon","color","className","children"]);return l.createElement("div",Object.assign({ref:r,className:(0,n.q)(i("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",c?(0,n.q)((0,s.bM)(c,a.K.background).bgColor,(0,s.bM)(c,a.K.darkBorder).borderColor,(0,s.bM)(c,a.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,n.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),m)},u),l.createElement("div",{className:(0,n.q)(i("header"),"flex items-start")},d?l.createElement(d,{className:(0,n.q)(i("icon"),"flex-none h-5 w-5 mr-1.5")}):null,l.createElement("h4",{className:(0,n.q)(i("title"),"font-semibold")},o)),l.createElement("p",{className:(0,n.q)(i("body"),"overflow-y-auto",p?"mt-2":"")},p))});d.displayName="Callout"},35829:function(e,r,o){o.d(r,{Z:function(){return i}});var t=o(5853),l=o(26898),a=o(13241),n=o(1153),s=o(2265);let i=s.forwardRef((e,r)=>{let{color:o,children:i,className:d}=e,c=(0,t._T)(e,["color","children","className"]);return s.createElement("p",Object.assign({ref:r,className:(0,a.q)("font-semibold text-tremor-metric",o?(0,n.bM)(o,l.K.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",d)},c),i)});i.displayName="Metric"},49096:function(e,r,o){o.d(r,{ZD:function(){return a}});var t=o(61994);let l=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,a=e=>{let r=function(){for(var r,o,l=arguments.length,a=Array(l),n=0;n{let t=Object.fromEntries(Object.entries(e||{}).filter(e=>{let[r]=e;return!["class","className"].includes(r)}));return r(o.map(e=>e(t)),null==e?void 0:e.class,null==e?void 0:e.className)}},cva:e=>o=>{var t;if((null==e?void 0:e.variants)==null)return r(null==e?void 0:e.base,null==o?void 0:o.class,null==o?void 0:o.className);let{variants:a,defaultVariants:n}=e,s=Object.keys(a).map(e=>{let r=null==o?void 0:o[e],t=null==n?void 0:n[e],s=l(r)||l(t);return a[e][s]}),i={...n,...o&&Object.entries(o).reduce((e,r)=>{let[o,t]=r;return void 0===t?e:{...e,[o]:t}},{})},d=null==e?void 0:null===(t=e.compoundVariants)||void 0===t?void 0:t.reduce((e,r)=>{let{class:o,className:t,...l}=r;return Object.entries(l).every(e=>{let[r,o]=e,t=i[r];return Array.isArray(o)?o.includes(t):t===o})?[...e,o,t]:e},[]);return r(null==e?void 0:e.base,s,d,null==o?void 0:o.class,null==o?void 0:o.className)},cx:r}},{compose:n,cva:s,cx:i}=a()},53335:function(e,r,o){o.d(r,{m6:function(){return ev}});let t=(e,r)=>{let o=Array(e.length+r.length);for(let r=0;r({classGroupId:e,validator:r}),a=(e=new Map,r=null,o)=>({nextPart:e,validators:r,classGroupId:o}),n=[],s=e=>{let r=c(e),{conflictingClassGroups:o,conflictingClassGroupModifiers:l}=e;return{getClassGroupId:e=>{if(e.startsWith("[")&&e.endsWith("]"))return d(e);let o=e.split("-"),t=""===o[0]&&o.length>1?1:0;return i(o,t,r)},getConflictingClassGroupIds:(e,r)=>{if(r){let r=l[e],a=o[e];return r?a?t(a,r):r:a||n}return o[e]||n}}},i=(e,r,o)=>{if(0==e.length-r)return o.classGroupId;let t=e[r],l=o.nextPart.get(t);if(l){let o=i(e,r+1,l);if(o)return o}let a=o.validators;if(null===a)return;let n=0===r?e.join("-"):e.slice(r).join("-"),s=a.length;for(let e=0;e-1===e.slice(1,-1).indexOf(":")?void 0:(()=>{let r=e.slice(1,-1),o=r.indexOf(":"),t=r.slice(0,o);return t?"arbitrary.."+t:void 0})(),c=e=>{let{theme:r,classGroups:o}=e;return m(o,r)},m=(e,r)=>{let o=a();for(let t in e)p(e[t],o,t,r);return o},p=(e,r,o,t)=>{let l=e.length;for(let a=0;a{if("string"==typeof e){b(e,r,o);return}if("function"==typeof e){f(e,r,o,t);return}g(e,r,o,t)},b=(e,r,o)=>{(""===e?r:h(r,e)).classGroupId=o},f=(e,r,o,t)=>{if(k(e)){p(e(t),r,o,t);return}null===r.validators&&(r.validators=[]),r.validators.push(l(o,e))},g=(e,r,o,t)=>{let l=Object.entries(e),a=l.length;for(let e=0;e{let o=e,t=r.split("-"),l=t.length;for(let e=0;e"isThemeGetter"in e&&!0===e.isThemeGetter,v=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let r=0,o=Object.create(null),t=Object.create(null),l=(l,a)=>{o[l]=a,++r>e&&(r=0,t=o,o=Object.create(null))};return{get(e){let r=o[e];return void 0!==r?r:void 0!==(r=t[e])?(l(e,r),r):void 0},set(e,r){e in o?o[e]=r:l(e,r)}}},x=[],w=(e,r,o,t,l)=>({modifiers:e,hasImportantModifier:r,baseClassName:o,maybePostfixModifierPosition:t,isExternal:l}),y=e=>{let{prefix:r,experimentalParseClassName:o}=e,t=e=>{let r;let o=[],t=0,l=0,a=0,n=e.length;for(let s=0;sa?r-a:void 0)};if(r){let e=r+":",o=t;t=r=>r.startsWith(e)?o(r.slice(e.length)):w(x,!1,r,void 0,!0)}if(o){let e=t;t=r=>o({className:r,parseClassName:e})}return t},z=e=>{let r=new Map;return e.orderSensitiveModifiers.forEach((e,o)=>{r.set(e,1e6+o)}),e=>{let o=[],t=[];for(let l=0;l0&&(t.sort(),o.push(...t),t=[]),o.push(a)):t.push(a)}return t.length>0&&(t.sort(),o.push(...t)),o}},j=e=>({cache:v(e.cacheSize),parseClassName:y(e),sortModifiers:z(e),...s(e)}),N=/\s+/,C=(e,r)=>{let{parseClassName:o,getClassGroupId:t,getConflictingClassGroupIds:l,sortModifiers:a}=r,n=[],s=e.trim().split(N),i="";for(let e=s.length-1;e>=0;e-=1){let r=s[e],{isExternal:d,modifiers:c,hasImportantModifier:m,baseClassName:p,maybePostfixModifierPosition:u}=o(r);if(d){i=r+(i.length>0?" "+i:i);continue}let b=!!u,f=t(b?p.substring(0,u):p);if(!f){if(!b||!(f=t(p))){i=r+(i.length>0?" "+i:i);continue}b=!1}let g=0===c.length?"":1===c.length?c[0]:a(c).join(":"),h=m?g+"!":g,k=h+f;if(n.indexOf(k)>-1)continue;n.push(k);let v=l(f,b);for(let e=0;e0?" "+i:i)}return i},O=(...e)=>{let r,o,t=0,l="";for(;t{let r;if("string"==typeof e)return e;let o="";for(let t=0;t{let r=r=>r[e]||E;return r.isThemeGetter=!0,r},q=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,W=/^\((?:(\w[\w-]*):)?(.+)\)$/i,$=/^\d+\/\d+$/,_=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,T=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,I=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,Z=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,A=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,H=e=>$.test(e),K=e=>!!e&&!Number.isNaN(Number(e)),S=e=>!!e&&Number.isInteger(Number(e)),V=e=>e.endsWith("%")&&K(e.slice(0,-1)),P=e=>_.test(e),R=()=>!0,B=e=>T.test(e)&&!I.test(e),D=()=>!1,F=e=>Z.test(e),J=e=>A.test(e),L=e=>!U(e)&&!et(e),Q=e=>ec(e,eb,D),U=e=>q.test(e),X=e=>ec(e,ef,B),Y=e=>ec(e,eg,K),ee=e=>ec(e,ep,D),er=e=>ec(e,eu,J),eo=e=>ec(e,ek,F),et=e=>W.test(e),el=e=>em(e,ef),ea=e=>em(e,eh),en=e=>em(e,ep),es=e=>em(e,eb),ei=e=>em(e,eu),ed=e=>em(e,ek,!0),ec=(e,r,o)=>{let t=q.exec(e);return!!t&&(t[1]?r(t[1]):o(t[2]))},em=(e,r,o=!1)=>{let t=W.exec(e);return!!t&&(t[1]?r(t[1]):o)},ep=e=>"position"===e||"percentage"===e,eu=e=>"image"===e||"url"===e,eb=e=>"length"===e||"size"===e||"bg-size"===e,ef=e=>"length"===e,eg=e=>"number"===e,eh=e=>"family-name"===e,ek=e=>"shadow"===e,ev=((e,...r)=>{let o,t,l,a;let n=e=>{let r=t(e);if(r)return r;let a=C(e,o);return l(e,a),a};return a=s=>(t=(o=j(r.reduce((e,r)=>r(e),e()))).cache.get,l=o.cache.set,a=n,n(s)),(...e)=>a(O(...e))})(()=>{let e=G("color"),r=G("font"),o=G("text"),t=G("font-weight"),l=G("tracking"),a=G("leading"),n=G("breakpoint"),s=G("container"),i=G("spacing"),d=G("radius"),c=G("shadow"),m=G("inset-shadow"),p=G("text-shadow"),u=G("drop-shadow"),b=G("blur"),f=G("perspective"),g=G("aspect"),h=G("ease"),k=G("animate"),v=()=>["auto","avoid","all","avoid-page","page","left","right","column"],x=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],w=()=>[...x(),et,U],y=()=>["auto","hidden","clip","visible","scroll"],z=()=>["auto","contain","none"],j=()=>[et,U,i],N=()=>[H,"full","auto",...j()],C=()=>[S,"none","subgrid",et,U],O=()=>["auto",{span:["full",S,et,U]},S,et,U],M=()=>[S,"auto",et,U],E=()=>["auto","min","max","fr",et,U],q=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],W=()=>["start","end","center","stretch","center-safe","end-safe"],$=()=>["auto",...j()],_=()=>[H,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...j()],T=()=>[e,et,U],I=()=>[...x(),en,ee,{position:[et,U]}],Z=()=>["no-repeat",{repeat:["","x","y","space","round"]}],A=()=>["auto","cover","contain",es,Q,{size:[et,U]}],B=()=>[V,el,X],D=()=>["","none","full",d,et,U],F=()=>["",K,el,X],J=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],em=()=>[K,V,en,ee],ep=()=>["","none",b,et,U],eu=()=>["none",K,et,U],eb=()=>["none",K,et,U],ef=()=>[K,et,U],eg=()=>[H,"full",...j()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[P],breakpoint:[P],color:[R],container:[P],"drop-shadow":[P],ease:["in","out","in-out"],font:[L],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[P],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[P],shadow:[P],spacing:["px",K],text:[P],"text-shadow":[P],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",H,U,et,g]}],container:["container"],columns:[{columns:[K,U,et,s]}],"break-after":[{"break-after":v()}],"break-before":[{"break-before":v()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:w()}],overflow:[{overflow:y()}],"overflow-x":[{"overflow-x":y()}],"overflow-y":[{"overflow-y":y()}],overscroll:[{overscroll:z()}],"overscroll-x":[{"overscroll-x":z()}],"overscroll-y":[{"overscroll-y":z()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:N()}],"inset-x":[{"inset-x":N()}],"inset-y":[{"inset-y":N()}],start:[{start:N()}],end:[{end:N()}],top:[{top:N()}],right:[{right:N()}],bottom:[{bottom:N()}],left:[{left:N()}],visibility:["visible","invisible","collapse"],z:[{z:[S,"auto",et,U]}],basis:[{basis:[H,"full","auto",s,...j()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[K,H,"auto","initial","none",U]}],grow:[{grow:["",K,et,U]}],shrink:[{shrink:["",K,et,U]}],order:[{order:[S,"first","last","none",et,U]}],"grid-cols":[{"grid-cols":C()}],"col-start-end":[{col:O()}],"col-start":[{"col-start":M()}],"col-end":[{"col-end":M()}],"grid-rows":[{"grid-rows":C()}],"row-start-end":[{row:O()}],"row-start":[{"row-start":M()}],"row-end":[{"row-end":M()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":E()}],"auto-rows":[{"auto-rows":E()}],gap:[{gap:j()}],"gap-x":[{"gap-x":j()}],"gap-y":[{"gap-y":j()}],"justify-content":[{justify:[...q(),"normal"]}],"justify-items":[{"justify-items":[...W(),"normal"]}],"justify-self":[{"justify-self":["auto",...W()]}],"align-content":[{content:["normal",...q()]}],"align-items":[{items:[...W(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...W(),{baseline:["","last"]}]}],"place-content":[{"place-content":q()}],"place-items":[{"place-items":[...W(),"baseline"]}],"place-self":[{"place-self":["auto",...W()]}],p:[{p:j()}],px:[{px:j()}],py:[{py:j()}],ps:[{ps:j()}],pe:[{pe:j()}],pt:[{pt:j()}],pr:[{pr:j()}],pb:[{pb:j()}],pl:[{pl:j()}],m:[{m:$()}],mx:[{mx:$()}],my:[{my:$()}],ms:[{ms:$()}],me:[{me:$()}],mt:[{mt:$()}],mr:[{mr:$()}],mb:[{mb:$()}],ml:[{ml:$()}],"space-x":[{"space-x":j()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":j()}],"space-y-reverse":["space-y-reverse"],size:[{size:_()}],w:[{w:[s,"screen",..._()]}],"min-w":[{"min-w":[s,"screen","none",..._()]}],"max-w":[{"max-w":[s,"screen","none","prose",{screen:[n]},..._()]}],h:[{h:["screen","lh",..._()]}],"min-h":[{"min-h":["screen","lh","none",..._()]}],"max-h":[{"max-h":["screen","lh",..._()]}],"font-size":[{text:["base",o,el,X]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[t,et,Y]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",V,U]}],"font-family":[{font:[ea,U,r]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[l,et,U]}],"line-clamp":[{"line-clamp":[K,"none",et,Y]}],leading:[{leading:[a,...j()]}],"list-image":[{"list-image":["none",et,U]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",et,U]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:T()}],"text-color":[{text:T()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...J(),"wavy"]}],"text-decoration-thickness":[{decoration:[K,"from-font","auto",et,X]}],"text-decoration-color":[{decoration:T()}],"underline-offset":[{"underline-offset":[K,"auto",et,U]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:j()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",et,U]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",et,U]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:I()}],"bg-repeat":[{bg:Z()}],"bg-size":[{bg:A()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},S,et,U],radial:["",et,U],conic:[S,et,U]},ei,er]}],"bg-color":[{bg:T()}],"gradient-from-pos":[{from:B()}],"gradient-via-pos":[{via:B()}],"gradient-to-pos":[{to:B()}],"gradient-from":[{from:T()}],"gradient-via":[{via:T()}],"gradient-to":[{to:T()}],rounded:[{rounded:D()}],"rounded-s":[{"rounded-s":D()}],"rounded-e":[{"rounded-e":D()}],"rounded-t":[{"rounded-t":D()}],"rounded-r":[{"rounded-r":D()}],"rounded-b":[{"rounded-b":D()}],"rounded-l":[{"rounded-l":D()}],"rounded-ss":[{"rounded-ss":D()}],"rounded-se":[{"rounded-se":D()}],"rounded-ee":[{"rounded-ee":D()}],"rounded-es":[{"rounded-es":D()}],"rounded-tl":[{"rounded-tl":D()}],"rounded-tr":[{"rounded-tr":D()}],"rounded-br":[{"rounded-br":D()}],"rounded-bl":[{"rounded-bl":D()}],"border-w":[{border:F()}],"border-w-x":[{"border-x":F()}],"border-w-y":[{"border-y":F()}],"border-w-s":[{"border-s":F()}],"border-w-e":[{"border-e":F()}],"border-w-t":[{"border-t":F()}],"border-w-r":[{"border-r":F()}],"border-w-b":[{"border-b":F()}],"border-w-l":[{"border-l":F()}],"divide-x":[{"divide-x":F()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":F()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...J(),"hidden","none"]}],"divide-style":[{divide:[...J(),"hidden","none"]}],"border-color":[{border:T()}],"border-color-x":[{"border-x":T()}],"border-color-y":[{"border-y":T()}],"border-color-s":[{"border-s":T()}],"border-color-e":[{"border-e":T()}],"border-color-t":[{"border-t":T()}],"border-color-r":[{"border-r":T()}],"border-color-b":[{"border-b":T()}],"border-color-l":[{"border-l":T()}],"divide-color":[{divide:T()}],"outline-style":[{outline:[...J(),"none","hidden"]}],"outline-offset":[{"outline-offset":[K,et,U]}],"outline-w":[{outline:["",K,el,X]}],"outline-color":[{outline:T()}],shadow:[{shadow:["","none",c,ed,eo]}],"shadow-color":[{shadow:T()}],"inset-shadow":[{"inset-shadow":["none",m,ed,eo]}],"inset-shadow-color":[{"inset-shadow":T()}],"ring-w":[{ring:F()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:T()}],"ring-offset-w":[{"ring-offset":[K,X]}],"ring-offset-color":[{"ring-offset":T()}],"inset-ring-w":[{"inset-ring":F()}],"inset-ring-color":[{"inset-ring":T()}],"text-shadow":[{"text-shadow":["none",p,ed,eo]}],"text-shadow-color":[{"text-shadow":T()}],opacity:[{opacity:[K,et,U]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[K]}],"mask-image-linear-from-pos":[{"mask-linear-from":em()}],"mask-image-linear-to-pos":[{"mask-linear-to":em()}],"mask-image-linear-from-color":[{"mask-linear-from":T()}],"mask-image-linear-to-color":[{"mask-linear-to":T()}],"mask-image-t-from-pos":[{"mask-t-from":em()}],"mask-image-t-to-pos":[{"mask-t-to":em()}],"mask-image-t-from-color":[{"mask-t-from":T()}],"mask-image-t-to-color":[{"mask-t-to":T()}],"mask-image-r-from-pos":[{"mask-r-from":em()}],"mask-image-r-to-pos":[{"mask-r-to":em()}],"mask-image-r-from-color":[{"mask-r-from":T()}],"mask-image-r-to-color":[{"mask-r-to":T()}],"mask-image-b-from-pos":[{"mask-b-from":em()}],"mask-image-b-to-pos":[{"mask-b-to":em()}],"mask-image-b-from-color":[{"mask-b-from":T()}],"mask-image-b-to-color":[{"mask-b-to":T()}],"mask-image-l-from-pos":[{"mask-l-from":em()}],"mask-image-l-to-pos":[{"mask-l-to":em()}],"mask-image-l-from-color":[{"mask-l-from":T()}],"mask-image-l-to-color":[{"mask-l-to":T()}],"mask-image-x-from-pos":[{"mask-x-from":em()}],"mask-image-x-to-pos":[{"mask-x-to":em()}],"mask-image-x-from-color":[{"mask-x-from":T()}],"mask-image-x-to-color":[{"mask-x-to":T()}],"mask-image-y-from-pos":[{"mask-y-from":em()}],"mask-image-y-to-pos":[{"mask-y-to":em()}],"mask-image-y-from-color":[{"mask-y-from":T()}],"mask-image-y-to-color":[{"mask-y-to":T()}],"mask-image-radial":[{"mask-radial":[et,U]}],"mask-image-radial-from-pos":[{"mask-radial-from":em()}],"mask-image-radial-to-pos":[{"mask-radial-to":em()}],"mask-image-radial-from-color":[{"mask-radial-from":T()}],"mask-image-radial-to-color":[{"mask-radial-to":T()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":x()}],"mask-image-conic-pos":[{"mask-conic":[K]}],"mask-image-conic-from-pos":[{"mask-conic-from":em()}],"mask-image-conic-to-pos":[{"mask-conic-to":em()}],"mask-image-conic-from-color":[{"mask-conic-from":T()}],"mask-image-conic-to-color":[{"mask-conic-to":T()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:I()}],"mask-repeat":[{mask:Z()}],"mask-size":[{mask:A()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",et,U]}],filter:[{filter:["","none",et,U]}],blur:[{blur:ep()}],brightness:[{brightness:[K,et,U]}],contrast:[{contrast:[K,et,U]}],"drop-shadow":[{"drop-shadow":["","none",u,ed,eo]}],"drop-shadow-color":[{"drop-shadow":T()}],grayscale:[{grayscale:["",K,et,U]}],"hue-rotate":[{"hue-rotate":[K,et,U]}],invert:[{invert:["",K,et,U]}],saturate:[{saturate:[K,et,U]}],sepia:[{sepia:["",K,et,U]}],"backdrop-filter":[{"backdrop-filter":["","none",et,U]}],"backdrop-blur":[{"backdrop-blur":ep()}],"backdrop-brightness":[{"backdrop-brightness":[K,et,U]}],"backdrop-contrast":[{"backdrop-contrast":[K,et,U]}],"backdrop-grayscale":[{"backdrop-grayscale":["",K,et,U]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[K,et,U]}],"backdrop-invert":[{"backdrop-invert":["",K,et,U]}],"backdrop-opacity":[{"backdrop-opacity":[K,et,U]}],"backdrop-saturate":[{"backdrop-saturate":[K,et,U]}],"backdrop-sepia":[{"backdrop-sepia":["",K,et,U]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":j()}],"border-spacing-x":[{"border-spacing-x":j()}],"border-spacing-y":[{"border-spacing-y":j()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",et,U]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[K,"initial",et,U]}],ease:[{ease:["linear","initial",h,et,U]}],delay:[{delay:[K,et,U]}],animate:[{animate:["none",k,et,U]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[f,et,U]}],"perspective-origin":[{"perspective-origin":w()}],rotate:[{rotate:eu()}],"rotate-x":[{"rotate-x":eu()}],"rotate-y":[{"rotate-y":eu()}],"rotate-z":[{"rotate-z":eu()}],scale:[{scale:eb()}],"scale-x":[{"scale-x":eb()}],"scale-y":[{"scale-y":eb()}],"scale-z":[{"scale-z":eb()}],"scale-3d":["scale-3d"],skew:[{skew:ef()}],"skew-x":[{"skew-x":ef()}],"skew-y":[{"skew-y":ef()}],transform:[{transform:[et,U,"","none","gpu","cpu"]}],"transform-origin":[{origin:w()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:eg()}],"translate-x":[{"translate-x":eg()}],"translate-y":[{"translate-y":eg()}],"translate-z":[{"translate-z":eg()}],"translate-none":["translate-none"],accent:[{accent:T()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:T()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",et,U]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":j()}],"scroll-mx":[{"scroll-mx":j()}],"scroll-my":[{"scroll-my":j()}],"scroll-ms":[{"scroll-ms":j()}],"scroll-me":[{"scroll-me":j()}],"scroll-mt":[{"scroll-mt":j()}],"scroll-mr":[{"scroll-mr":j()}],"scroll-mb":[{"scroll-mb":j()}],"scroll-ml":[{"scroll-ml":j()}],"scroll-p":[{"scroll-p":j()}],"scroll-px":[{"scroll-px":j()}],"scroll-py":[{"scroll-py":j()}],"scroll-ps":[{"scroll-ps":j()}],"scroll-pe":[{"scroll-pe":j()}],"scroll-pt":[{"scroll-pt":j()}],"scroll-pr":[{"scroll-pr":j()}],"scroll-pb":[{"scroll-pb":j()}],"scroll-pl":[{"scroll-pl":j()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",et,U]}],fill:[{fill:["none",...T()]}],"stroke-w":[{stroke:[K,el,X,Y]}],stroke:[{stroke:["none",...T()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})}}]); \ No newline at end of file +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3250],{49634:function(e,r,o){o.d(r,{Z:function(){return s}});var t=o(1119),l=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"}}]},name:"calendar",theme:"outlined"},n=o(55015),s=l.forwardRef(function(e,r){return l.createElement(n.Z,(0,t.Z)({},e,{ref:r,icon:a}))})},94789:function(e,r,o){o.d(r,{Z:function(){return d}});var t=o(5853),l=o(2265),a=o(26898),n=o(13241),s=o(1153);let i=(0,s.fn)("Callout"),d=l.forwardRef((e,r)=>{let{title:o,icon:d,color:c,className:m,children:p}=e,u=(0,t._T)(e,["title","icon","color","className","children"]);return l.createElement("div",Object.assign({ref:r,className:(0,n.q)(i("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",c?(0,n.q)((0,s.bM)(c,a.K.background).bgColor,(0,s.bM)(c,a.K.darkBorder).borderColor,(0,s.bM)(c,a.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,n.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),m)},u),l.createElement("div",{className:(0,n.q)(i("header"),"flex items-start")},d?l.createElement(d,{className:(0,n.q)(i("icon"),"flex-none h-5 w-5 mr-1.5")}):null,l.createElement("h4",{className:(0,n.q)(i("title"),"font-semibold")},o)),l.createElement("p",{className:(0,n.q)(i("body"),"overflow-y-auto",p?"mt-2":"")},p))});d.displayName="Callout"},35829:function(e,r,o){o.d(r,{Z:function(){return i}});var t=o(5853),l=o(26898),a=o(13241),n=o(1153),s=o(2265);let i=s.forwardRef((e,r)=>{let{color:o,children:i,className:d}=e,c=(0,t._T)(e,["color","children","className"]);return s.createElement("p",Object.assign({ref:r,className:(0,a.q)("font-semibold text-tremor-metric",o?(0,n.bM)(o,l.K.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",d)},c),i)});i.displayName="Metric"},49096:function(e,r,o){o.d(r,{ZD:function(){return a}});var t=o(87602);let l=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,a=e=>{let r=function(){for(var r,o,l=arguments.length,a=Array(l),n=0;n{let t=Object.fromEntries(Object.entries(e||{}).filter(e=>{let[r]=e;return!["class","className"].includes(r)}));return r(o.map(e=>e(t)),null==e?void 0:e.class,null==e?void 0:e.className)}},cva:e=>o=>{var t;if((null==e?void 0:e.variants)==null)return r(null==e?void 0:e.base,null==o?void 0:o.class,null==o?void 0:o.className);let{variants:a,defaultVariants:n}=e,s=Object.keys(a).map(e=>{let r=null==o?void 0:o[e],t=null==n?void 0:n[e],s=l(r)||l(t);return a[e][s]}),i={...n,...o&&Object.entries(o).reduce((e,r)=>{let[o,t]=r;return void 0===t?e:{...e,[o]:t}},{})},d=null==e?void 0:null===(t=e.compoundVariants)||void 0===t?void 0:t.reduce((e,r)=>{let{class:o,className:t,...l}=r;return Object.entries(l).every(e=>{let[r,o]=e,t=i[r];return Array.isArray(o)?o.includes(t):t===o})?[...e,o,t]:e},[]);return r(null==e?void 0:e.base,s,d,null==o?void 0:o.class,null==o?void 0:o.className)},cx:r}},{compose:n,cva:s,cx:i}=a()},53335:function(e,r,o){o.d(r,{m6:function(){return ev}});let t=(e,r)=>{let o=Array(e.length+r.length);for(let r=0;r({classGroupId:e,validator:r}),a=(e=new Map,r=null,o)=>({nextPart:e,validators:r,classGroupId:o}),n=[],s=e=>{let r=c(e),{conflictingClassGroups:o,conflictingClassGroupModifiers:l}=e;return{getClassGroupId:e=>{if(e.startsWith("[")&&e.endsWith("]"))return d(e);let o=e.split("-"),t=""===o[0]&&o.length>1?1:0;return i(o,t,r)},getConflictingClassGroupIds:(e,r)=>{if(r){let r=l[e],a=o[e];return r?a?t(a,r):r:a||n}return o[e]||n}}},i=(e,r,o)=>{if(0==e.length-r)return o.classGroupId;let t=e[r],l=o.nextPart.get(t);if(l){let o=i(e,r+1,l);if(o)return o}let a=o.validators;if(null===a)return;let n=0===r?e.join("-"):e.slice(r).join("-"),s=a.length;for(let e=0;e-1===e.slice(1,-1).indexOf(":")?void 0:(()=>{let r=e.slice(1,-1),o=r.indexOf(":"),t=r.slice(0,o);return t?"arbitrary.."+t:void 0})(),c=e=>{let{theme:r,classGroups:o}=e;return m(o,r)},m=(e,r)=>{let o=a();for(let t in e)p(e[t],o,t,r);return o},p=(e,r,o,t)=>{let l=e.length;for(let a=0;a{if("string"==typeof e){b(e,r,o);return}if("function"==typeof e){f(e,r,o,t);return}g(e,r,o,t)},b=(e,r,o)=>{(""===e?r:h(r,e)).classGroupId=o},f=(e,r,o,t)=>{if(k(e)){p(e(t),r,o,t);return}null===r.validators&&(r.validators=[]),r.validators.push(l(o,e))},g=(e,r,o,t)=>{let l=Object.entries(e),a=l.length;for(let e=0;e{let o=e,t=r.split("-"),l=t.length;for(let e=0;e"isThemeGetter"in e&&!0===e.isThemeGetter,v=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let r=0,o=Object.create(null),t=Object.create(null),l=(l,a)=>{o[l]=a,++r>e&&(r=0,t=o,o=Object.create(null))};return{get(e){let r=o[e];return void 0!==r?r:void 0!==(r=t[e])?(l(e,r),r):void 0},set(e,r){e in o?o[e]=r:l(e,r)}}},x=[],w=(e,r,o,t,l)=>({modifiers:e,hasImportantModifier:r,baseClassName:o,maybePostfixModifierPosition:t,isExternal:l}),y=e=>{let{prefix:r,experimentalParseClassName:o}=e,t=e=>{let r;let o=[],t=0,l=0,a=0,n=e.length;for(let s=0;sa?r-a:void 0)};if(r){let e=r+":",o=t;t=r=>r.startsWith(e)?o(r.slice(e.length)):w(x,!1,r,void 0,!0)}if(o){let e=t;t=r=>o({className:r,parseClassName:e})}return t},z=e=>{let r=new Map;return e.orderSensitiveModifiers.forEach((e,o)=>{r.set(e,1e6+o)}),e=>{let o=[],t=[];for(let l=0;l0&&(t.sort(),o.push(...t),t=[]),o.push(a)):t.push(a)}return t.length>0&&(t.sort(),o.push(...t)),o}},j=e=>({cache:v(e.cacheSize),parseClassName:y(e),sortModifiers:z(e),...s(e)}),N=/\s+/,C=(e,r)=>{let{parseClassName:o,getClassGroupId:t,getConflictingClassGroupIds:l,sortModifiers:a}=r,n=[],s=e.trim().split(N),i="";for(let e=s.length-1;e>=0;e-=1){let r=s[e],{isExternal:d,modifiers:c,hasImportantModifier:m,baseClassName:p,maybePostfixModifierPosition:u}=o(r);if(d){i=r+(i.length>0?" "+i:i);continue}let b=!!u,f=t(b?p.substring(0,u):p);if(!f){if(!b||!(f=t(p))){i=r+(i.length>0?" "+i:i);continue}b=!1}let g=0===c.length?"":1===c.length?c[0]:a(c).join(":"),h=m?g+"!":g,k=h+f;if(n.indexOf(k)>-1)continue;n.push(k);let v=l(f,b);for(let e=0;e0?" "+i:i)}return i},O=(...e)=>{let r,o,t=0,l="";for(;t{let r;if("string"==typeof e)return e;let o="";for(let t=0;t{let r=r=>r[e]||E;return r.isThemeGetter=!0,r},q=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,W=/^\((?:(\w[\w-]*):)?(.+)\)$/i,$=/^\d+\/\d+$/,_=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,T=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,I=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,Z=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,A=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,H=e=>$.test(e),K=e=>!!e&&!Number.isNaN(Number(e)),S=e=>!!e&&Number.isInteger(Number(e)),V=e=>e.endsWith("%")&&K(e.slice(0,-1)),P=e=>_.test(e),R=()=>!0,B=e=>T.test(e)&&!I.test(e),D=()=>!1,F=e=>Z.test(e),J=e=>A.test(e),L=e=>!U(e)&&!et(e),Q=e=>ec(e,eb,D),U=e=>q.test(e),X=e=>ec(e,ef,B),Y=e=>ec(e,eg,K),ee=e=>ec(e,ep,D),er=e=>ec(e,eu,J),eo=e=>ec(e,ek,F),et=e=>W.test(e),el=e=>em(e,ef),ea=e=>em(e,eh),en=e=>em(e,ep),es=e=>em(e,eb),ei=e=>em(e,eu),ed=e=>em(e,ek,!0),ec=(e,r,o)=>{let t=q.exec(e);return!!t&&(t[1]?r(t[1]):o(t[2]))},em=(e,r,o=!1)=>{let t=W.exec(e);return!!t&&(t[1]?r(t[1]):o)},ep=e=>"position"===e||"percentage"===e,eu=e=>"image"===e||"url"===e,eb=e=>"length"===e||"size"===e||"bg-size"===e,ef=e=>"length"===e,eg=e=>"number"===e,eh=e=>"family-name"===e,ek=e=>"shadow"===e,ev=((e,...r)=>{let o,t,l,a;let n=e=>{let r=t(e);if(r)return r;let a=C(e,o);return l(e,a),a};return a=s=>(t=(o=j(r.reduce((e,r)=>r(e),e()))).cache.get,l=o.cache.set,a=n,n(s)),(...e)=>a(O(...e))})(()=>{let e=G("color"),r=G("font"),o=G("text"),t=G("font-weight"),l=G("tracking"),a=G("leading"),n=G("breakpoint"),s=G("container"),i=G("spacing"),d=G("radius"),c=G("shadow"),m=G("inset-shadow"),p=G("text-shadow"),u=G("drop-shadow"),b=G("blur"),f=G("perspective"),g=G("aspect"),h=G("ease"),k=G("animate"),v=()=>["auto","avoid","all","avoid-page","page","left","right","column"],x=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],w=()=>[...x(),et,U],y=()=>["auto","hidden","clip","visible","scroll"],z=()=>["auto","contain","none"],j=()=>[et,U,i],N=()=>[H,"full","auto",...j()],C=()=>[S,"none","subgrid",et,U],O=()=>["auto",{span:["full",S,et,U]},S,et,U],M=()=>[S,"auto",et,U],E=()=>["auto","min","max","fr",et,U],q=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],W=()=>["start","end","center","stretch","center-safe","end-safe"],$=()=>["auto",...j()],_=()=>[H,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...j()],T=()=>[e,et,U],I=()=>[...x(),en,ee,{position:[et,U]}],Z=()=>["no-repeat",{repeat:["","x","y","space","round"]}],A=()=>["auto","cover","contain",es,Q,{size:[et,U]}],B=()=>[V,el,X],D=()=>["","none","full",d,et,U],F=()=>["",K,el,X],J=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],em=()=>[K,V,en,ee],ep=()=>["","none",b,et,U],eu=()=>["none",K,et,U],eb=()=>["none",K,et,U],ef=()=>[K,et,U],eg=()=>[H,"full",...j()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[P],breakpoint:[P],color:[R],container:[P],"drop-shadow":[P],ease:["in","out","in-out"],font:[L],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[P],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[P],shadow:[P],spacing:["px",K],text:[P],"text-shadow":[P],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",H,U,et,g]}],container:["container"],columns:[{columns:[K,U,et,s]}],"break-after":[{"break-after":v()}],"break-before":[{"break-before":v()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:w()}],overflow:[{overflow:y()}],"overflow-x":[{"overflow-x":y()}],"overflow-y":[{"overflow-y":y()}],overscroll:[{overscroll:z()}],"overscroll-x":[{"overscroll-x":z()}],"overscroll-y":[{"overscroll-y":z()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:N()}],"inset-x":[{"inset-x":N()}],"inset-y":[{"inset-y":N()}],start:[{start:N()}],end:[{end:N()}],top:[{top:N()}],right:[{right:N()}],bottom:[{bottom:N()}],left:[{left:N()}],visibility:["visible","invisible","collapse"],z:[{z:[S,"auto",et,U]}],basis:[{basis:[H,"full","auto",s,...j()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[K,H,"auto","initial","none",U]}],grow:[{grow:["",K,et,U]}],shrink:[{shrink:["",K,et,U]}],order:[{order:[S,"first","last","none",et,U]}],"grid-cols":[{"grid-cols":C()}],"col-start-end":[{col:O()}],"col-start":[{"col-start":M()}],"col-end":[{"col-end":M()}],"grid-rows":[{"grid-rows":C()}],"row-start-end":[{row:O()}],"row-start":[{"row-start":M()}],"row-end":[{"row-end":M()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":E()}],"auto-rows":[{"auto-rows":E()}],gap:[{gap:j()}],"gap-x":[{"gap-x":j()}],"gap-y":[{"gap-y":j()}],"justify-content":[{justify:[...q(),"normal"]}],"justify-items":[{"justify-items":[...W(),"normal"]}],"justify-self":[{"justify-self":["auto",...W()]}],"align-content":[{content:["normal",...q()]}],"align-items":[{items:[...W(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...W(),{baseline:["","last"]}]}],"place-content":[{"place-content":q()}],"place-items":[{"place-items":[...W(),"baseline"]}],"place-self":[{"place-self":["auto",...W()]}],p:[{p:j()}],px:[{px:j()}],py:[{py:j()}],ps:[{ps:j()}],pe:[{pe:j()}],pt:[{pt:j()}],pr:[{pr:j()}],pb:[{pb:j()}],pl:[{pl:j()}],m:[{m:$()}],mx:[{mx:$()}],my:[{my:$()}],ms:[{ms:$()}],me:[{me:$()}],mt:[{mt:$()}],mr:[{mr:$()}],mb:[{mb:$()}],ml:[{ml:$()}],"space-x":[{"space-x":j()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":j()}],"space-y-reverse":["space-y-reverse"],size:[{size:_()}],w:[{w:[s,"screen",..._()]}],"min-w":[{"min-w":[s,"screen","none",..._()]}],"max-w":[{"max-w":[s,"screen","none","prose",{screen:[n]},..._()]}],h:[{h:["screen","lh",..._()]}],"min-h":[{"min-h":["screen","lh","none",..._()]}],"max-h":[{"max-h":["screen","lh",..._()]}],"font-size":[{text:["base",o,el,X]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[t,et,Y]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",V,U]}],"font-family":[{font:[ea,U,r]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[l,et,U]}],"line-clamp":[{"line-clamp":[K,"none",et,Y]}],leading:[{leading:[a,...j()]}],"list-image":[{"list-image":["none",et,U]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",et,U]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:T()}],"text-color":[{text:T()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...J(),"wavy"]}],"text-decoration-thickness":[{decoration:[K,"from-font","auto",et,X]}],"text-decoration-color":[{decoration:T()}],"underline-offset":[{"underline-offset":[K,"auto",et,U]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:j()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",et,U]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",et,U]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:I()}],"bg-repeat":[{bg:Z()}],"bg-size":[{bg:A()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},S,et,U],radial:["",et,U],conic:[S,et,U]},ei,er]}],"bg-color":[{bg:T()}],"gradient-from-pos":[{from:B()}],"gradient-via-pos":[{via:B()}],"gradient-to-pos":[{to:B()}],"gradient-from":[{from:T()}],"gradient-via":[{via:T()}],"gradient-to":[{to:T()}],rounded:[{rounded:D()}],"rounded-s":[{"rounded-s":D()}],"rounded-e":[{"rounded-e":D()}],"rounded-t":[{"rounded-t":D()}],"rounded-r":[{"rounded-r":D()}],"rounded-b":[{"rounded-b":D()}],"rounded-l":[{"rounded-l":D()}],"rounded-ss":[{"rounded-ss":D()}],"rounded-se":[{"rounded-se":D()}],"rounded-ee":[{"rounded-ee":D()}],"rounded-es":[{"rounded-es":D()}],"rounded-tl":[{"rounded-tl":D()}],"rounded-tr":[{"rounded-tr":D()}],"rounded-br":[{"rounded-br":D()}],"rounded-bl":[{"rounded-bl":D()}],"border-w":[{border:F()}],"border-w-x":[{"border-x":F()}],"border-w-y":[{"border-y":F()}],"border-w-s":[{"border-s":F()}],"border-w-e":[{"border-e":F()}],"border-w-t":[{"border-t":F()}],"border-w-r":[{"border-r":F()}],"border-w-b":[{"border-b":F()}],"border-w-l":[{"border-l":F()}],"divide-x":[{"divide-x":F()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":F()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...J(),"hidden","none"]}],"divide-style":[{divide:[...J(),"hidden","none"]}],"border-color":[{border:T()}],"border-color-x":[{"border-x":T()}],"border-color-y":[{"border-y":T()}],"border-color-s":[{"border-s":T()}],"border-color-e":[{"border-e":T()}],"border-color-t":[{"border-t":T()}],"border-color-r":[{"border-r":T()}],"border-color-b":[{"border-b":T()}],"border-color-l":[{"border-l":T()}],"divide-color":[{divide:T()}],"outline-style":[{outline:[...J(),"none","hidden"]}],"outline-offset":[{"outline-offset":[K,et,U]}],"outline-w":[{outline:["",K,el,X]}],"outline-color":[{outline:T()}],shadow:[{shadow:["","none",c,ed,eo]}],"shadow-color":[{shadow:T()}],"inset-shadow":[{"inset-shadow":["none",m,ed,eo]}],"inset-shadow-color":[{"inset-shadow":T()}],"ring-w":[{ring:F()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:T()}],"ring-offset-w":[{"ring-offset":[K,X]}],"ring-offset-color":[{"ring-offset":T()}],"inset-ring-w":[{"inset-ring":F()}],"inset-ring-color":[{"inset-ring":T()}],"text-shadow":[{"text-shadow":["none",p,ed,eo]}],"text-shadow-color":[{"text-shadow":T()}],opacity:[{opacity:[K,et,U]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[K]}],"mask-image-linear-from-pos":[{"mask-linear-from":em()}],"mask-image-linear-to-pos":[{"mask-linear-to":em()}],"mask-image-linear-from-color":[{"mask-linear-from":T()}],"mask-image-linear-to-color":[{"mask-linear-to":T()}],"mask-image-t-from-pos":[{"mask-t-from":em()}],"mask-image-t-to-pos":[{"mask-t-to":em()}],"mask-image-t-from-color":[{"mask-t-from":T()}],"mask-image-t-to-color":[{"mask-t-to":T()}],"mask-image-r-from-pos":[{"mask-r-from":em()}],"mask-image-r-to-pos":[{"mask-r-to":em()}],"mask-image-r-from-color":[{"mask-r-from":T()}],"mask-image-r-to-color":[{"mask-r-to":T()}],"mask-image-b-from-pos":[{"mask-b-from":em()}],"mask-image-b-to-pos":[{"mask-b-to":em()}],"mask-image-b-from-color":[{"mask-b-from":T()}],"mask-image-b-to-color":[{"mask-b-to":T()}],"mask-image-l-from-pos":[{"mask-l-from":em()}],"mask-image-l-to-pos":[{"mask-l-to":em()}],"mask-image-l-from-color":[{"mask-l-from":T()}],"mask-image-l-to-color":[{"mask-l-to":T()}],"mask-image-x-from-pos":[{"mask-x-from":em()}],"mask-image-x-to-pos":[{"mask-x-to":em()}],"mask-image-x-from-color":[{"mask-x-from":T()}],"mask-image-x-to-color":[{"mask-x-to":T()}],"mask-image-y-from-pos":[{"mask-y-from":em()}],"mask-image-y-to-pos":[{"mask-y-to":em()}],"mask-image-y-from-color":[{"mask-y-from":T()}],"mask-image-y-to-color":[{"mask-y-to":T()}],"mask-image-radial":[{"mask-radial":[et,U]}],"mask-image-radial-from-pos":[{"mask-radial-from":em()}],"mask-image-radial-to-pos":[{"mask-radial-to":em()}],"mask-image-radial-from-color":[{"mask-radial-from":T()}],"mask-image-radial-to-color":[{"mask-radial-to":T()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":x()}],"mask-image-conic-pos":[{"mask-conic":[K]}],"mask-image-conic-from-pos":[{"mask-conic-from":em()}],"mask-image-conic-to-pos":[{"mask-conic-to":em()}],"mask-image-conic-from-color":[{"mask-conic-from":T()}],"mask-image-conic-to-color":[{"mask-conic-to":T()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:I()}],"mask-repeat":[{mask:Z()}],"mask-size":[{mask:A()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",et,U]}],filter:[{filter:["","none",et,U]}],blur:[{blur:ep()}],brightness:[{brightness:[K,et,U]}],contrast:[{contrast:[K,et,U]}],"drop-shadow":[{"drop-shadow":["","none",u,ed,eo]}],"drop-shadow-color":[{"drop-shadow":T()}],grayscale:[{grayscale:["",K,et,U]}],"hue-rotate":[{"hue-rotate":[K,et,U]}],invert:[{invert:["",K,et,U]}],saturate:[{saturate:[K,et,U]}],sepia:[{sepia:["",K,et,U]}],"backdrop-filter":[{"backdrop-filter":["","none",et,U]}],"backdrop-blur":[{"backdrop-blur":ep()}],"backdrop-brightness":[{"backdrop-brightness":[K,et,U]}],"backdrop-contrast":[{"backdrop-contrast":[K,et,U]}],"backdrop-grayscale":[{"backdrop-grayscale":["",K,et,U]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[K,et,U]}],"backdrop-invert":[{"backdrop-invert":["",K,et,U]}],"backdrop-opacity":[{"backdrop-opacity":[K,et,U]}],"backdrop-saturate":[{"backdrop-saturate":[K,et,U]}],"backdrop-sepia":[{"backdrop-sepia":["",K,et,U]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":j()}],"border-spacing-x":[{"border-spacing-x":j()}],"border-spacing-y":[{"border-spacing-y":j()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",et,U]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[K,"initial",et,U]}],ease:[{ease:["linear","initial",h,et,U]}],delay:[{delay:[K,et,U]}],animate:[{animate:["none",k,et,U]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[f,et,U]}],"perspective-origin":[{"perspective-origin":w()}],rotate:[{rotate:eu()}],"rotate-x":[{"rotate-x":eu()}],"rotate-y":[{"rotate-y":eu()}],"rotate-z":[{"rotate-z":eu()}],scale:[{scale:eb()}],"scale-x":[{"scale-x":eb()}],"scale-y":[{"scale-y":eb()}],"scale-z":[{"scale-z":eb()}],"scale-3d":["scale-3d"],skew:[{skew:ef()}],"skew-x":[{"skew-x":ef()}],"skew-y":[{"skew-y":ef()}],transform:[{transform:[et,U,"","none","gpu","cpu"]}],"transform-origin":[{origin:w()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:eg()}],"translate-x":[{"translate-x":eg()}],"translate-y":[{"translate-y":eg()}],"translate-z":[{"translate-z":eg()}],"translate-none":["translate-none"],accent:[{accent:T()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:T()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",et,U]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":j()}],"scroll-mx":[{"scroll-mx":j()}],"scroll-my":[{"scroll-my":j()}],"scroll-ms":[{"scroll-ms":j()}],"scroll-me":[{"scroll-me":j()}],"scroll-mt":[{"scroll-mt":j()}],"scroll-mr":[{"scroll-mr":j()}],"scroll-mb":[{"scroll-mb":j()}],"scroll-ml":[{"scroll-ml":j()}],"scroll-p":[{"scroll-p":j()}],"scroll-px":[{"scroll-px":j()}],"scroll-py":[{"scroll-py":j()}],"scroll-ps":[{"scroll-ps":j()}],"scroll-pe":[{"scroll-pe":j()}],"scroll-pt":[{"scroll-pt":j()}],"scroll-pr":[{"scroll-pr":j()}],"scroll-pb":[{"scroll-pb":j()}],"scroll-pl":[{"scroll-pl":j()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",et,U]}],fill:[{fill:["none",...T()]}],"stroke-w":[{stroke:[K,el,X,Y]}],stroke:[{stroke:["none",...T()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js b/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js deleted file mode 100644 index 3b9478ef07f..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/3325-4a3c766c7d12465e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3325],{41649:function(e,r,t){t.d(r,{Z:function(){return f}});var n=t(5853),o=t(2265),a=t(47187),l=t(7084),i=t(26898),d=t(13241),s=t(1153);let c={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},u={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},m=(0,s.fn)("Badge"),f=o.forwardRef((e,r)=>{let{color:t,icon:f,size:p=l.u8.SM,tooltip:g,className:b,children:h}=e,k=(0,n._T)(e,["color","icon","size","tooltip","className","children"]),v=f||null,{tooltipProps:x,getReferenceProps:w}=(0,a.l)();return o.createElement("span",Object.assign({ref:(0,s.lq)([r,x.refs.setReference]),className:(0,d.q)(m("root"),"w-max shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-small ring-1 ring-inset",t?(0,d.q)((0,s.bM)(t,i.K.background).bgColor,(0,s.bM)(t,i.K.iconText).textColor,(0,s.bM)(t,i.K.iconRing).ringColor,"bg-opacity-10 ring-opacity-20","dark:bg-opacity-5 dark:ring-opacity-60"):(0,d.q)("bg-tremor-brand-faint text-tremor-brand-emphasis ring-tremor-brand/20","dark:bg-dark-tremor-brand-muted/50 dark:text-dark-tremor-brand dark:ring-dark-tremor-subtle/20"),c[p].paddingX,c[p].paddingY,c[p].fontSize,b)},w,k),o.createElement(a.Z,Object.assign({text:g},x)),v?o.createElement(v,{className:(0,d.q)(m("icon"),"shrink-0 -ml-1 mr-1.5",u[p].height,u[p].width)}):null,o.createElement("span",{className:(0,d.q)(m("text"),"whitespace-nowrap")},h))});f.displayName="Badge"},47323:function(e,r,t){t.d(r,{Z:function(){return g}});var n=t(5853),o=t(2265),a=t(47187),l=t(7084),i=t(13241),d=t(1153),s=t(26898);let c={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},m={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},f=(e,r)=>{switch(e){case"simple":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:r?(0,d.bM)(r,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:r?(0,i.q)((0,d.bM)(r,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:r?(0,d.bM)(r,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:r?(0,i.q)((0,d.bM)(r,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},p=(0,d.fn)("Icon"),g=o.forwardRef((e,r)=>{let{icon:t,variant:s="simple",tooltip:g,size:b=l.u8.SM,color:h,className:k}=e,v=(0,n._T)(e,["icon","variant","tooltip","size","color","className"]),x=f(s,h),{tooltipProps:w,getReferenceProps:C}=(0,a.l)();return o.createElement("span",Object.assign({ref:(0,d.lq)([r,w.refs.setReference]),className:(0,i.q)(p("root"),"inline-flex shrink-0 items-center justify-center",x.bgColor,x.textColor,x.borderColor,x.ringColor,m[s].rounded,m[s].border,m[s].shadow,m[s].ring,c[b].paddingX,c[b].paddingY,k)},C,v),o.createElement(a.Z,Object.assign({text:g},w)),o.createElement(t,{className:(0,i.q)(p("icon"),"shrink-0",u[b].height,u[b].width)}))});g.displayName="Icon"},59341:function(e,r,t){t.d(r,{Z:function(){return R}});var n=t(5853),o=t(71049),a=t(11323),l=t(2265),i=t(66797),d=t(40099),s=t(74275),c=t(59456),u=t(93980),m=t(65573),f=t(67561),p=t(87550),g=t(628),b=t(80281),h=t(31370),k=t(20131),v=t(38929),x=t(52307),w=t(52724),C=t(7935);let y=(0,l.createContext)(null);y.displayName="GroupContext";let E=l.Fragment,N=Object.assign((0,v.yV)(function(e,r){var t;let n=(0,l.useId)(),E=(0,b.Q)(),N=(0,p.B)(),{id:T=E||"headlessui-switch-".concat(n),disabled:M=N||!1,checked:S,defaultChecked:q,onChange:L,name:j,value:R,form:O,autoFocus:P=!1,...F}=e,z=(0,l.useContext)(y),[I,_]=(0,l.useState)(null),K=(0,l.useRef)(null),B=(0,f.T)(K,r,null===z?null:z.setSwitch,_),H=(0,s.L)(q),[Z,D]=(0,d.q)(S,L,null!=H&&H),Y=(0,c.G)(),[X,A]=(0,l.useState)(!1),G=(0,u.z)(()=>{A(!0),null==D||D(!Z),Y.nextFrame(()=>{A(!1)})}),U=(0,u.z)(e=>{if((0,h.P)(e.currentTarget))return e.preventDefault();e.preventDefault(),G()}),V=(0,u.z)(e=>{e.key===w.R.Space?(e.preventDefault(),G()):e.key===w.R.Enter&&(0,k.g)(e.currentTarget)}),$=(0,u.z)(e=>e.preventDefault()),Q=(0,C.wp)(),W=(0,x.zH)(),{isFocusVisible:J,focusProps:ee}=(0,o.F)({autoFocus:P}),{isHovered:er,hoverProps:et}=(0,a.X)({isDisabled:M}),{pressed:en,pressProps:eo}=(0,i.x)({disabled:M}),ea=(0,l.useMemo)(()=>({checked:Z,disabled:M,hover:er,focus:J,active:en,autofocus:P,changing:X}),[Z,er,J,en,M,X,P]),el=(0,v.dG)({id:T,ref:B,role:"switch",type:(0,m.f)(e,I),tabIndex:-1===e.tabIndex?0:null!=(t=e.tabIndex)?t:0,"aria-checked":Z,"aria-labelledby":Q,"aria-describedby":W,disabled:M||void 0,autoFocus:P,onClick:U,onKeyUp:V,onKeyPress:$},ee,et,eo),ei=(0,l.useCallback)(()=>{if(void 0!==H)return null==D?void 0:D(H)},[D,H]),ed=(0,v.L6)();return l.createElement(l.Fragment,null,null!=j&&l.createElement(g.Mt,{disabled:M,data:{[j]:R||"on"},overrides:{type:"checkbox",checked:Z},form:O,onReset:ei}),ed({ourProps:el,theirProps:F,slot:ea,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var r;let[t,n]=(0,l.useState)(null),[o,a]=(0,C.bE)(),[i,d]=(0,x.fw)(),s=(0,l.useMemo)(()=>({switch:t,setSwitch:n}),[t,n]),c=(0,v.L6)();return l.createElement(d,{name:"Switch.Description",value:i},l.createElement(a,{name:"Switch.Label",value:o,props:{htmlFor:null==(r=s.switch)?void 0:r.id,onClick(e){t&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),t.click(),t.focus({preventScroll:!0}))}}},l.createElement(y.Provider,{value:s},c({ourProps:{},theirProps:e,slot:{},defaultTag:E,name:"Switch.Group"}))))},Label:C.__,Description:x.dk});var T=t(44140),M=t(26898),S=t(13241),q=t(1153),L=t(47187);let j=(0,q.fn)("Switch"),R=l.forwardRef((e,r)=>{let{checked:t,defaultChecked:o=!1,onChange:a,color:i,name:d,error:s,errorMessage:c,disabled:u,required:m,tooltip:f,id:p}=e,g=(0,n._T)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),b={bgColor:i?(0,q.bM)(i,M.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:i?(0,q.bM)(i,M.K.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[h,k]=(0,T.Z)(o,t),[v,x]=(0,l.useState)(!1),{tooltipProps:w,getReferenceProps:C}=(0,L.l)(300);return l.createElement("div",{className:"flex flex-row items-center justify-start"},l.createElement(L.Z,Object.assign({text:f},w)),l.createElement("div",Object.assign({ref:(0,q.lq)([r,w.refs.setReference]),className:(0,S.q)(j("root"),"flex flex-row relative h-5")},g,C),l.createElement("input",{type:"checkbox",className:(0,S.q)(j("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:d,required:m,checked:h,onChange:e=>{e.preventDefault()}}),l.createElement(N,{checked:h,onChange:e=>{k(e),null==a||a(e)},disabled:u,className:(0,S.q)(j("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",u?"cursor-not-allowed":""),onFocus:()=>x(!0),onBlur:()=>x(!1),id:p},l.createElement("span",{className:(0,S.q)(j("sr-only"),"sr-only")},"Switch ",h?"on":"off"),l.createElement("span",{"aria-hidden":"true",className:(0,S.q)(j("background"),h?b.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),l.createElement("span",{"aria-hidden":"true",className:(0,S.q)(j("round"),h?(0,S.q)(b.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",v?(0,S.q)("ring-2",b.ringColor):"")}))),s&&c?l.createElement("p",{className:(0,S.q)(j("errorMessage"),"text-sm text-red-500 mt-1 ")},c):null)});R.displayName="Switch"},21626:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("Table"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement("div",{className:(0,a.q)(l("root"),"overflow-auto",i)},o.createElement("table",Object.assign({ref:r,className:(0,a.q)(l("table"),"w-full text-tremor-default","text-tremor-content","dark:text-dark-tremor-content")},d),t))});i.displayName="Table"},97214:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableBody"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("tbody",Object.assign({ref:r,className:(0,a.q)(l("root"),"align-top divide-y","divide-tremor-border","dark:divide-dark-tremor-border",i)},d),t))});i.displayName="TableBody"},28241:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableCell"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("td",Object.assign({ref:r,className:(0,a.q)(l("root"),"align-middle whitespace-nowrap text-left p-4",i)},d),t))});i.displayName="TableCell"},58834:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableHead"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("thead",Object.assign({ref:r,className:(0,a.q)(l("root"),"text-left","text-tremor-content","dark:text-dark-tremor-content",i)},d),t))});i.displayName="TableHead"},69552:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableHeaderCell"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("th",Object.assign({ref:r,className:(0,a.q)(l("root"),"whitespace-nowrap text-left font-semibold top-0 px-4 py-3.5","text-tremor-content-strong","dark:text-dark-tremor-content-strong",i)},d),t))});i.displayName="TableHeaderCell"},71876:function(e,r,t){t.d(r,{Z:function(){return i}});var n=t(5853),o=t(2265),a=t(13241);let l=(0,t(1153).fn)("TableRow"),i=o.forwardRef((e,r)=>{let{children:t,className:i}=e,d=(0,n._T)(e,["children","className"]);return o.createElement(o.Fragment,null,o.createElement("tr",Object.assign({ref:r,className:(0,a.q)(l("row"),i)},d),t))});i.displayName="TableRow"},44140:function(e,r,t){t.d(r,{Z:function(){return o}});var n=t(2265);let o=(e,r)=>{let t=void 0!==r,[o,a]=(0,n.useState)(e);return[t?r:o,e=>{t||a(e)}]}},44643:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=o},91126:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});r.Z=o},74998:function(e,r,t){var n=t(2265);let o=n.forwardRef(function(e,r){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});r.Z=o},52307:function(e,r,t){t.d(r,{dk:function(){return m},fw:function(){return u},zH:function(){return c}});var n=t(2265),o=t(93980),a=t(73389),l=t(67561),i=t(87550),d=t(38929);let s=(0,n.createContext)(null);function c(){var e,r;return null!=(r=null==(e=(0,n.useContext)(s))?void 0:e.value)?r:void 0}function u(){let[e,r]=(0,n.useState)([]);return[e.length>0?e.join(" "):void 0,(0,n.useMemo)(()=>function(e){let t=(0,o.z)(e=>(r(r=>[...r,e]),()=>r(r=>{let t=r.slice(),n=t.indexOf(e);return -1!==n&&t.splice(n,1),t}))),a=(0,n.useMemo)(()=>({register:t,slot:e.slot,name:e.name,props:e.props,value:e.value}),[t,e.slot,e.name,e.props,e.value]);return n.createElement(s.Provider,{value:a},e.children)},[r])]}s.displayName="DescriptionContext";let m=Object.assign((0,d.yV)(function(e,r){let t=(0,n.useId)(),o=(0,i.B)(),{id:c="headlessui-description-".concat(t),...u}=e,m=function e(){let r=(0,n.useContext)(s);if(null===r){let r=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(r,e),r}return r}(),f=(0,l.T)(r);(0,a.e)(()=>m.register(c),[c,m.register]);let p=o||!1,g=(0,n.useMemo)(()=>({...m.slot,disabled:p}),[m.slot,p]),b={ref:f,...m.props,id:c};return(0,d.L6)()({ourProps:b,theirProps:u,slot:g,defaultTag:"p",name:m.name||"Description"})}),{})},7935:function(e,r,t){t.d(r,{__:function(){return f},bE:function(){return m},wp:function(){return u}});var n=t(2265),o=t(93980),a=t(73389),l=t(67561),i=t(87550),d=t(80281),s=t(38929);let c=(0,n.createContext)(null);function u(e){var r,t,o;let a=null!=(t=null==(r=(0,n.useContext)(c))?void 0:r.value)?t:void 0;return(null!=(o=null==e?void 0:e.length)?o:0)>0?[a,...e].filter(Boolean).join(" "):a}function m(){let{inherit:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=u(),[t,a]=(0,n.useState)([]),l=e?[r,...t].filter(Boolean):t;return[l.length>0?l.join(" "):void 0,(0,n.useMemo)(()=>function(e){let r=(0,o.z)(e=>(a(r=>[...r,e]),()=>a(r=>{let t=r.slice(),n=t.indexOf(e);return -1!==n&&t.splice(n,1),t}))),t=(0,n.useMemo)(()=>({register:r,slot:e.slot,name:e.name,props:e.props,value:e.value}),[r,e.slot,e.name,e.props,e.value]);return n.createElement(c.Provider,{value:t},e.children)},[a])]}c.displayName="LabelContext";let f=Object.assign((0,s.yV)(function(e,r){var t;let u=(0,n.useId)(),m=function e(){let r=(0,n.useContext)(c);if(null===r){let r=Error("You used a
    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 l`z2)l`SgN#7i}u-K}Bg{{^?-@)rOA literal 0 HcmV?d00001 diff --git a/tests/litellm/llms/vertex_ai/text_to_speech/test_transformation.py b/tests/litellm/llms/vertex_ai/text_to_speech/test_transformation.py new file mode 100644 index 00000000000..9a5888e8506 --- /dev/null +++ b/tests/litellm/llms/vertex_ai/text_to_speech/test_transformation.py @@ -0,0 +1,190 @@ +import json +import os +import sys +from unittest.mock import MagicMock, Mock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.llms.vertex_ai.text_to_speech.transformation import ( + VertexAITextToSpeechConfig, +) + + +class TestVertexAITextToSpeechConfig: + """Tests for VertexAITextToSpeechConfig transformation""" + + def test_get_complete_url(self): + """Test that get_complete_url returns the correct Google Cloud TTS API URL""" + config = VertexAITextToSpeechConfig() + + url = config.get_complete_url( + model="vertex_ai/chirp", + api_base=None, + litellm_params={}, + ) + + assert url == "https://texttospeech.googleapis.com/v1/text:synthesize" + + def test_get_complete_url_with_custom_api_base(self): + """Test that get_complete_url uses custom api_base when provided""" + config = VertexAITextToSpeechConfig() + + custom_url = "https://custom-tts-endpoint.example.com/v1/synthesize" + url = config.get_complete_url( + model="vertex_ai/chirp", + api_base=custom_url, + litellm_params={}, + ) + + assert url == custom_url + + @patch.object(VertexAITextToSpeechConfig, "_ensure_access_token") + @patch.object(VertexAITextToSpeechConfig, "_get_token_and_url") + def test_transform_text_to_speech_request_body( + self, mock_get_token, mock_ensure_token + ): + """Test that transform_text_to_speech_request generates correct request body""" + # Mock authentication + mock_ensure_token.return_value = ("mock-token", "test-project") + mock_get_token.return_value = ("mock-token", "mock-url") + + config = VertexAITextToSpeechConfig() + + # Test with voice dict in litellm_params (as set by dispatch) + result = config.transform_text_to_speech_request( + model="vertex_ai/chirp", + input="Hello, this is a test", + voice=None, + optional_params={ + "vertex_voice_dict": { + "languageCode": "en-US", + "name": "en-US-Chirp3-HD-Charon", + } + }, + litellm_params={ + "vertex_credentials": None, + "vertex_project": "test-project", + "vertex_location": "us-central1", + }, + headers={}, + ) + + # Verify request body structure + assert "dict_body" in result + request_body = result["dict_body"] + + assert "input" in request_body + assert request_body["input"] == {"text": "Hello, this is a test"} + + assert "voice" in request_body + assert request_body["voice"]["languageCode"] == "en-US" + assert request_body["voice"]["name"] == "en-US-Chirp3-HD-Charon" + + assert "audioConfig" in request_body + + # Verify headers contain auth + assert "headers" in result + assert "Authorization" in result["headers"] + + def test_voice_mapping_openai_to_vertex(self): + """Test that OpenAI voice names are correctly mapped to Vertex AI voices""" + config = VertexAITextToSpeechConfig() + + # Test the _map_voice_to_vertex_format helper + voice_str, voice_dict = config._map_voice_to_vertex_format("alloy") + + assert voice_str == "alloy" + assert voice_dict is not None + assert voice_dict["name"] == "en-US-Studio-O" + assert voice_dict["languageCode"] == "en-US" + + def test_voice_mapping_vertex_voice_passthrough(self): + """Test that Vertex AI voice names are passed through directly""" + config = VertexAITextToSpeechConfig() + + # Test with a Chirp3 HD voice + voice_str, voice_dict = config._map_voice_to_vertex_format( + "en-US-Chirp3-HD-Charon" + ) + + assert voice_str == "en-US-Chirp3-HD-Charon" + assert voice_dict is not None + assert voice_dict["name"] == "en-US-Chirp3-HD-Charon" + assert voice_dict["languageCode"] == "en-US" + + def test_voice_mapping_dict_passthrough(self): + """Test that voice dict is passed through unchanged""" + config = VertexAITextToSpeechConfig() + + voice_input = { + "languageCode": "de-DE", + "name": "de-DE-Chirp3-HD-Charon", + } + voice_str, voice_dict = config._map_voice_to_vertex_format(voice_input) + + assert voice_str is None + assert voice_dict == voice_input + + +@patch("litellm.llms.custom_httpx.llm_http_handler.HTTPHandler.post") +@patch.object(VertexAITextToSpeechConfig, "_ensure_access_token") +@patch.object(VertexAITextToSpeechConfig, "_get_token_and_url") +def test_litellm_speech_vertex_ai_chirp(mock_get_token, mock_ensure_token, mock_post): + """ + Test that litellm.speech(model="vertex_ai/chirp") sends the correct URL and request body + """ + # Mock authentication + mock_ensure_token.return_value = ("mock-token", "test-project") + mock_get_token.return_value = ("mock-token", "mock-url") + + # Mock HTTP response + mock_response = Mock(spec=httpx.Response) + mock_response.content = b'{"audioContent": "SGVsbG8gV29ybGQ="}' # base64 encoded "Hello World" + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = {"audioContent": "SGVsbG8gV29ybGQ="} + mock_post.return_value = mock_response + + litellm.speech( + model="vertex_ai/chirp", + input="Hello, this is a test", + voice="en-US-Chirp3-HD-Charon", + vertex_project="test-project", + vertex_location="us-central1", + ) + + # Verify the HTTP call was made + mock_post.assert_called_once() + call_kwargs = mock_post.call_args.kwargs + + # Verify the URL is the Google Cloud TTS API + assert call_kwargs["url"] == "https://texttospeech.googleapis.com/v1/text:synthesize" + + # Verify request body structure + assert "data" in call_kwargs + request_body = json.loads(call_kwargs["data"]) + + # Verify input + assert "input" in request_body + assert request_body["input"] == {"text": "Hello, this is a test"} + + # Verify voice + assert "voice" in request_body + assert request_body["voice"]["name"] == "en-US-Chirp3-HD-Charon" + assert request_body["voice"]["languageCode"] == "en-US" + + # Verify audioConfig + assert "audioConfig" in request_body + + # Verify headers contain authorization + assert "headers" in call_kwargs + assert "Authorization" in call_kwargs["headers"] + assert call_kwargs["headers"]["Authorization"] == "Bearer mock-token" + + From 7fb2f4730b2fd0eef4269e1ce711b5dbfa2be88e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 2 Dec 2025 15:53:10 -0800 Subject: [PATCH 265/349] build: remove duplicate packages --- docker/Dockerfile.non_root | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 2dcb7cb4787..4616ff9be60 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -10,7 +10,17 @@ WORKDIR /app # Install build dependencies including Node.js for UI build USER root -RUN apk add --no-cache build-base bash nodejs npm \ +RUN apk add --no-cache \ + clang \ + llvm \ + lld \ + gcc \ + python3-dev \ + linux-headers \ + build-base \ + bash \ + nodejs \ + npm \ && pip install --no-cache-dir --upgrade pip build # Copy project files From 9867cc6cf98422bb29aa326ec75a1c5068facce1 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 2 Dec 2025 15:58:27 -0800 Subject: [PATCH 266/349] Change edit team models to match create team models --- .../src/components/team/team_info.test.tsx | 115 +++++++++++++++++- .../src/components/team/team_info.tsx | 101 +++++++++++++-- 2 files changed, 202 insertions(+), 14 deletions(-) diff --git a/ui/litellm-dashboard/src/components/team/team_info.test.tsx b/ui/litellm-dashboard/src/components/team/team_info.test.tsx index 54362193350..3a87b42d253 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.test.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.test.tsx @@ -13,6 +13,7 @@ vi.mock("@/components/networking", () => ({ getGuardrailsList: vi.fn(), fetchMCPAccessGroups: vi.fn(), getTeamPermissionsCall: vi.fn(), + organizationInfoCall: vi.fn(), })); describe("TeamInfoView", () => { @@ -161,6 +162,116 @@ describe("TeamInfoView", () => { const allProxyModelsOption = screen.queryByText("All Proxy Models"); expect(allProxyModelsOption).not.toBeInTheDocument(); - }, // This is a workaround to fix the flaky test issue. TODO: Remove this once we have a better solution. - 10000); + }, 10000); // This is a workaround to fix the flaky test issue. TODO: Remove this once we have a better solution. + + it("should only show organization models in dropdown when team is in organization with limited models", async () => { + const organizationId = "org-123"; + const organizationModels = ["gpt-4", "claude-3-opus"]; + const userModels = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus", "claude-2"]; + + vi.mocked(networking.teamInfoCall).mockResolvedValue({ + team_id: "123", + team_info: { + team_alias: "Test Team", + team_id: "123", + organization_id: organizationId, + admins: ["admin@test.com"], + members: ["user1@test.com"], + members_with_roles: [ + { + user_id: "user1@test.com", + user_email: "user1@test.com", + role: "member", + spend: 0, + budget_id: "budget1", + }, + ], + metadata: {}, + tpm_limit: null, + rpm_limit: null, + max_budget: null, + budget_duration: null, + models: ["gpt-4"], + blocked: false, + spend: 0, + max_parallel_requests: null, + budget_reset_at: null, + model_id: null, + litellm_model_table: null, + created_at: "2024-01-01T00:00:00Z", + team_member_budget_table: null, + }, + keys: [], + team_memberships: [], + }); + + vi.mocked(networking.organizationInfoCall).mockResolvedValue({ + organization_id: organizationId, + organization_name: "Test Organization", + spend: 0, + max_budget: null, + models: organizationModels, + tpm_limit: null, + rpm_limit: null, + members: null, + }); + + vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] }); + vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]); + + render( + {}} + onClose={() => {}} + accessToken="123" + is_team_admin={true} + is_proxy_admin={true} + userModels={userModels} + editTeam={false} + premiumUser={false} + />, + ); + + await waitFor(() => { + expect(screen.getAllByText("Test Team")).not.toBeNull(); + }); + + const settingsTab = screen.getByRole("tab", { name: "Settings" }); + act(() => { + fireEvent.click(settingsTab); + }); + + await waitFor(() => { + expect(screen.getByText("Team Settings")).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + act(() => { + fireEvent.click(editButton); + }); + + await waitFor(() => { + expect(screen.getByLabelText("Models")).toBeInTheDocument(); + }); + + const modelsSelect = screen.getByLabelText("Models"); + act(() => { + fireEvent.mouseDown(modelsSelect); + }); + + await waitFor(() => { + const dropdownOptions = screen.getAllByRole("option"); + const optionTexts = dropdownOptions.map((option) => option.textContent); + + organizationModels.forEach((model) => { + expect(optionTexts).toContain(model); + }); + + const modelsNotInOrganization = userModels.filter((m) => !organizationModels.includes(m)); + modelsNotInOrganization.forEach((model) => { + expect(optionTexts).not.toContain(model); + }); + }); + }, 10000); }); diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx index a53d35e57bb..581722e8fa7 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.tsx @@ -2,6 +2,8 @@ import UserSearchModal from "@/components/common_components/user_search_modal"; import { getGuardrailsList, Member, + Organization, + organizationInfoCall, teamInfoCall, teamMemberAddCall, teamMemberDeleteCall, @@ -28,11 +30,11 @@ import { } from "@tremor/react"; import { Button, Form, Input, message, Select, Switch, Tooltip } from "antd"; import { CheckIcon, CopyIcon } from "lucide-react"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils"; import DeleteResourceModal from "../common_components/DeleteResourceModal"; import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector"; -import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; +import { getModelDisplayName, unfurlWildcardModelsInList } from "../key_team_helpers/fetch_available_models_team_key"; import LoggingSettingsView from "../logging_settings_view"; import MCPServerSelector from "../mcp_server_management/MCPServerSelector"; import MCPToolPermissions from "../mcp_server_management/MCPToolPermissions"; @@ -117,6 +119,29 @@ export interface TeamInfoProps { premiumUser?: boolean; } +const getOrganizationModels = (organization: Organization | null, userModels: string[]) => { + let tempModelsToPick = []; + + if (organization) { + // Check if organization has "all-proxy-models" in its models array + if (organization.models.includes("all-proxy-models")) { + // Treat as all-proxy-models (use userModels) + tempModelsToPick = userModels; + } else if (organization.models.length > 0) { + // Organization has specific models + tempModelsToPick = organization.models; + } else { + // Empty array [] is treated as all-proxy-models + tempModelsToPick = userModels; + } + } else { + // No organization, show all available models + tempModelsToPick = userModels; + } + + return unfurlWildcardModelsInList(tempModelsToPick, userModels); +}; + const TeamInfoView: React.FC = ({ teamId, onClose, @@ -143,6 +168,7 @@ const TeamInfoView: React.FC = ({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isTeamSaving, setIsTeamSaving] = useState(false); + const [organization, setOrganization] = useState(null); console.log("userModels in team info", userModels); @@ -166,6 +192,31 @@ const TeamInfoView: React.FC = ({ fetchTeamInfo(); }, [teamId, accessToken]); + // Fetch organization data when team has organization_id + useEffect(() => { + const fetchOrganization = async () => { + if (!accessToken || !teamData?.team_info?.organization_id) { + setOrganization(null); + return; + } + + try { + const orgData = await organizationInfoCall(accessToken, teamData.team_info.organization_id); + setOrganization(orgData); + } catch (error) { + console.error("Error fetching organization info:", error); + setOrganization(null); + } + }; + + fetchOrganization(); + }, [accessToken, teamData?.team_info?.organization_id]); + + // Compute modelsToPick based on organization and userModels + const modelsToPick = useMemo(() => { + return getOrganizationModels(organization, userModels); + }, [organization, userModels]); + const fetchMcpAccessGroups = async () => { if (!accessToken) return; if (mcpAccessGroupsLoaded) return; @@ -596,15 +647,41 @@ const TeamInfoView: React.FC = ({ rules={[{ required: true, message: "Please select at least one model" }]} > + From 8ee298f9c9c178ccc7191ebfbf63736898557443 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 2 Dec 2025 16:06:06 -0800 Subject: [PATCH 267/349] fix: remove python3 headers --- docker/Dockerfile.non_root | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 4616ff9be60..8b66a367eeb 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -15,7 +15,6 @@ RUN apk add --no-cache \ llvm \ lld \ gcc \ - python3-dev \ linux-headers \ build-base \ bash \ From 10d56e7c4682c868c9c11483d21f77abc3d042f5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 2 Dec 2025 16:19:34 -0800 Subject: [PATCH 268/349] add new deepseek-v3p2 (#17395) --- litellm/model_prices_and_context_window_backup.json | 13 +++++++++++++ model_prices_and_context_window.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f3398e470d8..932508824af 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -10421,6 +10421,19 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/deepseek-v3p2": { + "input_cost_per_token": 1.2e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 1.2e-06, + "source": "https://fireworks.ai/models/fireworks/deepseek-v3p2", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "fireworks_ai/accounts/fireworks/models/firefunction-v2": { "input_cost_per_token": 9e-07, "litellm_provider": "fireworks_ai", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f3398e470d8..932508824af 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -10421,6 +10421,19 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/deepseek-v3p2": { + "input_cost_per_token": 1.2e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 1.2e-06, + "source": "https://fireworks.ai/models/fireworks/deepseek-v3p2", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "fireworks_ai/accounts/fireworks/models/firefunction-v2": { "input_cost_per_token": 9e-07, "litellm_provider": "fireworks_ai", From 31cad8e6e63a9de464c1dbc3d5635a7fcc4962f5 Mon Sep 17 00:00:00 2001 From: flozonn <74357383+flozonn@users.noreply.github.com> Date: Wed, 3 Dec 2025 01:33:07 +0100 Subject: [PATCH 269/349] feat: Add Nova lite 2 reasoning support with reasoningConfig (#17371) --- litellm/constants.py | 3 + .../bedrock/chat/converse_transformation.py | 102 ++- model_prices_and_context_window.json | 65 ++ .../chat/test_converse_transformation.py | 33 + .../test_converse_transformation_nova_2.py | 794 ++++++++++++++++++ 5 files changed, 995 insertions(+), 2 deletions(-) create mode 100644 tests/test_litellm/llms/bedrock/chat/test_converse_transformation_nova_2.py diff --git a/litellm/constants.py b/litellm/constants.py index 1d42ef9a910..6a67a9a0e18 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -909,6 +909,9 @@ BEDROCK_CONVERSE_MODELS = [ "meta.llama3-2-3b-instruct-v1:0", "meta.llama3-2-11b-instruct-v1:0", "meta.llama3-2-90b-instruct-v1:0", + "amazon.nova-lite-v1:0", + "amazon.nova-2-lite-v1:0", + "amazon.nova-pro-v1:0", ] diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 3b3a138ec67..705f3c9e630 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -246,6 +246,93 @@ class AmazonConverseConfig(BaseConfig): llm_provider="bedrock", ) + def _is_nova_lite_2_model(self, model: str) -> bool: + """ + Check if the model is a Nova Lite 2 model that supports reasoningConfig. + + Nova Lite 2 models use a different reasoning configuration structure compared to + Anthropic's thinking parameter and GPT-OSS's reasoning_effort parameter. + + Supported models: + - amazon.nova-2-lite-v1:0 + - us.amazon.nova-2-lite-v1:0 + - eu.amazon.nova-2-lite-v1:0 + - apac.amazon.nova-2-lite-v1:0 + + Args: + model: The model identifier + + Returns: + True if the model is a Nova Lite 2 model, False otherwise + + Examples: + >>> config = AmazonConverseConfig() + >>> config._is_nova_lite_2_model("amazon.nova-2-lite-v1:0") + True + >>> config._is_nova_lite_2_model("us.amazon.nova-2-lite-v1:0") + True + >>> config._is_nova_lite_2_model("amazon.nova-pro-1-5-v1:0") + False + >>> config._is_nova_lite_2_model("amazon.nova-pro-v1:0") + False + """ + # Remove regional prefix if present (us., eu., apac.) + model_without_region = model + for prefix in ["us.", "eu.", "apac."]: + if model.startswith(prefix): + model_without_region = model[len(prefix) :] + break + + # Check if the model is specifically Nova Lite 2 + return "nova-2-lite" in model_without_region + + def _transform_reasoning_effort_to_reasoning_config( + self, reasoning_effort: str + ) -> dict: + """ + Transform reasoning_effort parameter to Nova 2 reasoningConfig structure. + + Nova 2 models use a reasoningConfig structure in additionalModelRequestFields + that differs from both Anthropic's thinking parameter and GPT-OSS's reasoning_effort. + + Args: + reasoning_effort: The reasoning effort level, must be "low" or "high" + + Returns: + dict: A dictionary containing the reasoningConfig structure: + { + "reasoningConfig": { + "type": "enabled", + "maxReasoningEffort": "low" | "medium" |"high" + } + } + + Raises: + BadRequestError: If reasoning_effort is not "low", "medium" or "high" + + Examples: + >>> config = AmazonConverseConfig() + >>> config._transform_reasoning_effort_to_reasoning_config("high") + {'reasoningConfig': {'type': 'enabled', 'maxReasoningEffort': 'high'}} + >>> config._transform_reasoning_effort_to_reasoning_config("low") + {'reasoningConfig': {'type': 'enabled', 'maxReasoningEffort': 'low'}} + """ + valid_values = ["low", "medium", "high"] + if reasoning_effort not in valid_values: + raise litellm.exceptions.BadRequestError( + message=f"Invalid reasoning_effort value '{reasoning_effort}' for Nova 2 models. " + f"Supported values: {valid_values}", + model="amazon.nova-2-lite-v1:0", + llm_provider="bedrock_converse", + ) + + return { + "reasoningConfig": { + "type": "enabled", + "maxReasoningEffort": reasoning_effort, + } + } + def get_supported_openai_params(self, model: str) -> List[str]: from litellm.utils import supports_function_calling @@ -299,6 +386,10 @@ class AmazonConverseConfig(BaseConfig): if "gpt-oss" in model: supported_params.append("reasoning_effort") + elif self._is_nova_lite_2_model(model): + # Nova Lite 2 models support reasoning_effort (transformed to reasoningConfig) + # These models use a different reasoning structure than Anthropic's thinking parameter + supported_params.append("reasoning_effort") elif ( "claude-3-7" in model or "claude-sonnet-4" in model @@ -564,6 +655,12 @@ class AmazonConverseConfig(BaseConfig): # GPT-OSS models: keep reasoning_effort as-is # It will be passed through to additionalModelRequestFields optional_params["reasoning_effort"] = value + elif self._is_nova_lite_2_model(model): + # Nova Lite 2 models: transform to reasoningConfig + reasoning_config = ( + self._transform_reasoning_effort_to_reasoning_config(value) + ) + optional_params.update(reasoning_config) else: # Anthropic and other models: convert to thinking parameter optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( @@ -574,8 +671,9 @@ class AmazonConverseConfig(BaseConfig): self._validate_request_metadata(value) # type: ignore optional_params["requestMetadata"] = value - # Only update thinking tokens for non-GPT-OSS models - if "gpt-oss" not in model: + # Only update thinking tokens for non-GPT-OSS models and non-Nova-Lite-2 models + # Nova Lite 2 handles token budgeting differently through reasoningConfig + if "gpt-oss" not in model and not self._is_nova_lite_2_model(model): self.update_optional_params_with_thinking_tokens( non_default_params=non_default_params, optional_params=optional_params ) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 932508824af..19ed734c5f8 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -269,6 +269,71 @@ "supports_response_schema": true, "supports_vision": true }, + "amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 3e-07, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "apac.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "eu.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "us.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "amazon.nova-micro-v1:0": { "input_cost_per_token": 3.5e-08, "litellm_provider": "bedrock_converse", diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index 37c95be72ce..e603f94ab87 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -2702,3 +2702,36 @@ def test_empty_assistant_message_handling(): finally: # Restore original modify_params setting litellm.modify_params = original_modify_params + + +def test_is_nova_lite_2_model(): + """Test the _is_nova_lite_2_model() method for detecting Nova 2 models.""" + config = AmazonConverseConfig() + + # Test with amazon.nova-2-lite-v1:0 + assert config._is_nova_lite_2_model("amazon.nova-2-lite-v1:0") is True + + # Test with regional variants + assert config._is_nova_lite_2_model("us.amazon.nova-2-lite-v1:0") is True + assert config._is_nova_lite_2_model("eu.amazon.nova-2-lite-v1:0") is True + assert config._is_nova_lite_2_model("apac.amazon.nova-2-lite-v1:0") is True + + # Test with other Nova 2 variants (pro, micro) + assert config._is_nova_lite_2_model("amazon.nova-pro-1-5-v1:0") is False + assert config._is_nova_lite_2_model("amazon.nova-micro-1-5-v1:0") is False + assert config._is_nova_lite_2_model("us.amazon.nova-pro-1-5-v1:0") is False + assert config._is_nova_lite_2_model("eu.amazon.nova-micro-1-5-v1:0") is False + + # Test with non-Nova-1.5 lite models (should return False) + assert config._is_nova_lite_2_model("amazon.nova-lite-v1:0") is False + assert config._is_nova_lite_2_model("amazon.nova-pro-v1:0") is False + assert config._is_nova_lite_2_model("amazon.nova-micro-v1:0") is False + + # Test with Nova v1:0 models (should return False) + assert config._is_nova_lite_2_model("us.amazon.nova-lite-v1:0") is False + assert config._is_nova_lite_2_model("eu.amazon.nova-pro-v1:0") is False + + # Test with completely different models (should return False) + assert config._is_nova_lite_2_model("anthropic.claude-3-5-sonnet-20240620-v1:0") is False + assert config._is_nova_lite_2_model("meta.llama3-70b-instruct-v1:0") is False + assert config._is_nova_lite_2_model("mistral.mistral-7b-instruct-v0:2") is False diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation_nova_2.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation_nova_2.py new file mode 100644 index 00000000000..23243dac201 --- /dev/null +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation_nova_2.py @@ -0,0 +1,794 @@ +""" +Unit tests for Amazon Nova 2 reasoning configuration transformation. + +Tests the _transform_reasoning_effort_to_reasoning_config method in AmazonConverseConfig. +""" + +import pytest +import sys +import os + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig + + +class TestNova15ReasoningTransformation: + """Test suite for Nova 2 reasoning effort transformation.""" + + def test_reasoning_effort_low_transformation(self): + """Test that reasoning_effort='low' is transformed to correct reasoningConfig structure.""" + config = AmazonConverseConfig() + + result = config._transform_reasoning_effort_to_reasoning_config("low") + + # Verify the structure + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "low" + + def test_reasoning_effort_high_transformation(self): + """Test that reasoning_effort='high' is transformed to correct reasoningConfig structure.""" + config = AmazonConverseConfig() + + result = config._transform_reasoning_effort_to_reasoning_config("high") + + # Verify the structure + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "high" + + def test_invalid_reasoning_effort_value(self): + """Test that invalid reasoning_effort values raise BadRequestError.""" + config = AmazonConverseConfig() + + # Test with invalid value "invalid" + with pytest.raises(litellm.exceptions.BadRequestError) as exc_info: + config._transform_reasoning_effort_to_reasoning_config("invalid") + + # Verify error message contains the invalid value and valid values + error_message = str(exc_info.value) + assert "invalid" in error_message + assert "low" in error_message + assert "high" in error_message + assert "Nova 2" in error_message + + def test_invalid_reasoning_effort_empty_string(self): + """Test that empty string raises BadRequestError.""" + config = AmazonConverseConfig() + + with pytest.raises(litellm.exceptions.BadRequestError) as exc_info: + config._transform_reasoning_effort_to_reasoning_config("") + + # Verify error message + error_message = str(exc_info.value) + assert "low" in error_message + assert "high" in error_message + + def test_invalid_reasoning_effort_wrong_case(self): + """Test that case-sensitive values are rejected (e.g., 'Low' instead of 'low').""" + config = AmazonConverseConfig() + + with pytest.raises(litellm.exceptions.BadRequestError): + config._transform_reasoning_effort_to_reasoning_config("Low") + + with pytest.raises(litellm.exceptions.BadRequestError): + config._transform_reasoning_effort_to_reasoning_config("HIGH") + + +class TestNova2ParameterMapping: + """Test suite for Nova 2 parameter mapping integration.""" + + def test_nova_2_reasoning_effort_low_mapping(self): + """Test that reasoning_effort='low' is correctly mapped to reasoningConfig for Nova 2.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + non_default_params = {"reasoning_effort": "low"} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "low" + + # Verify thinking is NOT in result + assert "thinking" not in result + + # Verify reasoning_effort is NOT kept as-is (should be transformed) + assert "reasoning_effort" not in result + + def test_nova_2_reasoning_effort_high_mapping(self): + """Test that reasoning_effort='high' is correctly mapped to reasoningConfig for Nova 2.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + non_default_params = {"reasoning_effort": "high"} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "high" + + # Verify thinking is NOT in result + assert "thinking" not in result + + # Verify reasoning_effort is NOT kept as-is (should be transformed) + assert "reasoning_effort" not in result + + def test_nova_2_without_reasoning_effort(self): + """Test that Nova 2 without reasoning_effort has no reasoningConfig in result.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + non_default_params = {"temperature": 0.7} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is NOT in result + assert "reasoningConfig" not in result + + # Verify thinking is NOT in result + assert "thinking" not in result + + # Verify reasoning_effort is NOT in result + assert "reasoning_effort" not in result + + def test_nova_2_regional_variant_us(self): + """Test that US regional variant of Nova 2 works correctly.""" + config = AmazonConverseConfig() + + model = "us.amazon.nova-2-lite-v1:0" + non_default_params = {"reasoning_effort": "high"} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "high" + + def test_nova_2_regional_variant_eu(self): + """Test that EU regional variant of Nova 2 works correctly.""" + config = AmazonConverseConfig() + + model = "eu.amazon.nova-2-lite-v1:0" + non_default_params = {"reasoning_effort": "low"} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "low" + + def test_nova_2_regional_variant_apac(self): + """Test that APAC regional variant of Nova 2 works correctly.""" + config = AmazonConverseConfig() + + model = "apac.amazon.nova-2-lite-v1:0" + non_default_params = {"reasoning_effort": "high"} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "high" + + def test_nova_2_with_other_params(self): + """Test that Nova 2 reasoning works alongside other parameters.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + non_default_params = { + "reasoning_effort": "high", + "temperature": 0.8, + "max_tokens": 1000, + "top_p": 0.9, + } + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + + # Verify reasoningConfig is in result + assert "reasoningConfig" in result + assert result["reasoningConfig"]["type"] == "enabled" + assert result["reasoningConfig"]["maxReasoningEffort"] == "high" + + # Verify other params are also present + assert result["temperature"] == 0.8 + assert result["maxTokens"] == 1000 + assert result["topP"] == 0.9 + + +class TestNova15SupportedParameters: + """Test suite for Nova 2 supported parameters.""" + + def test_nova_2_supports_reasoning_effort(self): + """Test that Nova 2 model reports reasoning_effort in supported params.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + supported_params = config.get_supported_openai_params(model) + + # Verify reasoning_effort is in supported params + assert "reasoning_effort" in supported_params + + # Verify thinking is NOT in supported params (Nova 2 uses reasoningConfig, not thinking) + assert "thinking" not in supported_params + + def test_nova_2_regional_variant_us_supported_params(self): + """Test that US regional variant returns same supported params.""" + config = AmazonConverseConfig() + + model = "us.amazon.nova-2-lite-v1:0" + supported_params = config.get_supported_openai_params(model) + + # Verify reasoning_effort is in supported params + assert "reasoning_effort" in supported_params + + # Verify thinking is NOT in supported params + assert "thinking" not in supported_params + + def test_nova_2_regional_variant_eu_supported_params(self): + """Test that EU regional variant returns same supported params.""" + config = AmazonConverseConfig() + + model = "eu.amazon.nova-2-lite-v1:0" + supported_params = config.get_supported_openai_params(model) + + # Verify reasoning_effort is in supported params + assert "reasoning_effort" in supported_params + + # Verify thinking is NOT in supported params + assert "thinking" not in supported_params + + def test_nova_2_regional_variant_apac_supported_params(self): + """Test that APAC regional variant returns same supported params.""" + config = AmazonConverseConfig() + + model = "apac.amazon.nova-2-lite-v1:0" + supported_params = config.get_supported_openai_params(model) + + # Verify reasoning_effort is in supported params + assert "reasoning_effort" in supported_params + + # Verify thinking is NOT in supported params + assert "thinking" not in supported_params + + def test_nova_2_has_standard_params(self): + """Test that Nova 2 still has all standard supported params.""" + config = AmazonConverseConfig() + + model = "amazon.nova-2-lite-v1:0" + supported_params = config.get_supported_openai_params(model) + + # Verify standard params are present + assert "max_tokens" in supported_params + assert "max_completion_tokens" in supported_params + assert "stream" in supported_params + assert "stream_options" in supported_params + assert "stop" in supported_params + assert "temperature" in supported_params + assert "top_p" in supported_params + assert "tools" in supported_params + assert "response_format" in supported_params + + +class TestNova15ResponseParsing: + """Test suite for Nova 2 response parsing.""" + + def test_transform_reasoning_content_single_block(self): + """Test that reasoning content is extracted correctly from a single block.""" + config = AmazonConverseConfig() + + reasoning_blocks = [ + {"reasoningText": {"text": "Let me think through this step by step..."}} + ] + + result = config._transform_reasoning_content(reasoning_blocks) + + assert result == "Let me think through this step by step..." + + def test_transform_reasoning_content_multiple_blocks(self): + """Test that reasoning content is concatenated from multiple blocks.""" + config = AmazonConverseConfig() + + reasoning_blocks = [ + {"reasoningText": {"text": "First, I need to analyze the problem. "}}, + {"reasoningText": {"text": "Then, I'll consider the solution."}}, + ] + + result = config._transform_reasoning_content(reasoning_blocks) + + assert ( + result + == "First, I need to analyze the problem. Then, I'll consider the solution." + ) + + def test_transform_reasoning_content_empty_blocks(self): + """Test that empty reasoning blocks return empty string.""" + config = AmazonConverseConfig() + + reasoning_blocks = [] + + result = config._transform_reasoning_content(reasoning_blocks) + + assert result == "" + + def test_transform_thinking_blocks_with_text(self): + """Test that thinking blocks are populated correctly with text.""" + config = AmazonConverseConfig() + + reasoning_blocks = [{"reasoningText": {"text": "My reasoning process..."}}] + + result = config._transform_thinking_blocks(reasoning_blocks) + + assert len(result) == 1 + assert result[0]["type"] == "thinking" + assert result[0]["thinking"] == "My reasoning process..." + assert "signature" not in result[0] + + def test_transform_thinking_blocks_with_signature(self): + """Test that signature field is preserved when present.""" + config = AmazonConverseConfig() + + reasoning_blocks = [ + { + "reasoningText": { + "text": "My reasoning...", + "signature": "signature-hash-12345", + } + } + ] + + result = config._transform_thinking_blocks(reasoning_blocks) + + assert len(result) == 1 + assert result[0]["type"] == "thinking" + assert result[0]["thinking"] == "My reasoning..." + assert result[0]["signature"] == "signature-hash-12345" + + def test_transform_thinking_blocks_with_redacted_content(self): + """Test that redacted content blocks are handled correctly.""" + config = AmazonConverseConfig() + + reasoning_blocks = [ + {"reasoningText": {"text": "First part of reasoning..."}}, + {"redactedContent": {}}, + {"reasoningText": {"text": "Second part after redaction..."}}, + ] + + result = config._transform_thinking_blocks(reasoning_blocks) + + assert len(result) == 3 + assert result[0]["type"] == "thinking" + assert result[0]["thinking"] == "First part of reasoning..." + assert result[1]["type"] == "redacted_thinking" + assert result[2]["type"] == "thinking" + assert result[2]["thinking"] == "Second part after redaction..." + + def test_transform_thinking_blocks_multiple_blocks(self): + """Test that multiple thinking blocks are all transformed.""" + config = AmazonConverseConfig() + + reasoning_blocks = [ + {"reasoningText": {"text": "Step 1: Analyze the problem"}}, + { + "reasoningText": { + "text": "Step 2: Consider solutions", + "signature": "sig-abc", + } + }, + {"reasoningText": {"text": "Step 3: Choose best approach"}}, + ] + + result = config._transform_thinking_blocks(reasoning_blocks) + + assert len(result) == 3 + assert all(block["type"] == "thinking" for block in result) + assert result[0]["thinking"] == "Step 1: Analyze the problem" + assert result[1]["thinking"] == "Step 2: Consider solutions" + assert result[1]["signature"] == "sig-abc" + assert result[2]["thinking"] == "Step 3: Choose best approach" + + def test_transform_thinking_blocks_empty_list(self): + """Test that empty thinking blocks list returns empty list.""" + config = AmazonConverseConfig() + + reasoning_blocks = [] + + result = config._transform_thinking_blocks(reasoning_blocks) + + assert result == [] + + def test_response_parsing_integration(self): + """Test that response parsing works end-to-end with Nova 2 structure.""" + config = AmazonConverseConfig() + + # Simulate a Nova 2 response with reasoning content + reasoning_blocks = [ + { + "reasoningText": { + "text": "Let me analyze this carefully. ", + "signature": "test-signature", + } + }, + {"reasoningText": {"text": "Based on my analysis, the answer is clear."}}, + ] + + # Test reasoning content extraction + reasoning_content = config._transform_reasoning_content(reasoning_blocks) + assert ( + reasoning_content + == "Let me analyze this carefully. Based on my analysis, the answer is clear." + ) + + # Test thinking blocks transformation + thinking_blocks = config._transform_thinking_blocks(reasoning_blocks) + assert len(thinking_blocks) == 2 + assert thinking_blocks[0]["thinking"] == "Let me analyze this carefully. " + assert thinking_blocks[0]["signature"] == "test-signature" + assert ( + thinking_blocks[1]["thinking"] + == "Based on my analysis, the answer is clear." + ) + + +class TestNova15StreamingResponseParsing: + """Test suite for Nova 2 streaming response parsing.""" + + def test_streaming_reasoning_content_start_event(self): + """Test that streaming start event with reasoningContent is handled correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a start event with redacted reasoning content + chunk_data = { + "start": {"reasoningContent": {"redactedContent": {}}}, + "contentBlockIndex": 0, + } + + result = handler.converse_chunk_parser(chunk_data) + + # Verify thinking blocks are populated + assert result.choices[0].delta.thinking_blocks is not None + assert len(result.choices[0].delta.thinking_blocks) == 1 + assert result.choices[0].delta.thinking_blocks[0]["type"] == "redacted_thinking" + + def test_streaming_reasoning_content_delta_text(self): + """Test that streaming delta event with reasoning text is handled correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a delta event with reasoning text + chunk_data = { + "delta": {"reasoningContent": {"text": "Let me think about this..."}}, + "contentBlockIndex": 0, + } + + result = handler.converse_chunk_parser(chunk_data) + + # Verify reasoning content is extracted + assert result.choices[0].delta.reasoning_content == "Let me think about this..." + + # Verify thinking blocks are populated + assert result.choices[0].delta.thinking_blocks is not None + assert len(result.choices[0].delta.thinking_blocks) == 1 + assert result.choices[0].delta.thinking_blocks[0]["type"] == "thinking" + assert ( + result.choices[0].delta.thinking_blocks[0]["thinking"] + == "Let me think about this..." + ) + + def test_streaming_reasoning_content_delta_signature(self): + """Test that streaming delta event with signature is handled correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a delta event with signature + chunk_data = { + "delta": {"reasoningContent": {"signature": "signature-hash-xyz"}}, + "contentBlockIndex": 0, + } + + result = handler.converse_chunk_parser(chunk_data) + + # Verify reasoning content is set to empty string for consistency + assert result.choices[0].delta.reasoning_content == "" + + # Verify thinking blocks are populated with signature + assert result.choices[0].delta.thinking_blocks is not None + assert len(result.choices[0].delta.thinking_blocks) == 1 + assert result.choices[0].delta.thinking_blocks[0]["type"] == "thinking" + assert ( + result.choices[0].delta.thinking_blocks[0]["signature"] + == "signature-hash-xyz" + ) + assert result.choices[0].delta.thinking_blocks[0]["thinking"] == "" + + def test_streaming_reasoning_content_multiple_deltas(self): + """Test that multiple reasoning content deltas are accumulated correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate multiple delta events + chunks = [ + { + "delta": {"reasoningContent": {"text": "First, "}}, + "contentBlockIndex": 0, + }, + { + "delta": {"reasoningContent": {"text": "I need to analyze "}}, + "contentBlockIndex": 0, + }, + { + "delta": {"reasoningContent": {"text": "the problem."}}, + "contentBlockIndex": 0, + }, + ] + + results = [] + for chunk_data in chunks: + result = handler.converse_chunk_parser(chunk_data) + results.append(result) + + # Verify each delta has the correct reasoning content + assert results[0].choices[0].delta.reasoning_content == "First, " + assert results[1].choices[0].delta.reasoning_content == "I need to analyze " + assert results[2].choices[0].delta.reasoning_content == "the problem." + + # Verify thinking blocks are populated for each delta + for result in results: + assert result.choices[0].delta.thinking_blocks is not None + assert len(result.choices[0].delta.thinking_blocks) == 1 + assert result.choices[0].delta.thinking_blocks[0]["type"] == "thinking" + + def test_streaming_reasoning_then_text_content(self): + """Test that reasoning content followed by text content is handled correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate reasoning content followed by text content + chunks = [ + { + "delta": {"reasoningContent": {"text": "Let me think..."}}, + "contentBlockIndex": 0, + }, + {"delta": {"text": "Based on my reasoning, "}, "contentBlockIndex": 1}, + {"delta": {"text": "the answer is 42."}, "contentBlockIndex": 1}, + ] + + results = [] + for chunk_data in chunks: + result = handler.converse_chunk_parser(chunk_data) + results.append(result) + + # Verify first chunk has reasoning content + assert results[0].choices[0].delta.reasoning_content == "Let me think..." + assert results[0].choices[0].delta.thinking_blocks is not None + + # Verify subsequent chunks have text content + assert results[1].choices[0].delta.content == "Based on my reasoning, " + assert results[2].choices[0].delta.content == "the answer is 42." + + def test_streaming_redacted_content_delta(self): + """Test that streaming delta with redacted content is handled correctly.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a delta event with redacted content + chunk_data = { + "delta": {"reasoningContent": {"redactedContent": {}}}, + "contentBlockIndex": 0, + } + + result = handler.converse_chunk_parser(chunk_data) + + # Verify reasoning content is set to empty string for consistency + assert result.choices[0].delta.reasoning_content == "" + + # Verify thinking blocks contain redacted block + assert result.choices[0].delta.thinking_blocks is not None + assert len(result.choices[0].delta.thinking_blocks) == 1 + assert result.choices[0].delta.thinking_blocks[0]["type"] == "redacted_thinking" + + def test_streaming_provider_specific_fields(self): + """Test that provider_specific_fields are populated in streaming responses.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a delta event with reasoning content + chunk_data = { + "delta": {"reasoningContent": {"text": "Reasoning text"}}, + "contentBlockIndex": 0, + } + + result = handler.converse_chunk_parser(chunk_data) + + # Verify provider_specific_fields are populated + assert result.choices[0].delta.provider_specific_fields is not None + assert "reasoningContent" in result.choices[0].delta.provider_specific_fields + assert ( + result.choices[0].delta.provider_specific_fields["reasoningContent"]["text"] + == "Reasoning text" + ) + + def test_streaming_mixed_content_blocks(self): + """Test streaming with mixed content blocks (reasoning, text, tool calls).""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + # Simulate a complex streaming scenario + chunks = [ + # Start with reasoning + { + "delta": { + "reasoningContent": { + "text": "I need to call a tool to get information." + } + }, + "contentBlockIndex": 0, + }, + # Tool use start + { + "start": {"toolUse": {"toolUseId": "tool-123", "name": "get_weather"}}, + "contentBlockIndex": 1, + }, + # Tool use delta + { + "delta": {"toolUse": {"input": '{"location": "NYC"}'}}, + "contentBlockIndex": 1, + }, + # Text response + {"delta": {"text": "The weather is sunny."}, "contentBlockIndex": 2}, + ] + + results = [] + for chunk_data in chunks: + result = handler.converse_chunk_parser(chunk_data) + results.append(result) + + # Verify reasoning content in first chunk + assert ( + results[0].choices[0].delta.reasoning_content + == "I need to call a tool to get information." + ) + + # Verify tool call in second and third chunks + assert results[1].choices[0].delta.tool_calls is not None + assert ( + results[1].choices[0].delta.tool_calls[0]["function"]["name"] + == "get_weather" + ) + assert results[2].choices[0].delta.tool_calls is not None + + # Verify text content in fourth chunk + assert results[3].choices[0].delta.content == "The weather is sunny." + + def test_extract_reasoning_content_str_with_text(self): + """Test extract_reasoning_content_str method with text.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + reasoning_block = {"text": "This is reasoning text"} + + result = handler.extract_reasoning_content_str(reasoning_block) + + assert result == "This is reasoning text" + + def test_extract_reasoning_content_str_without_text(self): + """Test extract_reasoning_content_str method without text (e.g., signature only).""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + reasoning_block = {"signature": "sig-123"} + + result = handler.extract_reasoning_content_str(reasoning_block) + + assert result is None + + def test_translate_thinking_blocks_streaming_text(self): + """Test translate_thinking_blocks method with text.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + thinking_block = {"text": "Thinking content"} + + result = handler.translate_thinking_blocks(thinking_block) + + assert result is not None + assert len(result) == 1 + assert result[0]["type"] == "thinking" + assert result[0]["thinking"] == "Thinking content" + + def test_translate_thinking_blocks_streaming_signature(self): + """Test translate_thinking_blocks method with signature.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + thinking_block = {"signature": "sig-abc"} + + result = handler.translate_thinking_blocks(thinking_block) + + assert result is not None + assert len(result) == 1 + assert result[0]["type"] == "thinking" + assert result[0]["signature"] == "sig-abc" + assert ( + result[0]["thinking"] == "" + ) # Empty string for consistency with Anthropic + + def test_translate_thinking_blocks_streaming_redacted(self): + """Test translate_thinking_blocks method with redacted content.""" + from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + handler = AWSEventStreamDecoder(model="amazon.nova-2-lite-v1:0") + + thinking_block = {"redactedContent": {}} + + result = handler.translate_thinking_blocks(thinking_block) + + assert result is not None + assert len(result) == 1 + assert result[0]["type"] == "redacted_thinking" From 4063501d6981a7b0606f4942deb2b7d0cc43dc06 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 2 Dec 2025 17:08:17 -0800 Subject: [PATCH 270/349] Show all credential values on Edit Credential Modal --- .../model_add/AddCredentialModal.test.tsx | 108 +++++++++++++++ .../model_add/AddCredentialModal.tsx | 118 +++++++++++++++++ .../model_add/EditCredentialModal.test.tsx | 123 ++++++++++++++++++ ...ntials_tab.tsx => EditCredentialModal.tsx} | 60 ++++----- .../src/components/model_add/credentials.tsx | 22 ++-- 5 files changed, 386 insertions(+), 45 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/model_add/AddCredentialModal.test.tsx create mode 100644 ui/litellm-dashboard/src/components/model_add/AddCredentialModal.tsx create mode 100644 ui/litellm-dashboard/src/components/model_add/EditCredentialModal.test.tsx rename ui/litellm-dashboard/src/components/model_add/{add_credentials_tab.tsx => EditCredentialModal.tsx} (78%) diff --git a/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.test.tsx b/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.test.tsx new file mode 100644 index 00000000000..aee7a0cdd1d --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.test.tsx @@ -0,0 +1,108 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { Providers } from "../provider_info_helpers"; +import AddCredentialModal from "./AddCredentialModal"; + +vi.mock("../networking", async () => { + const actual = await vi.importActual("../networking"); + return { + ...actual, + getProviderCreateMetadata: vi.fn().mockResolvedValue([ + { + provider: "OpenAI", + provider_display_name: Providers.OpenAI, + litellm_provider: "openai", + default_model_placeholder: "gpt-3.5-turbo", + credential_fields: [ + { + key: "api_key", + label: "OpenAI API Key", + field_type: "password", + required: true, + }, + { + key: "api_base", + label: "API Base", + field_type: "text", + placeholder: "https://api.openai.com/v1", + }, + ], + }, + { + provider: "Anthropic", + provider_display_name: Providers.Anthropic, + litellm_provider: "anthropic", + default_model_placeholder: "claude-3-opus-20240229", + credential_fields: [ + { + key: "api_key", + label: "Anthropic API Key", + field_type: "password", + required: true, + }, + ], + }, + ]), + }; +}); + +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }); + +const mockUploadProps = { + beforeUpload: vi.fn(), + onChange: vi.fn(), +}; + +describe("AddCredentialModal", () => { + it("should render", () => { + const queryClient = createQueryClient(); + const onCancel = vi.fn(); + const onAddCredential = vi.fn(); + + render( + + + , + ); + + expect(screen.getByText("Add New Credential")).toBeInTheDocument(); + expect(screen.getByLabelText("Credential Name:")).toBeInTheDocument(); + expect(screen.getByLabelText("Provider:")).toBeInTheDocument(); + }); + + it("should show the correct provider fields", async () => { + const queryClient = createQueryClient(); + const onCancel = vi.fn(); + const onAddCredential = vi.fn(); + + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByLabelText("OpenAI API Key")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("https://api.openai.com/v1")).toBeInTheDocument(); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.tsx b/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.tsx new file mode 100644 index 00000000000..694a98201c6 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_add/AddCredentialModal.tsx @@ -0,0 +1,118 @@ +import { TextInput } from "@tremor/react"; +import { Select as AntdSelect, Button, Form, Modal, Tooltip, Typography } from "antd"; +import type { UploadProps } from "antd/es/upload"; +import React, { useState } from "react"; +import ProviderSpecificFields from "../add_model/provider_specific_fields"; +import { Providers, providerLogoMap } from "../provider_info_helpers"; +const { Link } = Typography; + +interface AddCredentialsModalProps { + open: boolean; + onCancel: () => void; + onAddCredential: (values: any) => void; + uploadProps: UploadProps; +} + +const AddCredentialsModal: React.FC = ({ open, onCancel, onAddCredential, uploadProps }) => { + const [form] = Form.useForm(); + const [selectedProvider, setSelectedProvider] = useState(Providers.OpenAI); + + const handleSubmit = (values: any) => { + const filteredValues = Object.entries(values).reduce((acc, [key, value]) => { + if (value !== "" && value !== undefined && value !== null) { + acc[key] = value; + } + return acc; + }, {} as any); + onAddCredential(filteredValues); + form.resetFields(); + }; + + return ( + { + onCancel(); + form.resetFields(); + }} + footer={null} + width={600} + > +
    + {/* Credential Name */} + + + + + {/* Provider Selection */} + + { + setSelectedProvider(value as Providers); + form.setFieldValue("custom_llm_provider", value); + }} + > + {Object.entries(Providers).map(([providerEnum, providerDisplayName]) => ( + +
    + {`${providerEnum} { + const target = e.target as HTMLImageElement; + const parent = target.parentElement; + if (parent) { + const fallbackDiv = document.createElement("div"); + fallbackDiv.className = + "w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs"; + fallbackDiv.textContent = providerDisplayName.charAt(0); + parent.replaceChild(fallbackDiv, target); + } + }} + /> + {providerDisplayName} +
    +
    + ))} +
    +
    + + + + {/* Modal Footer */} +
    + + Need Help? + + +
    + + +
    +
    + +
    + ); +}; + +export default AddCredentialsModal; diff --git a/ui/litellm-dashboard/src/components/model_add/EditCredentialModal.test.tsx b/ui/litellm-dashboard/src/components/model_add/EditCredentialModal.test.tsx new file mode 100644 index 00000000000..def3b4f6cd7 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_add/EditCredentialModal.test.tsx @@ -0,0 +1,123 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { Providers } from "../provider_info_helpers"; +import { CredentialItem } from "../networking"; +import EditCredentialModal from "./EditCredentialModal"; + +vi.mock("../networking", async () => { + const actual = await vi.importActual("../networking"); + return { + ...actual, + getProviderCreateMetadata: vi.fn().mockResolvedValue([ + { + provider: "OpenAI", + provider_display_name: Providers.OpenAI, + litellm_provider: "openai", + default_model_placeholder: "gpt-3.5-turbo", + credential_fields: [ + { + key: "api_key", + label: "OpenAI API Key", + field_type: "password", + required: true, + }, + { + key: "api_base", + label: "API Base", + field_type: "text", + placeholder: "https://api.openai.com/v1", + }, + ], + }, + { + provider: "Anthropic", + provider_display_name: Providers.Anthropic, + litellm_provider: "anthropic", + default_model_placeholder: "claude-3-opus-20240229", + credential_fields: [ + { + key: "api_key", + label: "Anthropic API Key", + field_type: "password", + required: true, + }, + ], + }, + ]), + }; +}); + +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }); + +const mockUploadProps = { + beforeUpload: vi.fn(), + onChange: vi.fn(), +}; + +const mockCredential: CredentialItem = { + credential_name: "test-credential", + credential_values: { + api_key: "test-api-key", + api_base: "https://api.test.com", + }, + credential_info: { + custom_llm_provider: Providers.OpenAI, + }, +}; + +describe("EditCredentialModal", () => { + it("should render", () => { + const queryClient = createQueryClient(); + const onCancel = vi.fn(); + const onUpdateCredential = vi.fn(); + + render( + + + , + ); + + expect(screen.getByText("Edit Credential")).toBeInTheDocument(); + expect(screen.getByLabelText("Credential Name:")).toBeInTheDocument(); + expect(screen.getByLabelText("Provider:")).toBeInTheDocument(); + }); + + it("should render initial values", async () => { + const queryClient = createQueryClient(); + const onCancel = vi.fn(); + const onUpdateCredential = vi.fn(); + + render( + + + , + ); + + await waitFor(() => { + const credentialNameInput = screen.getByLabelText("Credential Name:") as HTMLInputElement; + expect(credentialNameInput.value).toBe("test-credential"); + expect(credentialNameInput.disabled).toBe(true); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/model_add/add_credentials_tab.tsx b/ui/litellm-dashboard/src/components/model_add/EditCredentialModal.tsx similarity index 78% rename from ui/litellm-dashboard/src/components/model_add/add_credentials_tab.tsx rename to ui/litellm-dashboard/src/components/model_add/EditCredentialModal.tsx index 9c061eb121a..b206ed6c91d 100644 --- a/ui/litellm-dashboard/src/components/model_add/add_credentials_tab.tsx +++ b/ui/litellm-dashboard/src/components/model_add/EditCredentialModal.tsx @@ -1,34 +1,29 @@ -import React, { useEffect, useState } from "react"; -import { Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd"; -import type { UploadProps } from "antd/es/upload"; -import { Providers, providerLogoMap } from "../provider_info_helpers"; -import ProviderSpecificFields from "../add_model/provider_specific_fields"; import { TextInput } from "@tremor/react"; +import { Select as AntdSelect, Button, Form, Modal, Tooltip, Typography } from "antd"; +import type { UploadProps } from "antd/es/upload"; +import { useEffect, useState } from "react"; +import ProviderSpecificFields from "../add_model/provider_specific_fields"; import { CredentialItem } from "../networking"; -const { Title, Link } = Typography; +import { Providers, providerLogoMap } from "../provider_info_helpers"; +const { Link } = Typography; -interface AddCredentialsModalProps { - isVisible: boolean; +interface EditCredentialsModalProps { + open: boolean; onCancel: () => void; - onAddCredential: (values: any) => void; onUpdateCredential: (values: any) => void; uploadProps: UploadProps; - addOrEdit: "add" | "edit"; existingCredential: CredentialItem | null; } -const AddCredentialsModal: React.FC = ({ - isVisible, +export default function EditCredentialsModal({ + open, onCancel, - onAddCredential, onUpdateCredential, uploadProps, - addOrEdit, existingCredential, -}) => { +}: EditCredentialsModalProps) { const [form] = Form.useForm(); - const [selectedProvider, setSelectedProvider] = useState(Providers.OpenAI); - const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(Providers.Anthropic); const handleSubmit = (values: any) => { const filteredValues = Object.entries(values).reduce((acc, [key, value]) => { @@ -37,23 +32,25 @@ const AddCredentialsModal: React.FC = ({ } return acc; }, {} as any); - if (addOrEdit === "add") { - onAddCredential(filteredValues); - } else { - onUpdateCredential(filteredValues); - } + onUpdateCredential(filteredValues); form.resetFields(); }; useEffect(() => { if (existingCredential) { + // Spread all credential_values dynamically, converting undefined/null to null for form compatibility + const credentialValues = Object.entries(existingCredential.credential_values || {}).reduce( + (acc, [key, value]) => { + acc[key] = value ?? null; + return acc; + }, + {} as Record, + ); + form.setFieldsValue({ credential_name: existingCredential.credential_name, custom_llm_provider: existingCredential.credential_info.custom_llm_provider, - api_base: existingCredential.credential_values.api_base, - api_version: existingCredential.credential_values.api_version, - base_model: existingCredential.credential_values.base_model, - api_key: existingCredential.credential_values.api_key, + ...credentialValues, }); setSelectedProvider(existingCredential.credential_info.custom_llm_provider as Providers); } @@ -61,14 +58,15 @@ const AddCredentialsModal: React.FC = ({ return ( { onCancel(); form.resetFields(); }} footer={null} width={600} + destroyOnHidden={true} >
    {/* Credential Name */} @@ -142,12 +140,10 @@ const AddCredentialsModal: React.FC = ({ > Cancel - +
    ); -}; - -export default AddCredentialsModal; +} diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.tsx index e36a759294b..eecd26db25f 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.tsx @@ -19,10 +19,11 @@ import { credentialUpdateCall, CredentialItem, } from "@/components/networking"; // Assume this is your networking function -import AddCredentialsTab from "./add_credentials_tab"; +import AddCredentialsTab from "./AddCredentialModal"; import CredentialDeleteModal from "./CredentialDeleteModal"; import { Form } from "antd"; import NotificationsManager from "../molecules/notifications_manager"; +import EditCredentialsModal from "./EditCredentialModal"; interface CredentialsPanelProps { accessToken: string | null; uploadProps: UploadProps; @@ -60,10 +61,10 @@ const CredentialsPanel: React.FC = ({ }, }; - const response = await credentialUpdateCall(accessToken, values.credential_name, newCredential); + await credentialUpdateCall(accessToken, values.credential_name, newCredential); NotificationsManager.success("Credential updated successfully"); setIsUpdateModalOpen(false); - fetchCredentials(accessToken); + await fetchCredentials(accessToken); }; const handleAddCredential = async (values: any) => { @@ -84,10 +85,10 @@ const CredentialsPanel: React.FC = ({ }; // Add to list and close modal - const response = await credentialCreateCall(accessToken, newCredential); + await credentialCreateCall(accessToken, newCredential); NotificationsManager.success("Credential added successfully"); setIsAddModalOpen(false); - fetchCredentials(accessToken); + await fetchCredentials(accessToken); }; useEffect(() => { @@ -189,23 +190,18 @@ const CredentialsPanel: React.FC = ({ {isAddModalOpen && ( setIsAddModalOpen(false)} uploadProps={uploadProps} - addOrEdit="add" - onUpdateCredential={handleUpdateCredential} - existingCredential={null} /> )} {isUpdateModalOpen && ( - setIsUpdateModalOpen(false)} - addOrEdit="edit" /> )} From 427074ac6e80ffe3b30054dd10c2a7427cfbf96f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 2 Dec 2025 17:27:50 -0800 Subject: [PATCH 271/349] Fix: Datadog callback regression when ddtrace is installed (#17393) * fix DD agent host logging * docs fix * test_datadog_agent_configuration * test_datadog_ignores_ddtrace_agent_host --- docs/my-website/docs/observability/datadog.md | 18 +++---- litellm/integrations/datadog/datadog.py | 13 ++--- tests/logging_callback_tests/test_datadog.py | 47 +++++++++++++++++-- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/docs/my-website/docs/observability/datadog.md b/docs/my-website/docs/observability/datadog.md index 5cb5ab3af2d..b2901650ea6 100644 --- a/docs/my-website/docs/observability/datadog.md +++ b/docs/my-website/docs/observability/datadog.md @@ -71,17 +71,19 @@ DD_SOURCE="litellm_dev" # [OPTIONAL] your datadog source. use to different Send logs through a local DataDog agent (useful for containerized environments): ```shell -DD_AGENT_HOST="localhost" # hostname or IP of DataDog agent -DD_AGENT_PORT="10518" # [OPTIONAL] port of DataDog agent (default: 10518) -DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (agent handles auth) -DD_SOURCE="litellm_dev" # [OPTIONAL] your datadog source +LITELLM_DD_AGENT_HOST="localhost" # hostname or IP of DataDog agent +LITELLM_DD_AGENT_PORT="10518" # [OPTIONAL] port of DataDog agent (default: 10518) +DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (agent handles auth) +DD_SOURCE="litellm_dev" # [OPTIONAL] your datadog source ``` -When `DD_AGENT_HOST` is set, logs are sent to the agent instead of directly to DataDog API. This is useful for: +When `LITELLM_DD_AGENT_HOST` is set, logs are sent to the agent instead of directly to DataDog API. This is useful for: - Centralized log shipping in containerized environments - Reducing direct API calls from multiple services - Leveraging agent-side processing and filtering +**Note:** We use `LITELLM_DD_AGENT_HOST` instead of `DD_AGENT_HOST` to avoid conflicts with `ddtrace` which automatically sets `DD_AGENT_HOST` for APM tracing. + **Step 3**: Start the proxy, make a test request Start proxy @@ -191,8 +193,8 @@ LiteLLM supports customizing the following Datadog environment variables |---------------------|-------------|---------------|----------| | `DD_API_KEY` | Your Datadog API key for authentication (required for direct API, optional for agent) | None | Conditional* | | `DD_SITE` | Your Datadog site (e.g., "us5.datadoghq.com") (required for direct API) | None | Conditional* | -| `DD_AGENT_HOST` | Hostname or IP of DataDog agent (e.g., "localhost"). When set, logs are sent to agent instead of direct API | None | ❌ No | -| `DD_AGENT_PORT` | Port of DataDog agent for log intake | "10518" | ❌ No | +| `LITELLM_DD_AGENT_HOST` | Hostname or IP of DataDog agent (e.g., "localhost"). When set, logs are sent to agent instead of direct API | None | ❌ No | +| `LITELLM_DD_AGENT_PORT` | Port of DataDog agent for log intake | "10518" | ❌ No | | `DD_ENV` | Environment tag for your logs (e.g., "production", "staging") | "unknown" | ❌ No | | `DD_SERVICE` | Service name for your logs | "litellm-server" | ❌ No | | `DD_SOURCE` | Source name for your logs | "litellm" | ❌ No | @@ -201,5 +203,5 @@ LiteLLM supports customizing the following Datadog environment variables | `POD_NAME` | Pod name tag (useful for Kubernetes deployments) | "unknown" | ❌ No | \* **Required when using Direct API** (default): `DD_API_KEY` and `DD_SITE` are required -\* **Optional when using DataDog Agent**: Set `DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required +\* **Optional when using DataDog Agent**: Set `LITELLM_DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 46e1a2c201f..21e1d562224 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -65,11 +65,11 @@ class DataDogLogger( `DD_SITE` - your datadog site, example = `"us5.datadoghq.com"` Optional environment variables (DataDog Agent): - `DD_AGENT_HOST` - hostname or IP of DataDog agent, example = `"localhost"` - `DD_AGENT_PORT` - port of DataDog agent (default: 10518 for logs) + `LITELLM_DD_AGENT_HOST` - hostname or IP of DataDog agent, example = `"localhost"` + `LITELLM_DD_AGENT_PORT` - port of DataDog agent (default: 10518 for logs) - Note: If DD_AGENT_HOST is set, logs will be sent to the agent instead of directly to DataDog API. - In this case, DD_API_KEY and DD_SITE are not required (agent handles authentication). + Note: We use LITELLM_DD_AGENT_HOST instead of DD_AGENT_HOST to avoid conflicts + with ddtrace which automatically sets DD_AGENT_HOST for APM tracing. """ try: verbose_logger.debug("Datadog: in init datadog logger") @@ -85,7 +85,8 @@ class DataDogLogger( ) # Configure DataDog endpoint (Agent or Direct API) - dd_agent_host = os.getenv("DD_AGENT_HOST") + # Use LITELLM_DD_AGENT_HOST to avoid conflicts with ddtrace's DD_AGENT_HOST + dd_agent_host = os.getenv("LITELLM_DD_AGENT_HOST") if dd_agent_host: self._configure_dd_agent(dd_agent_host=dd_agent_host) else: @@ -127,7 +128,7 @@ class DataDogLogger( Args: dd_agent_host: Hostname or IP of DataDog agent """ - dd_agent_port = os.getenv("DD_AGENT_PORT", "10518") # default port for logs + dd_agent_port = os.getenv("LITELLM_DD_AGENT_PORT", "10518") # default port for logs self.intake_url = f"http://{dd_agent_host}:{dd_agent_port}/api/v2/logs" self.DD_API_KEY = os.getenv("DD_API_KEY") # Optional when using agent verbose_logger.debug(f"Datadog: Using DD Agent at {self.intake_url}") diff --git a/tests/logging_callback_tests/test_datadog.py b/tests/logging_callback_tests/test_datadog.py index 13125aa4952..c877f34ac03 100644 --- a/tests/logging_callback_tests/test_datadog.py +++ b/tests/logging_callback_tests/test_datadog.py @@ -633,11 +633,14 @@ async def test_datadog_message_redaction(): def test_datadog_agent_configuration(): """ - Test that DataDog logger correctly configures agent endpoint when DD_AGENT_HOST is set + Test that DataDog logger correctly configures agent endpoint when LITELLM_DD_AGENT_HOST is set. + + Note: We use LITELLM_DD_AGENT_HOST instead of DD_AGENT_HOST to avoid conflicts + with ddtrace which automatically sets DD_AGENT_HOST for APM tracing. """ test_env = { - "DD_AGENT_HOST": "localhost", - "DD_AGENT_PORT": "10518", + "LITELLM_DD_AGENT_HOST": "localhost", + "LITELLM_DD_AGENT_PORT": "10518", } # Remove DD_SITE and DD_API_KEY to verify they're not required for agent mode @@ -654,4 +657,40 @@ def test_datadog_agent_configuration(): assert dd_logger.intake_url == "http://localhost:10518/api/v2/logs", f"Expected agent URL, got {dd_logger.intake_url}" # Verify DD_API_KEY is optional (can be None) - assert dd_logger.DD_API_KEY is None or isinstance(dd_logger.DD_API_KEY, str) \ No newline at end of file + assert dd_logger.DD_API_KEY is None or isinstance(dd_logger.DD_API_KEY, str) + + +def test_datadog_ignores_ddtrace_agent_host(): + """ + Regression test: Ensure DD_AGENT_HOST set by ddtrace doesn't interfere with LiteLLM logging. + + When users have ddtrace installed for APM tracing, it automatically sets DD_AGENT_HOST. + LiteLLM should ignore DD_AGENT_HOST and only use LITELLM_DD_AGENT_HOST for agent mode. + + This prevents the 404 error when ddtrace's DD_AGENT_HOST points to an APM endpoint + that doesn't support /api/v2/logs. + + Regression test for: https://github.com/BerriAI/litellm/issues/16379 + """ + test_env = { + # User's explicit config for LiteLLM logging (direct API) + "DD_API_KEY": "fake-api-key", + "DD_SITE": "us5.datadoghq.com", + # ddtrace automatically sets these for APM tracing + "DD_AGENT_HOST": "10.176.100.40", + "DD_AGENT_PORT": "8126", + } + + with patch.dict(os.environ, test_env, clear=False): + with patch("asyncio.create_task"): + dd_logger = DataDogLogger() + + # Verify direct API endpoint is used (DD_AGENT_HOST should be ignored) + expected_url = "https://http-intake.logs.us5.datadoghq.com/api/v2/logs" + assert dd_logger.intake_url == expected_url, ( + f"Expected direct API URL '{expected_url}', got '{dd_logger.intake_url}'. " + "DD_AGENT_HOST (set by ddtrace) should be ignored - only LITELLM_DD_AGENT_HOST should trigger agent mode." + ) + + # Verify API key is set correctly + assert dd_logger.DD_API_KEY == "fake-api-key" \ No newline at end of file From 8f565a811c2b74b54106c015febf5f2aa77b5656 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 2 Dec 2025 17:31:03 -0800 Subject: [PATCH 272/349] Merge with main --- ui/litellm-dashboard/src/components/model_add/credentials.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.tsx index 73e5ce80585..1bba14bef97 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.tsx @@ -19,6 +19,8 @@ import { } from "@tremor/react"; import { Form } from "antd"; import { UploadProps } from "antd/es/upload"; +import { useEffect, useState } from "react"; +import DeleteResourceModal from "../common_components/DeleteResourceModal"; import NotificationsManager from "../molecules/notifications_manager"; import AddCredentialsTab from "./AddCredentialModal"; import EditCredentialsModal from "./EditCredentialModal"; From afedc9aac33fc41c7b54af5c3420384774472007 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 2 Dec 2025 17:34:53 -0800 Subject: [PATCH 273/349] Fixing test --- ui/litellm-dashboard/src/components/view_users/table.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/view_users/table.test.tsx b/ui/litellm-dashboard/src/components/view_users/table.test.tsx index c688d5749d0..82f49d9618e 100644 --- a/ui/litellm-dashboard/src/components/view_users/table.test.tsx +++ b/ui/litellm-dashboard/src/components/view_users/table.test.tsx @@ -105,7 +105,7 @@ describe("UserDataTable", () => { "Spend (USD)", "Budget (USD)", "SSO ID", - "API Keys", + "Virtual Keys", "Created At", "Updated At", "Actions", From 7fea97a0c0ca7def95d9c048931fe2b9b6b3a3e2 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 2 Dec 2025 17:53:16 -0800 Subject: [PATCH 274/349] Add is_sso_configured to UI Config --- .../ui_discovery_endpoints.py | 3 +- .../ui_discovery_endpoints.py | 1 + .../test_ui_discovery_endpoints.py | 86 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py diff --git a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py index 9a6032fd084..b297634d6be 100644 --- a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -14,8 +14,9 @@ router = APIRouter() ) # if mounted at root path async def get_ui_config(): from litellm.proxy.utils import get_proxy_base_url, get_server_root_path - + from litellm.proxy.auth.auth_utils import _has_user_setup_sso return UiDiscoveryEndpoints( server_root_path=get_server_root_path(), proxy_base_url=get_proxy_base_url(), + is_sso_configured=_has_user_setup_sso(), ) diff --git a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py index 7132077b5fa..88f0627f533 100644 --- a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -6,3 +6,4 @@ from pydantic import BaseModel class UiDiscoveryEndpoints(BaseModel): server_root_path: str proxy_base_url: Optional[str] + is_sso_configured: bool diff --git a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py new file mode 100644 index 00000000000..b6e064823c5 --- /dev/null +++ b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py @@ -0,0 +1,86 @@ +import os +import sys +from unittest.mock import patch + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../..") +) + +from litellm.proxy.discovery_endpoints.ui_discovery_endpoints import router + + +def test_ui_discovery_endpoints_with_defaults(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/" + assert data["proxy_base_url"] is None + assert data["is_sso_configured"] is False + + +def test_ui_discovery_endpoints_with_custom_server_root_path(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/litellm" + assert data["proxy_base_url"] is None + assert data["is_sso_configured"] is False + + +def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + + response = client.get("/litellm/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/" + assert data["proxy_base_url"] == "https://proxy.example.com" + assert data["is_sso_configured"] is False + + +def test_ui_discovery_endpoints_with_sso_configured_when_sso_is_setup(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/litellm" + assert data["proxy_base_url"] == "https://proxy.example.com" + assert data["is_sso_configured"] is True + From 209e9e05aa3a5543639df154fe54974e0b708e9a Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 10:13:59 +0530 Subject: [PATCH 275/349] Fix gemini 3 last chunk thinking block --- .../vertex_and_google_ai_studio_gemini.py | 5 +++++ .../test_gemini_reasoning_content.py | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/local_testing/test_gemini_reasoning_content.py diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 5fef8c1ec49..cfc13f5601d 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -1091,6 +1091,11 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): if "thoughtSignature" in part: part_copy = part.copy() part_copy.pop("thoughtSignature") + + text_content = part_copy.get("text") + if isinstance(text_content, str) and text_content.strip() == "": + continue + thinking_blocks.append( ChatCompletionThinkingBlock( type="thinking", diff --git a/tests/local_testing/test_gemini_reasoning_content.py b/tests/local_testing/test_gemini_reasoning_content.py new file mode 100644 index 00000000000..7e516ae8439 --- /dev/null +++ b/tests/local_testing/test_gemini_reasoning_content.py @@ -0,0 +1,20 @@ +import json +from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexGeminiConfig + + +def test_empty_part_does_not_create_thinking_block(): + parts = [{"text": "", "thoughtSignature": "sig-1"}] + config = VertexGeminiConfig() + thinking_blocks = config._extract_thinking_blocks_from_parts(parts) + assert thinking_blocks == [] + + +def test_non_empty_part_creates_thinking_block(): + parts = [{"text": "Some thinking", "thoughtSignature": "sig-2"}] + config = VertexGeminiConfig() + thinking_blocks = config._extract_thinking_blocks_from_parts(parts) + assert len(thinking_blocks) == 1 + block = thinking_blocks[0] + # thinking should be valid JSON containing the text + parsed = json.loads(block["thinking"]) if isinstance(block["thinking"], str) else None + assert parsed is not None and parsed.get("text") == "Some thinking" From 40c203e32b5991cefa14d9d7befaac6385ee0538 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 11:04:53 +0530 Subject: [PATCH 276/349] Make thought sign in tool call id as a beta feat --- .../vertex_and_google_ai_studio_gemini.py | 12 +- .../test_thought_signature_in_tool_call_id.py | 277 ++++++++++++------ 2 files changed, 188 insertions(+), 101 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 5fef8c1ec49..1c2efe1eaa7 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -1205,14 +1205,16 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): } # Embed thought signature in ID for OpenAI client compatibility if thought_signature: - _tool_response_chunk[ - "id" - ] = _encode_tool_call_id_with_signature( - _tool_response_chunk["id"] or "", thought_signature - ) _tool_response_chunk["provider_specific_fields"] = { # type: ignore "thought_signature": thought_signature } + # Only embed in ID if preview features are enabled + if litellm.enable_preview_features: + _tool_response_chunk[ + "id" + ] = _encode_tool_call_id_with_signature( + _tool_response_chunk["id"] or "", thought_signature + ) _tools.append(_tool_response_chunk) cumulative_tool_call_idx += 1 if len(_tools) == 0: diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_thought_signature_in_tool_call_id.py b/tests/test_litellm/llms/vertex_ai/gemini/test_thought_signature_in_tool_call_id.py index 68c0f3bdbfc..46bb8930a7a 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_thought_signature_in_tool_call_id.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_thought_signature_in_tool_call_id.py @@ -4,9 +4,13 @@ Tests for embedding thought signatures in tool call IDs for OpenAI client compat When using OpenAI clients (instead of LiteLLM SDK), provider_specific_fields are not preserved. This test suite validates that thought signatures can be embedded in tool call IDs and extracted when converting back to Gemini format. + +Note: Embedding signatures in tool call IDs is a beta feature that requires +enable_preview_features=True to be enabled. """ import pytest +import litellm from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( VertexGeminiConfig, ) @@ -62,36 +66,57 @@ def test_encode_tool_call_id_without_signature(): assert decoded_signature is None -def test_tool_call_id_includes_signature_in_response(): - """Test that tool call IDs in responses include embedded thought signatures""" +@pytest.mark.parametrize("enable_preview_features", [True, False]) +def test_tool_call_id_includes_signature_in_response(enable_preview_features): + """Test that tool call IDs in responses include embedded thought signatures only when preview features are enabled""" test_signature = "Co4CAdHtim/rWgXbz2Ghp4tShzLeMASrPw6JJyYIC3cbVyZnKzU3uv8/wVzyS2sKRPL2m8QQHHXbNQhEEz500G7n/4ZMmksdTtfQcJMoT76S1DGwhnAiLwTgWCNXs3lEb4M19EVYoWFxhrH5Lr9YMIquoU9U4paydGwvZyIyigamIg4B6WnxrRsf0KZV12gJed0DZuKczvOFtHz3zUnmZRlOiTzd5gBVyQM+5jv1VI8m4WUKd6cN/5a5ZvaA0ggiO6kdVhlpIVs7GczSEVJD8KH4u02X7VSnb7CvykqDntZzV0y8rZFBEFGKrChmeHlWXP4D1IB3F9KQyhuLgWImMzg4BajKVxxMU737JGnNISy5" - parts_with_signature = [ - HttpxPartType( - functionCall={ - "name": "get_current_temperature", - "args": {"location": "Paris"}, - }, - thoughtSignature=test_signature, + # Save original state + original_flag = litellm.enable_preview_features + litellm.enable_preview_features = enable_preview_features + + try: + parts_with_signature = [ + HttpxPartType( + functionCall={ + "name": "get_current_temperature", + "args": {"location": "Paris"}, + }, + thoughtSignature=test_signature, + ) + ] + + function, tools, _ = VertexGeminiConfig._transform_parts( + parts=parts_with_signature, + cumulative_tool_call_idx=0, + is_function_call=False, ) - ] - function, tools, _ = VertexGeminiConfig._transform_parts( - parts=parts_with_signature, - cumulative_tool_call_idx=0, - is_function_call=False, - ) + # Verify tool call exists + assert tools is not None + assert len(tools) == 1 + tool_call_id = tools[0]["id"] + + # Verify signature is always in provider_specific_fields + assert tools[0].get("provider_specific_fields", {}).get("thought_signature") == test_signature - # Verify tool call ID includes thought signature - assert tools is not None - assert len(tools) == 1 - tool_call_id = tools[0]["id"] - assert THOUGHT_SIGNATURE_SEPARATOR in tool_call_id - - # Verify we can decode it using the factory function - tool_obj = {"id": tool_call_id, "type": "function"} - decoded_sig = _get_thought_signature_from_tool(tool_obj) - assert decoded_sig == test_signature + if enable_preview_features: + # When preview features enabled, signature should be embedded in ID + assert THOUGHT_SIGNATURE_SEPARATOR in tool_call_id + # Verify we can decode it using the factory function + tool_obj = {"id": tool_call_id, "type": "function"} + decoded_sig = _get_thought_signature_from_tool(tool_obj) + assert decoded_sig == test_signature + else: + # When preview features disabled, signature should NOT be embedded in ID + assert THOUGHT_SIGNATURE_SEPARATOR not in tool_call_id + # But we can still extract from provider_specific_fields + tool_obj = {"id": tool_call_id, "type": "function", "provider_specific_fields": {"thought_signature": test_signature}} + decoded_sig = _get_thought_signature_from_tool(tool_obj) + assert decoded_sig == test_signature + finally: + # Restore original state + litellm.enable_preview_features = original_flag def test_get_thought_signature_backward_compatibility(): @@ -168,97 +193,157 @@ def test_convert_to_gemini_with_embedded_signature(): assert gemini_parts[0]["thoughtSignature"] == test_signature -def test_openai_client_e2e_flow(): +@pytest.mark.parametrize("enable_preview_features", [True, False]) +def test_openai_client_e2e_flow(enable_preview_features): """ End-to-end test simulating OpenAI client usage: 1. LiteLLM receives response from Gemini with thought signature - 2. LiteLLM embeds signature in tool call ID + 2. LiteLLM embeds signature in tool call ID (if preview features enabled) 3. OpenAI client sends message back with same tool call ID - 4. LiteLLM extracts signature from ID and sends to Gemini + 4. LiteLLM extracts signature from ID/provider_specific_fields and sends to Gemini """ test_signature = "Co4CAdHtim/rWgXbz2Ghp4tShzLeMASrPw6JJyYIC3cbVyZnKzU3uv8/wVzyS2sKRPL2m8QQHHXbNQhEEz500G7n/4ZMmksdTtfQcJMoT76S1DGwhnAiLwTgWCNXs3lEb4M19EVYoWFxhrH5Lr9YMIquoU9U4paydGwvZyIyigamIg4B6WnxrRsf0KZV12gJed0DZuKczvOFtHz3zUnmZRlOiTzd5gBVyQM+5jv1VI8m4WUKd6cN/5a5ZvaA0ggiO6kdVhlpIVs7GczSEVJD8KH4u02X7VSnb7CvykqDntZzV0y8rZFBEFGKrChmeHlWXP4D1IB3F9KQyhuLgWImMzg4BajKVxxMU737JGnNISy5" - # Step 1: Gemini returns function call with thought signature - gemini_parts = [ - HttpxPartType( - functionCall={ - "name": "get_current_temperature", - "args": {"location": "Paris"}, - }, - thoughtSignature=test_signature, - ) - ] + # Save original state + original_flag = litellm.enable_preview_features + litellm.enable_preview_features = enable_preview_features - # Step 2: LiteLLM transforms to OpenAI format with embedded signature - function, tools, _ = VertexGeminiConfig._transform_parts( - parts=gemini_parts, - cumulative_tool_call_idx=0, - is_function_call=False, - ) - - assert tools is not None - assert len(tools) == 1 - tool_call_id = tools[0]["id"] - assert THOUGHT_SIGNATURE_SEPARATOR in tool_call_id - - # Step 3: OpenAI client sends back assistant message (preserves tool_call_id) - openai_assistant_message = { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": tool_call_id, # Preserved from response - "type": "function", - "function": { + try: + # Step 1: Gemini returns function call with thought signature + gemini_parts = [ + HttpxPartType( + functionCall={ "name": "get_current_temperature", - "arguments": '{"location": "Paris"}', + "args": {"location": "Paris"}, }, + thoughtSignature=test_signature, + ) + ] + + # Step 2: LiteLLM transforms to OpenAI format + function, tools, _ = VertexGeminiConfig._transform_parts( + parts=gemini_parts, + cumulative_tool_call_idx=0, + is_function_call=False, + ) + + assert tools is not None + assert len(tools) == 1 + tool_call_id = tools[0]["id"] + + if enable_preview_features: + # When preview features enabled, signature should be embedded in ID + assert THOUGHT_SIGNATURE_SEPARATOR in tool_call_id + else: + # When preview features disabled, signature should NOT be embedded in ID + assert THOUGHT_SIGNATURE_SEPARATOR not in tool_call_id + + # Step 3: OpenAI client sends back assistant message + # For the disabled case, we simulate that the client might have provider_specific_fields + # or we use the embedded ID if preview features were enabled + if enable_preview_features: + openai_assistant_message = { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": tool_call_id, # Preserved from response (with embedded signature) + "type": "function", + "function": { + "name": "get_current_temperature", + "arguments": '{"location": "Paris"}', + }, + } + ], + } + else: + # When preview features disabled, simulate that provider_specific_fields might be preserved + # (though in real OpenAI client usage, this might not happen) + # For this test, we'll use provider_specific_fields to show extraction still works + openai_assistant_message = { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": tool_call_id, # ID without embedded signature + "type": "function", + "function": { + "name": "get_current_temperature", + "arguments": '{"location": "Paris"}', + }, + "provider_specific_fields": {"thought_signature": test_signature}, + } + ], } - ], - } - # Step 4: LiteLLM converts back to Gemini format, extracting signature - gemini_parts_converted = convert_to_gemini_tool_call_invoke( - openai_assistant_message - ) + # Step 4: LiteLLM converts back to Gemini format, extracting signature + gemini_parts_converted = convert_to_gemini_tool_call_invoke( + openai_assistant_message + ) - # Verify signature is preserved through the round trip - assert len(gemini_parts_converted) == 1 - assert "thoughtSignature" in gemini_parts_converted[0] - assert gemini_parts_converted[0]["thoughtSignature"] == test_signature + # Verify signature is preserved through the round trip + assert len(gemini_parts_converted) == 1 + assert "thoughtSignature" in gemini_parts_converted[0] + assert gemini_parts_converted[0]["thoughtSignature"] == test_signature + finally: + # Restore original state + litellm.enable_preview_features = original_flag -def test_parallel_tool_calls_with_signatures(): +@pytest.mark.parametrize("enable_preview_features", [True, False]) +def test_parallel_tool_calls_with_signatures(enable_preview_features): """Test that parallel tool calls preserve signatures correctly""" signature1 = "signature_for_first_call" # Only first call has signature (Gemini behavior for parallel calls) - gemini_parts = [ - HttpxPartType( - functionCall={"name": "get_temperature", "args": {"location": "Paris"}}, - thoughtSignature=signature1, - ), - HttpxPartType( - functionCall={"name": "get_temperature", "args": {"location": "London"}}, - # No signature for second parallel call - ), - ] + # Save original state + original_flag = litellm.enable_preview_features + litellm.enable_preview_features = enable_preview_features - function, tools, _ = VertexGeminiConfig._transform_parts( - parts=gemini_parts, - cumulative_tool_call_idx=0, - is_function_call=False, - ) + try: + gemini_parts = [ + HttpxPartType( + functionCall={"name": "get_temperature", "args": {"location": "Paris"}}, + thoughtSignature=signature1, + ), + HttpxPartType( + functionCall={"name": "get_temperature", "args": {"location": "London"}}, + # No signature for second parallel call + ), + ] - assert tools is not None - assert len(tools) == 2 + function, tools, _ = VertexGeminiConfig._transform_parts( + parts=gemini_parts, + cumulative_tool_call_idx=0, + is_function_call=False, + ) - # First tool call has signature in ID - assert THOUGHT_SIGNATURE_SEPARATOR in tools[0]["id"] - sig1 = _get_thought_signature_from_tool({"id": tools[0]["id"], "type": "function"}) - assert sig1 == signature1 + assert tools is not None + assert len(tools) == 2 - # Second tool call has no signature in ID - assert THOUGHT_SIGNATURE_SEPARATOR not in tools[1]["id"] - sig2 = _get_thought_signature_from_tool({"id": tools[1]["id"], "type": "function"}) - assert sig2 is None + # First tool call should have signature in provider_specific_fields + assert tools[0].get("provider_specific_fields", {}).get("thought_signature") == signature1 + + if enable_preview_features: + # When preview features enabled, first tool call has signature in ID + assert THOUGHT_SIGNATURE_SEPARATOR in tools[0]["id"] + sig1 = _get_thought_signature_from_tool({"id": tools[0]["id"], "type": "function"}) + assert sig1 == signature1 + else: + # When preview features disabled, signature should NOT be in ID + assert THOUGHT_SIGNATURE_SEPARATOR not in tools[0]["id"] + # But we can extract from provider_specific_fields + sig1 = _get_thought_signature_from_tool({ + "id": tools[0]["id"], + "type": "function", + "provider_specific_fields": {"thought_signature": signature1} + }) + assert sig1 == signature1 + + # Second tool call has no signature in ID (regardless of flag) + assert THOUGHT_SIGNATURE_SEPARATOR not in tools[1]["id"] + sig2 = _get_thought_signature_from_tool({"id": tools[1]["id"], "type": "function"}) + assert sig2 is None + finally: + # Restore original state + litellm.enable_preview_features = original_flag From 099ccf56a746c9bca8afa4c0c62fdb52fa5abfa9 Mon Sep 17 00:00:00 2001 From: Richard Song <9144514+richardmcsong@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:57:07 -0500 Subject: [PATCH 277/349] Refactor add_schema_to_components to move definitions to components/schemas and add corresponding unit test (#17389) --- .../proxy/common_utils/custom_openapi_spec.py | 2 +- .../common_utils/test_custom_openapi_spec.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/common_utils/custom_openapi_spec.py b/litellm/proxy/common_utils/custom_openapi_spec.py index c448742f6dc..69472c2cda4 100644 --- a/litellm/proxy/common_utils/custom_openapi_spec.py +++ b/litellm/proxy/common_utils/custom_openapi_spec.py @@ -72,7 +72,7 @@ class CustomOpenAPISpec: openapi_schema["components"]["schemas"] = {} # Add the schema - openapi_schema["components"]["schemas"][schema_name] = schema_def + CustomOpenAPISpec._move_defs_to_components(openapi_schema, {schema_name: schema_def}) @staticmethod def add_request_body_to_paths(openapi_schema: Dict[str, Any], paths: List[str], schema_ref: str) -> None: diff --git a/tests/test_litellm/proxy/common_utils/test_custom_openapi_spec.py b/tests/test_litellm/proxy/common_utils/test_custom_openapi_spec.py index 7549b4259af..5fef35eb821 100644 --- a/tests/test_litellm/proxy/common_utils/test_custom_openapi_spec.py +++ b/tests/test_litellm/proxy/common_utils/test_custom_openapi_spec.py @@ -90,6 +90,35 @@ class TestCustomOpenAPISpec: ) assert result == base_openapi_schema +def test_defs_rewritten_in_add_schema_to_components(): + """ + Test that defs are rewritten to components/schemas in add_schema_to_components. + """ + + openapi_schema = {} + schema_name = "SchemaName" + schema_def = { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "anyOf": [ + {"$ref": "#/$defs/UserMessage"}, + {"$ref": "#/$defs/AssistantMessage"} + ] + } + } + }, + "$defs": { + "UserMessage": {"type": "object"}, + "AssistantMessage": {"type": "object"} + } + } + CustomOpenAPISpec.add_schema_to_components(openapi_schema=openapi_schema, schema_name=schema_name, schema_def=schema_def) + assert "$defs" not in openapi_schema + assert openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"]["items"]["anyOf"][0]["$ref"] == "#/components/schemas/UserMessage" + assert openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"]["items"]["anyOf"][1]["$ref"] == "#/components/schemas/AssistantMessage" def test_move_defs_to_components(): """ From f22bc0aab20e9b5336e73d67ba1631176cbacfd6 Mon Sep 17 00:00:00 2001 From: Matt Greathouse Date: Wed, 3 Dec 2025 01:00:19 -0500 Subject: [PATCH 278/349] Support Deepseek 3.2 with Reasoning (#17384) * Add openrouter/deepseek/deepseek-v3.2 * Added deepseek-provided v3.2 * Allow reasoning effort param for openrouter models that support it * Added tests --- .../llms/openrouter/chat/transformation.py | 15 ++++++++++ model_prices_and_context_window.json | 30 +++++++++++++++++++ .../test_openrouter_chat_transformation.py | 27 +++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/litellm/llms/openrouter/chat/transformation.py b/litellm/llms/openrouter/chat/transformation.py index f1eafe4e294..b5610852fd2 100644 --- a/litellm/llms/openrouter/chat/transformation.py +++ b/litellm/llms/openrouter/chat/transformation.py @@ -10,6 +10,7 @@ from enum import Enum from typing import Any, AsyncIterator, Iterator, List, Optional, Tuple, Union, cast import httpx +import litellm from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator from litellm.llms.base_llm.chat.transformation import BaseLLMException @@ -28,6 +29,20 @@ class CacheControlSupportedModels(str, Enum): class OpenrouterConfig(OpenAIGPTConfig): + def get_supported_openai_params(self, model: str) -> list: + """ + Allow reasoning parameters for models flagged as reasoning-capable. + """ + supported_params = super().get_supported_openai_params(model=model) + try: + if litellm.supports_reasoning( + model=model, custom_llm_provider="openrouter" + ) or litellm.supports_reasoning(model=model): + supported_params.append("reasoning_effort") + except Exception: + pass + return list(dict.fromkeys(supported_params)) + def map_openai_params( self, non_default_params: dict, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 19ed734c5f8..f82abce525f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -9629,6 +9629,21 @@ "supports_prompt_caching": true, "supports_tool_choice": true }, + "deepseek/deepseek-v3.2": { + "input_cost_per_token": 2.8e-07, + "input_cost_per_token_cache_hit": 2.8e-08, + "litellm_provider": "deepseek", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 4e-07, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "deepseek.v3-v1:0": { "input_cost_per_token": 5.8e-07, "litellm_provider": "bedrock_converse", @@ -20565,6 +20580,21 @@ "supports_reasoning": true, "supports_tool_choice": true }, + "openrouter/deepseek/deepseek-v3.2": { + "input_cost_per_token": 2.8e-07, + "input_cost_per_token_cache_hit": 2.8e-08, + "litellm_provider": "openrouter", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 4e-07, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "openrouter/deepseek/deepseek-v3.2-exp": { "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, diff --git a/tests/test_litellm/llms/openrouter/chat/test_openrouter_chat_transformation.py b/tests/test_litellm/llms/openrouter/chat/test_openrouter_chat_transformation.py index 64ac299fd79..d5a73b3fd12 100644 --- a/tests/test_litellm/llms/openrouter/chat/test_openrouter_chat_transformation.py +++ b/tests/test_litellm/llms/openrouter/chat/test_openrouter_chat_transformation.py @@ -489,3 +489,30 @@ def test_openrouter_cost_tracking_streaming(): # Verify cost field is preserved in the Usage object - this is the key data for cost tracking # The chunk_parser converts the dict to a Usage Pydantic model which includes the cost field assert result2.usage.cost == 0.0001 + + +def test_openrouter_reasoning_models_allow_reasoning_effort_param(): + """ + OpenRouter reasoning-capable models should accept the reasoning_effort param. + """ + config = OpenrouterConfig() + + supported_params = config.get_supported_openai_params( + model="openrouter/deepseek/deepseek-v3.2" + ) + + assert "reasoning_effort" in supported_params + assert supported_params.count("reasoning_effort") == 1 + + +def test_openrouter_non_reasoning_models_do_not_add_reasoning_effort(): + """ + Models without reasoning support should not gain reasoning-specific params. + """ + config = OpenrouterConfig() + + supported_params = config.get_supported_openai_params( + model="openrouter/anthropic/claude-3-5-haiku" + ) + + assert "reasoning_effort" not in supported_params From ae633184f72d8a12ae442a03ce86f51fc4f649fc Mon Sep 17 00:00:00 2001 From: mossbanay <2216177+mossbanay@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:02:57 +1100 Subject: [PATCH 279/349] Add model price & details for Bedrock model global.anthropic.claude-opus-4-5-20251101-v1:0 (#17380) --- model_prices_and_context_window.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f82abce525f..999c88dde05 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -23850,6 +23850,32 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "global.anthropic.claude-opus-4-5-20251101-v1:0": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "us.anthropic.claude-sonnet-4-20250514-v1:0": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, From 6b5ad5d5a6c9cce7008d0eb57258a826e6d82808 Mon Sep 17 00:00:00 2001 From: Ali Saleh Date: Wed, 3 Dec 2025 11:03:54 +0500 Subject: [PATCH 280/349] docs: Update Instructions For Phoenix Integration (#17373) --- .../docs/observability/phoenix_integration.md | 79 +++++++++++++++---- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/docs/my-website/docs/observability/phoenix_integration.md b/docs/my-website/docs/observability/phoenix_integration.md index ad337439934..898d780668d 100644 --- a/docs/my-website/docs/observability/phoenix_integration.md +++ b/docs/my-website/docs/observability/phoenix_integration.md @@ -6,7 +6,7 @@ Open source tracing and evaluation platform :::tip -This is community maintained, Please make an issue if you run into a bug +This is community maintained. Please make an issue if you run into a bug: https://github.com/BerriAI/litellm ::: @@ -31,19 +31,16 @@ litellm.callbacks = ["arize_phoenix"] import litellm import os -os.environ["PHOENIX_API_KEY"] = "" # Necessary only using Phoenix Cloud -os.environ["PHOENIX_COLLECTOR_HTTP_ENDPOINT"] = "" # The URL of your Phoenix OSS instance e.g. http://localhost:6006/v1/traces -os.environ["PHOENIX_PROJECT_NAME"]="litellm" # OPTIONAL: you can configure project names, otherwise traces would go to "default" project +# Set env variables +os.environ["PHOENIX_API_KEY"] = "d0*****" # Set the Phoenix API key here. It is necessary only when using Phoenix Cloud. +os.environ["PHOENIX_COLLECTOR_HTTP_ENDPOINT"] = "https://app.phoenix.arize.com/s//v1/traces" # Set the URL of your Phoenix OSS instance, otherwise tracer would use https://app.phoenix.arize.com/v1/traces for Phoenix Cloud. +os.environ["PHOENIX_PROJECT_NAME"] = "litellm" # Configure the project name, otherwise traces would go to "default" project. +os.environ['OPENAI_API_KEY'] = "fake-key" # Set the OpenAI API key here. -# This defaults to https://app.phoenix.arize.com/v1/traces for Phoenix Cloud - -# LLM API Keys -os.environ['OPENAI_API_KEY']="" - -# set arize as a callback, litellm will send the data to arize +# Set arize_phoenix as a callback & LiteLLM will send the data to Phoenix. litellm.callbacks = ["arize_phoenix"] - -# openai call + +# OpenAI call response = litellm.completion( model="gpt-3.5-turbo", messages=[ @@ -52,8 +49,9 @@ response = litellm.completion( ) ``` -### Using with LiteLLM Proxy +## Using with LiteLLM Proxy +1. Setup config.yaml ```yaml model_list: @@ -66,12 +64,63 @@ model_list: litellm_settings: callbacks: ["arize_phoenix"] +general_settings: + master_key: "sk-1234" + environment_variables: PHOENIX_API_KEY: "d0*****" - PHOENIX_COLLECTOR_ENDPOINT: "https://app.phoenix.arize.com/v1/traces" # OPTIONAL, for setting the GRPC endpoint - PHOENIX_COLLECTOR_HTTP_ENDPOINT: "https://app.phoenix.arize.com/v1/traces" # OPTIONAL, for setting the HTTP endpoint + PHOENIX_COLLECTOR_ENDPOINT: "https://app.phoenix.arize.com/s//v1/traces" # OPTIONAL - For setting the gRPC endpoint + PHOENIX_COLLECTOR_HTTP_ENDPOINT: "https://app.phoenix.arize.com/s//v1/traces" # OPTIONAL - For setting the HTTP endpoint ``` +2. Start the proxy + +```bash +litellm --config config.yaml +``` + +3. Test it! + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "Hi 👋 - i'm openai"}]}' +``` + +## Supported Phoenix Endpoints +Phoenix now supports multiple deployment types. The correct endpoint depends on which version of Phoenix Cloud you are using. + +**Phoenix Cloud (With Spaces - New Version)** +Use this if your Phoenix URL contains `/s/` path. + +```bash +https://app.phoenix.arize.com/s//v1/traces +``` + +**Phoenix Cloud (Legacy - Deprecated)** +Use this only if your deployment still shows the `/legacy` pattern. + +```bash +https://app.phoenix.arize.com/legacy/v1/traces +``` + +**Phoenix Cloud (Without Spaces - Old Version)** +Use this if your Phoenix Cloud URL does not contain `/s/` or `/legacy` path. + +```bash +https://app.phoenix.arize.com/v1/traces +``` + +**Self-Hosted Phoenix (Local Instance)** +Use this when running Phoenix on your machine or a private server. + +```bash +http://localhost:6006/v1/traces +``` + +Depending on which Phoenix Cloud version or deployment you are using, you should set the corresponding endpoint in `PHOENIX_COLLECTOR_HTTP_ENDPOINT` or `PHOENIX_COLLECTOR_ENDPOINT`. + ## Support & Talk to Founders - [Schedule Demo 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version) From 566adebdec41d3f2790de33e43d93892f529f597 Mon Sep 17 00:00:00 2001 From: Mariano Hielpos <108539968+mhielpos-asapp@users.noreply.github.com> Date: Wed, 3 Dec 2025 03:06:51 -0300 Subject: [PATCH 281/349] update model_prices_and_context_window.json (#17376) * update model_prices_and_context_window.json * update * update --- ...odel_prices_and_context_window_backup.json | 88 +++++++++++++++++-- model_prices_and_context_window.json | 88 +++++++++++++++++-- 2 files changed, 160 insertions(+), 16 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 932508824af..464f9c185f9 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -10772,25 +10772,25 @@ "supports_tool_choice": true }, "ft:babbage-002": { - "input_cost_per_token": 4e-07, + "input_cost_per_token": 1.6e-06, "input_cost_per_token_batches": 2e-07, "litellm_provider": "text-completion-openai", "max_input_tokens": 16384, "max_output_tokens": 4096, "max_tokens": 16384, "mode": "completion", - "output_cost_per_token": 4e-07, + "output_cost_per_token": 1.6e-06, "output_cost_per_token_batches": 2e-07 }, "ft:davinci-002": { - "input_cost_per_token": 2e-06, + "input_cost_per_token": 1.2e-05, "input_cost_per_token_batches": 1e-06, "litellm_provider": "text-completion-openai", "max_input_tokens": 16384, "max_output_tokens": 4096, "max_tokens": 16384, "mode": "completion", - "output_cost_per_token": 2e-06, + "output_cost_per_token": 1.2e-05, "output_cost_per_token_batches": 1e-06 }, "ft:gpt-3.5-turbo": { @@ -10853,6 +10853,7 @@ "supports_tool_choice": true }, "ft:gpt-4o-2024-08-06": { + "cache_read_input_token_cost": 1.875e-06, "input_cost_per_token": 3.75e-06, "input_cost_per_token_batches": 1.875e-06, "litellm_provider": "openai", @@ -10865,6 +10866,7 @@ "supports_function_calling": true, "supports_parallel_function_calling": true, "supports_pdf_input": true, + "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, @@ -10885,8 +10887,7 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true + "supports_tool_choice": true }, "ft:gpt-4o-mini-2024-07-18": { "cache_read_input_token_cost": 1.5e-07, @@ -10905,8 +10906,79 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true + "supports_tool_choice": true + }, + "ft:gpt-4.1-2025-04-14": { + "cache_read_input_token_cost": 7.5e-07, + "input_cost_per_token": 3e-06, + "input_cost_per_token_batches": 1.5e-06, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 1.2e-05, + "output_cost_per_token_batches": 6e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:gpt-4.1-mini-2025-04-14": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 8e-07, + "input_cost_per_token_batches": 4e-07, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 3.2e-06, + "output_cost_per_token_batches": 1.6e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:gpt-4.1-nano-2025-04-14": { + "cache_read_input_token_cost": 5e-08, + "input_cost_per_token": 2e-07, + "input_cost_per_token_batches": 1e-07, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8e-07, + "output_cost_per_token_batches": 4e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:o4-mini-2025-04-16": { + "cache_read_input_token_cost": 1e-06, + "input_cost_per_token": 4e-06, + "input_cost_per_token_batches": 2e-06, + "litellm_provider": "openai", + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "max_tokens": 100000, + "mode": "chat", + "output_cost_per_token": 1.6e-05, + "output_cost_per_token_batches": 8e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true }, "gemini-1.0-pro": { "input_cost_per_character": 1.25e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 999c88dde05..dbaa60e0f1d 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -10852,25 +10852,25 @@ "supports_tool_choice": true }, "ft:babbage-002": { - "input_cost_per_token": 4e-07, + "input_cost_per_token": 1.6e-06, "input_cost_per_token_batches": 2e-07, "litellm_provider": "text-completion-openai", "max_input_tokens": 16384, "max_output_tokens": 4096, "max_tokens": 16384, "mode": "completion", - "output_cost_per_token": 4e-07, + "output_cost_per_token": 1.6e-06, "output_cost_per_token_batches": 2e-07 }, "ft:davinci-002": { - "input_cost_per_token": 2e-06, + "input_cost_per_token": 1.2e-05, "input_cost_per_token_batches": 1e-06, "litellm_provider": "text-completion-openai", "max_input_tokens": 16384, "max_output_tokens": 4096, "max_tokens": 16384, "mode": "completion", - "output_cost_per_token": 2e-06, + "output_cost_per_token": 1.2e-05, "output_cost_per_token_batches": 1e-06 }, "ft:gpt-3.5-turbo": { @@ -10933,6 +10933,7 @@ "supports_tool_choice": true }, "ft:gpt-4o-2024-08-06": { + "cache_read_input_token_cost": 1.875e-06, "input_cost_per_token": 3.75e-06, "input_cost_per_token_batches": 1.875e-06, "litellm_provider": "openai", @@ -10945,6 +10946,7 @@ "supports_function_calling": true, "supports_parallel_function_calling": true, "supports_pdf_input": true, + "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, @@ -10965,8 +10967,7 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true + "supports_tool_choice": true }, "ft:gpt-4o-mini-2024-07-18": { "cache_read_input_token_cost": 1.5e-07, @@ -10985,8 +10986,79 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true + "supports_tool_choice": true + }, + "ft:gpt-4.1-2025-04-14": { + "cache_read_input_token_cost": 7.5e-07, + "input_cost_per_token": 3e-06, + "input_cost_per_token_batches": 1.5e-06, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 1.2e-05, + "output_cost_per_token_batches": 6e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:gpt-4.1-mini-2025-04-14": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 8e-07, + "input_cost_per_token_batches": 4e-07, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 3.2e-06, + "output_cost_per_token_batches": 1.6e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:gpt-4.1-nano-2025-04-14": { + "cache_read_input_token_cost": 5e-08, + "input_cost_per_token": 2e-07, + "input_cost_per_token_batches": 1e-07, + "litellm_provider": "openai", + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8e-07, + "output_cost_per_token_batches": 4e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "ft:o4-mini-2025-04-16": { + "cache_read_input_token_cost": 1e-06, + "input_cost_per_token": 4e-06, + "input_cost_per_token_batches": 2e-06, + "litellm_provider": "openai", + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "max_tokens": 100000, + "mode": "chat", + "output_cost_per_token": 1.6e-05, + "output_cost_per_token_batches": 8e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true }, "gemini-1.0-pro": { "input_cost_per_character": 1.25e-07, From 17faea96bb75be88f6761398e3ad2130a0d29cfc Mon Sep 17 00:00:00 2001 From: Jonathan Yang Date: Wed, 3 Dec 2025 07:09:57 +0100 Subject: [PATCH 282/349] fix: conditionally pass enable_cleanup_closed to aiohttp TCPConnector (#17367) * fix: conditionally pass enable_cleanup_closed to aiohttp TCPConnector Fixes deprecation warning on Python 3.12.7+ and 3.13.1+ where enable_cleanup_closed is no longer needed since the underlying CPython SSL connection leak bug was fixed. See: https://github.com/python/cpython/pull/118960 * chore: add aiohttp source reference to AIOHTTP_NEEDS_CLEANUP_CLOSED --- litellm/constants.py | 7 +++++++ litellm/llms/custom_httpx/http_handler.py | 3 ++- litellm/proxy/proxy_server.py | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 6a67a9a0e18..db617a2e475 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1,4 +1,5 @@ import os +import sys from typing import List, Literal DEFAULT_HEALTH_CHECK_PROMPT = str( @@ -103,6 +104,12 @@ _DEFAULT_TTL_FOR_HTTPX_CLIENTS = 3600 # 1 hour, re-use the same httpx client fo AIOHTTP_CONNECTOR_LIMIT = int(os.getenv("AIOHTTP_CONNECTOR_LIMIT", 0)) AIOHTTP_KEEPALIVE_TIMEOUT = int(os.getenv("AIOHTTP_KEEPALIVE_TIMEOUT", 120)) AIOHTTP_TTL_DNS_CACHE = int(os.getenv("AIOHTTP_TTL_DNS_CACHE", 300)) +# enable_cleanup_closed is only needed for Python versions with the SSL leak bug +# Fixed in Python 3.12.7+ and 3.13.1+ (see https://github.com/python/cpython/pull/118960) +# Reference: https://github.com/aio-libs/aiohttp/blob/master/aiohttp/connector.py#L74-L78 +AIOHTTP_NEEDS_CLEANUP_CLOSED = ( + (3, 13, 0) <= sys.version_info < (3, 13, 1) or sys.version_info < (3, 12, 7) +) # WebSocket constants # Default to None (unlimited) to match OpenAI's official agents SDK behavior diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index c35e910ab08..b06e8463abd 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -17,6 +17,7 @@ from litellm.constants import ( _DEFAULT_TTL_FOR_HTTPX_CLIENTS, AIOHTTP_CONNECTOR_LIMIT, AIOHTTP_KEEPALIVE_TIMEOUT, + AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_TTL_DNS_CACHE, DEFAULT_SSL_CIPHERS, ) @@ -798,7 +799,7 @@ class AsyncHTTPHandler: limit=AIOHTTP_CONNECTOR_LIMIT, keepalive_timeout=AIOHTTP_KEEPALIVE_TIMEOUT, ttl_dns_cache=AIOHTTP_TTL_DNS_CACHE, - enable_cleanup_closed=True, + enable_cleanup_closed=AIOHTTP_NEEDS_CLEANUP_CLOSED, **connector_kwargs, ), trust_env=trust_env, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 99789162318..a1e01caddc1 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -32,6 +32,7 @@ from litellm._uuid import uuid from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, AIOHTTP_KEEPALIVE_TIMEOUT, + AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_TTL_DNS_CACHE, AUDIO_SPEECH_CHUNK_SIZE, BASE_MCP_ROUTE, @@ -635,7 +636,7 @@ async def _initialize_shared_aiohttp_session(): limit=AIOHTTP_CONNECTOR_LIMIT, keepalive_timeout=AIOHTTP_KEEPALIVE_TIMEOUT, ttl_dns_cache=AIOHTTP_TTL_DNS_CACHE, - enable_cleanup_closed=True, + enable_cleanup_closed=AIOHTTP_NEEDS_CLEANUP_CLOSED, ) session = ClientSession(connector=connector) From 43dd9e4a90281e42fd1015cd72cbca5280fc4e4c Mon Sep 17 00:00:00 2001 From: Jonathan Yang Date: Wed, 3 Dec 2025 07:12:55 +0100 Subject: [PATCH 283/349] fix: replace deprecated .dict() with .model_dump() in streaming_handler (#17359) Replace Pydantic v1 `.dict()` method with v2 `.model_dump()` to fix PydanticDeprecatedSince20 warnings. The `.dict()` method is deprecated in Pydantic v2 and will be removed in v3. Fixes #5987 --- .../litellm_core_utils/streaming_handler.py | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 4d8e109d882..a7f460fab59 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -96,9 +96,9 @@ class CustomStreamWrapper: self.system_fingerprint: Optional[str] = None self.received_finish_reason: Optional[str] = None - self.intermittent_finish_reason: Optional[str] = ( - None # finish reasons that show up mid-stream - ) + self.intermittent_finish_reason: Optional[ + str + ] = None # finish reasons that show up mid-stream self.special_tokens = [ "<|assistant|>", "<|system|>", @@ -735,7 +735,7 @@ class CustomStreamWrapper: and completion_obj["function_call"] is not None ) or ( - "tool_calls" in model_response.choices[0].delta + "tool_calls" in model_response.choices[0].delta and model_response.choices[0].delta["tool_calls"] is not None ) or ( @@ -889,7 +889,6 @@ class CustomStreamWrapper: ## check if openai/azure chunk original_chunk = response_obj.get("original_chunk", None) if original_chunk: - if len(original_chunk.choices) > 0: choices = [] for choice in original_chunk.choices: @@ -906,7 +905,6 @@ class CustomStreamWrapper: print_verbose(f"choices in streaming: {choices}") setattr(model_response, "choices", choices) else: - return model_response.system_fingerprint = ( original_chunk.system_fingerprint @@ -1435,9 +1433,9 @@ class CustomStreamWrapper: _json_delta = delta.model_dump() print_verbose(f"_json_delta: {_json_delta}") if "role" not in _json_delta or _json_delta["role"] is None: - _json_delta["role"] = ( - "assistant" # mistral's api returns role as None - ) + _json_delta[ + "role" + ] = "assistant" # mistral's api returns role as None if "tool_calls" in _json_delta and isinstance( _json_delta["tool_calls"], list ): @@ -1533,7 +1531,7 @@ class CustomStreamWrapper: async def _call_post_streaming_deployment_hook(self, chunk): """ Call the post-call streaming deployment hook for callbacks. - + This allows callbacks to modify streaming chunks before they're returned. """ try: @@ -1544,15 +1542,17 @@ class CustomStreamWrapper: # Get request kwargs from logging object request_data = self.logging_obj.model_call_details call_type_str = self.logging_obj.call_type - + try: typed_call_type = CallTypes(call_type_str) except ValueError: typed_call_type = None - + # Call hooks for all callbacks for callback in litellm.callbacks: - if isinstance(callback, CustomLogger) and hasattr(callback, "async_post_call_streaming_deployment_hook"): + if isinstance(callback, CustomLogger) and hasattr( + callback, "async_post_call_streaming_deployment_hook" + ): result = await callback.async_post_call_streaming_deployment_hook( request_data=request_data, response_chunk=chunk, @@ -1560,11 +1560,14 @@ class CustomStreamWrapper: ) if result is not None: chunk = result - + return chunk except Exception as e: from litellm._logging import verbose_logger - verbose_logger.exception(f"Error in post-call streaming deployment hook: {str(e)}") + + verbose_logger.exception( + f"Error in post-call streaming deployment hook: {str(e)}" + ) return chunk def cache_streaming_response(self, processed_chunk, cache_hit: bool): @@ -1687,7 +1690,7 @@ class CustomStreamWrapper: response, "usage" ): # remove usage from chunk, only send on final chunk # Convert the object to a dictionary - obj_dict = response.dict() + obj_dict = response.model_dump() # Remove an attribute (e.g., 'attr2') if "usage" in obj_dict: @@ -1852,7 +1855,7 @@ class CustomStreamWrapper: processed_chunk, "usage" ): # remove usage from chunk, only send on final chunk # Convert the object to a dictionary - obj_dict = processed_chunk.dict() + obj_dict = processed_chunk.model_dump() # Remove an attribute (e.g., 'attr2') if "usage" in obj_dict: @@ -1872,11 +1875,15 @@ class CustomStreamWrapper: if self.sent_last_chunk is True and self.stream_options is None: usage = calculate_total_usage(chunks=self.chunks) processed_chunk._hidden_params["usage"] = usage - + # Call post-call streaming deployment hook for final chunk if self.sent_last_chunk is True: - processed_chunk = await self._call_post_streaming_deployment_hook(processed_chunk) - + processed_chunk = ( + await self._call_post_streaming_deployment_hook( + processed_chunk + ) + ) + return processed_chunk raise StopAsyncIteration else: # temporary patch for non-aiohttp async calls @@ -1890,9 +1897,9 @@ class CustomStreamWrapper: chunk = next(self.completion_stream) if chunk is not None and chunk != b"": print_verbose(f"PROCESSED CHUNK PRE CHUNK CREATOR: {chunk}") - processed_chunk: Optional[ModelResponseStream] = ( - self.chunk_creator(chunk=chunk) - ) + processed_chunk: Optional[ + ModelResponseStream + ] = self.chunk_creator(chunk=chunk) print_verbose( f"PROCESSED CHUNK POST CHUNK CREATOR: {processed_chunk}" ) From e289f5e454140042eda28ca27b8797078e2fe9f6 Mon Sep 17 00:00:00 2001 From: Deepak Tammali <45919384+deepaktammali@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:44:48 +0530 Subject: [PATCH 284/349] feat: make streaming chunk size configurable in bedrock converse and invoke handlers (#17357) --- litellm/llms/bedrock/chat/converse_handler.py | 10 ++++++++-- litellm/llms/bedrock/chat/invoke_handler.py | 20 ++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_handler.py b/litellm/llms/bedrock/chat/converse_handler.py index fd1f6f0c893..d5bd054118d 100644 --- a/litellm/llms/bedrock/chat/converse_handler.py +++ b/litellm/llms/bedrock/chat/converse_handler.py @@ -29,6 +29,7 @@ def make_sync_call( logging_obj: LiteLLMLoggingObject, json_mode: Optional[bool] = False, fake_stream: bool = False, + stream_chunk_size: int = 1024, ): if client is None: client = _get_httpx_client() # Create a new client if none provided @@ -66,7 +67,7 @@ def make_sync_call( ) else: decoder = AWSEventStreamDecoder(model=model) - completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=1024)) + completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=stream_chunk_size)) # LOGGING logging_obj.post_call( @@ -102,6 +103,7 @@ class BedrockConverseLLM(BaseAWSLLM): fake_stream: bool = False, json_mode: Optional[bool] = False, api_key: Optional[str] = None, + stream_chunk_size: int = 1024, ) -> CustomStreamWrapper: request_data = await litellm.AmazonConverseConfig()._async_transform_request( model=model, @@ -143,6 +145,7 @@ class BedrockConverseLLM(BaseAWSLLM): logging_obj=logging_obj, fake_stream=fake_stream, json_mode=json_mode, + stream_chunk_size=stream_chunk_size, ) streaming_response = CustomStreamWrapper( completion_stream=completion_stream, @@ -260,6 +263,7 @@ class BedrockConverseLLM(BaseAWSLLM): ): ## SETUP ## stream = optional_params.pop("stream", None) + stream_chunk_size = optional_params.pop("stream_chunk_size", 1024) unencoded_model_id = optional_params.pop("model_id", None) fake_stream = optional_params.pop("fake_stream", False) json_mode = optional_params.get("json_mode", False) @@ -356,7 +360,8 @@ class BedrockConverseLLM(BaseAWSLLM): json_mode=json_mode, fake_stream=fake_stream, credentials=credentials, - api_key=api_key + api_key=api_key, + stream_chunk_size=stream_chunk_size, ) # type: ignore ### ASYNC COMPLETION return self.async_completion( @@ -433,6 +438,7 @@ class BedrockConverseLLM(BaseAWSLLM): logging_obj=logging_obj, json_mode=json_mode, fake_stream=fake_stream, + stream_chunk_size=stream_chunk_size, ) streaming_response = CustomStreamWrapper( completion_stream=completion_stream, diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 7a960fd45d2..5e33a266449 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -192,6 +192,7 @@ async def make_call( fake_stream: bool = False, json_mode: Optional[bool] = False, bedrock_invoke_provider: Optional[litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL] = None, + stream_chunk_size: int = 1024, ): try: if client is None: @@ -235,7 +236,7 @@ async def make_call( json_mode=json_mode, ) completion_stream = decoder.aiter_bytes( - response.aiter_bytes(chunk_size=1024) + response.aiter_bytes(chunk_size=stream_chunk_size) ) elif bedrock_invoke_provider == "deepseek_r1": decoder = AmazonDeepSeekR1StreamDecoder( @@ -243,12 +244,12 @@ async def make_call( sync_stream=False, ) completion_stream = decoder.aiter_bytes( - response.aiter_bytes(chunk_size=1024) + response.aiter_bytes(chunk_size=stream_chunk_size) ) else: decoder = AWSEventStreamDecoder(model=model) completion_stream = decoder.aiter_bytes( - response.aiter_bytes(chunk_size=1024) + response.aiter_bytes(chunk_size=stream_chunk_size) ) # LOGGING @@ -281,6 +282,7 @@ def make_sync_call( fake_stream: bool = False, json_mode: Optional[bool] = False, bedrock_invoke_provider: Optional[litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL] = None, + stream_chunk_size: int = 1024, ): try: if client is None: @@ -321,16 +323,16 @@ def make_sync_call( sync_stream=True, json_mode=json_mode, ) - completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=1024)) + completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=stream_chunk_size)) elif bedrock_invoke_provider == "deepseek_r1": decoder = AmazonDeepSeekR1StreamDecoder( model=model, sync_stream=True, ) - completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=1024)) + completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=stream_chunk_size)) else: decoder = AWSEventStreamDecoder(model=model) - completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=1024)) + completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=stream_chunk_size)) # LOGGING logging_obj.post_call( @@ -729,6 +731,7 @@ class BedrockLLM(BaseAWSLLM): ## SETUP ## stream = optional_params.pop("stream", None) + stream_chunk_size = optional_params.pop("stream_chunk_size", 1024) provider = self.get_bedrock_invoke_provider(model) modelId = self.get_bedrock_model_id( @@ -1003,6 +1006,7 @@ class BedrockLLM(BaseAWSLLM): headers=prepped.headers, timeout=timeout, client=client, + stream_chunk_size=stream_chunk_size, ) # type: ignore ### ASYNC COMPLETION return self.async_completion( @@ -1048,7 +1052,7 @@ class BedrockLLM(BaseAWSLLM): decoder = AWSEventStreamDecoder(model=model) - completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=1024)) + completion_stream = decoder.iter_bytes(response.iter_bytes(chunk_size=stream_chunk_size)) streaming_response = CustomStreamWrapper( completion_stream=completion_stream, model=model, @@ -1168,6 +1172,7 @@ class BedrockLLM(BaseAWSLLM): logger_fn=None, headers={}, client: Optional[AsyncHTTPHandler] = None, + stream_chunk_size: int = 1024, ) -> CustomStreamWrapper: # The call is not made here; instead, we prepare the necessary objects for the stream. @@ -1183,6 +1188,7 @@ class BedrockLLM(BaseAWSLLM): messages=messages, logging_obj=logging_obj, fake_stream=True if "ai21" in api_base else False, + stream_chunk_size=stream_chunk_size, ), model=model, custom_llm_provider="bedrock", From 4c6604b0da6bcdd9c230632f203921047163d636 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Wed, 3 Dec 2025 03:25:26 -0300 Subject: [PATCH 285/349] Cleanup: Remove orphan docs pages and Docusaurus template files (#17356) * docs: update getting started page - Add Core Functions table with link to full list - Add Responses API section - Add Async section with acompletion() example - Add "Switch Providers with One Line" example - Clarify Basic Usage supports multiple endpoints - Update models to current versions (openai/gpt-4o, anthropic/claude-sonnet-4) - Use provider/model format throughout - Fix deprecated import: from openai.error -> from openai - Keep original structure: community key, More details links, observability env vars * Cleanup: Remove orphan docs pages and Docusaurus template files - Remove orphan getting_started.md (not linked in sidebar) - Remove Docusaurus template intro.md - Remove tutorial-basics/ directory (Docusaurus template) - Remove tutorial-extras/ directory (Docusaurus template) --- docs/my-website/docs/getting_started.md | 108 ------------- docs/my-website/src/pages/intro.md | 47 ------ .../src/pages/tutorial-basics/_category_.json | 8 - .../pages/tutorial-basics/congratulations.md | 23 --- .../tutorial-basics/create-a-blog-post.md | 34 ---- .../tutorial-basics/create-a-document.md | 57 ------- .../pages/tutorial-basics/create-a-page.md | 43 ----- .../pages/tutorial-basics/deploy-your-site.md | 31 ---- .../tutorial-basics/markdown-features.mdx | 150 ------------------ .../src/pages/tutorial-extras/_category_.json | 7 - .../img/docsVersionDropdown.png | Bin 25427 -> 0 bytes .../tutorial-extras/img/localeDropdown.png | Bin 27841 -> 0 bytes .../tutorial-extras/manage-docs-versions.md | 55 ------- .../tutorial-extras/translate-your-site.md | 88 ---------- 14 files changed, 651 deletions(-) delete mode 100644 docs/my-website/docs/getting_started.md delete mode 100644 docs/my-website/src/pages/intro.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/_category_.json delete mode 100644 docs/my-website/src/pages/tutorial-basics/congratulations.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/create-a-blog-post.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/create-a-document.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/create-a-page.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/deploy-your-site.md delete mode 100644 docs/my-website/src/pages/tutorial-basics/markdown-features.mdx delete mode 100644 docs/my-website/src/pages/tutorial-extras/_category_.json delete mode 100644 docs/my-website/src/pages/tutorial-extras/img/docsVersionDropdown.png delete mode 100644 docs/my-website/src/pages/tutorial-extras/img/localeDropdown.png delete mode 100644 docs/my-website/src/pages/tutorial-extras/manage-docs-versions.md delete mode 100644 docs/my-website/src/pages/tutorial-extras/translate-your-site.md diff --git a/docs/my-website/docs/getting_started.md b/docs/my-website/docs/getting_started.md deleted file mode 100644 index 6b2c1fd531e..00000000000 --- a/docs/my-website/docs/getting_started.md +++ /dev/null @@ -1,108 +0,0 @@ -# Getting Started - -import QuickStart from '../src/components/QuickStart.js' - -LiteLLM simplifies LLM API calls by mapping them all to the [OpenAI ChatCompletion format](https://platform.openai.com/docs/api-reference/chat). - -## basic usage - -By default we provide a free $10 community-key to try all providers supported on LiteLLM. - -```python -from litellm import completion - -## set ENV variables -os.environ["OPENAI_API_KEY"] = "your-api-key" -os.environ["COHERE_API_KEY"] = "your-api-key" - -messages = [{ "content": "Hello, how are you?","role": "user"}] - -# openai call -response = completion(model="gpt-3.5-turbo", messages=messages) - -# cohere call -response = completion("command-nightly", messages) -``` - -**Need a dedicated key?** -Email us @ krrish@berri.ai - -Next Steps 👉 [Call all supported models - e.g. Claude-2, Llama2-70b, etc.](./proxy_api.md#supported-models) - -More details 👉 - -- [Completion() function details](./completion/) -- [Overview of supported models / providers on LiteLLM](./providers/) -- [Search all models / providers](https://models.litellm.ai/) -- [Build your own OpenAI proxy](https://github.com/BerriAI/liteLLM-proxy/tree/main) - -## streaming - -Same example from before. Just pass in `stream=True` in the completion args. - -```python -from litellm import completion - -## set ENV variables -os.environ["OPENAI_API_KEY"] = "openai key" -os.environ["COHERE_API_KEY"] = "cohere key" - -messages = [{ "content": "Hello, how are you?","role": "user"}] - -# openai call -response = completion(model="gpt-3.5-turbo", messages=messages, stream=True) - -# cohere call -response = completion("command-nightly", messages, stream=True) - -print(response) -``` - -More details 👉 - -- [streaming + async](./completion/stream.md) -- [tutorial for streaming Llama2 on TogetherAI](./tutorials/TogetherAI_liteLLM.md) - -## exception handling - -LiteLLM maps exceptions across all supported providers to the OpenAI exceptions. All our exceptions inherit from OpenAI's exception types, so any error-handling you have for that, should work out of the box with LiteLLM. - -```python -from openai.error import OpenAIError -from litellm import completion - -os.environ["ANTHROPIC_API_KEY"] = "bad-key" -try: - # some code - completion(model="claude-instant-1", messages=[{"role": "user", "content": "Hey, how's it going?"}]) -except OpenAIError as e: - print(e) -``` - -## Logging Observability - Log LLM Input/Output ([Docs](https://docs.litellm.ai/docs/observability/callbacks)) - -LiteLLM exposes pre defined callbacks to send data to MLflow, Lunary, Langfuse, Helicone, Promptlayer, Traceloop, Slack - -```python -from litellm import completion - -## set env variables for logging tools (API key set up is not required when using MLflow) -os.environ["LUNARY_PUBLIC_KEY"] = "your-lunary-public-key" # get your public key at https://app.lunary.ai/settings -os.environ["HELICONE_API_KEY"] = "your-helicone-key" -os.environ["LANGFUSE_PUBLIC_KEY"] = "" -os.environ["LANGFUSE_SECRET_KEY"] = "" - -os.environ["OPENAI_API_KEY"] - -# set callbacks -litellm.success_callback = ["lunary", "mlflow", "langfuse", "helicone"] # log input/output to MLflow, langfuse, lunary, helicone - -#openai call -response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]) -``` - -More details 👉 - -- [exception mapping](./exception_mapping.md) -- [retries + model fallbacks for completion()](./completion/reliable_completions.md) -- [tutorial for model fallbacks with completion()](./tutorials/fallbacks.md) diff --git a/docs/my-website/src/pages/intro.md b/docs/my-website/src/pages/intro.md deleted file mode 100644 index 8a2e69d95f9..00000000000 --- a/docs/my-website/src/pages/intro.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Tutorial Intro - -Let's discover **Docusaurus in less than 5 minutes**. - -## Getting Started - -Get started by **creating a new site**. - -Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. - -### What you'll need - -- [Node.js](https://nodejs.org/en/download/) version 16.14 or above: - - When installing Node.js, you are recommended to check all checkboxes related to dependencies. - -## Generate a new site - -Generate a new Docusaurus site using the **classic template**. - -The classic template will automatically be added to your project after you run the command: - -```bash -npm init docusaurus@latest my-website classic -``` - -You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. - -The command also installs all necessary dependencies you need to run Docusaurus. - -## Start your site - -Run the development server: - -```bash -cd my-website -npm run start -``` - -The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. - -The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. - -Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/docs/my-website/src/pages/tutorial-basics/_category_.json b/docs/my-website/src/pages/tutorial-basics/_category_.json deleted file mode 100644 index 2e6db55b1eb..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Tutorial - Basics", - "position": 2, - "link": { - "type": "generated-index", - "description": "5 minutes to learn the most important Docusaurus concepts." - } -} diff --git a/docs/my-website/src/pages/tutorial-basics/congratulations.md b/docs/my-website/src/pages/tutorial-basics/congratulations.md deleted file mode 100644 index 04771a00b72..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/congratulations.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Congratulations! - -You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. - -Docusaurus has **much more to offer**! - -Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. - -Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) - -## What's next? - -- Read the [official documentation](https://docusaurus.io/) -- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config) -- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration) -- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) -- Add a [search bar](https://docusaurus.io/docs/search) -- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) -- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) diff --git a/docs/my-website/src/pages/tutorial-basics/create-a-blog-post.md b/docs/my-website/src/pages/tutorial-basics/create-a-blog-post.md deleted file mode 100644 index ea472bbaf87..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/create-a-blog-post.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Create a Blog Post - -Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... - -## Create your first Post - -Create a file at `blog/2021-02-28-greetings.md`: - -```md title="blog/2021-02-28-greetings.md" ---- -slug: greetings -title: Greetings! -authors: - - name: Joel Marcey - title: Co-creator of Docusaurus 1 - url: https://github.com/JoelMarcey - image_url: https://github.com/JoelMarcey.png - - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png -tags: [greetings] ---- - -Congratulations, you have made your first post! - -Feel free to play around and edit this post as much you like. -``` - -A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings). diff --git a/docs/my-website/src/pages/tutorial-basics/create-a-document.md b/docs/my-website/src/pages/tutorial-basics/create-a-document.md deleted file mode 100644 index ffddfa8eb8a..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/create-a-document.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Create a Document - -Documents are **groups of pages** connected through: - -- a **sidebar** -- **previous/next navigation** -- **versioning** - -## Create your first Doc - -Create a Markdown file at `docs/hello.md`: - -```md title="docs/hello.md" -# Hello - -This is my **first Docusaurus document**! -``` - -A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello). - -## Configure the Sidebar - -Docusaurus automatically **creates a sidebar** from the `docs` folder. - -Add metadata to customize the sidebar label and position: - -```md title="docs/hello.md" {1-4} ---- -sidebar_label: 'Hi!' -sidebar_position: 3 ---- - -# Hello - -This is my **first Docusaurus document**! -``` - -It is also possible to create your sidebar explicitly in `sidebars.js`: - -```js title="sidebars.js" -module.exports = { - tutorialSidebar: [ - 'intro', - // highlight-next-line - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], -}; -``` diff --git a/docs/my-website/src/pages/tutorial-basics/create-a-page.md b/docs/my-website/src/pages/tutorial-basics/create-a-page.md deleted file mode 100644 index 20e2ac30055..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/create-a-page.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Create a Page - -Add **Markdown or React** files to `src/pages` to create a **standalone page**: - -- `src/pages/index.js` → `localhost:3000/` -- `src/pages/foo.md` → `localhost:3000/foo` -- `src/pages/foo/bar.js` → `localhost:3000/foo/bar` - -## Create your first React Page - -Create a file at `src/pages/my-react-page.js`: - -```jsx title="src/pages/my-react-page.js" -import React from 'react'; -import Layout from '@theme/Layout'; - -export default function MyReactPage() { - return ( - -

    My React page

    -

    This is a React page

    -
    - ); -} -``` - -A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page). - -## Create your first Markdown Page - -Create a file at `src/pages/my-markdown-page.md`: - -```mdx title="src/pages/my-markdown-page.md" -# My Markdown page - -This is a Markdown page -``` - -A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page). diff --git a/docs/my-website/src/pages/tutorial-basics/deploy-your-site.md b/docs/my-website/src/pages/tutorial-basics/deploy-your-site.md deleted file mode 100644 index 1c50ee063ef..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/deploy-your-site.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Deploy your site - -Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). - -It builds your site as simple **static HTML, JavaScript and CSS files**. - -## Build your site - -Build your site **for production**: - -```bash -npm run build -``` - -The static files are generated in the `build` folder. - -## Deploy your site - -Test your production build locally: - -```bash -npm run serve -``` - -The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/). - -You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). diff --git a/docs/my-website/src/pages/tutorial-basics/markdown-features.mdx b/docs/my-website/src/pages/tutorial-basics/markdown-features.mdx deleted file mode 100644 index 0337f34d6a5..00000000000 --- a/docs/my-website/src/pages/tutorial-basics/markdown-features.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Markdown Features - -Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. - -## Front Matter - -Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): - -```text title="my-doc.md" -// highlight-start ---- -id: my-doc-id -title: My document title -description: My document description -slug: /my-custom-url ---- -// highlight-end - -## Markdown heading - -Markdown text with [links](./hello.md) -``` - -## Links - -Regular Markdown links are supported, using url paths or relative file paths. - -```md -Let's see how to [Create a page](/create-a-page). -``` - -```md -Let's see how to [Create a page](./create-a-page.md). -``` - -**Result:** Let's see how to [Create a page](./create-a-page.md). - -## Images - -Regular Markdown images are supported. - -You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): - -```md -![Docusaurus logo](/img/docusaurus.png) -``` - -![Docusaurus logo](/img/docusaurus.png) - -You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: - -```md -![Docusaurus logo](./img/docusaurus.png) -``` - -## Code Blocks - -Markdown code blocks are supported with Syntax highlighting. - - ```jsx title="src/components/HelloDocusaurus.js" - function HelloDocusaurus() { - return ( -

    Hello, Docusaurus!

    - ) - } - ``` - -```jsx title="src/components/HelloDocusaurus.js" -function HelloDocusaurus() { - return

    Hello, Docusaurus!

    ; -} -``` - -## Admonitions - -Docusaurus has a special syntax to create admonitions and callouts: - - :::tip My tip - - Use this awesome feature option - - ::: - - :::danger Take care - - This action is dangerous - - ::: - -:::tip My tip - -Use this awesome feature option - -::: - -:::danger Take care - -This action is dangerous - -::: - -## MDX and React Components - -[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: - -```jsx -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`) - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! -``` - -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`); - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! diff --git a/docs/my-website/src/pages/tutorial-extras/_category_.json b/docs/my-website/src/pages/tutorial-extras/_category_.json deleted file mode 100644 index a8ffcc19300..00000000000 --- a/docs/my-website/src/pages/tutorial-extras/_category_.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "label": "Tutorial - Extras", - "position": 3, - "link": { - "type": "generated-index" - } -} diff --git a/docs/my-website/src/pages/tutorial-extras/img/docsVersionDropdown.png b/docs/my-website/src/pages/tutorial-extras/img/docsVersionDropdown.png deleted file mode 100644 index 97e4164618b5f8beda34cfa699720aba0ad2e342..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25427 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 diff --git a/docs/my-website/src/pages/tutorial-extras/img/localeDropdown.png b/docs/my-website/src/pages/tutorial-extras/img/localeDropdown.png deleted file mode 100644 index e257edc1f932985396bf59584c7ccfaddf955779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27841 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 JrC|B>e*nH7H{}2T diff --git a/docs/my-website/src/pages/tutorial-extras/manage-docs-versions.md b/docs/my-website/src/pages/tutorial-extras/manage-docs-versions.md deleted file mode 100644 index e12c3f3444f..00000000000 --- a/docs/my-website/src/pages/tutorial-extras/manage-docs-versions.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Manage Docs Versions - -Docusaurus can manage multiple versions of your docs. - -## Create a docs version - -Release a version 1.0 of your project: - -```bash -npm run docusaurus docs:version 1.0 -``` - -The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. - -Your docs now have 2 versions: - -- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs -- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** - -## Add a Version Dropdown - -To navigate seamlessly across versions, add a version dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -module.exports = { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'docsVersionDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The docs version dropdown appears in your navbar: - -![Docs Version Dropdown](./img/docsVersionDropdown.png) - -## Update an existing version - -It is possible to edit versioned docs in their respective folder: - -- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` -- `docs/hello.md` updates `http://localhost:3000/docs/next/hello` diff --git a/docs/my-website/src/pages/tutorial-extras/translate-your-site.md b/docs/my-website/src/pages/tutorial-extras/translate-your-site.md deleted file mode 100644 index caeaffb0554..00000000000 --- a/docs/my-website/src/pages/tutorial-extras/translate-your-site.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Translate your site - -Let's translate `docs/intro.md` to French. - -## Configure i18n - -Modify `docusaurus.config.js` to add support for the `fr` locale: - -```js title="docusaurus.config.js" -module.exports = { - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - }, -}; -``` - -## Translate a doc - -Copy the `docs/intro.md` file to the `i18n/fr` folder: - -```bash -mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ - -cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md -``` - -Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. - -## Start your localized site - -Start your site on the French locale: - -```bash -npm run start -- --locale fr -``` - -Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. - -:::caution - -In development, you can only use one locale at a same time. - -::: - -## Add a Locale Dropdown - -To navigate seamlessly across languages, add a locale dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -module.exports = { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'localeDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The locale dropdown now appears in your navbar: - -![Locale Dropdown](./img/localeDropdown.png) - -## Build your localized site - -Build your site for a specific locale: - -```bash -npm run build -- --locale fr -``` - -Or build your site to include all the locales at once: - -```bash -npm run build -``` From 86350fe6d70dc62ecb5331a7df5f96a35b853305 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Wed, 3 Dec 2025 03:27:04 -0300 Subject: [PATCH 286/349] docs: add Google ADK and Harbor to projects (#17352) Both frameworks integrate with LiteLLM: - Google ADK uses LiteLLM for model-agnostic agent building - Harbor uses LiteLLM for agent evaluation across providers --- docs/my-website/docs/projects/Google ADK.md | 21 ++++++++++++++++++ docs/my-website/docs/projects/Harbor.md | 24 +++++++++++++++++++++ docs/my-website/sidebars.js | 4 +++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/my-website/docs/projects/Google ADK.md create mode 100644 docs/my-website/docs/projects/Harbor.md diff --git a/docs/my-website/docs/projects/Google ADK.md b/docs/my-website/docs/projects/Google ADK.md new file mode 100644 index 00000000000..25e910dcbad --- /dev/null +++ b/docs/my-website/docs/projects/Google ADK.md @@ -0,0 +1,21 @@ + +# Google ADK (Agent Development Kit) + +[Google ADK](https://github.com/google/adk-python) is an open-source, code-first Python framework for building, evaluating, and deploying sophisticated AI agents. While optimized for Gemini, ADK is model-agnostic and supports LiteLLM for using 100+ providers. + +```python +from google.adk.agents.llm_agent import Agent +from google.adk.models.lite_llm import LiteLlm + +root_agent = Agent( + model=LiteLlm(model="openai/gpt-4o"), # Or any LiteLLM-supported model + name="my_agent", + description="An agent using LiteLLM", + instruction="You are a helpful assistant.", + tools=[your_tools], +) +``` + +- [GitHub](https://github.com/google/adk-python) +- [Documentation](https://google.github.io/adk-docs) +- [LiteLLM Samples](https://github.com/google/adk-python/tree/main/contributing/samples/hello_world_litellm) diff --git a/docs/my-website/docs/projects/Harbor.md b/docs/my-website/docs/projects/Harbor.md new file mode 100644 index 00000000000..684dfa93720 --- /dev/null +++ b/docs/my-website/docs/projects/Harbor.md @@ -0,0 +1,24 @@ + +# Harbor + +[Harbor](https://github.com/laude-institute/harbor) is a framework from the creators of Terminal-Bench for evaluating and optimizing agents and language models. It uses LiteLLM to call 100+ LLM providers. + +```bash +# Install +pip install harbor + +# Run a benchmark with any LiteLLM-supported model +harbor run --dataset terminal-bench@2.0 \ + --agent claude-code \ + --model anthropic/claude-opus-4-1 \ + --n-concurrent 4 +``` + +Key features: +- Evaluate agents like Claude Code, OpenHands, Codex CLI +- Build and share benchmarks and environments +- Run experiments in parallel across cloud providers (Daytona, Modal) +- Generate rollouts for RL optimization + +- [GitHub](https://github.com/laude-institute/harbor) +- [Documentation](https://harborframework.com/docs) diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 983816ed216..a9790547e84 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -820,10 +820,12 @@ const sidebars = { "Learn how to deploy + call models from different providers on LiteLLM", slug: "/project", }, - items: [ + items: [ "projects/smolagents", "projects/mini-swe-agent", "projects/openai-agents", + "projects/Google ADK", + "projects/Harbor", "projects/Docq.AI", "projects/PDL", "projects/OpenInterpreter", From c173a4a27594b0a435f58a0be7633514bbeee440 Mon Sep 17 00:00:00 2001 From: Fabian Reinold <32450519+freinold@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:30:54 +0100 Subject: [PATCH 287/349] Helm Chart: add ingress-only labels (#17348) * feat(helm): add ingress-only labels * feat(helm): add ingress configuration tests * chore(helm): bump chart version --- deploy/charts/litellm-helm/Chart.yaml | 4 +- deploy/charts/litellm-helm/README.md | 125 +++++++++--------- .../litellm-helm/templates/ingress.yaml | 3 + .../litellm-helm/tests/ingress_tests.yaml | 45 +++++++ deploy/charts/litellm-helm/values.yaml | 45 ++++--- 5 files changed, 140 insertions(+), 82 deletions(-) create mode 100644 deploy/charts/litellm-helm/tests/ingress_tests.yaml diff --git a/deploy/charts/litellm-helm/Chart.yaml b/deploy/charts/litellm-helm/Chart.yaml index eedadebaa8e..7f14af7db5d 100644 --- a/deploy/charts/litellm-helm/Chart.yaml +++ b/deploy/charts/litellm-helm/Chart.yaml @@ -18,7 +18,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.4.8 +version: 0.4.9 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -33,5 +33,5 @@ dependencies: condition: db.deployStandalone - name: redis version: ">=18.0.0" - repository: oci://registry-1.docker.io/bitnamicharts + repository: oci://registry-1.docker.io/bitnamicharts condition: redis.enabled diff --git a/deploy/charts/litellm-helm/README.md b/deploy/charts/litellm-helm/README.md index 352c3e9ddff..6fdc423a177 100644 --- a/deploy/charts/litellm-helm/README.md +++ b/deploy/charts/litellm-helm/README.md @@ -10,46 +10,48 @@ - Helm 3.8.0+ If `db.deployStandalone` is used: + - PV provisioner support in the underlying infrastructure If `db.useStackgresOperator` is used (not yet implemented): -- The Stackgres Operator must already be installed in the Kubernetes Cluster. This chart will **not** install the operator if it is missing. + +- The Stackgres Operator must already be installed in the Kubernetes Cluster. This chart will **not** install the operator if it is missing. ## Parameters ### LiteLLM Proxy Deployment Settings -| Name | Description | Value | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| `replicaCount` | The number of LiteLLM Proxy pods to be deployed | `1` | -| `masterkeySecretName` | The name of the Kubernetes Secret that contains the Master API Key for LiteLLM. If not specified, use the generated secret name. | N/A | -| `masterkeySecretKey` | The key within the Kubernetes Secret that contains the Master API Key for LiteLLM. If not specified, use `masterkey` as the key. | N/A | -| `masterkey` | The Master API Key for LiteLLM. If not specified, a random key in the `sk-...` format is generated. | N/A | -| `environmentSecrets` | An optional array of Secret object names. The keys and values in these secrets will be presented to the LiteLLM proxy pod as environment variables. See below for an example Secret object. | `[]` | -| `environmentConfigMaps` | An optional array of ConfigMap object names. The keys and values in these configmaps will be presented to the LiteLLM proxy pod as environment variables. See below for an example Secret object. | `[]` | -| `image.repository` | LiteLLM Proxy image repository | `ghcr.io/berriai/litellm` | -| `image.pullPolicy` | LiteLLM Proxy image pull policy | `IfNotPresent` | -| `image.tag` | Overrides the image tag whose default the latest version of LiteLLM at the time this chart was published. | `""` | -| `imagePullSecrets` | Registry credentials for the LiteLLM and initContainer images. | `[]` | -| `serviceAccount.create` | Whether or not to create a Kubernetes Service Account for this deployment. The default is `false` because LiteLLM has no need to access the Kubernetes API. | `false` | -| `service.type` | Kubernetes Service type (e.g. `LoadBalancer`, `ClusterIP`, etc.) | `ClusterIP` | -| `service.port` | TCP port that the Kubernetes Service will listen on. Also the TCP port within the Pod that the proxy will listen on. | `4000` | -| `service.loadBalancerClass` | Optional LoadBalancer implementation class (only used when `service.type` is `LoadBalancer`) | `""` | -| `ingress.*` | See [values.yaml](./values.yaml) for example settings | N/A | -| `proxyConfigMap.create` | When `true`, render a ConfigMap from `.Values.proxy_config` and mount it. | `true` | -| `proxyConfigMap.name` | When `create=false`, name of the existing ConfigMap to mount. | `""` | -| `proxyConfigMap.key` | Key in the ConfigMap that contains the proxy config file. | `"config.yaml"` | -| `proxy_config.*` | See [values.yaml](./values.yaml) for default settings. Rendered into the ConfigMap’s `config.yaml` only when `proxyConfigMap.create=true`. See [example_config_yaml](../../../litellm/proxy/example_config_yaml/) for configuration examples. | `N/A` | -| `extraContainers[]` | An array of additional containers to be deployed as sidecars alongside the LiteLLM Proxy. -| `pdb.enabled` | Enable a PodDisruptionBudget for the LiteLLM proxy Deployment | `false` | -| `pdb.minAvailable` | Minimum number/percentage of pods that must be available during **voluntary** disruptions (choose **one** of minAvailable/maxUnavailable) | `null` | -| `pdb.maxUnavailable` | Maximum number/percentage of pods that can be unavailable during **voluntary** disruptions (choose **one** of minAvailable/maxUnavailable) | `null` | -| `pdb.annotations` | Extra metadata annotations to add to the PDB | `{}` | -| `pdb.labels` | Extra metadata labels to add to the PDB | `{}` | +| Name | Description | Value | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `replicaCount` | The number of LiteLLM Proxy pods to be deployed | `1` | +| `masterkeySecretName` | The name of the Kubernetes Secret that contains the Master API Key for LiteLLM. If not specified, use the generated secret name. | N/A | +| `masterkeySecretKey` | The key within the Kubernetes Secret that contains the Master API Key for LiteLLM. If not specified, use `masterkey` as the key. | N/A | +| `masterkey` | The Master API Key for LiteLLM. If not specified, a random key in the `sk-...` format is generated. | N/A | +| `environmentSecrets` | An optional array of Secret object names. The keys and values in these secrets will be presented to the LiteLLM proxy pod as environment variables. See below for an example Secret object. | `[]` | +| `environmentConfigMaps` | An optional array of ConfigMap object names. The keys and values in these configmaps will be presented to the LiteLLM proxy pod as environment variables. See below for an example Secret object. | `[]` | +| `image.repository` | LiteLLM Proxy image repository | `ghcr.io/berriai/litellm` | +| `image.pullPolicy` | LiteLLM Proxy image pull policy | `IfNotPresent` | +| `image.tag` | Overrides the image tag whose default the latest version of LiteLLM at the time this chart was published. | `""` | +| `imagePullSecrets` | Registry credentials for the LiteLLM and initContainer images. | `[]` | +| `serviceAccount.create` | Whether or not to create a Kubernetes Service Account for this deployment. The default is `false` because LiteLLM has no need to access the Kubernetes API. | `false` | +| `service.type` | Kubernetes Service type (e.g. `LoadBalancer`, `ClusterIP`, etc.) | `ClusterIP` | +| `service.port` | TCP port that the Kubernetes Service will listen on. Also the TCP port within the Pod that the proxy will listen on. | `4000` | +| `service.loadBalancerClass` | Optional LoadBalancer implementation class (only used when `service.type` is `LoadBalancer`) | `""` | +| `ingress.labels` | Additional labels for the Ingress resource | `{}` | +| `ingress.*` | See [values.yaml](./values.yaml) for example settings | N/A | +| `proxyConfigMap.create` | When `true`, render a ConfigMap from `.Values.proxy_config` and mount it. | `true` | +| `proxyConfigMap.name` | When `create=false`, name of the existing ConfigMap to mount. | `""` | +| `proxyConfigMap.key` | Key in the ConfigMap that contains the proxy config file. | `"config.yaml"` | +| `proxy_config.*` | See [values.yaml](./values.yaml) for default settings. Rendered into the ConfigMap’s `config.yaml` only when `proxyConfigMap.create=true`. See [example_config_yaml](../../../litellm/proxy/example_config_yaml/) for configuration examples. | `N/A` | +| `extraContainers[]` | An array of additional containers to be deployed as sidecars alongside the LiteLLM Proxy. | +| `pdb.enabled` | Enable a PodDisruptionBudget for the LiteLLM proxy Deployment | `false` | +| `pdb.minAvailable` | Minimum number/percentage of pods that must be available during **voluntary** disruptions (choose **one** of minAvailable/maxUnavailable) | `null` | +| `pdb.maxUnavailable` | Maximum number/percentage of pods that can be unavailable during **voluntary** disruptions (choose **one** of minAvailable/maxUnavailable) | `null` | +| `pdb.annotations` | Extra metadata annotations to add to the PDB | `{}` | +| `pdb.labels` | Extra metadata labels to add to the PDB | `{}` | #### Example `proxy_config` ConfigMap from values (default): - ``` proxyConfigMap: create: true @@ -67,7 +69,6 @@ proxy_config: #### Example using existing `proxyConfigMap` instead of creating it: - ``` proxyConfigMap: create: false @@ -77,8 +78,7 @@ proxyConfigMap: # proxy_config is ignored in this mode ``` -#### Example `environmentSecrets` Secret - +#### Example `environmentSecrets` Secret ``` apiVersion: v1 @@ -91,21 +91,23 @@ type: Opaque ``` ### Database Settings -| Name | Description | Value | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| `db.useExisting` | Use an existing Postgres database. A Kubernetes Secret object must exist that contains credentials for connecting to the database. An example secret object definition is provided below. | `false` | -| `db.endpoint` | If `db.useExisting` is `true`, this is the IP, Hostname or Service Name of the Postgres server to connect to. | `localhost` | -| `db.database` | If `db.useExisting` is `true`, the name of the existing database to connect to. | `litellm` | -| `db.url` | If `db.useExisting` is `true`, the connection url of the existing database to connect to can be overwritten with this value. | `postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_HOST)/$(DATABASE_NAME)` | -| `db.secret.name` | If `db.useExisting` is `true`, the name of the Kubernetes Secret that contains credentials. | `postgres` | -| `db.secret.usernameKey` | If `db.useExisting` is `true`, the name of the key within the Kubernetes Secret that holds the username for authenticating with the Postgres instance. | `username` | -| `db.secret.passwordKey` | If `db.useExisting` is `true`, the name of the key within the Kubernetes Secret that holds the password associates with the above user. | `password` | -| `db.useStackgresOperator` | Not yet implemented. | `false` | -| `db.deployStandalone` | Deploy a standalone, single instance deployment of Postgres, using the Bitnami postgresql chart. This is useful for getting started but doesn't provide HA or (by default) data backups. | `true` | -| `postgresql.*` | If `db.deployStandalone` is `true`, configuration passed to the Bitnami postgresql chart. See the [Bitnami Documentation](https://github.com/bitnami/charts/tree/main/bitnami/postgresql) for full configuration details. See [values.yaml](./values.yaml) for the default configuration. | See [values.yaml](./values.yaml) | -| `postgresql.auth.*` | If `db.deployStandalone` is `true`, care should be taken to ensure the default `password` and `postgres-password` values are **NOT** used. | `NoTaGrEaTpAsSwOrD` | + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `db.useExisting` | Use an existing Postgres database. A Kubernetes Secret object must exist that contains credentials for connecting to the database. An example secret object definition is provided below. | `false` | +| `db.endpoint` | If `db.useExisting` is `true`, this is the IP, Hostname or Service Name of the Postgres server to connect to. | `localhost` | +| `db.database` | If `db.useExisting` is `true`, the name of the existing database to connect to. | `litellm` | +| `db.url` | If `db.useExisting` is `true`, the connection url of the existing database to connect to can be overwritten with this value. | `postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_HOST)/$(DATABASE_NAME)` | +| `db.secret.name` | If `db.useExisting` is `true`, the name of the Kubernetes Secret that contains credentials. | `postgres` | +| `db.secret.usernameKey` | If `db.useExisting` is `true`, the name of the key within the Kubernetes Secret that holds the username for authenticating with the Postgres instance. | `username` | +| `db.secret.passwordKey` | If `db.useExisting` is `true`, the name of the key within the Kubernetes Secret that holds the password associates with the above user. | `password` | +| `db.useStackgresOperator` | Not yet implemented. | `false` | +| `db.deployStandalone` | Deploy a standalone, single instance deployment of Postgres, using the Bitnami postgresql chart. This is useful for getting started but doesn't provide HA or (by default) data backups. | `true` | +| `postgresql.*` | If `db.deployStandalone` is `true`, configuration passed to the Bitnami postgresql chart. See the [Bitnami Documentation](https://github.com/bitnami/charts/tree/main/bitnami/postgresql) for full configuration details. See [values.yaml](./values.yaml) for the default configuration. | See [values.yaml](./values.yaml) | +| `postgresql.auth.*` | If `db.deployStandalone` is `true`, care should be taken to ensure the default `password` and `postgres-password` values are **NOT** used. | `NoTaGrEaTpAsSwOrD` | #### Example Postgres `db.useExisting` Secret + ```yaml apiVersion: v1 kind: Secret @@ -143,7 +145,7 @@ metadata: name: litellm-env-secret type: Opaque data: - SOME_PASSWORD: cDZbUGVXeU5e0ZW # base64 encoded + SOME_PASSWORD: cDZbUGVXeU5e0ZW # base64 encoded ANOTHER_PASSWORD: AAZbUGVXeU5e0ZB # base64 encoded ``` @@ -153,23 +155,23 @@ Source: [GitHub Gist from troyharvey](https://gist.github.com/troyharvey/4506472 The migration job supports both ArgoCD and Helm hooks to ensure database migrations run at the appropriate time during deployments. -| Name | Description | Value | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| `migrationJob.enabled` | Enable or disable the schema migration Job | `true` | -| `migrationJob.backoffLimit` | Backoff limit for Job restarts | `4` | -| `migrationJob.ttlSecondsAfterFinished` | TTL for completed migration jobs | `120` | -| `migrationJob.annotations` | Additional annotations for the migration job pod | `{}` | -| `migrationJob.extraContainers` | Additional containers to run alongside the migration job | `[]` | -| `migrationJob.hooks.argocd.enabled` | Enable ArgoCD hooks for the migration job (uses PreSync hook with BeforeHookCreation delete policy) | `true` | -| `migrationJob.hooks.helm.enabled` | Enable Helm hooks for the migration job (uses pre-install,pre-upgrade hooks with before-hook-creation delete policy) | `false` | -| `migrationJob.hooks.helm.weight` | Helm hook execution order (lower weights executed first). Optional - defaults to "1" if not specified. | N/A | - +| Name | Description | Value | +| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------- | +| `migrationJob.enabled` | Enable or disable the schema migration Job | `true` | +| `migrationJob.backoffLimit` | Backoff limit for Job restarts | `4` | +| `migrationJob.ttlSecondsAfterFinished` | TTL for completed migration jobs | `120` | +| `migrationJob.annotations` | Additional annotations for the migration job pod | `{}` | +| `migrationJob.extraContainers` | Additional containers to run alongside the migration job | `[]` | +| `migrationJob.hooks.argocd.enabled` | Enable ArgoCD hooks for the migration job (uses PreSync hook with BeforeHookCreation delete policy) | `true` | +| `migrationJob.hooks.helm.enabled` | Enable Helm hooks for the migration job (uses pre-install,pre-upgrade hooks with before-hook-creation delete policy) | `false` | +| `migrationJob.hooks.helm.weight` | Helm hook execution order (lower weights executed first). Optional - defaults to "1" if not specified. | N/A | ## Accessing the Admin UI + When browsing to the URL published per the settings in `ingress.*`, you will -be prompted for **Admin Configuration**. The **Proxy Endpoint** is the internal +be prompted for **Admin Configuration**. The **Proxy Endpoint** is the internal (from the `litellm` pod's perspective) URL published by the `-litellm` -Kubernetes Service. If the deployment uses the default settings for this +Kubernetes Service. If the deployment uses the default settings for this service, the **Proxy Endpoint** should be set to `http://-litellm:4000`. The **Proxy Key** is the value specified for `masterkey` or, if a `masterkey` @@ -181,7 +183,8 @@ kubectl -n litellm get secret -litellm-masterkey -o jsonpath="{.data.ma ``` ## Admin UI Limitations -At the time of writing, the Admin UI is unable to add models. This is because + +At the time of writing, the Admin UI is unable to add models. This is because it would need to update the `config.yaml` file which is a exposed ConfigMap, and -therefore, read-only. This is a limitation of this helm chart, not the Admin UI +therefore, read-only. This is a limitation of this helm chart, not the Admin UI itself. diff --git a/deploy/charts/litellm-helm/templates/ingress.yaml b/deploy/charts/litellm-helm/templates/ingress.yaml index 09e8d715ab8..ea9ffcbb54c 100644 --- a/deploy/charts/litellm-helm/templates/ingress.yaml +++ b/deploy/charts/litellm-helm/templates/ingress.yaml @@ -18,6 +18,9 @@ metadata: name: {{ $fullName }} labels: {{- include "litellm.labels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} diff --git a/deploy/charts/litellm-helm/tests/ingress_tests.yaml b/deploy/charts/litellm-helm/tests/ingress_tests.yaml new file mode 100644 index 00000000000..aad6ecfcee8 --- /dev/null +++ b/deploy/charts/litellm-helm/tests/ingress_tests.yaml @@ -0,0 +1,45 @@ +suite: Ingress Configuration Tests +templates: + - ingress.yaml +tests: + - it: should not create Ingress by default + asserts: + - hasDocuments: + count: 0 + + - it: should create Ingress when enabled + set: + ingress.enabled: true + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Ingress + + - it: should add custom labels + set: + ingress.enabled: true + ingress.labels: + custom-label: "true" + another-label: "value" + asserts: + - isKind: + of: Ingress + - equal: + path: metadata.labels.custom-label + value: "true" + - equal: + path: metadata.labels.another-label + value: "value" + + - it: should add annotations + set: + ingress.enabled: true + ingress.annotations: + kubernetes.io/ingress.class: "nginx" + asserts: + - isKind: + of: Ingress + - equal: + path: metadata.annotations["kubernetes.io/ingress.class"] + value: "nginx" diff --git a/deploy/charts/litellm-helm/values.yaml b/deploy/charts/litellm-helm/values.yaml index acb8c9ca32f..35021157826 100644 --- a/deploy/charts/litellm-helm/values.yaml +++ b/deploy/charts/litellm-helm/values.yaml @@ -35,7 +35,8 @@ podAnnotations: {} podLabels: {} terminationGracePeriodSeconds: 90 -topologySpreadConstraints: [] +topologySpreadConstraints: + [] # - maxSkew: 1 # topologyKey: kubernetes.io/hostname # whenUnsatisfiable: DoNotSchedule @@ -46,7 +47,8 @@ topologySpreadConstraints: [] # At the time of writing, the litellm docker image requires write access to the # filesystem on startup so that prisma can install some dependencies. podSecurityContext: {} -securityContext: {} +securityContext: + {} # capabilities: # drop: # - ALL @@ -57,13 +59,15 @@ securityContext: {} # A list of Kubernetes Secret objects that will be exported to the LiteLLM proxy # pod as environment variables. These secrets can then be referenced in the # configuration file (or "litellm" ConfigMap) with `os.environ/` -environmentSecrets: [] +environmentSecrets: + [] # - litellm-env-secret # A list of Kubernetes ConfigMap objects that will be exported to the LiteLLM proxy # pod as environment variables. The ConfigMap kv-pairs can then be referenced in the # configuration file (or "litellm" ConfigMap) with `os.environ/` -environmentConfigMaps: [] +environmentConfigMaps: + [] # - litellm-env-configmap service: @@ -82,7 +86,9 @@ separateHealthPort: 8081 ingress: enabled: false className: "nginx" - annotations: {} + labels: {} + annotations: + {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: @@ -129,7 +135,8 @@ proxy_config: general_settings: master_key: os.environ/PROXY_MASTER_KEY -resources: {} +resources: + {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -231,7 +238,7 @@ migrationJob: # cpu: 100m # memory: 100Mi extraContainers: [] - + # Hook configuration hooks: argocd: @@ -240,30 +247,30 @@ migrationJob: enabled: false # Additional environment variables to be added to the deployment as a map of key-value pairs -envVars: { - # USE_DDTRACE: "true" -} +envVars: {} +# USE_DDTRACE: "true" # Additional environment variables to be added to the deployment as a list of k8s env vars -extraEnvVars: { - # - name: EXTRA_ENV_VAR - # value: EXTRA_ENV_VAR_VALUE -} +extraEnvVars: {} +# - name: EXTRA_ENV_VAR +# value: EXTRA_ENV_VAR_VALUE # Pod Disruption Budget pdb: enabled: false # Set exactly one of the following. If both are set, minAvailable takes precedence. - minAvailable: null # e.g. "50%" or 1 - maxUnavailable: null # e.g. 1 or "20%" + minAvailable: null # e.g. "50%" or 1 + maxUnavailable: null # e.g. 1 or "20%" annotations: {} labels: {} serviceMonitor: enabled: false - labels: {} + labels: + {} # test: test - annotations: {} + annotations: + {} # kubernetes.io/test: test interval: 15s scrapeTimeout: 10s @@ -273,4 +280,4 @@ serviceMonitor: # action: replace namespaceSelector: matchNames: [] - # - test-namespace \ No newline at end of file + # - test-namespace From 1ac2655b17f006f738632c27194153b93e9faa0c Mon Sep 17 00:00:00 2001 From: rioiart Date: Wed, 3 Dec 2025 07:46:03 +0100 Subject: [PATCH 288/349] Fix/organization max budget not enforced (#17334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add failing tests for organization budget enforcement bug Add comprehensive tests exposing that organization-level budgets are retrieved but never enforced during request authentication. Tests verify: 1. Basic org budget exceeded scenario (team under budget, org over) 2. Multiple teams collectively exceeding org budget 3. Organization budget fields exist but are never checked 4. Inconsistency between team budget enforcement (works) and org (doesn't) Tests intentionally fail to document the bug. Will be fixed in next commit. Related to organization_max_budget not being enforced in auth_checks.py * fix: enforce organization budget in auth checks Add organization budget enforcement to common_checks() in auth_checks.py. Previously, organization_max_budget was retrieved from DB but never checked, allowing teams to collectively exceed their organization's budget limit. Changes: - Add _organization_max_budget_check() function following team budget pattern - Call org budget check after team budget check in common_checks() - Add "organization_budget" to budget_alerts type literals - Update tests to verify org budget is enforced Budget hierarchy is now properly enforced: Organization Budget (hard ceiling) └─ Team Budget (sub-allocation) └─ Team Member Budget (per-user within team) └─ Key Budget (per-key) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: add organization_id to budget alerts, fix enum comparison and linting of newly added code - Add organization_id field to CallInfo class for better alert context - Include organization_id in budget alerts (token, soft, team, org) - Fix event_group enum comparison (was comparing enum to string) - Add OrganizationBudgetAlert class for organization budget alerting - Add organization_budget to test parameterizations - Apply Black formatting to slack_alerting.py --------- Co-authored-by: Claude --- .../SlackAlerting/budget_alert_types.py | 10 + .../SlackAlerting/slack_alerting.py | 19 +- litellm/proxy/_types.py | 1 + litellm/proxy/auth/auth_checks.py | 70 ++++ litellm/proxy/utils.py | 1 + tests/logging_callback_tests/test_alerting.py | 2 + .../test_organization_budget_enforcement.py | 344 ++++++++++++++++++ 7 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 tests/test_litellm/proxy/auth/test_organization_budget_enforcement.py diff --git a/litellm/integrations/SlackAlerting/budget_alert_types.py b/litellm/integrations/SlackAlerting/budget_alert_types.py index 1e9ad286e37..dadfef3fc40 100644 --- a/litellm/integrations/SlackAlerting/budget_alert_types.py +++ b/litellm/integrations/SlackAlerting/budget_alert_types.py @@ -50,6 +50,14 @@ class TeamBudgetAlert(BaseBudgetAlertType): return user_info.team_id or "default_id" +class OrganizationBudgetAlert(BaseBudgetAlertType): + def get_event_message(self) -> str: + return "Organization Budget: " + + def get_id(self, user_info: CallInfo) -> str: + return user_info.organization_id or "default_id" + + class TokenBudgetAlert(BaseBudgetAlertType): def get_event_message(self) -> str: return "Key Budget: " @@ -72,6 +80,7 @@ def get_budget_alert_type( "soft_budget", "user_budget", "team_budget", + "organization_budget", "proxy_budget", "projected_limit_exceeded", ], @@ -83,6 +92,7 @@ def get_budget_alert_type( "soft_budget": SoftBudgetAlert(), "user_budget": UserBudgetAlert(), "team_budget": TeamBudgetAlert(), + "organization_budget": OrganizationBudgetAlert(), "token_budget": TokenBudgetAlert(), "projected_limit_exceeded": ProjectedLimitExceededAlert(), } diff --git a/litellm/integrations/SlackAlerting/slack_alerting.py b/litellm/integrations/SlackAlerting/slack_alerting.py index 3efe5873786..0e691e2c43f 100644 --- a/litellm/integrations/SlackAlerting/slack_alerting.py +++ b/litellm/integrations/SlackAlerting/slack_alerting.py @@ -134,19 +134,25 @@ class SlackAlerting(CustomBatchLogger): if llm_router is not None: self.llm_router = llm_router - def _prepare_outage_value_for_cache(self, outage_value: Union[dict, ProviderRegionOutageModel, OutageModel]) -> dict: + def _prepare_outage_value_for_cache( + self, outage_value: Union[dict, ProviderRegionOutageModel, OutageModel] + ) -> dict: """ Helper method to prepare outage value for Redis caching. Converts set objects to lists for JSON serialization. """ # Convert to dict for processing cache_value = dict(outage_value) - - if "deployment_ids" in cache_value and isinstance(cache_value["deployment_ids"], set): + + if "deployment_ids" in cache_value and isinstance( + cache_value["deployment_ids"], set + ): cache_value["deployment_ids"] = list(cache_value["deployment_ids"]) return cache_value - def _restore_outage_value_from_cache(self, outage_value: Optional[dict]) -> Optional[dict]: + def _restore_outage_value_from_cache( + self, outage_value: Optional[dict] + ) -> Optional[dict]: """ Helper method to restore outage value after retrieving from cache. Converts list objects back to sets for proper handling. @@ -528,6 +534,7 @@ class SlackAlerting(CustomBatchLogger): "soft_budget", "user_budget", "team_budget", + "organization_budget", "proxy_budget", "projected_limit_exceeded", ], @@ -1338,7 +1345,7 @@ Model Info: subject=email_event["subject"], html=email_event["html"], ) - if webhook_event.event_group == "team": + if webhook_event.event_group == Litellm_EntityType.TEAM: from litellm.integrations.email_alerting import send_team_budget_alert await send_team_budget_alert(webhook_event=webhook_event) @@ -1399,7 +1406,7 @@ Model Info: current_time = datetime.now().strftime("%H:%M:%S") _proxy_base_url = os.getenv("PROXY_BASE_URL", None) # Use .name if it's an enum, otherwise use as is - alert_type_name = getattr(alert_type, 'name', alert_type) + alert_type_name = getattr(alert_type, "name", alert_type) alert_type_formatted = f"Alert type: `{alert_type_name}`" if alert_type == "daily_reports" or alert_type == "new_model_added": formatted_message = alert_type_formatted + message diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7e7d4049818..53a8627bc8f 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2444,6 +2444,7 @@ class CallInfo(LiteLLMPydanticObjectBase): user_id: Optional[str] = None team_id: Optional[str] = None team_alias: Optional[str] = None + organization_id: Optional[str] = None user_email: Optional[str] = None key_alias: Optional[str] = None projected_exceeded_date: Optional[str] = None diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index c9774b18b88..45b0752d4cd 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -143,6 +143,14 @@ async def common_checks( valid_token=valid_token, ) + # 3.1. If organization is in budget + await _organization_max_budget_check( + valid_token=valid_token, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + await _tag_max_budget_check( request_body=request_body, prisma_client=prisma_client, @@ -1893,6 +1901,7 @@ async def _virtual_key_max_budget_check( max_budget=valid_token.max_budget, user_id=valid_token.user_id, team_id=valid_token.team_id, + organization_id=valid_token.org_id, user_email=user_email, key_alias=valid_token.key_alias, event_group=Litellm_EntityType.KEY, @@ -1939,6 +1948,7 @@ async def _virtual_key_soft_budget_check( user_id=valid_token.user_id, team_id=valid_token.team_id, team_alias=valid_token.team_alias, + organization_id=valid_token.org_id, user_email=None, key_alias=valid_token.key_alias, event_group=Litellm_EntityType.KEY, @@ -1977,6 +1987,7 @@ async def _team_max_budget_check( user_id=valid_token.user_id, team_id=valid_token.team_id, team_alias=valid_token.team_alias, + organization_id=valid_token.org_id, event_group=Litellm_EntityType.TEAM, ) asyncio.create_task( @@ -1993,6 +2004,65 @@ async def _team_max_budget_check( ) +async def _organization_max_budget_check( + valid_token: Optional[UserAPIKeyAuth], + prisma_client: Optional[PrismaClient], + user_api_key_cache: DualCache, + proxy_logging_obj: ProxyLogging, +): + """ + Check if the organization is over its max budget. + + Raises: + BudgetExceededError if the organization is over its max budget. + Triggers a budget alert if the organization is over its max budget. + """ + # Only check if token has organization info and organization_max_budget is set + if ( + valid_token is None + or valid_token.org_id is None + or valid_token.organization_max_budget is None + or valid_token.organization_max_budget <= 0 + ): + return + + # Get organization object to check current spend + if prisma_client is not None: + org_table = await get_org_object( + org_id=valid_token.org_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + ) + + if ( + org_table is not None + and org_table.spend >= valid_token.organization_max_budget + ): + # Trigger budget alert + call_info = CallInfo( + token=valid_token.token, + spend=org_table.spend, + max_budget=valid_token.organization_max_budget, + user_id=valid_token.user_id, + team_id=valid_token.team_id, + team_alias=valid_token.team_alias, + organization_id=valid_token.org_id, + event_group=Litellm_EntityType.ORGANIZATION, + ) + asyncio.create_task( + proxy_logging_obj.budget_alerts( + type="organization_budget", + user_info=call_info, + ) + ) + + raise litellm.BudgetExceededError( + current_cost=org_table.spend, + max_budget=valid_token.organization_max_budget, + message=f"Budget has been exceeded! Organization={valid_token.org_id} Current cost: {org_table.spend}, Max budget: {valid_token.organization_max_budget}", + ) + + async def _tag_max_budget_check( request_body: dict, prisma_client: Optional[PrismaClient], diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 9594a55962c..3b746b757ec 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1072,6 +1072,7 @@ class ProxyLogging: "user_budget", "soft_budget", "team_budget", + "organization_budget", "proxy_budget", "projected_limit_exceeded", ], diff --git a/tests/logging_callback_tests/test_alerting.py b/tests/logging_callback_tests/test_alerting.py index b9ecfaeb3f0..ac7f5cd6aa1 100644 --- a/tests/logging_callback_tests/test_alerting.py +++ b/tests/logging_callback_tests/test_alerting.py @@ -477,6 +477,7 @@ async def test_send_daily_reports_all_zero_or_none(): "token_budget", "user_budget", "team_budget", + "organization_budget", "proxy_budget", "projected_limit_exceeded", ], @@ -514,6 +515,7 @@ async def test_send_token_budget_crossed_alerts(alerting_type): "token_budget", "user_budget", "team_budget", + "organization_budget", "proxy_budget", "projected_limit_exceeded", ], diff --git a/tests/test_litellm/proxy/auth/test_organization_budget_enforcement.py b/tests/test_litellm/proxy/auth/test_organization_budget_enforcement.py new file mode 100644 index 00000000000..9c2adca9cd3 --- /dev/null +++ b/tests/test_litellm/proxy/auth/test_organization_budget_enforcement.py @@ -0,0 +1,344 @@ +""" +Tests for organization budget enforcement. + +These tests verify that organization-level budgets are properly enforced during +request authentication. When an organization's spend exceeds its max_budget, +requests should fail with BudgetExceededError. + +This prevents teams within an organization from collectively exceeding the +organization's budget limit. +""" + +import asyncio +import os +import sys +from typing import Optional +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../../../")) + +import litellm +from litellm.proxy._types import ( + LiteLLM_BudgetTable, + LiteLLM_OrganizationTable, + LiteLLM_TeamTable, + UserAPIKeyAuth, +) +from litellm.proxy.auth.auth_checks import common_checks +from litellm.proxy.utils import ProxyLogging + + +@pytest.mark.asyncio +async def test_organization_budget_exceeded_blocks_request(): + """ + Bug: Organization budget is retrieved but NEVER enforced. + + When organization spend >= organization_max_budget, requests should fail + with BudgetExceededError. Currently this passes because no check exists. + """ + org_id = "test-org-budget-exceeded" + + # Organization with max_budget of 100, but spend is 150 + org_object = LiteLLM_OrganizationTable( + organization_id=org_id, + budget_id="org-budget-1", + spend=150.0, # Over budget! + models=["gpt-4"], + created_by="test", + updated_by="test", + litellm_budget_table=LiteLLM_BudgetTable( + max_budget=100.0, # Budget is 100 + ), + ) + + # Team within the organization (team itself is under budget) + team_object = LiteLLM_TeamTable( + team_id="test-team-1", + organization_id=org_id, + max_budget=50.0, # Team budget is 50 + spend=10.0, # Team spend is only 10 - under budget + models=["gpt-4"], + ) + + # Valid token with organization info + valid_token = UserAPIKeyAuth( + token="sk-test-123", + team_id="test-team-1", + org_id=org_id, + organization_max_budget=100.0, # This is set but never checked! + ) + + mock_request = MagicMock() + mock_request.url.path = "/v1/chat/completions" + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.budget_alerts = AsyncMock() + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + with patch("litellm.proxy.proxy_server.user_api_key_cache") as mock_cache: + with patch("litellm.proxy.auth.auth_checks.get_org_object", new_callable=AsyncMock) as mock_get_org: + mock_get_org.return_value = org_object + + # BUG: This should raise BudgetExceededError but currently passes + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body={"model": "gpt-4"}, + team_object=team_object, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=mock_request, + ) + + assert "Organization" in str(exc_info.value.message) + assert exc_info.value.current_cost == 150.0 + assert exc_info.value.max_budget == 100.0 + + +@pytest.mark.asyncio +async def test_multiple_teams_exceed_organization_budget(): + """ + Test that organization budget is enforced even when individual teams are under budget. + + Scenario: + - Organization max_budget = $5000, spend = $5000 (at limit) + - Team A spend = $1500 (under team budget of $2000) + - Request via Team A should FAIL because org is at budget limit + + Expected: Request fails with BudgetExceededError + """ + org_id = "multi-team-org" + + # Organization at budget limit + org_object = LiteLLM_OrganizationTable( + organization_id=org_id, + budget_id="org-budget-2", + spend=5000.0, # At $5000 limit + models=["gpt-4"], + created_by="test", + updated_by="test", + litellm_budget_table=LiteLLM_BudgetTable( + max_budget=5000.0, # Org budget is $5000 + ), + ) + + # Team A - under its own budget, but org is almost at limit + team_a = LiteLLM_TeamTable( + team_id="team-a", + organization_id=org_id, + max_budget=2000.0, + spend=1500.0, # Team A has spent $1500 of its $2000 budget + models=["gpt-4"], + ) + + valid_token = UserAPIKeyAuth( + token="sk-team-a-key", + team_id="team-a", + org_id=org_id, + organization_max_budget=5000.0, # Set but never enforced + ) + + mock_request = MagicMock() + mock_request.url.path = "/v1/chat/completions" + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.budget_alerts = AsyncMock() + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + with patch("litellm.proxy.proxy_server.user_api_key_cache") as mock_cache: + with patch("litellm.proxy.auth.auth_checks.get_org_object", new_callable=AsyncMock) as mock_get_org: + mock_get_org.return_value = org_object + + # Org is at budget limit, should raise BudgetExceededError + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body={"model": "gpt-4"}, + team_object=team_a, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=mock_request, + ) + + # Verify the error message mentions organization + assert "Organization" in str(exc_info.value.message) + assert exc_info.value.current_cost == 5000.0 + assert exc_info.value.max_budget == 5000.0 + + +@pytest.mark.asyncio +async def test_organization_budget_fields_are_checked(): + """ + Verify that organization_max_budget is populated in UserAPIKeyAuth + and BudgetExceededError is raised when organization is over budget. + """ + # Token has org budget info + valid_token = UserAPIKeyAuth( + token="sk-test", + team_id="test-team", + org_id="test-org", + organization_max_budget=100.0, # Budget is $100 + ) + + # Verify the field exists and is set + assert valid_token.organization_max_budget == 100.0 + assert valid_token.org_id == "test-org" + + team_object = LiteLLM_TeamTable( + team_id="test-team", + organization_id="test-org", + max_budget=None, + spend=0.0, + models=["gpt-4"], + ) + + mock_request = MagicMock() + mock_request.url.path = "/v1/chat/completions" + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.budget_alerts = AsyncMock() + + # Organization is over budget + org_over_budget = LiteLLM_OrganizationTable( + organization_id="test-org", + budget_id="budget-1", + spend=150.0, # Over $100 budget + models=["gpt-4"], + created_by="test", + updated_by="test", + litellm_budget_table=LiteLLM_BudgetTable(max_budget=100.0), + ) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + with patch("litellm.proxy.proxy_server.user_api_key_cache") as mock_cache: + with patch("litellm.proxy.auth.auth_checks.get_org_object", new_callable=AsyncMock) as mock_get_org: + mock_get_org.return_value = org_over_budget + + # Organization is over budget, should raise BudgetExceededError + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body={"model": "gpt-4"}, + team_object=team_object, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=mock_request, + ) + + assert exc_info.value.current_cost == 150.0 + assert exc_info.value.max_budget == 100.0 + + +@pytest.mark.asyncio +async def test_both_team_and_org_budget_enforced(): + """ + Verify that both team budget and organization budget are enforced consistently. + + This test verifies: + 1. Team over budget raises BudgetExceededError + 2. Organization over budget also raises BudgetExceededError + """ + mock_request = MagicMock() + mock_request.url.path = "/v1/chat/completions" + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.budget_alerts = AsyncMock() + + # Scenario A: Team over budget - should raise BudgetExceededError + team_over_budget = LiteLLM_TeamTable( + team_id="team-over", + max_budget=100.0, + spend=150.0, # Over budget + models=["gpt-4"], + ) + + valid_token_team = UserAPIKeyAuth( + token="sk-team-test", + team_id="team-over", + ) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + with patch("litellm.proxy.proxy_server.user_api_key_cache") as mock_cache: + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body={"model": "gpt-4"}, + team_object=team_over_budget, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token_team, + request=mock_request, + ) + assert "Team" in str(exc_info.value.message) + + # Scenario B: Org over budget - should also raise BudgetExceededError + org_over_budget = LiteLLM_OrganizationTable( + organization_id="org-over", + budget_id="budget-1", + spend=150.0, # Over $100 budget + models=["gpt-4"], + created_by="test", + updated_by="test", + litellm_budget_table=LiteLLM_BudgetTable(max_budget=100.0), + ) + + team_under_budget = LiteLLM_TeamTable( + team_id="team-under", + organization_id="org-over", + max_budget=50.0, + spend=10.0, # Team is fine + models=["gpt-4"], + ) + + valid_token_org = UserAPIKeyAuth( + token="sk-org-test", + team_id="team-under", + org_id="org-over", + organization_max_budget=100.0, + ) + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + with patch("litellm.proxy.proxy_server.user_api_key_cache") as mock_cache: + with patch("litellm.proxy.auth.auth_checks.get_org_object", new_callable=AsyncMock) as mock_get_org: + mock_get_org.return_value = org_over_budget + + # Organization is over budget, should raise BudgetExceededError + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body={"model": "gpt-4"}, + team_object=team_under_budget, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token_org, + request=mock_request, + ) + + assert "Organization" in str(exc_info.value.message) + assert exc_info.value.current_cost == 150.0 + assert exc_info.value.max_budget == 100.0 From 54e29e7828f698690963eafac8148aeaf466262f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 12:19:21 +0530 Subject: [PATCH 289/349] Enforce support of enforce_user_param to openai post endpoints --- litellm/proxy/auth/auth_checks.py | 13 +- .../proxy/test_enforce_user_param.py | 438 ++++++++++++++++++ 2 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/proxy/test_enforce_user_param.py diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index c9774b18b88..92cffe0ea82 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -182,7 +182,18 @@ async def common_checks( general_settings.get("enforce_user_param", None) is not None and general_settings["enforce_user_param"] is True ): - if RouteChecks.is_llm_api_route(route=route) and "user" not in request_body: + # Get HTTP method from request + http_method = request.method if hasattr(request, 'method') else None + + # Check if it's a POST request and if it's an OpenAI route but not MCP + is_post_method = http_method and http_method.upper() == "POST" + is_openai_route = RouteChecks.is_llm_api_route(route=route) + is_mcp_route = route in LiteLLMRoutes.mcp_routes.value or RouteChecks.check_route_access( + route=route, allowed_routes=LiteLLMRoutes.mcp_routes.value + ) + + # Enforce user param only for POST requests on OpenAI routes (excluding MCP routes) + if is_post_method and is_openai_route and not is_mcp_route and "user" not in request_body: raise Exception( f"'user' param not passed in. 'enforce_user_param'={general_settings['enforce_user_param']}" ) diff --git a/tests/test_litellm/proxy/test_enforce_user_param.py b/tests/test_litellm/proxy/test_enforce_user_param.py new file mode 100644 index 00000000000..5d9369d61bb --- /dev/null +++ b/tests/test_litellm/proxy/test_enforce_user_param.py @@ -0,0 +1,438 @@ +""" +Tests for enforce_user_param feature with POST/GET method filtering and MCP route exclusion. + +Tests verify that: +1. enforce_user_param only applies to POST requests +2. GET requests like /v1/models are not affected +3. MCP routes are excluded from enforcement +4. POST requests to completion endpoints still require user param when enforced +""" + +import asyncio +from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch + +import pytest +from fastapi import Request + +from litellm.proxy._types import LiteLLMRoutes, UserAPIKeyAuth +from litellm.proxy.auth.auth_checks import common_checks +from litellm.proxy.auth.route_checks import RouteChecks + + +class MockRequest: + """Mock FastAPI Request object""" + def __init__(self, method: str = "POST"): + self.method = method + + +def get_mock_user_token(): + """Create a mock UserAPIKeyAuth token for testing""" + return UserAPIKeyAuth( + api_key="test-key", + user_id="test-user", + team_id="test-team", + org_id="test-org", + models=["*"], + metadata={} + ) + + +class TestEnforceUserParamPostGetFiltering: + """Test POST/GET method filtering for enforce_user_param""" + + @pytest.mark.asyncio + async def test_post_completion_without_user_param_should_fail(self): + """POST to /v1/chat/completions without user param should raise error""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + with pytest.raises(Exception) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert "user" in str(exc_info.value).lower() + + @pytest.mark.asyncio + async def test_post_completion_with_user_param_should_pass(self): + """POST to /v1/chat/completions with user param should pass""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}], + "user": "user123" + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise exception + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_get_models_without_user_param_should_pass(self): + """GET to /v1/models without user param should NOT raise error""" + request = MockRequest(method="GET") + general_settings = {"enforce_user_param": True} + request_body = {} # GET requests typically don't have body + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise exception + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/models", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_get_files_without_user_param_should_pass(self): + """GET to /v1/files without user param should NOT raise error""" + request = MockRequest(method="GET") + general_settings = {"enforce_user_param": True} + request_body = {} + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/files", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_post_embeddings_without_user_param_should_fail(self): + """POST to /v1/embeddings without user param should raise error""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "text-embedding-ada-002", + "input": "test" + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + with pytest.raises(Exception) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/embeddings", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert "user" in str(exc_info.value).lower() + + @pytest.mark.asyncio + async def test_post_embeddings_with_user_param_should_pass(self): + """POST to /v1/embeddings with user param should pass""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "text-embedding-ada-002", + "input": "test", + "user": "user123" + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/embeddings", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + +class TestEnforceUserParamMCPExclusion: + """Test MCP route exclusion from enforce_user_param""" + + @pytest.mark.asyncio + async def test_mcp_route_without_user_param_should_pass(self): + """POST to MCP route without user param should NOT raise error""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = {"action": "list_tools"} + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise exception for MCP routes + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/mcp/tools/list", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_mcp_root_route_without_user_param_should_pass(self): + """POST to /mcp without user param should NOT raise error""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": True} + request_body = {"data": "test"} + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/mcp", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + +class TestEnforceUserParamDisabled: + """Test behavior when enforce_user_param is disabled""" + + @pytest.mark.asyncio + async def test_post_without_user_param_when_disabled_should_pass(self): + """POST without user param when enforce_user_param=False should pass""" + request = MockRequest(method="POST") + general_settings = {"enforce_user_param": False} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_post_without_user_param_when_not_set_should_pass(self): + """POST without user param when enforce_user_param not set should pass""" + request = MockRequest(method="POST") + general_settings = {} # enforce_user_param not set + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + +class TestEnforceUserParamEdgeCases: + """Test edge cases for enforce_user_param""" + + @pytest.mark.asyncio + async def test_request_without_method_attribute_should_pass(self): + """Request without method attribute should not raise error""" + request = MagicMock() + del request.method # Remove method attribute + request.__hasattr__ = MagicMock(return_value=False) + + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise error even without method + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_case_insensitive_http_method(self): + """HTTP method comparison should be case-insensitive""" + request = MockRequest(method="post") # lowercase + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + with pytest.raises(Exception) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert "user" in str(exc_info.value).lower() + + @pytest.mark.asyncio + async def test_put_method_should_not_enforce_user_param(self): + """PUT method should not enforce user param (only POST)""" + request = MockRequest(method="PUT") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise for PUT method + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + @pytest.mark.asyncio + async def test_patch_method_should_not_enforce_user_param(self): + """PATCH method should not enforce user param (only POST)""" + request = MockRequest(method="PATCH") + general_settings = {"enforce_user_param": True} + request_body = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}] + } + + with patch('litellm.proxy.auth.auth_checks._is_api_route_allowed', new_callable=AsyncMock, return_value=True): + # Should not raise for PATCH method + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings=general_settings, + route="/v1/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(), + valid_token=get_mock_user_token(), + request=request, + ) + + assert result is True + + +if __name__ == "__main__": + # Run tests with: pytest tests/test_litellm/proxy/test_enforce_user_param.py -v + pytest.main([__file__, "-v"]) From 74ba18df55906165e5796a5408cbfd1fc8047604 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 2 Dec 2025 22:50:13 -0800 Subject: [PATCH 290/349] Litellm chainguard fixes 12 02 2025 p1 (#17406) * build: update dockerfile non root * build: update build * build: update non root * build: dockerfile fixes * build: ensure dockerfile + dockerfile.database also work --- Dockerfile | 15 +++++---------- docker/Dockerfile.database | 16 +++++++++------- docker/Dockerfile.non_root | 8 +++++--- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index f75706805e0..d8397ec4811 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # Base image for building -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base # Runtime image -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base # Builder stage FROM $LITELLM_BUILD_IMAGE AS builder @@ -12,11 +12,9 @@ WORKDIR /app USER root # Install build dependencies -RUN apk add --no-cache gcc python3-dev openssl openssl-dev +RUN apk add --no-cache bash gcc py3-pip python3 python3-dev openssl openssl-dev - -RUN pip install --upgrade pip>=24.3.1 && \ - pip install build +RUN python -m pip install build # Copy the current directory contents into the container at /app COPY . . @@ -48,10 +46,7 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root # Install runtime dependencies -RUN apk add --no-cache openssl tzdata nodejs npm - -# Upgrade pip to fix CVE-2025-8869 -RUN pip install --upgrade pip>=24.3.1 +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip WORKDIR /app # Copy the current directory contents into the container at /app diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index 09b5265191b..0e804cbfd12 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -1,8 +1,8 @@ # Base image for building -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base # Runtime image -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base # Builder stage FROM $LITELLM_BUILD_IMAGE AS builder @@ -13,13 +13,15 @@ USER root # Install build dependencies RUN apk add --no-cache \ - build-base \ + bash \ + gcc \ + py3-pip \ + python3 \ python3-dev \ + openssl \ openssl-dev - -RUN pip install --upgrade pip && \ - pip install build +RUN python -m pip install build # Copy the current directory contents into the container at /app COPY . . @@ -46,7 +48,7 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root # Install runtime dependencies -RUN apk add --no-cache openssl +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip WORKDIR /app # Copy the current directory contents into the container at /app diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 8b66a367eeb..cd1633e319c 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -1,6 +1,6 @@ # Base images -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base # ----------------- # Builder Stage @@ -11,6 +11,8 @@ WORKDIR /app # Install build dependencies including Node.js for UI build USER root RUN apk add --no-cache \ + python3 \ + py3-pip \ clang \ llvm \ lld \ @@ -71,7 +73,7 @@ WORKDIR /app # Install runtime dependencies USER root RUN apk upgrade --no-cache && \ - apk add --no-cache bash libstdc++ ca-certificates openssl supervisor + apk add --no-cache python3 py3-pip bash openssl tzdata nodejs npm supervisor # Copy only necessary artifacts from builder stage for runtime COPY . . From 8edcc4ecc3fc8ca56447e039871176622cf11aba Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 2 Dec 2025 22:52:09 -0800 Subject: [PATCH 291/349] Guardrails API - add streaming support (#17400) * fix(initial-commit): adding a way to get the right response type based on the api route * feat(unified_guardrail.py): support streaming guardrails * test: update tests * fix: fix linting errors * test: update tests --- litellm/batches/main.py | 2 - litellm/constants.py | 18 +- litellm/integrations/custom_guardrail.py | 1 - litellm/litellm_core_utils/README.md | 1 + .../api_route_to_call_types.py | 38 ++ .../guardrail_translation/base_translation.py | 14 + .../chat/guardrail_translation/handler.py | 98 +++++- ...odel_prices_and_context_window_backup.json | 65 ++++ .../unified_guardrail/unified_guardrail.py | 110 +++++- litellm/proxy/utils.py | 21 +- litellm/types/utils.py | 327 +++++++++++++++++- .../test_apply_guardrail_endpoint.py | 116 ++++--- .../test_bedrock_apply_guardrail.py | 161 +++++---- .../rerank/test_rerank_guardrail_handler.py | 72 ++-- .../test_text_completion_guardrail_handler.py | 48 ++- ...test_image_generation_guardrail_handler.py | 30 +- ...test_openai_responses_guardrail_handler.py | 14 +- .../test_text_to_speech_guardrail_handler.py | 73 ++-- ...t_audio_transcription_guardrail_handler.py | 80 +++-- .../content_filter/test_content_filter.py | 173 +++++---- .../guardrail_hooks/test_presidio.py | 51 +-- 21 files changed, 1134 insertions(+), 379 deletions(-) create mode 100644 litellm/litellm_core_utils/api_route_to_call_types.py diff --git a/litellm/batches/main.py b/litellm/batches/main.py index 353b1e25698..b99f4a628dc 100644 --- a/litellm/batches/main.py +++ b/litellm/batches/main.py @@ -18,8 +18,6 @@ from typing import Any, Coroutine, Dict, Literal, Optional, Union, cast import httpx from openai.types.batch import BatchRequestCounts -from openai.types.batch import Metadata -from openai.types.batch import Metadata as OpenAIBatchMetadata import litellm from litellm._logging import verbose_logger diff --git a/litellm/constants.py b/litellm/constants.py index db617a2e475..57029cd2a8c 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -262,7 +262,9 @@ TOGETHER_AI_EMBEDDING_350_M = int(os.getenv("TOGETHER_AI_EMBEDDING_350_M", 350)) QDRANT_SCALAR_QUANTILE = float(os.getenv("QDRANT_SCALAR_QUANTILE", 0.99)) QDRANT_VECTOR_SIZE = int(os.getenv("QDRANT_VECTOR_SIZE", 1536)) CACHED_STREAMING_CHUNK_DELAY = float(os.getenv("CACHED_STREAMING_CHUNK_DELAY", 0.02)) -AUDIO_SPEECH_CHUNK_SIZE = 8192 # chunk_size for audio speech streaming. Balance between latency and memory usage +AUDIO_SPEECH_CHUNK_SIZE = int( + os.getenv("AUDIO_SPEECH_CHUNK_SIZE", 8192) +) # chunk_size for audio speech streaming. Balance between latency and memory usage MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB = int( os.getenv("MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB", 512) ) @@ -285,10 +287,16 @@ REDACTED_BY_LITELM_STRING = "REDACTED_BY_LITELM" MAX_LANGFUSE_INITIALIZED_CLIENTS = int( os.getenv("MAX_LANGFUSE_INITIALIZED_CLIENTS", 50) ) -LOGGING_WORKER_CONCURRENCY = int(os.getenv("LOGGING_WORKER_CONCURRENCY", 100)) # Must be above 0 +LOGGING_WORKER_CONCURRENCY = int( + os.getenv("LOGGING_WORKER_CONCURRENCY", 100) +) # Must be above 0 LOGGING_WORKER_MAX_QUEUE_SIZE = int(os.getenv("LOGGING_WORKER_MAX_QUEUE_SIZE", 50_000)) -LOGGING_WORKER_MAX_TIME_PER_COROUTINE = float(os.getenv("LOGGING_WORKER_MAX_TIME_PER_COROUTINE", 20.0)) -LOGGING_WORKER_CLEAR_PERCENTAGE = int(os.getenv("LOGGING_WORKER_CLEAR_PERCENTAGE", 50)) # Percentage of queue to clear (default: 50%) +LOGGING_WORKER_MAX_TIME_PER_COROUTINE = float( + os.getenv("LOGGING_WORKER_MAX_TIME_PER_COROUTINE", 20.0) +) +LOGGING_WORKER_CLEAR_PERCENTAGE = int( + os.getenv("LOGGING_WORKER_CLEAR_PERCENTAGE", 50) +) # Percentage of queue to clear (default: 50%) MAX_ITERATIONS_TO_CLEAR_QUEUE = int(os.getenv("MAX_ITERATIONS_TO_CLEAR_QUEUE", 200)) MAX_TIME_TO_CLEAR_QUEUE = float(os.getenv("MAX_TIME_TO_CLEAR_QUEUE", 5.0)) LOGGING_WORKER_AGGRESSIVE_CLEAR_COOLDOWN_SECONDS = float( @@ -866,7 +874,7 @@ BEDROCK_INVOKE_PROVIDERS_LITERAL = Literal[ "deepseek_r1", "qwen3", "twelvelabs", - "openai" + "openai", ] BEDROCK_EMBEDDING_PROVIDERS_LITERAL = Literal[ diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 7f74f5d2157..507a754a7e0 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -20,7 +20,6 @@ from litellm.types.guardrails import ( GuardrailEventHooks, LitellmParams, Mode, - PiiEntityType, ) from litellm.types.llms.openai import AllMessageValues from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel diff --git a/litellm/litellm_core_utils/README.md b/litellm/litellm_core_utils/README.md index 6494041291b..b61c8982762 100644 --- a/litellm/litellm_core_utils/README.md +++ b/litellm/litellm_core_utils/README.md @@ -9,4 +9,5 @@ Core files: - `default_encoding.py`: code for loading the default encoding (tiktoken) - `get_llm_provider_logic.py`: code for inferring the LLM provider from a given model name. - `duration_parser.py`: code for parsing durations - e.g. "1d", "1mo", "10s" +- `api_route_to_call_types.py`: mapping of API routes to their corresponding CallTypes (e.g., `/chat/completions` -> [acompletion, completion]) diff --git a/litellm/litellm_core_utils/api_route_to_call_types.py b/litellm/litellm_core_utils/api_route_to_call_types.py new file mode 100644 index 00000000000..35f83de1dd7 --- /dev/null +++ b/litellm/litellm_core_utils/api_route_to_call_types.py @@ -0,0 +1,38 @@ +""" +Dictionary mapping API routes to their corresponding CallTypes in LiteLLM. + +This dictionary maps each API endpoint to the CallTypes that can be used for that route. +Each route can have both async (prefixed with 'a') and sync call types. +""" + +from litellm.types.utils import API_ROUTE_TO_CALL_TYPES, CallTypes + + +def get_call_types_for_route(route: str) -> list: + """ + Get the list of CallTypes for a given API route. + + Args: + route: API route path (e.g., "/chat/completions") + + Returns: + List of CallTypes for that route, or empty list if route not found + """ + return API_ROUTE_TO_CALL_TYPES.get(route, []) + + +def get_routes_for_call_type(call_type: CallTypes) -> list: + """ + Get all routes that use a specific CallType. + + Args: + call_type: The CallType to search for + + Returns: + List of routes that use this CallType + """ + routes = [] + for route, types in API_ROUTE_TO_CALL_TYPES.items(): + if call_type in types: + routes.append(route) + return routes diff --git a/litellm/llms/base_llm/guardrail_translation/base_translation.py b/litellm/llms/base_llm/guardrail_translation/base_translation.py index 5acbf4e9f4f..c1ea3311bd8 100644 --- a/litellm/llms/base_llm/guardrail_translation/base_translation.py +++ b/litellm/llms/base_llm/guardrail_translation/base_translation.py @@ -84,3 +84,17 @@ class BaseTranslation(ABC): user_api_key_dict: User API key metadata (passed separately since response doesn't contain it) """ pass + + async def process_output_streaming_response( + self, + response: Any, + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, + user_api_key_dict: Optional["UserAPIKeyAuth"] = None, + ) -> Any: + """ + Process output streaming response with guardrails. + + Optional to override in subclasses. + """ + return response diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index 0abc94012ee..29fb12a6f74 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -14,16 +14,16 @@ Pattern Overview: This pattern can be replicated for other message formats (e.g., Anthropic). """ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast import litellm from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation -from litellm.types.utils import Choices +from litellm.types.utils import Choices, StreamingChoices if TYPE_CHECKING: from litellm.integrations.custom_guardrail import CustomGuardrail - from litellm.types.utils import ModelResponse + from litellm.types.utils import ModelResponse, ModelResponseStream class OpenAIChatCompletionsHandler(BaseTranslation): @@ -241,21 +241,79 @@ class OpenAIChatCompletionsHandler(BaseTranslation): return response - def _has_text_content(self, response: "ModelResponse") -> bool: + async def process_output_streaming_response( + self, + response: "ModelResponseStream", + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, + user_api_key_dict: Optional[Any] = None, + ) -> Any: + """ + Process output streaming response by applying guardrails to text content. + + Args: + response: LiteLLM ModelResponseStream object + guardrail_to_apply: The guardrail instance to apply + litellm_logging_obj: Optional logging object + user_api_key_dict: User API key metadata to pass to guardrails + + Returns: + Modified response with guardrail applied to content + + Response Format Support: + - String content: choice.message.content = "text here" + - List content: choice.message.content = [{"type": "text", "text": "text here"}, ...] + """ + + # Step 0: Check if response has any text content to process + if not self._has_text_content(response): + return response + + texts_to_check: List[str] = [] + images_to_check: List[str] = [] + task_mappings: List[Tuple[int, Optional[int]]] = [] + # Track (choice_index, content_index) for each text + + # Step 1: Extract all text content and images from response choices + for choice_idx, choice in enumerate(response.choices): + + self._extract_output_text_and_images( + choice=choice, + choice_idx=choice_idx, + texts_to_check=texts_to_check, + images_to_check=images_to_check, + task_mappings=task_mappings, + ) + + def _has_text_content( + self, response: Union["ModelResponse", "ModelResponseStream"] + ) -> bool: """ Check if response has any text content to process. Override this method to customize text content detection. """ - for choice in response.choices: - if isinstance(choice, litellm.Choices): - if choice.message.content and isinstance(choice.message.content, str): - return True + from litellm.types.utils import ModelResponse, ModelResponseStream + + if isinstance(response, ModelResponse): + for choice in response.choices: + if isinstance(choice, litellm.Choices): + if choice.message.content and isinstance( + choice.message.content, str + ): + return True + elif isinstance(response, ModelResponseStream): + for choice in response.choices: + if isinstance(choice, litellm.Choices): + if choice.message.content and isinstance( + choice.message.content, str + ): + return True return False def _extract_output_text_and_images( self, - choice: Any, + choice: Union[Choices, StreamingChoices], choice_idx: int, texts_to_check: List[str], images_to_check: List[str], @@ -266,21 +324,29 @@ class OpenAIChatCompletionsHandler(BaseTranslation): Override this method to customize text/image extraction logic. """ - if not isinstance(choice, litellm.Choices): - return - verbose_proxy_logger.debug( "OpenAI Chat Completions: Processing choice: %s", choice ) - if choice.message.content and isinstance(choice.message.content, str): + # Determine content source based on choice type + content = None + if isinstance(choice, litellm.Choices): + content = choice.message.content + elif isinstance(choice, litellm.StreamingChoices): + content = choice.delta.content + else: + # Unknown choice type, skip processing + return + + # Process content if it exists + if content and isinstance(content, str): # Simple string content - texts_to_check.append(choice.message.content) + texts_to_check.append(content) task_mappings.append((choice_idx, None)) - elif choice.message.content and isinstance(choice.message.content, list): + elif content and isinstance(content, list): # List content (e.g., multimodal response) - for content_idx, content_item in enumerate(choice.message.content): + for content_idx, content_item in enumerate(content): # Extract text content_text = content_item.get("text") if content_text: diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 464f9c185f9..0fc97ce7b0a 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -269,6 +269,71 @@ "supports_response_schema": true, "supports_vision": true }, + "amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 3e-07, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "apac.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "eu.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "us.amazon.nova-2-lite-v1:0": { + "input_cost_per_token": 6e-08, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.75e-06, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, + "amazon.nova-micro-v1:0": { "input_cost_per_token": 3.5e-08, "litellm_provider": "bedrock_converse", diff --git a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py index aeae19a8270..0f05696af42 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py +++ b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py @@ -13,6 +13,7 @@ from litellm.caching.caching import DualCache from litellm.cost_calculator import _infer_call_type from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.integrations.custom_logger import CustomLogger +from litellm.litellm_core_utils.api_route_to_call_types import get_call_types_for_route from litellm.llms import load_guardrail_translation_mappings from litellm.proxy._types import UserAPIKeyAuth from litellm.types.guardrails import GuardrailEventHooks @@ -176,6 +177,113 @@ class UnifiedLLMGuardrails(CustomLogger): See Aim guardrail implementation for an example - https://github.com/BerriAI/litellm/blob/d0e022cfacb8e9ebc5409bb652059b6fd97b45c0/litellm/proxy/guardrails/guardrail_hooks/aim.py#L168 Triggered by mode: 'post_call' + + Supports sampling_rate parameter to control how often chunks are processed. + sampling_rate=1 means every chunk, sampling_rate=5 means every 5th chunk, etc. """ + + global endpoint_guardrail_translation_mappings + from litellm.proxy.common_utils.callback_utils import ( + add_guardrail_to_applied_guardrails_header, + ) + + guardrail_to_apply: CustomGuardrail = request_data.pop( + "guardrail_to_apply", None + ) + + # Get sampling rate from guardrail config or optional_params, default to 5 + sampling_rate = 5 + if guardrail_to_apply is not None: + # Check guardrail config first + guardrail_config = getattr(guardrail_to_apply, "guardrail_config", {}) + sampling_rate = guardrail_config.get( + "streaming_sampling_rate", sampling_rate + ) + + # Also check optional_params as fallback + sampling_rate = self.optional_params.get( + "streaming_sampling_rate", sampling_rate + ) + + if guardrail_to_apply is None: + async for item in response: + yield item + return + + event_type: GuardrailEventHooks = GuardrailEventHooks.post_call + if ( + guardrail_to_apply.should_run_guardrail( + data=request_data, event_type=event_type + ) + is not True + ): + verbose_proxy_logger.debug( + "UnifiedLLMGuardrails: Post-call streaming scanning disabled for %s", + guardrail_to_apply.guardrail_name, + ) + async for item in response: + yield item + return + + # Initialize translation mappings if needed + if endpoint_guardrail_translation_mappings is None: + endpoint_guardrail_translation_mappings = ( + load_guardrail_translation_mappings() + ) + + # Infer call type from first chunk + call_type = None + chunk_counter = 0 + async for item in response: - yield item + chunk_counter += 1 + + # Infer call type from first chunk if not already done + if call_type is None and user_api_key_dict.request_route is not None: + call_types = get_call_types_for_route(user_api_key_dict.request_route) + if call_types is not None: + call_type = call_types[0] + + # If call type not supported, just pass through all chunks + if ( + call_type is None + or CallTypes(call_type) + not in endpoint_guardrail_translation_mappings + ): + yield item + async for remaining_item in response: + yield remaining_item + return + + # Process chunk based on sampling rate + if chunk_counter % sampling_rate == 0: + verbose_proxy_logger.debug( + "Processing streaming chunk %s (sampling_rate=%s) with guardrail %s", + chunk_counter, + sampling_rate, + guardrail_to_apply.guardrail_name, + ) + + endpoint_translation = endpoint_guardrail_translation_mappings[ + CallTypes(call_type) + ]() + + processed_item = ( + await endpoint_translation.process_output_streaming_response( + response=item, + guardrail_to_apply=guardrail_to_apply, + litellm_logging_obj=request_data.get("litellm_logging_obj"), + user_api_key_dict=user_api_key_dict, + ) + ) + + # Add guardrail to applied guardrails header (only once, on first processed chunk) + if chunk_counter == sampling_rate: + add_guardrail_to_applied_guardrails_header( + request_data=request_data, + guardrail_name=guardrail_to_apply.guardrail_name, + ) + + yield processed_item + else: + yield item diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 3b746b757ec..f0dccfae716 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1560,6 +1560,7 @@ class ProxyLogging: Covers: 1. /chat/completions """ + for callback in litellm.callbacks: _callback: Optional[CustomLogger] = None if isinstance(callback, str): @@ -1574,11 +1575,21 @@ class ProxyLogging: ) or _callback.should_run_guardrail( data=request_data, event_type=GuardrailEventHooks.post_call ): - response = _callback.async_post_call_streaming_iterator_hook( - user_api_key_dict=user_api_key_dict, - response=response, - request_data=request_data, - ) + if "apply_guardrail" in type(callback).__dict__: + request_data["guardrail_to_apply"] = callback + response = ( + unified_guardrail.async_post_call_streaming_iterator_hook( + user_api_key_dict=user_api_key_dict, + request_data=request_data, + response=response, + ) + ) + else: + response = _callback.async_post_call_streaming_iterator_hook( + user_api_key_dict=user_api_key_dict, + response=response, + request_data=request_data, + ) return response def _init_response_taking_too_long_task(self, data: Optional[dict] = None): diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 5a582194147..cf3b1480e6a 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -401,6 +401,328 @@ CallTypesLiteral = Literal[ "responses", ] +# Mapping of API routes to their corresponding call types +API_ROUTE_TO_CALL_TYPES = { + # Chat Completions + "/chat/completions": [CallTypes.acompletion, CallTypes.completion], + "/v1/chat/completions": [CallTypes.acompletion, CallTypes.completion], + "/engines/{model}/chat/completions": [CallTypes.acompletion, CallTypes.completion], + "/openai/deployments/{model}/chat/completions": [ + CallTypes.acompletion, + CallTypes.completion, + ], + # Text Completions + "/completions": [CallTypes.atext_completion, CallTypes.text_completion], + "/v1/completions": [CallTypes.atext_completion, CallTypes.text_completion], + "/engines/{model}/completions": [ + CallTypes.atext_completion, + CallTypes.text_completion, + ], + "/openai/deployments/{model}/completions": [ + CallTypes.atext_completion, + CallTypes.text_completion, + ], + # Embeddings + "/embeddings": [CallTypes.aembedding, CallTypes.embedding], + "/v1/embeddings": [CallTypes.aembedding, CallTypes.embedding], + "/engines/{model}/embeddings": [CallTypes.aembedding, CallTypes.embedding], + "/openai/deployments/{model}/embeddings": [ + CallTypes.aembedding, + CallTypes.embedding, + ], + # Image Generation + "/images/generations": [CallTypes.aimage_generation, CallTypes.image_generation], + "/v1/images/generations": [CallTypes.aimage_generation, CallTypes.image_generation], + "/engines/{model}/images/generations": [ + CallTypes.aimage_generation, + CallTypes.image_generation, + ], + "/openai/deployments/{model}/images/generations": [ + CallTypes.aimage_generation, + CallTypes.image_generation, + ], + # Image Edits + "/images/edits": [CallTypes.aimage_edit, CallTypes.image_edit], + "/v1/images/edits": [CallTypes.aimage_edit, CallTypes.image_edit], + # Audio Transcriptions + "/audio/transcriptions": [CallTypes.atranscription, CallTypes.transcription], + "/v1/audio/transcriptions": [CallTypes.atranscription, CallTypes.transcription], + # Audio Speech + "/audio/speech": [CallTypes.aspeech, CallTypes.speech], + "/v1/audio/speech": [CallTypes.aspeech, CallTypes.speech], + # Moderations + "/moderations": [CallTypes.amoderation, CallTypes.moderation], + "/v1/moderations": [CallTypes.amoderation, CallTypes.moderation], + # Rerank + "/rerank": [CallTypes.arerank, CallTypes.rerank], + "/v1/rerank": [CallTypes.arerank, CallTypes.rerank], + "/v2/rerank": [CallTypes.arerank, CallTypes.rerank], + # Search + "/search": [CallTypes.asearch, CallTypes.search], + "/v1/search": [CallTypes.asearch, CallTypes.search], + # Batches + "/batches": [CallTypes.acreate_batch, CallTypes.create_batch], + "/v1/batches": [CallTypes.acreate_batch, CallTypes.create_batch], + "/batches/{batch_id}": [CallTypes.aretrieve_batch, CallTypes.retrieve_batch], + "/v1/batches/{batch_id}": [CallTypes.aretrieve_batch, CallTypes.retrieve_batch], + # Files + "/files": [ + CallTypes.acreate_file, + CallTypes.create_file, + CallTypes.afile_list, + CallTypes.file_list, + ], + "/v1/files": [ + CallTypes.acreate_file, + CallTypes.create_file, + CallTypes.afile_list, + CallTypes.file_list, + ], + "/files/{file_id}": [ + CallTypes.afile_retrieve, + CallTypes.file_retrieve, + CallTypes.afile_delete, + CallTypes.file_delete, + ], + "/v1/files/{file_id}": [ + CallTypes.afile_retrieve, + CallTypes.file_retrieve, + CallTypes.afile_delete, + CallTypes.file_delete, + ], + "/files/{file_id}/content": [CallTypes.afile_content, CallTypes.file_content], + "/v1/files/{file_id}/content": [CallTypes.afile_content, CallTypes.file_content], + # Assistants + "/assistants": [ + CallTypes.aget_assistants, + CallTypes.get_assistants, + CallTypes.acreate_assistants, + CallTypes.create_assistants, + ], + "/v1/assistants": [ + CallTypes.aget_assistants, + CallTypes.get_assistants, + CallTypes.acreate_assistants, + CallTypes.create_assistants, + ], + "/assistants/{assistant_id}": [ + CallTypes.adelete_assistant, + CallTypes.delete_assistant, + ], + "/v1/assistants/{assistant_id}": [ + CallTypes.adelete_assistant, + CallTypes.delete_assistant, + ], + # Threads + "/threads": [CallTypes.acreate_thread, CallTypes.create_thread], + "/v1/threads": [CallTypes.acreate_thread, CallTypes.create_thread], + "/threads/{thread_id}": [CallTypes.aget_thread, CallTypes.get_thread], + "/v1/threads/{thread_id}": [CallTypes.aget_thread, CallTypes.get_thread], + # Thread Messages + "/threads/{thread_id}/messages": [ + CallTypes.a_add_message, + CallTypes.add_message, + CallTypes.aget_messages, + CallTypes.get_messages, + ], + "/v1/threads/{thread_id}/messages": [ + CallTypes.a_add_message, + CallTypes.add_message, + CallTypes.aget_messages, + CallTypes.get_messages, + ], + # Thread Runs + "/threads/{thread_id}/runs": [ + CallTypes.arun_thread, + CallTypes.run_thread, + CallTypes.arun_thread_stream, + CallTypes.run_thread_stream, + ], + "/v1/threads/{thread_id}/runs": [ + CallTypes.arun_thread, + CallTypes.run_thread, + CallTypes.arun_thread_stream, + CallTypes.run_thread_stream, + ], + # Fine-tuning Jobs + "/fine_tuning/jobs": [ + CallTypes.acreate_fine_tuning_job, + CallTypes.create_fine_tuning_job, + CallTypes.alist_fine_tuning_jobs, + CallTypes.list_fine_tuning_jobs, + ], + "/v1/fine_tuning/jobs": [ + CallTypes.acreate_fine_tuning_job, + CallTypes.create_fine_tuning_job, + CallTypes.alist_fine_tuning_jobs, + CallTypes.list_fine_tuning_jobs, + ], + "/fine_tuning/jobs/{fine_tuning_job_id}": [ + CallTypes.aretrieve_fine_tuning_job, + CallTypes.retrieve_fine_tuning_job, + ], + "/v1/fine_tuning/jobs/{fine_tuning_job_id}": [ + CallTypes.aretrieve_fine_tuning_job, + CallTypes.retrieve_fine_tuning_job, + ], + "/fine_tuning/jobs/{fine_tuning_job_id}/cancel": [ + CallTypes.acancel_fine_tuning_job, + CallTypes.cancel_fine_tuning_job, + ], + "/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel": [ + CallTypes.acancel_fine_tuning_job, + CallTypes.cancel_fine_tuning_job, + ], + # Video Generation + "/videos": [ + CallTypes.acreate_video, + CallTypes.create_video, + CallTypes.avideo_list, + CallTypes.video_list, + ], + "/v1/videos": [ + CallTypes.acreate_video, + CallTypes.create_video, + CallTypes.avideo_list, + CallTypes.video_list, + ], + "/videos/{video_id}": [ + CallTypes.avideo_retrieve, + CallTypes.video_retrieve, + CallTypes.avideo_delete, + CallTypes.video_delete, + ], + "/v1/videos/{video_id}": [ + CallTypes.avideo_retrieve, + CallTypes.video_retrieve, + CallTypes.avideo_delete, + CallTypes.video_delete, + ], + "/videos/{video_id}/content": [CallTypes.avideo_content, CallTypes.video_content], + "/v1/videos/{video_id}/content": [ + CallTypes.avideo_content, + CallTypes.video_content, + ], + "/videos/{video_id}/remix": [CallTypes.avideo_remix, CallTypes.video_remix], + "/v1/videos/{video_id}/remix": [CallTypes.avideo_remix, CallTypes.video_remix], + # Vector Stores + "/vector_stores": [CallTypes.avector_store_create, CallTypes.vector_store_create], + "/v1/vector_stores": [ + CallTypes.avector_store_create, + CallTypes.vector_store_create, + ], + "/vector_stores/{vector_store_id}/search": [ + CallTypes.avector_store_search, + CallTypes.vector_store_search, + ], + "/v1/vector_stores/{vector_store_id}/search": [ + CallTypes.avector_store_search, + CallTypes.vector_store_search, + ], + "/vector_stores/{vector_store_id}/files": [ + CallTypes.avector_store_file_create, + CallTypes.vector_store_file_create, + CallTypes.avector_store_file_list, + CallTypes.vector_store_file_list, + ], + "/v1/vector_stores/{vector_store_id}/files": [ + CallTypes.avector_store_file_create, + CallTypes.vector_store_file_create, + CallTypes.avector_store_file_list, + CallTypes.vector_store_file_list, + ], + "/vector_stores/{vector_store_id}/files/{file_id}": [ + CallTypes.avector_store_file_retrieve, + CallTypes.vector_store_file_retrieve, + CallTypes.avector_store_file_delete, + CallTypes.vector_store_file_delete, + ], + "/v1/vector_stores/{vector_store_id}/files/{file_id}": [ + CallTypes.avector_store_file_retrieve, + CallTypes.vector_store_file_retrieve, + CallTypes.avector_store_file_delete, + CallTypes.vector_store_file_delete, + ], + "/vector_stores/{vector_store_id}/files/{file_id}/content": [ + CallTypes.avector_store_file_content, + CallTypes.vector_store_file_content, + ], + "/v1/vector_stores/{vector_store_id}/files/{file_id}/content": [ + CallTypes.avector_store_file_content, + CallTypes.vector_store_file_content, + ], + "/vector_stores/{vector_store_id}/files/{file_id}/update": [ + CallTypes.avector_store_file_update, + CallTypes.vector_store_file_update, + ], + "/v1/vector_stores/{vector_store_id}/files/{file_id}/update": [ + CallTypes.avector_store_file_update, + CallTypes.vector_store_file_update, + ], + # Containers + "/containers": [ + CallTypes.acreate_container, + CallTypes.create_container, + CallTypes.alist_containers, + CallTypes.list_containers, + ], + "/v1/containers": [ + CallTypes.acreate_container, + CallTypes.create_container, + CallTypes.alist_containers, + CallTypes.list_containers, + ], + "/containers/{container_id}": [ + CallTypes.aretrieve_container, + CallTypes.retrieve_container, + CallTypes.adelete_container, + CallTypes.delete_container, + ], + "/v1/containers/{container_id}": [ + CallTypes.aretrieve_container, + CallTypes.retrieve_container, + CallTypes.adelete_container, + CallTypes.delete_container, + ], + # Responses API + "/responses": [CallTypes.aresponses, CallTypes.responses], + "/v1/responses": [CallTypes.aresponses, CallTypes.responses], + "/responses/{response_id}": [CallTypes.aresponses, CallTypes.responses], + "/v1/responses/{response_id}": [CallTypes.aresponses, CallTypes.responses], + "/responses/{response_id}/input_items": [CallTypes.alist_input_items], + "/v1/responses/{response_id}/input_items": [CallTypes.alist_input_items], + # Realtime API + "/realtime": [CallTypes.arealtime], + "/v1/realtime": [CallTypes.arealtime], + # Provider-specific routes + "/anthropic/v1/messages": [CallTypes.anthropic_messages], + # Google GenAI routes + "/generate_content": [CallTypes.agenerate_content, CallTypes.generate_content], + "/models/{model}:generateContent": [ + CallTypes.agenerate_content, + CallTypes.generate_content, + ], + "/generate_content_stream": [ + CallTypes.agenerate_content_stream, + CallTypes.generate_content_stream, + ], + "/models/{model}:streamGenerateContent": [ + CallTypes.agenerate_content_stream, + CallTypes.generate_content_stream, + ], + # MCP (Model Context Protocol) + "/mcp/call_tool": [CallTypes.call_mcp_tool], + # Passthrough endpoints + "/llm_passthrough": [ + CallTypes.llm_passthrough_route, + CallTypes.allm_passthrough_route, + ], + "/v1/llm_passthrough": [ + CallTypes.llm_passthrough_route, + CallTypes.allm_passthrough_route, + ], +} + class PassthroughCallTypes(Enum): passthrough_image_generation = "passthrough-image-generation" @@ -1060,7 +1382,10 @@ class Usage(CompletionUsage): # Auto-calculate text_tokens only if provider didn't set it explicitly # Formula: text_tokens = completion_tokens - reasoning_tokens - image_tokens - audio_tokens - if _completion_tokens_details.text_tokens is None and completion_tokens is not None: + if ( + _completion_tokens_details.text_tokens is None + and completion_tokens is not None + ): calculated_text_tokens = completion_tokens - reasoning_tokens # Subtract other modality tokens if present diff --git a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_apply_guardrail_endpoint.py b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_apply_guardrail_endpoint.py index 186056dac90..7ce99abdd15 100644 --- a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_apply_guardrail_endpoint.py +++ b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_apply_guardrail_endpoint.py @@ -1,6 +1,7 @@ """ Test the /guardrails/apply_guardrail endpoint """ + import os import sys from unittest.mock import AsyncMock, Mock, patch @@ -22,37 +23,45 @@ async def test_apply_guardrail_endpoint_returns_correct_response(): from litellm.proxy.guardrails.guardrail_endpoints import apply_guardrail # Mock the guardrail registry - with patch("litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY") as mock_registry: + with patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY" + ) as mock_registry: # Create a mock guardrail mock_guardrail = Mock(spec=CustomGuardrail) - mock_guardrail.apply_guardrail = AsyncMock(return_value="Redacted text: [REDACTED] and [REDACTED]") - + # Apply guardrail now returns a tuple (List[str], Optional[List[str]]) + mock_guardrail.apply_guardrail = AsyncMock( + return_value=(["Redacted text: [REDACTED] and [REDACTED]"], None) + ) + # Configure the registry to return our mock guardrail mock_registry.get_initialized_guardrail_callback.return_value = mock_guardrail - + # Create the request request = ApplyGuardrailRequest( guardrail_name="test-guardrail", text="Test text with PII", language="en", - entities=["EMAIL_ADDRESS", "PERSON"] + entities=["EMAIL_ADDRESS", "PERSON"], ) - + # Create a mock user API key user_api_key_dict = UserAPIKeyAuth(api_key="test-key") - + # Call the endpoint - response = await apply_guardrail(request=request, user_api_key_dict=user_api_key_dict) - + response = await apply_guardrail( + request=request, user_api_key_dict=user_api_key_dict + ) + # Verify the response is of the correct type assert isinstance(response, ApplyGuardrailResponse) assert response.response_text == "Redacted text: [REDACTED] and [REDACTED]" - - # Verify the guardrail was called with correct parameters + + # Verify the guardrail was called with correct parameters (new signature) mock_guardrail.apply_guardrail.assert_called_once_with( - text="Test text with PII", - language="en", - entities=["EMAIL_ADDRESS", "PERSON"] + texts=["Test text with PII"], + request_data={}, + input_type="request", + images=None, ) @@ -63,23 +72,23 @@ async def test_apply_guardrail_endpoint_guardrail_not_found(): from litellm.proxy.guardrails.guardrail_endpoints import apply_guardrail # Mock the guardrail registry to return None - with patch("litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY") as mock_registry: + with patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY" + ) as mock_registry: mock_registry.get_initialized_guardrail_callback.return_value = None - + # Create the request request = ApplyGuardrailRequest( - guardrail_name="non-existent-guardrail", - text="Test text", - language="en" + guardrail_name="non-existent-guardrail", text="Test text", language="en" ) - + # Create a mock user API key user_api_key_dict = UserAPIKeyAuth(api_key="test-key") - + # Verify exception is raised with pytest.raises(ProxyException) as exc_info: await apply_guardrail(request=request, user_api_key_dict=user_api_key_dict) - + assert "non-existent-guardrail" in exc_info.value.message assert "not found" in exc_info.value.message @@ -90,34 +99,41 @@ async def test_apply_guardrail_endpoint_with_presidio_guardrail(): from litellm.proxy.guardrails.guardrail_endpoints import apply_guardrail # Mock the guardrail registry - with patch("litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY") as mock_registry: + with patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY" + ) as mock_registry: # Create a mock guardrail that simulates Presidio behavior mock_guardrail = Mock(spec=CustomGuardrail) - # Simulate masking PII entities + # Simulate masking PII entities - returns tuple (List[str], Optional[List[str]]) mock_guardrail.apply_guardrail = AsyncMock( - return_value="My name is [PERSON] and my email is [EMAIL_ADDRESS]" + return_value=(["My name is [PERSON] and my email is [EMAIL_ADDRESS]"], None) ) - + # Configure the registry to return our mock guardrail mock_registry.get_initialized_guardrail_callback.return_value = mock_guardrail - + # Create the request request = ApplyGuardrailRequest( guardrail_name="pii-detection-guard", text="My name is John Doe and my email is john@example.com", language="en", - entities=["EMAIL_ADDRESS", "PERSON"] + entities=["EMAIL_ADDRESS", "PERSON"], ) - + # Create a mock user API key user_api_key_dict = UserAPIKeyAuth(api_key="test-key") - + # Call the endpoint - response = await apply_guardrail(request=request, user_api_key_dict=user_api_key_dict) - + response = await apply_guardrail( + request=request, user_api_key_dict=user_api_key_dict + ) + # Verify the response is of the correct type assert isinstance(response, ApplyGuardrailResponse) - assert response.response_text == "My name is [PERSON] and my email is [EMAIL_ADDRESS]" + assert ( + response.response_text + == "My name is [PERSON] and my email is [EMAIL_ADDRESS]" + ) assert "john@example.com" not in response.response_text assert "John Doe" not in response.response_text @@ -128,33 +144,37 @@ async def test_apply_guardrail_endpoint_without_optional_params(): from litellm.proxy.guardrails.guardrail_endpoints import apply_guardrail # Mock the guardrail registry - with patch("litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY") as mock_registry: + with patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY" + ) as mock_registry: # Create a mock guardrail mock_guardrail = Mock(spec=CustomGuardrail) - mock_guardrail.apply_guardrail = AsyncMock(return_value="Processed text") - + # Returns tuple (List[str], Optional[List[str]]) + mock_guardrail.apply_guardrail = AsyncMock( + return_value=(["Processed text"], None) + ) + # Configure the registry to return our mock guardrail mock_registry.get_initialized_guardrail_callback.return_value = mock_guardrail - + # Create the request without optional parameters request = ApplyGuardrailRequest( - guardrail_name="test-guardrail", - text="Test text" + guardrail_name="test-guardrail", text="Test text" ) - + # Create a mock user API key user_api_key_dict = UserAPIKeyAuth(api_key="test-key") - + # Call the endpoint - response = await apply_guardrail(request=request, user_api_key_dict=user_api_key_dict) - + response = await apply_guardrail( + request=request, user_api_key_dict=user_api_key_dict + ) + # Verify the response is of the correct type assert isinstance(response, ApplyGuardrailResponse) assert response.response_text == "Processed text" - - # Verify the guardrail was called with None for optional parameters + + # Verify the guardrail was called with new signature mock_guardrail.apply_guardrail.assert_called_once_with( - text="Test text", - language=None, - entities=None + texts=["Test text"], request_data={}, input_type="request", images=None ) diff --git a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py index e65d01e41e4..203bd05c57c 100644 --- a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py +++ b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py @@ -1,6 +1,7 @@ """ Test the Bedrock guardrail apply_guardrail functionality """ + import os import sys from unittest.mock import AsyncMock, Mock, patch @@ -23,32 +24,29 @@ async def test_bedrock_apply_guardrail_success(): guardrail = BedrockGuardrail( guardrail_name="test-bedrock-guard", guardrailIdentifier="test-guard-id", - guardrailVersion="DRAFT" + guardrailVersion="DRAFT", ) - + # Mock the make_bedrock_api_request method - with patch.object(guardrail, 'make_bedrock_api_request', new_callable=AsyncMock) as mock_api_request: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api_request: # Mock a successful response from Bedrock mock_response = { "action": "ALLOWED", - "content": [ - { - "text": { - "text": "This is a test message with some content" - } - } - ] + "content": [{"text": {"text": "This is a test message with some content"}}], } mock_api_request.return_value = mock_response - - # Test the apply_guardrail method - result = await guardrail.apply_guardrail( - text="This is a test message with some content", - language="en" + + # Test the apply_guardrail method with new signature + result, _ = await guardrail.apply_guardrail( + texts=["This is a test message with some content"], + request_data={}, + input_type="request", ) - + # Verify the result - assert result == "This is a test message with some content" + assert result == ["This is a test message with some content"] mock_api_request.assert_called_once() @@ -59,25 +57,23 @@ async def test_bedrock_apply_guardrail_blocked(): guardrail = BedrockGuardrail( guardrail_name="test-bedrock-guard", guardrailIdentifier="test-guard-id", - guardrailVersion="DRAFT" + guardrailVersion="DRAFT", ) - + # Mock the make_bedrock_api_request method - with patch.object(guardrail, 'make_bedrock_api_request', new_callable=AsyncMock) as mock_api_request: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api_request: # Mock a blocked response from Bedrock - mock_response = { - "action": "BLOCKED", - "reason": "Content violates policy" - } + mock_response = {"action": "BLOCKED", "reason": "Content violates policy"} mock_api_request.return_value = mock_response - + # Test the apply_guardrail method should raise an exception with pytest.raises(Exception) as exc_info: await guardrail.apply_guardrail( - text="This is blocked content", - language="en" + texts=["This is blocked content"], request_data={}, input_type="request" ) - + assert "Content blocked by Bedrock guardrail" in str(exc_info.value) assert "Content violates policy" in str(exc_info.value) @@ -89,30 +85,29 @@ async def test_bedrock_apply_guardrail_with_masking(): guardrail = BedrockGuardrail( guardrail_name="test-bedrock-guard", guardrailIdentifier="test-guard-id", - guardrailVersion="DRAFT" + guardrailVersion="DRAFT", ) - + # Mock the make_bedrock_api_request method - with patch.object(guardrail, 'make_bedrock_api_request', new_callable=AsyncMock) as mock_api_request: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api_request: # Mock a response with masked content mock_response = { "action": "ALLOWED", - "outputs": [ - { - "text": "This is a test message with [REDACTED] content" - } - ] + "outputs": [{"text": "This is a test message with [REDACTED] content"}], } mock_api_request.return_value = mock_response - - # Test the apply_guardrail method - result = await guardrail.apply_guardrail( - text="This is a test message with sensitive content", - language="en" + + # Test the apply_guardrail method with new signature + result, _ = await guardrail.apply_guardrail( + texts=["This is a test message with sensitive content"], + request_data={}, + input_type="request", ) - + # Verify the result contains the masked content - assert result == "This is a test message with [REDACTED] content" + assert result == ["This is a test message with [REDACTED] content"] mock_api_request.assert_called_once() @@ -123,21 +118,22 @@ async def test_bedrock_apply_guardrail_api_failure(): guardrail = BedrockGuardrail( guardrail_name="test-bedrock-guard", guardrailIdentifier="test-guard-id", - guardrailVersion="DRAFT" + guardrailVersion="DRAFT", ) - + # Mock the make_bedrock_api_request method to raise an exception - with patch.object(guardrail, 'make_bedrock_api_request', new_callable=AsyncMock) as mock_api_request: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api_request: mock_api_request.side_effect = Exception("API connection failed") - + # Test the apply_guardrail method should raise an exception with pytest.raises(Exception) as exc_info: await guardrail.apply_guardrail( - text="This is a test message", - language="en" + texts=["This is a test message"], request_data={}, input_type="request" ) - - assert "Bedrock guardrail failed" in str(exc_info.value) + + # The error message should contain the original exception assert "API connection failed" in str(exc_info.value) @@ -150,44 +146,50 @@ async def test_bedrock_apply_guardrail_endpoint_integration(): guardrail = BedrockGuardrail( guardrail_name="test-bedrock-guard", guardrailIdentifier="test-guard-id", - guardrailVersion="DRAFT" + guardrailVersion="DRAFT", ) - + # Mock the guardrail registry - with patch("litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY") as mock_registry: + with patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY" + ) as mock_registry: # Mock the make_bedrock_api_request method - with patch.object(guardrail, 'make_bedrock_api_request', new_callable=AsyncMock) as mock_api_request: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api_request: # Mock a successful response from Bedrock mock_response = { "action": "ALLOWED", - "outputs": [ - { - "text": "This is a test message with processed content" - } - ] + "outputs": [{"text": "This is a test message with processed content"}], } mock_api_request.return_value = mock_response - + # Configure the registry to return our guardrail mock_registry.get_initialized_guardrail_callback.return_value = guardrail - + # Create the request request = ApplyGuardrailRequest( guardrail_name="test-bedrock-guard", text="This is a test message with some content", - language="en" + language="en", ) - + # Create a mock user API key user_api_key_dict = UserAPIKeyAuth(api_key="test-key") - + # Call the endpoint - response = await apply_guardrail(request=request, user_api_key_dict=user_api_key_dict) - + response = await apply_guardrail( + request=request, user_api_key_dict=user_api_key_dict + ) + # Verify the response assert isinstance(response, ApplyGuardrailResponse) - assert response.response_text == "This is a test message with processed content" - mock_api_request.assert_called_once() + assert ( + response.response_text + == "This is a test message with processed content" + ) + # Note: The endpoint now calls apply_guardrail which internally calls make_bedrock_api_request + # The call count check has been removed as it may be called multiple times through the chain @pytest.mark.asyncio @@ -208,18 +210,21 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable request_data = {"messages": request_messages} - with patch.object(guardrail, "make_bedrock_api_request", new_callable=AsyncMock) as mock_api: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api: mock_api.return_value = {"action": "ALLOWED"} - result = await guardrail.apply_guardrail( - text="latest question", + result, _ = await guardrail.apply_guardrail( + texts=["latest question"], request_data=request_data, + input_type="request", ) assert mock_api.called _, kwargs = mock_api.call_args assert kwargs["messages"] == [request_messages[-1]] - assert result == "latest question" + assert result == ["latest question"] @pytest.mark.asyncio @@ -238,19 +243,23 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable request_data = {"messages": request_messages} - with patch.object(guardrail, "make_bedrock_api_request", new_callable=AsyncMock) as mock_api: + with patch.object( + guardrail, "make_bedrock_api_request", new_callable=AsyncMock + ) as mock_api: mock_api.return_value = {"action": "BLOCKED", "reason": "policy"} with pytest.raises(Exception, match="policy") as exc_info: await guardrail.apply_guardrail( - text="blocked", + texts=["blocked"], request_data=request_data, + input_type="request", ) assert mock_api.called _, kwargs = mock_api.call_args assert kwargs["messages"] == [request_messages[-1]] - assert "Bedrock guardrail failed" in str(exc_info.value) + assert "Content blocked by Bedrock guardrail" in str(exc_info.value) + def test_bedrock_guardrail_filters_latest_user_message_when_enabled(): guardrail = BedrockGuardrail( diff --git a/tests/test_litellm/llms/cohere/rerank/test_rerank_guardrail_handler.py b/tests/test_litellm/llms/cohere/rerank/test_rerank_guardrail_handler.py index 8fdc09fc752..9c2bbeb7a68 100644 --- a/tests/test_litellm/llms/cohere/rerank/test_rerank_guardrail_handler.py +++ b/tests/test_litellm/llms/cohere/rerank/test_rerank_guardrail_handler.py @@ -5,6 +5,7 @@ Unit tests for Cohere Rerank Guardrail Translation Handler import asyncio import os import sys +from typing import List, Optional, Tuple import pytest @@ -20,8 +21,10 @@ from litellm.types.utils import CallTypes class MockGuardrail(CustomGuardrail): """Mock guardrail for testing""" - async def apply_guardrail(self, text: str, language=None, entities=None) -> str: - return f"{text} [GUARDRAILED]" + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: + return ([f"{text} [GUARDRAILED]" for text in texts], None) class TestHandlerDiscovery: @@ -183,17 +186,20 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: import re - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - masked = masked.replace("John Doe", "[NAME_REDACTED]") - return masked + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + masked = masked.replace("John Doe", "[NAME_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = CohereRerankHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -231,21 +237,24 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: import re - # Mask emails - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - # Mask phone numbers - masked = re.sub(r"\d{3}-\d{3}-\d{4}", "[PHONE_REDACTED]", masked) - # Mask names - masked = masked.replace("Alice Smith", "[NAME_REDACTED]") - return masked + masked_texts = [] + for text in texts: + # Mask emails + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + # Mask phone numbers + masked = re.sub(r"\d{3}-\d{3}-\d{4}", "[PHONE_REDACTED]", masked) + # Mask names + masked = masked.replace("Alice Smith", "[NAME_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = CohereRerankHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -340,13 +349,16 @@ class TestContentFilteringScenario: """Mock content filter guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: bad_words = ["inappropriate", "offensive"] - filtered = text - for word in bad_words: - filtered = filtered.replace(word, "[FILTERED]") - return filtered + filtered_texts = [] + for text in texts: + filtered = text + for word in bad_words: + filtered = filtered.replace(word, "[FILTERED]") + filtered_texts.append(filtered) + return (filtered_texts, None) handler = CohereRerankHandler() guardrail = ContentFilterGuardrail(guardrail_name="content_filter") diff --git a/tests/test_litellm/llms/openai/completion/test_text_completion_guardrail_handler.py b/tests/test_litellm/llms/openai/completion/test_text_completion_guardrail_handler.py index 97ca423f773..c861e48ad48 100644 --- a/tests/test_litellm/llms/openai/completion/test_text_completion_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/completion/test_text_completion_guardrail_handler.py @@ -4,6 +4,7 @@ Unit tests for OpenAI Text Completion Guardrail Translation Handler import os import sys +from typing import List, Optional, Tuple from unittest.mock import MagicMock import pytest @@ -21,8 +22,10 @@ from litellm.types.utils import CallTypes, TextChoices, TextCompletionResponse class MockGuardrail(CustomGuardrail): """Mock guardrail for testing""" - async def apply_guardrail(self, text: str, language=None, entities=None) -> str: - return f"{text} [GUARDRAILED]" + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: + return ([f"{text} [GUARDRAILED]" for text in texts], None) class TestHandlerDiscovery: @@ -243,19 +246,22 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: replace email-like patterns import re - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - # Replace names (simple mock) - masked = masked.replace("John Doe", "[NAME_REDACTED]") - return masked + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + # Replace names (simple mock) + masked = masked.replace("John Doe", "[NAME_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAITextCompletionHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -303,15 +309,19 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: import re - return re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAITextCompletionHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") diff --git a/tests/test_litellm/llms/openai/image_generation/test_image_generation_guardrail_handler.py b/tests/test_litellm/llms/openai/image_generation/test_image_generation_guardrail_handler.py index 529d74f63d9..5e183e32208 100644 --- a/tests/test_litellm/llms/openai/image_generation/test_image_generation_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/image_generation/test_image_generation_guardrail_handler.py @@ -4,6 +4,7 @@ Unit tests for OpenAI Image Generation Guardrail Translation Handler import os import sys +from typing import List, Optional, Tuple import pytest @@ -20,8 +21,10 @@ from litellm.types.utils import CallTypes, ImageObject, ImageResponse class MockGuardrail(CustomGuardrail): """Mock guardrail for testing""" - async def apply_guardrail(self, text: str, language=None, entities=None) -> str: - return f"{text} [GUARDRAILED]" + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: + return ([f"{text} [GUARDRAILED]" for text in texts], None) class TestHandlerDiscovery: @@ -141,19 +144,22 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: replace email-like patterns import re - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - # Replace names (simple mock) - masked = masked.replace("John Doe", "[NAME_REDACTED]") - return masked + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + # Replace names (simple mock) + masked = masked.replace("John Doe", "[NAME_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAIImageGenerationHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") diff --git a/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py b/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py index b447c281aa3..c04f825e362 100644 --- a/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py @@ -7,7 +7,7 @@ with guardrail transformations. import os import sys -from typing import Any +from typing import Any, List, Optional, Tuple from unittest.mock import AsyncMock, MagicMock import pytest @@ -29,9 +29,11 @@ from litellm.types.utils import CallTypes class MockGuardrail(CustomGuardrail): """Mock guardrail for testing that transforms text""" - async def apply_guardrail(self, text: str) -> str: + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: """Append [GUARDRAILED] to text""" - return f"{text} [GUARDRAILED]" + return ([f"{text} [GUARDRAILED]" for text in texts], None) class TestOpenAIResponsesHandlerDiscovery: @@ -450,7 +452,10 @@ class TestOpenAIResponsesHandlerEdgeCases: "role": "user", "content": [ {"type": "text", "text": "List content"}, - {"type": "image_url", "image_url": {"url": "http://example.com"}}, + { + "type": "image_url", + "image_url": {"url": "http://example.com"}, + }, ], "type": "message", }, @@ -492,4 +497,3 @@ class TestOpenAIResponsesHandlerEdgeCases: # Should skip processing and return unchanged assert result == response - diff --git a/tests/test_litellm/llms/openai/speech/test_text_to_speech_guardrail_handler.py b/tests/test_litellm/llms/openai/speech/test_text_to_speech_guardrail_handler.py index b4064a22c17..dfd96beb2f4 100644 --- a/tests/test_litellm/llms/openai/speech/test_text_to_speech_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/speech/test_text_to_speech_guardrail_handler.py @@ -4,6 +4,7 @@ Unit tests for OpenAI Text-to-Speech Guardrail Translation Handler import os import sys +from typing import List, Optional, Tuple import pytest @@ -20,8 +21,10 @@ from litellm.types.utils import CallTypes class MockGuardrail(CustomGuardrail): """Mock guardrail for testing""" - async def apply_guardrail(self, text: str, language=None, entities=None) -> str: - return f"{text} [GUARDRAILED]" + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: + return ([f"{text} [GUARDRAILED]" for text in texts], None) class MockBinaryResponse: @@ -169,20 +172,23 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: replace email-like patterns import re - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - # Replace names (simple mock) - masked = masked.replace("John Doe", "[NAME_REDACTED]") - masked = masked.replace("555-1234", "[PHONE_REDACTED]") - return masked + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + # Replace names (simple mock) + masked = masked.replace("John Doe", "[NAME_REDACTED]") + masked = masked.replace("555-1234", "[PHONE_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAITextToSpeechHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -211,17 +217,24 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: import re - # Mask account numbers - masked = re.sub(r"account number \d{8,12}", "account number [REDACTED]", text) - # Mask SSNs - masked = re.sub(r"\d{3}-\d{2}-\d{4}", "[SSN_REDACTED]", masked) - # Mask credit cards - masked = re.sub(r"\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}", "[CC_REDACTED]", masked) - return masked + masked_texts = [] + for text in texts: + # Mask account numbers + masked = re.sub( + r"account number \d{8,12}", "account number [REDACTED]", text + ) + # Mask SSNs + masked = re.sub(r"\d{3}-\d{2}-\d{4}", "[SSN_REDACTED]", masked) + # Mask credit cards + masked = re.sub( + r"\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}", "[CC_REDACTED]", masked + ) + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAITextToSpeechHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -256,14 +269,17 @@ class TestContentModerationScenario: """Mock content filter guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: filter inappropriate words bad_words = ["badword", "inappropriate", "offensive"] - filtered = text - for word in bad_words: - filtered = filtered.replace(word, "[FILTERED]") - return filtered + filtered_texts = [] + for text in texts: + filtered = text + for word in bad_words: + filtered = filtered.replace(word, "[FILTERED]") + filtered_texts.append(filtered) + return (filtered_texts, None) handler = OpenAITextToSpeechHandler() guardrail = ContentFilterGuardrail(guardrail_name="content_filter") @@ -322,4 +338,3 @@ class TestMultilingualTTS: assert f"Testing with {voice} voice [GUARDRAILED]" == result["input"] assert result["voice"] == voice - diff --git a/tests/test_litellm/llms/openai/transcriptions/test_audio_transcription_guardrail_handler.py b/tests/test_litellm/llms/openai/transcriptions/test_audio_transcription_guardrail_handler.py index faa425eb714..4d2cb142b35 100644 --- a/tests/test_litellm/llms/openai/transcriptions/test_audio_transcription_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/transcriptions/test_audio_transcription_guardrail_handler.py @@ -4,6 +4,7 @@ Unit tests for OpenAI Audio Transcription Guardrail Translation Handler import os import sys +from typing import List, Optional, Tuple import pytest @@ -21,8 +22,10 @@ from litellm.utils import TranscriptionResponse class MockGuardrail(CustomGuardrail): """Mock guardrail for testing""" - async def apply_guardrail(self, text: str, language=None, entities=None) -> str: - return f"{text} [GUARDRAILED]" + async def apply_guardrail( + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: + return ([f"{text} [GUARDRAILED]" for text in texts], None) class TestHandlerDiscovery: @@ -140,20 +143,23 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: replace email-like patterns import re - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - text, - ) - # Replace names (simple mock) - masked = masked.replace("John Doe", "[NAME_REDACTED]") - masked = masked.replace("555-1234", "[PHONE_REDACTED]") - return masked + masked_texts = [] + for text in texts: + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + text, + ) + # Replace names (simple mock) + masked = masked.replace("John Doe", "[NAME_REDACTED]") + masked = masked.replace("555-1234", "[PHONE_REDACTED]") + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAIAudioTranscriptionHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -181,23 +187,26 @@ class TestPIIMaskingScenario: """Mock PII masking guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: import re - # Mask credit card numbers - masked = re.sub( - r"\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}", "[CC_REDACTED]", text - ) - # Mask SSNs - masked = re.sub(r"\d{3}-\d{2}-\d{4}", "[SSN_REDACTED]", masked) - # Mask emails - masked = re.sub( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - masked, - ) - return masked + masked_texts = [] + for text in texts: + # Mask credit card numbers + masked = re.sub( + r"\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}", "[CC_REDACTED]", text + ) + # Mask SSNs + masked = re.sub(r"\d{3}-\d{2}-\d{4}", "[SSN_REDACTED]", masked) + # Mask emails + masked = re.sub( + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "[EMAIL_REDACTED]", + masked, + ) + masked_texts.append(masked) + return (masked_texts, None) handler = OpenAIAudioTranscriptionHandler() guardrail = PIIMaskingGuardrail(guardrail_name="mask_pii") @@ -231,14 +240,17 @@ class TestContentModerationScenario: """Mock profanity filter guardrail""" async def apply_guardrail( - self, text: str, language=None, entities=None - ) -> str: + self, texts: List[str], request_data: dict, input_type: str, **kwargs + ) -> Tuple[List[str], Optional[List[str]]]: # Simple mock: replace common profanity bad_words = ["badword1", "badword2", "inappropriate"] - filtered = text - for word in bad_words: - filtered = filtered.replace(word, "[FILTERED]") - return filtered + filtered_texts = [] + for text in texts: + filtered = text + for word in bad_words: + filtered = filtered.replace(word, "[FILTERED]") + filtered_texts.append(filtered) + return (filtered_texts, None) handler = OpenAIAudioTranscriptionHandler() guardrail = ProfanityFilterGuardrail(guardrail_name="content_filter") diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py index e756dd6bd5d..265605c1637 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py @@ -40,12 +40,12 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-content-filter", patterns=patterns, ) - + assert guardrail.guardrail_name == "test-content-filter" assert len(guardrail.compiled_patterns) == 1 @@ -57,19 +57,19 @@ class TestContentFilterGuardrail: BlockedWord( keyword="secret_project", action=ContentFilterAction.BLOCK, - description="Top secret project" + description="Top secret project", ), BlockedWord( keyword="internal_api", action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-content-filter", blocked_words=blocked_words, ) - + assert len(guardrail.blocked_words) == 2 assert "secret_project" in guardrail.blocked_words assert guardrail.blocked_words["secret_project"][0] == ContentFilterAction.BLOCK @@ -85,18 +85,18 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-ssn", patterns=patterns, ) - + # Test with SSN result = guardrail._check_patterns("My SSN is 123-45-6789") assert result is not None assert result[1] == "us_ssn" assert result[2] == ContentFilterAction.BLOCK - + # Test without SSN result = guardrail._check_patterns("This is a normal message") assert result is None @@ -112,12 +112,12 @@ class TestContentFilterGuardrail: action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-email", patterns=patterns, ) - + result = guardrail._check_patterns("Contact me at test@example.com") assert result is not None assert result[1] == "email" @@ -135,12 +135,12 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-custom", patterns=patterns, ) - + result = guardrail._check_patterns("My ID is ABC-1234") assert result is not None assert result[1] == "custom_id" @@ -155,18 +155,18 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-words", blocked_words=blocked_words, ) - + # Test with blocked word result = guardrail._check_blocked_words("This is CONFIDENTIAL information") assert result is not None assert result[0] == "confidential" assert result[1] == ContentFilterAction.BLOCK - + # Test without blocked word result = guardrail._check_blocked_words("This is normal information") assert result is None @@ -183,15 +183,17 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-block", patterns=patterns, ) - + with pytest.raises(HTTPException) as exc_info: - await guardrail.apply_guardrail(text="My SSN is 123-45-6789") - + await guardrail.apply_guardrail( + texts=["My SSN is 123-45-6789"], request_data={}, input_type="request" + ) + assert exc_info.value.status_code == 400 assert "us_ssn" in str(exc_info.value.detail) @@ -207,17 +209,22 @@ class TestContentFilterGuardrail: action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-mask", patterns=patterns, ) - - result = await guardrail.apply_guardrail(text="Contact me at test@example.com") - + + result, _ = await guardrail.apply_guardrail( + texts=["Contact me at test@example.com"], + request_data={}, + input_type="request", + ) + assert result is not None - assert "[EMAIL_REDACTED]" in result - assert "test@example.com" not in result + assert len(result) == 1 + assert "[EMAIL_REDACTED]" in result[0] + assert "test@example.com" not in result[0] @pytest.mark.asyncio async def test_apply_guardrail_blocked_word_mask(self): @@ -230,17 +237,22 @@ class TestContentFilterGuardrail: action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-word-mask", blocked_words=blocked_words, ) - - result = await guardrail.apply_guardrail(text="This is PROPRIETARY information") - + + result, _ = await guardrail.apply_guardrail( + texts=["This is PROPRIETARY information"], + request_data={}, + input_type="request", + ) + assert result is not None - assert "[KEYWORD_REDACTED]" in result - assert "PROPRIETARY" not in result + assert len(result) == 1 + assert "[KEYWORD_REDACTED]" in result[0] + assert "PROPRIETARY" not in result[0] @pytest.mark.asyncio async def test_apply_guardrail_multiple_patterns(self): @@ -259,19 +271,22 @@ class TestContentFilterGuardrail: action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-multiple", patterns=patterns, ) - - result = await guardrail.apply_guardrail( - text="Contact user@test.com or SSN: 123-45-6789" + + result, _ = await guardrail.apply_guardrail( + texts=["Contact user@test.com or SSN: 123-45-6789"], + request_data={}, + input_type="request", ) - + assert result is not None + assert len(result) == 1 # At least one pattern should be redacted (first match wins) - assert "[EMAIL_REDACTED]" in result or "[US_SSN_REDACTED]" in result + assert "[EMAIL_REDACTED]" in result[0] or "[US_SSN_REDACTED]" in result[0] def test_mask_content(self): """ @@ -280,7 +295,7 @@ class TestContentFilterGuardrail: guardrail = ContentFilterGuardrail( guardrail_name="test-mask", ) - + masked = guardrail._mask_content("sensitive text", "us_ssn") assert masked == "[US_SSN_REDACTED]" @@ -291,28 +306,34 @@ class TestContentFilterGuardrail: import tempfile # Create a temporary blocked words file - with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: - f.write("""blocked_words: + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + f.write( + """blocked_words: - keyword: "test_keyword" action: "BLOCK" description: "Test keyword" - keyword: "another_word" action: "MASK" -""") +""" + ) temp_file = f.name - + try: guardrail = ContentFilterGuardrail( guardrail_name="test-file-load", blocked_words_file=temp_file, ) - + assert len(guardrail.blocked_words) == 2 assert "test_keyword" in guardrail.blocked_words - assert guardrail.blocked_words["test_keyword"][0] == ContentFilterAction.BLOCK + assert ( + guardrail.blocked_words["test_keyword"][0] == ContentFilterAction.BLOCK + ) assert guardrail.blocked_words["test_keyword"][1] == "Test keyword" assert "another_word" in guardrail.blocked_words - assert guardrail.blocked_words["another_word"][0] == ContentFilterAction.MASK + assert ( + guardrail.blocked_words["another_word"][0] == ContentFilterAction.MASK + ) finally: os.unlink(temp_file) @@ -327,17 +348,17 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-cc", patterns=patterns, ) - + # Test Visa card result = guardrail._check_patterns("My card is 4532-1234-5678-9010") assert result is not None assert result[1] == "visa" - + def test_api_key_patterns(self): """ Test API key pattern detection @@ -349,12 +370,12 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-api-key", patterns=patterns, ) - + # Test AWS Access Key result = guardrail._check_patterns("My key is AKIAIOSFODNN7EXAMPLE") assert result is not None @@ -368,7 +389,7 @@ class TestContentFilterGuardrail: from unittest.mock import AsyncMock from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices - + patterns = [ ContentFilterPattern( pattern_type="prebuilt", @@ -376,34 +397,40 @@ class TestContentFilterGuardrail: action=ContentFilterAction.MASK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-streaming-mask", patterns=patterns, event_hook=GuardrailEventHooks.during_call, ) - + # Create mock streaming chunks async def mock_stream(): # Chunk 1: contains email chunk1 = ModelResponseStream( id="chunk1", - choices=[StreamingChoices(delta=Delta(content="Contact me at test@example.com"), index=0)], + choices=[ + StreamingChoices( + delta=Delta(content="Contact me at test@example.com"), index=0 + ) + ], model="gpt-4", ) yield chunk1 - + # Chunk 2: normal content chunk2 = ModelResponseStream( id="chunk2", - choices=[StreamingChoices(delta=Delta(content=" for more info"), index=0)], + choices=[ + StreamingChoices(delta=Delta(content=" for more info"), index=0) + ], model="gpt-4", ) yield chunk2 - + user_api_key_dict = MagicMock() request_data = {} - + # Process streaming response result_chunks = [] async for chunk in guardrail.async_post_call_streaming_iterator_hook( @@ -412,7 +439,7 @@ class TestContentFilterGuardrail: request_data=request_data, ): result_chunks.append(chunk) - + assert len(result_chunks) == 2 # First chunk should have email masked assert "[EMAIL_REDACTED]" in result_chunks[0].choices[0].delta.content @@ -428,7 +455,7 @@ class TestContentFilterGuardrail: from unittest.mock import AsyncMock from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices - + patterns = [ ContentFilterPattern( pattern_type="prebuilt", @@ -436,25 +463,27 @@ class TestContentFilterGuardrail: action=ContentFilterAction.BLOCK, ), ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-streaming-block", patterns=patterns, event_hook=GuardrailEventHooks.during_call, ) - + # Create mock streaming chunks with SSN async def mock_stream(): chunk = ModelResponseStream( id="chunk1", - choices=[StreamingChoices(delta=Delta(content="SSN: 123-45-6789"), index=0)], + choices=[ + StreamingChoices(delta=Delta(content="SSN: 123-45-6789"), index=0) + ], model="gpt-4", ) yield chunk - + user_api_key_dict = MagicMock() request_data = {} - + # Should raise HTTPException when SSN is detected with pytest.raises(HTTPException) as exc_info: async for chunk in guardrail.async_post_call_streaming_iterator_hook( @@ -463,7 +492,7 @@ class TestContentFilterGuardrail: request_data=request_data, ): pass - + assert exc_info.value.status_code == 400 assert "us_ssn" in str(exc_info.value.detail) @@ -487,9 +516,9 @@ class TestContentFilterGuardrail: "action": "MASK", "name": "email", "pattern": None, - } + }, ] - + blocked_words = [ { "keyword": "langchain", @@ -500,19 +529,19 @@ class TestContentFilterGuardrail: "keyword": "openai", "action": "MASK", "description": "Competitor name", - } + }, ] - + guardrail = ContentFilterGuardrail( guardrail_name="test-db-format", patterns=patterns, blocked_words=blocked_words, ) - + assert guardrail.guardrail_name == "test-db-format" assert len(guardrail.compiled_patterns) == 2 assert len(guardrail.blocked_words) == 2 - + # Verify blocked_words are stored as dict assert "langchain" in guardrail.blocked_words assert guardrail.blocked_words["langchain"] == ("BLOCK", None) diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py index 9543b61ef69..3e19437fe83 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py @@ -537,24 +537,25 @@ async def test_logging_hook_multiple_content_items(presidio_guardrail): async def test_presidio_sets_guardrail_information_in_request_data(): """ Test that Presidio populates guardrail information into request_data metadata. - + This validates that add_standard_logging_guardrail_information_to_request_data correctly sets the guardrail information that will be used for logging. """ presidio = _OPTIONAL_PresidioPIIMasking( guardrail_name="test_presidio", output_parse_pii=True, + mock_testing=True, ) - + request_data = { "messages": [{"role": "user", "content": "Test"}], "model": "gpt-4o", "metadata": {}, } - + async def mock_check_pii(text, output_parse_pii, presidio_config, request_data): assert request_data is not None - + presidio.add_standard_logging_guardrail_information_to_request_data( guardrail_provider="presidio", guardrail_json_response=[], @@ -565,27 +566,30 @@ async def test_presidio_sets_guardrail_information_in_request_data(): duration=1.0, masked_entity_count={"EMAIL_ADDRESS": 1, "PERSON": 1}, ) - + return text - - with patch.object(presidio, 'check_pii', mock_check_pii): + + with patch.object(presidio, "check_pii", mock_check_pii): await presidio.apply_guardrail( - text="Test message", + texts=["Test message"], request_data=request_data, + input_type="request", ) - + assert "metadata" in request_data assert "standard_logging_guardrail_information" in request_data["metadata"] - - guardrail_info_list = request_data["metadata"]["standard_logging_guardrail_information"] + + guardrail_info_list = request_data["metadata"][ + "standard_logging_guardrail_information" + ] assert isinstance(guardrail_info_list, list) assert len(guardrail_info_list) > 0 - + guardrail_info = guardrail_info_list[0] assert "masked_entity_count" in guardrail_info assert guardrail_info["masked_entity_count"]["EMAIL_ADDRESS"] == 1 assert guardrail_info["masked_entity_count"]["PERSON"] == 1 - + print("✓ Presidio sets guardrail_information in request_data") @@ -593,7 +597,7 @@ async def test_presidio_sets_guardrail_information_in_request_data(): async def test_request_data_flows_to_apply_guardrail(): """ Test that request_data is correctly passed to apply_guardrail method. - + This validates the fix where guardrail translation handler passes data as request_data to apply_guardrail so guardrails can store metadata for logging. """ @@ -601,31 +605,32 @@ async def test_request_data_flows_to_apply_guardrail(): guardrail_name="test_presidio", output_parse_pii=True, ) - + request_data = { "messages": [{"role": "user", "content": "Test message"}], "model": "gpt-4o", "metadata": {}, } - + async def mock_check_pii(text, output_parse_pii, presidio_config, request_data): assert request_data is not None, "request_data should be passed to check_pii" assert "metadata" in request_data, "request_data should have metadata" - + request_data.setdefault("metadata", {}) request_data["metadata"]["test_flag"] = "passed_correctly" - + return text - - with patch.object(presidio, 'check_pii', mock_check_pii): + + with patch.object(presidio, "check_pii", mock_check_pii): result = await presidio.apply_guardrail( - text="Test message", + texts=["Test message"], request_data=request_data, + input_type="request", ) - + assert "metadata" in request_data assert request_data["metadata"].get("test_flag") == "passed_correctly" - + print("✓ request_data correctly passed to apply_guardrail") From 8eaabb4ad765cb1c8c7c8d5df6e2b0dcaf4cd3c5 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 15:29:47 +0530 Subject: [PATCH 292/349] Add vector store support for ragflow --- .../docs/completion/knowledgebase.md | 1 + .../docs/providers/ragflow_vector_store.md | 349 +++++++++++++++++ docs/my-website/docs/vector_stores/create.md | 1 + .../llms/ragflow/vector_stores/__init__.py | 2 + .../ragflow/vector_stores/transformation.py | 249 ++++++++++++ litellm/types/vector_stores.py | 4 +- litellm/utils.py | 6 + ...test_vector_store_create_provider_logic.py | 44 ++- .../test_ragflow_vector_store.py | 359 ++++++++++++++++++ 9 files changed, 1010 insertions(+), 5 deletions(-) create mode 100644 docs/my-website/docs/providers/ragflow_vector_store.md create mode 100644 litellm/llms/ragflow/vector_stores/__init__.py create mode 100644 litellm/llms/ragflow/vector_stores/transformation.py create mode 100644 tests/vector_store_tests/test_ragflow_vector_store.py diff --git a/docs/my-website/docs/completion/knowledgebase.md b/docs/my-website/docs/completion/knowledgebase.md index 759f7912d87..fd6ef7a9982 100644 --- a/docs/my-website/docs/completion/knowledgebase.md +++ b/docs/my-website/docs/completion/knowledgebase.md @@ -21,6 +21,7 @@ LiteLLM integrates with vector stores, allowing your models to access your organ - [Azure Vector Stores](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/file-search?tabs=python#vector-stores) (Cannot be directly queried. Only available for calling in Assistants messages. We will be adding Azure AI Search Vector Store API support soon.) - [Vertex AI RAG API](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-overview) - [Gemini File Search](https://ai.google.dev/gemini-api/docs/file-search) +- [RAGFlow Datasets](/docs/providers/ragflow_vector_store.md) (Dataset management only, search not supported) ## Quick Start diff --git a/docs/my-website/docs/providers/ragflow_vector_store.md b/docs/my-website/docs/providers/ragflow_vector_store.md new file mode 100644 index 00000000000..bc014cacbe6 --- /dev/null +++ b/docs/my-website/docs/providers/ragflow_vector_store.md @@ -0,0 +1,349 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Image from '@theme/IdealImage'; + +# RAGFlow Vector Stores + +Litellm support creation and management of datasets for document processing and knowledge base management in Ragflow. + +| Property | Details | +|----------|---------| +| Description | RAGFlow datasets enable document processing, chunking, and knowledge base management for RAG applications. | +| Provider Route on LiteLLM | `ragflow` in the litellm vector_store_registry | +| Provider Doc | [RAGFlow API Documentation ↗](https://ragflow.io/docs) | +| Supported Operations | Dataset Management (Create, List, Update, Delete) | +| Search/Retrieval | ❌ Not supported (management only) | + +## Quick Start + +### LiteLLM Python SDK + +```python showLineNumbers title="Example using LiteLLM Python SDK" +import os +import litellm + +# Set RAGFlow credentials +os.environ["RAGFLOW_API_KEY"] = "your-ragflow-api-key" +os.environ["RAGFLOW_API_BASE"] = "http://localhost:9380" # Optional, defaults to localhost:9380 + +# Create a RAGFlow dataset +response = litellm.vector_stores.create( + name="my-dataset", + custom_llm_provider="ragflow", + metadata={ + "description": "My knowledge base dataset", + "embedding_model": "BAAI/bge-large-zh-v1.5@BAAI", + "chunk_method": "naive" + } +) + +print(f"Created dataset ID: {response.id}") +print(f"Dataset name: {response.name}") +``` + +### LiteLLM Proxy + +#### 1. Configure your vector_store_registry + + + + +```yaml +model_list: + - model_name: gpt-4o-mini + litellm_params: + model: gpt-4o-mini + api_key: os.environ/OPENAI_API_KEY + +vector_store_registry: + - vector_store_name: "ragflow-knowledge-base" + litellm_params: + vector_store_id: "your-dataset-id" + custom_llm_provider: "ragflow" + api_key: os.environ/RAGFLOW_API_KEY + api_base: os.environ/RAGFLOW_API_BASE # Optional + vector_store_description: "RAGFlow dataset for knowledge base" + vector_store_metadata: + source: "Company documentation" +``` + + + + + +On the LiteLLM UI, Navigate to Experimental > Vector Stores > Create Vector Store. On this page you can create a vector store with a name, vector store id and credentials. + + + + + + +#### 2. Create a dataset via Proxy + + + + +```bash +curl http://localhost:4000/v1/vector_stores \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $LITELLM_API_KEY" \ + -d '{ + "name": "my-ragflow-dataset", + "custom_llm_provider": "ragflow", + "metadata": { + "description": "Test dataset", + "chunk_method": "naive" + } + }' +``` + + + + + +```python +from openai import OpenAI + +# Initialize client with your LiteLLM proxy URL +client = OpenAI( + base_url="http://localhost:4000", + api_key="your-litellm-api-key" +) + +# Create a RAGFlow dataset +response = client.vector_stores.create( + name="my-ragflow-dataset", + custom_llm_provider="ragflow", + metadata={ + "description": "Test dataset", + "chunk_method": "naive" + } +) + +print(f"Created dataset: {response.id}") +``` + + + + +## Configuration + +### Environment Variables + +RAGFlow vector stores support configuration via environment variables: + +- `RAGFLOW_API_KEY` - Your RAGFlow API key (required) +- `RAGFLOW_API_BASE` - RAGFlow API base URL (optional, defaults to `http://localhost:9380`) + +### Parameters + +You can also pass these via `litellm_params`: + +- `api_key` - RAGFlow API key (overrides `RAGFLOW_API_KEY` env var) +- `api_base` - RAGFlow API base URL (overrides `RAGFLOW_API_BASE` env var) + +## Dataset Creation Options + +### Basic Dataset Creation + +```python +response = litellm.vector_stores.create( + name="basic-dataset", + custom_llm_provider="ragflow" +) +``` + +### Dataset with Chunk Method + +RAGFlow supports various chunk methods for different document types: + + + + +```python +response = litellm.vector_stores.create( + name="general-dataset", + custom_llm_provider="ragflow", + metadata={ + "chunk_method": "naive", + "parser_config": { + "chunk_token_num": 512, + "delimiter": "\n", + "html4excel": False, + "layout_recognize": "DeepDOC" + } + } +) +``` + + + + + +```python +response = litellm.vector_stores.create( + name="book-dataset", + custom_llm_provider="ragflow", + metadata={ + "chunk_method": "book", + "parser_config": { + "raptor": { + "use_raptor": False + } + } + } +) +``` + + + + + +```python +response = litellm.vector_stores.create( + name="qa-dataset", + custom_llm_provider="ragflow", + metadata={ + "chunk_method": "qa", + "parser_config": { + "raptor": { + "use_raptor": False + } + } + } +) +``` + + + + + +```python +response = litellm.vector_stores.create( + name="paper-dataset", + custom_llm_provider="ragflow", + metadata={ + "chunk_method": "paper", + "parser_config": { + "raptor": { + "use_raptor": False + } + } + } +) +``` + + + + +### Dataset with Ingestion Pipeline + +Instead of using a chunk method, you can use an ingestion pipeline: + +```python +response = litellm.vector_stores.create( + name="pipeline-dataset", + custom_llm_provider="ragflow", + metadata={ + "parse_type": 2, # Number of parsers in your pipeline + "pipeline_id": "d0bebe30ae2211f0970942010a8e0005" # 32-character hex ID + } +) +``` + +**Note**: `chunk_method` and `pipeline_id` are mutually exclusive. Use one or the other. + +### Advanced Parser Configuration + +```python +response = litellm.vector_stores.create( + name="advanced-dataset", + custom_llm_provider="ragflow", + metadata={ + "chunk_method": "naive", + "description": "Advanced dataset with custom parser config", + "embedding_model": "BAAI/bge-large-zh-v1.5@BAAI", + "permission": "me", # or "team" + "parser_config": { + "chunk_token_num": 1024, + "delimiter": "\n!?;。;!?", + "html4excel": True, + "layout_recognize": "DeepDOC", + "auto_keywords": 5, + "auto_questions": 3, + "task_page_size": 12, + "raptor": { + "use_raptor": True + }, + "graphrag": { + "use_graphrag": False + } + } + } +) +``` + +## Supported Chunk Methods + +RAGFlow supports the following chunk methods: + +- `naive` - General purpose (default) +- `book` - For book documents +- `email` - For email documents +- `laws` - For legal documents +- `manual` - Manual chunking +- `one` - Single chunk +- `paper` - For academic papers +- `picture` - For image documents +- `presentation` - For presentation documents +- `qa` - Q&A format +- `table` - For table documents +- `tag` - Tag-based chunking + +## RAGFlow-Specific Parameters + +All RAGFlow-specific parameters should be passed via the `metadata` field: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `avatar` | string | Base64 encoding of the avatar (max 65535 chars) | +| `description` | string | Brief description of the dataset (max 65535 chars) | +| `embedding_model` | string | Embedding model name (e.g., "BAAI/bge-large-zh-v1.5@BAAI") | +| `permission` | string | Access permission: "me" (default) or "team" | +| `chunk_method` | string | Chunking method (see supported methods above) | +| `parser_config` | object | Parser configuration (varies by chunk_method) | +| `parse_type` | int | Number of parsers in pipeline (required with pipeline_id) | +| `pipeline_id` | string | 32-character hex pipeline ID (required with parse_type) | + +## Error Handling + +RAGFlow returns error responses in the following format: + +```json +{ + "code": 101, + "message": "Dataset name 'my-dataset' already exists" +} +``` + +LiteLLM automatically maps these to appropriate exceptions: + +- `code != 0` → Raises exception with the error message +- Missing required fields → Raises `ValueError` +- Mutually exclusive parameters → Raises `ValueError` + +## Limitations + +- **Search/Retrieval**: RAGFlow vector stores support dataset management only. Search operations are not supported and will raise `NotImplementedError`. +- **List/Update/Delete**: These operations are not yet implemented through the standard vector store API. Use RAGFlow's native API endpoints directly. + +## Further Reading + +Vector Stores: +- [Vector Store Creation](../vector_stores/create.md) +- [Using Vector Stores with Completions](../completion/knowledgebase.md) +- [Vector Store Registry](../completion/knowledgebase.md#vectorstoreregistry) + diff --git a/docs/my-website/docs/vector_stores/create.md b/docs/my-website/docs/vector_stores/create.md index 19b4f39cd9e..7025c490a32 100644 --- a/docs/my-website/docs/vector_stores/create.md +++ b/docs/my-website/docs/vector_stores/create.md @@ -14,6 +14,7 @@ Create a vector store which can be used to store and search document chunks for | End-user Tracking | ✅ | | | Support LLM Providers (OpenAI `/vector_stores` API) | **OpenAI** | Full vector stores API support across providers | | Support LLM Providers (Passthrough API) | [**Azure AI**](/docs/providers/azure_ai/azure_ai_vector_stores_passthrough) | Full vector stores API support across providers | +| Support LLM Providers (Dataset Management) | [**RAGFlow**](/docs/providers/ragflow_vector_store.md) | Dataset creation and management (search not supported) | ## Usage diff --git a/litellm/llms/ragflow/vector_stores/__init__.py b/litellm/llms/ragflow/vector_stores/__init__.py new file mode 100644 index 00000000000..3be29310b39 --- /dev/null +++ b/litellm/llms/ragflow/vector_stores/__init__.py @@ -0,0 +1,2 @@ +# RAGFlow vector stores module + diff --git a/litellm/llms/ragflow/vector_stores/transformation.py b/litellm/llms/ragflow/vector_stores/transformation.py new file mode 100644 index 00000000000..b6401a4b8d7 --- /dev/null +++ b/litellm/llms/ragflow/vector_stores/transformation.py @@ -0,0 +1,249 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import httpx + +from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.router import GenericLiteLLMParams +from litellm.types.vector_stores import ( + BaseVectorStoreAuthCredentials, + VectorStoreCreateOptionalRequestParams, + VectorStoreCreateResponse, + VectorStoreFileCounts, + VectorStoreIndexEndpoints, + VectorStoreSearchOptionalRequestParams, + VectorStoreSearchResponse, +) + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class RAGFlowVectorStoreConfig(BaseVectorStoreConfig): + """Vector store configuration for RAGFlow datasets.""" + + def get_auth_credentials( + self, litellm_params: dict + ) -> BaseVectorStoreAuthCredentials: + api_key = litellm_params.get("api_key") + if api_key is None: + # Try to get from environment variable + api_key = get_secret_str("RAGFLOW_API_KEY") + if api_key is None: + raise ValueError("api_key is required (set RAGFLOW_API_KEY env var or pass in litellm_params)") + return { + "headers": { + "Authorization": f"Bearer {api_key}", + }, + } + + def get_vector_store_endpoints_by_type(self) -> VectorStoreIndexEndpoints: + """RAGFlow vector stores are management-only, no search support.""" + return { + "read": [], + "write": [], + } + + def validate_environment( + self, headers: dict, litellm_params: Optional[GenericLiteLLMParams] + ) -> dict: + """Validate environment and set headers for RAGFlow API.""" + litellm_params = litellm_params or GenericLiteLLMParams() + api_key = ( + litellm_params.api_key + or get_secret_str("RAGFLOW_API_KEY") + ) + + if api_key is None: + raise ValueError("RAGFLOW_API_KEY is required (set env var or pass in litellm_params)") + + headers.update( + { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + } + ) + return headers + + def get_complete_url( + self, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + """ + Get the complete URL for RAGFlow datasets API. + + Supports: + - RAGFLOW_API_BASE env var + - api_base in litellm_params + - Default: http://localhost:9380 + """ + api_base = ( + api_base + or litellm_params.get("api_base") + or get_secret_str("RAGFLOW_API_BASE") + or "http://localhost:9380" + ) + + # Remove trailing slashes + api_base = api_base.rstrip("/") + + # RAGFlow datasets API endpoint + return f"{api_base}/api/v1/datasets" + + def transform_search_vector_store_request( + self, + vector_store_id: str, + query: Union[str, List[str]], + vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams, + api_base: str, + litellm_logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> Tuple[str, Dict]: + """RAGFlow vector stores are management-only, search is not supported.""" + raise NotImplementedError( + "RAGFlow vector stores support dataset management only, not search/retrieval" + ) + + def transform_search_vector_store_response( + self, response: httpx.Response, litellm_logging_obj: LiteLLMLoggingObj + ) -> VectorStoreSearchResponse: + """RAGFlow vector stores are management-only, search is not supported.""" + raise NotImplementedError( + "RAGFlow vector stores support dataset management only, not search/retrieval" + ) + + def transform_create_vector_store_request( + self, + vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams, + api_base: str, + ) -> Tuple[str, Dict]: + """ + Transform create request to RAGFlow POST /api/v1/datasets format. + + Maps LiteLLM params to RAGFlow dataset creation parameters. + RAGFlow-specific fields can be passed via metadata. + """ + url = api_base # Already includes /api/v1/datasets from get_complete_url + + # Extract name (required by RAGFlow) + name = vector_store_create_optional_params.get("name") + if not name: + raise ValueError("name is required for RAGFlow dataset creation") + + # Build request body + request_body: Dict[str, Any] = { + "name": name, + } + + # Extract RAGFlow-specific fields from metadata + metadata = vector_store_create_optional_params.get("metadata") + if metadata: + # RAGFlow-specific fields that can be in metadata + ragflow_fields = [ + "avatar", + "description", + "embedding_model", + "permission", + "chunk_method", + "parser_config", + "parse_type", + "pipeline_id", + ] + + for field in ragflow_fields: + if field in metadata: + request_body[field] = metadata[field] + + # Validate: chunk_method and pipeline_id are mutually exclusive + if "chunk_method" in request_body and "pipeline_id" in request_body: + raise ValueError( + "chunk_method and pipeline_id are mutually exclusive. " + "Specify either chunk_method or pipeline_id, not both." + ) + + # If neither chunk_method nor pipeline_id is specified, default to naive + if "chunk_method" not in request_body and "pipeline_id" not in request_body: + request_body["chunk_method"] = "naive" + + return url, request_body + + def transform_create_vector_store_response( + self, response: httpx.Response + ) -> VectorStoreCreateResponse: + """ + Transform RAGFlow response to VectorStoreCreateResponse format. + + RAGFlow response format: + { + "code": 0, + "data": { + "id": "...", + "name": "...", + "create_time": 1745836841611, # milliseconds + ... + } + } + """ + try: + response_json = response.json() + + # Check for RAGFlow error response + if response_json.get("code") != 0: + error_message = response_json.get("message", "Unknown error") + raise self.get_error_class( + error_message=error_message, + status_code=response.status_code, + headers=response.headers, + ) + + data = response_json.get("data", {}) + + # Extract dataset ID + dataset_id = data.get("id") + if not dataset_id: + raise ValueError("RAGFlow response missing dataset id") + + # Extract name + name = data.get("name") + + # Convert create_time from milliseconds to seconds (Unix timestamp) + create_time_ms = data.get("create_time", 0) + created_at = int(create_time_ms / 1000) if create_time_ms else None + + # Build VectorStoreCreateResponse + return VectorStoreCreateResponse( + id=dataset_id, + object="vector_store", + created_at=created_at or 0, + name=name, + bytes=0, # RAGFlow doesn't provide bytes in response + file_counts=VectorStoreFileCounts( + in_progress=0, + completed=0, + failed=0, + cancelled=0, + total=0, + ), + status="completed", + expires_after=None, + expires_at=None, + last_active_at=None, + metadata=None, + ) + except Exception as e: + # If it's already a ValueError we raised, re-raise it + if isinstance(e, ValueError) and "RAGFlow response" in str(e): + raise + # If it's already our error class (has status_code), re-raise + if hasattr(e, "status_code"): + raise + # Otherwise, wrap in our error class + raise self.get_error_class( + error_message=str(e), + status_code=response.status_code, + headers=response.headers, + ) + diff --git a/litellm/types/vector_stores.py b/litellm/types/vector_stores.py index 6ae0b4bd2fd..a4ceb2c9ac7 100644 --- a/litellm/types/vector_stores.py +++ b/litellm/types/vector_stores.py @@ -3,17 +3,15 @@ from datetime import datetime from enum import Enum from typing import Any, Dict, List, Literal, Optional, Tuple, Union -from annotated_types import Ge from pydantic import BaseModel from typing_extensions import TypedDict -from litellm.types.router import CredentialLiteLLMParams, GenericLiteLLMParams - class SupportedVectorStoreIntegrations(str, Enum): """Supported vector store integrations.""" BEDROCK = "bedrock" + RAGFLOW = "ragflow" class LiteLLM_VectorStoreConfig(TypedDict, total=False): diff --git a/litellm/utils.py b/litellm/utils.py index 44d4b5c25fd..b77c0e62e7d 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7633,6 +7633,12 @@ class ProviderConfigManager: ) return GeminiVectorStoreConfig() + elif litellm.LlmProviders.RAGFLOW == provider: + from litellm.llms.ragflow.vector_stores.transformation import ( + RAGFlowVectorStoreConfig, + ) + + return RAGFlowVectorStoreConfig() return None @staticmethod diff --git a/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py b/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py index 501a5ff0389..cca20847f12 100644 --- a/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py +++ b/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py @@ -9,9 +9,12 @@ sys.path.insert( ) # Adds the parent directory to the system path import litellm -from litellm.utils import ProviderConfigManager from litellm.llms.openai.vector_stores.transformation import OpenAIVectorStoreConfig -from litellm.llms.vertex_ai.vector_stores.rag_api.transformation import VertexVectorStoreConfig +from litellm.llms.ragflow.vector_stores.transformation import RAGFlowVectorStoreConfig +from litellm.llms.vertex_ai.vector_stores.rag_api.transformation import ( + VertexVectorStoreConfig, +) +from litellm.utils import ProviderConfigManager def test_vector_store_create_with_simple_provider_name(): @@ -100,3 +103,40 @@ def test_vector_store_create_with_provider_api_type(): print("✅ Test passed: Provider with api_type 'vertex_ai/rag_api' handled correctly") + +def test_vector_store_create_with_ragflow_provider(): + """ + Test that vector store create correctly handles RAGFlow provider. + + This should: + - Return correct RAGFlowVectorStoreConfig + - Support dataset management operations + """ + custom_llm_provider = "ragflow" + + # Simulate the logic from vector_stores/main.py create function + if "/" in custom_llm_provider: + pytest.fail("Should not enter this branch for RAGFlow provider") + else: + api_type = None + custom_llm_provider = custom_llm_provider # Keep as-is + + # Verify api_type is None + assert api_type is None, "api_type should be None for RAGFlow provider" + + # Verify custom_llm_provider is unchanged + assert custom_llm_provider == "ragflow", "custom_llm_provider should remain 'ragflow'" + + # Verify ProviderConfigManager returns correct config + vector_store_provider_config = ProviderConfigManager.get_provider_vector_stores_config( + provider=litellm.LlmProviders(custom_llm_provider), + api_type=api_type, + ) + + assert vector_store_provider_config is not None, "Should return a config for RAGFlow" + assert isinstance( + vector_store_provider_config, RAGFlowVectorStoreConfig + ), "Should return RAGFlowVectorStoreConfig for RAGFlow provider" + + print("✅ Test passed: RAGFlow provider handled correctly") + diff --git a/tests/vector_store_tests/test_ragflow_vector_store.py b/tests/vector_store_tests/test_ragflow_vector_store.py new file mode 100644 index 00000000000..0839bd7153b --- /dev/null +++ b/tests/vector_store_tests/test_ragflow_vector_store.py @@ -0,0 +1,359 @@ +""" +Test RAGFlow Vector Store helper functions and transformation. +""" +import os +import sys +import json +import pytest +from unittest.mock import Mock, patch, MagicMock +import httpx + +sys.path.insert(0, os.path.abspath("../..")) +import litellm + +from tests.vector_store_tests.base_vector_store_test import BaseVectorStoreTest +from litellm.llms.ragflow.vector_stores.transformation import RAGFlowVectorStoreConfig +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.types.vector_stores import VectorStoreCreateOptionalRequestParams + + +class TestRAGFlowVectorStore(BaseVectorStoreTest): + """ + Test the RAGFlow vector store transformation functionality. + """ + + def get_base_create_vector_store_args(self) -> dict: + """Must return the base create vector store args""" + return { + "custom_llm_provider": "ragflow", + "api_key": os.getenv("RAGFLOW_API_KEY", "test-api-key"), + "api_base": os.getenv("RAGFLOW_API_BASE", "http://localhost:9380") + } + + def get_base_request_args(self): + # RAGFlow doesn't support search, so we'll skip search tests + return { + "vector_store_id": "test-dataset-id", + "custom_llm_provider": "ragflow", + "query": "test query" + } + + def test_get_auth_credentials(self): + """Test that auth credentials are correctly extracted.""" + config = RAGFlowVectorStoreConfig() + + # Test with api_key in params + litellm_params = {"api_key": "test-api-key-123"} + credentials = config.get_auth_credentials(litellm_params) + assert "headers" in credentials + assert credentials["headers"]["Authorization"] == "Bearer test-api-key-123" + + # Test with missing api_key (should raise ValueError) + with pytest.raises(ValueError, match="api_key is required"): + config.get_auth_credentials({}) + + def test_get_complete_url(self): + """Test that complete URL is correctly constructed.""" + config = RAGFlowVectorStoreConfig() + + # Test with api_base in params + litellm_params = {"api_base": "http://custom-host:9999"} + url = config.get_complete_url(api_base=None, litellm_params=litellm_params) + assert url == "http://custom-host:9999/api/v1/datasets" + + # Test with api_base parameter + url = config.get_complete_url(api_base="http://test-host:8888", litellm_params={}) + assert url == "http://test-host:8888/api/v1/datasets" + + # Test with default (no api_base provided) + with patch.dict(os.environ, {}, clear=True): + url = config.get_complete_url(api_base=None, litellm_params={}) + assert url == "http://localhost:9380/api/v1/datasets" + + # Test with trailing slash removal + url = config.get_complete_url(api_base="http://test-host:8888/", litellm_params={}) + assert url == "http://test-host:8888/api/v1/datasets" + + def test_validate_environment(self): + """Test environment validation and header setting.""" + config = RAGFlowVectorStoreConfig() + from litellm.types.router import GenericLiteLLMParams + + # Test with api_key in litellm_params + litellm_params = GenericLiteLLMParams(api_key="test-key") + headers = config.validate_environment({}, litellm_params) + assert headers["Authorization"] == "Bearer test-key" + assert headers["Content-Type"] == "application/json" + + # Test with missing api_key + with pytest.raises(ValueError, match="RAGFLOW_API_KEY"): + config.validate_environment({}, GenericLiteLLMParams()) + + def test_get_vector_store_endpoints_by_type(self): + """Test that endpoints are correctly configured (empty for management only).""" + config = RAGFlowVectorStoreConfig() + endpoints = config.get_vector_store_endpoints_by_type() + assert endpoints["read"] == [] + assert endpoints["write"] == [] + + def test_transform_create_vector_store_request_basic(self): + """Test basic dataset creation request transformation.""" + config = RAGFlowVectorStoreConfig() + + params: VectorStoreCreateOptionalRequestParams = { + "name": "test-dataset" + } + + url, body = config.transform_create_vector_store_request( + params, "http://localhost:9380/api/v1/datasets" + ) + + assert url == "http://localhost:9380/api/v1/datasets" + assert body["name"] == "test-dataset" + assert body["chunk_method"] == "naive" # Default chunk method + + def test_transform_create_vector_store_request_with_metadata(self): + """Test dataset creation with RAGFlow-specific metadata.""" + config = RAGFlowVectorStoreConfig() + + params: VectorStoreCreateOptionalRequestParams = { + "name": "test-dataset-advanced", + "metadata": { + "description": "Test dataset", + "embedding_model": "BAAI/bge-large-zh-v1.5@BAAI", + "permission": "me", + "chunk_method": "naive", + "parser_config": { + "chunk_token_num": 512, + "delimiter": "\n" + } + } + } + + url, body = config.transform_create_vector_store_request( + params, "http://localhost:9380/api/v1/datasets" + ) + + assert body["name"] == "test-dataset-advanced" + assert body["description"] == "Test dataset" + assert body["embedding_model"] == "BAAI/bge-large-zh-v1.5@BAAI" + assert body["permission"] == "me" + assert body["chunk_method"] == "naive" + assert "parser_config" in body + assert body["parser_config"]["chunk_token_num"] == 512 + + def test_transform_create_vector_store_request_missing_name(self): + """Test that missing name raises ValueError.""" + config = RAGFlowVectorStoreConfig() + + params: VectorStoreCreateOptionalRequestParams = {} + + with pytest.raises(ValueError, match="name is required"): + config.transform_create_vector_store_request( + params, "http://localhost:9380/api/v1/datasets" + ) + + def test_transform_create_vector_store_request_mutually_exclusive(self): + """Test that chunk_method and pipeline_id are mutually exclusive.""" + config = RAGFlowVectorStoreConfig() + + params: VectorStoreCreateOptionalRequestParams = { + "name": "test-dataset", + "metadata": { + "chunk_method": "naive", + "pipeline_id": "d0bebe30ae2211f0970942010a8e0005" + } + } + + with pytest.raises(ValueError, match="mutually exclusive"): + config.transform_create_vector_store_request( + params, "http://localhost:9380/api/v1/datasets" + ) + + def test_transform_create_vector_store_request_with_pipeline(self): + """Test dataset creation with ingestion pipeline.""" + config = RAGFlowVectorStoreConfig() + + params: VectorStoreCreateOptionalRequestParams = { + "name": "test-pipeline-dataset", + "metadata": { + "parse_type": 2, + "pipeline_id": "d0bebe30ae2211f0970942010a8e0005" + } + } + + url, body = config.transform_create_vector_store_request( + params, "http://localhost:9380/api/v1/datasets" + ) + + assert body["name"] == "test-pipeline-dataset" + assert body["parse_type"] == 2 + assert body["pipeline_id"] == "d0bebe30ae2211f0970942010a8e0005" + assert "chunk_method" not in body + + def test_transform_create_vector_store_response_success(self): + """Test successful response transformation.""" + config = RAGFlowVectorStoreConfig() + + # Mock RAGFlow response + mock_response = Mock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.json.return_value = { + "code": 0, + "data": { + "id": "3b4de7d4241d11f0a6a79f24fc270c7f", + "name": "test-dataset", + "create_time": 1745836841611, + "chunk_method": "naive", + "embedding_model": "BAAI/bge-large-zh-v1.5@BAAI" + } + } + + response = config.transform_create_vector_store_response(mock_response) + + assert response["id"] == "3b4de7d4241d11f0a6a79f24fc270c7f" + assert response["name"] == "test-dataset" + assert response["object"] == "vector_store" + assert response["status"] == "completed" + assert response["created_at"] == 1745836841 # Converted from milliseconds + assert response["bytes"] == 0 + assert "file_counts" in response + + def test_transform_create_vector_store_response_error(self): + """Test error response transformation.""" + config = RAGFlowVectorStoreConfig() + + # Mock RAGFlow error response + mock_response = Mock(spec=httpx.Response) + mock_response.status_code = 400 + mock_response.headers = {} + mock_response.json.return_value = { + "code": 101, + "message": "Dataset name 'test-dataset' already exists" + } + + with pytest.raises(Exception): # Should raise BaseLLMException + config.transform_create_vector_store_response(mock_response) + + def test_transform_create_vector_store_response_missing_id(self): + """Test response with missing dataset ID.""" + config = RAGFlowVectorStoreConfig() + + mock_response = Mock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.json.return_value = { + "code": 0, + "data": { + "name": "test-dataset" + # Missing "id" + } + } + + with pytest.raises(ValueError, match="missing dataset id"): + config.transform_create_vector_store_response(mock_response) + + def test_transform_search_vector_store_request_not_implemented(self): + """Test that search operations raise NotImplementedError.""" + config = RAGFlowVectorStoreConfig() + logging_obj = MagicMock(spec=LiteLLMLoggingObj) + + with pytest.raises(NotImplementedError, match="management only"): + config.transform_search_vector_store_request( + vector_store_id="test-id", + query="test query", + vector_store_search_optional_params={}, + api_base="http://localhost:9380", + litellm_logging_obj=logging_obj, + litellm_params={} + ) + + def test_transform_search_vector_store_response_not_implemented(self): + """Test that search response transformation raises NotImplementedError.""" + config = RAGFlowVectorStoreConfig() + logging_obj = MagicMock(spec=LiteLLMLoggingObj) + mock_response = Mock(spec=httpx.Response) + + with pytest.raises(NotImplementedError, match="management only"): + config.transform_search_vector_store_response(mock_response, logging_obj) + + def _validate_vector_store_create_response(self, response): + """Override to handle RAGFlow-specific response format.""" + # RAGFlow IDs are hex strings (not OpenAI-style vs_* format) + # So we override the base validation to not check for vs_ prefix + assert isinstance(response, dict), f"Response should be a dict, got {type(response)}" + assert "id" in response, "Missing required field 'id' in create response" + assert "object" in response, "Missing required field 'object' in create response" + assert "created_at" in response, "Missing required field 'created_at' in create response" + + assert response["object"] == "vector_store", \ + f"Expected object to be 'vector_store', got '{response['object']}'" + + assert isinstance(response["id"], str), \ + f"id should be a string, got {type(response['id'])}" + assert len(response["id"]) > 0, "id should not be empty" + # RAGFlow IDs are hex strings, not OpenAI-style vs_* format + + assert isinstance(response["created_at"], int), \ + f"created_at should be an integer, got {type(response['created_at'])}" + assert response["created_at"] > 0, "created_at should be a positive timestamp" + + print(f"✅ RAGFlow create response validation passed: Dataset '{response['id']}' created successfully") + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_create_vector_store(self, sync_mode): + """Override to handle RAGFlow-specific connection errors.""" + litellm._turn_on_debug() + litellm.set_verbose = True + base_request_args = self.get_base_create_vector_store_args() + + # Skip if no API key is set + if not os.getenv("RAGFLOW_API_KEY") and not base_request_args.get("api_key"): + pytest.skip("RAGFLOW_API_KEY not set, skipping integration test") + + # Extract custom_llm_provider from base args if present + create_args = base_request_args + try: + if sync_mode: + response = litellm.vector_stores.create( + name=f"test-ragflow-{int(__import__('time').time())}", + **create_args + ) + else: + response = await litellm.vector_stores.acreate( + name=f"test-ragflow-{int(__import__('time').time())}", + **create_args + ) + except litellm.InternalServerError: + pytest.skip("Skipping test due to litellm.InternalServerError") + except Exception as e: + error_str = str(e).lower() + error_type = type(e).__name__ + + # Check if it's a connection error + if (isinstance(e, (ConnectionError, OSError)) or + "connection" in error_str or + "connect" in error_str or + "APIConnectionError" in error_type): + pytest.skip(f"Skipping test due to connection error (RAGFlow instance may not be running): {e}") + + # If this is an authentication or permission error, skip the test + if "authentication" in error_str or "permission" in error_str or "unauthorized" in error_str: + pytest.skip(f"Skipping test due to authentication/permission error: {e}") + + # Re-raise if it's not a handled error + raise + + print("litellm create response=", json.dumps(response, indent=4, default=str)) + + # Validate response structure + self._validate_vector_store_create_response(response) + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_search_vector_store(self, sync_mode): + """Override search test - RAGFlow doesn't support search.""" + pytest.skip("RAGFlow vector stores support dataset management only, not search") + From dad0b2c1117d3c2624d27dd229f7faa8babfbb41 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 15:32:42 +0530 Subject: [PATCH 293/349] Fix unused imports --- litellm/llms/ragflow/chat/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/ragflow/chat/transformation.py b/litellm/llms/ragflow/chat/transformation.py index d33a1593be8..58fbfa83c98 100644 --- a/litellm/llms/ragflow/chat/transformation.py +++ b/litellm/llms/ragflow/chat/transformation.py @@ -10,7 +10,7 @@ Model name format: - Agent: ragflow/agent/{agent_id}/{model_name} """ -from typing import Any, List, Optional, Tuple +from typing import List, Optional, Tuple import litellm from litellm.llms.openai.openai import OpenAIConfig From c9c7823f43b072a62ca2cd80bb9fc33f309dd1b1 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 3 Dec 2025 17:31:20 +0530 Subject: [PATCH 294/349] Fix bedrock models in model map --- ...odel_prices_and_context_window_backup.json | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 0fc97ce7b0a..dbaa60e0f1d 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -9629,6 +9629,21 @@ "supports_prompt_caching": true, "supports_tool_choice": true }, + "deepseek/deepseek-v3.2": { + "input_cost_per_token": 2.8e-07, + "input_cost_per_token_cache_hit": 2.8e-08, + "litellm_provider": "deepseek", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 4e-07, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "deepseek.v3-v1:0": { "input_cost_per_token": 5.8e-07, "litellm_provider": "bedrock_converse", @@ -20637,6 +20652,21 @@ "supports_reasoning": true, "supports_tool_choice": true }, + "openrouter/deepseek/deepseek-v3.2": { + "input_cost_per_token": 2.8e-07, + "input_cost_per_token_cache_hit": 2.8e-08, + "litellm_provider": "openrouter", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 4e-07, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "openrouter/deepseek/deepseek-v3.2-exp": { "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, @@ -23892,6 +23922,32 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "global.anthropic.claude-opus-4-5-20251101-v1:0": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 5e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 2.5e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "us.anthropic.claude-sonnet-4-20250514-v1:0": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, From be5dd234bfe46f28bdfced4333f4df7562236c64 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 3 Dec 2025 08:01:26 -0800 Subject: [PATCH 295/349] docs: fix list --- docs/my-website/docs/providers/bedrock.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index a9ac85a7571..7fc7758b15c 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -43,6 +43,8 @@ export AWS_BEARER_TOKEN_BEDROCK="your-api-key" Option 2: use the api_key parameter to pass in API key for completion, embedding, image_generation API calls. + + ```python response = completion( model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", @@ -50,7 +52,17 @@ response = completion( api_key="your-api-key" ) ``` - + + +```yaml +model_list: + - model_name: bedrock-claude-3-sonnet + litellm_params: + model: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + api_key: os.environ/AWS_BEARER_TOKEN_BEDROCK +``` + + ## Usage From 5e791464afb6eb2b6606d20cf23bdb87dd02e10d Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:07:02 -0300 Subject: [PATCH 296/349] docs: add Microsoft Agent Lightning to projects (#17422) Add Agent Lightning, Microsoft's open-source framework for training AI agents with RL, APO, and SFT. Uses LiteLLM Proxy for LLM routing and trace collection. --- docs/my-website/docs/projects/Agent Lightning.md | 10 ++++++++++ docs/my-website/sidebars.js | 1 + 2 files changed, 11 insertions(+) create mode 100644 docs/my-website/docs/projects/Agent Lightning.md diff --git a/docs/my-website/docs/projects/Agent Lightning.md b/docs/my-website/docs/projects/Agent Lightning.md new file mode 100644 index 00000000000..28e5546e398 --- /dev/null +++ b/docs/my-website/docs/projects/Agent Lightning.md @@ -0,0 +1,10 @@ + +# Agent Lightning + +[Agent Lightning](https://github.com/microsoft/agent-lightning) is Microsoft's open-source framework for training and optimizing AI agents with Reinforcement Learning, Automatic Prompt Optimization, and Supervised Fine-tuning — with almost zero code changes. + +It works with any agent framework including LangChain, OpenAI Agents SDK, AutoGen, and CrewAI. Agent Lightning uses LiteLLM Proxy under the hood to route LLM requests and collect traces that power its training algorithms. + +- [GitHub](https://github.com/microsoft/agent-lightning) +- [Docs](https://microsoft.github.io/agent-lightning/) +- [arXiv Paper](https://arxiv.org/abs/2508.03680) diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index e2f682f0a50..4a0f592c422 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -826,6 +826,7 @@ const sidebars = { "projects/mini-swe-agent", "projects/openai-agents", "projects/Google ADK", + "projects/Agent Lightning", "projects/Harbor", "projects/Docq.AI", "projects/PDL", From 0a7602bb7cc4c21b5cbe2c0283889004e25018ea Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Wed, 3 Dec 2025 10:43:27 -0800 Subject: [PATCH 297/349] fix: prevent memory leak in aiohttp connection pooling (#17388) * fix: prevent memory leak in aiohttp connection pooling Add connection limits to aiohttp TCPConnector to prevent unbounded connection growth that causes memory leaks. Without these limits, aiohttp's _wrap_create_connection can accumulate connections indefinitely in long-running processes. Changes: - Set default limit of 300 total connections and 50 per host - Apply limits to shared proxy session initialization - Apply limits to HTTP handler transport creation - Configurable via AIOHTTP_CONNECTOR_LIMIT and AIOHTTP_CONNECTOR_LIMIT_PER_HOST environment variables - Set to 0 for unlimited (not recommended for production) This fix covers: - All standard LLM provider API calls (OpenAI, Anthropic, etc.) - Proxy server shared session - Most guardrail HTTP calls Impact: Prevents memory exhaustion in high-traffic deployments and long-running proxy servers that make thousands of API calls. Testing: Verified connection limits are applied correctly and existing functionality remains unchanged. --- litellm/constants.py | 6 ++++-- litellm/llms/custom_httpx/http_handler.py | 20 +++++++++++------ litellm/proxy/proxy_server.py | 26 ++++++++++++++--------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 05ccfb5993f..5ab56bcaa2f 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -100,8 +100,10 @@ RUNWAYML_POLLING_TIMEOUT = int( ########## Networking constants ############################################################## _DEFAULT_TTL_FOR_HTTPX_CLIENTS = 3600 # 1 hour, re-use the same httpx client for 1 hour -# Aiohttp connection pooling constants -AIOHTTP_CONNECTOR_LIMIT = int(os.getenv("AIOHTTP_CONNECTOR_LIMIT", 0)) +# Aiohttp connection pooling - prevents memory leaks from unbounded connection growth +# Set to 0 for unlimited (not recommended for production) +AIOHTTP_CONNECTOR_LIMIT = int(os.getenv("AIOHTTP_CONNECTOR_LIMIT", 300)) +AIOHTTP_CONNECTOR_LIMIT_PER_HOST = int(os.getenv("AIOHTTP_CONNECTOR_LIMIT_PER_HOST", 50)) AIOHTTP_KEEPALIVE_TIMEOUT = int(os.getenv("AIOHTTP_KEEPALIVE_TIMEOUT", 120)) AIOHTTP_TTL_DNS_CACHE = int(os.getenv("AIOHTTP_TTL_DNS_CACHE", 300)) # enable_cleanup_closed is only needed for Python versions with the SSL leak bug diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index b06e8463abd..d9a9d4f9dc1 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -16,6 +16,7 @@ from litellm._logging import verbose_logger from litellm.constants import ( _DEFAULT_TTL_FOR_HTTPX_CLIENTS, AIOHTTP_CONNECTOR_LIMIT, + AIOHTTP_CONNECTOR_LIMIT_PER_HOST, AIOHTTP_KEEPALIVE_TIMEOUT, AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_TTL_DNS_CACHE, @@ -793,15 +794,20 @@ class AsyncHTTPHandler: verbose_logger.debug( "NEW SESSION: Creating new ClientSession (no shared session provided)" ) + transport_connector_kwargs = { + "keepalive_timeout": AIOHTTP_KEEPALIVE_TIMEOUT, + "ttl_dns_cache": AIOHTTP_TTL_DNS_CACHE, + "enable_cleanup_closed": True, + **connector_kwargs, + } + if AIOHTTP_CONNECTOR_LIMIT > 0: + transport_connector_kwargs["limit"] = AIOHTTP_CONNECTOR_LIMIT + if AIOHTTP_CONNECTOR_LIMIT_PER_HOST > 0: + transport_connector_kwargs["limit_per_host"] = AIOHTTP_CONNECTOR_LIMIT_PER_HOST + return LiteLLMAiohttpTransport( client=lambda: ClientSession( - connector=TCPConnector( - limit=AIOHTTP_CONNECTOR_LIMIT, - keepalive_timeout=AIOHTTP_KEEPALIVE_TIMEOUT, - ttl_dns_cache=AIOHTTP_TTL_DNS_CACHE, - enable_cleanup_closed=AIOHTTP_NEEDS_CLEANUP_CLOSED, - **connector_kwargs, - ), + connector=TCPConnector(**transport_connector_kwargs), trust_env=trust_env, ), ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a1e01caddc1..f2c7c361c45 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -31,6 +31,7 @@ from typing import ( from litellm._uuid import uuid from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, + AIOHTTP_CONNECTOR_LIMIT_PER_HOST, AIOHTTP_KEEPALIVE_TIMEOUT, AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_TTL_DNS_CACHE, @@ -627,21 +628,26 @@ async def proxy_shutdown_event(): async def _initialize_shared_aiohttp_session(): - """Initialize shared aiohttp session for connection reuse.""" + """Initialize shared aiohttp session for connection reuse with connection limits.""" try: from aiohttp import ClientSession, TCPConnector - # Create connector with connection pooling settings optimized for long-lived connections - connector = TCPConnector( - limit=AIOHTTP_CONNECTOR_LIMIT, - keepalive_timeout=AIOHTTP_KEEPALIVE_TIMEOUT, - ttl_dns_cache=AIOHTTP_TTL_DNS_CACHE, - enable_cleanup_closed=AIOHTTP_NEEDS_CLEANUP_CLOSED, - ) - + connector_kwargs = { + "keepalive_timeout": AIOHTTP_KEEPALIVE_TIMEOUT, + "ttl_dns_cache": AIOHTTP_TTL_DNS_CACHE, + "enable_cleanup_closed": True, + } + if AIOHTTP_CONNECTOR_LIMIT > 0: + connector_kwargs["limit"] = AIOHTTP_CONNECTOR_LIMIT + if AIOHTTP_CONNECTOR_LIMIT_PER_HOST > 0: + connector_kwargs["limit_per_host"] = AIOHTTP_CONNECTOR_LIMIT_PER_HOST + + connector = TCPConnector(**connector_kwargs) session = ClientSession(connector=connector) + verbose_proxy_logger.info( - f"SESSION REUSE: Created shared aiohttp session for connection pooling (ID: {id(session)})" + f"SESSION REUSE: Created shared aiohttp session for connection pooling (ID: {id(session)}, " + f"limit={AIOHTTP_CONNECTOR_LIMIT}, limit_per_host={AIOHTTP_CONNECTOR_LIMIT_PER_HOST})" ) return session except Exception as e: From 5ecebe2a578190fb8ecf09d1f4611749bc76ea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Gar=C3=A9?= <90070734+FelipeRodriguesGare@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:00:18 -0300 Subject: [PATCH 298/349] adding status parameter as optinal for FileObject (#17431) --- litellm/types/llms/openai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 61d58e4c86d..20b0cb4b2c3 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -303,7 +303,7 @@ class OpenAIFileObject(BaseModel): `fine-tune`, `fine-tune-results`, `vision`, and `user_data`. """ - status: Literal["uploaded", "processed", "error"] + status: Optional[Literal["uploaded", "processed", "error"]] """Deprecated. The current status of the file, which can be either `uploaded`, `processed`, or From e6620fcdad88a9076bc49ebd6bb830e756ff49a7 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 11:01:10 -0800 Subject: [PATCH 299/349] Ruff checks --- litellm/proxy/auth/login_utils.py | 43 ++++++++++++------- .../proxy/auth/test_login_utils.py | 38 +++++++++++++++- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/litellm/proxy/auth/login_utils.py b/litellm/proxy/auth/login_utils.py index 94f2f698e04..6ef983b221c 100644 --- a/litellm/proxy/auth/login_utils.py +++ b/litellm/proxy/auth/login_utils.py @@ -34,6 +34,33 @@ from litellm.secret_managers.main import get_secret_bool from litellm.types.proxy.ui_sso import ReturnedUITokenObject +def get_ui_credentials(master_key: Optional[str]) -> tuple[str, str]: + """ + Get UI username and password from environment variables or master key. + + Args: + master_key: Master key for the proxy (used as fallback for password) + + Returns: + tuple[str, str]: A tuple containing (ui_username, ui_password) + + Raises: + ProxyException: If neither UI_PASSWORD nor master_key is available + """ + ui_username = os.getenv("UI_USERNAME", "admin") + ui_password = os.getenv("UI_PASSWORD", None) + if ui_password is None: + ui_password = str(master_key) if master_key is not None else None + if ui_password is None: + raise ProxyException( + message="set Proxy master key to use UI. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", + type=ProxyErrorTypes.auth_error, + param="UI_PASSWORD", + code=500, + ) + return ui_username, ui_password + + class LoginResult: """Result object containing authentication data from login.""" @@ -85,17 +112,7 @@ async def authenticate_user( code=500, ) - ui_username = os.getenv("UI_USERNAME", "admin") - ui_password = os.getenv("UI_PASSWORD", None) - if ui_password is None: - ui_password = str(master_key) if master_key is not None else None - if ui_password is None: - raise ProxyException( - message="set Proxy master key to use UI. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", - type=ProxyErrorTypes.auth_error, - param="UI_PASSWORD", - code=500, - ) + ui_username, ui_password = get_ui_credentials(master_key) # Check if we can find the `username` in the db. On the UI, users can enter username=their email _user_row: Optional[LiteLLM_UserTable] = None @@ -116,10 +133,6 @@ async def authenticate_user( ), ) - disabled_non_admin_personal_key_creation = ( - get_disabled_non_admin_personal_key_creation() - ) - """ To login to Admin UI, we support the following - Login with UI_USERNAME and UI_PASSWORD diff --git a/tests/test_litellm/proxy/auth/test_login_utils.py b/tests/test_litellm/proxy/auth/test_login_utils.py index abb7b58c292..201461dc8b5 100644 --- a/tests/test_litellm/proxy/auth/test_login_utils.py +++ b/tests/test_litellm/proxy/auth/test_login_utils.py @@ -18,7 +18,43 @@ from litellm.proxy._types import ( ProxyException, hash_token, ) -from litellm.proxy.auth.login_utils import LoginResult, authenticate_user +from litellm.proxy.auth.login_utils import ( + LoginResult, + authenticate_user, + get_ui_credentials, +) + + +def test_get_ui_credentials_prefers_explicit_password(): + """The configured UI password should be returned when available.""" + with patch.dict( + os.environ, + {"UI_USERNAME": "test-admin", "UI_PASSWORD": "secure-pass"}, + clear=True, + ): + username, password = get_ui_credentials(master_key="sk-123") + + assert username == "test-admin" + assert password == "secure-pass" + + +def test_get_ui_credentials_can_use_master_key(): + """Master key should be used as password when UI_PASSWORD is missing.""" + with patch.dict(os.environ, {"UI_USERNAME": "fallback-admin"}, clear=True): + username, password = get_ui_credentials(master_key="fallback-key") + + assert username == "fallback-admin" + assert password == "fallback-key" + + +def test_get_ui_credentials_requires_password(): + """Missing UI password and master key results in error.""" + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(ProxyException) as exc_info: + get_ui_credentials(master_key=None) + + assert exc_info.value.type == ProxyErrorTypes.auth_error + assert exc_info.value.code == "500" @pytest.mark.asyncio From 82a8134d7a1a26e74d720a130e7d41709e0b0852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Gar=C3=A9?= <90070734+FelipeRodriguesGare@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:24:32 -0300 Subject: [PATCH 300/349] fixing optional parameter default value (#17434) --- litellm/types/llms/openai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 20b0cb4b2c3..9fb97d47d17 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -303,7 +303,7 @@ class OpenAIFileObject(BaseModel): `fine-tune`, `fine-tune-results`, `vision`, and `user_data`. """ - status: Optional[Literal["uploaded", "processed", "error"]] + status: Optional[Literal["uploaded", "processed", "error"]] = None """Deprecated. The current status of the file, which can be either `uploaded`, `processed`, or From 56328e653590bc114a0585a1378e38e1fccfad9b Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Wed, 3 Dec 2025 11:40:16 -0800 Subject: [PATCH 301/349] =?UTF-8?q?[Refactor#2]=20litellm/init=20=E2=80=93?= =?UTF-8?q?=20Lazy-load=20utils=20to=20reduce=20memory=20+=20import=20time?= =?UTF-8?q?=20(#17171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: lazy load utils.py imports Lazy-load most functions and response types from utils.py to avoid loading tiktoken and other heavy dependencies at import time. This significantly reduces memory usage when importing completion from litellm. --- litellm/__init__.py | 147 +++------- litellm/_lazy_imports.py | 259 ++++++++++++++++++ litellm/images/main.py | 4 +- litellm/llms/azure/azure.py | 2 +- litellm/llms/azure_ai/embed/handler.py | 4 +- .../amazon_nova_transformation.py | 3 +- litellm/llms/bedrock/common_utils.py | 19 ++ .../amazon_nova_canvas_transformation.py | 3 +- .../image/amazon_stability1_transformation.py | 3 +- .../image/amazon_stability3_transformation.py | 3 +- .../image/amazon_titan_transformation.py | 2 +- litellm/llms/ovhcloud/chat/transformation.py | 4 +- litellm/llms/together_ai/chat.py | 3 +- litellm/llms/vertex_ai/common_utils.py | 3 +- .../batch_embed_content_handler.py | 2 +- .../batch_embed_content_transformation.py | 2 +- .../image_generation_handler.py | 2 +- .../embedding_handler.py | 4 +- .../vertex_embeddings/embedding_handler.py | 4 +- litellm/main.py | 11 +- 20 files changed, 354 insertions(+), 130 deletions(-) create mode 100644 litellm/_lazy_imports.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 595a0132099..3d40a9b15cf 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1056,57 +1056,10 @@ from .timeout import timeout from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.litellm_core_utils.core_helpers import remove_index_from_tool_calls from litellm.litellm_core_utils.token_counter import get_modified_max_tokens -from .utils import ( - client, - exception_type, - get_optional_params, - get_response_string, - token_counter, - create_pretrained_tokenizer, - create_tokenizer, - supports_function_calling, - supports_web_search, - supports_url_context, - supports_response_schema, - supports_parallel_function_calling, - supports_vision, - supports_audio_input, - supports_audio_output, - supports_system_messages, - supports_reasoning, - get_litellm_params, - acreate, - get_max_tokens, - get_model_info, - register_prompt_template, - validate_environment, - check_valid_key, - register_model, - encode, - decode, - _calculate_retry_after, - _should_retry, - get_supported_openai_params, - get_api_base, - get_first_chars_messages, - ModelResponse, - ModelResponseStream, - EmbeddingResponse, - ImageResponse, - TranscriptionResponse, - TextCompletionResponse, - get_provider_fields, - ModelResponseListIterator, - get_valid_models, -) - -ALL_LITELLM_RESPONSE_TYPES = [ - ModelResponse, - EmbeddingResponse, - ImageResponse, - TranscriptionResponse, - TextCompletionResponse, -] +# client must be imported immediately as it's used as a decorator at function definition time +from .utils import client +# Note: Most other utils imports are lazy-loaded via __getattr__ to avoid loading utils.py +# (which imports tiktoken) at import time from .llms.bytez.chat.transformation import BytezChatConfig from .llms.custom_llm import CustomLLM @@ -1538,56 +1491,6 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None: # Lazy loading system for heavy modules to reduce initial import time and memory usage -def _lazy_import_cost_calculator(name: str) -> Any: - """Lazy import for cost_calculator functions.""" - from .cost_calculator import ( - completion_cost as _completion_cost, - cost_per_token as _cost_per_token, - response_cost_calculator as _response_cost_calculator, - ) - - _cost_functions = { - "completion_cost": _completion_cost, - "cost_per_token": _cost_per_token, - "response_cost_calculator": _response_cost_calculator, - } - - func = _cost_functions[name] - globals()[name] = func - return func - - -def _lazy_import_litellm_logging(name: str) -> Any: - """Lazy import for litellm_logging module.""" - try: - from litellm.litellm_core_utils.litellm_logging import ( - Logging as _Logging, - modify_integration as _modify_integration, - ) - - _logging_objects = { - "Logging": _Logging, - "modify_integration": _modify_integration, - } - - obj = _logging_objects[name] - globals()[name] = obj - return obj - except Exception as e: - raise AttributeError( - f"module {__name__!r} has no attribute {name!r}. " - f"Lazy import failed: {e}" - ) from e - - -_LAZY_LOAD_REGISTRY: Dict[str, Callable[[str], Any]] = { - "completion_cost": _lazy_import_cost_calculator, - "cost_per_token": _lazy_import_cost_calculator, - "response_cost_calculator": _lazy_import_cost_calculator, - "Logging": _lazy_import_litellm_logging, - "modify_integration": _lazy_import_litellm_logging, -} - if TYPE_CHECKING: cost_per_token: Callable[..., Tuple[float, float]] @@ -1598,7 +1501,45 @@ if TYPE_CHECKING: def __getattr__(name: str) -> Any: """Lazy import handler for cost_calculator and litellm_logging functions.""" - if name in _LAZY_LOAD_REGISTRY: - return _LAZY_LOAD_REGISTRY[name](name) + # Lazy load cost_calculator functions + _cost_calculator_names = ( + "completion_cost", + "cost_per_token", + "response_cost_calculator", + ) + if name in _cost_calculator_names: + from ._lazy_imports import _lazy_import_cost_calculator + return _lazy_import_cost_calculator(name) + + # Lazy load litellm_logging functions + _litellm_logging_names = ( + "Logging", + "modify_integration", + ) + if name in _litellm_logging_names: + from ._lazy_imports import _lazy_import_litellm_logging + return _lazy_import_litellm_logging(name) + + # Lazy load utils functions + _utils_names = ( + "exception_type", "get_optional_params", "get_response_string", "token_counter", + "create_pretrained_tokenizer", "create_tokenizer", "supports_function_calling", + "supports_web_search", "supports_url_context", "supports_response_schema", + "supports_parallel_function_calling", "supports_vision", "supports_audio_input", + "supports_audio_output", "supports_system_messages", "supports_reasoning", + "get_litellm_params", "acreate", "get_max_tokens", "get_model_info", + "register_prompt_template", "validate_environment", "check_valid_key", + "register_model", "encode", "decode", "_calculate_retry_after", "_should_retry", + "get_supported_openai_params", "get_api_base", "get_first_chars_messages", + "ModelResponse", "ModelResponseStream", "EmbeddingResponse", "ImageResponse", + "TranscriptionResponse", "TextCompletionResponse", "get_provider_fields", + "ModelResponseListIterator", "get_valid_models", + ) + if name in _utils_names: + from ._lazy_imports import _lazy_import_utils + return _lazy_import_utils(name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +# ALL_LITELLM_RESPONSE_TYPES is lazy-loaded via __getattr__ to avoid loading utils at import time diff --git a/litellm/_lazy_imports.py b/litellm/_lazy_imports.py new file mode 100644 index 00000000000..91b16864de1 --- /dev/null +++ b/litellm/_lazy_imports.py @@ -0,0 +1,259 @@ +from typing import Any +import sys + +def _get_litellm_globals() -> dict: + """Helper to get the globals dictionary of the litellm module.""" + return sys.modules["litellm"].__dict__ + +# Lazy import for utils module - imports only the requested item by name. +# Note: PLR0915 (too many statements) is suppressed because the many if statements +# are intentional - each attribute is imported individually only when requested, +# ensuring true lazy imports rather than importing the entire utils module. +def _lazy_import_utils(name: str) -> Any: # noqa: PLR0915 + """Lazy import for utils module - imports only the requested item by name.""" + _globals = _get_litellm_globals() + if name == "exception_type": + from .utils import exception_type as _exception_type + _globals["exception_type"] = _exception_type + return _exception_type + + if name == "get_optional_params": + from .utils import get_optional_params as _get_optional_params + _globals["get_optional_params"] = _get_optional_params + return _get_optional_params + + if name == "get_response_string": + from .utils import get_response_string as _get_response_string + _globals["get_response_string"] = _get_response_string + return _get_response_string + + if name == "token_counter": + from .utils import token_counter as _token_counter + _globals["token_counter"] = _token_counter + return _token_counter + + if name == "create_pretrained_tokenizer": + from .utils import create_pretrained_tokenizer as _create_pretrained_tokenizer + _globals["create_pretrained_tokenizer"] = _create_pretrained_tokenizer + return _create_pretrained_tokenizer + + if name == "create_tokenizer": + from .utils import create_tokenizer as _create_tokenizer + _globals["create_tokenizer"] = _create_tokenizer + return _create_tokenizer + + if name == "supports_function_calling": + from .utils import supports_function_calling as _supports_function_calling + _globals["supports_function_calling"] = _supports_function_calling + return _supports_function_calling + + if name == "supports_web_search": + from .utils import supports_web_search as _supports_web_search + _globals["supports_web_search"] = _supports_web_search + return _supports_web_search + + if name == "supports_url_context": + from .utils import supports_url_context as _supports_url_context + _globals["supports_url_context"] = _supports_url_context + return _supports_url_context + + if name == "supports_response_schema": + from .utils import supports_response_schema as _supports_response_schema + _globals["supports_response_schema"] = _supports_response_schema + return _supports_response_schema + + if name == "supports_parallel_function_calling": + from .utils import supports_parallel_function_calling as _supports_parallel_function_calling + _globals["supports_parallel_function_calling"] = _supports_parallel_function_calling + return _supports_parallel_function_calling + + if name == "supports_vision": + from .utils import supports_vision as _supports_vision + _globals["supports_vision"] = _supports_vision + return _supports_vision + + if name == "supports_audio_input": + from .utils import supports_audio_input as _supports_audio_input + _globals["supports_audio_input"] = _supports_audio_input + return _supports_audio_input + + if name == "supports_audio_output": + from .utils import supports_audio_output as _supports_audio_output + _globals["supports_audio_output"] = _supports_audio_output + return _supports_audio_output + + if name == "supports_system_messages": + from .utils import supports_system_messages as _supports_system_messages + _globals["supports_system_messages"] = _supports_system_messages + return _supports_system_messages + + if name == "supports_reasoning": + from .utils import supports_reasoning as _supports_reasoning + _globals["supports_reasoning"] = _supports_reasoning + return _supports_reasoning + + if name == "get_litellm_params": + from .utils import get_litellm_params as _get_litellm_params + _globals["get_litellm_params"] = _get_litellm_params + return _get_litellm_params + + if name == "acreate": + from .utils import acreate as _acreate + _globals["acreate"] = _acreate + return _acreate + + if name == "get_max_tokens": + from .utils import get_max_tokens as _get_max_tokens + _globals["get_max_tokens"] = _get_max_tokens + return _get_max_tokens + + if name == "get_model_info": + from .utils import get_model_info as _get_model_info + _globals["get_model_info"] = _get_model_info + return _get_model_info + + if name == "register_prompt_template": + from .utils import register_prompt_template as _register_prompt_template + _globals["register_prompt_template"] = _register_prompt_template + return _register_prompt_template + + if name == "validate_environment": + from .utils import validate_environment as _validate_environment + _globals["validate_environment"] = _validate_environment + return _validate_environment + + if name == "check_valid_key": + from .utils import check_valid_key as _check_valid_key + _globals["check_valid_key"] = _check_valid_key + return _check_valid_key + + if name == "register_model": + from .utils import register_model as _register_model + _globals["register_model"] = _register_model + return _register_model + + if name == "encode": + from .utils import encode as _encode + _globals["encode"] = _encode + return _encode + + if name == "decode": + from .utils import decode as _decode + _globals["decode"] = _decode + return _decode + + if name == "_calculate_retry_after": + from .utils import _calculate_retry_after as __calculate_retry_after + _globals["_calculate_retry_after"] = __calculate_retry_after + return __calculate_retry_after + + if name == "_should_retry": + from .utils import _should_retry as __should_retry + _globals["_should_retry"] = __should_retry + return __should_retry + + if name == "get_supported_openai_params": + from .utils import get_supported_openai_params as _get_supported_openai_params + _globals["get_supported_openai_params"] = _get_supported_openai_params + return _get_supported_openai_params + + if name == "get_api_base": + from .utils import get_api_base as _get_api_base + _globals["get_api_base"] = _get_api_base + return _get_api_base + + if name == "get_first_chars_messages": + from .utils import get_first_chars_messages as _get_first_chars_messages + _globals["get_first_chars_messages"] = _get_first_chars_messages + return _get_first_chars_messages + + if name == "ModelResponse": + from .utils import ModelResponse as _ModelResponse + _globals["ModelResponse"] = _ModelResponse + return _ModelResponse + + if name == "ModelResponseStream": + from .utils import ModelResponseStream as _ModelResponseStream + _globals["ModelResponseStream"] = _ModelResponseStream + return _ModelResponseStream + + if name == "EmbeddingResponse": + from .utils import EmbeddingResponse as _EmbeddingResponse + _globals["EmbeddingResponse"] = _EmbeddingResponse + return _EmbeddingResponse + + if name == "ImageResponse": + from .utils import ImageResponse as _ImageResponse + _globals["ImageResponse"] = _ImageResponse + return _ImageResponse + + if name == "TranscriptionResponse": + from .utils import TranscriptionResponse as _TranscriptionResponse + _globals["TranscriptionResponse"] = _TranscriptionResponse + return _TranscriptionResponse + + if name == "TextCompletionResponse": + from .utils import TextCompletionResponse as _TextCompletionResponse + _globals["TextCompletionResponse"] = _TextCompletionResponse + return _TextCompletionResponse + + if name == "get_provider_fields": + from .utils import get_provider_fields as _get_provider_fields + _globals["get_provider_fields"] = _get_provider_fields + return _get_provider_fields + + if name == "ModelResponseListIterator": + from .utils import ModelResponseListIterator as _ModelResponseListIterator + _globals["ModelResponseListIterator"] = _ModelResponseListIterator + return _ModelResponseListIterator + + if name == "get_valid_models": + from .utils import get_valid_models as _get_valid_models + _globals["get_valid_models"] = _get_valid_models + return _get_valid_models + + raise AttributeError(f"Utils lazy import: unknown attribute {name!r}") + + +def _lazy_import_cost_calculator(name: str) -> Any: + """Lazy import for cost_calculator functions.""" + _globals = _get_litellm_globals() + from .cost_calculator import ( + completion_cost as _completion_cost, + cost_per_token as _cost_per_token, + response_cost_calculator as _response_cost_calculator, + ) + + _cost_functions = { + "completion_cost": _completion_cost, + "cost_per_token": _cost_per_token, + "response_cost_calculator": _response_cost_calculator, + } + + func = _cost_functions[name] + _globals[name] = func + return func + + +def _lazy_import_litellm_logging(name: str) -> Any: + """Lazy import for litellm_logging module.""" + _globals = _get_litellm_globals() + try: + from litellm.litellm_core_utils.litellm_logging import ( + Logging as _Logging, + modify_integration as _modify_integration, + ) + + _logging_objects = { + "Logging": _Logging, + "modify_integration": _modify_integration, + } + + obj = _logging_objects[name] + _globals[name] = obj + return obj + except Exception as e: + raise AttributeError( + f"module 'litellm' has no attribute {name!r}. " + f"Lazy import failed: {e}" + ) from e \ No newline at end of file diff --git a/litellm/images/main.py b/litellm/images/main.py index eacd4778299..770b16c1ed2 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -6,7 +6,9 @@ from typing import Any, Coroutine, Dict, List, Literal, Optional, Union, cast, o import httpx import litellm -from litellm import client, exception_type, get_litellm_params +from litellm.utils import exception_type, get_litellm_params +# client is imported from litellm as it's a decorator +from litellm import client from litellm.constants import DEFAULT_IMAGE_ENDPOINT_MODEL from litellm.constants import request_timeout as DEFAULT_REQUEST_TIMEOUT from litellm.exceptions import LiteLLMUnknownProvider diff --git a/litellm/llms/azure/azure.py b/litellm/llms/azure/azure.py index e7aa93ac882..994afa26e9c 100644 --- a/litellm/llms/azure/azure.py +++ b/litellm/llms/azure/azure.py @@ -1020,7 +1020,7 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): headers: dict, client=None, timeout=None, - ) -> litellm.ImageResponse: + ) -> ImageResponse: response: Optional[dict] = None try: diff --git a/litellm/llms/azure_ai/embed/handler.py b/litellm/llms/azure_ai/embed/handler.py index 13b8cc4cf29..67733d1ccb5 100644 --- a/litellm/llms/azure_ai/embed/handler.py +++ b/litellm/llms/azure_ai/embed/handler.py @@ -58,7 +58,7 @@ class AzureAIEmbedding(OpenAIChatCompletion): data: ImageEmbeddingRequest, timeout: float, logging_obj, - model_response: litellm.EmbeddingResponse, + model_response: EmbeddingResponse, optional_params: dict, api_key: Optional[str], api_base: Optional[str], @@ -138,7 +138,7 @@ class AzureAIEmbedding(OpenAIChatCompletion): input: List, timeout: float, logging_obj, - model_response: litellm.EmbeddingResponse, + model_response: EmbeddingResponse, optional_params: dict, api_key: Optional[str] = None, api_base: Optional[str] = None, diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_nova_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_nova_transformation.py index a81d55f0ad2..3506c8f1cc0 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_nova_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_nova_transformation.py @@ -10,7 +10,6 @@ from typing import Any, List, Optional import httpx -import litellm from litellm.litellm_core_utils.litellm_logging import Logging from litellm.types.llms.bedrock import BedrockInvokeNovaRequest from litellm.types.llms.openai import AllMessageValues @@ -80,7 +79,7 @@ class AmazonInvokeNovaConfig(AmazonInvokeConfig, AmazonConverseConfig): encoding: Any, api_key: Optional[str] = None, json_mode: Optional[bool] = None, - ) -> litellm.ModelResponse: + ) -> ModelResponse: return AmazonConverseConfig.transform_response( self, model, diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index ad115bc7e92..03ca7a5ad29 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -27,6 +27,25 @@ class BedrockError(BaseLLMException): pass +# Lazy import cache to avoid circular imports and performance impact +_get_model_info = None + + +def get_cached_model_info(): + """ + Lazy import and cache get_model_info to avoid circular imports. + + This function is used by bedrock transformation classes that need get_model_info + but cannot import it at module level due to circular import issues. + The function is cached after first use to avoid performance impact. + """ + global _get_model_info + if _get_model_info is None: + from litellm import get_model_info + _get_model_info = get_model_info + return _get_model_info + + class AmazonBedrockGlobalConfig: def __init__(self): pass diff --git a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py index f2b94b617c0..18366999583 100644 --- a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py +++ b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py @@ -3,7 +3,6 @@ from typing import Any, Dict, List, Optional from openai.types.image import Image -from litellm import get_model_info from litellm.types.llms.bedrock import ( AmazonNovaCanvasColorGuidedGenerationParams, AmazonNovaCanvasColorGuidedRequest, @@ -15,6 +14,7 @@ from litellm.types.llms.bedrock import ( AmazonNovaCanvasTextToImageRequest, AmazonNovaCanvasTextToImageResponse, ) +from litellm.llms.bedrock.common_utils import get_cached_model_info from litellm.types.utils import ImageResponse @@ -207,6 +207,7 @@ class AmazonNovaCanvasConfig: size: Optional[str] = None, optional_params: Optional[dict] = None, ) -> float: + get_model_info = get_cached_model_info() model_info = get_model_info( model=model, custom_llm_provider="bedrock", diff --git a/litellm/llms/bedrock/image/amazon_stability1_transformation.py b/litellm/llms/bedrock/image/amazon_stability1_transformation.py index 63af32f3f56..07f82cec232 100644 --- a/litellm/llms/bedrock/image/amazon_stability1_transformation.py +++ b/litellm/llms/bedrock/image/amazon_stability1_transformation.py @@ -5,7 +5,7 @@ from typing import List, Optional from openai.types.image import Image -from litellm import get_model_info +from litellm.llms.bedrock.common_utils import get_cached_model_info from litellm.types.utils import ImageResponse @@ -151,6 +151,7 @@ class AmazonStabilityConfig: size = size or "1024-x-1024" model = f"{size}/{steps}/{model}" + get_model_info = get_cached_model_info() model_info = get_model_info( model=model, custom_llm_provider="bedrock", diff --git a/litellm/llms/bedrock/image/amazon_stability3_transformation.py b/litellm/llms/bedrock/image/amazon_stability3_transformation.py index 445a2fe1100..160d0af8e80 100644 --- a/litellm/llms/bedrock/image/amazon_stability3_transformation.py +++ b/litellm/llms/bedrock/image/amazon_stability3_transformation.py @@ -3,12 +3,12 @@ from typing import List, Optional from openai.types.image import Image -from litellm import get_model_info from litellm.llms.bedrock.common_utils import BedrockError from litellm.types.llms.bedrock import ( AmazonStability3TextToImageRequest, AmazonStability3TextToImageResponse, ) +from litellm.llms.bedrock.common_utils import get_cached_model_info from litellm.types.utils import ImageResponse @@ -115,6 +115,7 @@ class AmazonStability3Config: size: Optional[str] = None, optional_params: Optional[dict] = None, ) -> float: + get_model_info = get_cached_model_info() model_info = get_model_info( model=model, custom_llm_provider="bedrock", diff --git a/litellm/llms/bedrock/image/amazon_titan_transformation.py b/litellm/llms/bedrock/image/amazon_titan_transformation.py index bed9ad0c300..65411cabdcf 100644 --- a/litellm/llms/bedrock/image/amazon_titan_transformation.py +++ b/litellm/llms/bedrock/image/amazon_titan_transformation.py @@ -7,7 +7,7 @@ from typing import List, Optional from openai.types.image import Image -from litellm import get_model_info +from litellm.utils import get_model_info from litellm.types.llms.bedrock import ( AmazonNovaCanvasImageGenerationConfig, AmazonTitanImageGenerationRequestBody, diff --git a/litellm/llms/ovhcloud/chat/transformation.py b/litellm/llms/ovhcloud/chat/transformation.py index 6bdc28620ff..e9dc5be3eed 100644 --- a/litellm/llms/ovhcloud/chat/transformation.py +++ b/litellm/llms/ovhcloud/chat/transformation.py @@ -7,7 +7,9 @@ More information on our website: https://endpoints.ai.cloud.ovh.net from typing import Optional, Union, List import httpx -from litellm import ModelResponseStream, OpenAIGPTConfig, get_model_info, verbose_logger +from litellm.utils import ModelResponseStream, get_model_info +from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm._logging import verbose_logger from litellm.llms.ovhcloud.utils import OVHCloudException from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator from litellm.llms.base_llm.chat.transformation import BaseLLMException diff --git a/litellm/llms/together_ai/chat.py b/litellm/llms/together_ai/chat.py index 06d33f69750..e8a784d2779 100644 --- a/litellm/llms/together_ai/chat.py +++ b/litellm/llms/together_ai/chat.py @@ -8,7 +8,8 @@ Docs: https://docs.together.ai/reference/completions-1 from typing import Optional -from litellm import get_model_info, verbose_logger +from litellm.utils import get_model_info +from litellm._logging import verbose_logger from ..openai.chat.gpt_transformation import OpenAIGPTConfig diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index dc6a3170afe..a3ea8afe40d 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -5,7 +5,8 @@ from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, get_ty import httpx import litellm -from litellm import supports_response_schema, supports_system_messages, verbose_logger +from litellm.utils import supports_response_schema, supports_system_messages +from litellm._logging import verbose_logger from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs from litellm.llms.base_llm.base_utils import BaseLLMModelInfo, BaseTokenCounter diff --git a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py index af9af71fef4..859bb0a6984 100644 --- a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py +++ b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py @@ -8,7 +8,7 @@ from typing import Any, Literal, Optional, Union import httpx import litellm -from litellm import EmbeddingResponse +from litellm.types.utils import EmbeddingResponse from litellm.llms.custom_httpx.http_handler import ( AsyncHTTPHandler, HTTPHandler, diff --git a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py index 2c0f5dad228..455ec1d18f5 100644 --- a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py +++ b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py @@ -6,7 +6,7 @@ Why separate file? Make it easy to see how transformation works from typing import List -from litellm import EmbeddingResponse +from litellm.types.utils import EmbeddingResponse from litellm.types.llms.openai import EmbeddingInput from litellm.types.llms.vertex_ai import ( ContentType, diff --git a/litellm/llms/vertex_ai/image_generation/image_generation_handler.py b/litellm/llms/vertex_ai/image_generation/image_generation_handler.py index 04be4de8e32..e14cfe3be0b 100644 --- a/litellm/llms/vertex_ai/image_generation/image_generation_handler.py +++ b/litellm/llms/vertex_ai/image_generation/image_generation_handler.py @@ -176,7 +176,7 @@ class VertexImageGeneration(VertexLLM): vertex_project: Optional[str], vertex_location: Optional[str], vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], - model_response: litellm.ImageResponse, + model_response: ImageResponse, logging_obj: Any, model: str = "imagegeneration", # vertex ai uses imagegeneration as the default model client: Optional[AsyncHTTPHandler] = None, diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py index 582d7a4c569..d0ffc7be0a6 100644 --- a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py @@ -147,13 +147,13 @@ class VertexMultimodalEmbedding(VertexLLM): optional_params: dict, litellm_params: dict, data: dict, - model_response: litellm.EmbeddingResponse, + model_response: EmbeddingResponse, timeout: Optional[Union[float, httpx.Timeout]], logging_obj: LiteLLMLoggingObj, headers={}, client: Optional[AsyncHTTPHandler] = None, api_key: Optional[str] = None, - ) -> litellm.EmbeddingResponse: + ) -> EmbeddingResponse: if client is None: _params = {} if timeout is not None: diff --git a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py index a170e6cc7f2..aaa6a0bb95f 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py @@ -137,7 +137,7 @@ class VertexEmbedding(VertexBase): self, model: str, input: Union[list, str], - model_response: litellm.EmbeddingResponse, + model_response: EmbeddingResponse, logging_obj: LiteLLMLoggingObject, optional_params: dict, custom_llm_provider: Literal[ @@ -152,7 +152,7 @@ class VertexEmbedding(VertexBase): gemini_api_key: Optional[str] = None, extra_headers: Optional[dict] = None, encoding=None, - ) -> litellm.EmbeddingResponse: + ) -> EmbeddingResponse: """ Async embedding implementation """ diff --git a/litellm/main.py b/litellm/main.py index 4f3abab52b7..0e19699309d 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -52,13 +52,10 @@ from pydantic import BaseModel from typing_extensions import overload import litellm -from litellm import ( # type: ignore - client, - exception_type, - get_litellm_params, - get_optional_params, -) - +# client must be imported from litellm as it's a decorator used at function definition time +from litellm import client +# Other utils are imported directly to avoid circular imports +from litellm.utils import exception_type, get_litellm_params, get_optional_params # Logging is imported lazily when needed to avoid loading litellm_logging at import time if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging From 9783f0ff6c4edf302eb2845c9a6c0c4fdbf5958b Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 12:14:48 -0800 Subject: [PATCH 302/349] Remove second scrollbar when sidebar is expanded + tooltip z index --- ui/litellm-dashboard/src/app/page.tsx | 2 +- ui/litellm-dashboard/src/components/model_dashboard/table.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 422d140e07c..a71d7dd87d3 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -328,7 +328,7 @@ export default function CreateKeyPage() { sidebarCollapsed={sidebarCollapsed} onToggleSidebar={toggleSidebar} /> -
    + )} + + {/* Agent Selector - shown ONLY for A2A Agents endpoint */} + {endpointType === EndpointType.A2A_AGENTS && ( +
    + + Select Agent + + + {agentInfo.length === 0 && ( + + No agents found. Create agents via /v1/agents endpoint. + + )} +
    + )}
    @@ -1440,7 +1550,8 @@ const ChatUI: React.FC = ({ )} {message.role === "assistant" && - (message.timeToFirstToken || message.totalLatency || message.usage) && ( + (message.timeToFirstToken || message.totalLatency || message.usage) && + !message.a2aMetadata && ( = ({ toolName={message.toolName} /> )} + + {/* A2A Metrics - show for A2A agent responses */} + {message.role === "assistant" && message.a2aMetadata && ( + + )}
    +
    diff --git a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx index b74060db6c5..344ff2e94f2 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx @@ -100,7 +100,7 @@ export function ModelDataTable({ key={header.id} className={`py-1 h-8 relative ${ header.id === "actions" - ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8" + ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] w-[120px] ml-8" : "" } ${header.column.columnDef.meta?.className || ""}`} style={{ @@ -160,7 +160,7 @@ export function ModelDataTable({ key={cell.id} className={`py-0.5 ${ cell.column.id === "actions" - ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8" + ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] w-[120px] ml-8" : "" } ${cell.column.columnDef.meta?.className || ""}`} style={{ From 9bb292f478dab11c0cd8088fde353fc349fb8e93 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 12:41:45 -0800 Subject: [PATCH 303/349] V2 login route --- litellm/proxy/proxy_server.py | 47 +++++++++++++++++ tests/test_litellm/proxy/test_proxy_server.py | 52 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9789f495508..4d971e8ce42 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8318,6 +8318,53 @@ async def login(request: Request): # noqa: PLR0915 return redirect_response +@router.post( + "/v2/login", include_in_schema=False +) # hidden helper for UI logins via API +async def login_v2(request: Request): # noqa: PLR0915 + global premium_user, general_settings, master_key + from litellm.proxy.auth.login_utils import authenticate_user, create_ui_token_object + from litellm.proxy.utils import get_custom_url + + body = await request.json() + username = str(body.get("username")) + password = str(body.get("password")) + + login_result = await authenticate_user( + username=username, + password=password, + master_key=master_key, + prisma_client=prisma_client, + ) + + returned_ui_token_object = create_ui_token_object( + login_result=login_result, + general_settings=general_settings, + premium_user=premium_user, + ) + + import jwt + + jwt_token = jwt.encode( # type: ignore + cast(dict, returned_ui_token_object), + master_key, + algorithm="HS256", + ) + + litellm_dashboard_ui = get_custom_url(str(request.base_url)) + if litellm_dashboard_ui.endswith("/"): + litellm_dashboard_ui += "ui/" + else: + litellm_dashboard_ui += "/ui/" + litellm_dashboard_ui += "?login=success" + + json_response = JSONResponse( + content={"redirect_url": litellm_dashboard_ui}, + status_code=status.HTTP_200_OK, + ) + json_response.set_cookie(key="token", value=jwt_token) + return json_response + @app.get("/onboarding/get_token", include_in_schema=False) async def onboarding(invite_link: str, request: Request): """ diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 8e87b67933f..7b3157f8ee1 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -71,6 +71,58 @@ def client_no_auth(): return TestClient(app) +def test_login_v2_returns_redirect_url_and_sets_cookie(monkeypatch): + mock_login_result = {"user_id": "test-user"} + mock_prisma_client = MagicMock() + mock_authenticate_user = AsyncMock(return_value=mock_login_result) + mock_create_ui_token_object = MagicMock(return_value={"user_id": "test-user"}) + mock_jwt_encode = MagicMock(return_value="signed-token") + + monkeypatch.setattr( + "litellm.proxy.auth.login_utils.authenticate_user", + mock_authenticate_user, + ) + monkeypatch.setattr( + "litellm.proxy.auth.login_utils.create_ui_token_object", + mock_create_ui_token_object, + ) + monkeypatch.setattr("jwt.encode", mock_jwt_encode) + monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "test-master-key") + monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", False) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + client = TestClient(app) + response = client.post( + "/v2/login", + json={"username": "alice", "password": "secret"}, + ) + + assert response.status_code == 200 + assert ( + response.json() + == {"redirect_url": "http://testserver/ui/?login=success"} + ) + assert response.cookies.get("token") == "signed-token" + + mock_authenticate_user.assert_awaited_once_with( + username="alice", + password="secret", + master_key="test-master-key", + prisma_client=mock_prisma_client, + ) + mock_create_ui_token_object.assert_called_once_with( + login_result=mock_login_result, + general_settings={}, + premium_user=False, + ) + mock_jwt_encode.assert_called_once_with( + {"user_id": "test-user"}, + "test-master-key", + algorithm="HS256", + ) + + @pytest.mark.asyncio async def test_initialize_scheduled_jobs_credentials(monkeypatch): """ From 5f43e7a2d2b2787e26f6f1da4efa30b0aeeb0391 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 1 Dec 2025 17:15:59 -0800 Subject: [PATCH 304/349] New login page WIP --- .../app/(dashboard)/hooks/login/useLogin.ts | 17 +++ .../(dashboard)/hooks/uiConfig/useUIConfig.ts | 14 ++ ui/litellm-dashboard/src/app/login/page.tsx | 123 ++++++++++++++++++ .../src/components/networking.tsx | 65 ++++++++- 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/uiConfig/useUIConfig.ts create mode 100644 ui/litellm-dashboard/src/app/login/page.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts new file mode 100644 index 00000000000..486d2e39929 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts @@ -0,0 +1,17 @@ +import { useMutation } from "@tanstack/react-query"; +import { loginCall, LoginRequest } from "@/components/networking"; + +export const useLogin = () => { + return useMutation({ + mutationFn: async ({ username, password }: LoginRequest) => { + const result = await loginCall(username, password); + return result; + }, + onSuccess: (data) => { + // Redirect to UI on successful login + if (data.success && data.redirectUrl) { + window.location.href = data.redirectUrl; + } + }, + }); +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiConfig/useUIConfig.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiConfig/useUIConfig.ts new file mode 100644 index 00000000000..147fdc241b0 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiConfig/useUIConfig.ts @@ -0,0 +1,14 @@ +import { getUiConfig, LiteLLMWellKnownUiConfig } from "@/components/networking"; +import { useQuery } from "@tanstack/react-query"; +import { createQueryKeys } from "../common/queryKeysFactory"; + +const uiConfigKeys = createQueryKeys("uiConfig"); + +export const useUIConfig = () => { + return useQuery({ + queryKey: uiConfigKeys.list({}), + queryFn: async () => await getUiConfig(), + staleTime: 24 * 60 * 60 * 1000, // 24 hours - data rarely changes + gcTime: 24 * 60 * 60 * 1000, // 24 hours - keep in cache for 24 hours + }); +}; diff --git a/ui/litellm-dashboard/src/app/login/page.tsx b/ui/litellm-dashboard/src/app/login/page.tsx new file mode 100644 index 00000000000..4ffe7cbfe80 --- /dev/null +++ b/ui/litellm-dashboard/src/app/login/page.tsx @@ -0,0 +1,123 @@ +"use client"; + +import React, { useState } from "react"; +import { Form, Input, Button, Alert, Typography, Card, Spin, Space } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; +import { useLogin } from "@/app/(dashboard)/hooks/login/useLogin"; + +function LoginPageContent() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const { isLoading: isConfigLoading } = useUIConfig(); + const loginMutation = useLogin(); + + const handleSubmit = async () => { + loginMutation.mutate({ username, password }); + }; + + const error = loginMutation.error instanceof Error ? loginMutation.error.message : null; + const isLoading = loginMutation.isPending; + + const { Title, Text, Paragraph } = Typography; + + if (isConfigLoading) { + return ( +
    + +
    + ); + } + + return ( +
    + + +
    + 🚅 LiteLLM +
    + +
    + Login + Access your LiteLLM Admin UI. +
    + + + + By default, Username is admin and + Password is your set LiteLLM Proxy + MASTER_KEY. + + + Need to set UI credentials or SSO?{" "} +
    + Check the documentation + + . + + + } + type="info" + icon={} + showIcon + /> + + {error && } + +
    + + setUsername(e.target.value)} + disabled={isLoading} + size="large" + className="rounded-md border-gray-300" + /> + + + + setPassword(e.target.value)} + disabled={isLoading} + size="large" + /> + + + + + +
    + + +
    + ); +} + +export default function LoginPage() { + const queryClient = new QueryClient(); + + return ( + + + + ); +} diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 14763930aaf..76b79c0f8b8 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -124,7 +124,7 @@ export interface PromptSpec { prompt_info: PromptInfo; created_at?: string; updated_at?: string; - version?: number; // Explicit version number for version history + version?: number; // Explicit version number for version history } export interface PromptTemplateBase { @@ -7414,7 +7414,11 @@ interface RegisterMcpOAuthClientPayload { token_endpoint_auth_method?: string; } -export const registerMcpOAuthClient = async (accessToken: string, serverId: string, payload: RegisterMcpOAuthClientPayload) => { +export const registerMcpOAuthClient = async ( + accessToken: string, + serverId: string, + payload: RegisterMcpOAuthClientPayload, +) => { const base = getProxyBaseUrl(); const normalizedServerId = encodeURIComponent(serverId.trim()); const url = `${base}/v1/mcp/server/oauth/${normalizedServerId}/register`; @@ -7424,7 +7428,7 @@ export const registerMcpOAuthClient = async (accessToken: string, serverId: stri headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}`, "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", + Accept: "application/json, text/event-stream", }, body: JSON.stringify(payload), }); @@ -7969,3 +7973,58 @@ const deriveErrorMessage = (errorData: any): string => { JSON.stringify(errorData) ); }; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface LoginResponse { + success: boolean; + redirectUrl?: string; +} + +export const loginCall = async (username: string, password: string): Promise => { + const proxyBaseUrl = getProxyBaseUrl(); + const loginUrl = proxyBaseUrl ? `${proxyBaseUrl}/login` : "/login"; + + const formData = new FormData(); + formData.append("username", username); + formData.append("password", password); + + const response = await fetch(loginUrl, { + method: "POST", + body: formData, + credentials: "include", + redirect: "manual", + }); + + // Handle redirect status codes (301, 302, 303, 307, 308) + if (response.status >= 300 && response.status < 400) { + const redirectUrl = response.headers.get("Location"); + + if (!redirectUrl) { + throw new Error("Login redirect missing Location header"); + } + + return { + success: true, + redirectUrl, + }; + } + + if (response.ok) { + return { + success: true, + redirectUrl: "/uia/", + }; + } + + // Otherwise, try to extract an error + let errorText = "Invalid username or password"; + try { + errorText = await response.text(); + } catch {} + + throw new Error(errorText || "Login failed"); +}; From 9b3d8302cf9e2bd52df4bcdc49c6e0c08f181e9f Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 3 Dec 2025 14:12:50 -0800 Subject: [PATCH 305/349] docs fix stable --- docs/my-website/release_notes/v1.80.5-stable/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/release_notes/v1.80.5-stable/index.md b/docs/my-website/release_notes/v1.80.5-stable/index.md index 4324fdef776..7c3283ce349 100644 --- a/docs/my-website/release_notes/v1.80.5-stable/index.md +++ b/docs/my-website/release_notes/v1.80.5-stable/index.md @@ -1,5 +1,5 @@ --- -title: "[PREVIEW] v1.80.5.rc.2 - Gemini 3.0 Support" +title: "v1.80.5-stable - Gemini 3.0 Support" slug: "v1-80-5" date: 2025-11-22T10:00:00 authors: @@ -27,7 +27,7 @@ import TabItem from '@theme/TabItem'; docker run \ -e STORE_MODEL_IN_DB=True \ -p 4000:4000 \ -ghcr.io/berriai/litellm:v1.80.5.rc.2 +ghcr.io/berriai/litellm:v1.80.5-stable ``` From 100cfc11acf73e6c8bd92f7f856568f7623f0d4f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 14:13:28 -0800 Subject: [PATCH 306/349] [Bug Fix] Parallel Request Limiter with /messages (#17426) * fix: use standard_logging_object for parallel request limiter * fix test parallel request limtier --- .../hooks/parallel_request_limiter_v3.py | 34 ++++++++++--------- .../hooks/test_parallel_request_limiter_v3.py | 10 +++--- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/litellm/proxy/hooks/parallel_request_limiter_v3.py b/litellm/proxy/hooks/parallel_request_limiter_v3.py index 9436df585a3..2abba1d4976 100644 --- a/litellm/proxy/hooks/parallel_request_limiter_v3.py +++ b/litellm/proxy/hooks/parallel_request_limiter_v3.py @@ -1343,19 +1343,19 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): "INSIDE parallel request limiter ASYNC SUCCESS LOGGING" ) - # Get metadata from kwargs - litellm_metadata = kwargs["litellm_params"].get( - get_metadata_variable_name_from_kwargs(kwargs), {} + # Get metadata from standard_logging_object - this correctly handles both + # 'metadata' and 'litellm_metadata' fields from litellm_params + standard_logging_object = kwargs.get("standard_logging_object") or {} + standard_logging_metadata = standard_logging_object.get("metadata") or {} + + # user_api_key_hash is the same as user_api_key (it's the hash) + user_api_key = standard_logging_metadata.get("user_api_key_hash") + user_api_key_user_id = standard_logging_metadata.get("user_api_key_user_id") + user_api_key_team_id = standard_logging_metadata.get("user_api_key_team_id") + user_api_key_organization_id = standard_logging_metadata.get( + "user_api_key_org_id" ) - if litellm_metadata is None: - return - user_api_key = litellm_metadata.get("user_api_key") - user_api_key_user_id = litellm_metadata.get("user_api_key_user_id") - user_api_key_team_id = litellm_metadata.get("user_api_key_team_id") - user_api_key_organization_id = litellm_metadata.get( - "user_api_key_organization_id" - ) - user_api_key_end_user_id = kwargs.get("user") or litellm_metadata.get( + user_api_key_end_user_id = kwargs.get("user") or standard_logging_metadata.get( "user_api_key_end_user_id" ) model_group = get_model_group_from_litellm_kwargs(kwargs) @@ -1501,10 +1501,12 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): litellm_parent_otel_span: Union[ Span, None ] = _get_parent_otel_span_from_kwargs(kwargs) - litellm_metadata = kwargs["litellm_params"]["metadata"] - user_api_key = ( - litellm_metadata.get("user_api_key") if litellm_metadata else None - ) + # Get metadata from standard_logging_object - this correctly handles both + # 'metadata' and 'litellm_metadata' fields from litellm_params + standard_logging_object = kwargs.get("standard_logging_object") or {} + standard_logging_metadata = standard_logging_object.get("metadata") or {} + user_api_key = standard_logging_metadata.get("user_api_key_hash") + pipeline_operations: List[RedisPipelineIncrementOperation] = [] if user_api_key: diff --git a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py index 6574c500fcf..c8c30d41b5e 100644 --- a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py +++ b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py @@ -22,6 +22,7 @@ from litellm.proxy.hooks.parallel_request_limiter_v3 import ( from litellm.proxy.utils import InternalUsageCache, ProxyLogging, hash_token from litellm.types.utils import ModelResponse, Usage + class TimeController: def __init__(self): self._current = datetime.utcnow() @@ -461,10 +462,11 @@ async def test_token_rate_limit_type_respected_v3(monkeypatch, token_rate_limit_ ) # Create mock kwargs for the success event + # Use standard_logging_object which is the canonical source for metadata mock_kwargs = { - "litellm_params": { + "standard_logging_object": { "metadata": { - "user_api_key": _api_key, + "user_api_key_hash": _api_key, "user_api_key_user_id": None, "user_api_key_team_id": None, "user_api_key_end_user_id": None, @@ -532,8 +534,8 @@ async def test_async_log_failure_event_v3(): internal_usage_cache=InternalUsageCache(local_cache) ) - # Mock kwargs with user_api_key - mock_kwargs = {"litellm_params": {"metadata": {"user_api_key": _api_key}}} + # Mock kwargs with user_api_key via standard_logging_object + mock_kwargs = {"standard_logging_object": {"metadata": {"user_api_key_hash": _api_key}}} # Capture pipeline operations captured_ops = [] From b14b4a71129516c97ae9a23b41668f6602e8ed21 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 15:05:29 -0800 Subject: [PATCH 307/349] New login screen using v2/login --- .../app/(dashboard)/hooks/login/useLogin.ts | 6 -- ui/litellm-dashboard/src/app/login/page.tsx | 65 ++++++++--- ui/litellm-dashboard/src/app/page.tsx | 102 +++++++----------- .../common_components/LoadingScreen.test.tsx | 10 ++ .../common_components/LoadingScreen.tsx | 15 +++ .../src/components/networking.tsx | 50 +++------ .../src/utils/jwtUtils.test.ts | 53 +++++++++ ui/litellm-dashboard/src/utils/jwtUtils.ts | 14 +++ 8 files changed, 193 insertions(+), 122 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/common_components/LoadingScreen.test.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/LoadingScreen.tsx create mode 100644 ui/litellm-dashboard/src/utils/jwtUtils.test.ts create mode 100644 ui/litellm-dashboard/src/utils/jwtUtils.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts index 486d2e39929..a15b4a06d13 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/login/useLogin.ts @@ -7,11 +7,5 @@ export const useLogin = () => { const result = await loginCall(username, password); return result; }, - onSuccess: (data) => { - // Redirect to UI on successful login - if (data.success && data.redirectUrl) { - window.location.href = data.redirectUrl; - } - }, }); }; diff --git a/ui/litellm-dashboard/src/app/login/page.tsx b/ui/litellm-dashboard/src/app/login/page.tsx index 4ffe7cbfe80..6248639b063 100644 --- a/ui/litellm-dashboard/src/app/login/page.tsx +++ b/ui/litellm-dashboard/src/app/login/page.tsx @@ -1,33 +1,57 @@ "use client"; -import React, { useState } from "react"; -import { Form, Input, Button, Alert, Typography, Card, Spin, Space } from "antd"; +import { useLogin } from "@/app/(dashboard)/hooks/login/useLogin"; +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; +import LoadingScreen from "@/components/common_components/LoadingScreen"; +import { getProxyBaseUrl } from "@/components/networking"; +import { getCookie } from "@/utils/cookieUtils"; +import { isJwtExpired } from "@/utils/jwtUtils"; import { InfoCircleOutlined } from "@ant-design/icons"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; -import { useLogin } from "@/app/(dashboard)/hooks/login/useLogin"; +import { Alert, Button, Card, Form, Input, Space, Typography } from "antd"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; function LoginPageContent() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(true); const { isLoading: isConfigLoading } = useUIConfig(); const loginMutation = useLogin(); + const router = useRouter(); - const handleSubmit = async () => { - loginMutation.mutate({ username, password }); + useEffect(() => { + if (isConfigLoading) { + return; + } + + const rawToken = getCookie("token"); + if (rawToken && !isJwtExpired(rawToken)) { + router.replace(`${getProxyBaseUrl()}/ui`); + return; + } + + setIsLoading(false); + }, [isConfigLoading, router]); + + const handleSubmit = () => { + loginMutation.mutate( + { username, password }, + { + onSuccess: (data) => { + router.push(data.redirect_url); + }, + }, + ); }; const error = loginMutation.error instanceof Error ? loginMutation.error.message : null; - const isLoading = loginMutation.isPending; + const isLoginLoading = loginMutation.isPending; const { Title, Text, Paragraph } = Typography; - if (isConfigLoading) { - return ( -
    - -
    - ); + if (isConfigLoading || isLoading) { + return ; } return ( @@ -79,7 +103,7 @@ function LoginPageContent() { autoComplete="username" value={username} onChange={(e) => setUsername(e.target.value)} - disabled={isLoading} + disabled={isLoginLoading} size="large" className="rounded-md border-gray-300" /> @@ -95,14 +119,21 @@ function LoginPageContent() { autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} - disabled={isLoading} + disabled={isLoginLoading} size="large" /> - diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index a71d7dd87d3..68060ae6968 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -1,49 +1,47 @@ "use client"; -import React, { Suspense, useEffect, useState } from "react"; -import { useSearchParams } from "next/navigation"; -import { jwtDecode } from "jwt-decode"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { Team } from "@/components/key_team_helpers/key_list"; -import Navbar from "@/components/navbar"; -import { ThemeProvider } from "@/contexts/ThemeContext"; -import UserDashboard from "@/components/user_dashboard"; -import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView"; -import ViewUserDashboard from "@/components/view_users"; -import Organizations from "@/components/organizations"; -import { fetchOrganizations } from "@/components/organizations"; -import AdminPanel from "@/components/admins"; -import Settings from "@/components/settings"; -import GeneralSettings from "@/components/general_settings"; -import PassThroughSettings from "@/components/pass_through_settings"; -import BudgetPanel from "@/components/budgets/budget_panel"; -import SpendLogsTable from "@/components/view_logs"; -import ModelHubTable from "@/components/model_hub_table"; -import PublicModelHub from "@/components/public_model_hub"; -import NewUsagePage from "@/components/new_usage"; import APIReferenceView from "@/app/(dashboard)/api-reference/APIReferenceView"; -import PlaygroundPage from "@/app/(dashboard)/playground/page"; -import Usage from "@/components/usage"; -import CacheDashboard from "@/components/cache_dashboard"; -import { getUiConfig, proxyBaseUrl, setGlobalLitellmHeaderName } from "@/components/networking"; -import { Organization } from "@/components/networking"; -import GuardrailsPanel from "@/components/guardrails"; -import AgentsPanel from "@/components/agents"; -import PromptsPanel from "@/components/prompts"; -import TransformRequestPanel from "@/components/transform_request"; -import { fetchUserModels } from "@/components/organisms/create_key_button"; -import { fetchTeams } from "@/components/common_components/fetch_teams"; -import { MCPServers } from "@/components/mcp_tools"; -import TagManagement from "@/components/tag_management"; -import VectorStoreManagement from "@/components/vector_store_management"; -import UIThemeSettings from "@/components/ui_theme_settings"; -import { CostTrackingSettings } from "@/components/CostTrackingSettings"; -import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; -import { cx } from "@/lib/cva.config"; import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider"; +import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView"; +import PlaygroundPage from "@/app/(dashboard)/playground/page"; +import AdminPanel from "@/components/admins"; +import AgentsPanel from "@/components/agents"; +import BudgetPanel from "@/components/budgets/budget_panel"; +import CacheDashboard from "@/components/cache_dashboard"; +import { fetchTeams } from "@/components/common_components/fetch_teams"; +import LoadingScreen from "@/components/common_components/LoadingScreen"; +import { CostTrackingSettings } from "@/components/CostTrackingSettings"; +import GeneralSettings from "@/components/general_settings"; +import GuardrailsPanel from "@/components/guardrails"; +import { Team } from "@/components/key_team_helpers/key_list"; +import { MCPServers } from "@/components/mcp_tools"; +import ModelHubTable from "@/components/model_hub_table"; +import Navbar from "@/components/navbar"; +import { getUiConfig, Organization, proxyBaseUrl, setGlobalLitellmHeaderName } from "@/components/networking"; +import NewUsagePage from "@/components/new_usage"; import OldTeams from "@/components/OldTeams"; +import { fetchUserModels } from "@/components/organisms/create_key_button"; +import Organizations, { fetchOrganizations } from "@/components/organizations"; +import PassThroughSettings from "@/components/pass_through_settings"; +import PromptsPanel from "@/components/prompts"; +import PublicModelHub from "@/components/public_model_hub"; import { SearchTools } from "@/components/search_tools"; +import Settings from "@/components/settings"; +import TagManagement from "@/components/tag_management"; +import TransformRequestPanel from "@/components/transform_request"; +import UIThemeSettings from "@/components/ui_theme_settings"; +import Usage from "@/components/usage"; +import UserDashboard from "@/components/user_dashboard"; +import VectorStoreManagement from "@/components/vector_store_management"; +import SpendLogsTable from "@/components/view_logs"; +import ViewUserDashboard from "@/components/view_users"; +import { ThemeProvider } from "@/contexts/ThemeContext"; +import { isJwtExpired } from "@/utils/jwtUtils"; import { isAdminRole } from "@/utils/roles"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { jwtDecode } from "jwt-decode"; +import { useSearchParams } from "next/navigation"; +import { Suspense, useEffect, useState } from "react"; function getCookie(name: string) { // Safer cookie read + decoding; handles '=' inside values @@ -62,19 +60,6 @@ function deleteCookie(name: string, path = "/") { document.cookie = `${name}=; Max-Age=0; Path=${path}`; } -function isJwtExpired(token: string): boolean { - try { - const decoded: any = jwtDecode(token); - if (decoded && typeof decoded.exp === "number") { - return decoded.exp * 1000 <= Date.now(); - } - return false; - } catch { - // If we can't decode, treat as invalid/expired - return true; - } -} - function formatUserRole(userRole: string) { if (!userRole) { return "Undefined Role"; @@ -112,19 +97,6 @@ interface ProxySettings { const queryClient = new QueryClient(); -function LoadingScreen() { - return ( -
    -
    🚅 LiteLLM
    - -
    - - Loading... -
    -
    - ); -} - export default function CreateKeyPage() { const [userRole, setUserRole] = useState(""); const [premiumUser, setPremiumUser] = useState(false); diff --git a/ui/litellm-dashboard/src/components/common_components/LoadingScreen.test.tsx b/ui/litellm-dashboard/src/components/common_components/LoadingScreen.test.tsx new file mode 100644 index 00000000000..5601c53d574 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/LoadingScreen.test.tsx @@ -0,0 +1,10 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import LoadingScreen from "./LoadingScreen"; + +describe("LoadingScreen", () => { + it("should render", () => { + render(); + expect(screen.getByText("Loading...")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/LoadingScreen.tsx b/ui/litellm-dashboard/src/components/common_components/LoadingScreen.tsx new file mode 100644 index 00000000000..6144fa6aad1 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/LoadingScreen.tsx @@ -0,0 +1,15 @@ +import { cx } from "@/lib/cva.config"; +import { UiLoadingSpinner } from "../ui/ui-loading-spinner"; + +export default function LoadingScreen() { + return ( +
    +
    🚅 LiteLLM
    + +
    + + Loading... +
    +
    + ); +} diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 76b79c0f8b8..061d62d6700 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -7980,51 +7980,33 @@ export interface LoginRequest { } export interface LoginResponse { - success: boolean; - redirectUrl?: string; + redirect_url: string; } export const loginCall = async (username: string, password: string): Promise => { const proxyBaseUrl = getProxyBaseUrl(); - const loginUrl = proxyBaseUrl ? `${proxyBaseUrl}/login` : "/login"; + const loginUrl = proxyBaseUrl ? `${proxyBaseUrl}/v2/login` : "/v2/login"; - const formData = new FormData(); - formData.append("username", username); - formData.append("password", password); + const body = JSON.stringify({ + username, + password, + }); const response = await fetch(loginUrl, { method: "POST", - body: formData, + body, credentials: "include", - redirect: "manual", + headers: { + "Content-Type": "application/json", + }, }); - // Handle redirect status codes (301, 302, 303, 307, 308) - if (response.status >= 300 && response.status < 400) { - const redirectUrl = response.headers.get("Location"); - - if (!redirectUrl) { - throw new Error("Login redirect missing Location header"); - } - - return { - success: true, - redirectUrl, - }; + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + throw new Error(errorMessage); } - if (response.ok) { - return { - success: true, - redirectUrl: "/uia/", - }; - } - - // Otherwise, try to extract an error - let errorText = "Invalid username or password"; - try { - errorText = await response.text(); - } catch {} - - throw new Error(errorText || "Login failed"); + const data = await response.json(); + return data; }; diff --git a/ui/litellm-dashboard/src/utils/jwtUtils.test.ts b/ui/litellm-dashboard/src/utils/jwtUtils.test.ts new file mode 100644 index 00000000000..d695a3c37bb --- /dev/null +++ b/ui/litellm-dashboard/src/utils/jwtUtils.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { isJwtExpired } from "./jwtUtils"; +import { jwtDecode } from "jwt-decode"; + +vi.mock("jwt-decode"); + +describe("jwtUtils", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should return true if the token is expired", () => { + const mockDateNow = 1716838401000; + vi.spyOn(Date, "now").mockReturnValue(mockDateNow); + vi.mocked(jwtDecode).mockReturnValue({ + exp: Math.floor(mockDateNow / 1000) - 1, + user_id: "test", + }); + + expect(isJwtExpired("any-token")).toBe(true); + }); + + it("should return false if the token is not expired", () => { + const mockDateNow = 1716838401000; + vi.spyOn(Date, "now").mockReturnValue(mockDateNow); + vi.mocked(jwtDecode).mockReturnValue({ + exp: Math.floor(mockDateNow / 1000) + 1000, + user_id: "test", + }); + + expect(isJwtExpired("any-token")).toBe(false); + }); + + it("should return false if the token does not have an exp field", () => { + vi.mocked(jwtDecode).mockReturnValue({ + user_id: "test", + }); + + expect(isJwtExpired("any-token")).toBe(false); + }); + + it("should return true if jwtDecode throws an error", () => { + vi.mocked(jwtDecode).mockImplementation(() => { + throw new Error("Invalid token"); + }); + + expect(isJwtExpired("invalid-token")).toBe(true); + }); +}); diff --git a/ui/litellm-dashboard/src/utils/jwtUtils.ts b/ui/litellm-dashboard/src/utils/jwtUtils.ts new file mode 100644 index 00000000000..d3db41a411a --- /dev/null +++ b/ui/litellm-dashboard/src/utils/jwtUtils.ts @@ -0,0 +1,14 @@ +import { jwtDecode } from "jwt-decode"; + +export function isJwtExpired(token: string): boolean { + try { + const decoded: any = jwtDecode(token); + if (decoded && typeof decoded.exp === "number") { + return decoded.exp * 1000 <= Date.now(); + } + return false; + } catch { + // If we can't decode, treat as invalid/expired + return true; + } +} From 43da0793d34840fcdaedc5c18a1b97797e33f369 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 15:13:19 -0800 Subject: [PATCH 308/349] Fixed typo --- ui/litellm-dashboard/src/app/login/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/app/login/page.tsx b/ui/litellm-dashboard/src/app/login/page.tsx index 6248639b063..bd24240a6cf 100644 --- a/ui/litellm-dashboard/src/app/login/page.tsx +++ b/ui/litellm-dashboard/src/app/login/page.tsx @@ -133,7 +133,7 @@ function LoginPageContent() { block size="large" > - {isLoginLoading ? "Loggin in..." : "Login"} + {isLoginLoading ? "Logging in..." : "Login"} From 462d423d86ce424b887f599d2b6427099936831e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:19:34 -0800 Subject: [PATCH 309/349] Bump mcp from 1.10.1 to 1.23.0 in /.circleci (#17363) Bumps [mcp](https://github.com/modelcontextprotocol/python-sdk) from 1.10.1 to 1.23.0. - [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases) - [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md) - [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.10.1...v1.23.0) --- updated-dependencies: - dependency-name: mcp dependency-version: 1.23.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .circleci/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt index 2294c84813c..6f1043f59a5 100644 --- a/.circleci/requirements.txt +++ b/.circleci/requirements.txt @@ -13,7 +13,7 @@ google-cloud-aiplatform==1.43.0 google-cloud-iam==2.19.1 fastapi-sso==0.16.0 uvloop==0.21.0 -mcp==1.10.1 # for MCP server +mcp==1.23.0 # for MCP server semantic_router==0.1.10 # for auto-routing with litellm fastuuid==0.12.0 responses==0.25.7 # for proxy client tests \ No newline at end of file From de4b79851ffc09e7140ef6e125fdc7b00e2580b6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 15:26:25 -0800 Subject: [PATCH 310/349] Change e2e test to look for Virtual Keys instead of API Keys --- .../e2e_ui_tests/view_internal_user.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts index 1d263e50511..e578ab57e3f 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts @@ -34,10 +34,10 @@ test("view internal user page", async ({ page }) => { // The UI renders badges in each row - we just verify the column structure exists const rowCount = await page.locator("tbody tr").count(); expect(rowCount).toBeGreaterThan(0); - - // Verify table headers are present (including API Keys column) - const apiKeysHeader = page.locator("th", { hasText: "API Keys" }); - await expect(apiKeysHeader).toBeVisible(); + + // Verify table headers are present (including Virtual Keys column) + const virtualKeysHeader = page.locator("th", { hasText: "Virtual Keys" }); + await expect(virtualKeysHeader).toBeVisible(); // test pagination // Wait for pagination controls to be visible From 37c598441f6906e62b40c8aebacff1ed51d50c0f Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 15:48:50 -0800 Subject: [PATCH 311/349] Change is_sso_configured to auto_redirect_to_sso --- docs/my-website/docs/proxy/config_settings.md | 1 + .../ui_discovery_endpoints.py | 6 +- .../ui_discovery_endpoints.py | 2 +- .../test_ui_discovery_endpoints.py | 78 ++++++++++++++++--- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 82955dadb53..b71e100e157 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -377,6 +377,7 @@ router_settings: | ATHINA_API_KEY | API key for Athina service | ATHINA_BASE_URL | Base URL for Athina service (defaults to `https://log.athina.ai`) | AUTH_STRATEGY | Strategy used for authentication (e.g., OAuth, API key) +| AUTO_REDIRECT_UI_LOGIN_TO_SSO | Flag to enable automatic redirect of UI login page to SSO when SSO is configured. Default is **true** | ANTHROPIC_API_KEY | API key for Anthropic service | ANTHROPIC_API_BASE | Base URL for Anthropic API. Default is https://api.anthropic.com | AWS_ACCESS_KEY_ID | Access Key ID for AWS services diff --git a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py index b297634d6be..9aaa2fb8381 100644 --- a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -1,4 +1,5 @@ #### Analytics Endpoints ##### +import os from fastapi import APIRouter from litellm.types.proxy.discovery_endpoints.ui_discovery_endpoints import ( @@ -15,8 +16,11 @@ router = APIRouter() async def get_ui_config(): from litellm.proxy.utils import get_proxy_base_url, get_server_root_path from litellm.proxy.auth.auth_utils import _has_user_setup_sso + + auto_redirect_ui_login_to_sso = os.getenv("AUTO_REDIRECT_UI_LOGIN_TO_SSO", "true").lower() == "true" + return UiDiscoveryEndpoints( server_root_path=get_server_root_path(), proxy_base_url=get_proxy_base_url(), - is_sso_configured=_has_user_setup_sso(), + auto_redirect_to_sso=_has_user_setup_sso() and auto_redirect_ui_login_to_sso, ) diff --git a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py index 88f0627f533..f100dd35fa6 100644 --- a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -6,4 +6,4 @@ from pydantic import BaseModel class UiDiscoveryEndpoints(BaseModel): server_root_path: str proxy_base_url: Optional[str] - is_sso_configured: bool + auto_redirect_to_sso: bool diff --git a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py index b6e064823c5..599d5437589 100644 --- a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py +++ b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py @@ -20,7 +20,8 @@ def test_ui_discovery_endpoints_with_defaults(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -28,7 +29,7 @@ def test_ui_discovery_endpoints_with_defaults(): data = response.json() assert data["server_root_path"] == "/" assert data["proxy_base_url"] is None - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False def test_ui_discovery_endpoints_with_custom_server_root_path(): @@ -38,7 +39,8 @@ def test_ui_discovery_endpoints_with_custom_server_root_path(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -46,7 +48,7 @@ def test_ui_discovery_endpoints_with_custom_server_root_path(): data = response.json() assert data["server_root_path"] == "/litellm" assert data["proxy_base_url"] is None - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): @@ -56,7 +58,8 @@ def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/litellm/.well-known/litellm-ui-config") @@ -64,17 +67,18 @@ def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): data = response.json() assert data["server_root_path"] == "/" assert data["proxy_base_url"] == "https://proxy.example.com" - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False -def test_ui_discovery_endpoints_with_sso_configured_when_sso_is_setup(): +def test_ui_discovery_endpoints_with_sso_configured_and_auto_redirect_enabled(): app = FastAPI() app.include_router(router) client = TestClient(app) with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -82,5 +86,61 @@ def test_ui_discovery_endpoints_with_sso_configured_when_sso_is_setup(): data = response.json() assert data["server_root_path"] == "/litellm" assert data["proxy_base_url"] == "https://proxy.example.com" - assert data["is_sso_configured"] is True + assert data["auto_redirect_to_sso"] is True + + +def test_ui_discovery_endpoints_with_sso_configured_but_auto_redirect_disabled(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "false"}, clear=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/litellm" + assert data["proxy_base_url"] == "https://proxy.example.com" + assert data["auto_redirect_to_sso"] is False + + +def test_ui_discovery_endpoints_with_sso_not_configured_but_auto_redirect_enabled(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/" + assert data["proxy_base_url"] is None + assert data["auto_redirect_to_sso"] is False + + +def test_ui_discovery_endpoints_both_routes_return_same_data(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): + + response1 = client.get("/.well-known/litellm-ui-config") + response2 = client.get("/litellm/.well-known/litellm-ui-config") + + assert response1.status_code == 200 + assert response2.status_code == 200 + assert response1.json() == response2.json() From d215576477d613c1f0c4c5c30524b50b2099f087 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 17:07:12 -0800 Subject: [PATCH 312/349] Add auto redirect to SSO to new login page --- litellm/proxy/proxy_server.py | 5 +- .../e2e_ui_tests/login_to_ui.spec.ts | 4 +- .../src/app/login/LoginPage.test.tsx | 172 ++++++++++++++++++ .../src/app/login/LoginPage.tsx | 159 ++++++++++++++++ ui/litellm-dashboard/src/app/login/page.tsx | 153 +--------------- .../src/components/networking.tsx | 1 + 6 files changed, 339 insertions(+), 155 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/login/LoginPage.test.tsx create mode 100644 ui/litellm-dashboard/src/app/login/LoginPage.tsx diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4d971e8ce42..40436e1716b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -555,10 +555,11 @@ else: global_max_parallel_request_retry_timeout_env ) -ui_link = f"{server_root_path}/ui/" +ui_link = f"{server_root_path}/ui/login" +fallback_login_link = f"{server_root_path}/fallback/login" model_hub_link = f"{server_root_path}/ui/model_hub_table" ui_message = ( - f"👉 [```LiteLLM Admin Panel on /ui```]({ui_link}). Create, Edit Keys with SSO" + f"👉 [```LiteLLM Admin Panel on /ui```]({ui_link}). Create, Edit Keys with SSO. Having issues? Try [```Fallback Login```]({fallback_login_link})" ) ui_message += "\n\n💸 [```LiteLLM Model Cost Map```](https://models.litellm.ai/)." diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts index f691389de53..fc4d4653246 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts @@ -10,7 +10,7 @@ import { test, expect } from "@playwright/test"; test("admin login test", async ({ page }) => { // Go to the specified URL - await page.goto("http://localhost:4000/ui"); + await page.goto("http://localhost:4000/ui/login"); // Enter "admin" in the username input field await page.fill('input[name="username"]', "admin"); @@ -19,7 +19,7 @@ test("admin login test", async ({ page }) => { await page.fill('input[name="password"]', "gm"); // Optionally, you can add an assertion to verify the login button is enabled - const loginButton = page.locator('input[type="submit"]'); + const loginButton = page.locator('button[type="submit"]'); await expect(loginButton).toBeEnabled(); // Optionally, you can click the login button to submit the form diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx new file mode 100644 index 00000000000..cce063eceb7 --- /dev/null +++ b/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx @@ -0,0 +1,172 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import LoginPage from "./LoginPage"; + +const mockPush = vi.fn(); +const mockReplace = vi.fn(); + +vi.mock("next/navigation", () => ({ + useRouter: vi.fn(() => ({ + push: mockPush, + replace: mockReplace, + })), +})); + +vi.mock("@/app/(dashboard)/hooks/uiConfig/useUIConfig", () => ({ + useUIConfig: vi.fn(), +})); + +vi.mock("@/utils/cookieUtils", () => ({ + getCookie: vi.fn(), +})); + +vi.mock("@/utils/jwtUtils", () => ({ + isJwtExpired: vi.fn(), +})); + +vi.mock("@/components/networking", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getProxyBaseUrl: vi.fn().mockReturnValue("http://localhost:4000"), + }; +}); + +vi.mock("@/app/(dashboard)/hooks/login/useLogin", () => ({ + useLogin: vi.fn(() => ({ + mutate: vi.fn(), + isPending: false, + error: null, + })), +})); + +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; +import { getCookie } from "@/utils/cookieUtils"; +import { isJwtExpired } from "@/utils/jwtUtils"; + +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }); + +describe("LoginPage", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockPush.mockClear(); + mockReplace.mockClear(); + }); + + it("should render", async () => { + (useUIConfig as ReturnType).mockReturnValue({ + data: { auto_redirect_to_sso: false, server_root_path: "/", proxy_base_url: null }, + isLoading: false, + }); + (getCookie as ReturnType).mockReturnValue(null); + + const queryClient = createQueryClient(); + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByRole("heading", { name: "Login" })).toBeInTheDocument(); + }); + }); + + it("should call router.replace to dashboard when jwt is valid", async () => { + const validToken = "valid-token"; + (useUIConfig as ReturnType).mockReturnValue({ + data: { auto_redirect_to_sso: false, server_root_path: "/", proxy_base_url: null }, + isLoading: false, + }); + (getCookie as ReturnType).mockReturnValue(validToken); + (isJwtExpired as ReturnType).mockReturnValue(false); + + const queryClient = createQueryClient(); + render( + + + , + ); + + await waitFor(() => { + expect(mockReplace).toHaveBeenCalledWith("http://localhost:4000/ui"); + }); + }); + + it("should call router.push to SSO when jwt is invalid and auto_redirect_to_sso is true", async () => { + const invalidToken = "invalid-token"; + (useUIConfig as ReturnType).mockReturnValue({ + data: { auto_redirect_to_sso: true, server_root_path: "/", proxy_base_url: null }, + isLoading: false, + }); + (getCookie as ReturnType).mockReturnValue(invalidToken); + (isJwtExpired as ReturnType).mockReturnValue(true); + + const queryClient = createQueryClient(); + render( + + + , + ); + + await waitFor(() => { + expect(mockPush).toHaveBeenCalledWith("http://localhost:4000/sso/key/generate"); + }); + }); + + it("should not call router when jwt is invalid and auto_redirect_to_sso is false", async () => { + const invalidToken = "invalid-token"; + (useUIConfig as ReturnType).mockReturnValue({ + data: { auto_redirect_to_sso: false, server_root_path: "/", proxy_base_url: null }, + isLoading: false, + }); + (getCookie as ReturnType).mockReturnValue(invalidToken); + (isJwtExpired as ReturnType).mockReturnValue(true); + + const queryClient = createQueryClient(); + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByRole("heading", { name: "Login" })).toBeInTheDocument(); + }); + + expect(mockPush).not.toHaveBeenCalled(); + expect(mockReplace).not.toHaveBeenCalled(); + }); + + it("should send user to dashboard when jwt is valid even if auto_redirect_to_sso is true", async () => { + const validToken = "valid-token"; + (useUIConfig as ReturnType).mockReturnValue({ + data: { auto_redirect_to_sso: true, server_root_path: "/", proxy_base_url: null }, + isLoading: false, + }); + (getCookie as ReturnType).mockReturnValue(validToken); + (isJwtExpired as ReturnType).mockReturnValue(false); + + const queryClient = createQueryClient(); + render( + + + , + ); + + await waitFor(() => { + expect(mockReplace).toHaveBeenCalledWith("http://localhost:4000/ui"); + }); + + expect(mockPush).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.tsx new file mode 100644 index 00000000000..85f2c6dd870 --- /dev/null +++ b/ui/litellm-dashboard/src/app/login/LoginPage.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { useLogin } from "@/app/(dashboard)/hooks/login/useLogin"; +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; +import LoadingScreen from "@/components/common_components/LoadingScreen"; +import { getProxyBaseUrl } from "@/components/networking"; +import { getCookie } from "@/utils/cookieUtils"; +import { isJwtExpired } from "@/utils/jwtUtils"; +import { InfoCircleOutlined } from "@ant-design/icons"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Alert, Button, Card, Form, Input, Space, Typography } from "antd"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; + +function LoginPageContent() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const { data: uiConfig, isLoading: isConfigLoading } = useUIConfig(); + const loginMutation = useLogin(); + const router = useRouter(); + + useEffect(() => { + if (isConfigLoading) { + return; + } + + const rawToken = getCookie("token"); + if (rawToken && !isJwtExpired(rawToken)) { + router.replace(`${getProxyBaseUrl()}/ui`); + return; + } + + if (uiConfig && uiConfig.auto_redirect_to_sso) { + router.push(`${getProxyBaseUrl()}/sso/key/generate`); + return; + } + + setIsLoading(false); + }, [isConfigLoading, router, uiConfig]); + + const handleSubmit = () => { + loginMutation.mutate( + { username, password }, + { + onSuccess: (data) => { + router.push(data.redirect_url); + }, + }, + ); + }; + + const error = loginMutation.error instanceof Error ? loginMutation.error.message : null; + const isLoginLoading = loginMutation.isPending; + + const { Title, Text, Paragraph } = Typography; + + if (isConfigLoading || isLoading) { + return ; + } + + return ( +
    + + +
    + 🚅 LiteLLM +
    + +
    + Login + Access your LiteLLM Admin UI. +
    + + + + By default, Username is admin and + Password is your set LiteLLM Proxy + MASTER_KEY. + + + Need to set UI credentials or SSO?{" "} + + Check the documentation + + . + + + } + type="info" + icon={} + showIcon + /> + + {error && } + +
    + + setUsername(e.target.value)} + disabled={isLoginLoading} + size="large" + className="rounded-md border-gray-300" + /> + + + + setPassword(e.target.value)} + disabled={isLoginLoading} + size="large" + /> + + + + + +
    +
    +
    +
    + ); +} + +export default function LoginPage() { + const queryClient = new QueryClient(); + + return ( + + + + ); +} diff --git a/ui/litellm-dashboard/src/app/login/page.tsx b/ui/litellm-dashboard/src/app/login/page.tsx index bd24240a6cf..a539f87ee5e 100644 --- a/ui/litellm-dashboard/src/app/login/page.tsx +++ b/ui/litellm-dashboard/src/app/login/page.tsx @@ -1,154 +1,5 @@ "use client"; -import { useLogin } from "@/app/(dashboard)/hooks/login/useLogin"; -import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; -import LoadingScreen from "@/components/common_components/LoadingScreen"; -import { getProxyBaseUrl } from "@/components/networking"; -import { getCookie } from "@/utils/cookieUtils"; -import { isJwtExpired } from "@/utils/jwtUtils"; -import { InfoCircleOutlined } from "@ant-design/icons"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { Alert, Button, Card, Form, Input, Space, Typography } from "antd"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import LoginPage from "./LoginPage"; -function LoginPageContent() { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const { isLoading: isConfigLoading } = useUIConfig(); - const loginMutation = useLogin(); - const router = useRouter(); - - useEffect(() => { - if (isConfigLoading) { - return; - } - - const rawToken = getCookie("token"); - if (rawToken && !isJwtExpired(rawToken)) { - router.replace(`${getProxyBaseUrl()}/ui`); - return; - } - - setIsLoading(false); - }, [isConfigLoading, router]); - - const handleSubmit = () => { - loginMutation.mutate( - { username, password }, - { - onSuccess: (data) => { - router.push(data.redirect_url); - }, - }, - ); - }; - - const error = loginMutation.error instanceof Error ? loginMutation.error.message : null; - const isLoginLoading = loginMutation.isPending; - - const { Title, Text, Paragraph } = Typography; - - if (isConfigLoading || isLoading) { - return ; - } - - return ( -
    - - -
    - 🚅 LiteLLM -
    - -
    - Login - Access your LiteLLM Admin UI. -
    - - - - By default, Username is admin and - Password is your set LiteLLM Proxy - MASTER_KEY. - - - Need to set UI credentials or SSO?{" "} - - Check the documentation - - . - - - } - type="info" - icon={} - showIcon - /> - - {error && } - -
    - - setUsername(e.target.value)} - disabled={isLoginLoading} - size="large" - className="rounded-md border-gray-300" - /> - - - - setPassword(e.target.value)} - disabled={isLoginLoading} - size="large" - /> - - - - - -
    -
    -
    -
    - ); -} - -export default function LoginPage() { - const queryClient = new QueryClient(); - - return ( - - - - ); -} +export default LoginPage; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 061d62d6700..0e45c0f3a91 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -205,6 +205,7 @@ export interface PublicModelHubInfo { export interface LiteLLMWellKnownUiConfig { server_root_path: string; proxy_base_url: string | null; + auto_redirect_to_sso: boolean; } export interface CredentialsResponse { From f035984dd79082bd0bc07154615606be3594e32d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 18:54:31 -0800 Subject: [PATCH 313/349] fix: cyberark allow setting ssl verfiy to false (#17433) --- .../docs/secret_managers/cyberark.md | 19 +++++++++++ .../cyberark_secret_manager.py | 33 ++++++++++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/docs/my-website/docs/secret_managers/cyberark.md b/docs/my-website/docs/secret_managers/cyberark.md index 37aa1086691..c33aa286703 100644 --- a/docs/my-website/docs/secret_managers/cyberark.md +++ b/docs/my-website/docs/secret_managers/cyberark.md @@ -41,6 +41,7 @@ CYBERARK_CLIENT_KEY="path/to/client.key" # OPTIONAL CYBERARK_REFRESH_INTERVAL="300" # defaults to 300 seconds (5 minutes), frequency of token refresh +CYBERARK_SSL_VERIFY="true" # defaults to true, set to "false" to disable SSL verification (for self-signed certificates) ``` **Step 2.** Add to proxy config.yaml @@ -172,6 +173,24 @@ If these commands work successfully against your CyberArk instance, then CyberAr - The `CYBERARK_API_BASE` URL is accessible from your LiteLLM instance - Your API key or certificates have the necessary permissions in CyberArk +### SSL Certificate Errors + +If you encounter SSL certificate verification errors like: + +``` +RuntimeError: Could not authenticate to CyberArk Conjur: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain +``` + +This typically occurs when your CyberArk Conjur instance uses a self-signed certificate. You can disable SSL verification by setting: + +```bash +CYBERARK_SSL_VERIFY="false" +``` + +:::warning +Disabling SSL verification is insecure and should only be used for testing or development environments with self-signed certificates. For production, configure your certificate chain properly or use certificate-based authentication with `CYBERARK_CLIENT_CERT` and `CYBERARK_CLIENT_KEY`. +::: + ## Video Walkthrough This video walks through using CyberArk Conjur as a secret manager with LiteLLM. We create a virtual key in the LiteLLM Admin UI and verify it exists in CyberArk. Then we rotate the secret key and verify it exists in CyberArk. diff --git a/litellm/secret_managers/cyberark_secret_manager.py b/litellm/secret_managers/cyberark_secret_manager.py index 5f1e8b5cd07..2745df00778 100644 --- a/litellm/secret_managers/cyberark_secret_manager.py +++ b/litellm/secret_managers/cyberark_secret_manager.py @@ -16,6 +16,7 @@ from litellm.llms.custom_httpx.http_handler import ( from litellm.proxy._types import KeyManagementSystem from .base_secret_manager import BaseSecretManager +from .main import str_to_bool class CyberArkSecretManager(BaseSecretManager): @@ -32,6 +33,11 @@ class CyberArkSecretManager(BaseSecretManager): self.tls_cert_path = os.getenv("CYBERARK_CLIENT_CERT", "") self.tls_key_path = os.getenv("CYBERARK_CLIENT_KEY", "") + # SSL verification - can be disabled for self-signed certificates + # Set CYBERARK_SSL_VERIFY=false to disable SSL verification + ssl_verify_env = str_to_bool(os.getenv("CYBERARK_SSL_VERIFY")) + self.ssl_verify: bool = ssl_verify_env if ssl_verify_env is not None else True + # Validate environment if not self.conjur_api_key and not ( self.tls_cert_path and self.tls_key_path @@ -52,6 +58,11 @@ class CyberArkSecretManager(BaseSecretManager): f"CyberArk secret manager is only available for premium users. {CommonProxyErrors.not_premium_user.value}" ) + if not self.ssl_verify: + verbose_logger.warning( + "CyberArk SSL verification is disabled. This is insecure and should only be used for testing with self-signed certificates." + ) + def _authenticate(self) -> str: """ Authenticate with CyberArk Conjur and get a session token. @@ -71,13 +82,16 @@ class CyberArkSecretManager(BaseSecretManager): try: if self.tls_cert_path and self.tls_key_path: - # Certificate-based authentication - http_client = httpx.Client(cert=(self.tls_cert_path, self.tls_key_path)) + # Certificate-based authentication - need custom client for cert + http_client = httpx.Client( + cert=(self.tls_cert_path, self.tls_key_path), + verify=self.ssl_verify, + ) resp = http_client.post(auth_url, content=self.conjur_api_key) else: # API key authentication - http_handler = _get_httpx_client() - resp = http_handler.post(auth_url, content=self.conjur_api_key) + http_handler = _get_httpx_client(params={"ssl_verify": self.ssl_verify}) + resp = http_handler.client.post(auth_url, content=self.conjur_api_key) resp.raise_for_status() @@ -117,8 +131,8 @@ class CyberArkSecretManager(BaseSecretManager): policy_yaml = f"- !variable {secret_name}\n" try: - client = _get_httpx_client() - resp = client.post( + client = _get_httpx_client(params={"ssl_verify": self.ssl_verify}) + resp = client.client.post( policy_url, headers={ **self._get_request_headers(), @@ -180,6 +194,7 @@ class CyberArkSecretManager(BaseSecretManager): async_client = get_async_httpx_client( llm_provider=httpxSpecialProvider.SecretManager, + params={"ssl_verify": self.ssl_verify}, ) try: @@ -227,11 +242,11 @@ class CyberArkSecretManager(BaseSecretManager): if self.cache.get_cache(secret_name) is not None: return self.cache.get_cache(secret_name) - sync_client = _get_httpx_client() + sync_client = _get_httpx_client(params={"ssl_verify": self.ssl_verify}) try: url = self.get_url(secret_name) - response = sync_client.get(url, headers=self._get_request_headers()) + response = sync_client.client.get(url, headers=self._get_request_headers()) response.raise_for_status() # CyberArk Conjur returns the raw secret value as text @@ -278,7 +293,7 @@ class CyberArkSecretManager(BaseSecretManager): """ async_client = get_async_httpx_client( llm_provider=httpxSpecialProvider.SecretManager, - params={"timeout": timeout}, + params={"ssl_verify": self.ssl_verify}, ) try: From 4370f6fb74aa2d51d32c542e0314f078b65ec75e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 18:54:55 -0800 Subject: [PATCH 314/349] [Feat] Agent Gateway - Allow invoking agents through AI Gateway (#17440) * init litellm A2a client * simpler a2a client interface * test a2a * move a2a invoking tests * test fix * ensure a2a send message is tracked n logs * rename tags * add streaming handlng * add a2a invocation --- .../proxy/vector_stores/endpoints.py | 337 ++++++++++++++++++ litellm/a2a/__init__.py | 59 +++ litellm/a2a/client.py | 107 ++++++ litellm/a2a/main.py | 261 ++++++++++++++ litellm/litellm_core_utils/litellm_logging.py | 2 + .../proxy/agent_endpoints/a2a_endpoints.py | 189 ++++++++++ litellm/proxy/agent_endpoints/endpoints.py | 14 +- litellm/proxy/proxy_server.py | 2 + litellm/types/agents.py | 47 ++- litellm/types/llms/custom_http.py | 1 + tests/agent_tests/test_a2a.py | 138 +++++++ 11 files changed, 1148 insertions(+), 9 deletions(-) create mode 100644 enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py create mode 100644 litellm/a2a/__init__.py create mode 100644 litellm/a2a/client.py create mode 100644 litellm/a2a/main.py create mode 100644 litellm/proxy/agent_endpoints/a2a_endpoints.py create mode 100644 tests/agent_tests/test_a2a.py diff --git a/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py b/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py new file mode 100644 index 00000000000..fdb1dba372f --- /dev/null +++ b/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py @@ -0,0 +1,337 @@ +""" +VECTOR STORE MANAGEMENT + +All /vector_store management endpoints + +/vector_store/new +/vector_store/delete +/vector_store/list +""" + +import copy +import json +from typing import List, Optional + +from fastapi import APIRouter, Depends, HTTPException + +import litellm +from litellm._logging import verbose_proxy_logger +from litellm.litellm_core_utils.safe_json_dumps import safe_dumps +from litellm.proxy._types import ( + LiteLLM_ManagedVectorStoresTable, + ResponseLiteLLM_ManagedVectorStore, + UserAPIKeyAuth, +) +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.types.vector_stores import ( + LiteLLM_ManagedVectorStore, + LiteLLM_ManagedVectorStoreListResponse, + VectorStoreDeleteRequest, + VectorStoreInfoRequest, + VectorStoreUpdateRequest, +) +from litellm.vector_stores.vector_store_registry import VectorStoreRegistry + +router = APIRouter() + + +######################################################## +# Management Endpoints +######################################################## +@router.post( + "/vector_store/new", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], +) +async def new_vector_store( + vector_store: LiteLLM_ManagedVectorStore, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Create a new vector store. + + Parameters: + - vector_store_id: str - Unique identifier for the vector store + - custom_llm_provider: str - Provider of the vector store + - vector_store_name: Optional[str] - Name of the vector store + - vector_store_description: Optional[str] - Description of the vector store + - vector_store_metadata: Optional[Dict] - Additional metadata for the vector store + """ + from litellm.proxy.proxy_server import prisma_client + from litellm.types.router import GenericLiteLLMParams + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Database not connected") + + try: + # Check if vector store already exists + existing_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.find_unique( + where={"vector_store_id": vector_store.get("vector_store_id")} + ) + ) + if existing_vector_store is not None: + raise HTTPException( + status_code=400, + detail=f"Vector store with ID {vector_store.get('vector_store_id')} already exists", + ) + + if vector_store.get("vector_store_metadata") is not None: + vector_store["vector_store_metadata"] = safe_dumps( + vector_store.get("vector_store_metadata") + ) + + # Safely handle JSON serialization of litellm_params + litellm_params_json: Optional[str] = None + _input_litellm_params: dict = vector_store.get("litellm_params", {}) or {} + if _input_litellm_params is not None: + litellm_params_dict = GenericLiteLLMParams( + **_input_litellm_params + ).model_dump(exclude_none=True) + litellm_params_json = safe_dumps(litellm_params_dict) + del vector_store["litellm_params"] + + _new_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.create( + data={ + **vector_store, + "litellm_params": litellm_params_json, + } + ) + ) + + new_vector_store: LiteLLM_ManagedVectorStore = LiteLLM_ManagedVectorStore( + **_new_vector_store.model_dump() + ) + + # Add vector store to registry + if litellm.vector_store_registry is not None: + litellm.vector_store_registry.add_vector_store_to_registry( + vector_store=new_vector_store + ) + + return { + "status": "success", + "message": f"Vector store {vector_store.get('vector_store_id')} created successfully", + "vector_store": new_vector_store, + } + except Exception as e: + verbose_proxy_logger.exception(f"Error creating vector store: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get( + "/vector_store/list", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], + response_model=LiteLLM_ManagedVectorStoreListResponse, +) +async def list_vector_stores( + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + page: int = 1, + page_size: int = 100, +): + """ + List all available vector stores with optional filtering and pagination. + Combines both in-memory vector stores and those stored in the database. + + Parameters: + - page: int - Page number for pagination (default: 1) + - page_size: int - Number of items per page (default: 100) + """ + from litellm.proxy.proxy_server import prisma_client + + seen_vector_store_ids = set() + + try: + # Get in-memory vector stores + in_memory_vector_stores: List[LiteLLM_ManagedVectorStore] = [] + if litellm.vector_store_registry is not None: + in_memory_vector_stores = copy.deepcopy( + litellm.vector_store_registry.vector_stores + ) + + # Get vector stores from database + vector_stores_from_db = await VectorStoreRegistry._get_vector_stores_from_db( + prisma_client=prisma_client + ) + + # Combine in-memory and database vector stores + combined_vector_stores: List[LiteLLM_ManagedVectorStore] = [] + for vector_store in in_memory_vector_stores + vector_stores_from_db: + vector_store_id = vector_store.get("vector_store_id", None) + if vector_store_id not in seen_vector_store_ids: + combined_vector_stores.append(vector_store) + seen_vector_store_ids.add(vector_store_id) + + total_count = len(combined_vector_stores) + total_pages = (total_count + page_size - 1) // page_size + + # Format response using LiteLLM_ManagedVectorStoreListResponse + response = LiteLLM_ManagedVectorStoreListResponse( + object="list", + data=combined_vector_stores, + total_count=total_count, + current_page=page, + total_pages=total_pages, + ) + + return response + except Exception as e: + verbose_proxy_logger.exception(f"Error listing vector stores: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/vector_store/delete", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], +) +async def delete_vector_store( + data: VectorStoreDeleteRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Delete a vector store. + + Parameters: + - vector_store_id: str - ID of the vector store to delete + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Database not connected") + + try: + # Check if vector store exists + existing_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.find_unique( + where={"vector_store_id": data.vector_store_id} + ) + ) + if existing_vector_store is None: + raise HTTPException( + status_code=404, + detail=f"Vector store with ID {data.vector_store_id} not found", + ) + + # Delete vector store + await prisma_client.db.litellm_managedvectorstorestable.delete( + where={"vector_store_id": data.vector_store_id} + ) + + # Delete vector store from registry + if litellm.vector_store_registry is not None: + litellm.vector_store_registry.delete_vector_store_from_registry( + vector_store_id=data.vector_store_id + ) + + return {"message": f"Vector store {data.vector_store_id} deleted successfully"} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/vector_store/info", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], + response_model=ResponseLiteLLM_ManagedVectorStore, +) +async def get_vector_store_info( + data: VectorStoreInfoRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """Return a single vector store's details""" + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Database not connected") + + try: + if litellm.vector_store_registry is not None: + vector_store = litellm.vector_store_registry.get_litellm_managed_vector_store_from_registry( + vector_store_id=data.vector_store_id + ) + if vector_store is not None: + vector_store_metadata = vector_store.get("vector_store_metadata") + # Parse metadata if it's a JSON string + parsed_metadata: Optional[dict] = None + if isinstance(vector_store_metadata, str): + parsed_metadata = json.loads(vector_store_metadata) + elif isinstance(vector_store_metadata, dict): + parsed_metadata = vector_store_metadata + + vector_store_pydantic_obj = LiteLLM_ManagedVectorStoresTable( + vector_store_id=vector_store.get("vector_store_id") or "", + custom_llm_provider=vector_store.get("custom_llm_provider") or "", + vector_store_name=vector_store.get("vector_store_name") or None, + vector_store_description=vector_store.get( + "vector_store_description" + ) + or None, + vector_store_metadata=parsed_metadata, + created_at=vector_store.get("created_at") or None, + updated_at=vector_store.get("updated_at") or None, + litellm_credential_name=vector_store.get("litellm_credential_name"), + litellm_params=vector_store.get("litellm_params") or None, + ) + return {"vector_store": vector_store_pydantic_obj} + + vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.find_unique( + where={"vector_store_id": data.vector_store_id} + ) + ) + if vector_store is None: + raise HTTPException( + status_code=404, + detail=f"Vector store with ID {data.vector_store_id} not found", + ) + + vector_store_dict = vector_store.model_dump() # type: ignore[attr-defined] + return {"vector_store": vector_store_dict} + except Exception as e: + verbose_proxy_logger.exception(f"Error getting vector store info: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/vector_store/update", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], +) +async def update_vector_store( + data: VectorStoreUpdateRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """Update vector store details""" + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Database not connected") + + try: + update_data = data.model_dump(exclude_unset=True) + vector_store_id = update_data.pop("vector_store_id") + if update_data.get("vector_store_metadata") is not None: + update_data["vector_store_metadata"] = safe_dumps( + update_data["vector_store_metadata"] + ) + + updated = await prisma_client.db.litellm_managedvectorstorestable.update( + where={"vector_store_id": vector_store_id}, + data=update_data, + ) + + updated_vs = LiteLLM_ManagedVectorStore(**updated.model_dump()) + + if litellm.vector_store_registry is not None: + litellm.vector_store_registry.update_vector_store_in_registry( + vector_store_id=vector_store_id, + updated_data=updated_vs, + ) + + return {"vector_store": updated_vs} + except Exception as e: + verbose_proxy_logger.exception(f"Error updating vector store: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/litellm/a2a/__init__.py b/litellm/a2a/__init__.py new file mode 100644 index 00000000000..a2e9770cd20 --- /dev/null +++ b/litellm/a2a/__init__.py @@ -0,0 +1,59 @@ +""" +LiteLLM A2A - Wrapper for invoking A2A protocol agents. + +This module provides a thin wrapper around the official `a2a` SDK that: +- Handles httpx client creation and agent card resolution +- Adds LiteLLM logging via @client decorator +- Matches the A2A SDK interface (SendMessageRequest, SendMessageResponse, etc.) + +Example usage (standalone functions with @client decorator): + ```python + from litellm.a2a import asend_message + from a2a.types import SendMessageRequest, MessageSendParams + from uuid import uuid4 + + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams( + message={ + "role": "user", + "parts": [{"kind": "text", "text": "Hello!"}], + "messageId": uuid4().hex, + } + ) + ) + response = await asend_message( + base_url="http://localhost:10001", + request=request, + ) + print(response.model_dump(mode='json', exclude_none=True)) + ``` + +Example usage (class-based): + ```python + from litellm.a2a import A2AClient + + client = A2AClient(base_url="http://localhost:10001") + response = await client.send_message(request) + ``` +""" + +from litellm.a2a.client import A2AClient +from litellm.a2a.main import ( + aget_agent_card, + asend_message, + asend_message_streaming, + create_a2a_client, + send_message, +) +from litellm.types.agents import LiteLLMSendMessageResponse + +__all__ = [ + "A2AClient", + "asend_message", + "send_message", + "asend_message_streaming", + "aget_agent_card", + "create_a2a_client", + "LiteLLMSendMessageResponse", +] diff --git a/litellm/a2a/client.py b/litellm/a2a/client.py new file mode 100644 index 00000000000..0c3b8fff2ba --- /dev/null +++ b/litellm/a2a/client.py @@ -0,0 +1,107 @@ +""" +LiteLLM A2A Client class. + +Provides a class-based interface for A2A agent invocation. +""" + +from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional + +from litellm.types.agents import LiteLLMSendMessageResponse + +if TYPE_CHECKING: + from a2a.client import A2AClient as A2AClientType + from a2a.types import ( + AgentCard, + SendMessageRequest, + SendStreamingMessageRequest, + SendStreamingMessageResponse, + ) + + +class A2AClient: + """ + LiteLLM wrapper for A2A agent invocation. + + Creates the underlying A2A client once on first use and reuses it. + + Example: + ```python + from litellm.a2a import A2AClient + from a2a.types import SendMessageRequest, MessageSendParams + from uuid import uuid4 + + client = A2AClient(base_url="http://localhost:10001") + + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams( + message={ + "role": "user", + "parts": [{"kind": "text", "text": "Hello!"}], + "messageId": uuid4().hex, + } + ) + ) + response = await client.send_message(request) + ``` + """ + + def __init__( + self, + base_url: str, + timeout: float = 60.0, + extra_headers: Optional[Dict[str, str]] = None, + ): + """ + Initialize the A2A client wrapper. + + Args: + base_url: The base URL of the A2A agent (e.g., "http://localhost:10001") + timeout: Request timeout in seconds (default: 60.0) + extra_headers: Optional additional headers to include in requests + """ + self.base_url = base_url + self.timeout = timeout + self.extra_headers = extra_headers + self._a2a_client: Optional["A2AClientType"] = None + + async def _get_client(self) -> "A2AClientType": + """Get or create the underlying A2A client.""" + if self._a2a_client is None: + from litellm.a2a.main import create_a2a_client + + self._a2a_client = await create_a2a_client( + base_url=self.base_url, + timeout=self.timeout, + extra_headers=self.extra_headers, + ) + return self._a2a_client + + async def get_agent_card(self) -> "AgentCard": + """Fetch the agent card from the server.""" + from litellm.a2a.main import aget_agent_card + + return await aget_agent_card( + base_url=self.base_url, + timeout=self.timeout, + extra_headers=self.extra_headers, + ) + + async def send_message( + self, request: "SendMessageRequest" + ) -> LiteLLMSendMessageResponse: + """Send a message to the A2A agent.""" + from litellm.a2a.main import asend_message + + a2a_client = await self._get_client() + return await asend_message(a2a_client=a2a_client, request=request) + + async def send_message_streaming( + self, request: "SendStreamingMessageRequest" + ) -> AsyncIterator["SendStreamingMessageResponse"]: + """Send a streaming message to the A2A agent.""" + from litellm.a2a.main import asend_message_streaming + + a2a_client = await self._get_client() + async for chunk in asend_message_streaming(a2a_client=a2a_client, request=request): + yield chunk diff --git a/litellm/a2a/main.py b/litellm/a2a/main.py new file mode 100644 index 00000000000..7e293dec81e --- /dev/null +++ b/litellm/a2a/main.py @@ -0,0 +1,261 @@ +""" +LiteLLM A2A SDK functions. + +Provides standalone functions with @client decorator for LiteLLM logging integration. +""" + +import asyncio +from typing import TYPE_CHECKING, Any, AsyncIterator, Coroutine, Dict, Optional, Union + +from litellm._logging import verbose_logger +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) +from litellm.types.agents import LiteLLMSendMessageResponse +from litellm.utils import client + +if TYPE_CHECKING: + from a2a.client import A2AClient as A2AClientType + from a2a.types import ( + AgentCard, + SendMessageRequest, + SendStreamingMessageRequest, + SendStreamingMessageResponse, + ) + +# Runtime imports with availability check +A2A_SDK_AVAILABLE = False +A2ACardResolver: Any = None +_A2AClient: Any = None + +try: + from a2a.client import A2ACardResolver # type: ignore[no-redef] + from a2a.client import A2AClient as _A2AClient # type: ignore[no-redef] + + A2A_SDK_AVAILABLE = True +except ImportError: + pass + + +@client +async def asend_message( + a2a_client: "A2AClientType", + request: "SendMessageRequest", + **kwargs: Any, +) -> LiteLLMSendMessageResponse: + """ + Async: Send a message to an A2A agent. + + Uses the @client decorator for LiteLLM logging and tracking. + + Args: + a2a_client: An initialized a2a.client.A2AClient instance + request: SendMessageRequest from a2a.types + **kwargs: Additional arguments passed to the client decorator + + Returns: + LiteLLMSendMessageResponse (wraps a2a SendMessageResponse with _hidden_params) + + Example: + ```python + from litellm.a2a import asend_message, create_a2a_client + from a2a.types import SendMessageRequest, MessageSendParams + from uuid import uuid4 + + # Create client once + a2a_client = await create_a2a_client(base_url="http://localhost:10001") + + # Use it for multiple requests + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams( + message={ + "role": "user", + "parts": [{"kind": "text", "text": "Hello!"}], + "messageId": uuid4().hex, + } + ) + ) + response = await asend_message(a2a_client=a2a_client, request=request) + ``` + """ + verbose_logger.info(f"A2A send_message request_id={request.id}") + + a2a_response = await a2a_client.send_message(request) + + verbose_logger.info(f"A2A send_message completed, request_id={request.id}") + + # Wrap in LiteLLM response type for _hidden_params support + response = LiteLLMSendMessageResponse.from_a2a_response(a2a_response) + + return response + + +@client +def send_message( + a2a_client: "A2AClientType", + request: "SendMessageRequest", + **kwargs: Any, +) -> Union[LiteLLMSendMessageResponse, Coroutine[Any, Any, LiteLLMSendMessageResponse]]: + """ + Sync: Send a message to an A2A agent. + + Uses the @client decorator for LiteLLM logging and tracking. + + Args: + a2a_client: An initialized a2a.client.A2AClient instance + request: SendMessageRequest from a2a.types + **kwargs: Additional arguments passed to the client decorator + + Returns: + LiteLLMSendMessageResponse (wraps a2a SendMessageResponse with _hidden_params) + """ + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + + if loop is not None: + return asend_message(a2a_client=a2a_client, request=request, **kwargs) + else: + return asyncio.run(asend_message(a2a_client=a2a_client, request=request, **kwargs)) + + +async def asend_message_streaming( + a2a_client: "A2AClientType", + request: "SendStreamingMessageRequest", +) -> AsyncIterator["SendStreamingMessageResponse"]: + """ + Async: Send a streaming message to an A2A agent. + + Args: + a2a_client: An initialized a2a.client.A2AClient instance + request: SendStreamingMessageRequest from a2a.types + + Yields: + SendStreamingMessageResponse chunks from the agent + """ + verbose_logger.info(f"A2A send_message_streaming request_id={request.id}") + + stream = a2a_client.send_message_streaming(request) + + chunk_count = 0 + async for chunk in stream: + chunk_count += 1 + yield chunk + + verbose_logger.info( + f"A2A send_message_streaming completed, request_id={request.id}, chunks={chunk_count}" + ) + + +async def create_a2a_client( + base_url: str, + timeout: float = 60.0, + extra_headers: Optional[Dict[str, str]] = None, +) -> "A2AClientType": + """ + Create an A2A client for the given agent URL. + + This resolves the agent card and returns a ready-to-use A2A client. + The client can be reused for multiple requests. + + Args: + base_url: The base URL of the A2A agent (e.g., "http://localhost:10001") + timeout: Request timeout in seconds (default: 60.0) + extra_headers: Optional additional headers to include in requests + + Returns: + An initialized a2a.client.A2AClient instance + + Example: + ```python + from litellm.a2a import create_a2a_client, asend_message + + # Create client once + client = await create_a2a_client(base_url="http://localhost:10001") + + # Reuse for multiple requests + response1 = await asend_message(a2a_client=client, request=request1) + response2 = await asend_message(a2a_client=client, request=request2) + ``` + """ + if not A2A_SDK_AVAILABLE: + raise ImportError( + "The 'a2a' package is required for A2A agent invocation. " + "Install it with: pip install a2a" + ) + + verbose_logger.info(f"Creating A2A client for {base_url}") + + # Use LiteLLM's cached httpx client + http_handler = get_async_httpx_client( + llm_provider=httpxSpecialProvider.A2A, + params={"timeout": timeout}, + ) + httpx_client = http_handler.client + + # Resolve agent card + resolver = A2ACardResolver( + httpx_client=httpx_client, + base_url=base_url, + ) + agent_card = await resolver.get_agent_card() + + verbose_logger.debug( + f"Resolved agent card: {agent_card.name if hasattr(agent_card, 'name') else 'unknown'}" + ) + + # Create and return A2A client + a2a_client = _A2AClient( + httpx_client=httpx_client, + agent_card=agent_card, + ) + + verbose_logger.info(f"A2A client created for {base_url}") + + return a2a_client + + +async def aget_agent_card( + base_url: str, + timeout: float = 60.0, + extra_headers: Optional[Dict[str, str]] = None, +) -> "AgentCard": + """ + Fetch the agent card from an A2A agent. + + Args: + base_url: The base URL of the A2A agent (e.g., "http://localhost:10001") + timeout: Request timeout in seconds (default: 60.0) + extra_headers: Optional additional headers to include in requests + + Returns: + AgentCard from the A2A agent + """ + if not A2A_SDK_AVAILABLE: + raise ImportError( + "The 'a2a' package is required for A2A agent invocation. " + "Install it with: pip install a2a" + ) + + verbose_logger.info(f"Fetching agent card from {base_url}") + + # Use LiteLLM's cached httpx client + http_handler = get_async_httpx_client( + llm_provider=httpxSpecialProvider.A2A, + params={"timeout": timeout}, + ) + httpx_client = http_handler.client + + resolver = A2ACardResolver( + httpx_client=httpx_client, + base_url=base_url, + ) + agent_card = await resolver.get_agent_card() + + verbose_logger.info( + f"Fetched agent card: {agent_card.name if hasattr(agent_card, 'name') else 'unknown'}" + ) + return agent_card diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 0b0f483ff74..4fd2c988797 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -71,6 +71,7 @@ from litellm.litellm_core_utils.redact_messages import ( from litellm.llms.base_llm.ocr.transformation import OCRResponse from litellm.llms.base_llm.search.transformation import SearchResponse from litellm.responses.utils import ResponseAPILoggingUtils +from litellm.types.agents import LiteLLMSendMessageResponse from litellm.types.containers.main import ContainerObject from litellm.types.llms.openai import ( AllMessageValues, @@ -1738,6 +1739,7 @@ class Logging(LiteLLMLoggingBaseClass): and logging_result.get("object") == "search" # Search API (dict format) or isinstance(logging_result, VideoObject) or isinstance(logging_result, ContainerObject) + or isinstance(logging_result, LiteLLMSendMessageResponse) # A2A or (self.call_type == CallTypes.call_mcp_tool.value) ): return True diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py new file mode 100644 index 00000000000..4534e313c29 --- /dev/null +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -0,0 +1,189 @@ +""" +A2A Protocol endpoints for LiteLLM Proxy. + +Allows clients to invoke agents through LiteLLM using the A2A protocol. +The A2A SDK can point to LiteLLM's URL and invoke agents registered with LiteLLM. +""" + +import json +from typing import Any, Optional + +from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import JSONResponse, StreamingResponse + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth + +router = APIRouter() + + +def _jsonrpc_error( + request_id: Optional[str], + code: int, + message: str, + status_code: int = 400, +) -> JSONResponse: + """Create a JSON-RPC 2.0 error response.""" + return JSONResponse( + content={ + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": code, "message": message}, + }, + status_code=status_code, + ) + + +def _get_agent(agent_id: str): + """Look up an agent by ID or name. Returns None if not found.""" + from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry + + agent = global_agent_registry.get_agent_by_id(agent_id=agent_id) + if agent is None: + agent = global_agent_registry.get_agent_by_name(agent_name=agent_id) + return agent + + +async def _handle_send_message( + a2a_client: Any, + request_id: str, + params: dict, +) -> JSONResponse: + """Handle message/send method.""" + from a2a.types import MessageSendParams, SendMessageRequest + + from litellm.a2a import asend_message + + a2a_request = SendMessageRequest( + id=request_id, + params=MessageSendParams(**params), + ) + response = await asend_message(a2a_client=a2a_client, request=a2a_request) + return JSONResponse(content=response.model_dump(mode="json", exclude_none=True)) + + +async def _handle_stream_message( + a2a_client: Any, + request_id: str, + params: dict, +) -> StreamingResponse: + """Handle message/stream method.""" + from a2a.types import MessageSendParams, SendStreamingMessageRequest + + a2a_request = SendStreamingMessageRequest( + id=request_id, + params=MessageSendParams(**params), + ) + + async def stream_response(): + try: + async for chunk in a2a_client.send_message_streaming(a2a_request): + yield json.dumps(chunk.model_dump(mode="json", exclude_none=True)) + "\n" + except Exception as e: + verbose_proxy_logger.exception(f"Error streaming A2A response: {e}") + yield json.dumps({ + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32603, "message": f"Streaming error: {str(e)}"}, + }) + "\n" + + return StreamingResponse(stream_response(), media_type="application/x-ndjson") + + +@router.get( + "/a2a/{agent_id}/.well-known/agent-card.json", + tags=["[beta] A2A Agents"], + dependencies=[Depends(user_api_key_auth)], +) +async def get_agent_card( + agent_id: str, + request: Request, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Get the agent card for an agent (A2A discovery endpoint). + + The URL in the agent card is rewritten to point to the LiteLLM proxy, + so all subsequent A2A calls go through LiteLLM for logging and cost tracking. + """ + try: + agent = _get_agent(agent_id) + if agent is None: + raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' not found") + + # Copy and rewrite URL to point to LiteLLM proxy + agent_card = dict(agent.agent_card_params) + agent_card["url"] = f"{str(request.base_url).rstrip('/')}/a2a/{agent_id}" + + verbose_proxy_logger.debug( + f"Returning agent card for '{agent_id}' with proxy URL: {agent_card['url']}" + ) + return JSONResponse(content=agent_card) + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception(f"Error getting agent card: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/a2a/{agent_id}", + tags=["[beta] A2A Agents"], + dependencies=[Depends(user_api_key_auth)], +) +async def invoke_agent_a2a( + agent_id: str, + request: Request, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Invoke an agent using the A2A protocol (JSON-RPC 2.0). + + Supported methods: + - message/send: Send a message and get a response + - message/stream: Send a message and stream the response + """ + from litellm.a2a import create_a2a_client + + body = {} + try: + body = await request.json() + verbose_proxy_logger.debug(f"A2A request for agent '{agent_id}': {body}") + + # Validate JSON-RPC format + if body.get("jsonrpc") != "2.0": + return _jsonrpc_error(body.get("id"), -32600, "Invalid Request: jsonrpc must be '2.0'") + + request_id = body.get("id") + method = body.get("method") + params = body.get("params", {}) + + # Find the agent + agent = _get_agent(agent_id) + if agent is None: + return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' not found", 404) + + # Get backend URL + agent_url = agent.agent_card_params.get("url") + if not agent_url: + return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' has no URL configured", 500) + + verbose_proxy_logger.info(f"Proxying A2A request to agent '{agent_id}' at {agent_url}") + + # Create A2A client and dispatch to handler + a2a_client = await create_a2a_client(base_url=agent_url) + + if method == "message/send": + return await _handle_send_message(a2a_client, request_id, params) + elif method == "message/stream": + return await _handle_stream_message(a2a_client, request_id, params) + else: + return _jsonrpc_error(request_id, -32601, f"Method '{method}' not found") + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception(f"Error invoking agent: {e}") + return _jsonrpc_error(body.get("id"), -32603, f"Internal error: {str(e)}", 500) diff --git a/litellm/proxy/agent_endpoints/endpoints.py b/litellm/proxy/agent_endpoints/endpoints.py index 489c8e82302..90688036d84 100644 --- a/litellm/proxy/agent_endpoints/endpoints.py +++ b/litellm/proxy/agent_endpoints/endpoints.py @@ -29,7 +29,7 @@ router = APIRouter() @router.get( "/v1/agents", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=List[AgentResponse], ) @@ -101,7 +101,7 @@ from litellm.proxy.agent_endpoints.agent_registry import ( @router.post( "/v1/agents", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentResponse, ) @@ -196,7 +196,7 @@ async def create_agent( @router.get( "/v1/agents/{agent_id}", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentResponse, ) @@ -239,7 +239,7 @@ async def get_agent_by_id(agent_id: str): @router.put( "/v1/agents/{agent_id}", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentResponse, ) @@ -328,7 +328,7 @@ async def update_agent( @router.patch( "/v1/agents/{agent_id}", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentResponse, ) @@ -471,7 +471,7 @@ async def delete_agent(agent_id: str): @router.post( "/v1/agents/{agent_id}/make_public", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentMakePublicResponse, ) @@ -585,7 +585,7 @@ async def make_agent_public( @router.post( "/v1/agents/make_public", - tags=["[beta] Agents"], + tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], response_model=AgentMakePublicResponse, ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4d971e8ce42..8e2c229c914 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -188,6 +188,7 @@ from litellm.proxy._experimental.mcp_server.tool_registry import ( global_mcp_tool_registry, ) from litellm.proxy._types import * +from litellm.proxy.agent_endpoints.a2a_endpoints import router as a2a_router from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry from litellm.proxy.agent_endpoints.endpoints import router as agent_endpoints_router from litellm.proxy.analytics_endpoints.analytics_endpoints import ( @@ -10068,6 +10069,7 @@ app.include_router(user_agent_analytics_router) app.include_router(enterprise_router) app.include_router(ui_discovery_endpoints_router) app.include_router(agent_endpoints_router) +app.include_router(a2a_router) ######################################################## # MCP Server ######################################################## diff --git a/litellm/types/agents.py b/litellm/types/agents.py index 850dab0ea7f..2eb26dc6227 100644 --- a/litellm/types/agents.py +++ b/litellm/types/agents.py @@ -1,9 +1,14 @@ from datetime import datetime -from typing import Any, Dict, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, PrivateAttr from typing_extensions import Required, TypedDict +from litellm.types.llms.base import LiteLLMPydanticObjectBase + +if TYPE_CHECKING: + from a2a.types import SendMessageResponse + # AgentProvider class AgentProvider(TypedDict, total=False): @@ -200,3 +205,41 @@ class AgentMakePublicResponse(BaseModel): class MakeAgentsPublicRequest(BaseModel): agent_ids: List[str] + + +class LiteLLMSendMessageResponse(LiteLLMPydanticObjectBase): + """ + LiteLLM wrapper for A2A SendMessageResponse. + + Wraps the a2a SDK's SendMessageResponse with LiteLLM's _hidden_params + for cost tracking and logging integration. + """ + + # A2A response fields + id: str + jsonrpc: str = "2.0" + result: Optional[Dict[str, Any]] = None + error: Optional[Dict[str, Any]] = None + + model_config = {"extra": "allow"} + + # LiteLLM private attributes for logging/cost tracking + _hidden_params: dict = PrivateAttr(default_factory=dict) + + @classmethod + def from_a2a_response( + cls, response: "SendMessageResponse" + ) -> "LiteLLMSendMessageResponse": + """ + Create a LiteLLMSendMessageResponse from an a2a SDK SendMessageResponse. + + Args: + response: The a2a SDK SendMessageResponse + + Returns: + LiteLLMSendMessageResponse with _hidden_params support + """ + # Convert the a2a response to a dict + response_dict = response.model_dump(mode="json", exclude_none=True) + + return cls(**response_dict) diff --git a/litellm/types/llms/custom_http.py b/litellm/types/llms/custom_http.py index 4d5ca02f032..9ed25005c05 100644 --- a/litellm/types/llms/custom_http.py +++ b/litellm/types/llms/custom_http.py @@ -24,6 +24,7 @@ class httpxSpecialProvider(str, Enum): Search = "search" MCP = "mcp" RAG = "rag" + A2A = "a2a" VerifyTypes = Union[str, bool, ssl.SSLContext] diff --git a/tests/agent_tests/test_a2a.py b/tests/agent_tests/test_a2a.py new file mode 100644 index 00000000000..fb5620e44a1 --- /dev/null +++ b/tests/agent_tests/test_a2a.py @@ -0,0 +1,138 @@ +""" +Test for LiteLLM A2A module. + +Run with: + pytest tests/agent_tests/test_a2a.py -v -s +""" + +import asyncio +import os +import sys +import json +from typing import Optional +from uuid import uuid4 + +import pytest + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.types.utils import StandardLoggingPayload + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +from a2a.types import MessageSendParams, SendMessageRequest + + +@pytest.mark.asyncio +async def test_asend_message_with_client_decorator(): + """ + Test asend_message standalone function with @client decorator. + This tests the LiteLLM logging integration. + """ + litellm._turn_on_debug() + from litellm.a2a import asend_message, create_a2a_client + + # Create the A2A client first + a2a_client = await create_a2a_client(base_url="http://localhost:10001") + + # Build the request matching A2A SDK spec + send_message_payload = { + "message": { + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hello from @client decorated function!", + } + ], + "messageId": uuid4().hex, + }, + } + + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams(**send_message_payload), + ) + + # Send message using standalone function with @client decorator + response = await asend_message(a2a_client=a2a_client, request=request) + + # Print response for debugging + print("\n=== A2A Response (standalone with @client) ===") + print(response.model_dump(mode="json", exclude_none=True)) + + # Basic assertions + assert response is not None + + +class TestA2ALogger(CustomLogger): + """Custom logger to capture A2A logging payloads for testing.""" + + def __init__(self): + self.standard_logging_payload: Optional[StandardLoggingPayload] = None + self.logged_kwargs: Optional[dict] = None + self.log_success_called = False + super().__init__() + + async def async_log_success_event( + self, kwargs, response_obj, start_time, end_time + ): + print("TestA2ALogger: async_log_success_event called") + self.log_success_called = True + self.logged_kwargs = kwargs + self.standard_logging_payload = kwargs.get("standard_logging_object", None) + print(f"Captured standard_logging_payload: {self.standard_logging_payload}") + + +@pytest.mark.asyncio +async def test_a2a_logging_payload(): + """ + Test that A2A calls create a standard logging payload. + Validates the @client decorator integration with LiteLLM logging. + """ + # Reset callbacks and set up custom logger + litellm.logging_callback_manager._reset_all_callbacks() + test_logger = TestA2ALogger() + litellm.callbacks = [test_logger] + + from litellm.a2a import asend_message, create_a2a_client + + # Create the A2A client first + a2a_client = await create_a2a_client(base_url="http://localhost:10001") + + # Build the request + send_message_payload = { + "message": { + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hello! Testing logging payload.", + } + ], + "messageId": uuid4().hex, + }, + } + + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams(**send_message_payload), + ) + + # Send message + response = await asend_message(a2a_client=a2a_client, request=request) + + # Give async logging time to complete + await asyncio.sleep(1) + + # Print debug info + print("\n=== Logging Validation ===") + print(f"log_success_called: {test_logger.log_success_called}") + print(f"standard_logging_payload: {test_logger.standard_logging_payload}") + print(f"logged kwargs: {json.dumps(test_logger.logged_kwargs, indent=4, default=str)}") + + assert test_logger.standard_logging_payload is not None + + \ No newline at end of file From 585aee2ae465ce051c0d769aa71b31e94e17a1c9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 18:57:18 -0800 Subject: [PATCH 315/349] [Feat] Agent Gateway - Allow tracking request / response in "Logs" Page (#17449) * init litellm A2a client * simpler a2a client interface * test a2a * move a2a invoking tests * test fix * ensure a2a send message is tracked n logs * rename tags * add streaming handlng * add a2a invocation * add a2a invocation i cost calc * test_a2a_logging_payload * update invoke_agent_a2a * test_invoke_agent_a2a_adds_litellm_data * add A2a agent --- litellm/a2a/cost_calculator.py | 36 ++++++ litellm/a2a/main.py | 41 ++++++- litellm/cost_calculator.py | 11 ++ .../out/assets/logos/a2a_agent.png | Bin 0 -> 72568 bytes .../proxy/agent_endpoints/a2a_endpoints.py | 67 ++++++---- litellm/types/utils.py | 11 ++ tests/agent_tests/test_a2a.py | 31 ++++- .../proxy/agent_endpoints/__init__.py | 0 .../agent_endpoints/test_a2a_endpoints.py | 114 ++++++++++++++++++ .../public/assets/logos/a2a_agent.png | Bin 0 -> 72568 bytes .../src/components/provider_info_helpers.tsx | 4 + 11 files changed, 289 insertions(+), 26 deletions(-) create mode 100644 litellm/a2a/cost_calculator.py create mode 100644 litellm/proxy/_experimental/out/assets/logos/a2a_agent.png create mode 100644 tests/test_litellm/proxy/agent_endpoints/__init__.py create mode 100644 tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py create mode 100644 ui/litellm-dashboard/public/assets/logos/a2a_agent.png diff --git a/litellm/a2a/cost_calculator.py b/litellm/a2a/cost_calculator.py new file mode 100644 index 00000000000..2d821bb9c3b --- /dev/null +++ b/litellm/a2a/cost_calculator.py @@ -0,0 +1,36 @@ +""" +Cost calculator for A2A (Agent-to-Agent) calls. +""" + +from typing import TYPE_CHECKING, Any, Optional + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import ( + Logging as LitellmLoggingObject, + ) +else: + LitellmLoggingObject = Any + + +class A2ACostCalculator: + @staticmethod + def calculate_a2a_cost( + litellm_logging_obj: Optional[LitellmLoggingObject], + ) -> float: + """ + Calculate the cost of an A2A send_message call. + + Default is 0.0. In the future, users can configure cost per agent call. + """ + if litellm_logging_obj is None: + return 0.0 + + # Check if user set a custom response cost + response_cost = litellm_logging_obj.model_call_details.get( + "response_cost", None + ) + if response_cost is not None: + return response_cost + + # Default to 0.0 for A2A calls + return 0.0 diff --git a/litellm/a2a/main.py b/litellm/a2a/main.py index 7e293dec81e..7827581ceac 100644 --- a/litellm/a2a/main.py +++ b/litellm/a2a/main.py @@ -38,6 +38,38 @@ except ImportError: pass +def _get_a2a_model_info(a2a_client: Any, kwargs: Dict[str, Any]) -> str: + """ + Extract agent info and set model/custom_llm_provider for cost tracking. + + Sets model info on the litellm_logging_obj if available. + Returns the agent name for logging. + """ + agent_name = "unknown" + + # Try to get agent card from our stored attribute first, then fallback to SDK attribute + agent_card = getattr(a2a_client, "_litellm_agent_card", None) + if agent_card is None: + agent_card = getattr(a2a_client, "agent_card", None) + + if agent_card is not None: + agent_name = getattr(agent_card, "name", "unknown") or "unknown" + + # Build model string + model = f"a2a_agent/{agent_name}" + custom_llm_provider = "a2a_agent" + + # Set on litellm_logging_obj if available (for standard logging payload) + litellm_logging_obj = kwargs.get("litellm_logging_obj") + if litellm_logging_obj is not None: + litellm_logging_obj.model = model + litellm_logging_obj.custom_llm_provider = custom_llm_provider + litellm_logging_obj.model_call_details["model"] = model + litellm_logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider + + return agent_name + + @client async def asend_message( a2a_client: "A2AClientType", @@ -80,7 +112,9 @@ async def asend_message( response = await asend_message(a2a_client=a2a_client, request=request) ``` """ - verbose_logger.info(f"A2A send_message request_id={request.id}") + agent_name = _get_a2a_model_info(a2a_client, kwargs) + + verbose_logger.info(f"A2A send_message request_id={request.id}, agent={agent_name}") a2a_response = await a2a_client.send_message(request) @@ -207,12 +241,15 @@ async def create_a2a_client( f"Resolved agent card: {agent_card.name if hasattr(agent_card, 'name') else 'unknown'}" ) - # Create and return A2A client + # Create A2A client a2a_client = _A2AClient( httpx_client=httpx_client, agent_card=agent_card, ) + # Store agent_card on client for later retrieval (SDK doesn't expose it) + a2a_client._litellm_agent_card = agent_card # type: ignore[attr-defined] + verbose_logger.info(f"A2A client created for {base_url}") return a2a_client diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9ef26d23ce2..943eb069565 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -934,6 +934,17 @@ def completion_cost( # noqa: PLR0915 prompt_tokens = token_counter(model=model, text=prompt) completion_tokens = token_counter(model=model, text=completion) + # Handle A2A calls before model check - A2A doesn't require a model + if call_type in ( + CallTypes.asend_message.value, + CallTypes.send_message.value, + ): + from litellm.a2a.cost_calculator import A2ACostCalculator + + return A2ACostCalculator.calculate_a2a_cost( + litellm_logging_obj=litellm_logging_obj + ) + if model is None: raise ValueError( f"Model is None and does not exist in passed completion_response. Passed completion_response={completion_response}, model={model}" diff --git a/litellm/proxy/_experimental/out/assets/logos/a2a_agent.png b/litellm/proxy/_experimental/out/assets/logos/a2a_agent.png new file mode 100644 index 0000000000000000000000000000000000000000..305ae1acaf434f9b4151cdabdc86038abdd34f00 GIT binary patch literal 72568 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 literal 0 HcmV?d00001 diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py index 4534e313c29..ffb861a206f 100644 --- a/litellm/proxy/agent_endpoints/a2a_endpoints.py +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -8,7 +8,7 @@ The A2A SDK can point to LiteLLM's URL and invoke agents registered with LiteLLM import json from typing import Any, Optional -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi.responses import JSONResponse, StreamingResponse from litellm._logging import verbose_proxy_logger @@ -45,24 +45,6 @@ def _get_agent(agent_id: str): return agent -async def _handle_send_message( - a2a_client: Any, - request_id: str, - params: dict, -) -> JSONResponse: - """Handle message/send method.""" - from a2a.types import MessageSendParams, SendMessageRequest - - from litellm.a2a import asend_message - - a2a_request = SendMessageRequest( - id=request_id, - params=MessageSendParams(**params), - ) - response = await asend_message(a2a_client=a2a_client, request=a2a_request) - return JSONResponse(content=response.model_dump(mode="json", exclude_none=True)) - - async def _handle_stream_message( a2a_client: Any, request_id: str, @@ -136,6 +118,7 @@ async def get_agent_card( async def invoke_agent_a2a( agent_id: str, request: Request, + fastapi_response: Response, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ @@ -145,7 +128,13 @@ async def invoke_agent_a2a( - message/send: Send a message and get a response - message/stream: Send a message and stream the response """ - from litellm.a2a import create_a2a_client + from litellm.a2a import asend_message, create_a2a_client + from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request + from litellm.proxy.proxy_server import ( + general_settings, + proxy_config, + version, + ) body = {} try: @@ -165,18 +154,50 @@ async def invoke_agent_a2a( if agent is None: return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' not found", 404) - # Get backend URL + # Get backend URL and agent name agent_url = agent.agent_card_params.get("url") + agent_name = agent.agent_card_params.get("name", agent_id) if not agent_url: return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' has no URL configured", 500) verbose_proxy_logger.info(f"Proxying A2A request to agent '{agent_id}' at {agent_url}") - # Create A2A client and dispatch to handler + # Set up data dict for litellm processing + body.update({ + "model": f"a2a_agent/{agent_name}", + "custom_llm_provider": "a2a_agent", + }) + + # Add litellm data (user_api_key, user_id, team_id, etc.) + data = await add_litellm_data_to_request( + data=body, + request=request, + user_api_key_dict=user_api_key_dict, + proxy_config=proxy_config, + general_settings=general_settings, + version=version, + ) + + # Create A2A client a2a_client = await create_a2a_client(base_url=agent_url) if method == "message/send": - return await _handle_send_message(a2a_client, request_id, params) + from a2a.types import MessageSendParams, SendMessageRequest + + a2a_request = SendMessageRequest( + id=request_id, + params=MessageSendParams(**params), + ) + + # Pass litellm data through kwargs for proper logging + response = await asend_message( + a2a_client=a2a_client, + request=a2a_request, + metadata=data.get("metadata", {}), + proxy_server_request=data.get("proxy_server_request"), + ) + return JSONResponse(content=response.model_dump(mode="json", exclude_none=True)) + elif method == "message/stream": return await _handle_stream_message(a2a_client, request_id, params) else: diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 6f2277cccd3..cb4d5c4d47f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -345,6 +345,12 @@ class CallTypes(str, Enum): ######################################################### call_mcp_tool = "call_mcp_tool" + ######################################################### + # A2A Call Types + ######################################################### + asend_message = "asend_message" + send_message = "send_message" + CallTypesLiteral = Literal[ "embedding", @@ -397,6 +403,8 @@ CallTypesLiteral = Literal[ "vector_store_file_delete", "avector_store_file_delete", "call_mcp_tool", + "asend_message", + "send_message", "aresponses", "responses", ] @@ -712,6 +720,8 @@ API_ROUTE_TO_CALL_TYPES = { ], # MCP (Model Context Protocol) "/mcp/call_tool": [CallTypes.call_mcp_tool], + # A2A (Agent-to-Agent) + "/a2a/{agent_id}": [CallTypes.asend_message, CallTypes.send_message], # Passthrough endpoints "/llm_passthrough": [ CallTypes.llm_passthrough_route, @@ -2981,6 +2991,7 @@ class LlmProviders(str, Enum): WANDB = "wandb" OVHCLOUD = "ovhcloud" LEMONADE = "lemonade" + A2A_AGENT = "a2a_agent" # Create a set of all provider values for quick lookup diff --git a/tests/agent_tests/test_a2a.py b/tests/agent_tests/test_a2a.py index fb5620e44a1..675b3b98e4d 100644 --- a/tests/agent_tests/test_a2a.py +++ b/tests/agent_tests/test_a2a.py @@ -133,6 +133,35 @@ async def test_a2a_logging_payload(): print(f"standard_logging_payload: {test_logger.standard_logging_payload}") print(f"logged kwargs: {json.dumps(test_logger.logged_kwargs, indent=4, default=str)}") + # Verify logging was called + assert test_logger.log_success_called is True assert test_logger.standard_logging_payload is not None - \ No newline at end of file + # Verify standard_logging_payload exists + slp = test_logger.standard_logging_payload + assert slp is not None + + # Get values from standard logging payload + logged_model = slp.get("model") if isinstance(slp, dict) else getattr(slp, "model", None) + logged_provider = slp.get("custom_llm_provider") if isinstance(slp, dict) else getattr(slp, "custom_llm_provider", None) + call_type = slp.get("call_type") if isinstance(slp, dict) else getattr(slp, "call_type", None) + response_cost = slp.get("response_cost") if isinstance(slp, dict) else getattr(slp, "response_cost", None) + + print(f"\n=== Standard Logging Payload Validation ===") + print(f"model: {logged_model}") + print(f"custom_llm_provider: {logged_provider}") + print(f"call_type: {call_type}") + print(f"response_cost: {response_cost}") + + # Verify model and custom_llm_provider are set correctly + assert logged_model is not None, "model should be set" + assert "a2a_agent/" in logged_model, f"model should contain 'a2a_agent/', got: {logged_model}" + assert logged_provider == "a2a_agent", f"custom_llm_provider should be 'a2a_agent', got: {logged_provider}" + + # Verify call_type is correct for A2A + assert call_type == "asend_message", f"call_type should be 'asend_message', got: {call_type}" + + # Verify response_cost is set to 0.0 (not None, not an error) + # This confirms the A2A cost calculator is working + assert response_cost is not None, "response_cost should not be None" + assert response_cost == 0.0, f"response_cost should be 0.0 for A2A, got: {response_cost}" diff --git a/tests/test_litellm/proxy/agent_endpoints/__init__.py b/tests/test_litellm/proxy/agent_endpoints/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py new file mode 100644 index 00000000000..c9b8917b20c --- /dev/null +++ b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py @@ -0,0 +1,114 @@ +""" +Mock tests for A2A endpoints. + +Tests that invoke_agent_a2a properly integrates with add_litellm_data_to_request. +""" + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +@pytest.mark.asyncio +async def test_invoke_agent_a2a_adds_litellm_data(): + """ + Test that invoke_agent_a2a calls add_litellm_data_to_request + and the resulting data includes proxy_server_request. + """ + from litellm.proxy._types import UserAPIKeyAuth + + # Track the data passed to add_litellm_data_to_request + captured_data = {} + + async def mock_add_litellm_data(data, **kwargs): + # Simulate what add_litellm_data_to_request does + data["proxy_server_request"] = { + "url": "http://localhost:4000/a2a/test-agent", + "method": "POST", + "headers": {}, + "body": dict(data), + } + captured_data.update(data) + return data + + # Mock response from asend_message + mock_response = MagicMock() + mock_response.model_dump.return_value = { + "jsonrpc": "2.0", + "id": "test-id", + "result": {"status": "success"}, + } + + # Mock agent + mock_agent = MagicMock() + mock_agent.agent_card_params = { + "url": "http://backend-agent:10001", + "name": "Test Agent", + } + + # Mock request + mock_request = MagicMock() + mock_request.json = AsyncMock(return_value={ + "jsonrpc": "2.0", + "id": "test-id", + "method": "message/send", + "params": { + "message": { + "role": "user", + "parts": [{"kind": "text", "text": "Hello"}], + "messageId": "msg-123", + } + }, + }) + + mock_user_api_key_dict = UserAPIKeyAuth( + api_key="sk-test-key", + user_id="test-user", + team_id="test-team", + ) + + # Patch at the source modules + with patch( + "litellm.proxy.agent_endpoints.a2a_endpoints._get_agent", + return_value=mock_agent, + ), patch( + "litellm.proxy.litellm_pre_call_utils.add_litellm_data_to_request", + side_effect=mock_add_litellm_data, + ) as mock_add_data, patch( + "litellm.a2a.create_a2a_client", + new_callable=AsyncMock, + ), patch( + "litellm.a2a.asend_message", + new_callable=AsyncMock, + return_value=mock_response, + ), patch( + "litellm.proxy.proxy_server.general_settings", + {}, + ), patch( + "litellm.proxy.proxy_server.proxy_config", + MagicMock(), + ), patch( + "litellm.proxy.proxy_server.version", + "1.0.0", + ): + from litellm.proxy.agent_endpoints.a2a_endpoints import invoke_agent_a2a + + mock_fastapi_response = MagicMock() + + result = await invoke_agent_a2a( + agent_id="test-agent", + request=mock_request, + fastapi_response=mock_fastapi_response, + user_api_key_dict=mock_user_api_key_dict, + ) + + # Verify add_litellm_data_to_request was called + mock_add_data.assert_called_once() + + # Verify model and custom_llm_provider were set + assert captured_data.get("model") == "a2a_agent/Test Agent" + assert captured_data.get("custom_llm_provider") == "a2a_agent" + + # Verify proxy_server_request was added + assert "proxy_server_request" in captured_data + assert captured_data["proxy_server_request"]["method"] == "POST" diff --git a/ui/litellm-dashboard/public/assets/logos/a2a_agent.png b/ui/litellm-dashboard/public/assets/logos/a2a_agent.png new file mode 100644 index 0000000000000000000000000000000000000000..305ae1acaf434f9b4151cdabdc86038abdd34f00 GIT binary patch literal 72568 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 literal 0 HcmV?d00001 diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 110be8e376b..277786aa8ce 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -1,4 +1,6 @@ + export enum Providers { + A2A_Agent = "A2A Agent", AIML = "AI/ML API", Bedrock = "Amazon Bedrock", Anthropic = "Anthropic", @@ -43,6 +45,7 @@ export enum Providers { } export const provider_map: Record = { + A2A_Agent: "a2a_agent", AIML: "aiml", OpenAI: "openai", OpenAI_Text: "text-completion-openai", @@ -89,6 +92,7 @@ export const provider_map: Record = { const asset_logos_folder = "../ui/assets/logos/"; export const providerLogoMap: Record = { + [Providers.A2A_Agent]: `${asset_logos_folder}a2a_agent.png`, [Providers.AIML]: `${asset_logos_folder}aiml_api.svg`, [Providers.Anthropic]: `${asset_logos_folder}anthropic.svg`, [Providers.AssemblyAI]: `${asset_logos_folder}assemblyai_small.png`, From e4f954b354e637770e4dff261c91d9f66a601e54 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 18:57:41 -0800 Subject: [PATCH 316/349] [Docs] Agent Gateway (#17454) * init litellm A2a client * simpler a2a client interface * test a2a * move a2a invoking tests * test fix * ensure a2a send message is tracked n logs * rename tags * add streaming handlng * add a2a invocation * add a2a invocation i cost calc * test_a2a_logging_payload * update invoke_agent_a2a * test_invoke_agent_a2a_adds_litellm_data * add A2a agent * fix endpoints on A2a * UI allow testing a2a endpoints * add agent imgs * add a2a as an endpoint * add a2a * docs a2a invoke * docs a2a * docs A2a invoke --- docs/my-website/docs/a2a.md | 198 +++++++++ docs/my-website/img/add_agent1.png | 0 docs/my-website/img/add_agent_1.png | Bin 0 -> 294807 bytes docs/my-website/img/agent2.png | Bin 0 -> 825788 bytes docs/my-website/sidebars.js | 1 + .../proxy/agent_endpoints/a2a_endpoints.py | 10 + provider_endpoints_support.json | 276 ++++++++---- .../playground/chat_ui/A2AMetrics.tsx | 231 ++++++++++ .../components/playground/chat_ui/ChatUI.tsx | 330 +++++++++----- .../playground/chat_ui/chatConstants.ts | 1 + .../chat_ui/mode_endpoint_mapping.tsx | 1 + .../components/playground/chat_ui/types.ts | 13 + .../playground/llm_calls/a2a_send_message.tsx | 413 ++++++++++++++++++ .../playground/llm_calls/fetch_agents.tsx | 52 +++ 14 files changed, 1330 insertions(+), 196 deletions(-) create mode 100644 docs/my-website/docs/a2a.md create mode 100644 docs/my-website/img/add_agent1.png create mode 100644 docs/my-website/img/add_agent_1.png create mode 100644 docs/my-website/img/agent2.png create mode 100644 ui/litellm-dashboard/src/components/playground/chat_ui/A2AMetrics.tsx create mode 100644 ui/litellm-dashboard/src/components/playground/llm_calls/a2a_send_message.tsx create mode 100644 ui/litellm-dashboard/src/components/playground/llm_calls/fetch_agents.tsx diff --git a/docs/my-website/docs/a2a.md b/docs/my-website/docs/a2a.md new file mode 100644 index 00000000000..7851f50d88e --- /dev/null +++ b/docs/my-website/docs/a2a.md @@ -0,0 +1,198 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Image from '@theme/IdealImage'; + +# /a2a - Agent Gateway (A2A Protocol) + +| Feature | Supported | +|---------|-----------| +| Logging | ✅ | +| Load Balancing | ✅ | +| Streaming | ✅ | + +:::tip + +LiteLLM follows the [A2A (Agent-to-Agent) Protocol](https://github.com/google/A2A) for invoking agents. + +::: + +## Adding your Agent + +You can add A2A-compatible agents through the LiteLLM Admin UI. + +1. Navigate to the **Agents** tab +2. Click **Add Agent** +3. Enter the agent name (e.g., `ij-local`) and the URL of your A2A agent + + + +The URL should be the invocation URL for your A2A agent (e.g., `http://localhost:10001`). + +## Invoking your Agents + +Use the [A2A Python SDK](https://pypi.org/project/a2a/) to invoke agents through LiteLLM: + +- `base_url`: Your LiteLLM proxy URL + `/a2a/{agent_name}` +- `headers`: Include your LiteLLM Virtual Key for authentication + +```python showLineNumbers title="invoke_a2a_agent.py" +from uuid import uuid4 +import httpx +import asyncio +from a2a.client import A2ACardResolver, A2AClient +from a2a.types import MessageSendParams, SendMessageRequest + +# === CONFIGURE THESE === +LITELLM_BASE_URL = "http://localhost:4000" # Your LiteLLM proxy URL +LITELLM_VIRTUAL_KEY = "sk-1234" # Your LiteLLM Virtual Key +LITELLM_AGENT_NAME = "ij-local" # Agent name registered in LiteLLM +# ======================= + +async def main(): + base_url = f"{LITELLM_BASE_URL}/a2a/{LITELLM_AGENT_NAME}" + headers = {"Authorization": f"Bearer {LITELLM_VIRTUAL_KEY}"} + + async with httpx.AsyncClient(headers=headers) as httpx_client: + # Resolve agent card and create client + resolver = A2ACardResolver(httpx_client=httpx_client, base_url=base_url) + agent_card = await resolver.get_agent_card() + client = A2AClient(httpx_client=httpx_client, agent_card=agent_card) + + # Send a message + request = SendMessageRequest( + id=str(uuid4()), + params=MessageSendParams( + message={ + "role": "user", + "parts": [{"kind": "text", "text": "Hello, what can you do?"}], + "messageId": uuid4().hex, + } + ), + ) + response = await client.send_message(request) + print(response.model_dump(mode="json", exclude_none=True)) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +### Streaming Responses + +For streaming responses, use `send_message_streaming`: + +```python showLineNumbers title="invoke_a2a_agent_streaming.py" +from uuid import uuid4 +import httpx +import asyncio +from a2a.client import A2ACardResolver, A2AClient +from a2a.types import MessageSendParams, SendStreamingMessageRequest + +# === CONFIGURE THESE === +LITELLM_BASE_URL = "http://localhost:4000" # Your LiteLLM proxy URL +LITELLM_VIRTUAL_KEY = "sk-1234" # Your LiteLLM Virtual Key +LITELLM_AGENT_NAME = "ij-local" # Agent name registered in LiteLLM +# ======================= + +async def main(): + base_url = f"{LITELLM_BASE_URL}/a2a/{LITELLM_AGENT_NAME}" + headers = {"Authorization": f"Bearer {LITELLM_VIRTUAL_KEY}"} + + async with httpx.AsyncClient(headers=headers) as httpx_client: + # Resolve agent card and create client + resolver = A2ACardResolver(httpx_client=httpx_client, base_url=base_url) + agent_card = await resolver.get_agent_card() + client = A2AClient(httpx_client=httpx_client, agent_card=agent_card) + + # Send a streaming message + request = SendStreamingMessageRequest( + id=str(uuid4()), + params=MessageSendParams( + message={ + "role": "user", + "parts": [{"kind": "text", "text": "Hello, what can you do?"}], + "messageId": uuid4().hex, + } + ), + ) + + # Stream the response + async for chunk in client.send_message_streaming(request): + print(chunk.model_dump(mode="json", exclude_none=True)) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Tracking Agent Logs + +After invoking an agent, you can view the request logs in the LiteLLM **Logs** tab. + +The logs show: +- **Request/Response content** sent to and received from the agent +- **User, Key, Team** information for tracking who made the request +- **Latency and cost** metrics + + + +## API Reference + +### Endpoint + +``` +POST /a2a/{agent_name}/message/send +``` + +### Authentication + +Include your LiteLLM Virtual Key in the `Authorization` header: + +``` +Authorization: Bearer sk-your-litellm-key +``` + +### Request Format + +LiteLLM follows the [A2A JSON-RPC 2.0 specification](https://github.com/google/A2A): + +```json title="Request Body" +{ + "jsonrpc": "2.0", + "id": "unique-request-id", + "method": "message/send", + "params": { + "message": { + "role": "user", + "parts": [{"kind": "text", "text": "Your message here"}], + "messageId": "unique-message-id" + } + } +} +``` + +### Response Format + +```json title="Response" +{ + "jsonrpc": "2.0", + "id": "unique-request-id", + "result": { + "kind": "task", + "id": "task-id", + "contextId": "context-id", + "status": {"state": "completed", "timestamp": "2025-01-01T00:00:00Z"}, + "artifacts": [ + { + "artifactId": "artifact-id", + "name": "response", + "parts": [{"kind": "text", "text": "Agent response here"}] + } + ] + } +} +``` diff --git a/docs/my-website/img/add_agent1.png b/docs/my-website/img/add_agent1.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/my-website/img/add_agent_1.png b/docs/my-website/img/add_agent_1.png new file mode 100644 index 0000000000000000000000000000000000000000..e60435996a931ce61aeecba409322b0c0218bdea GIT binary patch literal 294807 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 McH33#xEy9w!C=Ck>5=%EM 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ν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

    t8_B|4Wo*GIq0XU?Ay3cQ=n1Z>7yiq1jK7YruW0 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.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 ^_sv%qAFz*{(I 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

    KMAA|3TSpdS6eMo_~E0B)0D43gbbs-G> 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

  • 5XuwDI)$aq3+JfL1InVhex8}6ClQI^D#B;ZCoQdyM^wak_$v0XVD?kb zL(Ig>0krHPi}c4v0n(c!Lg?yzs)Y-}@H5^#d_w;`Mh8>HYcBZ?;df9?#7igMW~sok z&=j`J6>dcwQzHKwNfqisn(ky2hD*N}RIVCg{|UTlPez7@PH%7g7?1Q^fA>{Xv{hRK zbt|y9n_JOO=W~1*J3;aqg{B(jEsdn)G&uabw@&GlCBh8w$$r?a4+KtutM8qqF|p)! zdom!l0enYQciv6f%ZO{0j`N#+*O_2Jp0f$!2#-L2Y=Xt4GmqjIq+_i!bRXnaP#?OP zUh@!GN)+<(Y-BkJCl)`&U|VECZGIQd`3tS@y6;DiW0(Vz-xMPHiQdq+K#ZV2WYzL( zXfs*_dLdkP(d;`{4yD_FBa{rCz^iP$e9(TkA9=c1OZY5)jnd`sm)b`}T1WOZrQ7da zJ(v^GKUV@DsM18wLH2o&V}07RXPA5W7L&fx#3$O4B=x8z%NQWBpEmP0$~AF*nQd73 zi+x$rHYttkw{%?08R;#u?8djDJ0#}rsK}_v9(lrP4rga9S8MXj&>oZ-*h=#?Y`^Rv z-T_)S_nv^T`}DTC_(dLf!-65Ma33YF!RZMT%w{Lv?|$q~Ora?zzgriiHwiANc*;`#Cy`UBqluJ_` z9b4kv6@nbTZo)pwBBnyhH^Rv~`DFTqn7?Fnb}jySk?@~!bifvye(Hi^_LN<{GOn%d z150h?0_d-$3Z)^KWbditEGdY}<8(9`0&Jd5#p8q1N#i~p*2&&z3~mof<#tdn;6J2W z1C_3n13bI}XnWVd$}^JH{!83~?{p)1C_8;k1Jd`|9_yjv=Y@cVTlV{v_;A4;MXQCf zj~qiKo8{tpZabGe-BFBUMepbLUXmK&s;*hz%v}equbAga>*v|DCMCKQe>GfwJ5{0J zX=8(Fi;b2X)GeGCYR&!7%yH!FZT@lZ2N|8OO#zdfd;_OtzJ{O2fih z>Fx1W_=Uv7hlHor+GAz$ut?eKrfMa8#O`hhs9OO2%H&=ACq4}9fVR@okG(scX-gNjGaPN-(Hr+%fS^pSJQ6G8{S{xbvR<32eh!!h zfbW)Ye>rKF@^bQavIDU~{q6IVK^#$gp?pI>HZJMZq4JA}o0d`8_{F4!cexLRpRV04gHKeC}T}lc${krkvv!--~3sPoky^6Q^J!$(rDety&!-^N+_*_{vyS%l%0;e^AM4#K>Bs=SOC)f|?4ydz`E3KUu!MboZuItg#I{pY8OZsb5ZPW5 zv2KYxw*dKLYGFXiKEpRZ<}}7Fiaiqsm7FnsAPqb#Dqlw=h9AQo4eNTM-9Egcq+Hxs zFWq?J;IZ2~8T=iuMrw@{N&>T(T+eZRSp2Jgv~k57maO9}MveZE+ZdmJ8lBUJ1xyoz zeQ27hswzuTTu|rzp3fgP=`MZ}D&{1f4+40ai*U-MS7U2T;K3XA)Zc+o!}8Lmammsg z92~jL@Q~juev{yi1JC#2uXdMQgEhqg?`3?24ZC+dCIkn%osd;fl2{O5TQ=B=8#mo) zdO_T~y%R(Fl$G_BF~hw_%$PjVWh$;Jm z9-P??iIJTtVkS4;f|wPWg$v<3$m!eZSL}z}JYj49!ti@?(Q{{(5OfD#IEXt7YrKf( zyCVX|&ck!kw%HzQ?7WmyH2#45E*)zG%ZMSFO!i(3Kl!I7$P?BD1t8; zbhM*g`O6*L$TD0GTyy2yZPJN<)r-CY?%-F``X@9WJa9|)ej%~H3S=tdp#74=<mRNdaQWC1`Hh)>?hLgcYzFHA)ID@aA zY_f}p=%9F`?Xi<{_UB~JHH5SglzoI;WsVXFJ(9R;df|!Qw~Or}*?0XevXcY)pD@-* zm;cSIzy{;rTMm@_@L|kbnCg?SyR-evfm>vx9tLQ;`0)p&_0b@Z(~T*Wnnardb&jSB69DkhyI*9`6l*2yXuQx!>004xPdRBm` zH}Fu;A|0j6D=H8-r;=#s%}a5;DfV zi5*y^9dR^;R8jJ8R7He#?%E{s#L<{<|B@p0-(w-LDw*HfL$ZR?u1P@A55Snd5jXLP z<4^J&pn3z0YaW!9)YAOnnc3Mx2(LSL@4f)(U*02Y%3^2Fu2!KBOzHbl_v5QUtQ^r< ziD=}>^4R0>MfAZ|g+iWvan*jRX7r`S@{;7M zfIOQfOzX1TBDI>j-z=!*lmM{z_oQha(e6|_eOe93BpTRelDYu3WiOAgTP+Jc$THfG zjy~b1qv4Y*HdyCPo_jD@4wS42c84q&0xQ{l9*@xmW1@*y;<@htshIx}?S$uOQ9H$o zeHhTFJx6-Dvw4)HDgR>f-3Shd)Ep0_R0M#*fGg_yH;wG?->*GKGCv%syYpYKT|%gT zNE=!CO5Id?Ky&UtPAC(?STnqOkwgvUBDSiks>8yEK8 z%zq~5H(3de@iV|`{Ix9B7QEItcjke0_qwhdW)_4{gSYK}H5;NJG!AMwsz zB^d;1a7E!=4Glku-x??&`yyxy(X1PI!_2i^Hi_VM=U<3qn%IGxDoWA>C1tXulS}bk zyvC2x&HXb_?Q=CaHnxN-Z$A0OF0o&jmv^$Gr>{?xwT5KPUfiXs0d)_1=mm3I%iKjVpUcQaU_8q&gTYCBg{OjS_>fL8l{nS+X1H$5Bm27R)LqabAO@}r z3J6#MJ@CS{ojZ483Eh%+;S@-HhviD)Wy{9>EdWd~=1zLC1qNpKfzq9;7EfaZJplw2 zUgKZ1|9?6Z|9kjSbB!OY&pZ51rg<)K7=A*4>Bv)y#kasG^mdXJAzYzDIsgxF;(9>@ zsSnF6y$Ja2I>xq_rMJ6z>eUE+0luI^{ZIF&H4!iG=+Ok)S=um~<>fu6@v*tmdu5mb zQj|=S{I<{VR0G0?HH@>WGA57R%%R0(%eg$?oNInuxU*Xg(5X}eBiChm6} z4geW>Wt1w0i5COFcOrpEzW>{Bn&VD5>7=lrAPEgnhR&)KmQ)9XW+C!A`}+;9T)8p< z;FK!ok5>-Ebn@pTeULC<_W*1MI#>m4m1>@3|0`Kf^y~3uM zFWAjlocd1fP@JjeZohpwN4sZuxZ^t8E*-g~wr@8#x0^iqxj}DQc_mE>0M5xPh$2U4 zaq*&yMdc1tv~zG`8v=Ko21#B?in^7^K#2U~qG>AkM%&EH%=*BE1U+o8_D-6Itv_5m@jH{3N9JbceYUrtl?^`{4YP>`-Pms}v>3R)h`Y-Dh|7?OHwY~A zzK>vY{P}F(RG!dvRNc?}e8$eIDUvHMC(q=3%jq2%V7{fg(FWHv1?w5H{iWL@@|}+S z_L(qB${BQi=_QG3yV=6#Ddo-;C}N~2j;>Ix-#92HjQji)VG+;3ib@N z-K-0=%xZQoJz*xAC;1vYZ1eIJrKci;1@+5?t`ZGmZOQi#V^o72Y-&%n{6U)tOg$S8vRG=h91Dri6G2)xOVolR?CNYMCx*%i--LlKDrx+7+0No*vIWLW*Ho z)@e2-G)HvRneNVEt30w#E3lW$zbDh60e&%)9wjkE-!lp0UDNG3^zGvq`{XIcbS2g= zS(g4uK==fjM`h7}l1>Xxt3VG+WoeX@^!*yLvyn=|%+cvii4U|jiwfDf9D>YSuP`gj z=~5pWld7Z}k5!tghvNQ;rCch={sH@VWEpOI6SY0Ag}z*EV`@%(etg#Xlyt6y-30M4 zmGa_!6CuT2?AyA-?gf8;twsr+8>AG@G0sTwnVciCbP>9Hk3u(yJN>rO?DGDJ*90=h z;PK<58}36O?>>*0v;X$vh@;gO*;^O9a!1Qi5yLVMx2Z4F7;nHXU`S{((pn#THSF_S|lJz*T+0oUKjJtm$YSb zXRnccUSSO8PT-afL9B>4wD#oXu<_VPn-va)`ebFE=OOO2T}7QxbiMfLteENXFAF4* zd|exyQM`6s)zjI?OBVE1sC=>^@@z173HDHO+v!uNr;j%5bFTK#>;whoe+(9lDej0M zdo?#g7RkNhzJYXAMVGm+*49?W7G|{%F-skZmX*q`QdS2EerVPOOND~=Sn?zaCtO}9k#-8qY&#@ePPKNf%v+j)?e<) zGhYWZl4c>Apw!? zRX~*2Q_h)rd(_?-59 zgJUa^i|V;4Aa18puJqc36!0WACg!8*)laB5noE6>hdVEKTkS2;d?Pod?%F9H9Twz6 zPVhc<6}gM)kBWE&mWy%A!WkQz_s_El!iG_Vd9WO1mH-nlYhH@d45RQ9~kddz^g1;`7Z!O4qO}Zz_-`?=%OFrn> zu)CeoT=4tmKi1@>{eY-PB&omK`sZHa|Ea}*u;NrHsIw2sP{`yGKpD}{&?ssW@!O1x ziX-Oc%E`;)!FG@gGMKnTGVDk^%Il+&%=Lj!$27DOzF$cijYjJ^%e9Dk)4HcpmeHAx zJ!yO|!e3(H)VYlQ3BBD1!GCC#W@T$NFfvk&yVG}#a@KUUfS zf=5Redb#Tg{>9tK`YT*AXFuvMxN4UNXqPb@k73XNSIDo&y-KR5NEeJT295gP^h-cT zgtIU=LVGzlw5=Y0Zw*{2H+2xpYo{$dVe0r7zG)Y+nU0)w>T&3A#3<}# zAgCx_b=7ZlMkyWrRV`?|4@*bco_L;M^)+34Q1Z>gH+qtJ1?lR=d1n0#ixZ5hb9BQ` zN(+s?v4^%LAoeb7SP|@-3o2f5`~aI)UoPM}t2|r(Ag%*SR>veuU)~y2P>GC+tSn^q zPKP!2e3H&XzUtjzZvEh(V^g!lv(u54f})T2^@nbudbVauzkW;W2tFg>dbK0||aW@XUn`&u4ruNg;;F1hS4HdioPPJ&kkE3eyR2xA(fKPF~U>C{ud zKg{(v&Z5)ZWk0%C_PRmhm6E7FtKOFhSB#~)ty0{rQsrd6x;kD;@S{Tt-(=9wMS$(X zr8PIxWV_=q%S_Q^6ps^)N)2<)Lnhn#r9lm*v$sYvmdhNXA`eTRyFU4cgcUv7K);=xk#Qk-A8%#r>WyWf3C*so90p6=gKC+^X1&cc z5RA?gM)$a|;$gW32Il5|Wlzh{shM+`#;j02nN^^G=dv(jsw0_Ov}(r; z;cLv{X=VP8y*N&`zP`TDE%6xxBctb>ON6_+Dq?kw!o{@YamaA&K@omAUVf6QmEZ7O z8uI(|@0jw?JEYYcej4go+&pi*-d*Xs(%qfo@-SA?!=9WAok_`Lo`GVZ4^dmZ zs`CeUTB;C>{_g9AB>fx?%5?iB|7?HzR0E}4+DgVfBn_6);Jrgh{%Y$-J$)k-JlA@o z>=5DTJMt{K;K+1`kv-IYzy_uml|{RD+yImkYr_K~L0zO;i5{e?!#$D4daFhjW6xOu6* zUSm_sAX!3Kq|eQR%rO|AX=r6?o#tQG zHsVV$?b(SR>wmSM7{>0+zV<%M!!RHM&#tT=`DIIP)=~3^m6Ovz5dBCL=0U%w**_ol zJ9nqCGE@@x=l#oTZr_6oPNOwDfpOTt>lUBR$xRSxJ5~TdU3h@2p8O2l-(eS4nVT!P zupMP}Q<-L2PqG4CSgF+@#u|32@_T++Ad0h4g)v{`0cI16hroIz&eRtV9+|7X36pQH zM*_yzuct+DPK0Rald`=X5U=kyTzp@LMKY=D*O(~x=ourwW?6k5Lw#PKh$N-wVOl3v z4!UIRy%)c^VhMAz8YqOUW0CcDcn+sSQ=nSBrP#))JRgmwd%Vbq1SJau0W+eKm0HxA zW!hx??0zP%_hPm0!;HNVML9L~X`FZNV2<(N=lnQ7UR>U)wWM@GlQ(b*3z@%s`)>Z? zM>+^AIS*&r1BsgA*Bh{pVRvGrjOL?*$8&xJm2l#+dVlo~cJ-vs=P()b1$*gA`;5JA zcUHCpr8aFc)6IO9CCq2}I3u3xorrS*t|7s$g;yY- zD?4AgtYm*Fmf7u}S0E@)%>g)zipS<8iv>}H4H1>~Je!M=>1^;R(S967|K@djW_~?e zTon0f);o1vx%XOxYPeoTKH1+S`H;ii};DtccUjCdcoZXY9sq1yKc z+x=2d$?LBR7DiQJC-9GIjHZj;!rXQk;dMfs*cXzSRPj@bl>=Gk>VZ>ORF3`tT`2j| z#E(H;<$E{O_dyXcb=NDt*cIrl-#Vq>s9RZwv{K{L<55`{CzlW?!8i%0p5K9WS!zA$ z9Z6i%4J|7+Ic=EKcZ`x;SsSKf{nFKRa%aYP3USU4s$K!14Q0{0E^6>?l!%|%%;}U( z@6(j-?H$B3XrTr2SEhOiOurW4I6imwU0Ud$UBrNm+Pw4^vLpjUl3hG{HTjI=Fzs` zm!&VRcGFc5RL*xEX3y7Mv0Gacs96P{g0vyjR^7^&iOE9LEvs|`ql`@#m%8gp8^s5TaHe5iwfs<;a>W1AkL+t>LEs%44FApg;6M7|I5y5*b17*6%r@buKx_X``HrY}s`_np#*Hkb1 zE$w0jhw!ML#*1*C2oh{OsFFyIMiLc$J5h__=?+d_=YwyFYe3dN#r;lc1{;&!=hBrM zp@}O`7W=E#za8BDHu$dNge&j5lJ8tFl9H~%ktKektU2qd__2%~9<8yu9z*pB?!uhR z>Z)bE)}>Kvx0)*>yOKcWe40*fx-7-8+r-i>FD%_}dH$-f7JOey54-RW+|uWg6D2&{ z6i?1m?qVn^%IN{a(CIdU6r) zp+-UBNBuiEiH{U-O<2uH5Xu4TH9i_j6;nQXF#+$vO36hUo9C&~5 zGwuN{pWE!RsieG^yopAMq+alLzYWU5M#^lp<`fInTMPkTox6l=n-UbcGhuoF?jFQ1EJP*(Vu9k~_Wms`{X+~uz zP1iEbv0Itcja&O)qE5(u>_$G~W*?9DJ-sKSQ?~X;lgjFM$kyVSst18EBdlCPOs{f_ zzo^}J3CW6|kE2XxQb^-=)s08JtT$fM#J)B!zEl;JH{CVl8|dG^Nl6psHPnA-5srKs zHu$==1wY~W&!zXLYEXGkQwnVUjn4v%lmY|~uY*_zq0B(P-$#=3SupWat8oh!rHq9N zT-91ho>6X?7H_2PJLsupmK-iKE9>SpTD{0tK4fjc5{zzR_TKbcD@u-4n|RMTndy^j z4VEWryu<2PhFk3aumD7mGQpjKN{`FH?kV44VmeJUuaEqn{7lGl^BMg)RmBok=3c&m zIkMNxkLj)mvYWlgV2R}QJt6mCab~jk<6M6;PfyS9cd|l`T0Kr)^*y5*K#)dG6|D_6 zsXW`*y1pd5L+|cl9%SbFLrKpsnX8ZS`nTtV^X%d&DTLX$k!r8m-yfLv)X(Ghcv`l@ z!`i|s6=*Ril51LKDIj~D(it2iOpdj!tTB_f+d90zjbqh$pw(et4ZL-h?--|sT_l2v z3*p@#Z$ZsdTJnZIGJD<-ayz9QZlE1QUcR)POB>;;#j)m^a=AwK;!3x!>*?OM&^wm| z>p6y?jP9(M;4K$3co5;BkZXTy-b=B*$@lG$jqLB&JKH}25Lbkjl z8SW-?y$-*{mGt@Vd(C<85`L>%>UHYx(;vTe%9(13r@t91{pvGnGS(}=gB785ZdPy2 z9ar?)^xF4c`#_xduNzO%@;2Ylqk2^LBwvL= zgg3PpYnJ@hgB*i9QI*;XDR#d!7pUf_#p=4!IjXh3gxOPjgWSqzR7Yf5U|0MCl%l#a z4XYMOeZZrkzPH_7tJu?RXv;E;DWG(sakZRR{H7<(tJF6}`?b!rrE=@8m2H~AR-)wM zXAE1H0I38tP3g(Xv-n&d-UhuyGv=$;v#|>+4j-WvE+G-y4d^cMimazT4#VH0H>lg< z)lA2_XEMhF*#fBxjOIR5%~5D$^L1;q+U2INV{#Z8O^u};%htc%+2kQC>1mpO0sM13qIdm z*IiTb_jJ!acT^j zpv)8b>lh0BsbYj{F7)ECZEdKpNdn9Wv$bq>voe??ywac0KZog2uYr)n@8?Ixch&%o+5k`t*j^C+(Ykt) zg<0=v#m}tTwXgyavkAL`raKb3@NFwjG_bkLDJ;C=n|9zqnSqV|j0xp-9NUz!C{bL` z9{>JgtFZ@I=Jx&GX@W(&llpX8t{09tB=H`8Tr%Fa^!gxLf5zZc%a7o|MMg%2COU$)(UwikP#aHJHSeaM~?%AAZ{Ie z-O;|9iIYDjV_*(ddZ2XanpJq~MWxcRA6d@)Mt6p8QBB!nKz*<_Bflj7r*ZVg$62p=|ol#4Vv zQP(Aaw;Z=T)rKVmeUqjP&Gr^lV3+g5m?9^*0-kJ0tx~1>`n>ffJo07;hu8?5Pcevn zrmE&bdxhek$=knraSbR)I^-<-@aRwE{8Zrub^r0HXrN+#}v0U8~9mbO``CY#guwS8x(ddzgklm5s1ok zKoJY|JT~WNbvU*n4Jysb9AeMiQIKzUO%lOzs=)1Hl-h{%{RVF=LmGPB+UDW~>(6rD zR+d`EDtJ#Q=#clADwo|UG|!V~{NS-Z7w&ub_3EX2LG-WopEC!<))_H&_SiaPqQR&X z_M58f^|M~-d81%LQDXwoSEM{yzO$>_17$%zYgwjRn zhZQEayDsa7tb}1;7`;}pM&^&DwteBZa$6cuS{H0{TTTu!SB}cWR5$vsr8nVnM63BS z^4>FhcAw5n%gvlo2>m~-y=Pcc+qyOk2vU@yh=?>1g(Y2S(gK2_NK;y<0R%*PuOR_M zz(NrbP+CBGD53Y>J4g$?NGEg%0Yc!L?)U6{_Il59yWa0Q{tzybIp&;WJkJ+mkN9*lH$C>Z$LVR(oQjY7d%F|m~+QtKz%f>ZOz#yuxexbVcp+HGe-6heHuZ$$; zKp#r%HCEVHMd#gi@#1?gofRA)LvGTEyHz-br_hwrC66Rkm? z^8zr;KHYK5{a966(aihcC&f5`x!ELn94^1E#|GCvyQvBru~{=twITrIOVGo)U0Yn|IupMH>Wth0_K^4te| z%DGKyru5aE#)^uF2Ki;z247+lhQOsx)G;e1V{P3serw6rWUkzk=`*iimtPmpigZ4? z{+L=-mF*99+TrvVZwAjm7f#iv5eB)bQq#x{3D@>)SF~P2u-oIBwVLA`+?89XJO_&8 zoux@J+DS%(;NWhC5bX74js+H^3be(7Y?^@-UGRL{kTy`4*N3@7iGEpip(w*!CrNv5 zEH?eT-FT-@4K^EXNb}W()y@FsX)+j1C)C&3P0^-A=-F|Rqa|B=3_}bLv18Tyc0+t+ z^C!oLH|H}1`REDkUl$G!HPZH;v>R?^c#qb#2dqV%RwA3T$E%R-GkNK7{)2kEDA}84 zD$C_0H6M68?Utci$Jf>=!=E5tx=jD^3W6Sel>j%2Q`L;Z4|hdZK@S#(hh)hTxAv%( z5+HY~TAF^8z{6Iq0ZWooo0)%K>w|-}eSr?UW^5`aj*wKqCH3ryp{iL?KlkLbMQF+K zdDk2kYQ1*2kD8iP3pqsI!l>jYd$|W}eWFe69LHQbE;d(^-Vm zx8-+xJ}ENunCwO$*FlM>h(uHNsU64i^w#5DNe9tdH~zQrF;;AfXy}?H7~On7!pZ+^ zgLk(r>w}SLA&C)!^MOm~169u?o04Ak(3o4aZR+;NEJm8}4SGnXer4EmuMTlZprQSeN(Du7YDEWG-%>+p|o|7CLwu&dhcf-cG zC<6f>hXB1-n72qp!&(1o*`r8V%BZfAbz1`pZtrJ@7mN=ZZNW9GT@J=&ceS4Oc7DmE z01cC8KF#CN+mdo$K$bBx$EnZ;UQ$nxp}#oDrM;yiI+-5*&f<#a$h@L~ol!zoqt9#K zaQWdPtaL?|cPY|S#7 z4SKXAZE9hM>N$5J=`;F9lQr8=+7TIT@`1s7umEHZRKT`2?Xv$QQ$Ocf7LPq5 zx8e#L08EgrZ);V%j*V^1Q-h$~bu1}V6zZnXy`Ej!GDmMLwjEtz(MeyS4 zO8VVzRI`#{HE@;vx5a?>te?@q8>p^wgKn%5i*WNvzYf+fn#8;97pv#Z^#Vm48T*MR zlvEtcOXd?n+BSA)1x+BfxQ&&sgQaF&Bl)k~jA}Myq@6-X^Ixh`a6B$`D##mGK%XlW zO1rP(Cp7F*Bs z!Cd~_kxg{zRoR#@FE{U!?{^W^rVHZQ-vLaaVpUx3UeXbnl+Q`#;_;V$8*B2D%KnYW z%s&?D2({R`*S zYW9mEwogoV0k2ea7qhs0ip#do=E-@zpsyzT__ZGfjVc3(LyRDx-2f8bt{A~Oo^m#? zGl(}tU+)shMBww`Vs2brPr#G{n~AjJkI{v)oRY!pAIA!F{ex^TrMDPDwe`d+vtke5 zqEJdi<298iHu3RT>D1{dG&9dT-gpV4+wQRC7Y|IIQ}`ey-3=Z?GvS~k7X@{0tjF#> zO#)D%^on^T=YVSZ0^hW(4Kkf>>upthrR(YLt@9C!K|w*e4cQ&xcsx}r=B`m!Pp_zq zWOZSod>WNVhWCA+EOJoESq4lau(>&v?kdRTw3XJVu;d8h;_HfJod(97eeF6rw}K@Mee&N63p8~ zDER^aHhNBCOeP~%WcK~WvC}}svhX>>okIBYSyAU{2zH4d&H)meo?p`3?~oOT$0|_( zhesx!9}ZiCoq3B6PCZXM4zX(0*3qxLT_^j}`ovp8JKq=Cr?l&~IaA&cmLOqKf~98D z0bPB)v;nupRLAL5Q zQ>Agri0d6pUs~$VP@%Q@IXk_*G|OpuF;oFI-^jK9aKPGx>qmp5)zuQ=8o@rYZBjMY zD^AZp5?38^+8%ulJ&uZLGN+}g3lG|B<&6yw543s9HT(rha`O7M{2=c85KW?_)goI^ zox>v1F=SfCv%XTAqAowPT?}O)zTsx#>DkPSC<`>Ju-cF`*q}EA62)p>7gO*(eZR1> z>ajp&zp?sT+;Xm;JeTEmdbIHg5tl#K0AZKwedsND&)>@|7RVBj!kmpV3QC#^sCSlFoFXS7Y}8(xiO+6H<(=FUCb#;e!oWYTSr z@VwyY;e!6kHJ5a9_dOS9ptP+;%dNbqY7mLR$Ox@%9ewFLcxL-B9+bix(Eo0oiK5lLQScSp!7g=FLL*3W+t!TeOS87nQq;nsrb`@?r zYM+iMEDx(ELJ!8{+yW~4UqYwJQWRjCw$@R1v?#2RjxDf?R|ge!OPHhM8sOk1xvx2~19yxDUa3U>)XFh+)__A<)s zdsl|rh0b@VPJqk3kz#8ojFWI&Z;AN~7;81kkNuL@q3;h%P|xa6n$tT^b&^d@a}h!V zQ!k;`_F+G;UT}IIN&kl}if74T9genMv#AlhlfGTfb-v??5_Zq)#gD)bC8F*NLs{8k zaVzL+M&zB<`aTKl*b}B$O%hekMaE-ZHg6H8w*r~r z!W9kTvdyE)e;t|pGxQbFzSlgwfNVlE>?f7xo^zU@_^(5@v@ArZ%JXmI7 zuNqfWejHZc2*rc%EM&5o~NI$=qn^hACTp(#I}~RQKe;9 zmX$JC-Q~#KTrTwuw?=kO>D%}BS<2YI94zndjlZuB#d}L@$(Cf~NleMe%sB+86bTo; z&SJT;ri0nVeD_0SXqG&m5lmQ?^w3B_CgSkB)Vcfzn+*ih#<$4En{~Z;`B4ylqc+v$ za-RCLwG!~gLWK+1PBMhq_$3e_6fG(7?ynvc7}5!*hC&*`ON}e*oC0zt5UFo_^GlN78E9m4ycKy66)NZX~YBGT`CM!NxJvwy@4w^LqHMh zE!pWbw(>Bnla8i=rCt_DQ6`Cb>dH5;kil>#?4xJN;Ury1xR1lbzU&xOe$jADPM1Pn z70k!o-F>3dO*sddBTWh`x_8gc)U+Vj1iJACOwZ2BbYVGA9?2UcK%I5JdAJX%~@ zcvA+1t8zQ)`UVz(gs~%c831zvbu&jH6Tg}&%giF-JPY1y14sEQTkyFLI!kTOwX-=@ zIi{|nCZ&zXb9&=WPaPqu3~P1K;40$`q|KV`)O&3(zdKOD89-p2GtBwpPQtK6);k(8 zR7GD~s$Plu4adX%J5r7(nWBf~&(aQZnZ+P(YC2KT!SmSwPopy9My+`6&0r8H@Fk7jIrZ%OZLAlVnaXPMQ^g~!?0xGN2Li+fBtu9LQ;1WGeTR+I-+QzM)lC% zWNWlwem9h5syPoAC#>1iEW>?i^v>Vej??L$jcO#XKQXDUs+u_1oU-9oi_ft_9c;$Jq(r*~b9ZL^pg_-H9@0`(sk$ zOb=TtzN!u{h1uolx28g#^=_Sn5l*+xYVD@0B}kj_)z{Yn%n#TE(AUXOqlKLYDpr@P zJJ$h8I>&yBnI=BjeUD!yhijVCaSl@%cyo2dkyD< zwb+|Jh*tQAFT>ixMtf_v#+U@LZ5|UoK=py_&IpfX^S4eHjGGon?x_Pd$Rti-RR%(7;6MIk z(7UZSzLe3UmdVSiR~AU99MSsjfe!>hG+{^PG(}b!g7IZP6Epxjkr54tjbX{*(cvVf zu5{i~Jr1cYn3Z#}uW0PxSldRP$RJXpdaZ3E2cd~nhb+wlRZ&x6DnWE(>0B)R_w9Dq zCS~E9OtM?!f;gplg~*H2XRX->ofA_0BdD3KP8dlzjyw3-c6Dm`XcI<>o}PyV^r{3- z2npc`Ma0z5iejlDmtbq}=OW-L@R4%BM#R?z^!gML z@sbLLko4^ZxC=TlU=-N&?C67_F3cs;fp_F;rhK`&eR7>P6X>gafwAuuT@(&T4r2^` z9dL{GuV$nOTirPLajNh8MTV<--O+_bzmK3?wv}5uVc$~H0usnC0@uLw`jUg_Gzj^zB!qMZ0$t*PpM%i_? z4RC;+)gTbeh@J-spGyE}js9LwB6Zvp1B)-vprw+$Z%ccJm(;64p~j7^en<@9Yq!k; zd#G(cGX>g7G7aIY18H>QMhpH^K=wngfSi@#CfLL<`QSQ=!e{hc56~E>qpfYBug2e$ zU1vS^L~3i;1K`rUEw$NT7}!}7UrV2a#=f$(y;zrBc?@t=-OQd6?xWHY(9f?Qfwp)C zhI_C}&-(Xy(~8CDD|+i^rcSL7d(+~fJsY_kZ%}Ub#fvYOM?dX$K6{Sue>Z=y1l{t! zJ*TWQJI~Q^t{>YI>OLNB2UCu`qf%o(%wwRd<$5$PDLs@WhXacF6oiU$iko?^Mg>|3 z3c51GpB)wnq3VC;Sc9eGXJ$EuV?1h2quBCQcO0`?-HbE?60);sh$jLcwKUw9tj{h; z*f0ZYM>0IoF0m|;MoH&APG#`kE*U$A1&Wb68u~M%7-Dpt;B(pxX@FaT%ds8?YW{59 z>?bE?Y##T;QxO|z2Z!(MX+%o0?4hq*AGr(BExr-2IS$Fpffz1e#K!V!ppB>2h#`?n z4OX-~2;BUA<8j;GxRI~}$M_WP{4ehYzxbjvGf-%5ZO`F0&nyHxB`BgH6WpFxq9`l- ziQu_*jeo~Rw>rFStk|$FZ0)3To36NZWOn-*MF2SJc!3fQOPGv4bgLMc`LMGynfVR` zClp2~`{G?{{3<1fvB2YcR5oKGScn`jMU!RfmQH9*-DMQ8r(Qh>VWnS~L@5f?25@^u9=F8jUrtx@>=J&%{F+UweGOL%cxDvnOG47CX+ zk2{^RmX89DMi!C>1aJ;}`L^lgj~G0)g$RmquMu6+qq6Jv%QdfSkTHmTv*^W{g7zn> z*!NA0^0S(A{T2+`r7y>J=>|f55|Mj6{eZElz{c;jDxHmekRWouJ*2Yox*5s%@6v{s}`d@dXL>K+lNmv*i&?Rzz4x_wZ}OI4shAm z`jiG&GrR~beqAli`>Lzx+SyF`nS(zgsGoB;;d8q)8mgn~wghFyai`(Pk=9PK_wYVRbUJ#KOdzED1!iQeHLiJ{$C4i?7K7EUe>(HyJUq?{y zrNssAJfos9_euor2+qpeDY(sZkkN!BV>@rW7AdDP+<8^aM<*W4J%eE=G4|e5rIlpV zf2vVP4MhM&oP50sKLQ~HXi7Mx~PrZg;?`3stY*UNdm`+FH^$hI}|J zzF;=vI|8EZ{)VimjbhkB6Bx9Wa~sahBp4+e3}8?O&z=0!j%R{BX_GLqwPszV&q|CU z^;vdo+9VKfQ=9Rl!xMHJkU6O1tNZ(w*xkiayQZLi2f|}tUvq5x0Uofz6&&nraPMx1 zO+1o4-@;bBwX$9~5>`s*-uG0w%pP&=M)5n;2QP+0Esx$k7wL(Z4I0dd$Pm7wsK0D@ zsR8e9v$c7#>k(J%m(lvDvrsn$?I?6A8L~R>>3*z<9LJ%J>b<187$f`IwmvV3jP%qB zmv44@kNZ!an!^A39P?+s1hANLTIUF($bj%;V~-_SuhlO?T7Sw@bX)qH(_T7TMVw~I zoSp1(to%%Q=%PrPskXdCoyh1JBu}Ph2u64SLcmt`s6$<)D^tIsaVDbFeM4XK52LO> z{(v{|8f}f2bS1)%SM%SNrt!?P<44ijv1Z$FE-@m6am?fU*i4!*fkYp(0c(?Q5nHugr)jYb~NUr1}~* z=VO|Dt`pl2pRR0Dw5XV;ZiOUYGS6BXTatGBNl~~jAE|oExnVB}$h)!ZZ`pktY&`sW z&sdNSkL^xlWqIyjuc@&S@JSG7aGWZIH#L)zvl&jg93~m<_m_}tdkX^XeIxwF7VOXk zg=8M+El(-0B4r+4J4f^TTG0?p7tj}l<;*Ql>d(IHp{FtM5WCztpg{|ZQJhZPDLOEC zFpct=`v9pq{jwqB+Rw{}$WO^o4sVZ++==$)Blm9{7dC92425C^}K-HGq+ zjYGjtF93yJ8!Ki!HmWR+xusS;FjF5H?Bt$%-qJT}{Iz#1Db(3Oe=8PaH`S>vE}b}> zp$exCCGGie;ezdOVVb79QrmI?tCVf7`cOo|S5rLfb(l9I(`1DJ;&1OOE8zvl@gQWY z-6j}d1C^DEr*Z1A*6afGsHUK_Wt-2y$Mlh_%BpGwosTRI$Y`C#i$x4Vjcsu3Qui#_ z)1l8FV1i3+3oap6X4hLjxG!`o*#M;m@_Uq^3i(9xape<-hB}}tL_zcrJTnc{Kxmc2 zU&Xy+tA>W-To zCK}Az*7pPVMpozCSh-oJ-yD)6u6%1hofki?fh`Bi=3lQH$qM(*%({$MP!=~xG^Rxy zVY|MwLx=m;%u0fXXpbbaOUIgid}Ue%^D`1&_fiLbNAnq6>h)56nnX_hKRv{M9CzhR zBzHl~d*viWu~B+`=gtLyFiUS2moxCP7Xg|#?-z{;GmJfxxnEIMxM)B7d$QOe&4XMA z81LkQ8t!WbI@iF`7hmSf{1SxjvHERWmp7=E zrL^kA8;ew3yPLjT;8aH|`yv36bx%-yIV>(yzlcqQ)IQpeC&uQRyC{rNeqOl_=v>l+ zLaWk{uTe9`U^zq8<)COy3ZvzW=Fj0VL0rgO=*BnU#q6-Qj9N#mrcAeTZ?Ir@6PE$^B)0>t#9hnK)l1~aQkCvSTxVx z0qtQ)D=T*%tb*f^DJ%g+Ptemh#$b=iSF!UcA8j6}*^eT=o>H&&;~Y;Uvo~IP1s<$y zJGh?RM5KX`1Nk6viMw~!+ZI3_s%Gl=Z)*+Xv;^voou`ZM3NQF-=DB`2<;&%E(I$&= zTf_H$?^+?KAx`8NdIkwFRt2W1Cxf2-2gMHFnNJHFcPOWqZDoTbm#ENc)9_}<{F8Z9 zSsnWFJUxHgS&)tGEpJ4nH&i}mAG+`bQ*=yD-gx-bxye;Fn2WhneO`Y;C(2gh6*~$& zl;NmCFzhcFI&#c4yFQjUav>dFeT7Pg%xlBNb@1NxHr6R4Qzq==sP2@w=!$8t6y!oA zKGiG>rgKf=xY}xKz4&aa?qr#+{4|riN*0?O{^nyT@+GZTyD=gIIk!uxcA;dA}TPAv#7Lr7v^$*r1mVKc1s~Zfgb&cy^jgrgLZp1fv!kZ zqPQX-E>f|op9Mo(Voo}+m;f$#zi0SfK{h7j^{R77VmD`DsN@-RCmU?M^NwwL+eKvf zbn}ex@-Tk&m|0I9wzktZQgWwDVSl{zl;)v$LUSDc4hd zxob7?(jq>Vd(CF>zRKF#!LtovfrS?AR?e}93W52>{=E1Zm3Kz!p&{v}Gx3xzq|()@ z+&_nzvhtnh=A;B3=(sVfsHphq0U93<#BA=?Btieqp*?w>7^e3)**1_?bnMM$8gF}+ zAo5{sZO%9Jer;^+LW)Vs>q4rJ>NKNPae8digJzZKc54^zUZ{!Pa#ihmNU|2urzOkq za0qI>X8HX2a|*?^nkQeJD($Ab(F|{UU^C`i6H$>QF_BG+-!ay8H|MlB!bwUb^u|o< zu6zmw34F%{&Tgb`=T4({k@_H`!R0&KnZc#WLaFlKs33|V8T#O@JM}qccb72T2g=5g z*$nDfrU>*m{53B-q$ODn_9IGud_4T^kye*Fe4{WxRrY~@Z-9^NV|zu-nJecWT3e6gR9M2VhnR9H?j{$M7OIJxT$8^BX(@P5w2Ua1|iiEuewix4&v%!3=v zKZ|4z;l@9_$i~vQV~3GsVv5mMXmF4bF@3{zu4DIZPTxocbIs(i0mAdPb7IC3+~y2h z_5@{RYa?qeWE2^fC!?>94sM9&KrL<3ryf>DD9PWX7gi|?En*bZEm9x3*|f;;p{c}$ zi!cI&JzcO;YFzA&>ndp3Uv!fB|70!pIc0S@@Qfy*ibd%bG)D93U4AKnUS9&}z7GJF zDRJA?26FaWuRzM0SAW%x_HX{!7bpqUO5pa7E2fJbP-e(5l8g$y0Ab-b|(%#2Vs7X#rbob)3HA-fU)L2JWdQf*}l{I{2k-x|ytK zY}!!oKqpbW1A;vcD7}l2lb_oJp^#JJLMDvJ?2Tgb9{QR}Hw(Rr#l?m0HO};GLr`QW z(7Kfo=HECUv3=|cdw%F$Iu9!3hXrv3Z-2dhQ?TbyZ_Yy|kC7+bR!A!x_k&B){jnNL zQR`CrvrwOnT?X`2Tf5kP4rUR{1$_%iMEqsDIZ%cFm15G!!|LULS@eThu?s6fkAS`t z*cNoA2<5ZLTMD2gO-!Bx(fs0o|0}0f2!r$UGSiGCY{%5)A4k8$tZe{Ue7;)m)q4gy zdKSW2g<^w)5XM~MpL8=+HM`e5~NqnzBG&efNXj>rxG52HpBI=O+li1-y*i3$g> z=2TZ8@;CHNx*eJU@`_OeVGJ<_5cs9c{IL8xVdwu^@L3-J(J!OfkSZp@Vt4n^tPhOq zqZPuCF`sU#UFTe6PIv8LN1v{@^;*v%MX3`#+4u?6a@hvC(Y~rFwmGBF)zw+3YO__8 zixGSCeg&EH%jD2zDv%tK%IdS6p(lZXE^NP}=U(qmRv4L>uqh}gi2nN3>L*nfzkYE$ z5b}0cUnfs^_^+_QUrqAA|IOb(@MHXE^Kj61lAEi?*ueXz%1UarR_w76(e{fUo^F_h z`?UXs0z*Nc8UPTBm6*2t@+Oy6+VPT^sl#D=pea>gz+Se&Y-N zhd*1-Pv8HL^jC#_CSt%8Sf;cT?rF$Hbpw4}f?HLmcN3Xj3c75c`~A)9rLTNZrGSFA zGb@FDW$FKyJo-abO_nqTXKbK~zL&}=Nn&9zPtQ>Rg-funIWNh}N7_YXk`yBLpJ|}} zE)JDrFi@N_!{G@!n5MhN%31uF?LV*-g*PSK~7Cc4^5)9X$l+xTl{gm)M85V!-o%XI31$DeyD#v zw__P4P)a=!MnS0HCB4Ff%F8Piw(QkaRqa`{Uiztw08@Y-0BY6J+&swuOQZkiTm5T* zznJyOJN^aVpG&!&Rwq<=PE+mf**2{WkMs|`_<7KL-?`h>*IQb!vN0U^vNn@jNG6WSE7fI^`P410%%oc!xi2 zid>{uZlWA)^Do2*jP>__q^6}du=I#ALgr%qj=s3z4!&$Q4F21}{_lI{zs|GSO`s7S zj$@_wc4edhurS9uqj>FxEFtUwJ{CZz0CbPX0dW5UTEcY~DxWa^ZnU3|SI$d%IWm%u zOKju?A>zwt^A1YNtIYIf^H*&sDc@an0q=iO`^{SV)tdu*MdTwO{mS@cJ8l|rJe0A% z)5cCKoXzW*_kIb$>-x)g`P1Y8IH&SATF)&iIqh)hvN2<1xw&*-&lrU7Am&@UHGUAX z@6=xWjWz?D7U<`uHFG!lvBx_(ef@Ex0s6|nqrCM~ymXY&ysn9}4ZEI*Nf|D#kyf)- zJ^|3&=Ei-QUu}{9^;RZ{7n*zb2$r~Cm@NNVyMMl~{WH0e&iR`6UOD7l3?krmX^?=x zNzRNJ{f|)ZCGodi*1r+<$rr>tu`@4B#KQlx2>`K>t6nB1{gRT8`DA6M98P+Gz{t10kFX2_FgOO- z%O+Rtw~v1F0)Te$)3^8b9@tu~^G}?rh)YXLuj6W~gM(NB?Y60M4{YCOdn&{G8xL>X|X=oi8&*d8n=8kp1$HjOr>X z#pUJYj&+8(Zq9HiL~ZRk^5F1z1Crx=4*_UTQE~BLLA!@rMtXW*Ei5boAMayMLjF1P z|50(+4dsmGI(7xu&0m_o5lm@5dPomI^O}AlwmdTURGP>1s$!{EOI z81=7G028sUCc5@FZ})t`_F>#U!U416EXBSa0tZJ%8-nW zjRC#8;d%fJTn)hP;C_1IRpq)muyN!FBKuo|{YldP>I#48Ghh~FJo{I6qX2)UJ*tM$!KtMy-$kiJ`hodspi1-xBw~ zGqm9y0D1T+?Jv?Me>H!<``c1A;K)e$z8!n;za$<2WYbgNkO6ydGykv6rO+~fqSkk$ z&`tCI25R~jHqgJ891Ls?bpru2zm>q>&EP)=@Rtw-yx3m$7w&X__mba!uUrEV{wTJS z{}-?8UvK!A0@dvWERM@5T~WYae*B*={_9WcqX0_~wLdHVza~K@y%(DMj%3WqFTK5d z4KUo2lQ}S3TePUwmiLU`rT>Wq(4q%8Xcp$?5BXcd|Ce5-`zNue3;wXsBa$>0s6VW& zou*+2u}pV#brq3axH>wLYrxIlf%@ONo6*m^;YJHBH7Nk#@4jg`-VP5Shp|r+0M=+5 zfK)*C!QP!5Ps-oQ$#2fVe;8IKn?Dc3s5-`!?-&!R-qT~z=;$-%^ zg%2>sXX9##(o7`FgKp0rxXQSuNON*F0R}-%PL6G5i`TD8CI5Y@{Utc^0cwfttgM2$ zIa6Li!Lkw?v_1rqXz}u;zOC(g{ODtMlHbkY%bx*HcL*Phkt*}`Q3U5>Tc8_VOi0LN zetv%Npb!8F-+v~$w=q!`9JdskobY$Hf_lTNo^yeW>lW5Td8 za8}XsC4o zyY`M@-lC|m)NlO{K(72N1d{LzNW(nnvUNZ}fI9$tjR##P9#&LzQ*@EeDM z$X^NAak%}rF*V#taZ}1M0wv$Sf8TyP9RL8p2MSdn<7ug>1D`Gd{yX4)S_3B55 zI}_7gZIO)M8G-dDm)|BBS9MMQP5WWbKrXVlt@U(?m-F1Yb7H{1y62<6qsCoIu6NkH z-rJO58T0o%UH_w7Nyw#$-~$8VJ%HT-fbdR59{F6SSm*tX&BY1ySE}&&$QIJTAY5Ad*rXC)XNct=6K;X&ew{w>I>T>a z7L?R9BN7LV)fdx;M29ffBzoWG3pG29YP&6Zh1_8?q*Vx}ess}9jjri|F5`5+GsO?f zq(dHO@m;oPT~JZ@{|S=k>*G_BT_$;>iKf!#rXnaLTf;5^eg&hH>J zmfOf13Ju&F939}RVH9|O(IO61X<1%2TxPuALa68!*e_7-!|oh>uYEqBTz(qKm%Cxt zpQxv0IB)g_0O>p?Pc_~)2K=%x7}94GGZxO`*GE#2>q z3Dp;pU;mioquQ>jU05*x3J{pUX>|<^`KS-8q{D%fi;{}UXYFhqrdRMHmGaQ0D?(%{ z#G3Vg=STT_k^s)$JPm)C`Ollzb7#o`S-&e+uCxbHGZ*W>>{3@&jtmJ2c_6N7V8Hv6 zXGRMQ=1uCw0Cb-n4~%_fxRA9;sM+y+J`Z$bR91DD&LY8Y1EYHd@dnj{ILW?k}LrIzJ>5(hT? zV=*Ip(bGO}L>#*2suaS?xoXySfL3ph+@3C&w3YDLO)V}i;mIj;?e+OeBx6ME{5rVD zvahosKC^#L}nS9q*wdd%$^sSeXB12nIh51 zEeck4R?aR)KCR4HIV|>dAd8EzNlVNO6zn6!xbwZh8Mn&-#A(fP{oM5`BtAjcKF8M$ z@iOfw-ShHEH&0E+BD!id>h%3$9;>}nf>=K_sbx+rkRSR>_tw$_S zwd-=8Vs)8Mp7QXfRwFguD=Rlopw)@7cb{wv+3a3Xx<;QNgyg;!i%Gs5do?;r>ABvW zFz3}xNp22~hRgI!f!8`eE6ukOyB?f1n_926ytyKgL3Jx5Uv4cwjF*SEh1~9TbL*4+ zl_y_6NkA@tKMSYVJom0g*X*7ja{T&Ou}g4qB8G0p6e9VUcG_j@v4GjP*C}J%X;d!y zZ)FN|)wmk+X#vFWx)C5vbSgJYR%7hOOnZ=-5@=j@ zu@=&oo=(HI{j`RMpYX$zbTl=CVFEg>FTy{BzK^z|=jQd?(Dz_GK;lYaH$-(6k{>ke zHXBRtzsqAlm!66zx~!{?GJeQsQ)6K4$SS-HFhLFw?r=#yy;oq|U0+VQ?W+M@IBUH> zi80p*(#Y8>VYiJ=y}MJn9Hw}Wie$zC1vQR!M3ws;*6PfC;l7p8`P$$mFAF?(kNmj0 zAWI2fWQ9|E*93Q(K}zMzT6XoG!>5rG4m+3G21#!Ps21LRCUsEz8~yx3A|6L_qQ(Jm z8r3NPwA?6Zz*!(Dn9FY%`h>S{Ip+K)<^CU^)%T$M{FlVfSSBb`{bhN{zHrFO${J5Y zw`4Xv0D{?&*KwOzza>jzoE<==L`BRI{$|jDIbQXw?CI^K1A2)H4gKd_X2dCB7g_FP zynk=OexpoFZhkpp)!6)@@7~FS^Tc~K!L(gfaboQ)kn*|%$R~*0JHD}N_C-{fa$Y^@p@nd~PO^eD z3Kv-Aa=!KRdGd1^boU^2cD${hxus~<1BCa;=AKYo%V_0pPuq809%6>4YD(kl+`G8$ zkCZA@K~@9ZMM%?`r4xF_W>B)HkCfD&RNH$-Y&+1KYzKT{uGI5zbA4a#w=`tRXfkG8 zv;XZiHeN~}2k}#I5vhNU)}nA7x}8ZVT1E^?3PRop+>bf2c;uG0F5&#FarR!V;}ZYw z5jndC*z)3h5Y=)nvD}s*{H!`&E~KQiB&7#5>(aYm6?;fT>^&?-LB=la{m|W1K$$p9 zn2**n;bY&lQ-;ko9Qr}V60XcP0z6Wig6W2-1zlvlDOk5x>VxmdxWy^)=qBgQW7Hyj zV;1Sb8Fmbjyrmk0RzQ_XrJ7UJ?{e(+wo>oTCS+TT3}sO*L!LKK*Je6ce^#CFNIG+9 zdRJqB{h@EJlJF+zB!Yo;`0Sudy~DHl9%AN|X*@$zOveRO4Od_qX+@kl#W)L%$8M3I zC%dba-}1fa`x(J%@gHBxRoA?gsx@}1B(Wb=m z;2RumxR6o8hSAOgY_A~jogiU9^V7CdP5W}{m9GK;I_48C2q{aLHA z;_z`FK?8nR=9^(ZCBCabBPj;1CqBXAMtDvp$ESm8y_9Yw9R{^>?awQOt_BfCjh zQSse^3CIXv_>H#r6-^un?SYI4@7yH&5j$kS|PyG6&jMnYA{P zw8nF!G&K%S)n1`nSTd4gcsK>0^;7XPJ7^AZI{vEJh6N>B*=mVzgQsDSpMKo0?`t!D z9iT|8(rQTbW;w^mhQUc~wUDqI>ydVy@(M-RW%H%uo#OL-B;Eoov>&GpacnHv23JTB zAg52Mx4AK*w;2}K7HY}q_FYi5w{VlSPd6*w0zDf#z69QVlz-M%ca%0Xf{3{5xWW|z zvOK=HBFnPbM=cp$CYFQtvWyR-x~LVihZ$}gJaVJP`b}O~nCii#<<=-f_r}r2Y(n66UAAp1yLN)yjrDB{y*0qjlMxRxby0`3ncZ5?6y>sA4zfVP`?t#B5ivQ{?B@%4-~cr(N}`=|pu3u;m1gREnA0 zyYIx$renW;y#oN*#Ax}Tg_vd}4i66}%~ML}{%=+MwVnL)o7`jFkrYRh|8;NYu$@A{UI+&7kNVVaR1bZ-MX@x5m7;8w%hVwM0J z7ca5Ixx5{2MA0+g_BC*PJE@h0UDTc3k|7?PZDVsqOeG}roJoALqs^-)Zw;GITsf|; zju^S5)66=4ZJbOY2^iY(PAb4wJi$qTq0YkTHm>xuTYTkd9vI-9~<8qCrNt}8n4~@oJdAgwQ5`B$tiYket<|6_k_HD ztKq7A4j+=Sm^rh5BlaVlb$0$s`x7PhI6h6HSM(wdGU#NrK~g_nBu`o~QXV=@Suo`l|hp4izQsewn#@uhep3 zbZDzxlPoTVk|z%?%ew}0Gq{XV9jAU!hm?tb5c%jbG=qxbVZ zsDxttE^($ZIkml^bj@6bw73~)f<09GC*Se6isE6S8&h~!PM6+_C9G|q(&agbtg*|P z#iw)D3%95oWx^f3xep?Z@U*Xvqvl4LzbS@~z=p8WAWLYkCw{X4hSS3B9gb^XK$#xa ziI%fsjum`e+3QmyE}PtXu;O1>L_f5|xY51ci_s?vxn%PG?c07E zPu65^^=I@bHdfr*Rf_?{3Y`iY>hq`pNm%j07ApX}+E_KA71wskepySDDd2cX3o zjJ~3uA>{n*Z3n+MX;NI0wwUnAA$i73a?P7mPGY3tZl$%_#$m0^TuJYP8XKasu6rUmTDPov^K^puzS5dJ61@L)&%3eU zO*m!iB^lgpy7hR^qa7`!yK+rBl*|k_X{X)Fx?}HYpBTCt6gWnN1=B2~5y7+75z$FR zuRa{P^OGLkFLOPXL3^n+v~s+ydCqx`;drUsGvAfS+qOaf)p{iN=#~jD}e9P-A$n?|6a7uVbF9Et}{-CzfP>NeqN-}nEN(v;YIF%z? zv_?~&T!*4*>GeuvW1=S|q7*76@*@vGZi^6`57SmwFiKXlljV0ipRsxnkH_D57xVv7 z_TJHKK79Xp5;L^6qD3gBYSpT})u`EOtJEG6PNKs%is z^jZS`z`a`aH2%yD+sVOL=*KBC5nj(x)4>C24SVGMRus!QeQSC6v13)^og>qm`BoKw zzG*tSX}!qp?vz5hFh0CmUHD#uH8V)YGxfO6GqrEA2k$PXlcOME?)c$@3u*T3!2`=r zo}9k{?})Gs=gu%N}Ryijd!e$)5i&z|$){u+y_ zQZ0eox@kM}PcAnW>ka70Sbjjmhhzx$*Km9gt_m0KzC=9qR8R~c`Z^*0)(8h0jOcgQ zB?mec`mMYzdgX1|u+>7A2WNOkO!e5${jG&%>0vyhj>~SZIOsp~?ipR?3>NyfK1J_X z?7=h`=J!YHDd`7H$X zTHAMc@q05(1EDy9QE7&?k(Y;8f^oLxsub@;xa~r=LIdniB}=^D2|mmAebj>1#$rCg z*mehAGu+aOQ(ph=*}=}l!LX`(QLwrw=X10oIq~=R3fx25 zRv}aObn2b=B7qPeNI#0Jm})U=yWQ2L5mS>Md#z`E45<|d zQV|_N7?V8B2;If`BGS0IY2WGbURA{RpcyjX=}e{zcDt{6-MndnjZfhqDTATw9`!Rq zqK_+5$VyU-5PJf-DmtHBbbFWF^;3e`RnmGAy+*EST_$TNF-p@_%R$k*VPpZX8;J_) zv%+|JQb^{HX@QF6)aklK+t-MHkQTD{!7f#q&}<$a-JVok2!BncL-ol-EjMKfJ|U-w zL}t?b(9*ye7TdN0EsczWf7DDqAVjs%&313*=!?IY))nT)fXYI%R}K!vNr~#2*rOTT z;K6CRDujC5<7S7?H`BnpqHkKUfvVg988zg>?r%}gbQk(7poh&jcK~-onU%MGa%?;D zMW_{3kH$a(;IWyAn0DUCp6_|`{qq&9KGcXo%Dz9be1c3!o@!5BpLa%uvX zm|Dniiw~|+lnq_l^ex!Il}QEO3x6QUZ0+knP?Oj4ElU8OJYA7|l`91C zn3lsgo30cX65S7P zFt*>~sg5@s0Op1VURlh=Ze4T7=u2qqOKS|f7GJ~NWPqhoM+A0kDDpbMv^L8##j}0` zklG`|FDe`kXUvPA%5@OXfA|GngGx8WuwjCg3iNTZOS`kb z-b#%v2QH#FYaMzH&s*MXdXtksjnlzvT(v%Hm5nPPpj@)4>FW}xbkTvxRSQaUny3Au zx0V!&c6n$H4T~=HEq_2$Q3VzhLk;SF4u$sM?k8ueUDCvaxD>wUst+aX3z}>k8wh@d zC83j_Q>9UaejWCTtO&p7W<_eYNGS8>!S-hI`%t}Pecr=~kik&f`@fbIxjz&gZ%*bB zw5DORtBo6MpdOXat~VzVh0SsZF=Wf)a%kLs5kbW|UT7XreZF$yr_T+2fJ^c2){4vV zenmq4to1zyC3*{?itu}(0TmBY3W9aX5p3=mXTuE=v=g5e&Qn;_b#2zyoVTnv_Z%jP zPp$&|>Or-*t1upV{QUVgLz>1C2Zy1eHC}GZ`?BWd=WW^s+~Fg-zrlZ#6>|4D&;#!7 zv#BqiTSqu^aSO0IGo|5dJ45-{QlxNGBE_p1)%vRUcVRevK>IU!+BCRnf7fybLD|H0ujWSIDEj5mk*xj zdJmvlONDQ^U#P!^puIO6h{+fjVg9O$zqBE-NJ5Ye`wT}|6yEb{xO-(dM|3e&rNf)_ z%*dPJ%Cxi|F#{}7=+=3}NMJU4(XJ;%w7*Voy4+qEZ@La=^D~0%%Ve5K z%{EfF>?)tG=NE7%b;sJM$Q8JIE@>(_Prh${Lrn0dV);cu(T}kn({TmwiIy@o#(m!K z&qs$Ri2(dHc740=fo^lT(bQt_Eb@lXtC^7V_HMR)ly|oxxQEm0=haaX$3zBR=}@;V z5c)tDn+m>h^eQ@8Jxv9BuCmmbJ^p4x{Xf`5dD*dZN5}c!gB`t-g{SiVgE#*52-IA} zzUbIkF24}n!K8D2+-Ne}bB;2O93*RJ$W`=>y8Wri6@SozWZP+Nm}WtC_Gj#&TSU3< z2fzOFGZMQ>+1{~Jvg)T1adju2Yx17&afRWw?*LLPzR!Y99Gb{OG2;PJo7MW9?7S@5 zTBR&qJ!uIb*dAA1oJel6^1EX09OvRr$JEc#$vT7JOWZFkX&CVb-!Zo4J@$u~1FOS$%sDxVD}LG!W8*WdO4QfY zee>8*HRxP2Y==GwUs?Z9`|wvofzoJ9_I8jeK;;Di`EJkZN`Wcz6mqcx`3=smnmPDO zmMu(ZKgq|Qkb|QS{<@#VdO(!@jlzyPA?wy;LNGE0-4MO_1=tZ-rjGEdm5d%M3|ish z5q6rNkA-BC<_H(f5`etvE++&Dl3?d3W?dd1JDwJnsC4x)eFhgPP4U}n^*dc zIvk2w6|nVF4G^Zy9=5`GcY4ZYCPb3*E#sB;oLn3dHES}37AWrg#W_F*d2UUkYZRmw z;EU(I!*ek*2NNw#zjsL7)6W)5oY);zF@J76dXg;JLi*u*;QPhhuipjt99_j5v1du` zqY42PrL@aI)?V1mJ)E1P>ITh3*oEMY|Aej<$<`PAR$83@8iFWcEefdHPzo_K$;dLe zHX#GF#iZrr3i;X#jSn2{mKMU)_>HvEJF&imSZ4|gU5z=*Xh^Jpn(onw;Zye}1S5hO zEyw?h%$>X;c}0?8!B|l*;T3u6B-HuQMj9N{8%7JJz6E^ke_)*w?A2zIHd0UjEA-@u zc{D&tCQk3rZw$dhmuuccv(@@EZCm>~dD4m|EISM4*!T4hr?2+jZ00TZ9wO2nV93Z@ zIwuWIE%0GVmjkzncWAzy)pf48V6^s4>p{y^tr=?9Rc-zCUo$XUhq3piIyj%uCEMJX zC*jAN^#J^YlNY%xvfztmsJQ?ZFJ#%aNg>GlJT3O|T=~`?^z=`sLJvpojTr$wSgp&+ z(idnv)w}q@RKA@_UaPS1_CA|Nz87Zo09MzxoC0-BraO&8ZYmyyPUzmZw^|Ns7X_`lWf$IHAE~rw6n&RbBywJY=_I= z?@;5>`COVFIpABCX$;SB8+0d1srq6eX3n8PE)bWt6=2;dckunelv(VPN)((_-W@qf zfcs#Nr252FZsY;qVVt}Yd3nTvK&U2>D=FCgJ6mSfha@Gp=*~lPyQ2?6LePcRelPW2 zF7busL_}%?avqv>x762XEpc4wO9~BMzO;a_xBjF8#k}yybc&08V~4x|?!=Ve!ToK4 z4rM%kw64Y+5$HPDctsdt7eX>l#BU*ZD~0K#CP)8%fYs21zR=N%{|F zCWw3ZR&S)hS&)YpkcFgBbI+q8>0)~Y-}o4uxEJ;lB9m!hvEJMktC~veArX-HcYZ3| zM0;K_+mUN|FuMmdf(XtxKdf6mX=n( z`?lQ>%cZV%7vzor-x|H2?^nCEvGI~i_0_ki6-r8Q_MR&0_se*XTRQLVObT@z`m{f_ z0r-=f*?(u`sncNXb0w@Gv+LW>hi9^I@M2bqw%I z=2g8F;#?fB*u!zD-gbX$bf`J=h$nuA_c zSAxU(&qEtS!be-b_bUQ6F;Az|efK}1UuJ2@&O4zW$xc|UPqIa~b3I>>b?SPIa#;Y0*|G?%F5>t#Oe4UiTe-9TO^E>0BG>L$x9^q6eDr)h)|w zkq7uGaa#ZzZ^VURcAxrrT>j*>g}c2424@wCtG#jHaVagmHU6sdKtCc=>MFJV4~Aed z(lr~lFSpf*Z*;dB06|ZyXV)4B_jDaEne1p*PzxAnaZmKQwfFnlphbGKN|Ay2d;Vxk zo64jyMs!y(AX9(|XELuMCDKj?MiQ?zD)oFoj7RGrJxG0Af>-$xOocbNH)7;m*JXc2 z$s|`uvTXa`o_KOO;wUQZx?=LNeJ#~tTkWT-X5mtRMHmb7m3Mof`PQ@fs~;#-@ui*e zI2A=TAd^ov`7#rIZgTeG+DPn}+{h=R(G;WCOtP9TWyq~!bTC?gD1hojKwI~?Ac_G8 zNcjvbZ#xT*lvNL|G5J>?yypF7n=h`k8YjQ9>x>4aG+|Lw?30rHfS-lE1Deti_Ztfp zsul1z9uBICjHqkmSL?;z)mPZxKG?~ys`zqcLp!==z`M;JL?5rIqlI-p~K!xeXBzK>)Rgic!VOqV$lf$Cb1jz8v zWBmfZkze`vDi*0n3CFloq3)#Agz^1WRraEw)m}cb*d;dfgRu{tsY9jTb1jCX$iF_} zL!#u`Y#?QQI?IB~OKZ>yR4`3so4X@h)Nk{)8)cQSv&kkBBYhlO_JIW2dR=zU(3b#t zyTn;SUa~|&!B-@D_Xr8Whb;kkV)AIKG}>iXla%J(vm3-Voq203(0=t;u44^xhGT_hZOEXIQCW-LrjQ znbbw=(KyRlZawfa9jk~-*|T_a>=?q*Y)V-{h%cqqj08p;Uz6LRA~qe%xZ2bt1(~{l zV&Cd#$_U-Enm@Q*QIjyv))06eB^1tO1li>Nw-O{ZW4dh;ILviGuoq2qJPnHshxwfq z%#W46O(b_%{Kq1uBUw@Mu3Q#$=rvliHvmPw^zAn>HMRJB+(MjX<)!=)#Pr}({6S^? z?APjBkz0_nRXw_}J$gT_QFPXN^oKRybJE;}tvVxUMAm&m?6_OLwb)Fdc$u8UHDDTk zVtxkSCc=hQg)hj#u?T=%I9Rd%!}o_`vR8y^TNwZ%N`7I4#`X1U7rMsT`U z>9M%wEzv6y4>;`5L%-?xe<^9CxB`?cv|?5D7|K~?^BV}(KeRx=dLOunV#oq=LoviG z35UC7e>77SX58Tq`onFZwu0*!KC|NQHlC{Q^H}Y964B)Q=FI#GD!GY~H>E`{8Sw3L z>f1H0-EkZQ6HULtF~82;zBfnu72fYJXuubtas?~l5DFS{h%nTPjRkU7rkcmyC(O^% zu`9#;nI(oO`H2wK1RV<1QJGS`t55Stu|^8vgj8+7J$k_9*uMM$i{5pzD4};?tA{cl zEO4>ys{Y1qH!(wJ)mYd)YVQx!ePyy4@TIQG_bN_>d0V{vE2YMtqQqaJ1vKgcOs0IS z&CR7&b$2xZTGq;B-HA&{RnRNp_ z{cRY18$P{05+F_sU+gW=*AliJ`uc}5j%A+pLc3%%U%hx3@CoRbMDsbnVh8J6@x8@{ zu6TzqAGML_=Z}wyseSd+gYN4dchqTF4Pyw&E{u;I@P(1Tff9&Q94m8YzaL?f!{;br z>(yRBJjGXic9OGc8D9L`DO|^v>AL%7Doc#wA*7h^`xi|1aa6FWvNJ>YC3nJ}ht9C%oq9 z#LHc-uDJaCaHH0FtNDD1-B(+9`hsw=H&^R1Y-_yTF35$Dn9gE~!=e591p?*-WQ2@U z*#LWD(QWdMvvX6mTsvX+COW)xVYgkojW81X>aL)a52Gwpdx`-)MPA^oM>r{AsbBY{ zS^F&E2b1BymW2~MlxD)0Yb5#9%-NP!^td5@p9DzmgvyMET?h!gV(pDFvVkrooTvSH z%i*3~ig)(Y9=ZITof5kXx`6Lea$ce^ElVm6HG2VN@~zA zkcLI(bpLqnc`c35wVS|^f}~(>r=1ruPQtz|{(HKK>_WG4?zs6}+^9sxDtQk*vAXU4 zqr&Qk5z(lii}Z+s<toD3nwr)NH}Be}23-yw!Nc zEX~XM-w*Db!Ss=5f+wfzxpH*B)xVwa=EzEz7kfXhBvK1XA7SiHt?#7{qC$C+HFn>< z<;vOZY%_k;3*VEWAx#y|obw)BIiQRR5^pOlt2@qWJ9TNeI47}(FwhKk-vQ^bOs)vf10N;DkKCCV z4?@ob%vFXKm+#$F$lQ$&pBR#+C=!|v)FM!R4dUVVp3oK#RK?FH)6%*sZ(*A=Bm5=o zP0Pe=8Xxk$TIJ7?_bRY8t)va8^F(b%4_nh~`cCL!V41=Nfm*|b$^nS=pHj7wgK4G; zg-WamuAxcgjqagn80y8wwVBnNOA&uf;R^Q;oC>*TKC{kDe`zbz%KrrNC6U@Lf`30h zKRrgZhE79_t05}Z^!TeULuryB=>l<0QMD`)KU?TfLfwh?N4C&Uv*OiP`oAwNEq5+A zn1J*sdo6fFtv_H3wg#1P@|XLluM8Jlj>Tvwg0O9Xb{U}M`d%3s`P)pR;(NbC9wYat zE>l=@qt*Z_yh-0guZX1(Q;s6G9hkAkQ7d9nB_#l>XSbf8&qbtpQ6WRfoE3D+*v!Hn z)K3D`5R`o$^4~vFB|{5#mv&EN;_5(MLIa)Ugp57_kBx(s5{z^bI8Yh4=RqQ_1wn}F zec-orVyr1Dsp+EOfs2nU8v=85^mNx%MjS|tUd@R1%CYVn8^$UMvD{-ab{BZHvfvF! z@sqZY!Wa^}=JDrV+Akg|6ab{5&Brr9?wH@vE4pw69laHKOjUZpmIPG?nAMK{z29GZ zJ6%3-yY!&837C5#^=5JNhkFnM{8-0d_v|dAL2h9~92&Euj8RIC)^G*Xs-|X%|2d|L z2z7VnDe_`Dwr2Eki=*VvB-jL%l~O?cZQKtg={W_&$O0*iPXYv4d{xfd z?e;rrB1ZK-v@tw4G1jhuh-r%Diw$P2=PJ3qP%CFx)gB2aUxH~puB8LDLGe(J-y9bywo zfjv>kQ4dZ^pqAZWD=@#TN359U4#oc>ZDE!!Bd{K_TX!{|#M(7Mc|GUnvrBI7^kmz_ z&z5%!^|5FyQ6lqEuU3rm5Ie{9>Zh$*y*Ux%G`Gm{u#LKN*2Gq!6}~0HvK(9JyDUky zBPQ$b0#$%x8@$6)xE?}$i`|4@0UgZEs?V+P>S51-+Aj9wyq|-Yb@sI~v<=)DY~+b$ zLuAz;!|PF8EyXpO>C__`1+ZC~N4T$sill>M5@aKGFi6#@aQj$0U`9YZ{%Orzu%YzA zn1M9*+x)pcW>35+p#QIU-hPS89!Ia3VE)^?*GwGj;7HrJ(mQ*uXft^q987D@A#O@i zbyv|(qD&@`y9jzEA13%5>4+}Q!NX-bw3t|e6ksOuFtnmy|Ie1Z3yHYy&}H!I&_T!H z6Xc-~U(hh>sb}QCk$y|tDh=kyR56Wo`z;OraF_SZj!A|A*WyXlt*Qzp-h?_bux0qk zIQuhE3`dEUPft?uA%yO3cmW%lDxJ_&q!nE4A(Fuc^T9mpKY<70PRXSpo zusQ2_rK*(FeMml`)JXK-uFn4&p6zT$`#1mDe_avkasoGQ-*xqh`c7F&%Xb&FaQ&q* zjYrfUFhCJ(rF{E#75~1?!-u7IpIKiBy+`YT#uT%Fmhbd$pZvD4+ic4uFpqpNR2W{C z3mMv@b*P;oGjg9P6z0laADNq~eoR`4o&e|X@l~VGnQ|R@C_nUc{>-XYbbvm~2;c1! z_It?z$}^UkDX;s0G)m2LvEBD>7JAer`>uQ(Q^MI7q4xR#t4e1p)3YFK zB5dFoW-o(UuLvjq{+UbjjBMRod$M}ck(IS&3Cbf6N9SF~Q66 z%c9UT-o$gI+d*!AA7y`h`;7OOnzk7R^zy!wYRIJMNj6lR&II!msF|0cI}wE!A_aw= ze?_C>C2>2xHkF{9j}qP}wFCPh9%}Bn&{jPw;3tgZsfodBHQywO^3T!c{y8t_nx59~ z`gAektuZ3pNU(no(5MQqZ4@Va3B6|G_T24FJk-uwNLUCwZ`2)SepJHM7&RsrEFbm+ zCUApk&wyir7fEcb^Ui-cRc}#SO&y9&P<=Vcv|mRLh4M_8R7H)r0_aGc8Cf*+BrRl` zdpU*YsITECf!mtPnVFBjAj*O7^Q#cJPM{A_L$cAt&b0WM-w5%*&)x4#JZ5oc$vX$i z$=IlsRsQ==f*`lcqer_Ngo5<`+%XnzPC+*Vr{DLY;Q!wzbWUhHPO-wT?{wHkZKv`bi zq2)c$(dEnMif%dK6tkjX{@FER(1z6A3>UghShEUT3l}(XhRUnzIBqssaQeWGO-y|E z>r5TeC|?iHXf}`8W!>M|q2V1pSJY6~d>l0}I~kt~6fL|Zk#Jm`1iqt7GPI*`1R8LHWy%C1Q zh1Umi%u;Vg##9juQlWIjX&)r&V&!+%GGgUfj;hDiH0K1Lwr`Ta&SeO)Ax!^F@A{gq z)q`5@?pBMQU~Y!8YP$_}R^Es9YRII&HKtJ8;i8t#o@7TxkCC%)W#vubNk~(6$3jIK z1vNS%&S)COgZ=v@PwB?|9191t6O(ZWu(Qb>2OrSuULZ^H?US|P3u<|(aG?N+r^P!P zCnYVqi1GpNe6>65Fih5X_jd`jjGr)cD{)7WNP|AM6m)F(3s)w^%iuaCiFt4N-}w&O zu*7HU|Fm6YaIc#QJk5)6Pkw7eZGH$*73=Y-eN=J-w&K>tV-aL)ml&SDJrW!e^l<~# zCQ6HHx`L*_NRd`slWd0TaQ|Yf{wLP?zkE!X#}RX}GHVKLtSiL3Vq{^}ZdUkl)`{xL zclT+do25jU~;W&ok=5=`{Psf}+6u`Z`^jqFn_m zLx7)P+1OFXYnm#v+;-Xg!Q`nYcmbOO*^(+xM=ve``o<#M{&3iK;(5&SH|roT%ZrLM zAjfc=a*<+H0999h&8}5rPVMC@TE`o0%5lb*$%a3~h?!)2_@pVXiHM}61&z=7^9~Vi zRtMiG7qJb={6!nmJRGmGJN+yoGTPkoGY6?%ZMVVvGX$5GLj->ezBN7pJXH-n?+KBl zD^ltB{mWsVE|+wCqAy~FWy6n6;cG^*ChrKw;9NOpjF>NKRv#2k!S-a z;NwwneR&hThI`&aZGi&qP}+{s97T2}MWCb75w$gSy<%9SNIg*3wYXevvAYFo%D1%M zD32_Z$;M3UtwyPU4H!*Ua4$|R>@WL-a9_kRpRcnGsvVLYCMR!ncXY!8tGD60f`)wG z49#+0$;ry!(ht89@lssFN^91do^t4mq(lP(sDVFxnpq zXoQRP>eWUUr!ldLiWnF`jnM$vt;_aJU_Ot;Z}t_@KlW;3yL5GluUCw0HIO+!4epJ^ zsiM9^xA6BFzk6x#g2>-aJHO#>*?32Yto>%f(!;lJ-%-*|(d3TZC;)^$wnZ$lv)|)l zVW+(A-)%4H%D;c_bK@Hr<&(xM-aS%spcF4RH;R4RvZ{Po73|%&%ft&P&u?t2w~Fx7 zbfaj#H9adIIu^lp<7+!hjT}Ck0l6M0nrmR*ZqfdC#1-Y%*U!{`1JHGCm zv&!a4Jw@b7^rX}LdsQ|P`(hPCguKqfGn0(W{+;QUgPOHbi=Adw=Ex@IKq2@>tLrv* zV;oIMICt>1ecU`-XriKeqH1cN{0X^~pz$;fOISI!|VXttjsv0gpMvYL`-l7E5>LY6`T|7V22FfhKeiUCLvYX zuogXJ!SAfW>zoERtmz|zgpQDbJ5grghXZ5T6xAGioJJ1eNc7k0r0f)ao*E@-Y=3z8 zPU}d3mj(+X55zxfv?NPPGc{u;KWp za}RFjZz~RHGpd$GD79hB1QhY*RJZk>hmf^UuNY>AT!(UR{8MbjUa)dR>wH1hAEl|E z&NGr>939ke?)s9;5LICBhCJT2cu!sv7BHD`z!U)sFT^lCBY)J!lD!bqTyx8N&n~1KiigjS)Vh`mF0!X0@Tn`7YAA+DY52_!I}LG3hZ994cdngKD zgN1$r?6A%-9f0G-W_eXFxXW17e&xpA%9krVJh&9L5`(Fdfnf1=PY|9g9?#n|-Oi0U zn`?G&deMI7dhl*)dE%H}uK0C9#KoYtUH>i7<$$UdP!>rWYBgI$TBFF)gbAJ8yd|1} zn+vh=*`qHacTUE58|P~=SGB5HM75jDKydeqgWCUG^M9F6zLh?B+L2waS5Ct6D6jWL z1s9hq5$rY$1EFzhjsFa2YevdS8Y_;@H^pHarkl9G^MIt%o6vC~0Kt}1-WyrJ1MQ#w zGRB^J(YH6;cjEEXrMpxFkgE6MIyDZ?zj^^B%&+I7YmgjUFe7QrnIOn~2MVd0TKHNA z7_@u%=3q2<{==oac8W>-ajXTV(0qeiXaT7xbIy96d|Wh%T@I%N+j@jn#O(DpUdz3@ zBrkcU=>y1xA*9X6mgpH(iad9>NPNUVVFZO9_03-O$wLL4(Smmi3{4X48I&81$BqAF z#iGL~j~uE{-j!OL$Zq-+zNjMg^e=-yl>!1py*$<8!|UKb!#V^7f9sc;mBSXlgl$0( z_{lNF!-D@M4*y?E%75=M?Z4TxRf1}_n_R;P+;6-3$g!E@ghx5psdxYW-T(0qLzLK$ zZn_cI#yfO9=q`N*$TYHohAAp^0pgd*D46>rcI zGu04rYo@&Dcx$GK{hfe{@%E{!Iw?oxWVK`$0FccjM`XqfSyb6pZY|L;aGM1-mX_)S ziqmU)MlFLQ5Az*<=gKgxM!Lyg^PXFa0&|4NqsHR6zfJ*v;)Jd zOIye|)^1nMr6uTerNu=XlgkW{KC@Nrb{BPC_OBU zoCrA;q?<{Ssj(|XdE{2BJEcd+5NV=@sUmKduezx_!GVfVu+RxFDoQV!U>mQ)=wCFS-ExxY|Cf;-&Qv2QMa2#&?O7p;b{VU?jBY96$ zWAv|!O%C<+U&Rjk@0bJ%n+RbV$j#C5NlMI?Zj1Bzpe5j(1aHk(LOy%VR7Vy}r~ac_ zKor~V-g^g6{>Zy^;9L95QY8RUnrz$(lE#t7p+BSU?)Enk+9Ml*dg)Pvl=0-$kZMe{ zp@B=zbej)*+GRmQ*)S$Kv-w5`vD$AGEUt6c@~oUGXP+hn`q&9mX6tokoJ~;CF9EFo z#ETC6T8Y&llAfxY=_^$g0Wj44()%(%#Ac0t2vejW=Xu=xhB?))y!z3shD{@an8O)S zefAVThrt(|FJz?%@@YF#bHX^&cy4bs=)NfCfvB6ztC?=9ZW1e!5p%xOIH!m%o*|a>mV=Zq7%aBKmoK9?P&7O>*aag-G z4Y3Wv+4z;DJQlVs62+>g8XKOoAD~8OmAW7*({Yrfy(h+_u6PRF~H_G}%ZW z67ZjP5NCr;hx^dz$IxaVR(Ohka0plN+GCc?TK=WBt5csl+Y0!| zTunB-JI{q*gvN}no!C5iGRbH!30ykP84l6?WRia?dga&&codA2OY#J08{nM^qfMl`+lLf}Fk41+mk)WUWa2+LyD zt`tCebs#o3rM#&`d{RdvJye_#vhMdYS-CsuZLBDagF8^NciX{MnoKlzz%XIEFi;ma z$H9qH!tnP{(_wHI*;{^(!7+VnS^yhoDC~nlsnrqShC)107RK!}vcwb6J}Zf9R9;RE z-ZDo{{QjAP`{tk~?>UD+Oy7_2>Tz{Gv@q3IR(o~{yLJ7`XSK^uf=hL-PuS2qeJ%aw zWrJ)DZyBz`?uTBN)ax%VzoE!~f2+mAC45@RFQYv*-=ys_pe&s=eca$_v3+~v@sm7P zU=Xzvqh*8h7cn~|=UXZe8%n4Mef-knH9gd@Ft6Smcc3zQkWJ&~uA%yJSdL-s>lT0* zJ!I-&{7c-@;CSFNa~{rBEV`D)rdc-SWiLRqUK=7tanLIyAK`^#U3y||NLT+;{47nj-3Ronsmf8c zQJw;q)0?p)Kw}Kh-!&~Jo2k238j2S(^xi8 zXH51*!JcZMd-UE9wIBR$6s)vNZ&nJf#Q7};{MkHdPZfAzW-Ta(6gK8ox4kkW{S?>! z9hAtW|931@bM6`U4pfq|Oca;|n}eoNDd0v9TA3Pe+k(IB6+OF=!R~WKLzdOZ=&HP} zS}^7Xn1@}AEb;Wd#39K|rdvyYQpg$`H`uxOEw6EB79+gf)+|<0hviU4_q(G}{Bd8S zk%4}YfW#8YhKsyMyPzN;P3;}3|E|VV=WasglZ`z@ z%%Xd@>NmEFE&EG4_L7TXy^5I7EA{3*Q z{LiKf{(~`e$reZ?yrBvInD6%QjJE${Jt5~*l!K^q=PG2}7f-s_wyEIy^&T`Uc*^m| z3`g+{GtnJj4d#7`h;s1`mjY9!knRL7Iv;sCxi6$Zxgv9|>F8>XcK)nwxqLJukJ30J zsLm6V8r@5q0#OO9p8KA6pd_)zA=xA__9s0CSLdO|l6j*7&vuxOUgf4xcWT+7C#`;A zN=fzyMu2+RmKifB?iz}|=V^NsJyLA#|(I2_{>c3ixZ@N>eqp`B4GV)Fe}2Aj}E z-I=6g6)u>z4Ua-9pN-7ITc{BIyk+>D8ppF%Asx&6=9h^W>WBz-mgJxvirZ^l!hQ}d zlp5e&q2hBs4&!Q`@kAV?-(7i*&_~ias+xFuIBDMq$x)G zZ;1HDJe%_VRl^cFc`cL+EqrRM_e$vAs)qWDnxr+5vHqX8F5TC8mdaBnqhNT(kvp_d zq4CxS{p#?oVR*=yb>_NDp;f*ijG3Yx1llQ|x#o={KOsyoqxyg*75M1upp}mAdh$f* zAbU?2s@0s;l}8z4O%NcpPYy@n{k?;7qQ>0xDWz|~nqO>o#gx5js4|OhPX!_o#{FMK zf08qm5ZbN@(LH(SJLOGhHwTB1({RS#E{K!Pk-pmWZ#T5V5JSB zajIHSf@yfa=y9f3oWyC04wNQ%zp7ePQHp_0#Ocs%=Hnj9L@lXI)8i$@L^3CPZ~mVA zL*OjA3_vs-{(*F7b#v|uV+Z}Z&dpwD_defgQc^k|R>W)>6-unk<}Df1~ElU&OqndDn0FksU|lB$?c8%bj%A zX)gWl{*gQl<80OE8^m-o6`YmxywazzX`%@RWAN~K;Dyoz>wjcy`0s=1e@&JD>qq(0 ze|8e=-l+QwDF+4YT20<%he6z>|BZQe+&u#C9cwDFHD!{bI4C7+WrErZB5o_&-5PWlxY>g zw{R6@*HUh39+6Yx{G5fE&}3zfkcLV2Xp3OGAKbGnx((dcw$pu0D|Gdxi3LpSxcU5# zYheZ)&K)!LnUEZQ>O>BW^sYi&$nh(nmZDL|z23hXPGdN1Q^>;DK2!|-qw;^?9lAoI z-Rfa9R)}X#F?<8F@H2jSukfO|!n$E}bI|ng2G9wVIUlj@jz{aWM>JZ@*$LPo2|%iX zSVm%00Vo@x$&jybD{ns95Jz~+`E-A^_Z(fCXiWXkk|JR4MbP{Z=0+03uD%sEoC__O za&!{x4hXmcz^aJOotC$y)S@&Hl}gim$i>k`xP za%e4R@IXQSx|wkbMh+ZVaJ%G40aElf@OBz1Q? zJ88nJwe1StEo-nqO&hq`lvOn{>A*s*)R6T)V<0#2+S2T!fSVgzp+Vv-+hlE5Bjb~?&N!HcRCV+xI zk*g`~D<1>q?oJ&?V8+H{;G1>4F9LtU1K{aozUy%K*iF!+_vcwh&K?cHTHL9p>n-Ef z@tTN}6r>>+IZtL%ogb_y7#m?X^n{Cvz|*NxHUkrQOM57SmRRWaBE@e?eWVp#pk{-~ zPmk_6JLMG2e*WBZqQ(Q&X3Da-AWQZdD)cLRKS-n$@WO^Q1Yta9By*le;wS&#z@>j3 zkpI{4ZBP715u51#TyA=aZiG36T)w_*0q_UQSpbm&l;>TnrZ*{n&X@0r1MD)%ivJ)o zf6smu!ceWi?pe&JvK`TUvR+PSNtkQ1QzQKuzQxvur=?SBKU)7??lM|xuzqR`%>~Fg zyj1eqYt-Wn(wP!8>c(^9e6(iBmuM9#-PcI?<`!GjaxlO_jU0kThh%PClrnouv2+)E zMwYf_7G9j<04~E{;}s*lwd|th!9OG|Yju8j&YYw+(l!jk70tu;OCWvntvi=vhSsFd z$+q9MY_NmRG`9?dL)=Obwla9X7k*$@37xjPIKj9mwacMMVod>r%$8tcKOu~$ym<}3 z9OP?CT>5LGQLwcaJ^1JRaC7QWmCHcp!RtT^d}m^qKPl>`bSaU=etxDlcuuXaADv1I z7%qlB-J*VIMH_1kY7{fbrq}BA-SO*he(ITO7=5{TQB4qXG(l{m8BwuXNR5ctUcQff zj_KHh@9E$$28+`3&?%Y~CBIV<-nAD^FXyi(R2d!eD2kB)zJp8SyP(52at1@I8ymK^wa|8 zTyKPKWEjvtU`=PcrCyLH&~RL~^!+>GmWGOm04pC+(XS9e)2w*)EdY(((WSGg18a@; z1^2YJ>~8F}W!#VWel{YH%Jg+1tZzi^?dsf9Sf#t+S#zf%R+A&QG8|i89e1dj{ z!00>0ec)pbu&WA>`wKEgcqaJ!mGqdZ62Yo+4+7Ux{&yjmM+1o%bA=iOZE;n(KI zXO*A? z`GX4TEtjUmmXUSyo4N;zGM}R?ifqFJ+A^lvS+odF-8St3h|zRpsL}yW@9r0(iIsp~ z;KMhuvAx_8>x&N_-e}yzl0)@keTN0p=~{ zLZ1o&PF7S)K_myNG_~rGXUn&Y*={e?4m7vEy)X*~EmZ*@&LM-dblMw~rDctLQum9g z_|$D?O0EkmwKi?FI#ylpHRaeFlPg=MP0K&URfT*sk%6sz2EiuWMOS1>=WyJ=^9bDCQ^AiJ`{xEML}-d=rzTp@qt z5#sz}&l2Q((M}FXg)jf&1t=<@z-_gXe%NAnA*hr16?FPNWI$nif%k&*kB|ex1w+y4 zlMRcK6h4&c;tvWxRyd?EQ;2o`2^zo7L_Cfl+Pxx)|L&%jLYGoq91qw9`tGlsy~{m4 zlP|b(o)>ALDUG$7gJASopx7g@pk*9IG^7&j@wM2^xRVQfl^n`&sz63UJ;MoQj`=$F z5ZFpHH)*lleU>W_y+fcvD*{j1>92gDP@X?qNRtj)&mr_8Yrd>Y&;9ztpWseT!3AqV zhEbK%<|5)%Dez{Hh}bPwNrHnkeV;HV4F$VWEes6_$7)tAS}fNqfad!P5{~AT5EWn8 zw}zH~vp?zK_!-K>_^|5VM^yb^WA^{&Z?_f5@lz1EiT}if0~M`X1E@b_Xz@i1cKZ7} zb7B1T-ZSiL#;*hNGc9C=Cx00rjd{goUXildqSERHtFXyK+PrqoxiShxem@ZY+MSnX z^erQ-X%V!n)JE%UzbQ#hL8qJ*cv}wY55=hiibf;su63-uVYjbeU=NQ)2Wr5tcR3 zIRB*w`5p9^Ap&l)wH4#J3K#9UUm$@GL93lf(b>3KkPr{!PR=|a`gb3FfB4vLZ^ z_jm7F?_KZD`})1>Ue@_z)?v?{bM}7r^X&b^=i?`Mh6OuKv+8?zM{)0^x^tRv(3n(} zmbsB;_fEY~Mok6p1b_3qgZIFf$Y-woxR*@F}%_sK90Ew@fy5=&UD z2`jGog7LMweDw^CwetKDMw>k@iud5yK{`K`wW3-82s?rRa>mE12oq1(1XBh(a?vlR zC3*5-fj4YRmjWJ$+Nv6n$(i0eJ#|T%=s$;c8)OM|9n}cC?IdpziQ%1_63*Cp^J zA6BV4r4&XGZ2c>Vo0XK~>-2G#CzVsK z)|hTTzP72vNAfO$Q19<1r$2bLl%2O{ps=U^03=|xp~cI?+vE2cCl^dKj6dS(yw1W* z?BaiSzCheHIb;HG_RZoq*?z?ZGhdqO;QG+|O`G!@D~k{)-vGd^$+@XK-m8}{zQD7; zn4e9(0WfWXKhQ7LqE?lpa>37o4W=QL!;b3dJge8Rh*PZ;7FPV%zWXvrJ?=sRK^x`p zm|ssJr-$P;gwZVtxzu!_maA{Cr!2KU_KGVZA|klv@Jk&Mk`yHdZ?c-pHX9zrVire& zt-#dN9rkmMZl6Csds?G+D?RN!`AZi9_!OAu3G%Zw6}9kdYFx`I(bY| zS-6f+z#l~LB<_)0+8ALij(s#4LTgb(s%Hn*YU)Nw4iBuRx9?yYW_^2th;65()E=g~ zEkXpcX=Qo@hJ{zcG#nCx5g`~BJGW?_68@s#o$>7F#PJ|2fNT=Rx$dwiyL8;b03k0} zDvq(JvMKCJ&+^p^u>Az5C1#rJjT*761cP86se)$jF~-ooO|6JF!k|XDzFS!(MDYa5 z%bJCVdD>JZ(K2>XjJcDG%$CUz$Z#G0y!Jr?ug;7`XqDX>@p?!Xk>Vp!1oN6J49dZ|8=FF|r*3f^1F7sFD)IzwGrojqi2EJ%??R|}4rTdDR{B#&U z_vh+vm2iDzCafOEeDWk1<=CWKkM#_g4d`*02Ac67F zHG=A#aWI}Fe521X@xM@*iVc!wcSuMP*qM4j<09(04xDF0j$YrEuXWm~$4+)1+ZP-h zyw(w%9(SLU&l{6^JiSMgPfM51%NHshzru{-|k{Q|Pj zH7hw98j6Y0IzhM*F@H2)`=KQjpvhAA`jR}SR%!Qo|&5hjOQL?A5VE`gR*DhaL%82 zZ}V;psLwPDdVyNmY=(OsZlcRAu_L$#3$2X*t@Nlwu9;zlO4vOKJR@HNTYeLN5 z`&A39TQ?AWcFSInhfVD{#@orfe4%pejat@R(~x!gVczGswvH_w)F)1F9jE0IP+^^{ ziWU?12Q)Hly!AY%yCp4+;<~blx)-SW;?3)bYRSec6uD$M>7Af$Sin3}i!v~lQl z9r`T6D`-AXntNYnsdz4si_Gk*d4Myot{Lu<@H1O8GZQ3=={bOHoSUUsh=m~+3p&Q` zP|a)m#@+JVwCU@8%MI%DhT9E~kWQ6|?sU8TGNue&eFe)jhDqFz*HzmNn?Uvj zv^c;aR0~$Ah0OKtISmUEbH)0#)(#Q&VWFreXMz{0s_vTfLYv{Wa;1m#o~tO*EX~g^ zAG&iawl5Tp;>Lf{-^Z^}E9pL({IT%TbVBo9a*)de;Rn%)w+s_%YNtF>#AbQXEKRIO z@20~xedBcjEAb=OU90Pg#G;HbQM`4C^S05>8l8-i`!cI)WH@$Er(1m7fLzF_O9Gfu z-PVH{xvRhapi-nzbL18XcSD?xP`=W3`D4vnZv%y2?_BfJE5W47Os_2Bje?vWVy=n~ z52@GKQ|I{+21MfMv6+nARZ5Pu0+WsLp89Tztk(c* zTrzGaubHEr^FcFP{LN$0dC4NJTt}IPjc8#|H2+r|!Y68Wf{Ikh=Qja@xqG8qPu0}S zTmqck{HtJ6J96-gj*Z<=7}8fo5eK-IpWsZa!)cdSBGK^J8!v`)q{|K3PY~-4+h1K^ zb|C_{@w-HvsSfA!^Yb>O@yvuBF^4#-oADFez>qiW*lfnAoNhu+CC7L~X}%nWa05O1tb z?p~$O9%j|UCwcUNzJVr;%>|A(ziE4J&?`6b4jl(tw{O;IJMD;R1xV((8uCm-B(Zf8 za(O_aDJVr68&O-vHWY%E8T1)I_E311yH7fp|LYf2z9G~gra4?mATBs=VF7el3Bm-A z#xqfU(@Vucy^hqIsqo)wmk^g|6&@FU!u!LN=HL^*lZLs#4-OfTyBaYua`AgzEs_aR zv}Ho2KJxt<&T;rGG878#xyOEAjCc!?4S};-4K>Ubf(scq&1jp3x=!TC%3TKGs#yq$ zO;?3rIC|tX#^9Z=K=IGz+I3GK`5k5E0bFQ&x8LM~xQ}H_uoMr>-EsFIvn8sd;_4$%LXolb|DTl=kRROcum;KG@XyvvmrKG zrlcG3xs1SqeRY`&xZ7gO`Ml>a?#kxM5*2ks`+>$eQq$?4H(oGzf&X{h=UXdEXyQ=E zz{5~hk3jc!gN@l6zrPe+Zz{~oMCVe$UFc`#C)6tw-@`i%^jD`y90@v(h<--AOo4%KWH{#yqM2%kBN;vI9%)1;{2U} zG0GHhgGMf3lgqBc0H&(%2`qAN1JN^q2h<0=nF{EWNv+GhXk2Tt+s>G73MUAKWwh_< zS+R8ff-VU4TrV);04jIbf|l!4aD>B~(T<=%RCVVL^6Xl$il(yeQg-`t^kbtV)5nd= z1rzwy^_XUWFSJ5b!bMlY4-A(zsa0_74CX>!q+U0q+50-@b1#X-&A`bys?gw1y)u!G zzeatpkwO%n=QA@INTY|cPR?8La4@OK0?H3A;b#IjoJJ?1vj&%DKFj?{0W`)0skldP zH@+=zPpLv2_L3VqIaP|WVUz_vQCF|iGX?*^C4oabXHYYrkbmmqf4hIE#m@Hasv>{C z!Y%;)Akq29CvV{Ym^hdB9DGX=ZH!XL#}kOkjdnlBU052u1vNEwe6m#MmufJ|3yB-? zJ8A=?u}QHWFozWdH2?+ahP77Fqt~Ez`^7jxX(M>{(h|1<9NH}RV_^o2c2o_7!tFu$ z$LH4#=mYuAMm~QFp6vir^+0${)yy9J2<_+`2(?8$bPB5TUXk!aGs;0*VHN%d&W5Pg zbR_ap!tFXfmwJ{PTSwI_uN|FrUp25YuxMv_*SEdBDnDTlDhvB$azKqqN%5yESSDQ2 z!{ZA;pFP_jOKXhbQnlph=%|PL{!D(R?B6Yzf47Pj2{5C#C6qOHqPk=tOn?e>w3HFxTfxl~posA*&$@Mq!IExuZU60jg^Ne6jq) zhdya(>BT%95pr_!M!<~>I$w))JeaRfPfhiRT&d6gYb(@99t@C@HxHzyrOkPFAeb}e zt^fw4Ms4gLRDYg_|2#jz-Z;Q`!lRs~G^4t@s0fIO>*C@_P(ZB-EiybDAe6e#&+iEb zXz}u_SZHY_s%c+3{%NKOtvl02aK&*Ss|=yQ`h0T2IC-lX2!Pk$@u z|K5iDyCw0jmL$Iy26l)}+CTl`-?s0{d&ssw@#gdEOD#5+%ab?v@7=3SPNrg%_irjL zEnNWe+W5G*w)0#-E%<*Yd;f53n>Yy69&~cF{LGsi`uzFc?`Snp%svi)T=N6GJ>S27 zci0$+7k65eBqk>h+*vG#sr=Pq{hI~-pW8U;Vo{AdPbT=5ikAUIk#6qpj-%=P9fnNe z4w=*wy1Kgje0*bgqzs!2r4s)aBKbF8!PaK8S}l7Kf9I=QFi@;z=;3kl5`SuDX5lxm z%4>l1yS}l}v|d}`Z)f(;LiKNZAd&qoY0wv9XNqRu-aq;ejyZAXWe0%!`ZhiNPnpY; zpOcS&{=K^V&!hX_e_q|DB4};?QRYS2l&z?u5=Kfd()a%Tdj}xA<^@}n`mL|<0e{5Y z1!AX4UP&SPAHLkbedGUnlSC5J`T?uUhtG$8IKL-b1u-_Jq5kygG4t}&lvPDVMMuHu z<9{N5??70_#>Ugr)7v3!qF-e~TN@i2y#qte{_9Ww;SMYCK*-R-#>R9-H@8~A$es)M z{rrA+qvXzCooD{1+_`|U`1$$MQ&KETwF|_b?&#>~h@1D|0BL5mU%!6c(|gD9-_Ov0 z7|DPtLbV4s?Xc^Gy=?$2E#6@KYF4J^i(qM1bobJY6F;CZ1iX?8E_J|CMt3pbHBN6(uDDEiEk$hpXMU zX=!bXR3T?*X8GOd%V&Qpn3WU+)P$5itpt1qwSB)SB>~)Nk3c3{afCB?&(O&-hm2Wz z^BUvYt6qENFJJ%n?l8D9v`eD%LNnu2+vm?C6L~VUUkI6*nVn31eVZ2)+yPJpIZ(r+ z$B*&kOju+7a{Vf@0ZJ)Y^Sd!+oJ32Y?3QY}OL8RJ->}`ET^<|5^hw)I$>! z4L~|mSydIhA+udW6{-qhaeVXUjUICq>tBx^H(+2v?~A|^XwLBHdA4FPCBLm&`1YA0{^mCZTB3o9$UQP25r`&hjM9?7&IDWT~Jjg?A1Nw9#1H@Y*vh3{vZg5Enm%XFoDYFzX1uLL`_R@bmx&D`tYw`s~dquY4 zb2B~`t)R&lfVI%j)wKrx@xuqs*y!jZbou|Uy`My+;_UdRSM@Q9E?8SzTV4Q$sEo^M z(5>6IU#qLf!n**P)97t^hJR$)8u5bx|DpcFhoPhl4~EVg(SWIcN4mGW>#~CVv8>E} zkObIc0g1YAfg)ig$V}v&uX?pl*W*RDqWVVGdjfC6J)sw%Ih6=N`N=U0Vl`%C44z+1zuIV7n45dkTOC2hB0Jjy zkQO?7qCDjOj}jN`%>qn0yiLBn0{*eQd<@8ddi%TRx1ytC88CT^tk>)7>v8nsMThu` zf2}3|Wts{wij89u`uqExfOACSo5>P`r%y*-eiwk)J2-sd8tNobG=2WuLN-15Vski! zUrg-u^Tx`x45$p(0VLD6<(`;)kuL()&;U{`R*(&c%e?b-&w>&lkWOCY7~J65my$RJ zvgqvZ3RT`L&+jd$uSwT3))ee`y;CrB-G9_JNZRV&qoc~%QAHW!7qTSU{H3A3+5Q-; zeV0jj-|Pe|#Cvw<+(kA?S7si%7a11OWXW-dE!I@^gQHa71Vc_vd1XiU#EVz&+Y80t zTHR|%x*dwLSpcZ39hFVDc=m4`L!wMxt9M79~I(xm}Vh_DlYADRVB;YC7u@L!9ja(U9n4cwg!>O z{gC>Knzd!JU-Y}jB;g-Q8pB8ye@> zt{{keT+%Jt;~SD?y~rYIp~w2FOw+gVAj5Lo{dCA+QXK_&qekT`y&Xag z1%(sl@RqS>G!t|u1S@^vVe6gwH7f$TAk+P9Q?+gqow$%==Y;r01@^m`=D#~S9< znXr6vlXltVz;-jRY-Db;_@Q@5M*@2l>>3FAnxof6;~dRp&f|#qU>8HDu#xP+Gk1++ z79ZKcF~!^RbOgM?X!vwL!_?JXzw{;)}K0Z}K~1%QxHnM%(%mjK`dURF_Yy2N-F z0RfW!#BIjtkmSi7%>QWR-#KiceD}foY~KKcY+Z4D`SPU}(@M2@DEOzkY0JGt*}Dff z=a8G~-|EfFtD&kLNHwn~_g}?60}>%6o4+@1i2FV4j&ZymcMjO7tr@(75QBSfk7a?W zMt`Hm8N7M(iJB4cTOQ~ZTux~YZ!U*vEa5|#8jNR4Y_)1Bdm{}+IHwBQ&)fPRJzAL6 zDcWBavk#@Jbo|&@D{AY2ICJ!(>c-%i*4by4@QPb%^=dDUr=$E*z_aVPP`5yqFA1am z9%6#QI*}>Tzpa(yRP^|Ri911+G0Edccl*8g1ZN1HXzWxW8g-uc_2%qwICr)5Ee#96 zKkqIiwL=rO^ACT3p`XRhx-MHCUcXAulL=fFhc7w_IxktMCa@xzMJdj<5F>wVloUq9bm-uCj6w>@4f*x^oSF<#YVu<;=9c zt8aL53Fm8@OUcDSwH#6R^pqGmC827vi3)05T$sr{;YNU%*uh05b&t3Vkzx-wk5I6xSM5E@_DZIIxH+3Yjv~*E*YPlbHHIgQ_Id=bHA85UiH!5^gpFMH&Z(|LzN-X zS^TC=Lc8;dS?G@Tqm#fOkJn-`R`n&F;vhc~6d72k%pHaftw+fCNU`-Ql9 zBMrmh$FBS%19e*}N*$o|M;lopu2(*WBHk?1Ka&U+g+H`T9$*bT4bVxFK_0ifwtYfK zsINWgAU<7}r5}m^dK%Us93aEUcw9hC26?V@ipV~ zb#%1u*QNAmmp8rYo$3jXFL?)@EWP&k4z{sJ_6@xp8W-1fVa-3r%{w3$Olt_VF>-k) zpE$KMFx)oa?)I$9RmTww1arr9YXL2v-y`zTH88f0glx?U%P&yove`SdXby_X-bYmI zuomdz3vrEno|y0TUJ1p(u@P&6iOa_BXyrMjh>9|o27Nv)`on3qi&;(a{RQ*v8*jIi z$PMz`I$NlhjyClnWEeQjHE3LgZUA)A!^o0`f3g$KQwdU9Z**R9l9H0ry2pt7 zBr91=)UWOttXTGOQmzGG|EOqUIPWvRa0C88AftHhepTDLNEWC7k@j6Y{Gfv<=GyaQ zM{ePjv9aTCKBKaTtRI8%jJ5!E(a}rwpG_pnTcd)V4yP0e7se3)%-G!2CtVb0|G{ic zZ^A<-E{*`&^$pe=pJ!Q9-fN8h4}Z22)Si*4g?}5GW)9(-O&#@uEm`tL4-4$snf=(W za@W(SJaAZpDn0n6!FMU{fl|v5hi{QR%oWk6W zH}{zrb%XVOwb^|~&{kzoPNeAK=*oy&^!kqY#PuaM>z6s*5|cFJ=_=mF&4ZuEZc$N# z4zxJC`9z}N(z~UzOa!?vbQb349S4Ka(^FIX(@FUgTOBG^c@V{hwTa@CO3-%kN@0Sk!~6%n(LT|s&clo6E3>ORDV?u zJcrq|KT>;~0zUcJUVY&SI)s5JqbwRUyOB7kdnzbXz$uVb-%5vdU5Q3HtT!Erli0@x zn#;^xS6xHB^FBh)5ZClfg>=E$4;dyLCO z_tsVfZjfyz&##0GJ1xSC4L+m5`xF7rGM+87B99?8_|uhjbXQf4;hn1c19iv4rdLeC zuYN7MCsKc4E+}Qgc$jYbD2-yQ4#HA%rOWMj`u^@v&$2o9x2gp%cr7Q+TRO4h`FA1# zrF#{BR4*#4UgwP$G6Wgf^QKrFN0E^5sIi=+q_x@%1o@Y^&2a^RvOoA6|9zK>(pY1w zS+N=g&K(ye)V(}<&huY}qc>@CGTT!^*cD_*Yj^;6Lpq;*SHU|3CeQ5Dk>_Hx35LO&XsdS zm|4p5xH8l3h>^P|+8NY(7f*GVZoCz|jedQp#3)KZev1j1Yp{jng>3q<(3cD*Fga2W z6mx)lGO4MvmU6RPkSwxhguC#p#2mC}XA_jm|42KuVy5~XAN+$%?9Qw#+XQTU~T80tErs6>!Y@`k9=$OM$T=$>E)W|N)ktd%d)?f6zf6ClrO1Go194^ zgny=`3rMbilXyVoVk-QYFfVJK_ulQ`orHFf<9MyLK$#%a`i-4aNSprK1ti?lF^|Y3 zHHMxu2(9PeGW9u;y`3fm8Ef`f>)|)<{hh|52O+-HwJEw`HioLDCFa+WCX=ZICraB* zBn23}suyCOygqD=^eb zJ-RkD5V+H%VhAcT)N3j+8l>(ld1Iu@TPNP|>^(D1b3jMR^C~F{R+kR04U{Fe@W60- zu&~KeOF!8*Q@u_3@Yr^quuVBB8L|6SEak@yq3w&c4JlYQHMw~jIA6)-^=g)vuL}AlE9;dBcAfX4 zHfr0Yi=zI@1CorP!h-=tO(r2b1H+Sd^$UyBw-`_Z!{9nA?OH{1t#6VGzQ9U8*$Q2 z>w6kk+{%@WX5&IR$WB|zGg7nbBTHSzv?ISj-*H+k8cUI${qQ+Uztg3CzUmV9jl%mQ zMct;QW6>+1`%ak57)g=(^pYBK!8wf^(mz1<$Tn*N}XSx%)= z6H5q1vu8zFYssGbAI{prGKoM~R;GFqw#ilc;RD^@I~@PxD7{Q=hNt0_kFh+Nm73VL zKJ0!K$9T8Qg`mY>TPhptVy!nak4@7z?mHdQcZMFX@E4E{Ur(Lr9?4b)7m2D{zG%bS zyU)kx;U)$xjJrPHfS|Cx>Pnu@uhtLCKyRe4QGSSJ)7yxQ_wDWm64ryatw3AonrA+? z4X!Xt!fAoV4e6Vs%aGHhx6%2I08SxM^Vk;#G6PvAg}Sith}Lnm%a+3C6vkY@Xk}jF zHQU?4{)MZoItMWnNcTE zxxABe!d@h2-+rO-HI7KNZ5%^vc|)B8u8NC|sGyv#la#OT(AaCB@}eZ3E;_D}8tiEV z8`Sy&=NovP5wG#YjSA*w#e@`;O_-a=`L@DTI;PfPHeqzw#P`TuA-u;i@h)V|Z|qf$ z82h9+pXnRmaHA{^3HR5t;@^QveM+7)gs@reABOdnsUS8kE-3UNQc?lGK12&}9Q)wt z(h1ArX1!s|t=6JM3r#EmXkX(YQChs;80~4!w3R%p#pj1U5*R+PPteSh9Og=3B|mDJ z^t!VrAS#fN&s#Us=&Xu&pqghTO9vK9440iPVCpnk2>GG?EjvW)k&z9D2~G*+Dx zw!cQXAjNT-N}@~B2;R^ zvMs(~seJts^Prt+^*yQ@gXS0gY++Fm^@haAw1B~{0iS9wS5nLLpZTzqnbU^+0Y1nZ zO!Z3dv5ymedVKm;>f3@Yh$d2Kn=yW^@iU@IIhy*L-9qnXDF&hl?3a>_ZQo~X!z|+v z?brJW#MolMLs^W^?oHbt@e(WNO)o#XMcF>a+aWU0?fVBOp7tkhAS)AJ3 zYPqx236i(yP`J4)BS_bE-WdGQN`fpCGHG_&$>Xav66y0B!?^2Ps@1aMTFIS_YFyb< zg=hP3!TGFYJ=axe`+5Tlkolf%M3sZYL~LPt^HHg5PI)J1_@FH*r(Uh)!0MiNn9lpN zZ)M1t`rPFQBDlGlrJjD1)hFzCm*(3)MsS~uz!^GLqz*6QPqeG|c18cBHyR5RkvB2u zvqnw+zxS#Bht605VbPTEDgcbrGD^IJ$%5ANiU4mhWj`;`CLc|Z4^uIh&D{3D*aiUg z1Cx9@Tb{C0b7({aNgDEbYM#5J^EO}DGGf6;ZhE+aG)vuA7FudI5x1H;lGUFp(#V~O z-;gx3hJNu-5h3yp5x^sdEPnCwWpgj@AUE5?x2v9QW9x0KRfm3UwgCfsN6VM5c>tD} zIo6uQl&mAU!+CH;o#0?3ThSXFgF?Zcp58(z^zU0^ z!@B$E!1KsTXJ4k`-X|M_1}5Kl@AC+FDBr0om-j)+b-a~kpU*Gcjn}h*PpAwmEZgV{ zRDJaLE_UBg+*@FCF0Elon@ao1(}wb+ECKsp&_Gn~Xw0ZQx(wOAf-A@7Ote0BGKL(B z8ePR7(;5YNz2z}!hP+4OMkN+f98!PPl6c%5)5F}aKa%ic=*1Fdvm4XqAw>QNGYaV{ zuj3>C{f5*if5B!NAC2vFDpNveLxfK0`24R-tF`5^&!niFubZLq{9e zYpL!B)6n561@;}Tby!}aA`p;cAC3Vbc)UBiel?y+X+zEO1p9`-&IpcLW|6MyC9JX3 ztD5{ddzzP?KDQ{dsG&$*xUQlxY4apC9U5x6QY$ExzVXd8N#C}`zUszT;_|xQ(a@N+ zcICF&fehKVFBNEusx!2z|(aEEH}#_WvPAIh;kTx|2@U%Miyq?cTsh!ZJZhH z1lQY@YfORN59|_#@3~m9is;q{HoWyd#<-3&IJj^yH_Upo#5mb4&>cj+n2G7@v+knK zDBOeVLEyE2D!+kxxJ1PAR=>DNgtc{Zjg zitI31D+XCMb2|`G%-tJgcY7_WMLF-L?#46f{)Arw4G;~RWu$Y?3duH&*` zR|)`?+{(3wRS0w>*Xn|zlGhl~@!JWrr&sRbo`!bgQ<#~(eKs0#tIUBKOT!0>cyR&( zHg)zrzR|6CVD(let%vMhDFFk`dSQoo}~GEz;(yB@Q%X(@0hNg#i~DLHylo|eMNR= zwu{0Hz1jcZQT;>mS|fijo?2S*kpC4L9=PIlFu%p=N~A#($unDFP&Z%$&37c1J4x&} zqWs3A17P=jmA@F)ZrKoJQ)`EV;dOa3-t+l#pj<00*MrXMz0UnIQ}kzMIln2wNg8bf zAfRdUyH339#=QWQJ09-C7*F!!D$ecnnc64yL_$T0KqO{LRSVd-=E;0}lehBuF=UqiOYDeEqzjEHNQ)ID% z&5FQqZf%2?j=)~`-q}l~qom}e@P=12rm0=LAY{X{bD7`(TLU3b!l)0Uk3jfH8ZqsH zdsW*$j*?BHwePRFl_tJgfaFUkUr;f#4Ws6`X^R7KeuKWuxE%~CJsjYh9p@j{n)qP% zR5I`eD9=o+rpOVtju|CGceS+gksp}J9o4uFaVFHnqX=l|LmE*dE_oQ`%Q43U6`(nn+|q1IaA^^#+E1GzlwPrX>O1~2BS z%`?JL*A3x2dNWh=qJr(4`sGGnX5-awR&i-Gvo6r5j?U7qO*PHK!#Q{>53moI@iEUT z)5?wlPF@}yXfG_v%K3h8fXy9*%~Dcyu(sG1y}a2Rd=+t8+PHOku)v2JGRIN1Yp~Xb zZfoR6IDA#*d-lM#U|$grlwcjxKPE8l_Ff1YqprP>q7XQbzM6x87r!U*W`ld(h-zUq zZAOiGT`kJ5hAj6(w1f!y9_|j-yeTrG!1PvDxb78esjL|`39ux--1u_q&LQS)EJg-u z=O=`{4%1)W2)N7jJ6GN{dDBxDwZMimpr)lL6ndXNsU1B7vNjdUIb$h5g?1jb5Q`|v zhYGAQ&|#;DaLx9)Zm`F%SoaD+utt04+$ne2hPkwdNEWvOSfph8W7sftzgP9uMei8} z63ygM<6cnsxbHuk(8&nS_HfIS@x3@PPL<9F zyUE0#g}gTHKpq=liUw|@`m=Ps)Q5)x-VPGw`oXL8KX22r)pK)m&SUS*6v6mky)d$< z(GiBt^?L~qDZO-zZ*TC-VFc~5K@bUP0lj4!Brv)?d-O%n4r*ci-N8vu#xwS@W2TtIg%$S6P z+S5_JS-G+qX;&E`I$S_nW=wt1V%&YY9{?swER>S|Os&H`72kwXvtoj7)rN!lpI+$h z0ZnmYBx_TN?rea=WM~6jH^KSX(RN`V*r9hBd#-^T(a~1h6C{Q6Z=x2V~Z zp^pN`df_UQ>;oWV|0$xQ-oBH8Hc+<_{U(B%ODjH%o_B{V-WZ!%K1=>v&&&_q0zZ<#soAzJYLFX;w#eR-APl) z>#Q392GXdFw{=Hej7b`rn80eibhk@h0O=jB_XPyJ3Cb;IR97u$LoTsNAp z78Mqny){9vT{IY2zYs}t$QB*hOw8c*1OQdWcVw67D_p90> zH!2I=b&brhZ!Y+LPr@c2=sJv!A%0^y`6h*mifxyxi3E1H*t!yivslLMBqF3J7d9dK z(pK0@pqXS3Oo^I{=<3mcmwvMq>}>8Q93x@)O32c?9yxCi6_=VVHQ2*W;iNKc6q9Nq z6jG8t8X^%0Q(W0mbg?P$^6+@SV!0?c(BF5)n<8HPHm2|bI3+nC45+zgKOAj<`LgC( zPsZp$ld`i484?43h{S_!jaslim%tL?}$+XUyzjD@pcH^&{uNZw(X(z&0^mv=G+FxlrSkQ$k?$JKqY zH0f9AKNm4T7$t~S-W}sX-|Jh(s+I}epOO`H)yLY_l&K9ct=>q?Yv$rpc^26`X=|}k zy(iW>h^8@VRP?b; z#33;=i)zweWE~(EodeA!p&6e??Yz`^QsekIk%kB+oFtqHPlCZlXb`)FIal-4_lQ7JQk~QYaaVT!_Pr>cQ9O4T^Yx zkmH*;f$_wS<`aV~$KbKWC^I@E^-?8CBvFAN4cK(Cb}k`La;e|WZF5Qu}du8fjWY#=^eIF#=Nj?)4*ZA)N)o* z+l5gIX)NP*!dkW$6}0Jfxzd*pS@ANIm^M7>gBI6G)Yg^|TGYvd?Rzszl*7YL`+1*$ zt#8zx6=z7M5CzGgIbO&^7uhgl-v$JWz7G?9JK!QyLAed>c?#G|RxNo{_W#OeaIig- zZ%mlT7P*CqH*ir2J(~MEW!JfIB^@HgRLg)jpx9Zmdhrb6aPWwZ+r-O9o*0{WN1J|r zv?p$Rb88;rd35KJi^UPrgNQWv$^F5W&enC+$NKDW{rhtXL3*6`xLLD;US2^zpLq)c zig&*^(!s?p(^0>9_iq@q*-z}bc zNEp2?B5I}U(}D3R#zHK-ze|}g`}B}5Uwc@V$RIcChb(<9wFJ>7&4j*wAQq?o(_-2D z5FPLE!T6w$G=!8*RPS#u^!K>)Wa_F|qOY>nr5+(3Mv^r{eH9$=kvbw9Xd!^%#XV-7 zQNqc?(TZ71IrMx05t}%iJvZqqu(utcy=hb8nm;Q2s^ayQNGq3;_{IR#fF~-6TC}bX z?SMKZdZc6%5O_9co2M~hpUfe#EV_V zoe4&0(QGPV3AkD2IKKOGP!KF}D=U?ET(s90v-W2?J4ygo+2)|Q&ybA%gcBfGi+LJ{ zP-7&c%y_;Vf13z3R^58DYrfFp-8fpAi}O>I9xN{Hn92-=s)9bpTL|tDimukSr+P@? zF?1uaHecYee+;@Z@oAaOPF%~jA8>IQ4?B_av&tqmqrfg~k_i9qDhxZ~Gocou<&GO~ zx;F{N#WDj(_mmd>C&#%*mI-NIs>TNgnZWuiZRi9Qbj|BZH6s%>vu@@w@Y>(RdXu0M zsZd@~+cf@C>3RYt``h&r0qvHppo6~IVzxk@olN4Wu2s_5Bm>lHO_7o|Vesm3+@xrU zMsHiRV#zTT_pX~U9d=2|fVRs{|3hh848jT0r(IRfD>1&a`vcG3ju2!VWOh@u1$>Co zdg3E_+0^-7stSN0BOB_bquyO{EgEdTFb?yYvg9{Y=(jA7i&z}5kJ`vC#&u6)B(AI2 zd6>Svzd`pNM}`U-MPn7WV{6pfWaE=71xw&4Ln*l|;`{p88MSE+4Y}011gS3-(}cbakUm*;ff%RFtN{4O(-LSrRQZqyi>$G9&#tvcto z)6q@=hCGB-xEzcz6hzk(Vj6a_(e9zIEmil87I4cc^C~;r_H@>Z6BqRndY)cC}C40obj$I7z*86 znQLKaEOA@<_%KJR6GFQU)YLIr?fJ}z<0Y^9zmtV6$v|yg@J5jMUACy}_Qdxxjy(Ci zDT9z+ALSq{f|cU5Ejrr^0Y$RG3`To>Q3E=@k}u_pCi87`ub+`Gj5eo)H@h>~Yls1? z-5bT!i}Y&T)z65klvlBqsb?tgwpPXFu3xELa|n0R(E-4rE%A@qtu#tZ%vY%riX6(T zNS=42_X{+vDt9h_g;=c4c@o``4rCo2TLnLA@r0If9HzM^*nb0JNM4wjoR5nWJoaj7 zHP{b*@VI=zGhW1uVLQv2-^_wM0fsPY+;AsjTg6PTxNr2jB>?>GSo3LvkUmCTk$cCn zH|g}aEmdGb@BF6xo};z2)*mwPR~xv1lF$Yii`bu9{M*#nRb+hEq_%?N+}vEp!u4UK zf&);JRQHKuoF@`|oKo<>2BVa-Bk(4Gd)d|0tgyPz5(vPqTY1E+(F~Pp=Ggp(f-^2b zRQYe+Z}5hjaKF>cmz}!?3e1;==V-q0njBr3ZLtF2qPY%FpIj}2;GR@sxV3AE-vX`? z1%`Z_c2D(Edwlsv5PYdT3YXs?E$%*~Pj}l`ZSL;~r}}3dfv{?s5SKKA3THJC11+rt z>V6&{1)#jrsfcG{Z2vc;9Y1SNIrsaN;SGp_Q;xV}E%gu;>KfU(Vho!4s3UGW!Gzkk zRoJ73^AdUKH9?LKOs}_0Umv8!&9@aqviJ>I8}9_zNdB&a-%Z$nUQ;peZK127Z9Ver zq8-a>g+?6==w^>=e{LLIzama#ULd-5lrTOBP8W7YiD~eI zmtS}41dAonfPcQt;~Hy*D`t62<0^KiAe2v{f12OWd@rNwP+$!tM;v{M^FhOA?|K$r ze$z@<5SuUDek8#eHb=^yuo%!HT6xir(VL=_GgNt@*32gXth0^w->nw!rK4!DE7dbpxYD}G^4YnHz{0(_+c>PAq&HJ(9Ue& zE)10SaW2nn);Q-%q4W!ys1lKrVdBAh(_hd*PM^lzy5D{kG-f(?S<>EgAtsYvU|Yoj zIz7H1cI1-OJ#!BEI%e4fhLxEfRm~#<{HCpdM67)D(L;F4Q{_`;io@-AW0^b^4dK&{ z`aL5Bod#QT1@tPd_5!3=Z~QQsJLP!yyABY z1wD#*o&B={;XJa~_CEn1s>83oXss)lW4k7WPbX>$V*KhCZk^3s#Mb@1S-1i>dT$+o zc+qfkcau9$5w4?MX@47E5wZ)rH9=_8u^nI|l`rfYlfw#eYFmk}+U#K?RsdY52U&V1 z%0f|08N)ueVMPkz$&!OzE8ty@1j10FYL@_!)jrzJQ%@YBSz>xAY)txzh1{Q$OV^L9 zR|m#`*qnCv2u0kH4)3S4M%D{rM)IsVMr;xX*E>~TkBPOVFabv9b-|@f>qG3@F-tc` zOU?`CMx9KU^qHhG)OVzJ#!iW5>hthx4}|i$Xx5k+r}QR8;qSckh%v9vd9O@>{q_r zW7?=!&p-+EcHt^qoC+?z)`{w(NzKfv$qV1Zk*K^O1oB^q?AFRKoO$9(Ik_xI%{8Q<2wR=UXNtXBMYIc79TT+>u%` zvGs}eF4e~nRbB!v(G;sOfTeqnmqXJAvrkS66lrN>Fc%}GzyA01(HBz zmTweNVOjBQ#ap?fZP!@a3Zf|mJoio%#@cuMt|6?brW|h)|Mj$ci-Op9{P7X_ThR*V z95azX65SFsb%FQ@HP#q#g)I~Dj%48s?ukhUGpL_GAU|UrxvYZEZe!(s;8^Yd^29)# zu#?nxIC*q{oiTVB?JQyw@%HIjQr-P0^vVVJ0cX#5?2OnRv zX9hZ0zi_?&ruWwc<-hADR<|kMMWML^kGZu>DkF@V?7rPWBK=}KXhHyHn*)&`U93{R zm-_m4^hzo+Pv-y-r)t5#i&25mri?e)wNl|flg6%ycrz`;_X_DIpluqPsc9|udlStw zQM>|mnqzz;euK(%8leP(P@XPV)Ksbb7A*2$YXDUk1tZKH8`5IW6}i7PElXff9_jdg z7rZ${$(^N}x`Xz=oGkE?)%WHKs}%HKBR*?}f+wOQuCh)CI6x8$y?pOY%(>jBM?oj^ z196p0w`>CBh;=fOrHWgore1LW39aZsYC@hay7?ON^bA7`5KUn+Dpr*R6ZKZcqx^x@8 zcPT+oQ4v9rCS9r^U1~^>E{LFl^kN01g&KMnLJvJas8Rw12oOks5c0)uzIWz*pZk91 ze%_fcKQfaub8^mJd#|V5S2XFTC4f#yO_zaI7p?3rt2pa57HxhYS>17x!V#t}g*Cqn6o<%z( zXCt&z;bP+6@BEd|I&nJ^UOhN36h6ZTVNkV3sQ5>{75zlB&NF@6Yc z$%l5wrAWwO(&@q6-Nk0A0$f8GKsAEri@3oY`N5hbg**h0inAouq>($!%1YLO^Y&Qp zd*;rZ@(`wsB(aOAFC=$4oAmoZ?OSTn=nAt|k_5~|m_mWCD|leuz$g6{WfM-eaqrLR z^u%lb&y*HjW7#kBm~7_jR?*^A&(byI#^`5^t192;s7zSLEJypU-l%!XWjqw=V{XhfK+bFyUSNIt$zpuBQb0Wcbr8eM~9Rn~L`5Dy{3>#5r35)b3nE}Fal9uaVsF8JO^w;X|e$vxvw zb6z-|nBfZ&J0Xx0K?MnD5v|2a%_`fkdoNG0_!h+dwgI}Kl2N__8~bw!vq@5jYd70hAQ5~gawlf1Tk-?AN0L$==2&}R1#h?SNaEJk;h zg}J?{EmYI{pb%)?U>80|JCflr!06rXpq8d0Bzspm!bo-<=9BQ@TDD-Ee>Qqs&l{5D z8FKD4#ztYG_xYtsI3nPs9x2b?;(IgHd)C(h**g*T5D44-L|_}XWqR3$a#xz997=L_ z#I8+FPe&^qseb*Mp7oY#OO~v{=1v0~5}`RSrf&RV0l2BB%zvIm>+}eiGZ4yxR#l<7 zy7;VuvHkgzOUW-cW{;tl!+oT~uSW)~2%m$sM8;Yfy#ty;D&Dyx99|~+8aTdZDSIr* zy5N^2rThMEMG!P78Z_wA!Q#Ru=ToFI1^L|NdfI@hHXwZNxoUMf;UK<@)z>8 zOXmVtov7m1waeqKKeEE?1cAv?YxWbDv(n|}Rlq@*P#S}Z3LcJKN8f}< zZH*2vmaOP7?-erj0C|G4pOE}F)4|`JHnzg3M5T>Cu1PSGJQv~zvQp~BHs!O_Y@2n| z;v%f0Z^7>@sB7)IfCVD77VoBVsYmqJ&L4-ag7(eH)c;2hcvSf>p|0ZRP$%J6 z=DpvH$8l$+FdD}uY2V#c6sdR;^}{HuxIhDW@Cc5r=X`~0CE1*C8 z<^33DZrCTKiAm}g-aYTzACbv$jLKO849~W=`+<`jv_Jjmul2anm8WRo1vDMsU66A4 zz?>zoJ^sxG+nzkxURPgXvykZuZ6O#Ow+(Onm7T=A_um>5xYDc5L;* zn@-h5-uZIVat_sR^ss1fwTs7ecQwxOVFm^UOMdae$;r`v`1@)#wb5vfDo)J6e3Ejt zE=3;WhC-(Z2nWs0O>}RcvF&`#mDV6MndE)xXW9}{Jqi25^(Eg4_{8Z2A90gosCJI- z)kqvRlH)Uqv+bRB}Vnj}P^TCDps?|AoF63qAFHIwvS(YUn z)2(8(#_51~rF=fWXd+@VPe1Emw!ijm&NkM@{J9g8$tw7g}JsV?DY8DL!SS^zVFR;QqJbyW$|V{)XDzB z`=A4hUyH&40&s2TVwvcvhv!fEl!yEh4JHl6GPBG&bUNj@I=8Q!!HAyQxB!|an_sgX zo1T$6H1?&D@(J`qb}X}^@m1T#3L?`{6&p~ukxOYl&HRMRdP)|duS$sffiiVl8wDfK zDsy8|@Ylb#>`dzA8e5qC~{*tP5=}FUL+zK|=!)WeW*>NZx&eLjD~iK%D}8)1i!Ipfh1&Gwa^#u-PB3;N8Vr z3nt1hrxwpXDDj7#t^ht3{jc)QnIz9MjIu+-l8vjKyM3LNA(6vRSw`N5!3r**IQpKn zUsHGhV!CGd0lS?N5d=tC;T0M*(GC#T4lrohKYMgFxr&6p6l9otdPlDL5|3AN$3ygh z`A(S3=?=x}KHzK_fMek;N32PFZ8ZB52qIzh-;Wx-=q)l%`?OuxbD!4d^Da1@6?#R zrw=J(;}V;%T(Kiv+;!4y?<*X?*wdG_$QA>ioG@2d6M%`WJ~G_I0n#F_)N0pfp1(9H zX}J@&Zi1P{rwog=%47FzX0e2svz|g%DXy1*&jjlW+U;9^8FtHlK2(!sqEStj z0UmC5NsUrbHl4OMmw^zMV93t7nDL5!lw?jv`YGKXYKZdj^i8Az+OQZt(rJt3PRNp0 zH};=w#yw5L{X2%BQ!rZ{&Q>|BHF$l8NVl6LAO1MQHh7hzzqeP80X5*eLxLH%8Xube z8z}ehfm#~+lq-#BGFH0{m49QP@=TO}-;0#1=>P5T0Hx9A!Ssq`NIEbKSZoU~{-m%rU`a*XV)1hFrD~$7qOnG z+`vjYo^s(;os|TblIY5#-1i$e(zJ-y4uxpcFY7Xe^m}Mq%)2I+{8jb@VdwsNC{SeP z!}3ZmH1Vx6+bAA{Ryp@eVeY@U0P5WG!VaAr)9$1?Rab~`TD(ss_IF;8OS%d?Fe9OW z(vj-a1`Nfy-h^3+RzIG2o^y*8v;x&Y(^8)d_vV{BHqG6S><35~!!tOJPs_0twC!YB z^{QSfR~^p(k=itSELm11g2VZm;Ic@#>C>kc#>NU%##D~4Uk@R|CHvQ8w1N6B&2Zr2 z7bUfvj=!ChkF@2EHK<0|sDpcOp&j&)tFoMvI?wU&Gax`N9bs>@2HUuon|OP4F4pfqSVOTpeoVTfyxqe7pqSUR-J%vvulK+~kHTw6o-s71^Qu9+M^0z}v`4JeYTP~W{$GU;#*V`nX1_11i0QrgBXYo|e^@Tfmnja<2$2Fj!e zviqV)rpEq~9xhxq9z!IzX&7g~2~NKxLl_Uv;+#x7iX zx~s@jLf8=g{(XAS;)wMjm(@EZKicm~qjZ>0)~-~h#_GNj%uSC9R-((8UY=c>=vRvW zS|7F)(rza#@nJQa*w6I4-n>=J5qRrf({kE38J=I^%k8b+S{~*#qw`bEoz%R&65=ZkZh=O)_gd(|%YUwUb0o1%|_gsL5OcX8)chVx3GAh~4Q~ z_XGZp6ZykFU0BK*pbg6^C2~~);|Aw;Z$}oU}MBe#26Yv=P z{pn&+ZG%k5Wmr<}<8OECB~c4U-6|z?&nLHSL~V`b7nr{!|5pA{I8khd*ltZY_%LYWQyfcFk)2=gRcr`yi?3+oNX z5Lh!mg#8LE;XzeLa+fq(S;4D-MG$OaoPNK7W>lc7_h?GhvD~2EjdTe9+?J*x*&Y{) z8eT|w9{DM_$4-;YVonSQ)#c`kN?2R!aeb7r_gt?)J^D!?LuL6qQ}3)hTNg}>O=KpB zu08C?J2v^Tm>O$%-GjaI@}rbbW+n}AU~=4KaT>(u0--zMvO__(K`22W03m*0DTR-pmeO;PcTcq2n zTqh&*<5#o03HRI1Q)3X1_mf6{`7T7 zwV1;iavX2kHN)PAxA1`Ab}Vq=*SC5zJ+2oift_V-Q9rWwwHrj`a8=fA0qs-Eo~pD-ah>Pr88To zzb2U%ebd;&9uSPn=+DThkS?)=4&)nVK98eId7mJ~;y&(4rL1c6-m8(-F%WnkC$qXv zI-L(JIoJN^V+?am2|e2u{A@+@Q7+4kHD8T|Xgk(ZPb*o|r-f>;rV3ulrMz%8RUD71 zJ#qh&xo#Qr%97GY_f^@RW2R@m86M5VWwVy88MS}|QQmh6JB;t8fb5m7ep2?znBSvg zxK*#P{^AaNrmA9lT}7^VLhD;@+wf>YkjxLd^bTcTk9*xGmm=zw*$@330ZBT&J0rsP!FH&J^IhLom!Ov~6MWv? zG%s<24djb`;49yK4f@Qn3IemLoAS9>85ULBh=5orsCX@` zO@3%!vR9^V#mJp|t1=W)^akZCAP>~I96a%b>9bF6YnqVVd4(~o$-f8o8?Js1)7(r< z(1HQXVHPW&M4L||{guyTM`OZXtqDHi=175v|2qWgZ?9A8zJ9_@lFmySC`^HfDLXki z)eG$nr03AxO4vUnD|gMcgx7CQ)RZ$*njAG#ILi+WM!V9hz00T;(b)x&GYeP0ADj1B zrZkHt(I%fAHB*O;G%`#l_Qo8B=w4g@L6uuj{^#jn!D&2@{vp$u>Vf;I4AWBd zJQ+kr>K;m1WzJDn0`w7g)>11_d%o)3btk9P$dVa$aK>YxaMaM$x%$-SZq(ftnw3Jv zL45Icl&3pP1n6Z+7Tq(?TWyqygX9cyYc})O@=mUz)FF;6FoOQ>*=$Us9sdCLLiD@k zoDRowre!{)?iFd&4bRxy>oWJAkT>7=@89#ChPb0Qg|l)iURWeU)N(Y^shci$_%rNe z@OX=jO)8Hg1wzfHQ&&tx3o|YzCXD8MH zzJcXlX`YN&vEnBK(z-yk*7U>KFwp0O0d#6=T4M!wQj0h2GfSCRv(#2nO!u0^Ef%4t zS9yuY9i|I4K{+?xWNuN$*RZFFsnpg&Ep9}am!&wu_WA=V7y5kiIF!sA_W+Rb36-O3 z=FU<1Z-!b7!=EmY*A~K)St_gP`pg#`)bgAChl^t!eiYVC#hndI4Q%a8S^?`1YpirY zDeEFhaK+A9oQxG#ylCih(%yF$FVYel>|Hzcv+^A{Ok+9of~c@@-zv8QK=!JF%A`LH zqS&hhM)aSFO7MX)gwu;Q=q;ZNu)~j4>86X35UPURvJFz-ndt3HpSG^X&NZkgN>4gJ zyw!aYjOAxbtg(w;E>ZD?1WG%((+K=YA<1KL@Mb^dGgp07e#M+_jROe}Zu2otN|<-~ z@Iiy!ckc-mf4ZV)lT*Uw?QS{5^P!weZ`RtMp{d(HCx8!r(PybUq&BvY(idkn;u1Dc z(%F8O{9oN>@9tjpX$$anc;W6|o0{sk6Kt?F(^?+p=H`Ck+Sj>31sT&L<5Fo&LB}!X0RyDl>gI_grWvBfXq4Q*@CinhNI-53^bOzLaP15cMB>qYJNaR!d%{F+f_v}o!;NRc;H`q*D+l9~UcU~0I zbcj%UJg^&h-eTWLlXjeC=JVv@FIxsjqEt$H(>wpizn)Ke+e9vK*#S?C$q{MNggH2^ zlI!{~nLo7=l?%*P>^b3CKU2J|8Xq1W4O2j?DR5K?^x^;TlsrUfL38qqwMe+y*6qY^ z>4vx5ugP~-9=(0`lCplE5Bw+tTF~Kcq*zb~11=pV@vE<$z#6rJ`89%M3xBOm3=*zU zI=@2sH@`lA=7CM*Uq{g0i~jcH$(-g22gfribLn)oF%jPVmURB(#gVH$x96F@_^p`m zIH83j1ZWp*TaGL(>6iRQvg;o?f1m2t;lWc>CUd1`>*VjVkTZZ^Sp+u|R4Fg|x{Y~;v5U|M|1Z4`lNg*+xeE&YV%S5@1{J{H$CK45;!SJ zcIe$#Q9{uf_>@hJjmUVC{8i~QiElB#p~8s8CW5n)0S zDE^I$x1rv}3(v`=V~>=RVqW$QG{c+sFIjqS(9O!aZ@v<_7qXn{QQCB6H88*?;x>;< z#rtb&wnTmYZX=%;y-p*SuIxm~Ie2W5MLV?GiW`Pb7g`JT@6jn;6TUEUz><%oF;3ur z3A_JNM0z|ymGN+YU?42}e!U;8_u5{LPxeiygL=TM1213TOWFMN`Qkzf zj%(Vluk8>l%Rfa*+TVY3W~~wH_i9Mm9fZ>&?0O$qdOd!;b!DjnTD)5mQC=j}G8nm* z`B?|~c8E)=@O?eS5au{oX*g*~s5SqZGD9rbb=g4U3(<+NH&*czACF_+UU4#nyL=Da z-*s`S)9M1&S|amS6O5 zM&bfDFY)BNp9Qghu-9|FRp->?j0XZ&In=fHnERTF!hq%uGPTsZlS)`HYB^kwjRh57 z#FRlIPphOBAiZL8Y*RgV*uqQ98^x{g@oy_%jt|>2!^CDD`Qi5`fKOp{-$+?E8noP- zEJHs=$dmXDtuadXA8z@#)^G1=P|yjzKKDPr+EyiY(?}Byq0HRJdUWdm9>&nS8|whx zL&3!p)+cjg7qrivr~0R_&mb!H3-P7Tf1Vpnm9o_7+7UIXd$sX|K&D%BXw;|+$Tw)E z{oM9yoc-i%Ebq)Q`G!HIO>>A6)$gweOCY2?vuOQpyGZnMZ3uVq*RNNi^f*SrsGPv9 zRuZ9Ax2<^($I)Xv3?$P73){6IJIf~o9+4>&!(7Q+Z3Q|c_P$oKCap)TS&GSfD>hUZ zt}2f?>xs4!^rm~9Sv45bjPHQ5gP$&{D-TZwk>n8=eOI;V!msrwrx4N!1h_xI*-G<=y>U+SjJv&k_%<#c3Lgrd z?u_vN^1Hs{jT-~hbEQoX+0dwQ8v<`PhI>m|HFD0h!!-8-;joWLM5qH=ZO;(WxX9B^$k5L`R8qa%- z&}(0CP^gy8#gLzPl=q;$3i$d%dD%kSu)K$hx6PkmGVskG%TCkEp6#5UpAM)oa}!hx zdc(oxkA2We+w|M_MWMVDr1(^c`j)>f=KX(u$n^saAFwX^f4Wy z#JwU1(=CVNUO?W|T|WX4G~cv)c49Y3My-;SW(m@hf*&f%4}U2=ff5z3H}8>hc7%HE-l(>p1iR&GbNB^ zJ4%xbU+&hx!Sf<=J(}BxTAM)k9g`oRqtETQ+*kq^xO|+wdIyaOi>h7|IAMF!Qo8qy4w+y~)O^+22j_ZEq zW)LbTj-!}gyx`wvAmJl3hmsG;RdrogS!3a`!bC#WP=DcSh!R#y(@>JIHLJFSz4K|N zvu1{C)W@vtbV($y9MF7LWCoOBNv00PL6My(voEb8wB#(&GHcdjc@-=5xqO1Z`U$_D z!N>i4L*K7(Nd8RReS<_K64UC!s(Gl#`oj!(bS6VY+`in9OTxtgq8z^Y-LEa=Ag|pB zpoEaTfnY2%@1jF7o}T*T#NW(-6oYK>izWWXx7Mhfqyls#rvzI0JSLyvtMu|w(} zD?J|q2-~&D>hw0VWuMKUl@L`jj~ekOh={gjHR(0EbCltdTR~={i|OLHX8gmKTGV6& z>SJ7I1ADw|de6pT;$-1;O+{C5)mu5MqnV-bY>+>2Mo^=vX)tVInHEnP->+hCcu2?b zG%)Q`jO@x5kHU5)J)3UyTA|^zK(*;-pOV-F6D9l|RzfrOOed@?kZxof>ox%-hCaP# z%@GN?3=p2xLkAveSBa~#9u$r7)&{xmPt@a5X-V*pJb%8zV8`^ExS z_}GfZ@1koF&51vXk_-<&d!;6Ac;tC6o-Lfs2!PgpUdT4Bt8zq17aQGq4oF2+1S17P1!213Skz1coHb(=;8=8Vho()x4T`iarMR_RCp_szMm*7*=c*JWH4l{UBMf z1>ckh@pw~qrmNM4jo44IfausenB#G>3PobUE1%Llf`sN)JqR{b zK3^S|th;`c68#)Jd>n!o@vIRh($0ht;a=OlT(;%GjGQ4g2ScV-vvZ2mzi9G8b?rfi zhPTLpLR!w!b*@u%96A21;l_{PE|+Lij>?_%c{wU&hqQ^{A7p)cGTXSgqDa((i5E8Q z_1|pS3~Lwip+Dj-JyjzLk`N@P1;VtUPLM6zrGZ1V+d>Pd<2DyT)4O3N3Dw>itHX_V z5NOaG<3tD#z49cjRp@QYVFGRQM1|+)R6J++iXNgh1jAOjvor#-4{@J^#$%ZI*dvY@ zetGTpsBp$fuPlW(D`!%Opx&ba{Yos%E-lb*4_j5(LEx#iBt_vt9V(|}a zBXoGTll5FLvCG`v>358AUk<^aZa}~<2LP$_H^sXxLQu32N>(Ndv>r_HhzdSyj1p zB8HgBvN;CT#;$Y%=Q`Jjr$nn&#M-7Vt%u>U7AafRt*T&fxAz(i;TKmlGsocJ8F$c~ zuq+MF?B-g>3gYp0t&X7L2Qm{e+ox{)r?hBztlZU@Sa{mrH<%*R^?pb%!>6#-vQ`E} z0b)MK;<}Go#dFox@+<@{Ra(#5ISu}(Nf%S2? z8+``5yczWFvivWtFharh65wnEOpJwXC&{#|haY)r-8c9`7XO(`S^`TBw2&^nV z^>gZZeV%!4m`cbp@MUd*4H;ih8F)%Mowz1g`l9y?JTNd&PQnV30j&&ng1_=yA^{*_ z3bAnq_Tw#80GypPObnQjNt?iDW2~un{wA2$9Z}O|+MdF8OOk{_#eS2*AwVUNTH`2b z%%&;=&FBddd-$Ks0q4RMxP}9&yQJF=W8z|H?#_$QBo^VDV5i^Z3N$ebHN793+7u4YP@sep#O*tQL22vd_EYG}98=8sNOZ?Xt7Tg^}7LZIlg(j}Wu9^|w>9`SS;ncJXKleY-aIBaSa zNvObm(Js>&vwQ$W&KU4|EU!rk!~=fYFR=*NX$ahq;$A|vf6|SO6>{8?(-Oy5 zz=~x9>S+mPz(UA;aU_Bt!g&6H$)U*`^9eT-O(nO>E{U|~O+KBNuIK&g>n0dPVJx5j5OOgef{i~N3(%|66=6Mn(GlY>z%W;Z zfCrNNxp^i+Y7VsozMBMLH|djgWN$)cW^i__i4t#YEN$H-%pmj=E%=c4-gq!10CV0p zj5g%6Y-dRSa#6A7_bkM&jcHyy!_$j5hn(Ss<#)hCqEr^apT(&jpqqCiBPto*i`Qw6 zL~b(Bi>J>{=Hru!j1cvu6s=jco>+#4)s}?^$cn(E?ZaWO2M>G;Z)n=DaOb56wF;}2 z?j>H9myCMAA!OSOwpNY3#CA#d&N8LI^NDLTMN?Yioyoz|YJxWd`b{BB93W%O9?1Km zj$fN&8<`aO{{A(}*iA7TC15k{Z9Ec;!M60t7wzin=tDo9fl{C`dZs#|;4q_BjNEUjfq4-HFT6Jr`Kj zT2P$Cx4*Dj#LQ8_ag87I=^ZTT@;0m*$Y%Ls+L&L%t}H9@(+wB%O@x+-jm-w?^$nik z8c8i-t*Gw>2I*VpH>8=9fv&7YZS(!b3UC~rh-7T~THOi{;G5yU@mSSo`(d>Ps~UcL zy1zS-+y6=mXdkrjm`yV~d-r*7->Uud^UHtJ#Mdj3FE$)JjET*Ck&_KOQu;Ot92`y zV>Zj}<~%4KI8%K;+2g0>C&J-(M|`5U^$3-~O_!(qz<67@VtZ7XUxfkUvcV1CNRLW#`v@cKchYWRm&A>!ecq|L-OO*uwNVTGZfp6VqjVstM$0zfq+){VMy#f2 z-cC5K81@dodY7u-6?D8FqKVFQ_oy)Ygb1$m(2L_gT=0;Tf$YNP6Lxbqej{#}I3tcf z10FNxd4Zc-h8B$D1QHhN{NHZl7#1Is4j+F^{doC?Wrp0w<^^z4^SmojGEdU3qzrQA z#cOc(+5PA;DSK`MDTdWrUl8k>htXeUKajz}^A23`&>F{BNbMXPaarQP9dhG%7^I&$ zpXb()Z=>#0*?cyn-qQThI~Uzlab_z z3D>t>lp$dQHX)Q5;`UmW7fblQl#Q6Vy|+^Ix9TttV`OnrQKcj+jd)NQ>T5piBN1mXtL4XG6SWtw-i_KTd={Kx=DEG~<>!=^yCuUf_W_03(_;2CBpsW8`Gk!7Tr6xpSl5L0;-tRrzTd*Zz&d@Jwo>zI*fxA@6PEtJOwan z92I%Ek{CH9>5yqjsbhZlS-&LBgJE6!hc~6=-%KKo8ei=hmC!a<19-&DAr3r9cn|jF zY_UwUrnaX2I5&FhOWkGG3<>s9PSmGNb^n3#UWcX=-0N@w2Kb2)*OO%9mqoNZ?x&&V z8xx)!PZ!KAJoz;WbH~K`8;&q3349s@68VYD9HkDhA20^Wp$6w7Ozn-QIdhx0v`sop z*X1Ots{H$94ulAbyelSfe*q=W%`lOLn2t<~oSQ(U^?ZqYE`dW#pr423KNFF*zoWvY zp95I$ppG&1WWhTNkVnq;Oe-9YbpT6hQ01B}PE6@V#rH96b|+q)(P1U?g0DDQKGS2> zpPT>&0kbRxSdhaN(wk4#IpB>`stZFa5-^bH(^4ZlJzJGd%J=>PEHAT9{#}SrcE-rhUhr+=i{9oU&Vt}O*o|ZeH1H!=RC#NWhQvzl$4tJW ze%4JS+_Pb)!RS!)W?+p+@Q;wL{jIH%-4?0o=CQWhA9%~3+v zsB2VCP@T?@me8i1I+B3JqxJrKtQQRT#Rn^r^)oT2m=$m01EN_md(hsp~qwX_C>U5O1C1)*cbq2M~+OFzZI#l;^X1e;K30`dd8-c z>LNG2FP1r$&+a0=ceX4fxiLKNZ~5uorB}+^T;{LG%I_?8!gPQoQ07_PGO089U|m`j zw!_S;DjP-8JHE{|V>|xJb6^+0x+aCx_}(o36h4nya&&qFSBBfS{Q-wX3pAu6fCfhq>Y9rbt8v^ZUlYuor4{_M4a*M)1M!gC*CZ0 z-FV}ut`8EsF;D8oguT0!Y~t!09?H57S!n824q9xS6}h%&5FNIBuwk=>h(t%|<?QPr^>Ix(F<@r3!)Saw9RsXZRyIM;<1kZbZGeH6V$s0~Ie24iPX8-o?4Cu);on;{J7*z&U-Yx~i2&0()j^_Y3w zs)!4sMW4&G^h(D??hLDy!4qx~X-xU<$o9cR}7pCiCg7T@Q2#dAUY zB-2_&X=7MizKp|*zvArw0=vJ~`DzedI8-wr2XQuE66T0jGckA^-m@ki2Q^z@*O)Ni zh)60Pg^25f+*5^7hMpxo=l#W`RlC z7{W27GUKPrZ9ei>h;214`?OF? z8EnmVPxPL}M!QF}B1fXfhMW;cNGU|NRVNPhwO5Z|ay@_=!(sZ48J+u9%jykbxc-}; z7pCP20DNEIvjH<|aeA{jQ8%5voh0?eyK3k#wZ*n0cn4&1PIWM{q`|p*1@!gsU|t+= zRFC_F{z96HpK@_8acP))fT@}n*=)TL;@W#$1iu@-tVAD0G14H7KAFwT$Nwn19^3Ra zD8JS4<7t!Ypz^q_Nwf?eT}83ZKvYpWhHad%mtoG1KQ1B^_)(K!twL5`QS5@rQ#z}q zlT=S##Qs%q?l!(}Em-TZjynW)Iqd|p%!NT&T9(1~i?v?KQVkwve3nyG^7*yFgghZH zB=k<3#_G)$mkv{#U^5Lee+R}%zd8@Xk7ts2)w7n>Hj{7X!c8#?7MK~64N@2%hJ;AH za0WMA&|N248P;D^1O&!zJZH|-zekF*{9TnhA4VeL+9+AGBkm5Lva^8y;q+#)l2gd_ z(eP;{m@@lEyTs9VPGSelV#Z*q1PstNAl$Sp)$IP}aT}DdV>x5{uA&D0%d@fcu7|Zu zz{2*S>gHQ0g7YoTyj6X|{F+uSnwtLUV>(@-O+jjES^`7j%FEi`FF%m3lC#+~Dvi5}ZklW!Ad zZce%`cv#yyKNg(XYjHR0}% z(Zzo--JgxODv1JBDZW}yCsG3o8gk@yx!DaZTbMi?>gEZ`2dOQfYt!!Q((N))HgIL> zSyE>E0>&z!t5X+cy=81-j#V_x~=&q$Qrah1Exy=hl31<=kxs;%7cy*MA91 z5(;2Ecm+^_?NT6e~DRt_p`0*s`l{dZ#7d?X@QcG5)Oy-LT*kJ zmy)&4$Z8r=GBWU%!%{CYfn+CLW?t>p&cy%3tp9=KU9-pyh1Oe}x|0=7r^!5~5Akkf zv>^aHsDexpGnQ0WN0OD*q$OBs-u~Td|L?E0b&=)CPM-rJP=%z!!$W4^gM0VNwLmB4 zWYx7;oi(x|m8p%5CmI1^?RU?3lyh@>_=_&@Bl9&c z8CD8~PQHRne$kCJny|vZ%ZgaDgubjGi%mwS?eFiGxWIF%26=V&6#hRw9rgJ2=i}gC zt>yuxZfoS!Jx&uPvo;MaEiI}c-XwbhZX5T9T+0x=pS(NzD~`>BENJ?UYx(bXL4VoF zNQay^m;!H2Rmie{(#kA#C|cuvMg+}i{$`H;@8h%0=}KE|R$6bkCt4Q0x%tqfj|>7S z761H6pY^y$0{{R-4f1-`R#uLZwU(!MUy-#rJ;>)0%mD1);AMXqZsGcO7RlX*8#gwb z9{HwMO2(&5&CR)6t{Ogk_|kVDr|&eIK>3%J{L2}PH1nV1mu?>oiEruZfn`!wb;F!8 zHZQMVzfLZx%8Xdk3^{O>cLguDok!NVo0&^rzuwglu8EO0>shnf zpN9N;=k4X940jsb@88eK3inL5A64e_oZV$)WLOPdTwJm|Lw)AWi(ym{vknjyirZ4i$>+>=o7L=b5&<7Ypeyr zq&Ku9iawx|StaUou(3K>Ua^|YX&jSwH<6D=XM>(T+u6WkkMExi+14fX88gDjz|awh zF7gDEhsiEK;m=F}OA@SY0TFCwDZk_YF4+F}Yu@)6-=Ayw&Wg!kE(;@_*;6*adwVO$ zgC|P>zcpRe%Uqm!98O7lSv)gI5YRI1lr?+K@X9~5{EzE`$4j*d~E>lC(5{$Fdu11=Pnl|e^GM+2rVW&N!Q{)d%@{;UnEmitWqsrCQ& zu0{Dv_Z~fh9vmFRT=bf)8<`Kq8vKEW$aNno8gR;ge8)wdi)0gi;iEqZ|F0?RzsK*t zT;nR$c6QDu_i)V@5)i*rLlzfqTN})B`9rfsNz0I+)?{;(7fGFD_HH+pPIA4 zQW^Mc(bE&Qp?h6M6Q}yNCm-Jl zXwIDAwt-6lnYl_5Mr6N$x9-QA2;Ouh>tN#cGOD)bx-gb6EG*J*+x=dbGkuTfBCq8P zr@J5L*?rzzyk%J*^2uYazO$&P*Z|JK#bL2v(_Cuk!%9vT4CuRAdi=!^A&s#C8SAFO6OsoD}^^0T>iLjtzuql9KU|=XQE_hU7 zxX1mEzNd7IPb^X++N1oG_UF3l1w-k_0Hr8jm01&9T)Vy)B zkL9Vksk1X(rWf+@^DJoE)Q4|+9_z2q3rr1FP2H<4n!-rWxm)T(k>pe)4Og4zv_&j4 z6*CUd)~~ZgEc}&JtobNHh?<4P;7;pLuJMBXz6X}g;AXGZU#SoPkD}oNruIYPCyvym z@S|I1Oyvm&2gVf3&&)vP(2yGbo4!3Ez$s#;58EH@Q7E|I6us5@d89SaYs{Db<||h> zyhiL_9PGc(R)3;cb#dDnk>JrBMyh`~T`4;q_~5~Vbo0g@kv#!<$2Vj}V=`r;(cQK)5 zoj!Veb=z2F*0o-3niGRAnJ;fY!y;H`)G;#E)&lG@JI@@yg>s$+LA=i%Zx_;ba!CIl z(%w6&sczjDriumyR1lEIIk5`&VoeT}q)4qfC97AGId_mM|N0pH z+ompyBhH%F2hCW^nJ_SmyK2%W{{n|}{o|<&;ez(Rv5B*uqZ5=-=w+O9xODk)AtKZBURrQ5`2`=Q;q)qXDdCy20JP+s%2_eWtJXP z5th*qnb%dAsHdx|o7*t~=#p(c4O~AK6anW1zLQlAYl-TiH8~=D7aTFhWxs93_~F@5 zYq4+@D5q4(o3i;mIkg{QJ>&dM zCPIh7#h?E-F5Op%*(w&Vgsyxu?Ww)ga}bIgH#gj?6|A;!>nc|1s{di+wZV|TUwpLc zZfkdx_OP41u6QQeKc;pfS^!}(1cZgXfx&XWc6Ct(wqIf6kUd*@D=8Tf6afi6HQj>0 zbQqe9yC@%ayl2?5qb`uKlbMyB;9&Fa_wQ`FS|jSYbI9F4Hx5n*l>1Wo4r&93Jz#h^ zKbA&FFU|fnzq`=cva4r2G(IE{?u)LpH@)e@pTA+7WGF>ZND$WQ)5i@Rl{rHPhr_ioC26{a(Ynz%50UNw86+c;59hT+8*_f9%c5r*%|Na*a z{(*5k5DeLMzrkoPbsipU&@4~1@v2%sus zag8SnbWW(F43qliBRvny>QF2Q(LYhqDQqy!=$3q{{H*fJ&-piL-lY;ZekSMkr+kSY z>~&;olNS;awm5pq;Ty!Tz?gLDexz{fhMCfSFSZYy{YY+-+(V3Xo;7-Cv&3TV%!j?G zqoYq5b|B9uryx73S`LFU6#A9&b?-CVfmv$>o_-{XtC{7QM^ad$4yF(hr7+?Gxe zrTw~=4g19&rs2^<`^VcXD*;*dHB}NX`rVe1y#-B7$d&X-{u}Gvj~cA^DTV33CK~g6 z{4x2)6PnVCfo$GaNU6glJo5BJMgFD1gK7a_=p}4!5sK2uYAP8k zuF{G&60mDrss!RV9%=lF3L1bz8F4B#k#eh$;Cx!_u8|UYKzRQ#VmLf#C8uCrCf6Ti zcU_1XVo-G~vi2Qo1LK@8S)_qzRjttqII$&w9kp=0>Ggj2@L@~#!TP9~isViR{-{lc z2LBGr#ILHZekrfjmId5A0r_P~y;CAKBIddnB8!Oqhf4qe2E&d1r&bv4iJY5RBPCxl zV%JSQDjw0MwEfDzdi1-uaw>l%j;$8UgqS{9e7LzthPJJ;tCSC;)qn4(poPo`@Edvh z!2fm{JW+1(__&5EcWRK2Fs3a4k1v{rYPrX3j5grDaMsl(#D1~4;yM2Lb9D3MrD%m5Bia=McVss;N@aIbHV}8(!mzI@faMURLQzQ4^)ns=>0ouek&Q-H5ktusP zR$(h|zFAsYO2?^lbPOk~!e0zkGHk*Yzgo&w>;V)-9|TqwzAR}z<1J(pTEI|1*z|0) zmGD?Uo4x@$K2=kLm^Q5+`V5W!hjmk-#np1d#8D==t;VTZx0PMcVEx4WI_diQKpx}D zUk93^V#m-Htp)=w3ycOsDT_$=%~XIafCeUebrB35VTbipO*Z(ZUP{nr*aY~NzOu4% zZWL_79R((IgVyQ-Lk9*+L^xp1(`&}3J|ja|%$j}Qx5o!yBo$A{DCPr2UQ-cC1J73l ztqCET8XG)Oz@>S?sBk<#gtr|w(lXwM?3>$u#CwXxF3C7n7!655Op)<^F@|D(A#7qE zgZCR=`PiiyL~1S6f6tzK2}|owNyU{Cx7~@_E2AT78A>tjGI=pN+6fM0Fqm@6jXM8z zSO!KbMv_P5F4;Kg+mYwf>U*#j%O8lB@4Gb=#PfPeUwO@8nR7l$xuWXh13Ch z4xV-ht|0^2A|x-rv?4EvcYdPU`V_pdIgY<)W(N*XF4dxqEp(NDW{-BfLEuIYyPm^1 z{S(yQY8^HWVdD#dD?lTL)%*eW%fb_3JDLlrik+%<&iH<*E@%PNN;7d(gO1&L@aZ~i z)^M4n3d{Z1PnR7d*}3!Fb$ph6fO#;RXGJa7;D^VCFh}0OTJo@9R z(z}(mmHuSSDe{jkR+23dv)4PhG?E(B0eA|o;)0ow zred9z2(m7S!xZ#z0;t)`>8|U@O7`)-$p5Zalb5cbRYR&K*=U}1;jPC1ysQ#(7YEKTIXZ(M zo2^P*i@cHrCt&280L7qCY_4@v{>NDN7VhqX#cvXuMeQ>hn+vf&Kh!II8Kk+W-PTX8JD~Y zo$#6q&*97Apx+#Ry6{Up)YjOI$2Km=D_AH zH4{Cw_J|)~T}&A$^UYeeOVDDBk&)OEFI)rVLH`!%CoYv zzdD#U2ek-k>lmZf^M+s!D@UM<*6UGV&9D$@rv77L(WHB+_Ye*I9rp2dhS6c3EB!b8 z{0p|aF;_4A3qeDXXFlTYvFGt3O}S(7)20 zj*0c{hnrVWo{50XkY12jqt1fw|B-E?v*MHOtuHw%Q}tBVl-LV|*$hAw;b0DAUVi=t zAhXhaj*1NHnmzW*ntuRHOi1ghj_sX&HY2+Q<5dOvr;&ygS%I(lQT^sBsI7s9D`)}A zwhURH^*zSjxw*-jv06QJiE9qOe$|-A=BWMfO!b8onI`PP^2*R}4Sk`T{P4uCJQ{6u z$=KL-b;$=K_`#mS!@ZCYugRB&f4Hb5pmaPLoiX8y1W-5WDf585DcR^c2!z>?!F@chY*wTRD0I{y| z$!=Z~g^lSS!Ih3v2vvSbpQ4`NUjTEyz~v#8V*@IcLut5HgOHJyJt}@*RG7XHvM3=H zQn)mvi7^=JWH1r9bEgtO1ZYrYwcLkHqI`n>eR#G9gm`t*EFaSvk{?Il2d7yK64JhTgWQ!2r;dzsUVSPtXN2Y+7 zk`i_)AHc`7=JnPGE!yOrq(n!%c6bpb{L>ZAbFFlfslxUNhEU8Gh&oZA>*kNpZ_rRWpJ@JlBh_yf|eYFu$PPNuGN=NWR)uN=ks|Ie!v>j}YI)2dR z6AA5Tqv7c5eI9ziAnQLfB!fnj-tRoz%jT4sn!RN`Y4Y>WXw)A|1i&-Bpa9y2ca!F| zMR_hV$Mew#T@R2i0oVcdk_4V;Brr=*ywwV<&CJY9|I6FYuO0wcUpE>0H$gEGJYH(v z*(|KNV1VQhb!wuoCLAOE*~!-QRA*KJ&?rIyXVI2!qoo)22Q+{ZlmI#%cCv-N#cpbu zt;q%nO~I|vGRs^Zz_Ks<$ggAWE0Gt+Vgxohm$c$m^q;e-HYn|D-8a~rNzu(3C^tZt>X zUSR{94LuFRUU&UlV)T{b2#JVcUozGp`hAj96ZC9Ii zyiE5^uXBG|{QvF4{V&gd^=oifjPHl-j?+A1$kCdpBO1#)wPNbc+9GC3L2trLQpH^R8Evoql^^(C0U>8;PKz>tc z)%Q(xp}xFakS`US_pBM13YpMuVg*Pih{BGtw}Lz5`jTLW&zke?4c;O$C*d&31D!AW zHZJdPTyNEXtd(Uy;`6Q9XPq!7^>(t2w??5&QEMcwP}hFV@BDZ6&&ZzNm6BU^Mmmr8 z0-;#Fh*JY?T~n4S`--f5Z9O&ELs7?Pod;p=v>alupgx$&$zM_ht~tu~d1>qzwWYZr zO6q2@Y_qgIb%rI0V~;=U7ynrMat1Ul+ynrVi^S~4U8B$t$C`yJxQQ*8B~S@*c@eAB zc!v_DAG;qvo?jcQ$X7}byiMWDaDQ{gbe;oD209pm!-h1^oovsv92lQsj7#$0Kn5Tl z$oP9L4vqMO;&6>CqHQv{{nccXdE!KS4;@`(Z5nD&C-mo5{hl(5gooX}`q`A6oE$XR z&^Lf1)H?U1C;;DZIEgO)I2;!fg!CMD4!f||F{P&Q%T+5(XIo!+tV+_{OE^((6!Nz5 zXf;)y&HyU8=?_eo)*ob;zsZ=N5`3QMDd1*g{My8i@9AhfJ^2Jomd{cp(_AgMjq9W} zospFOtA8PeNRivGMqNF?udM-s@T7UC>06O~6 zQq$c4Chl=K;hD-N>cqLPVhUvw2oHbrrwbg1kOfSXb+aARf27*_n=?YfuAmwDry9fQ zJAic>2P3z2nNowADJdxr5~ogD0p)215I*%>dbW*c#)$md<7($QJ5WvvL$Pk1ot?Hv z>PCU90E_7-4qKAaq|k}6);fwvFhbP=BUdy43R}<6urf8!^Moo4AuyQ-Lr0x3e1Y_n z!tdtaQ00Xq8;_A92-JaQC>|8ckcSIY>|RZvE@^r7_3KwI145{P-;XDe?D%w>xzc{y z@87@wLr!!BPFGayJ^23p`{qg_pc97CsK?>2L#$ z+NQpiKw=7f$1^W)&#U~&Ne65nCdd>iBFOnNMkN-s*`IBc?i4Au{&|fxE&L_hyz3&? zZ_kv&W5t`sA`}{yp<1=+i&-@%vMNddE#=|iA-XwC)u21zAT@)StQo~LWnjM9->xpC zI|j$~MY07worfl&B@AT4;*gv*XFIx=T(-~}nRZ@-e1IN@ z2W+*Lmd>&BGr^6wgATwK3oK-yTT5<#)YsLu1_&9g>9W-W zh$#1s8;@lYcp`b13-y!3K6Q3<$o;aSY+*ajc7`69Sa6>AOCzosV(WaNp253D1u6Rh14J=;gk`I)+8Y5T#p^lhwEDiNbqB z7nj0nTeaM8DRikO?_lRO%y{ZVjNlbAYbX6;g&pfQkbT>46qrwc-haXZZt9_~FDGPT zm-{ku&g;P+PjRR0lF9rVA^2}QSzIxIubv-GSGrq49yVfz^$+$3REX6yi|{~(LZqs7 z{mi>~D#-bKGH?CF=9%N;V|zn<5uW|s*`5q?4PgzgKe_g(xdLq&dx_EPJov% zmD{IA47kz#=Z6UVRP+NGqoWUo@0&qSqISgw=rxG(F;^^{P)Nuk4}D?Y7yv=z9?+UK zKBWYbhl0$^>zdKXo}Nn3kg4~b{3R9Z#`2#(B(M^)zcn=l+e@4Z*}4F)+Z0FFd+&wu zBv!ej3;s;?8+;@_tF0Lxu{b|4Wm>{*Sv411z%vIHdZol>bql~UuH-4FE=(8~C+bY6 zcD`NOBO_^}3v7S-o%$ZLdOmsoZ+HD)j;3yK5IIgR zmij$5&d$lq90@shxd7-auTXXqyL`YN$7zY`Je=o}e-pCD$;I_Uu&&{}6=P~Z&k7Lx zjsYH_#d#fFwe?Hntk1_keb&Ex^nd%ZJOezG9zW~pg|pHCy$~n`3VG0hxalHii=2)d zPdXc4LZML6@`MQ^WZxZWQl)q&P+%MQvE{V=w}0=?%iy2h2GA5vA>UXNfyHhfyOg9P zzk9Ul6W9p(I<-=+Ka#V5qe&qNq<+e|Nw!LD_$M{!e|{4G>8GMpWSW99RPF0z;M-Qu z(u#_DJhmq7Z{NNR$TCw!MMWk4R@Z;}H~)X$DcL&W)8eTv{I==%axMUduvlzw;Lo3r zo~x)-(Xfe!ED=$TkrK0+rK2N&Idn<-vza5Wgmy6s7_mk?R#W?g0rNXqQ)9PTWnhx5 zDr`DZ!_J!{B^f|4TlE@4?eSHQc8WvVPjp{p$y(1QPwSY;-Kg!?FWuTmH8XppHjqfw zvPpOkRL((-&z(ERb&|S#SWc}WyF>u0pN-rpw=TDd0dLo*OHqwimLesxvkc>tb9DZf z|9O-56;3J76k3uxg|r3=dIQ830%tdcfO2t40P?eLt*A~e%P#a2hu`)?bgB9C5ks&$ z5|gTweK!&|${(fy2|fX&HV=dR7LK42Fxahv(b(@EMWX(t%;8K@+u?!|;!MiCyu9X2 zGGdgLU03`)Q(yb#esANf$>1Ln9vh|H#<0FiQk(`~j7>Ni9b%<++qeq~0GQ%88zL~_ z9CyQa;V_n|!cA7~ls~{-|5DJLA*O(#4tNG+PJ|q_y*s{DjCVs0z4e#Hk~o-L)I=XRiP21 zy$aOJ1|x>GD5F#nWUc3#1vz%V1hjL?x+TNhBO~jmde13iE4=Sv$$7Zf)a`56lH_-s zcTAHtw5O^5w>wGT82}(%pqH%Hq=@hd)u;5D0u%+^_{L~i&IJ}>J_;w$QQwI%O}S4) z>E^CcZ6FdF#b7k-YE!H8_84H0Gv5625{+bFa~JkKu=72|AGZ7?As#~W^Yd49R)_Ct zG5E4+ObFHl1oboeYA~!`mGQhR$>)FKWyquq*dGbM`Qq|Ih=VtxC1e0|zXi=?? z)(GhTPB9#=(a2`-*zO`JjY}A?itRzpK$e+`2NVAd+$Hc^3SbNeb z?9O3->NfX&*fwv=M_Gh2;Ih>Gh~~iBu7=5h+*yQ(P(<$3Y7Sq*-mqu|BG!6~XRI6Z z9H{_L?h(+tdG)y)VTDNn{q(67_EIjY!oYPdqH=Ux`Wwn@%PdaIm|pO7o?FHm03b8N zZTUjHe6ZRp$s&C(m8F%N&2K4+9`_wnPksskggJyDsPGm~(Eb325Q z!^;!p4Uyu=Z)Q2>6-KU0Z{@mh9S7*=@Wxfr)9Y*Kj4&mHOOjH(OM$gGMC-Q^!Y8J1 zs(f^j-@xiIWvKA0?I?@j#ZNJQ-w3M&JHZX!+T$KRM9gI?!Gg8q58%7`jr=Q~ZZwa~ zq$WfwHd@gilM>LW!(ywb-Hufo{W|N6g+&W=;A-BRLs+?#j0E4kqzBxrpD)kyugr^O zzF$0d?!|ehTC*10|J^q1!vY;v+aXj8l2m}#l$cdlhS z<;s<-iQXLxm4nybF3&Zm?{T!sDaxfD;_?z1CH0$e8Tim`6nz zLk7>}n2M|E5Mac^5U*dq7OcU)VLJ}C^1T|3EwCB9`r)7v#A2?2Ablp4_4pyi1_YRS zO!Q*BQR{u+fnM5mc5Rb|^6wNEM(De;U(E@^U-dOLHLY{kU-}qPh2?BqKIx;OVHKr2 z_`c*$(~5n<){9+@a|lPFu2bY~7TE(KRs)w6OJTlmvW&Q;+eiAdj`g&mCKZUvjBK-9 z>5la8*?a0DLdm>+h1Jy~oArA=Z={7Qic$s$f>+og4dANdxx9mk zG;M&_a2)mHhhwzM0dqKkA_Og;TqMIEpjb$RLz%#C2Kc7y;3P=tz+jw z@5Hq&E}+PDhrtGM(DxjUY-_Dc&~kfz{Z%eJJ$y4IWq7AN+9qVL9hX0sF8yTbd5y$v z&eVByl-68!R1FWBW9Qj|4OPDF$(Z*D4bE2?|9&F_o4!=O@yMUGA(oEA^7p%w2RSAD zGJnS-@Ski1pGeLXJPpEGP|~NI0H4hI@lt9Pkj9M;7wXH?>CF5rDB#n0c>r|jB_J&) z^^*80QM=I@CqVl{+y3Miqj5QCD^f~REmOQ(*`fPt`i(H?$ncW%VNG$NRXTv~!#d_0 zu|sgVc3}vD$Kboko~862GmE%eZaCAuzY2C~Pq%3+O@0K??Q1saH}B@GS}T!#+gQuB~4 zs;FYi{}lH__n~^|V|PgcVe3?y^@DPEh_Al5V6r7~jKvk~7a%(fmfXiJ2>6e}wu3n+ zWVG_)j2mHiJAj1EV9s5>DQIUz>J_foCchLIF(cA=tb`-t@;#>EikC@Gu&72FQ`=CK zkasgNv+lzce#o5mh`~~I4>=q6#FR@X|BPfWLBxKHUT${mdn*72rivThC@1-)&Eeax zMTXd~-A?uw7if)Ovz@{YS2Z)a_pCIeA3hy)qxrr?E4eM}b!tCeeQ%#v+;Na28K+lN zlkE)f;Q_ag?p;LDRl>|I3r9K91m4 z3nY>IAn{-(d09`g1Q2zgn`_cAZma-OOSmu*c0u?sE-aKmP{&HNFVfK_D6FR;>jv&M zm2}CYCr^B$ZHx4(hOhAJJ3K#Oovh6N8G%Z@tN3*PuAk|riNz%c_@)#eaw@t+DRADl zRi#`pLjkuoVP*`8G@ocFwiQL?(XyBKM|<=ILF8?xkoA&n&pbBDOUW0w4>m%($|T$? zNuluKQ37lgmE$NH_YE`V(ug{2fG*MaE`*EN?c_1cT>U1`!HD8Y@*nVH#pCu%?p=E# zTyyMh#NhY*w})?Aei?kMSo+2Fw7ojlC){;&(W#}~KzVzwU~g~T-~ZTl7&*mJLb@Fe zOEv(i5q{&cvDgBGnZSLYRJtZ^Qm2-SW5MCX~f1ZYIPMqHq%zEa`*~&lCxDz))@N)ce2@d+FrSFt0XUY$kaDq zxS*sPT{Y&I>eY3>|G~P#6d_{a_3@+21e;gisJih+ZM=QlH=e$~2|_--)StP?`+NPT zZQyMAKQ!m2WQ)&IWp$LM1Xd-~ptOG@x$6!Cks9szB>8+!b;N^c&O}cUhW8(1LE|jf zE)Ma_7cX~!T+P2)6V#|?ZcC}BC8etsKSci&q={2N;zLq!mF#X5LWoR&0Ui zh0bT#7V^zz)G`$+OcAN|wI(u*kKv;>7;XGSDSb0H74kO!TXn+bQrFkZ&D)?+abU{9 zX25K4eLfI+TO2@Bs7c)@eHo^;?&^}77iM4>l#LsF+_(Dgb^tS|6C8N+YOj~OpIg^pc|CaXZW{@QnI!{iKoNvp)@=m978T)j$c zc9c7PJF2qcVOuO_pxfkf z(lG8`ULN~~K&?)rJ4|}#=j9n_CjOQite1SiW;J7y)}@cY(5mBPsFW=Z¥XtTiZf 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
      7aBo)?gjfCPdFiT)SK4e2aWJ0r1 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 +} + +interface A2AMetricsProps { + a2aMetadata?: A2ATaskMetadata; + timeToFirstToken?: number; + totalLatency?: number; +} + +const getStatusIcon = (state?: string) => { + switch (state) { + case "completed": + return ; + case "working": + case "submitted": + return ; + case "failed": + case "canceled": + return ; + default: + return ; + } +}; + +const getStatusColor = (state?: string) => { + switch (state) { + case "completed": + return "bg-green-100 text-green-700"; + case "working": + case "submitted": + return "bg-blue-100 text-blue-700"; + case "failed": + case "canceled": + return "bg-red-100 text-red-700"; + default: + return "bg-gray-100 text-gray-700"; + } +}; + +const formatTimestamp = (timestamp?: string) => { + if (!timestamp) return null; + try { + const date = new Date(timestamp); + return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + } catch { + return timestamp; + } +}; + +const truncateId = (id?: string, length = 8) => { + if (!id) return null; + return id.length > length ? `${id.substring(0, length)}…` : id; +}; + +const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); +}; + +const A2AMetrics: React.FC = ({ a2aMetadata, timeToFirstToken, totalLatency }) => { + const [showDetails, setShowDetails] = useState(false); + + if (!a2aMetadata && !timeToFirstToken && !totalLatency) return null; + + const { taskId, contextId, status, metadata } = a2aMetadata || {}; + const formattedTime = formatTimestamp(status?.timestamp); + + return ( +
      + {/* A2A Metadata Header */} +
      + + A2A Metadata +
      + + {/* Main metrics row */} +
      + {/* Status badge */} + {status?.state && ( + + {getStatusIcon(status.state)} + {status.state} + + )} + + {/* Timestamp */} + {formattedTime && ( + + + + {formattedTime} + + + )} + + {/* Latency */} + {totalLatency !== undefined && ( + + + + {(totalLatency / 1000).toFixed(2)}s + + + )} + + {/* Time to first token */} + {timeToFirstToken !== undefined && ( + + + TTFT: {(timeToFirstToken / 1000).toFixed(2)}s + + + )} +
      + + {/* IDs row */} +
      + {/* Task ID */} + {taskId && ( + + copyToClipboard(taskId)} + > + + Task: {truncateId(taskId)} + + + + )} + + {/* Context/Session ID */} + {contextId && ( + + copyToClipboard(contextId)} + > + + Session: {truncateId(contextId)} + + + + )} + + {/* Details toggle */} + {(metadata || status?.message) && ( + + )} +
      + + {/* Expandable details panel */} + {showDetails && ( +
      + {/* Status message */} + {status?.message && ( +
      + Status Message: + {status.message} +
      + )} + + {/* Full IDs */} + {taskId && ( +
      + Task ID: + {taskId} + copyToClipboard(taskId)} + /> +
      + )} + + {contextId && ( +
      + Session ID: + {contextId} + copyToClipboard(contextId)} + /> +
      + )} + + {/* Metadata fields */} + {metadata && Object.keys(metadata).length > 0 && ( +
      + Custom Metadata: +
      +                {JSON.stringify(metadata, null, 2)}
      +              
      +
      + )} +
      + )} +
      + ); +}; + +export default A2AMetrics; diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx index c2924c048c3..18b5bb82dd4 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx @@ -51,6 +51,10 @@ import { fetchAvailableModels, ModelGroup } from "../llm_calls/fetch_models"; import { makeOpenAIImageEditsRequest } from "../llm_calls/image_edits"; import { makeOpenAIImageGenerationRequest } from "../llm_calls/image_generation"; import { makeOpenAIResponsesRequest } from "../llm_calls/responses_api"; +import { Agent, fetchAvailableAgents } from "../llm_calls/fetch_agents"; +import { makeA2AStreamMessageRequest } from "../llm_calls/a2a_send_message"; +import A2AMetrics from "./A2AMetrics"; +import { A2ATaskMetadata } from "./types"; import MCPEventsDisplay, { MCPEvent } from "./MCPEventsDisplay"; import { EndpointType, getEndpointType } from "./mode_endpoint_mapping"; import ReasoningContent from "./ReasoningContent"; @@ -124,6 +128,8 @@ const ChatUI: React.FC = ({ const [selectedModel, setSelectedModel] = useState(undefined); const [showCustomModelInput, setShowCustomModelInput] = useState(false); const [modelInfo, setModelInfo] = useState([]); + const [agentInfo, setAgentInfo] = useState([]); + const [selectedAgent, setSelectedAgent] = useState(undefined); const customModelTimeout = useRef(null); const [endpointType, setEndpointType] = useState( () => sessionStorage.getItem("endpointType") || EndpointType.CHAT, @@ -340,6 +346,29 @@ const ChatUI: React.FC = ({ loadMCPTools(); }, [accessToken, userID, userRole, apiKeySource, apiKey, token]); + // Fetch agents when A2A endpoint is selected + useEffect(() => { + const userApiKey = apiKeySource === "session" ? accessToken : apiKey; + if (!userApiKey || endpointType !== EndpointType.A2A_AGENTS) { + return; + } + + const loadAgents = async () => { + try { + const agents = await fetchAvailableAgents(userApiKey); + setAgentInfo(agents); + // Clear selection if current agent not in list + if (selectedAgent && !agents.some((a) => a.agent_name === selectedAgent)) { + setSelectedAgent(undefined); + } + } catch (error) { + console.error("Error fetching agents:", error); + } + }; + + loadAgents(); + }, [accessToken, apiKeySource, apiKey, endpointType]); + useEffect(() => { // Scroll to the bottom of the chat whenever chatHistory updates if (chatEndRef.current) { @@ -469,6 +498,23 @@ const ChatUI: React.FC = ({ }); }; + const updateA2AMetadata = (a2aMetadata: A2ATaskMetadata) => { + console.log("Received A2A metadata:", a2aMetadata); + setChatHistory((prevHistory) => { + const lastMessage = prevHistory[prevHistory.length - 1]; + + if (lastMessage && lastMessage.role === "assistant") { + const updatedMessage = { + ...lastMessage, + a2aMetadata, + }; + return [...prevHistory.slice(0, prevHistory.length - 1), updatedMessage]; + } + + return prevHistory; + }); + }; + const updateTotalLatency = (totalLatency: number) => { setChatHistory((prevHistory) => { const lastMessage = prevHistory[prevHistory.length - 1]; @@ -684,6 +730,12 @@ const ChatUI: React.FC = ({ return; } + // For A2A agents, require agent selection + if (endpointType === EndpointType.A2A_AGENTS && !selectedAgent) { + NotificationsManager.fromBackend("Please select an agent to send a message"); + return; + } + if (!token || !userRole || !userID) { return; } @@ -908,6 +960,20 @@ const ChatUI: React.FC = ({ } } } + + // Handle A2A agent calls (separate from model-based calls) - use streaming + if (endpointType === EndpointType.A2A_AGENTS && selectedAgent) { + await makeA2AStreamMessageRequest( + selectedAgent, + inputMessage, + (chunk, model) => updateTextUI("assistant", chunk, model), + effectiveApiKey, + signal, + updateTimingData, + updateTotalLatency, + updateA2AMetadata, + ); + } } catch (error) { if (signal.aborted) { console.log("Request was cancelled"); @@ -1038,11 +1104,13 @@ const ChatUI: React.FC = ({ endpointType={endpointType} onEndpointChange={(value) => { setEndpointType(value); - // Clear model selection when switching endpoint type + // Clear model/agent selection when switching endpoint type setSelectedModel(undefined); + setSelectedAgent(undefined); setShowCustomModelInput(false); try { sessionStorage.removeItem("selectedModel"); + sessionStorage.removeItem("selectedAgent"); } catch {} }} className="mb-4" @@ -1077,103 +1145,145 @@ const ChatUI: React.FC = ({ />
  • -
    - - - Select Model - - {isChatModel() ? ( - + + + Select Model + + {isChatModel() ? ( + + } + title="Model Settings" + trigger="click" + placement="right" + > +
    + {showCustomModelInput && ( + { + // Using setTimeout to create a simple debounce effect + if (customModelTimeout.current) { + clearTimeout(customModelTimeout.current); + } + + customModelTimeout.current = setTimeout(() => { + setSelectedModel(value); + }, 500); // 500ms delay after typing stops + }} + /> + )} +
    @@ -1685,13 +1805,15 @@ const ChatUI: React.FC = ({ endpointType === EndpointType.RESPONSES || endpointType === EndpointType.ANTHROPIC_MESSAGES ? "Type your message... (Shift+Enter for new line)" - : endpointType === EndpointType.IMAGE_EDITS - ? "Describe how you want to edit the image..." - : endpointType === EndpointType.SPEECH - ? "Enter text to convert to speech..." - : endpointType === EndpointType.TRANSCRIPTION - ? "Optional: Add context or prompt for transcription..." - : "Describe the image you want to generate..." + : endpointType === EndpointType.A2A_AGENTS + ? "Send a message to the A2A agent..." + : endpointType === EndpointType.IMAGE_EDITS + ? "Describe how you want to edit the image..." + : endpointType === EndpointType.SPEECH + ? "Enter text to convert to speech..." + : endpointType === EndpointType.TRANSCRIPTION + ? "Optional: Add context or prompt for transcription..." + : "Describe the image you want to generate..." } disabled={isLoading} className="flex-1" diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/chatConstants.ts b/ui/litellm-dashboard/src/components/playground/chat_ui/chatConstants.ts index a64c0264f4c..e9b44836455 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/chatConstants.ts +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/chatConstants.ts @@ -42,4 +42,5 @@ export const ENDPOINT_OPTIONS = [ { value: EndpointType.EMBEDDINGS, label: "/v1/embeddings" }, { value: EndpointType.SPEECH, label: "/v1/audio/speech" }, { value: EndpointType.TRANSCRIPTION, label: "/v1/audio/transcriptions" }, + { value: EndpointType.A2A_AGENTS, label: "/v1/a2a/message/send" }, ]; diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/mode_endpoint_mapping.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/mode_endpoint_mapping.tsx index 479d3cb8940..8ad0ae04a6d 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/mode_endpoint_mapping.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/mode_endpoint_mapping.tsx @@ -25,6 +25,7 @@ export enum EndpointType { EMBEDDINGS = "embeddings", SPEECH = "speech", TRANSCRIPTION = "transcription", + A2A_AGENTS = "a2a_agents", // add additional endpoint types if required } diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/types.ts b/ui/litellm-dashboard/src/components/playground/chat_ui/types.ts index 15c90221fc0..4e43e6ec77d 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/types.ts +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/types.ts @@ -70,12 +70,24 @@ export interface VectorStoreSearchResponse { data: VectorStoreSearchResult[]; } +export interface A2ATaskMetadata { + taskId?: string; + contextId?: string; + status?: { + state?: string; + timestamp?: string; + message?: string; + }; + metadata?: Record; +} + export interface MessageType { role: string; content: string | MultimodalContent[]; model?: string; isImage?: boolean; isAudio?: boolean; + isEmbeddings?: boolean; reasoningContent?: string; timeToFirstToken?: number; totalLatency?: number; @@ -93,6 +105,7 @@ export interface MessageType { detail: string; }; searchResults?: VectorStoreSearchResponse[]; + a2aMetadata?: A2ATaskMetadata; } export interface MultimodalContent { diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/a2a_send_message.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/a2a_send_message.tsx new file mode 100644 index 00000000000..7d799a837a7 --- /dev/null +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/a2a_send_message.tsx @@ -0,0 +1,413 @@ +// a2a_send_message.tsx +// A2A Protocol (JSON-RPC 2.0) implementation for sending messages to agents + +import { v4 as uuidv4 } from "uuid"; +import { getProxyBaseUrl } from "../../networking"; +import { A2ATaskMetadata } from "../chat_ui/types"; + +interface A2AMessagePart { + kind: "text"; + text: string; +} + +interface A2AMessage { + kind: "message"; + messageId: string; + role: "user" | "agent"; + parts: A2AMessagePart[]; +} + +interface A2AJsonRpcRequest { + jsonrpc: "2.0"; + id: string; + method: string; + params: { + message: A2AMessage; + }; +} + +interface A2AJsonRpcResponse { + jsonrpc: "2.0"; + id: string; + result?: { + kind?: string; + parts?: A2AMessagePart[]; + id?: string; + contextId?: string; + status?: { + state?: string; + timestamp?: string; + message?: { + parts?: A2AMessagePart[]; + }; + }; + metadata?: Record; + artifacts?: Array<{ + artifactId?: string; + name?: string; + parts?: A2AMessagePart[]; + }>; + [key: string]: any; + }; + error?: { + code: number; + message: string; + }; +} + +/** + * Extracts A2A task metadata from the response result. + */ +const extractA2AMetadata = (result: A2AJsonRpcResponse["result"]): A2ATaskMetadata | undefined => { + if (!result) return undefined; + + const metadata: A2ATaskMetadata = {}; + + // Extract task ID + if (result.id) { + metadata.taskId = result.id; + } + + // Extract context/session ID + if (result.contextId) { + metadata.contextId = result.contextId; + } + + // Extract status + if (result.status) { + metadata.status = { + state: result.status.state, + timestamp: result.status.timestamp, + }; + + // Extract status message text if present + if (result.status.message?.parts) { + const statusText = result.status.message.parts + .filter((p: any) => p.kind === "text" && p.text) + .map((p: any) => p.text) + .join(" "); + if (statusText) { + metadata.status.message = statusText; + } + } + } + + // Extract custom metadata + if (result.metadata && typeof result.metadata === "object") { + metadata.metadata = result.metadata; + } + + return Object.keys(metadata).length > 0 ? metadata : undefined; +}; + +/** + * Sends a message to an A2A agent using the JSON-RPC 2.0 protocol. + * Uses the non-streaming message/send method. + */ +export const makeA2ASendMessageRequest = async ( + agentId: string, + message: string, + onTextUpdate: (chunk: string, model?: string) => void, + accessToken: string, + signal?: AbortSignal, + onTimingData?: (timeToFirstToken: number) => void, + onTotalLatency?: (totalLatency: number) => void, + onA2AMetadata?: (metadata: A2ATaskMetadata) => void, +): Promise => { + const proxyBaseUrl = getProxyBaseUrl(); + const url = proxyBaseUrl + ? `${proxyBaseUrl}/a2a/${agentId}/message/send` + : `/a2a/${agentId}/message/send`; + + const requestId = uuidv4(); + const messageId = uuidv4().replace(/-/g, ""); + + const jsonRpcRequest: A2AJsonRpcRequest = { + jsonrpc: "2.0", + id: requestId, + method: "message/send", + params: { + message: { + kind: "message", + messageId: messageId, + role: "user", + parts: [{ kind: "text", text: message }], + }, + }, + }; + + const startTime = performance.now(); + + try { + const response = await fetch(url, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonRpcRequest), + signal, + }); + + const timeToFirstToken = performance.now() - startTime; + if (onTimingData) { + onTimingData(timeToFirstToken); + } + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error?.message || errorData.detail || `HTTP ${response.status}`); + } + + const jsonRpcResponse: A2AJsonRpcResponse = await response.json(); + + const totalLatency = performance.now() - startTime; + if (onTotalLatency) { + onTotalLatency(totalLatency); + } + + if (jsonRpcResponse.error) { + throw new Error(jsonRpcResponse.error.message); + } + + // Extract text and metadata from response + const result = jsonRpcResponse.result; + if (result) { + let responseText = ""; + + // Extract and send A2A metadata + const a2aMetadata = extractA2AMetadata(result); + if (a2aMetadata && onA2AMetadata) { + onA2AMetadata(a2aMetadata); + } + + // A2A Task response format with artifacts array + // Extract text from artifacts[*].parts[*] where kind === "text" + if (result.artifacts && Array.isArray(result.artifacts)) { + for (const artifact of result.artifacts) { + if (artifact.parts && Array.isArray(artifact.parts)) { + for (const part of artifact.parts) { + if (part.kind === "text" && part.text) { + responseText += part.text; + } + } + } + } + } + // Fallback: direct parts array (simpler response format) + else if (result.parts && Array.isArray(result.parts)) { + for (const part of result.parts) { + if (part.kind === "text" && part.text) { + responseText += part.text; + } + } + } + // Fallback: status.message.parts format + else if (result.status?.message?.parts) { + for (const part of result.status.message.parts) { + if (part.kind === "text" && part.text) { + responseText += part.text; + } + } + } + + if (responseText) { + onTextUpdate(responseText, `a2a_agent/${agentId}`); + } else { + // Fallback: show raw result if we couldn't parse it + console.warn("Could not extract text from A2A response, showing raw JSON:", result); + onTextUpdate(JSON.stringify(result, null, 2), `a2a_agent/${agentId}`); + } + } + } catch (error) { + if (signal?.aborted) { + console.log("A2A request was cancelled"); + return; + } + console.error("A2A send message error:", error); + throw error; + } +}; + +/** + * Sends a streaming message to an A2A agent using the JSON-RPC 2.0 protocol. + * Uses the message/stream method with NDJSON responses. + */ +export const makeA2AStreamMessageRequest = async ( + agentId: string, + message: string, + onTextUpdate: (chunk: string, model?: string) => void, + accessToken: string, + signal?: AbortSignal, + onTimingData?: (timeToFirstToken: number) => void, + onTotalLatency?: (totalLatency: number) => void, + onA2AMetadata?: (metadata: A2ATaskMetadata) => void, +): Promise => { + const proxyBaseUrl = getProxyBaseUrl(); + const url = proxyBaseUrl + ? `${proxyBaseUrl}/a2a/${agentId}` + : `/a2a/${agentId}`; + + const requestId = uuidv4(); + const messageId = uuidv4().replace(/-/g, ""); + + const jsonRpcRequest: A2AJsonRpcRequest = { + jsonrpc: "2.0", + id: requestId, + method: "message/stream", + params: { + message: { + kind: "message", + messageId: messageId, + role: "user", + parts: [{ kind: "text", text: message }], + }, + }, + }; + + const startTime = performance.now(); + let firstChunkReceived = false; + let latestMetadata: A2ATaskMetadata | undefined; + let accumulatedText = ""; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonRpcRequest), + signal, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error?.message || errorData.detail || `HTTP ${response.status}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error("No response body"); + } + + const decoder = new TextDecoder(); + let buffer = ""; + let done = false; + + while (!done) { + const readResult = await reader.read(); + done = readResult.done; + const value = readResult.value; + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + + for (const line of lines) { + if (!line.trim()) continue; + + try { + const chunk = JSON.parse(line); + + if (!firstChunkReceived) { + firstChunkReceived = true; + const timeToFirstToken = performance.now() - startTime; + if (onTimingData) { + onTimingData(timeToFirstToken); + } + } + + // Handle streaming chunks - extract text from various A2A formats + const result = chunk.result; + if (result) { + // Extract metadata from each chunk (keep latest) + const chunkMetadata = extractA2AMetadata(result); + if (chunkMetadata) { + latestMetadata = { ...latestMetadata, ...chunkMetadata }; + } + + const chunkKind = result.kind; + + // Handle artifact-update chunks (streaming response content) + // Note: streaming uses "artifact" (singular), not "artifacts" (plural) + if (chunkKind === "artifact-update" && result.artifact) { + const artifact = result.artifact; + if (artifact.parts && Array.isArray(artifact.parts)) { + for (const part of artifact.parts) { + if (part.kind === "text" && part.text) { + // Accumulate actual response content + accumulatedText += part.text; + onTextUpdate(accumulatedText, `a2a_agent/${agentId}`); + } + } + } + } + // Handle non-streaming Task response format with artifacts array (plural) + else if (result.artifacts && Array.isArray(result.artifacts)) { + for (const artifact of result.artifacts) { + if (artifact.parts && Array.isArray(artifact.parts)) { + for (const part of artifact.parts) { + if (part.kind === "text" && part.text) { + accumulatedText += part.text; + onTextUpdate(accumulatedText, `a2a_agent/${agentId}`); + } + } + } + } + } + // Handle status-update chunks (progress messages like "Processing request...") + // Only show these temporarily if we haven't received actual content yet + else if (chunkKind === "status-update" && result.status?.message?.parts) { + // Skip status messages once we have real content + if (!accumulatedText) { + for (const part of result.status.message.parts) { + if (part.kind === "text" && part.text) { + // Show as temporary status - will be replaced when real content arrives + onTextUpdate(part.text, `a2a_agent/${agentId}`); + } + } + } + } + // Direct parts array (fallback) + else if (result.parts && Array.isArray(result.parts)) { + for (const part of result.parts) { + if (part.kind === "text" && part.text) { + accumulatedText += part.text; + onTextUpdate(accumulatedText, `a2a_agent/${agentId}`); + } + } + } + } + + if (chunk.error) { + throw new Error(chunk.error.message); + } + } catch (parseError) { + // Only warn if it's not a JSON parse error on an empty/partial line + if (line.trim().length > 0) { + console.warn("Failed to parse A2A streaming chunk:", line, parseError); + } + } + } + } + + const totalLatency = performance.now() - startTime; + if (onTotalLatency) { + onTotalLatency(totalLatency); + } + + // Send final metadata after streaming completes + if (latestMetadata && onA2AMetadata) { + onA2AMetadata(latestMetadata); + } + } catch (error) { + if (signal?.aborted) { + console.log("A2A streaming request was cancelled"); + return; + } + console.error("A2A stream message error:", error); + throw error; + } +}; diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/fetch_agents.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/fetch_agents.tsx new file mode 100644 index 00000000000..054e2216b63 --- /dev/null +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/fetch_agents.tsx @@ -0,0 +1,52 @@ +// fetch_agents.tsx + +import { getProxyBaseUrl } from "../../networking"; + +export interface Agent { + agent_id: string; + agent_name: string; + description?: string; + agent_card_params?: { + name?: string; + description?: string; + url?: string; + }; +} + +/** + * Fetches available A2A agents from /v1/agents endpoint. + */ +export const fetchAvailableAgents = async (accessToken: string): Promise => { + try { + const proxyBaseUrl = getProxyBaseUrl(); + const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents` : `/v1/agents`; + + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.detail || "Failed to fetch agents"); + } + + const agents: Agent[] = await response.json(); + console.log("Fetched agents:", agents); + + // Sort agents alphabetically by name + agents.sort((a, b) => { + const nameA = a.agent_name || a.agent_id; + const nameB = b.agent_name || b.agent_id; + return nameA.localeCompare(nameB); + }); + + return agents; + } catch (error) { + console.error("Error fetching agents:", error); + throw error; + } +}; From b96657a0830458f4ad3788dae4a4fe8f148e832f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 18:57:57 -0800 Subject: [PATCH 317/349] [Feat] UI - Agent Gateway - allow testing agents on UI (#17455) * init litellm A2a client * simpler a2a client interface * test a2a * move a2a invoking tests * test fix * ensure a2a send message is tracked n logs * rename tags * add streaming handlng * add a2a invocation * add a2a invocation i cost calc * test_a2a_logging_payload * update invoke_agent_a2a * test_invoke_agent_a2a_adds_litellm_data * add A2a agent * fix endpoints on A2a * UI allow testing a2a endpoints From e7f066ea7dfffd55a2b9aa69d1f1ec86c0b9526b Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 3 Dec 2025 19:32:30 -0800 Subject: [PATCH 318/349] test_hosted_vllm_full_workflow --- tests/batches_tests/test_hosted_vllm_batches_and_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/batches_tests/test_hosted_vllm_batches_and_files.py b/tests/batches_tests/test_hosted_vllm_batches_and_files.py index 432dc81bad2..aa4d847a45d 100644 --- a/tests/batches_tests/test_hosted_vllm_batches_and_files.py +++ b/tests/batches_tests/test_hosted_vllm_batches_and_files.py @@ -26,6 +26,7 @@ SERVER_URL = "https://exampleopenaiendpoint-production-0ee2.up.railway.app/v1" @pytest.mark.asyncio() +@pytest.mark.skip(reason="Local only test") async def test_hosted_vllm_full_workflow(): """ Test the complete workflow: create file -> create batch -> retrieve batch -> retrieve file. From 92cca5d515d984f497ed74d5005ee0ace50fdc37 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 3 Dec 2025 19:32:40 -0800 Subject: [PATCH 319/349] test_cyberark_write_and_read_secret --- tests/litellm_utils_tests/test_cyberark.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/litellm_utils_tests/test_cyberark.py b/tests/litellm_utils_tests/test_cyberark.py index 24c84687705..b94e5949534 100644 --- a/tests/litellm_utils_tests/test_cyberark.py +++ b/tests/litellm_utils_tests/test_cyberark.py @@ -53,13 +53,14 @@ async def test_cyberark_write_and_read_secret(): secret_value = f"test-value-{uuid.uuid4()}" # Mock sync httpx client (for auth, ensure variable exists, sync read) + # The _get_httpx_client returns an HTTPHandler with a .client property mock_sync_client = MagicMock() - # Auth response - mock_sync_client.post.return_value = create_mock_response( + # Auth response - note: the actual client is accessed via .client property + mock_sync_client.client.post.return_value = create_mock_response( status_code=200, text="mock-token" ) # Sync read response - mock_sync_client.get.return_value = create_mock_response( + mock_sync_client.client.get.return_value = create_mock_response( status_code=200, text=secret_value ) @@ -123,9 +124,10 @@ async def test_cyberark_rotate_secret(): current_value = {"value": initial_key_value} # Mock sync httpx client (for auth, ensure variable exists, sync reads) + # The _get_httpx_client returns an HTTPHandler with a .client property mock_sync_client = MagicMock() - # Auth response - mock_sync_client.post.return_value = create_mock_response( + # Auth response - note: the actual client is accessed via .client property + mock_sync_client.client.post.return_value = create_mock_response( status_code=200, text="mock-token" ) @@ -133,7 +135,7 @@ async def test_cyberark_rotate_secret(): def get_mock_sync_read_response(*args, **kwargs): return create_mock_response(status_code=200, text=current_value["value"]) - mock_sync_client.get.side_effect = get_mock_sync_read_response + mock_sync_client.client.get.side_effect = get_mock_sync_read_response # Mock async httpx client (for async writes and reads) mock_async_client = AsyncMock() @@ -228,9 +230,10 @@ async def test_cyberark_rotate_secret_with_new_alias(): secrets_store = {} # Mock sync httpx client (for auth, ensure variable exists, sync reads) + # The _get_httpx_client returns an HTTPHandler with a .client property mock_sync_client = MagicMock() - # Auth response - mock_sync_client.post.return_value = create_mock_response( + # Auth response - note: the actual client is accessed via .client property + mock_sync_client.client.post.return_value = create_mock_response( status_code=200, text="mock-token" ) @@ -243,7 +246,7 @@ async def test_cyberark_rotate_secret_with_new_alias(): return create_mock_response(status_code=200, text=secret_val) return create_mock_response(status_code=404, text="Not found") - mock_sync_client.get.side_effect = get_mock_sync_read + mock_sync_client.client.get.side_effect = get_mock_sync_read # Mock async httpx client (for async writes and reads) mock_async_client = AsyncMock() From 8a824b7c17c6e30d751a0b6fe31b2637391e8b19 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 3 Dec 2025 19:35:55 -0800 Subject: [PATCH 320/349] fix mypy linting --- litellm/{a2a => a2a_protocol}/__init__.py | 8 ++++---- litellm/{a2a => a2a_protocol}/client.py | 10 +++++----- litellm/{a2a => a2a_protocol}/cost_calculator.py | 0 litellm/{a2a => a2a_protocol}/main.py | 4 ++-- litellm/cost_calculator.py | 2 +- litellm/proxy/agent_endpoints/a2a_endpoints.py | 2 +- tests/agent_tests/test_a2a.py | 4 ++-- .../proxy/agent_endpoints/test_a2a_endpoints.py | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) rename litellm/{a2a => a2a_protocol}/__init__.py (88%) rename litellm/{a2a => a2a_protocol}/client.py (90%) rename litellm/{a2a => a2a_protocol}/cost_calculator.py (100%) rename litellm/{a2a => a2a_protocol}/main.py (98%) diff --git a/litellm/a2a/__init__.py b/litellm/a2a_protocol/__init__.py similarity index 88% rename from litellm/a2a/__init__.py rename to litellm/a2a_protocol/__init__.py index a2e9770cd20..d8d349bb98a 100644 --- a/litellm/a2a/__init__.py +++ b/litellm/a2a_protocol/__init__.py @@ -8,7 +8,7 @@ This module provides a thin wrapper around the official `a2a` SDK that: Example usage (standalone functions with @client decorator): ```python - from litellm.a2a import asend_message + from litellm.a2a_protocol import asend_message from a2a.types import SendMessageRequest, MessageSendParams from uuid import uuid4 @@ -31,15 +31,15 @@ Example usage (standalone functions with @client decorator): Example usage (class-based): ```python - from litellm.a2a import A2AClient + from litellm.a2a_protocol import A2AClient client = A2AClient(base_url="http://localhost:10001") response = await client.send_message(request) ``` """ -from litellm.a2a.client import A2AClient -from litellm.a2a.main import ( +from litellm.a2a_protocol.client import A2AClient +from litellm.a2a_protocol.main import ( aget_agent_card, asend_message, asend_message_streaming, diff --git a/litellm/a2a/client.py b/litellm/a2a_protocol/client.py similarity index 90% rename from litellm/a2a/client.py rename to litellm/a2a_protocol/client.py index 0c3b8fff2ba..31f7c3b6a90 100644 --- a/litellm/a2a/client.py +++ b/litellm/a2a_protocol/client.py @@ -26,7 +26,7 @@ class A2AClient: Example: ```python - from litellm.a2a import A2AClient + from litellm.a2a_protocol import A2AClient from a2a.types import SendMessageRequest, MessageSendParams from uuid import uuid4 @@ -68,7 +68,7 @@ class A2AClient: async def _get_client(self) -> "A2AClientType": """Get or create the underlying A2A client.""" if self._a2a_client is None: - from litellm.a2a.main import create_a2a_client + from litellm.a2a_protocol.main import create_a2a_client self._a2a_client = await create_a2a_client( base_url=self.base_url, @@ -79,7 +79,7 @@ class A2AClient: async def get_agent_card(self) -> "AgentCard": """Fetch the agent card from the server.""" - from litellm.a2a.main import aget_agent_card + from litellm.a2a_protocol.main import aget_agent_card return await aget_agent_card( base_url=self.base_url, @@ -91,7 +91,7 @@ class A2AClient: self, request: "SendMessageRequest" ) -> LiteLLMSendMessageResponse: """Send a message to the A2A agent.""" - from litellm.a2a.main import asend_message + from litellm.a2a_protocol.main import asend_message a2a_client = await self._get_client() return await asend_message(a2a_client=a2a_client, request=request) @@ -100,7 +100,7 @@ class A2AClient: self, request: "SendStreamingMessageRequest" ) -> AsyncIterator["SendStreamingMessageResponse"]: """Send a streaming message to the A2A agent.""" - from litellm.a2a.main import asend_message_streaming + from litellm.a2a_protocol.main import asend_message_streaming a2a_client = await self._get_client() async for chunk in asend_message_streaming(a2a_client=a2a_client, request=request): diff --git a/litellm/a2a/cost_calculator.py b/litellm/a2a_protocol/cost_calculator.py similarity index 100% rename from litellm/a2a/cost_calculator.py rename to litellm/a2a_protocol/cost_calculator.py diff --git a/litellm/a2a/main.py b/litellm/a2a_protocol/main.py similarity index 98% rename from litellm/a2a/main.py rename to litellm/a2a_protocol/main.py index 7827581ceac..5cb238904f0 100644 --- a/litellm/a2a/main.py +++ b/litellm/a2a_protocol/main.py @@ -91,7 +91,7 @@ async def asend_message( Example: ```python - from litellm.a2a import asend_message, create_a2a_client + from litellm.a2a_protocol import asend_message, create_a2a_client from a2a.types import SendMessageRequest, MessageSendParams from uuid import uuid4 @@ -205,7 +205,7 @@ async def create_a2a_client( Example: ```python - from litellm.a2a import create_a2a_client, asend_message + from litellm.a2a_protocol import create_a2a_client, asend_message # Create client once client = await create_a2a_client(base_url="http://localhost:10001") diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 943eb069565..ea7caf93ed0 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -939,7 +939,7 @@ def completion_cost( # noqa: PLR0915 CallTypes.asend_message.value, CallTypes.send_message.value, ): - from litellm.a2a.cost_calculator import A2ACostCalculator + from litellm.a2a_protocol.cost_calculator import A2ACostCalculator return A2ACostCalculator.calculate_a2a_cost( litellm_logging_obj=litellm_logging_obj diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py index 69c1c5938b6..32af1232f6b 100644 --- a/litellm/proxy/agent_endpoints/a2a_endpoints.py +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -138,7 +138,7 @@ async def invoke_agent_a2a( - message/send: Send a message and get a response - message/stream: Send a message and stream the response """ - from litellm.a2a import asend_message, create_a2a_client + from litellm.a2a_protocol import asend_message, create_a2a_client from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request from litellm.proxy.proxy_server import ( general_settings, diff --git a/tests/agent_tests/test_a2a.py b/tests/agent_tests/test_a2a.py index 675b3b98e4d..eeab2680564 100644 --- a/tests/agent_tests/test_a2a.py +++ b/tests/agent_tests/test_a2a.py @@ -32,7 +32,7 @@ async def test_asend_message_with_client_decorator(): This tests the LiteLLM logging integration. """ litellm._turn_on_debug() - from litellm.a2a import asend_message, create_a2a_client + from litellm.a2a_protocol import asend_message, create_a2a_client # Create the A2A client first a2a_client = await create_a2a_client(base_url="http://localhost:10001") @@ -97,7 +97,7 @@ async def test_a2a_logging_payload(): test_logger = TestA2ALogger() litellm.callbacks = [test_logger] - from litellm.a2a import asend_message, create_a2a_client + from litellm.a2a_protocol import asend_message, create_a2a_client # Create the A2A client first a2a_client = await create_a2a_client(base_url="http://localhost:10001") diff --git a/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py index c9b8917b20c..c7257073b33 100644 --- a/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py +++ b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py @@ -75,10 +75,10 @@ async def test_invoke_agent_a2a_adds_litellm_data(): "litellm.proxy.litellm_pre_call_utils.add_litellm_data_to_request", side_effect=mock_add_litellm_data, ) as mock_add_data, patch( - "litellm.a2a.create_a2a_client", + "litellm.a2a_protocol.create_a2a_client", new_callable=AsyncMock, ), patch( - "litellm.a2a.asend_message", + "litellm.a2a_protocol.asend_message", new_callable=AsyncMock, return_value=mock_response, ), patch( From 607d517c013a087f8b38d0ea01bf9b1435db056f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 3 Dec 2025 19:44:24 -0800 Subject: [PATCH 321/349] Revert "Bump mcp from 1.10.1 to 1.23.0 in /.circleci (#17363)" (#17457) This reverts commit 462d423d86ce424b887f599d2b6427099936831e. --- .circleci/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt index 6f1043f59a5..2294c84813c 100644 --- a/.circleci/requirements.txt +++ b/.circleci/requirements.txt @@ -13,7 +13,7 @@ google-cloud-aiplatform==1.43.0 google-cloud-iam==2.19.1 fastapi-sso==0.16.0 uvloop==0.21.0 -mcp==1.23.0 # for MCP server +mcp==1.10.1 # for MCP server semantic_router==0.1.10 # for auto-routing with litellm fastuuid==0.12.0 responses==0.25.7 # for proxy client tests \ No newline at end of file From a711b63b062ab7b907ccd7fd976a4c6eccd5f4ca Mon Sep 17 00:00:00 2001 From: Xander Song Date: Wed, 3 Dec 2025 22:50:20 -0500 Subject: [PATCH 322/349] fix: clean arize-phoenix traces (#16611) * fix * clean * clean * clean * clean * clean * lint --- litellm/integrations/arize/arize_phoenix.py | 57 +++++++++++++++++-- litellm/integrations/opentelemetry.py | 9 +-- litellm/litellm_core_utils/litellm_logging.py | 8 +-- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/litellm/integrations/arize/arize_phoenix.py b/litellm/integrations/arize/arize_phoenix.py index ab70dd9d0e2..4a6e0cec8ca 100644 --- a/litellm/integrations/arize/arize_phoenix.py +++ b/litellm/integrations/arize/arize_phoenix.py @@ -1,18 +1,20 @@ import os -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any, Optional, Union +from datetime import datetime from litellm._logging import verbose_logger from litellm.integrations.arize import _utils from litellm.integrations.arize._utils import ArizeOTELAttributes from litellm.types.integrations.arize_phoenix import ArizePhoenixConfig +from litellm.types.services import ServiceLoggerPayload +from litellm.integrations.opentelemetry import OpenTelemetry if TYPE_CHECKING: from opentelemetry.trace import Span as _Span + from litellm.integrations.opentelemetry import OpenTelemetryConfig as _OpenTelemetryConfig from litellm.types.integrations.arize import Protocol as _Protocol - from .opentelemetry import OpenTelemetryConfig as _OpenTelemetryConfig - Protocol = _Protocol OpenTelemetryConfig = _OpenTelemetryConfig Span = Union[_Span, Any] @@ -25,7 +27,11 @@ else: ARIZE_HOSTED_PHOENIX_ENDPOINT = "https://otlp.arize.com/v1/traces" -class ArizePhoenixLogger: +class ArizePhoenixLogger(OpenTelemetry): + def set_attributes(self, span: Span, kwargs, response_obj: Optional[Any]): + ArizePhoenixLogger.set_arize_phoenix_attributes(span, kwargs, response_obj) + return + @staticmethod def set_arize_phoenix_attributes(span: Span, kwargs, response_obj): _utils.set_attributes(span, kwargs, response_obj, ArizeOTELAttributes) @@ -97,3 +103,46 @@ class ArizePhoenixLogger: endpoint=endpoint, project_name=project_name, ) + + async def async_service_success_hook( + self, + payload: ServiceLoggerPayload, + parent_otel_span: Optional[Span] = None, + start_time: Optional[Union[datetime, float]] = None, + end_time: Optional[Union[datetime, float]] = None, + event_metadata: Optional[dict] = None, + ): + pass # suppress additional spans + + async def async_service_failure_hook( + self, + payload: ServiceLoggerPayload, + error: Optional[str] = "", + parent_otel_span: Optional[Span] = None, + start_time: Optional[Union[datetime, float]] = None, + end_time: Optional[Union[float, datetime]] = None, + event_metadata: Optional[dict] = None, + ): + pass # suppress additional spans + + def create_litellm_proxy_request_started_span( + self, + start_time: datetime, + headers: dict, + ): + pass # suppress additional spans + + async def async_health_check(self): + + config = self.get_arize_phoenix_config() + + if not config.otlp_auth_headers: + return { + "status": "unhealthy", + "error_message": "PHOENIX_API_KEY environment variable not set", + } + + return { + "status": "healthy", + "message": "Arize-Phoenix credentials are configured properly", + } \ No newline at end of file diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index 90bf19b21fe..23bffc6c7e5 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -1065,14 +1065,7 @@ class OpenTelemetry(CustomLogger): self, span: Span, kwargs, response_obj: Optional[Any] ): try: - if self.callback_name == "arize_phoenix": - from litellm.integrations.arize.arize_phoenix import ArizePhoenixLogger - - ArizePhoenixLogger.set_arize_phoenix_attributes( - span, kwargs, response_obj - ) - return - elif self.callback_name == "langtrace": + if self.callback_name == "langtrace": from litellm.integrations.langtrace import LangtraceAttributes LangtraceAttributes().set_langtrace_attributes( diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 4fd2c988797..6440e5f6dee 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -3619,15 +3619,15 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 for callback in _in_memory_loggers: if ( - isinstance(callback, OpenTelemetry) + isinstance(callback, ArizePhoenixLogger) and callback.callback_name == "arize_phoenix" ): return callback # type: ignore - _otel_logger = OpenTelemetry( + _arize_phoenix_otel_logger = ArizePhoenixLogger( config=otel_config, callback_name="arize_phoenix" ) - _in_memory_loggers.append(_otel_logger) - return _otel_logger # type: ignore + _in_memory_loggers.append(_arize_phoenix_otel_logger) + return _arize_phoenix_otel_logger # type: ignore elif logging_integration == "otel": from litellm.integrations.opentelemetry import OpenTelemetry From 3de84b3f8b2e6d001827da05bae088114b24eae1 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 20:34:41 -0800 Subject: [PATCH 323/349] e2e tests --- litellm/proxy/proxy_server.py | 2 +- .../proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts | 8 ++++++-- .../e2e_ui_tests/search_users.spec.ts | 4 ++-- .../e2e_ui_tests/view_internal_user.spec.ts | 8 ++++---- .../e2e_ui_tests/view_user_info.spec.ts | 1 + ui/litellm-dashboard/src/app/page.tsx | 2 +- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 40436e1716b..ebe2a90f37f 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -555,7 +555,7 @@ else: global_max_parallel_request_retry_timeout_env ) -ui_link = f"{server_root_path}/ui/login" +ui_link = f"{server_root_path}/ui" fallback_login_link = f"{server_root_path}/fallback/login" model_hub_link = f"{server_root_path}/ui/model_hub_table" ui_message = ( diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts index fc4d4653246..793ae53b39a 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts @@ -10,7 +10,9 @@ import { test, expect } from "@playwright/test"; test("admin login test", async ({ page }) => { // Go to the specified URL - await page.goto("http://localhost:4000/ui/login"); + await page.goto("http://localhost:4000/ui"); + + page.screenshot({ path: "login_before.png" }); // Enter "admin" in the username input field await page.fill('input[name="username"]', "admin"); @@ -18,8 +20,10 @@ test("admin login test", async ({ page }) => { // Enter "gm" in the password input field await page.fill('input[name="password"]', "gm"); + page.screenshot({ path: "login_after_inputs.png" }); + // Optionally, you can add an assertion to verify the login button is enabled - const loginButton = page.locator('button[type="submit"]'); + const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); // Optionally, you can click the login button to submit the form diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts index 7b9da6a27db..a384248a5f7 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts @@ -31,7 +31,7 @@ test("user search test", async ({ page }) => { await page.fill('input[name="password"]', "gm"); console.log("Filled login credentials"); - const loginButton = page.locator('input[type="submit"]'); + const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); await loginButton.click(); console.log("Clicked login button"); @@ -138,7 +138,7 @@ test("user filter test", async ({ page }) => { await page.fill('input[name="password"]', "gm"); console.log("Filled login credentials"); - const loginButton = page.locator('input[type="submit"]'); + const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); await loginButton.click(); console.log("Clicked login button"); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts index e578ab57e3f..dd6f812df4a 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts @@ -15,7 +15,7 @@ test("view internal user page", async ({ page }) => { await page.fill('input[name="password"]', "gm"); // Click the login button - const loginButton = page.locator('input[type="submit"]'); + const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); await loginButton.click(); @@ -35,9 +35,9 @@ test("view internal user page", async ({ page }) => { const rowCount = await page.locator("tbody tr").count(); expect(rowCount).toBeGreaterThan(0); - // Verify table headers are present (including Virtual Keys column) - const virtualKeysHeader = page.locator("th", { hasText: "Virtual Keys" }); - await expect(virtualKeysHeader).toBeVisible(); + const userIdHeader = page.locator("th", { hasText: "User ID" }); + page.screenshot({ path: "user_id_header.png" }); + await expect(userIdHeader).toBeVisible(); // test pagination // Wait for pagination controls to be visible diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 01eadc9ad1a..742dbd14d44 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -14,6 +14,7 @@ test.describe("User Info View", () => { // Wait for loading state to disappear await page.waitForSelector('text="🚅 Loading users..."', { state: "hidden", + timeout: 10000, }); // Wait for users table to load await page.waitForSelector("table"); diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 68060ae6968..20f5480c970 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -186,7 +186,7 @@ export default function CreateKeyPage() { useEffect(() => { if (redirectToLogin) { // Replace instead of assigning to avoid back-button loops - const dest = (proxyBaseUrl || "") + "/sso/key/generate"; + const dest = (proxyBaseUrl || "") + "/ui/login"; window.location.replace(dest); } }, [redirectToLogin]); From 2072dcefa87c0dbd1b747873fbe4cd2b034844e6 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 4 Dec 2025 10:20:45 +0530 Subject: [PATCH 324/349] Map output_tokens_details of responses API to completion_tokens_details --- .../llms/azure/chat/gpt_5_transformation.py | 1 - litellm/responses/utils.py | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/litellm/llms/azure/chat/gpt_5_transformation.py b/litellm/llms/azure/chat/gpt_5_transformation.py index 2d0ce8b5bce..87f81d117f0 100644 --- a/litellm/llms/azure/chat/gpt_5_transformation.py +++ b/litellm/llms/azure/chat/gpt_5_transformation.py @@ -60,7 +60,6 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): message=( "Azure OpenAI does not support reasoning_effort='none' for this model. " "Supported values are: 'low', 'medium', and 'high'. " - "Note: gpt-5.1 does support reasoning_effort='none'. " "To drop this parameter, set `litellm.drop_params=True` or for proxy:\n\n" "`litellm_settings:\n drop_params: true`\n" "Issue: https://github.com/BerriAI/litellm/issues/16704" diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 198182cf118..ad99609e905 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -24,7 +24,12 @@ from litellm.types.llms.openai import ( ResponseText, ) from litellm.types.responses.main import DecodedResponseId -from litellm.types.utils import PromptTokensDetails, SpecialEnums, Usage +from litellm.types.utils import ( + CompletionTokensDetailsWrapper, + PromptTokensDetails, + SpecialEnums, + Usage, +) class ResponsesAPIRequestUtils: @@ -366,10 +371,11 @@ class ResponsesAPIRequestUtils: Headers from tools.headers in request body should be passed to MCP server. """ from starlette.datastructures import Headers + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( MCPRequestHandler, ) - + # Extract headers from secret_fields which contains the original request headers raw_headers_from_request: Optional[Dict[str, str]] = None if secret_fields and isinstance(secret_fields, dict): @@ -445,11 +451,20 @@ class ResponseAPILoggingUtils: cached_tokens=response_api_usage.input_tokens_details.cached_tokens, audio_tokens=response_api_usage.input_tokens_details.audio_tokens, ) + completion_tokens_details: Optional[CompletionTokensDetailsWrapper] = None + if response_api_usage.output_tokens_details: + completion_tokens_details = CompletionTokensDetailsWrapper( + reasoning_tokens=getattr( + response_api_usage.output_tokens_details, "reasoning_tokens", None + ) + ) + chat_usage = Usage( prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, total_tokens=prompt_tokens + completion_tokens, prompt_tokens_details=prompt_tokens_details, + completion_tokens_details=completion_tokens_details, ) # Preserve cost attribute if it exists on ResponseAPIUsage From be0530a6b331dc32f2ca58c46fccea5f60f598c4 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 3 Dec 2025 20:54:56 -0800 Subject: [PATCH 325/349] fix(unified_guardrail.py): correctly map a v1/messages call to the anthropic unified guardrail (#17424) * fix(unified_guardrail.py): correctly map a v1/messages call to the anthropic unified guardrail * fix: add more rigorous call type checks * fix(anthropic_endpoints/endpoints.py): initialize logging object at the beginning of endpoint ensures call id + trace id are emitted to guardrail api * feat(anthropic/chat/guardrail_translation): support streaming guardrails sample on every 5 chunks * fix(openai/chat/guardrail_translation): support openai streaming guardrails * fix: initial commit fixing output guardrails for responses api * feat(openai/responses/guardrail_translation): handler.py - fix output checks on responses api * fix(openai/responses/guardrail_translation/handler.py): ensure responses api guardrails work on streaming * test: update tests * test: update tests * test: update tests * fix(bedrock_guardrails.py): fix post call streaming iterator logic * fix: fix return * fix(bedrock_guardrails.py): fix --- .../mock_bedrock_guardrail_server.py | 2 + litellm/cost_calculator.py | 37 +- .../chat/guardrail_translation/handler.py | 110 ++ litellm/llms/anthropic/chat/handler.py | 8 +- .../guardrail_translation/base_translation.py | 6 +- .../chat/guardrail_translation/handler.py | 188 ++- .../guardrail_translation/handler.py | 53 +- litellm/proxy/_new_secret_config.yaml | 20 +- .../proxy/anthropic_endpoints/endpoints.py | 221 +--- litellm/proxy/common_request_processing.py | 50 +- .../guardrail_hooks/bedrock_guardrails.py | 34 +- .../unified_guardrail/unified_guardrail.py | 52 +- litellm/proxy/route_llm_request.py | 20 +- litellm/proxy/utils.py | 4 + litellm/router.py | 33 +- litellm/types/utils.py | 1 + .../test_bedrock_guardrails.py | 1095 +++++++++-------- ...test_openai_responses_guardrail_handler.py | 159 ++- .../content_filter/test_content_filter.py | 15 +- 19 files changed, 1211 insertions(+), 897 deletions(-) diff --git a/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py index f75a53e8798..c9f7f13f6aa 100644 --- a/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py +++ b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py @@ -429,6 +429,8 @@ class LitellmBasicGuardrailRequest(BaseModel): request_data: Dict[str, Any] = Field(default_factory=dict) additional_provider_specific_params: Dict[str, Any] = Field(default_factory=dict) input_type: Literal["request", "response"] + litellm_call_id: Optional[str] = None + litellm_trace_id: Optional[str] = None class LitellmBasicGuardrailResponse(BaseModel): diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index ea7caf93ed0..57eab6d29a3 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -95,6 +95,7 @@ from litellm.utils import ( EmbeddingResponse, ImageResponse, ModelResponse, + ModelResponseStream, ProviderConfigManager, TextCompletionResponse, TranscriptionResponse, @@ -654,7 +655,9 @@ def _infer_call_type( if completion_response is None: return None - if isinstance(completion_response, ModelResponse): + if isinstance(completion_response, ModelResponse) or isinstance( + completion_response, ModelResponseStream + ): return "completion" elif isinstance(completion_response, EmbeddingResponse): return "embedding" @@ -1057,29 +1060,33 @@ def completion_cost( # noqa: PLR0915 number_of_queries = len(query) elif query is not None: number_of_queries = 1 - + search_model = model or "" if custom_llm_provider and "/" not in search_model: # If model is like "tavily-search", construct "tavily/search" for cost lookup search_model = f"{custom_llm_provider}/search" - - prompt_cost, completion_cost_result = search_provider_cost_per_query( - model=search_model, - custom_llm_provider=custom_llm_provider, - number_of_queries=number_of_queries, - optional_params=optional_params, + + prompt_cost, completion_cost_result = ( + search_provider_cost_per_query( + model=search_model, + custom_llm_provider=custom_llm_provider, + number_of_queries=number_of_queries, + optional_params=optional_params, + ) ) - + # Return the total cost (prompt_cost + completion_cost, but for search it's just prompt_cost) _final_cost = prompt_cost + completion_cost_result - + # Apply discount original_cost = _final_cost - _final_cost, discount_percent, discount_amount = _apply_cost_discount( - base_cost=_final_cost, - custom_llm_provider=custom_llm_provider, + _final_cost, discount_percent, discount_amount = ( + _apply_cost_discount( + base_cost=_final_cost, + custom_llm_provider=custom_llm_provider, + ) ) - + # Store cost breakdown in logging object if available _store_cost_breakdown_in_logging_obj( litellm_logging_obj=litellm_logging_obj, @@ -1091,7 +1098,7 @@ def completion_cost( # noqa: PLR0915 discount_percent=discount_percent, discount_amount=discount_amount, ) - + return _final_cost elif call_type == CallTypes.arealtime.value and isinstance( completion_response, LiteLLMRealtimeStreamLoggingObject diff --git a/litellm/llms/anthropic/chat/guardrail_translation/handler.py b/litellm/llms/anthropic/chat/guardrail_translation/handler.py index 383a4941238..e248e409de4 100644 --- a/litellm/llms/anthropic/chat/guardrail_translation/handler.py +++ b/litellm/llms/anthropic/chat/guardrail_translation/handler.py @@ -12,6 +12,7 @@ Pattern Overview: 4. Apply guardrail responses back to the original structure """ +import json from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast from litellm._logging import verbose_proxy_logger @@ -246,6 +247,115 @@ class AnthropicMessagesHandler(BaseTranslation): return response + async def process_output_streaming_response( + self, + responses_so_far: List[Any], + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, + user_api_key_dict: Optional[Any] = None, + ) -> List[Any]: + """ + Process output streaming response by applying guardrails to text content. + + Get the string so far, check the apply guardrail to the string so far, and return the list of responses so far. + """ + string_so_far = self.get_streaming_string_so_far(responses_so_far) + _, _ = ( + await guardrail_to_apply.apply_guardrail( # allow rejecting the response, if invalid + texts=[string_so_far], + request_data={}, + input_type="response", + logging_obj=litellm_logging_obj, + images=None, + ) + ) + return responses_so_far + + def get_streaming_string_so_far(self, responses_so_far: List[Any]) -> str: + """ + Parse streaming responses and extract accumulated text content. + + Handles two formats: + 1. Raw bytes in SSE (Server-Sent Events) format from Anthropic API + 2. Parsed dict objects (for backwards compatibility) + + SSE format example: + b'event: content_block_delta\\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" curious"}}\\n\\n' + + Dict format example: + { + "type": "content_block_delta", + "index": 0, + "delta": { + "type": "text_delta", + "text": " curious" + } + } + """ + text_so_far = "" + for response in responses_so_far: + # Handle raw bytes in SSE format + if isinstance(response, bytes): + text_so_far += self._extract_text_from_sse(response) + # Handle already-parsed dict format + elif isinstance(response, dict): + delta = response.get("delta") if response.get("delta") else None + if delta and delta.get("type") == "text_delta": + text = delta.get("text", "") + if text: + text_so_far += text + return text_so_far + + def _extract_text_from_sse(self, sse_bytes: bytes) -> str: + """ + Extract text content from Server-Sent Events (SSE) format. + + Args: + sse_bytes: Raw bytes in SSE format + + Returns: + Accumulated text from all content_block_delta events + """ + text = "" + try: + # Decode bytes to string + sse_string = sse_bytes.decode("utf-8") + + # Split by double newline to get individual events + events = sse_string.split("\n\n") + + for event in events: + if not event.strip(): + continue + + # Parse event lines + lines = event.strip().split("\n") + event_type = None + data_line = None + + for line in lines: + if line.startswith("event:"): + event_type = line[6:].strip() + elif line.startswith("data:"): + data_line = line[5:].strip() + + # Only process content_block_delta events + if event_type == "content_block_delta" and data_line: + try: + data = json.loads(data_line) + delta = data.get("delta", {}) + if delta.get("type") == "text_delta": + text += delta.get("text", "") + except json.JSONDecodeError: + verbose_proxy_logger.warning( + f"Failed to parse JSON from SSE data: {data_line}" + ) + + except Exception as e: + verbose_proxy_logger.error(f"Error extracting text from SSE: {e}") + + return text + def _has_text_content(self, response: "AnthropicMessagesResponse") -> bool: """ Check if response has any text content to process. diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index b363b747de5..36156d56a59 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -436,9 +436,7 @@ class AnthropicChatCompletion(BaseLLM): else: if client is None or not isinstance(client, HTTPHandler): - client = _get_httpx_client( - params={"timeout": timeout} - ) + client = _get_httpx_client(params={"timeout": timeout}) else: client = client @@ -528,9 +526,7 @@ class ModelResponseIterator: usage_object=cast(dict, anthropic_usage_chunk), reasoning_content=None ) - def _content_block_delta_helper( - self, chunk: dict - ) -> Tuple[ + def _content_block_delta_helper(self, chunk: dict) -> Tuple[ str, Optional[ChatCompletionToolCallChunk], List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]], diff --git a/litellm/llms/base_llm/guardrail_translation/base_translation.py b/litellm/llms/base_llm/guardrail_translation/base_translation.py index c1ea3311bd8..7106c207bd6 100644 --- a/litellm/llms/base_llm/guardrail_translation/base_translation.py +++ b/litellm/llms/base_llm/guardrail_translation/base_translation.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional if TYPE_CHECKING: from litellm.integrations.custom_guardrail import CustomGuardrail @@ -87,7 +87,7 @@ class BaseTranslation(ABC): async def process_output_streaming_response( self, - response: Any, + responses_so_far: List[Any], guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, user_api_key_dict: Optional["UserAPIKeyAuth"] = None, @@ -97,4 +97,4 @@ class BaseTranslation(ABC): Optional to override in subclasses. """ - return response + return responses_so_far diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index 29fb12a6f74..2b381e28bc0 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -243,48 +243,122 @@ class OpenAIChatCompletionsHandler(BaseTranslation): async def process_output_streaming_response( self, - response: "ModelResponseStream", + responses_so_far: List["ModelResponseStream"], guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: Optional[Any] = None, user_api_key_dict: Optional[Any] = None, - ) -> Any: + ) -> List["ModelResponseStream"]: """ - Process output streaming response by applying guardrails to text content. + Process output streaming responses by applying guardrails to text content. Args: - response: LiteLLM ModelResponseStream object + responses_so_far: List of LiteLLM ModelResponseStream objects guardrail_to_apply: The guardrail instance to apply litellm_logging_obj: Optional logging object user_api_key_dict: User API key metadata to pass to guardrails Returns: - Modified response with guardrail applied to content + Modified list of responses with guardrail applied to content Response Format Support: - String content: choice.message.content = "text here" - List content: choice.message.content = [{"type": "text", "text": "text here"}, ...] """ - # Step 0: Check if response has any text content to process - if not self._has_text_content(response): - return response + # Step 0: Check if any response has text content to process + has_any_text_content = False + for response in responses_so_far: + if self._has_text_content(response): + has_any_text_content = True + break + if not has_any_text_content: + verbose_proxy_logger.warning( + "OpenAI Chat Completions: No text content in streaming responses, skipping guardrail" + ) + return responses_so_far + + # Step 1: Combine all streaming chunks into complete text per choice + # For streaming, we need to concatenate all delta.content across all chunks + # Key: (choice_idx, content_idx), Value: combined text + combined_texts: Dict[Tuple[int, Optional[int]], str] = {} + + for response_idx, response in enumerate(responses_so_far): + for choice_idx, choice in enumerate(response.choices): + if isinstance(choice, litellm.StreamingChoices): + content = choice.delta.content + elif isinstance(choice, litellm.Choices): + content = choice.message.content + else: + continue + + if content is None: + continue + + if isinstance(content, str): + # String content - accumulate for this choice + key = (choice_idx, None) + if key not in combined_texts: + combined_texts[key] = "" + combined_texts[key] += content + + elif isinstance(content, list): + # List content - accumulate for each content item + for content_idx, content_item in enumerate(content): + text_str = content_item.get("text") + if text_str: + key = (choice_idx, content_idx) + if key not in combined_texts: + combined_texts[key] = "" + combined_texts[key] += text_str + + # Step 2: Create lists for guardrail processing texts_to_check: List[str] = [] images_to_check: List[str] = [] task_mappings: List[Tuple[int, Optional[int]]] = [] - # Track (choice_index, content_index) for each text + # Track (choice_index, content_index) for each combined text - # Step 1: Extract all text content and images from response choices - for choice_idx, choice in enumerate(response.choices): + for (choice_idx, content_idx), combined_text in combined_texts.items(): + texts_to_check.append(combined_text) + task_mappings.append((choice_idx, content_idx)) - self._extract_output_text_and_images( - choice=choice, - choice_idx=choice_idx, - texts_to_check=texts_to_check, - images_to_check=images_to_check, + # Step 3: Apply guardrail to all combined texts in batch + if texts_to_check: + # Create a request_data dict with response info and user API key metadata + request_data: dict = {"responses": responses_so_far} + + # Add user API key metadata with prefixed keys + user_metadata = self.transform_user_api_key_dict_to_metadata( + user_api_key_dict + ) + if user_metadata: + request_data["litellm_metadata"] = user_metadata + + guardrailed_texts, guardrailed_images = ( + await guardrail_to_apply.apply_guardrail( + texts=texts_to_check, + request_data=request_data, + input_type="response", + images=images_to_check if images_to_check else None, + logging_obj=litellm_logging_obj, + ) + ) + + # Step 4: Apply guardrailed text back to all streaming chunks + # For each choice, replace the combined text across all chunks + await self._apply_guardrail_responses_to_output_streaming( + responses=responses_so_far, + guardrailed_texts=guardrailed_texts, task_mappings=task_mappings, ) + verbose_proxy_logger.debug( + "OpenAI Chat Completions: Processed output streaming responses: %s", + responses_so_far, + ) + + return responses_so_far + def _has_text_content( self, response: Union["ModelResponse", "ModelResponseStream"] ) -> bool: @@ -304,10 +378,8 @@ class OpenAIChatCompletionsHandler(BaseTranslation): return True elif isinstance(response, ModelResponseStream): for choice in response.choices: - if isinstance(choice, litellm.Choices): - if choice.message.content and isinstance( - choice.message.content, str - ): + if isinstance(choice, litellm.StreamingChoices): + if choice.delta.content and isinstance(choice.delta.content, str): return True return False @@ -394,3 +466,79 @@ class OpenAIChatCompletionsHandler(BaseTranslation): ][ "text" ] = guardrail_response + + async def _apply_guardrail_responses_to_output_streaming( + self, + responses: List["ModelResponseStream"], + guardrailed_texts: List[str], + task_mappings: List[Tuple[int, Optional[int]]], + ) -> None: + """ + Apply guardrail responses back to output streaming responses. + + For streaming responses, the guardrailed text (which is the combined text from all chunks) + is placed in the first chunk, and subsequent chunks are cleared. + + Args: + responses: List of ModelResponseStream objects to modify + guardrailed_texts: List of guardrailed text responses (combined from all chunks) + task_mappings: List of tuples (choice_idx, content_idx) + + Override this method to customize how responses are applied to streaming responses. + """ + # Build a mapping of what guardrailed text to use for each (choice_idx, content_idx) + guardrail_map: Dict[Tuple[int, Optional[int]], str] = {} + for task_idx, guardrail_response in enumerate(guardrailed_texts): + mapping = task_mappings[task_idx] + choice_idx = cast(int, mapping[0]) + content_idx_optional = cast(Optional[int], mapping[1]) + guardrail_map[(choice_idx, content_idx_optional)] = guardrail_response + + # Track which choices we've already set the guardrailed text for + # Key: (choice_idx, content_idx), Value: boolean (True if already set) + already_set: Dict[Tuple[int, Optional[int]], bool] = {} + + # Iterate through all responses and update content + for response_idx, response in enumerate(responses): + for choice_idx_in_response, choice in enumerate(response.choices): + if isinstance(choice, litellm.StreamingChoices): + content = choice.delta.content + elif isinstance(choice, litellm.Choices): + content = choice.message.content + else: + continue + + if content is None: + continue + + if isinstance(content, str): + # String content + key = (choice_idx_in_response, None) + if key in guardrail_map: + if key not in already_set: + # First chunk - set the complete guardrailed text + if isinstance(choice, litellm.StreamingChoices): + choice.delta.content = guardrail_map[key] + elif isinstance(choice, litellm.Choices): + choice.message.content = guardrail_map[key] + already_set[key] = True + else: + # Subsequent chunks - clear the content + if isinstance(choice, litellm.StreamingChoices): + choice.delta.content = "" + elif isinstance(choice, litellm.Choices): + choice.message.content = "" + + elif isinstance(content, list): + # List content - handle each content item + for content_idx, content_item in enumerate(content): + if "text" in content_item: + key = (choice_idx_in_response, content_idx) + if key in guardrail_map: + if key not in already_set: + # First chunk - set the complete guardrailed text + content_item["text"] = guardrail_map[key] + already_set[key] = True + else: + # Subsequent chunks - clear the text + content_item["text"] = "" diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index 667a72a426a..7ef6576159e 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -30,6 +30,8 @@ Output: response.output is List[GenericResponseOutputItem] where each has: from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union, cast +from openai import BaseModel + from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation from litellm.types.responses.main import GenericResponseOutputItem, OutputText @@ -275,6 +277,32 @@ class OpenAIResponsesHandler(BaseTranslation): return response + async def process_output_streaming_response( + self, + responses_so_far: List[Any], + guardrail_to_apply: "CustomGuardrail", + litellm_logging_obj: Optional[Any] = None, + user_api_key_dict: Optional[Any] = None, + ) -> List[Any]: + """ + Process output streaming response by applying guardrails to text content. + """ + string_so_far = self.get_streaming_string_so_far(responses_so_far) + guardrailed_text, _ = await guardrail_to_apply.apply_guardrail( + texts=[string_so_far], + request_data={}, + input_type="response", + logging_obj=litellm_logging_obj, + images=None, + ) + return responses_so_far + + def get_streaming_string_so_far(self, responses_so_far: List[Any]) -> str: + """ + Get the string so far from the responses so far. + """ + return "".join([response.get("text", "") for response in responses_so_far]) + def _has_text_content(self, response: "ResponsesAPIResponse") -> bool: """ Check if response has any text content to process. @@ -285,6 +313,17 @@ class OpenAIResponsesHandler(BaseTranslation): return False for output_item in response.output: + if isinstance(output_item, BaseModel): + try: + generic_response_output_item = ( + GenericResponseOutputItem.model_validate( + output_item.model_dump() + ) + ) + if generic_response_output_item.content: + output_item = generic_response_output_item + except Exception: + continue if isinstance(output_item, (GenericResponseOutputItem, dict)): content = ( output_item.content @@ -296,9 +335,11 @@ class OpenAIResponsesHandler(BaseTranslation): # Check if it's an OutputText with text if isinstance(content_item, OutputText): if content_item.text: + return True elif isinstance(content_item, dict): if content_item.get("text"): + return True return False @@ -316,8 +357,16 @@ class OpenAIResponsesHandler(BaseTranslation): Override this method to customize text/image extraction logic. """ # Handle both GenericResponseOutputItem and dict - if isinstance(output_item, GenericResponseOutputItem): - content = output_item.content + content: Optional[Union[List[OutputText], List[dict]]] = None + if isinstance(output_item, BaseModel): + try: + generic_response_output_item = GenericResponseOutputItem.model_validate( + output_item.model_dump() + ) + if generic_response_output_item.content: + content = generic_response_output_item.content + except Exception: + return elif isinstance(output_item, dict): content = output_item.get("content", []) else: diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index d2bbe8ee6e3..f1916e99ff9 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -3,23 +3,19 @@ model_list: litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY + - model_name: claude-sonnet-4-5-20250929 + litellm_params: + model: anthropic/claude-sonnet-4-5-20250929 + - model_name: gpt-4.1-mini + litellm_params: + model: openai/gpt-4.1-mini + guardrails: - # - guardrail_name: model-armor-shield - # litellm_params: - # guardrail: model_armor - # mode: "post_call" # Run on both input and output - # template_id: "test-prompt-template" # Required: Your Model Armor template ID - # project_id: "test-vector-store-db" # Your GCP project ID - # location: "us" # GCP location (default: us-central1) - # mask_request_content: true # Enable request content masking - # mask_response_content: true # Enable response content masking - # fail_on_error: true # Fail request if Model Armor errors (default: true) - # default_on: true # Run by default for all requests - guardrail_name: generic-guardrail litellm_params: guardrail: generic_guardrail_api - mode: [pre_call, post_call] + mode: ["pre_call", "post_call", "during_call"] headers: Authorization: Bearer mock-bedrock-token-12345 api_base: http://localhost:8080 diff --git a/litellm/proxy/anthropic_endpoints/endpoints.py b/litellm/proxy/anthropic_endpoints/endpoints.py index abea9e6fee1..3317560904d 100644 --- a/litellm/proxy/anthropic_endpoints/endpoints.py +++ b/litellm/proxy/anthropic_endpoints/endpoints.py @@ -2,20 +2,14 @@ Unified /v1/messages endpoint - (Anthropic Spec) """ -import asyncio -from fastapi import APIRouter, Depends, HTTPException, Request, Response, status +from fastapi import APIRouter, Depends, HTTPException, Request, Response -import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import * from litellm.proxy.auth.user_api_key_auth import user_api_key_auth -from litellm.proxy.common_request_processing import ( - ProxyBaseLLMRequestProcessing, - create_streaming_response, -) +from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing from litellm.proxy.common_utils.http_parsing_utils import _read_request_body -from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request from litellm.types.utils import TokenCountResponse router = APIRouter() @@ -49,169 +43,28 @@ async def anthropic_response( # noqa: PLR0915 version, ) - request_data = await _read_request_body(request=request) - data: dict = {**request_data} + data = await _read_request_body(request=request) + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) try: - data["model"] = ( - general_settings.get("completion_model", None) # server default - or user_model # model name passed via cli args - or data.get("model", None) # default passed in http request - ) - if user_model: - data["model"] = user_model - - data = await add_litellm_data_to_request( - data=data, # type: ignore + result = await base_llm_response_processor.base_process_llm_request( request=request, - general_settings=general_settings, + fastapi_response=fastapi_response, user_api_key_dict=user_api_key_dict, - version=version, + route_type="anthropic_messages", + proxy_logging_obj=proxy_logging_obj, + llm_router=llm_router, + general_settings=general_settings, proxy_config=proxy_config, + select_data_generator=None, + model=None, + user_model=user_model, + user_temperature=user_temperature, + user_request_timeout=user_request_timeout, + user_max_tokens=user_max_tokens, + user_api_base=user_api_base, + version=version, ) - - # override with user settings, these are params passed via cli - if user_temperature: - data["temperature"] = user_temperature - if user_request_timeout: - data["request_timeout"] = user_request_timeout - if user_max_tokens: - data["max_tokens"] = user_max_tokens - if user_api_base: - data["api_base"] = user_api_base - - ### MODEL ALIAS MAPPING ### - # check if model name in model alias map - # get the actual model name - if data["model"] in litellm.model_alias_map: - data["model"] = litellm.model_alias_map[data["model"]] - - ### CALL HOOKS ### - modify incoming data before calling the model - data = await proxy_logging_obj.pre_call_hook( # type: ignore - user_api_key_dict=user_api_key_dict, data=data, call_type=CallTypes.anthropic_messages.value - ) - - tasks = [] - tasks.append( - proxy_logging_obj.during_call_hook( - data=data, - user_api_key_dict=user_api_key_dict, - call_type=ProxyBaseLLMRequestProcessing._get_pre_call_type( - route_type="anthropic_messages" # type: ignore - ), - ) - ) - - ### ROUTE THE REQUESTs ### - router_model_names = llm_router.model_names if llm_router is not None else [] - - # skip router if user passed their key - if ( - llm_router is not None and data["model"] in router_model_names - ): # model in router model list - llm_coro = llm_router.aanthropic_messages(**data) - elif ( - llm_router is not None - and llm_router.model_group_alias is not None - and data["model"] in llm_router.model_group_alias - ): # model set in model_group_alias - llm_coro = llm_router.aanthropic_messages(**data) - elif ( - llm_router is not None and data["model"] in llm_router.deployment_names - ): # model in router deployments, calling a specific deployment on the router - llm_coro = llm_router.aanthropic_messages(**data, specific_deployment=True) - elif ( - llm_router is not None and llm_router.has_model_id(data["model"]) - ): # model in router model list - llm_coro = llm_router.aanthropic_messages(**data) - elif ( - llm_router is not None - and data["model"] not in router_model_names - and ( - llm_router.default_deployment is not None - or len(llm_router.pattern_router.patterns) > 0 - ) - ): # model in router deployments, calling a specific deployment on the router - llm_coro = llm_router.aanthropic_messages(**data) - elif user_model is not None: # `litellm --model ` - llm_coro = litellm.anthropic_messages(**data) - else: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail={ - "error": "completion: Invalid model name passed in model=" - + data.get("model", "") - }, - ) - - tasks.append(llm_coro) - - # wait for call to end - llm_responses = asyncio.gather( - *tasks - ) # run the moderation check in parallel to the actual llm api call - - responses = await llm_responses - - response = responses[1] - - # Extract model_id from request metadata (set by router during routing) - litellm_metadata = data.get("litellm_metadata", {}) or {} - model_info = litellm_metadata.get("model_info", {}) or {} - model_id = model_info.get("id", "") or "" - - # Get other metadata from hidden_params - hidden_params = getattr(response, "_hidden_params", {}) or {} - cache_key = hidden_params.get("cache_key", None) or "" - api_base = hidden_params.get("api_base", None) or "" - response_cost = hidden_params.get("response_cost", None) or "" - - ### ALERTING ### - asyncio.create_task( - proxy_logging_obj.update_request_status( - litellm_call_id=data.get("litellm_call_id", ""), status="success" - ) - ) - - verbose_proxy_logger.debug("final response: %s", response) - - fastapi_response.headers.update( - ProxyBaseLLMRequestProcessing.get_custom_headers( - user_api_key_dict=user_api_key_dict, - model_id=model_id, - cache_key=cache_key, - api_base=api_base, - version=version, - response_cost=response_cost, - request_data=data, - hidden_params=hidden_params, - ) - ) - - if ( - "stream" in data and data["stream"] is True - ): # use generate_responses to stream responses - selected_data_generator = ( - ProxyBaseLLMRequestProcessing.async_sse_data_generator( - response=response, - user_api_key_dict=user_api_key_dict, - request_data=data, - proxy_logging_obj=proxy_logging_obj, - ) - ) - - return await create_streaming_response( - generator=selected_data_generator, - media_type="text/event-stream", - headers=dict(fastapi_response.headers), - ) - - ### CALL HOOKS ### - modify outgoing data - response = await proxy_logging_obj.post_call_success_hook( - data=data, user_api_key_dict=user_api_key_dict, response=response # type: ignore - ) - - verbose_proxy_logger.debug("\nResponse from Litellm:\n{}".format(response)) - return response + return result except Exception as e: await proxy_logging_obj.post_call_failure_hook( user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data @@ -280,35 +133,30 @@ async def count_tokens( Returns: {"input_tokens": } """ from litellm.proxy.proxy_server import token_counter as internal_token_counter - + try: request_data = await _read_request_body(request=request) data: dict = {**request_data} - + # Extract required fields model_name = data.get("model") messages = data.get("messages", []) - + if not model_name: raise HTTPException( - status_code=400, - detail={"error": "model parameter is required"} + status_code=400, detail={"error": "model parameter is required"} ) - + if not messages: raise HTTPException( - status_code=400, - detail={"error": "messages parameter is required"} + status_code=400, detail={"error": "messages parameter is required"} ) - + # Create TokenCountRequest for the internal endpoint from litellm.proxy._types import TokenCountRequest - - token_request = TokenCountRequest( - model=model_name, - messages=messages - ) - + + token_request = TokenCountRequest(model=model_name, messages=messages) + # Call the internal token counter function with direct request flag set to False token_response = await internal_token_counter( request=token_request, @@ -319,17 +167,18 @@ async def count_tokens( _token_response_dict = token_response.model_dump() elif isinstance(token_response, dict): _token_response_dict = token_response - + # Convert the internal response to Anthropic API format return {"input_tokens": _token_response_dict.get("total_tokens", 0)} - + except HTTPException: raise except Exception as e: verbose_proxy_logger.exception( - "litellm.proxy.anthropic_endpoints.count_tokens(): Exception occurred - {}".format(str(e)) + "litellm.proxy.anthropic_endpoints.count_tokens(): Exception occurred - {}".format( + str(e) + ) ) raise HTTPException( - status_code=500, - detail={"error": f"Internal server error: {str(e)}"} + status_code=500, detail={"error": f"Internal server error: {str(e)}"} ) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 1c6c9b97173..0d2ffc70f29 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -337,6 +337,7 @@ class ProxyBaseLLMRequestProcessing: "alist_skills", "aget_skill", "adelete_skill", + "anthropic_messages", ], version: Optional[str] = None, user_model: Optional[str] = None, @@ -461,11 +462,12 @@ class ProxyBaseLLMRequestProcessing: "alist_skills", "aget_skill", "adelete_skill", + "anthropic_messages", ], proxy_logging_obj: ProxyLogging, general_settings: dict, proxy_config: ProxyConfig, - select_data_generator: Callable, + select_data_generator: Optional[Callable] = None, llm_router: Optional[Router] = None, model: Optional[str] = None, user_model: Optional[str] = None, @@ -605,7 +607,21 @@ class ProxyBaseLLMRequestProcessing: status_code=response.status_code, headers=custom_headers, ) - else: + elif route_type == "anthropic_messages": + selected_data_generator = ( + ProxyBaseLLMRequestProcessing.async_sse_data_generator( + response=response, + user_api_key_dict=user_api_key_dict, + request_data=self.data, + proxy_logging_obj=proxy_logging_obj, + ) + ) + return await create_streaming_response( + generator=selected_data_generator, + media_type="text/event-stream", + headers=custom_headers, + ) + elif select_data_generator: selected_data_generator = select_data_generator( response=response, user_api_key_dict=user_api_key_dict, @@ -804,21 +820,30 @@ class ProxyBaseLLMRequestProcessing: # This matches the original behavior before the refactor in commit 511d435f6f error_body = await e.response.aread() error_text = error_body.decode("utf-8") - + raise HTTPException( status_code=e.response.status_code, detail={"error": error_text}, ) - error_msg = f"{str(e)}" + error_msg = f"{str(e)}" # Check for AttributeError in various places: # 1. Direct AttributeError (already handled above) # 2. In underlying exception (__cause__, __context__, original_exception) has_attribute_error = ( - (isinstance(e, Exception) and isinstance(getattr(e, "__cause__", None), AttributeError)) - or (isinstance(e, Exception) and isinstance(getattr(e, "__context__", None), AttributeError)) - or (isinstance(e, Exception) and isinstance(getattr(e, "original_exception", None), AttributeError)) + ( + isinstance(e, Exception) + and isinstance(getattr(e, "__cause__", None), AttributeError) + ) + or ( + isinstance(e, Exception) + and isinstance(getattr(e, "__context__", None), AttributeError) + ) + or ( + isinstance(e, Exception) + and isinstance(getattr(e, "original_exception", None), AttributeError) + ) ) - + if has_attribute_error: raise ProxyException( message=f"Invalid request format: {error_msg}", @@ -1110,7 +1135,9 @@ class ProxyBaseLLMRequestProcessing: return obj return None - def maybe_get_model_id(self, _logging_obj: Optional[LiteLLMLoggingObj]) -> Optional[str]: + def maybe_get_model_id( + self, _logging_obj: Optional[LiteLLMLoggingObj] + ) -> Optional[str]: """ Get model_id from logging object or request metadata. @@ -1120,10 +1147,7 @@ class ProxyBaseLLMRequestProcessing: model_id = None if _logging_obj: # 1. Try getting from litellm_params (updated during call) - if ( - hasattr(_logging_obj, "litellm_params") - and _logging_obj.litellm_params - ): + if hasattr(_logging_obj, "litellm_params") and _logging_obj.litellm_params: # First check direct model_info path (set by router.py with selected deployment) model_info = _logging_obj.litellm_params.get("model_info") or {} model_id = model_info.get("id", None) diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index bd1e805361c..3932e1220b4 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -729,9 +729,9 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): ######################################################### ########## 1. Make the Bedrock API request ########## ######################################################### - bedrock_guardrail_response: Optional[ - Union[BedrockGuardrailResponse, str] - ] = None + bedrock_guardrail_response: Optional[Union[BedrockGuardrailResponse, str]] = ( + None + ) try: bedrock_guardrail_response = await self.make_bedrock_api_request( source="INPUT", messages=filtered_messages, request_data=data @@ -801,9 +801,9 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): ######################################################### ########## 1. Make the Bedrock API request ########## ######################################################### - bedrock_guardrail_response: Optional[ - Union[BedrockGuardrailResponse, str] - ] = None + bedrock_guardrail_response: Optional[Union[BedrockGuardrailResponse, str]] = ( + None + ) try: bedrock_guardrail_response = await self.make_bedrock_api_request( source="INPUT", messages=filtered_messages, request_data=data @@ -1276,10 +1276,9 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): masked_texts = [] - for text in texts: - mock_messages: List[AllMessageValues] = [ - ChatCompletionUserMessage(role="user", content=text) - ] + mock_messages: List[AllMessageValues] = [ + ChatCompletionUserMessage(role="user", content=text) for text in texts + ] request_messages = mock_messages filter_result = self._prepare_guardrail_messages_for_role( messages=request_messages @@ -1292,19 +1291,12 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): request_data=request_data, ) - bedrock_response = await self.make_bedrock_api_request( - source="INPUT", - messages=mock_messages, - request_data=request_data, - ) - if bedrock_response.get("action") == "BLOCKED": raise Exception( f"Content blocked by Bedrock guardrail: {bedrock_response.get('reason', 'Unknown reason')}" ) # Apply any masking that was applied by the guardrail - masked_text = text output_list = bedrock_response.get("output") if output_list: # If the guardrail returned modified content, use that @@ -1312,7 +1304,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): text_content = output_item.get("text") if text_content: masked_text = str(text_content) - break + masked_texts.append(masked_text) else: outputs_list = bedrock_response.get("outputs") if outputs_list: @@ -1321,15 +1313,13 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): text_content = output_item.get("text") if text_content: masked_text = str(text_content) - break - - masked_texts.append(masked_text) + masked_texts.append(masked_text) verbose_proxy_logger.debug( "Bedrock Guardrail: Successfully applied guardrail" ) - return masked_texts, images + return masked_texts or texts, images except Exception as e: verbose_proxy_logger.error( diff --git a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py index 0f05696af42..d234dffc511 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py +++ b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py @@ -6,7 +6,7 @@ Unified Guardrail, leveraging LiteLLM's /applyGuardrail endpoint 3. Implements a way to call /applyGuardrail endpoint for `/chat/completions` + `/v1/messages` requests on async_post_call_streaming_iterator_hook """ -from typing import Any, AsyncGenerator, Union +from typing import Any, AsyncGenerator, List, Optional, Union from litellm._logging import verbose_proxy_logger from litellm.caching.caching import DualCache @@ -126,16 +126,21 @@ class UnifiedLLMGuardrails(CustomLogger): ) is not True ): - return verbose_proxy_logger.debug( "async_post_call_success_hook response: %s", response ) - call_type = _infer_call_type(call_type=None, completion_response=response) + call_type: Optional[CallTypesLiteral] = None + if user_api_key_dict.request_route is not None: + call_types = get_call_types_for_route(user_api_key_dict.request_route) + if call_types is not None: + call_type = call_types[0] if call_type is None: + call_type = _infer_call_type(call_type=None, completion_response=response) + if call_type is None: return response if endpoint_guardrail_translation_mappings is None: @@ -183,9 +188,6 @@ class UnifiedLLMGuardrails(CustomLogger): """ global endpoint_guardrail_translation_mappings - from litellm.proxy.common_utils.callback_utils import ( - add_guardrail_to_applied_guardrails_header, - ) guardrail_to_apply: CustomGuardrail = request_data.pop( "guardrail_to_apply", None @@ -234,9 +236,11 @@ class UnifiedLLMGuardrails(CustomLogger): # Infer call type from first chunk call_type = None chunk_counter = 0 + responses_so_far: List[Any] = [] async for item in response: chunk_counter += 1 + responses_so_far.append(item) # Infer call type from first chunk if not already done if call_type is None and user_api_key_dict.request_route is not None: @@ -244,19 +248,22 @@ class UnifiedLLMGuardrails(CustomLogger): if call_types is not None: call_type = call_types[0] - # If call type not supported, just pass through all chunks - if ( - call_type is None - or CallTypes(call_type) - not in endpoint_guardrail_translation_mappings - ): - yield item - async for remaining_item in response: - yield remaining_item - return + if call_type is None: + call_type = _infer_call_type(call_type=None, completion_response=item) + + # If call type not supported, just pass through all chunks + if ( + call_type is None + or CallTypes(call_type) not in endpoint_guardrail_translation_mappings + ): + yield item + async for remaining_item in response: + yield remaining_item + return # Process chunk based on sampling rate if chunk_counter % sampling_rate == 0: + verbose_proxy_logger.debug( "Processing streaming chunk %s (sampling_rate=%s) with guardrail %s", chunk_counter, @@ -268,22 +275,17 @@ class UnifiedLLMGuardrails(CustomLogger): CallTypes(call_type) ]() - processed_item = ( + processed_items = ( await endpoint_translation.process_output_streaming_response( - response=item, + responses_so_far=responses_so_far, guardrail_to_apply=guardrail_to_apply, litellm_logging_obj=request_data.get("litellm_logging_obj"), user_api_key_dict=user_api_key_dict, ) ) - # Add guardrail to applied guardrails header (only once, on first processed chunk) - if chunk_counter == sampling_rate: - add_guardrail_to_applied_guardrails_header( - request_data=request_data, - guardrail_name=guardrail_to_apply.guardrail_name, - ) + last_item = processed_items[-1] - yield processed_item + yield last_item else: yield item diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 79e316d4702..221aa16f912 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -75,12 +75,13 @@ def add_shared_session_to_data(data: dict) -> None: """ Add shared aiohttp session for connection reuse (prevents cold starts). Silently continues without session reuse if import fails or session is unavailable. - + Args: data: Dictionary to add the shared session to """ try: from litellm.proxy.proxy_server import shared_aiohttp_session + if shared_aiohttp_session is not None and not shared_aiohttp_session.closed: data["shared_session"] = shared_aiohttp_session except Exception: @@ -136,13 +137,14 @@ async def route_request( "aget_skill", "adelete_skill", "aingest", + "anthropic_messages", ], ): """ Common helper to route the request """ add_shared_session_to_data(data) - + team_id = get_team_id_from_data(data) router_model_names = llm_router.model_names if llm_router is not None else [] @@ -177,7 +179,12 @@ async def route_request( return llm_router.abatch_completion(models=models, **data) elif llm_router is not None: # Skip model-based routing for container operations - if route_type in ["acreate_container", "alist_containers", "aretrieve_container", "adelete_container"]: + if route_type in [ + "acreate_container", + "alist_containers", + "aretrieve_container", + "adelete_container", + ]: return getattr(llm_router, f"{route_type}")(**data) if route_type in [ "avideo_list", @@ -196,7 +203,7 @@ async def route_request( ] and (data.get("model") is None or data.get("model") == ""): # These endpoints don't need a model, use custom_llm_provider directly return getattr(litellm, f"{route_type}")(**data) - + team_model_name = ( llm_router.map_team_model(data["model"], team_id) if team_id is not None @@ -206,9 +213,8 @@ async def route_request( data["model"] = team_model_name return getattr(llm_router, f"{route_type}")(**data) - elif ( - data["model"] in router_model_names - or llm_router.has_model_id(data["model"]) + elif data["model"] in router_model_names or llm_router.has_model_id( + data["model"] ): return getattr(llm_router, f"{route_type}")(**data) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index f0dccfae716..28a7ef001d5 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1417,6 +1417,7 @@ class ProxyLogging: 3. /image/generation 4. /files """ + from litellm.types.guardrails import GuardrailEventHooks guardrail_callbacks: List[CustomGuardrail] = [] @@ -1451,6 +1452,7 @@ class ProxyLogging: continue guardrail_response: Optional[Any] = None + if "apply_guardrail" in type(callback).__dict__: data["guardrail_to_apply"] = callback guardrail_response = ( @@ -1562,6 +1564,7 @@ class ProxyLogging: """ for callback in litellm.callbacks: + _callback: Optional[CustomLogger] = None if isinstance(callback, str): _callback = litellm.litellm_core_utils.litellm_logging.get_custom_logger_compatible_class( @@ -1575,6 +1578,7 @@ class ProxyLogging: ) or _callback.should_run_guardrail( data=request_data, event_type=GuardrailEventHooks.post_call ): + if "apply_guardrail" in type(callback).__dict__: request_data["guardrail_to_apply"] = callback response = ( diff --git a/litellm/router.py b/litellm/router.py index a52e0260bad..9488f341cbf 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -153,11 +153,7 @@ from litellm.types.utils import ( ) from litellm.types.utils import ModelInfo from litellm.types.utils import ModelInfo as ModelMapInfo -from litellm.types.utils import ( - ModelResponseStream, - StandardLoggingPayload, - Usage, -) +from litellm.types.utils import ModelResponseStream, StandardLoggingPayload, Usage from litellm.utils import ( CustomStreamWrapper, EmbeddingResponse, @@ -779,6 +775,9 @@ class Router: self.aanthropic_messages = self.factory_function( litellm.anthropic_messages, call_type="anthropic_messages" ) + self.anthropic_messages = self.factory_function( + litellm.anthropic_messages, call_type="anthropic_messages" + ) self.agenerate_content = self.factory_function( litellm.agenerate_content, call_type="agenerate_content" ) @@ -886,6 +885,7 @@ class Router: from litellm.vector_store_files.main import ( update as vector_store_file_update_fn, ) + self.avector_store_file_create = self.factory_function( avector_store_file_create_fn, call_type="avector_store_file_create" ) @@ -3865,6 +3865,7 @@ class Router: "retrieve_container", "delete_container", ): + def sync_wrapper( custom_llm_provider: Optional[str] = None, client: Optional[Any] = None, @@ -5944,36 +5945,38 @@ class Router: """ Get API credentials and provider info from a model name in model_list. Useful for passthrough endpoints (files, batches, etc.) that need credentials. - + This method tries to find a deployment by model_id first, and if not found, it tries to find by model_group_name (model_name). - + Args: model_id: Model ID or model name from model_list (e.g., "gpt-4o-litellm") - + Returns: Dictionary containing api_key, api_base, custom_llm_provider, etc. Returns None if model not found. - + Example: credentials = router.get_deployment_credentials_with_provider("gpt-4o-litellm") # Returns: {"api_key": "sk-...", "custom_llm_provider": "openai", ...} """ # Try to get deployment by model_id first deployment = self.get_deployment(model_id=model_id) - + # If not found, try by model_group_name if deployment is None: - deployment = self.get_deployment_by_model_group_name(model_group_name=model_id) - + deployment = self.get_deployment_by_model_group_name( + model_group_name=model_id + ) + if deployment is None: return None - + # Get basic credentials credentials = CredentialLiteLLMParams( **deployment.litellm_params.model_dump(exclude_none=True) ).model_dump(exclude_none=True) - + # Add custom_llm_provider if deployment.litellm_params.custom_llm_provider: credentials["custom_llm_provider"] = ( @@ -5986,7 +5989,7 @@ class Router: )[0] else: credentials["custom_llm_provider"] = "openai" # default - + return credentials @overload diff --git a/litellm/types/utils.py b/litellm/types/utils.py index cb4d5c4d47f..3d92e78d494 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -731,6 +731,7 @@ API_ROUTE_TO_CALL_TYPES = { CallTypes.llm_passthrough_route, CallTypes.allm_passthrough_route, ], + "/v1/messages": [CallTypes.anthropic_messages], } diff --git a/tests/guardrails_tests/test_bedrock_guardrails.py b/tests/guardrails_tests/test_bedrock_guardrails.py index b0e192c00a7..1795ff23605 100644 --- a/tests/guardrails_tests/test_bedrock_guardrails.py +++ b/tests/guardrails_tests/test_bedrock_guardrails.py @@ -2,6 +2,7 @@ import sys import os import io, asyncio import pytest + sys.path.insert(0, os.path.abspath("../..")) import litellm from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrail @@ -9,11 +10,12 @@ from litellm.proxy._types import UserAPIKeyAuth from litellm.caching import DualCache from unittest.mock import MagicMock, AsyncMock, patch + @pytest.mark.asyncio async def test_bedrock_guardrails_pii_masking(): # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( guardrailIdentifier="wf0hkdb5x07f", guardrailVersion="DRAFT", @@ -25,14 +27,17 @@ async def test_bedrock_guardrails_pii_masking(): {"role": "user", "content": "Hello, my phone number is +1 412 555 1212"}, {"role": "assistant", "content": "Hello, how can I help you today?"}, {"role": "user", "content": "I need to cancel my order"}, - {"role": "user", "content": "ok, my credit card number is 1234-5678-9012-3456"}, + { + "role": "user", + "content": "ok, my credit card number is 1234-5678-9012-3456", + }, ], } response = await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) print("response after moderation hook", response) @@ -40,14 +45,17 @@ async def test_bedrock_guardrails_pii_masking(): assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE}" assert response["messages"][1]["content"] == "Hello, how can I help you today?" assert response["messages"][2]["content"] == "I need to cancel my order" - assert response["messages"][3]["content"] == "ok, my credit card number is {CREDIT_DEBIT_CARD_NUMBER}" + assert ( + response["messages"][3]["content"] + == "ok, my credit card number is {CREDIT_DEBIT_CARD_NUMBER}" + ) @pytest.mark.asyncio async def test_bedrock_guardrails_pii_masking_content_list(): # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( guardrailIdentifier="wf0hkdb5x07f", guardrailVersion="DRAFT", @@ -56,34 +64,41 @@ async def test_bedrock_guardrails_pii_masking_content_list(): request_data = { "model": "gpt-4o", "messages": [ - {"role": "user", "content": [ - {"type": "text", "text": "Hello, my phone number is +1 412 555 1212"}, - {"type": "text", "text": "what time is it?"}, - ]}, - {"role": "assistant", "content": "Hello, how can I help you today?"}, { "role": "user", - "content": "who is the president of the united states?" - } + "content": [ + { + "type": "text", + "text": "Hello, my phone number is +1 412 555 1212", + }, + {"type": "text", "text": "what time is it?"}, + ], + }, + {"role": "assistant", "content": "Hello, how can I help you today?"}, + {"role": "user", "content": "who is the president of the united states?"}, ], } response = await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) print(response) - + if response: # Only assert if response is not None # Verify that the list content is properly masked assert isinstance(response["messages"][0]["content"], list) - assert response["messages"][0]["content"][0]["text"] == "Hello, my phone number is {PHONE}" + assert ( + response["messages"][0]["content"][0]["text"] + == "Hello, my phone number is {PHONE}" + ) assert response["messages"][0]["content"][1]["text"] == "what time is it?" assert response["messages"][1]["content"] == "Hello, how can I help you today?" - assert response["messages"][2]["content"] == "who is the president of the united states?" - - + assert ( + response["messages"][2]["content"] + == "who is the president of the united states?" + ) @pytest.mark.asyncio @@ -92,10 +107,10 @@ async def test_bedrock_guardrails_block_messages_api(): Test that guardrails block messages API requests containing 'coffee' and raise the expected exception. """ from fastapi import HTTPException - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( guardrailIdentifier="ff6ujrregl1q", guardrailVersion="DRAFT", @@ -104,14 +119,17 @@ async def test_bedrock_guardrails_block_messages_api(): request_data = { "model": "claude-3-5-sonnet-20240620", "messages": [ - {"role": "user", "content": [ - {"type": "text", "text": "Hello, my phone number is +1 412 555 1212"}, - {"type": "text", "text": "what time is it?"}, - ]}, { "role": "user", - "content": "tell me about coffee" - } + "content": [ + { + "type": "text", + "text": "Hello, my phone number is +1 412 555 1212", + }, + {"type": "text", "text": "what time is it?"}, + ], + }, + {"role": "user", "content": "tell me about coffee"}, ], } @@ -122,13 +140,17 @@ async def test_bedrock_guardrails_block_messages_api(): call_type="anthropic_messages", cache=MagicMock(spec=DualCache), ) - + exception = exc_info.value assert exception.status_code == 400 detail = exception.detail assert isinstance(detail, dict) assert detail["error"] == "Violated guardrail policy" - assert detail["bedrock_guardrail_response"] == "Sorry, the model cannot answer this question. coffee guardrail applied " + assert ( + detail["bedrock_guardrail_response"] + == "Sorry, the model cannot answer this question. coffee guardrail applied " + ) + @pytest.mark.asyncio async def test_bedrock_guardrails_block_responses_api(): @@ -136,10 +158,10 @@ async def test_bedrock_guardrails_block_responses_api(): Test that guardrails block responses API requests containing 'coffee' and raise the expected exception. """ from fastapi import HTTPException - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( guardrailIdentifier="ff6ujrregl1q", guardrailVersion="DRAFT", @@ -158,14 +180,16 @@ async def test_bedrock_guardrails_block_responses_api(): call_type="responses", cache=MagicMock(spec=DualCache), ) - + exception = exc_info.value assert exception.status_code == 400 detail = exception.detail assert isinstance(detail, dict) assert detail["error"] == "Violated guardrail policy" - assert detail["bedrock_guardrail_response"] == "Sorry, the model cannot answer this question. coffee guardrail applied " - + assert ( + detail["bedrock_guardrail_response"] + == "Sorry, the model cannot answer this question. coffee guardrail applied " + ) @pytest.mark.asyncio @@ -182,7 +206,7 @@ async def test_bedrock_guardrails_with_streaming(): user_api_key_cache=mock_user_api_key_cache, premium_user=True, ) - + guardrail = BedrockGuardrail( guardrailIdentifier="ff6ujrregl1q", guardrailVersion="DRAFT", @@ -194,14 +218,9 @@ async def test_bedrock_guardrails_with_streaming(): request_data = { "model": "gpt-4o", - "messages": [ - { - "role": "user", - "content": "Hi I like coffee" - } - ], + "messages": [{"role": "user", "content": "Hi I like coffee"}], "stream": True, - "metadata": {"guardrails": ["bedrock-post-guard"]} + "metadata": {"guardrails": ["bedrock-post-guard"]}, } response = await litellm.acompletion( @@ -213,7 +232,7 @@ async def test_bedrock_guardrails_with_streaming(): response=response, request_data=request_data, ) - + async for chunk in response: print(chunk) @@ -231,7 +250,7 @@ async def test_bedrock_guardrails_with_streaming_no_violation(): user_api_key_cache=mock_user_api_key_cache, premium_user=True, ) - + guardrail = BedrockGuardrail( guardrailIdentifier="ff6ujrregl1q", guardrailVersion="DRAFT", @@ -241,17 +260,11 @@ async def test_bedrock_guardrails_with_streaming_no_violation(): litellm.callbacks.append(guardrail) - request_data = { "model": "gpt-4o", - "messages": [ - { - "role": "user", - "content": "hi" - } - ], + "messages": [{"role": "user", "content": "hi"}], "stream": True, - "metadata": {"guardrails": ["bedrock-post-guard"]} + "metadata": {"guardrails": ["bedrock-post-guard"]}, } response = await litellm.acompletion( @@ -263,11 +276,10 @@ async def test_bedrock_guardrails_with_streaming_no_violation(): response=response, request_data=request_data, ) - - + async for chunk in response: print(chunk) - + @pytest.mark.asyncio async def test_bedrock_guardrails_streaming_request_body_mock(): @@ -277,7 +289,7 @@ async def test_bedrock_guardrails_streaming_request_body_mock(): from litellm.proxy._types import UserAPIKeyAuth from litellm.caching import DualCache from litellm.types.guardrails import GuardrailEventHooks - + # Create mock objects mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) @@ -297,79 +309,68 @@ async def test_bedrock_guardrails_streaming_request_body_mock(): litellm.Choices( index=0, message=litellm.Message( - role="assistant", - content="The capital of Spain is Madrid." + role="assistant", content="The capital of Spain is Madrid." ), - finish_reason="stop" + finish_reason="stop", ) ], created=1234567890, model="gpt-4o", - object="chat.completion" + object="chat.completion", ) # Mock Bedrock API response mock_bedrock_response = MagicMock() mock_bedrock_response.status_code = 200 - mock_bedrock_response.json.return_value = { - "action": "NONE", - "outputs": [] - } + mock_bedrock_response.json.return_value = {"action": "NONE", "outputs": []} # Patch the async_handler.post method to capture the request body - with patch.object(guardrail, 'async_handler') as mock_async_handler: + with patch.object(guardrail, "async_handler") as mock_async_handler: mock_async_handler.post = AsyncMock(return_value=mock_bedrock_response) - + # Test data - simulating request data and assembled response request_data = { "model": "gpt-4o", - "messages": [ - { - "role": "user", - "content": "what's the capital of spain?" - } - ], + "messages": [{"role": "user", "content": "what's the capital of spain?"}], "stream": True, - "metadata": {"guardrails": ["bedrock-post-guard"]} + "metadata": {"guardrails": ["bedrock-post-guard"]}, } # Call the method that should make the Bedrock API request await guardrail.make_bedrock_api_request( - source="OUTPUT", - response=mock_response, - request_data=request_data + source="OUTPUT", response=mock_response, request_data=request_data ) # Verify the API call was made mock_async_handler.post.assert_called_once() - + # Get the request data that was passed call_args = mock_async_handler.post.call_args - + # The data should be in the 'data' parameter of the prepared request # We need to parse the JSON from the prepared request body - prepared_request_body = call_args.kwargs.get('data') - + prepared_request_body = call_args.kwargs.get("data") + # Parse the JSON body if isinstance(prepared_request_body, bytes): - actual_body = json.loads(prepared_request_body.decode('utf-8')) + actual_body = json.loads(prepared_request_body.decode("utf-8")) else: actual_body = json.loads(prepared_request_body) - + # Expected body based on the convert_to_bedrock_format method behavior expected_body = { - 'source': 'OUTPUT', - 'content': [ - {'text': {'text': 'The capital of Spain is Madrid.'}} - ] + "source": "OUTPUT", + "content": [{"text": {"text": "The capital of Spain is Madrid."}}], } - + print("Actual Bedrock request body:", json.dumps(actual_body, indent=2)) print("Expected Bedrock request body:", json.dumps(expected_body, indent=2)) - + # Assert the request body matches exactly - assert actual_body == expected_body, f"Request body mismatch. Expected: {expected_body}, Got: {actual_body}" - + assert ( + actual_body == expected_body + ), f"Request body mismatch. Expected: {expected_body}, Got: {actual_body}" + @pytest.mark.asyncio async def test_bedrock_guardrail_aws_param_persistence(): @@ -387,23 +388,31 @@ async def test_bedrock_guardrail_aws_param_persistence(): guardrail_name="bedrock-post-guard", ) - with patch.object(guardrail, "get_credentials", wraps=guardrail.get_credentials) as mock_get_creds: + with patch.object( + guardrail, "get_credentials", wraps=guardrail.get_credentials + ) as mock_get_creds: for i in range(3): request_data = { "model": "gpt-4o", - "messages": [ - {"role": "user", "content": f"request {i}"} - ], + "messages": [{"role": "user", "content": f"request {i}"}], "stream": False, - "metadata": {"guardrails": ["bedrock-post-guard"]} + "metadata": {"guardrails": ["bedrock-post-guard"]}, } - with patch.object(guardrail.async_handler, "post", new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: # Configure the mock response properly mock_response = AsyncMock() mock_response.status_code = 200 - mock_response.json = MagicMock(return_value={"action": "NONE", "outputs": []}) + mock_response.json = MagicMock( + return_value={"action": "NONE", "outputs": []} + ) mock_post.return_value = mock_response - await guardrail.make_bedrock_api_request(source="INPUT", messages=request_data.get("messages"), request_data=request_data) + await guardrail.make_bedrock_api_request( + source="INPUT", + messages=request_data.get("messages"), + request_data=request_data, + ) assert mock_get_creds.call_count == 3 for call in mock_get_creds.call_args_list: @@ -413,114 +422,124 @@ async def test_bedrock_guardrail_aws_param_persistence(): assert kwargs["aws_secret_access_key"] == "test-secret-key" assert kwargs["aws_region_name"] == "us-east-1" + @pytest.mark.asyncio async def test_bedrock_guardrail_blocked_vs_anonymized_actions(): """Test that BLOCKED actions raise exceptions but ANONYMIZED actions do not""" from unittest.mock import MagicMock - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrail - from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrailResponse - - guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockGuardrail, ) - + from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockGuardrailResponse, + ) + + guardrail = BedrockGuardrail( + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" + ) + # Test 1: ANONYMIZED action should NOT raise exception anonymized_response: BedrockGuardrailResponse = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "Hello, my phone number is {PHONE}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [{ - "type": "PHONE", - "match": "+1 412 555 1212", - "action": "ANONYMIZED" - }] + "outputs": [{"text": "Hello, my phone number is {PHONE}"}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "PHONE", + "match": "+1 412 555 1212", + "action": "ANONYMIZED", + } + ] + } } - }] + ], } - - should_raise = guardrail._should_raise_guardrail_blocked_exception(anonymized_response) + + should_raise = guardrail._should_raise_guardrail_blocked_exception( + anonymized_response + ) assert should_raise is False, "ANONYMIZED actions should not raise exceptions" - + # Test 2: BLOCKED action should raise exception blocked_response: BedrockGuardrailResponse = { - "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "I can't provide that information." - }], - "assessments": [{ - "topicPolicy": { - "topics": [{ - "name": "Sensitive Topic", - "type": "DENY", - "action": "BLOCKED" - }] + "action": "GUARDRAIL_INTERVENED", + "outputs": [{"text": "I can't provide that information."}], + "assessments": [ + { + "topicPolicy": { + "topics": [ + {"name": "Sensitive Topic", "type": "DENY", "action": "BLOCKED"} + ] + } } - }] + ], } - + should_raise = guardrail._should_raise_guardrail_blocked_exception(blocked_response) assert should_raise is True, "BLOCKED actions should raise exceptions" - + # Test 3: Mixed actions - should raise if ANY action is BLOCKED mixed_response: BedrockGuardrailResponse = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "I can't provide that information." - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [{ - "type": "PHONE", - "match": "+1 412 555 1212", - "action": "ANONYMIZED" - }] - }, - "topicPolicy": { - "topics": [{ - "name": "Blocked Topic", - "type": "DENY", - "action": "BLOCKED" - }] + "outputs": [{"text": "I can't provide that information."}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "PHONE", + "match": "+1 412 555 1212", + "action": "ANONYMIZED", + } + ] + }, + "topicPolicy": { + "topics": [ + {"name": "Blocked Topic", "type": "DENY", "action": "BLOCKED"} + ] + }, } - }] + ], } - + should_raise = guardrail._should_raise_guardrail_blocked_exception(mixed_response) - assert should_raise is True, "Mixed actions with any BLOCKED should raise exceptions" - + assert ( + should_raise is True + ), "Mixed actions with any BLOCKED should raise exceptions" + # Test 4: NONE action should not raise exception none_response: BedrockGuardrailResponse = { "action": "NONE", "outputs": [], - "assessments": [] + "assessments": [], } - + should_raise = guardrail._should_raise_guardrail_blocked_exception(none_response) assert should_raise is False, "NONE actions should not raise exceptions" - + # Test 5: Test other policy types with BLOCKED actions content_blocked_response: BedrockGuardrailResponse = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "I can't provide that information." - }], - "assessments": [{ - "contentPolicy": { - "filters": [{ - "type": "VIOLENCE", - "confidence": "HIGH", - "action": "BLOCKED" - }] + "outputs": [{"text": "I can't provide that information."}], + "assessments": [ + { + "contentPolicy": { + "filters": [ + {"type": "VIOLENCE", "confidence": "HIGH", "action": "BLOCKED"} + ] + } } - }] + ], } - - should_raise = guardrail._should_raise_guardrail_blocked_exception(content_blocked_response) - assert should_raise is True, "Content policy BLOCKED actions should raise exceptions" + + should_raise = guardrail._should_raise_guardrail_blocked_exception( + content_blocked_response + ) + assert ( + should_raise is True + ), "Content policy BLOCKED actions should raise exceptions" @pytest.mark.asyncio @@ -529,10 +548,10 @@ async def test_bedrock_guardrail_masking_with_anonymized_response(): from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth from litellm.caching import DualCache - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT", @@ -544,18 +563,20 @@ async def test_bedrock_guardrail_masking_with_anonymized_response(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "Hello, my phone number is {PHONE}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [{ - "type": "PHONE", - "match": "+1 412 555 1212", - "action": "ANONYMIZED" - }] + "outputs": [{"text": "Hello, my phone number is {PHONE}"}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "PHONE", + "match": "+1 412 555 1212", + "action": "ANONYMIZED", + } + ] + } } - }] + ], } request_data = { @@ -566,21 +587,28 @@ async def test_bedrock_guardrail_masking_with_anonymized_response(): } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # This should NOT raise an exception since action is ANONYMIZED try: response = await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) # Should succeed and return data with masked content assert response is not None - assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE}" + assert ( + response["messages"][0]["content"] + == "Hello, my phone number is {PHONE}" + ) except Exception as e: - pytest.fail(f"Should not raise exception for ANONYMIZED actions, but got: {e}") + pytest.fail( + f"Should not raise exception for ANONYMIZED actions, but got: {e}" + ) @pytest.mark.asyncio @@ -588,10 +616,10 @@ async def test_bedrock_guardrail_uses_masked_output_without_masking_flags(): """Test that masked output from guardrails is used even when masking flags are not enabled""" from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Create guardrail WITHOUT masking flags enabled guardrail = BedrockGuardrail( guardrailIdentifier="test-guardrail", @@ -604,48 +632,56 @@ async def test_bedrock_guardrail_uses_masked_output_without_masking_flags(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "Hello, my phone number is {PHONE} and email is {EMAIL}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [ - { - "type": "PHONE", - "match": "+1 412 555 1212", - "action": "ANONYMIZED" - }, - { - "type": "EMAIL", - "match": "user@example.com", - "action": "ANONYMIZED" - } - ] + "outputs": [{"text": "Hello, my phone number is {PHONE} and email is {EMAIL}"}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "PHONE", + "match": "+1 412 555 1212", + "action": "ANONYMIZED", + }, + { + "type": "EMAIL", + "match": "user@example.com", + "action": "ANONYMIZED", + }, + ] + } } - }] + ], } request_data = { "model": "gpt-4o", "messages": [ - {"role": "user", "content": "Hello, my phone number is +1 412 555 1212 and email is user@example.com"}, + { + "role": "user", + "content": "Hello, my phone number is +1 412 555 1212 and email is user@example.com", + }, ], } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # This should use the masked output even without masking flags response = await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) - + # Should use the masked content from guardrail output assert response is not None - assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE} and email is {EMAIL}" + assert ( + response["messages"][0]["content"] + == "Hello, my phone number is {PHONE} and email is {EMAIL}" + ) print("✅ Masked output was applied even without masking flags enabled") @@ -654,10 +690,10 @@ async def test_bedrock_guardrail_response_pii_masking_non_streaming(): """Test that PII masking is applied to response content in non-streaming scenarios""" from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Create guardrail with response masking enabled guardrail = BedrockGuardrail( guardrailIdentifier="test-guardrail", @@ -669,25 +705,29 @@ async def test_bedrock_guardrail_response_pii_masking_non_streaming(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "My credit card number is {CREDIT_DEBIT_CARD_NUMBER} and my phone is {PHONE}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [ - { - "type": "CREDIT_DEBIT_CARD_NUMBER", - "match": "1234-5678-9012-3456", - "action": "ANONYMIZED" - }, - { - "type": "PHONE", - "match": "+1 412 555 1212", - "action": "ANONYMIZED" - } - ] + "outputs": [ + { + "text": "My credit card number is {CREDIT_DEBIT_CARD_NUMBER} and my phone is {PHONE}" } - }] + ], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "CREDIT_DEBIT_CARD_NUMBER", + "match": "1234-5678-9012-3456", + "action": "ANONYMIZED", + }, + { + "type": "PHONE", + "match": "+1 412 555 1212", + "action": "ANONYMIZED", + }, + ] + } + } + ], } # Create a mock response that contains PII @@ -697,15 +737,15 @@ async def test_bedrock_guardrail_response_pii_masking_non_streaming(): litellm.Choices( index=0, message=litellm.Message( - role="assistant", - content="My credit card number is 1234-5678-9012-3456 and my phone is +1 412 555 1212" + role="assistant", + content="My credit card number is 1234-5678-9012-3456 and my phone is +1 412 555 1212", ), - finish_reason="stop" + finish_reason="stop", ) ], created=1234567890, model="gpt-4o", - object="chat.completion" + object="chat.completion", ) request_data = { @@ -716,18 +756,23 @@ async def test_bedrock_guardrail_response_pii_masking_non_streaming(): } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Call the post-call success hook await guardrail.async_post_call_success_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - response=mock_response + response=mock_response, ) - + # Verify that the response content was masked - assert mock_response.choices[0].message.content == "My credit card number is {CREDIT_DEBIT_CARD_NUMBER} and my phone is {PHONE}" + assert ( + mock_response.choices[0].message.content + == "My credit card number is {CREDIT_DEBIT_CARD_NUMBER} and my phone is {PHONE}" + ) print("✓ Non-streaming response PII masking test passed") @@ -737,10 +782,10 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth from litellm.types.utils import ModelResponseStream - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Create guardrail with response masking enabled guardrail = BedrockGuardrail( guardrailIdentifier="test-guardrail", @@ -752,25 +797,25 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "Sure! My email is {EMAIL} and SSN is {US_SSN}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [ - { - "type": "EMAIL", - "match": "john@example.com", - "action": "ANONYMIZED" - }, - { - "type": "US_SSN", - "match": "123-45-6789", - "action": "ANONYMIZED" - } - ] + "outputs": [{"text": "Sure! My email is {EMAIL} and SSN is {US_SSN}"}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "EMAIL", + "match": "john@example.com", + "action": "ANONYMIZED", + }, + { + "type": "US_SSN", + "match": "123-45-6789", + "action": "ANONYMIZED", + }, + ] + } } - }] + ], } # Create mock streaming chunks @@ -782,25 +827,27 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): litellm.utils.StreamingChoices( index=0, delta=litellm.utils.Delta(content="Sure! My email is "), - finish_reason=None + finish_reason=None, ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" + object="chat.completion.chunk", ), ModelResponseStream( id="test-id", choices=[ litellm.utils.StreamingChoices( index=0, - delta=litellm.utils.Delta(content="john@example.com and SSN is "), - finish_reason=None + delta=litellm.utils.Delta( + content="john@example.com and SSN is " + ), + finish_reason=None, ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" + object="chat.completion.chunk", ), ModelResponseStream( id="test-id", @@ -808,13 +855,13 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): litellm.utils.StreamingChoices( index=0, delta=litellm.utils.Delta(content="123-45-6789"), - finish_reason="stop" + finish_reason="stop", ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" - ) + object="chat.completion.chunk", + ), ] for chunk in chunks: yield chunk @@ -828,32 +875,37 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Call the streaming hook masked_stream = guardrail.async_post_call_streaming_iterator_hook( user_api_key_dict=mock_user_api_key_dict, response=mock_streaming_response(), - request_data=request_data + request_data=request_data, ) - + # Collect all chunks from the masked stream masked_chunks = [] async for chunk in masked_stream: masked_chunks.append(chunk) - + # Verify that we got chunks back assert len(masked_chunks) > 0 - + # Reconstruct the full response from chunks to verify masking full_content = "" for chunk in masked_chunks: - if hasattr(chunk, 'choices') and chunk.choices: - if hasattr(chunk.choices[0], 'delta') and chunk.choices[0].delta: - if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: + if hasattr(chunk, "choices") and chunk.choices: + if hasattr(chunk.choices[0], "delta") and chunk.choices[0].delta: + if ( + hasattr(chunk.choices[0].delta, "content") + and chunk.choices[0].delta.content + ): full_content += chunk.choices[0].delta.content - + # Verify that the reconstructed content contains the masked PII assert "Sure! My email is {EMAIL} and SSN is {US_SSN}" == full_content print("✓ Streaming response PII masking test passed") @@ -862,64 +914,70 @@ async def test_bedrock_guardrail_response_pii_masking_streaming(): @pytest.mark.asyncio async def test_convert_to_bedrock_format_input_source(): """Test convert_to_bedrock_format with INPUT source and mock messages""" - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrail - from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockRequest + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockGuardrail, + ) + from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockRequest, + ) from unittest.mock import patch - + # Create the guardrail instance guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) - + # Mock messages mock_messages = [ {"role": "user", "content": "Hello, how are you?"}, {"role": "assistant", "content": "I'm doing well, thank you!"}, - {"role": "user", "content": [ - {"type": "text", "text": "What's the weather like?"}, - {"type": "text", "text": "Is it sunny today?"} - ]} + { + "role": "user", + "content": [ + {"type": "text", "text": "What's the weather like?"}, + {"type": "text", "text": "Is it sunny today?"}, + ], + }, ] - + # Call the method - result = guardrail.convert_to_bedrock_format( - source="INPUT", - messages=mock_messages - ) - + result = guardrail.convert_to_bedrock_format(source="INPUT", messages=mock_messages) + # Verify the result structure assert isinstance(result, dict) assert result.get("source") == "INPUT" assert "content" in result assert isinstance(result.get("content"), list) - + # Verify content items expected_content_items = [ {"text": {"text": "Hello, how are you?"}}, {"text": {"text": "I'm doing well, thank you!"}}, {"text": {"text": "What's the weather like?"}}, - {"text": {"text": "Is it sunny today?"}} + {"text": {"text": "Is it sunny today?"}}, ] - + assert result.get("content") == expected_content_items print("✅ INPUT source test passed - result:", result) -@pytest.mark.asyncio +@pytest.mark.asyncio async def test_convert_to_bedrock_format_output_source(): """Test convert_to_bedrock_format with OUTPUT source and mock ModelResponse""" - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrail - from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockRequest + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockGuardrail, + ) + from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockRequest, + ) import litellm from unittest.mock import patch - - # Create the guardrail instance + + # Create the guardrail instance guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) - + # Mock ModelResponse mock_response = litellm.ModelResponse( id="test-response-id", @@ -927,43 +985,40 @@ async def test_convert_to_bedrock_format_output_source(): litellm.Choices( index=0, message=litellm.Message( - role="assistant", - content="This is a test response from the model." + role="assistant", content="This is a test response from the model." ), - finish_reason="stop" + finish_reason="stop", ), litellm.Choices( - index=1, + index=1, message=litellm.Message( - role="assistant", - content="This is a second choice response." + role="assistant", content="This is a second choice response." ), - finish_reason="stop" - ) + finish_reason="stop", + ), ], created=1234567890, model="gpt-4o", - object="chat.completion" + object="chat.completion", ) - + # Call the method result = guardrail.convert_to_bedrock_format( - source="OUTPUT", - response=mock_response + source="OUTPUT", response=mock_response ) - + # Verify the result structure assert isinstance(result, dict) assert result.get("source") == "OUTPUT" assert "content" in result assert isinstance(result.get("content"), list) - + # Verify content items - should contain both choice contents expected_content_items = [ {"text": {"text": "This is a test response from the model."}}, - {"text": {"text": "This is a second choice response."}} + {"text": {"text": "This is a second choice response."}}, ] - + assert result.get("content") == expected_content_items print("✅ OUTPUT source test passed - result:", result) @@ -975,16 +1030,15 @@ async def test_convert_to_bedrock_format_post_call_streaming_hook(): from litellm.proxy._types import UserAPIKeyAuth from litellm.types.utils import ModelResponseStream import litellm - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Create guardrail instance guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) - + # Mock streaming chunks that contain PII async def mock_streaming_response(): chunks = [ @@ -994,12 +1048,12 @@ async def test_convert_to_bedrock_format_post_call_streaming_hook(): litellm.utils.StreamingChoices( index=0, delta=litellm.utils.Delta(content="My email is "), - finish_reason=None + finish_reason=None, ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" + object="chat.completion.chunk", ), ModelResponseStream( id="test-id", @@ -1007,99 +1061,121 @@ async def test_convert_to_bedrock_format_post_call_streaming_hook(): litellm.utils.StreamingChoices( index=0, delta=litellm.utils.Delta(content="john@example.com"), - finish_reason="stop" + finish_reason="stop", ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" - ) + object="chat.completion.chunk", + ), ] for chunk in chunks: yield chunk - + # Mock Bedrock API response with PII masking mock_bedrock_response = MagicMock() mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "My email is {EMAIL}" - }], - "assessments": [{ - "sensitiveInformationPolicy": { - "piiEntities": [{ - "type": "EMAIL", - "match": "john@example.com", - "action": "ANONYMIZED" - }] + "outputs": [{"text": "My email is {EMAIL}"}], + "assessments": [ + { + "sensitiveInformationPolicy": { + "piiEntities": [ + { + "type": "EMAIL", + "match": "john@example.com", + "action": "ANONYMIZED", + } + ] + } } - }] + ], } - + request_data = { "model": "gpt-4o", - "messages": [ - {"role": "user", "content": "What's your email?"} - ], - "stream": True + "messages": [{"role": "user", "content": "What's your email?"}], + "stream": True, } - + # Track which bedrock API calls were made bedrock_calls = [] - + # Mock the make_bedrock_api_request method to track calls - async def mock_make_bedrock_api_request(source, messages=None, response=None, request_data=None): - bedrock_calls.append({ - "source": source, - "messages": messages, - "response": response, - "request_data": request_data - }) + async def mock_make_bedrock_api_request( + source, messages=None, response=None, request_data=None + ): + bedrock_calls.append( + { + "source": source, + "messages": messages, + "response": response, + "request_data": request_data, + } + ) # Return the mock bedrock response - from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrailResponse + from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( + BedrockGuardrailResponse, + ) + return BedrockGuardrailResponse(**mock_bedrock_response.json()) - + # Patch the bedrock API request method - with patch.object(guardrail, 'make_bedrock_api_request', side_effect=mock_make_bedrock_api_request): - + with patch.object( + guardrail, "make_bedrock_api_request", side_effect=mock_make_bedrock_api_request + ): + # Call the streaming hook result_generator = guardrail.async_post_call_streaming_iterator_hook( user_api_key_dict=mock_user_api_key_dict, response=mock_streaming_response(), - request_data=request_data + request_data=request_data, ) - + # Collect all chunks from the result result_chunks = [] async for chunk in result_generator: result_chunks.append(chunk) - + # Verify bedrock API calls were made - assert len(bedrock_calls) == 2, f"Expected 2 bedrock calls (INPUT and OUTPUT), got {len(bedrock_calls)}" - + assert ( + len(bedrock_calls) == 2 + ), f"Expected 2 bedrock calls (INPUT and OUTPUT), got {len(bedrock_calls)}" + # Find the OUTPUT call output_calls = [call for call in bedrock_calls if call["source"] == "OUTPUT"] - assert len(output_calls) == 1, f"Expected 1 OUTPUT call, got {len(output_calls)}" - + assert ( + len(output_calls) == 1 + ), f"Expected 1 OUTPUT call, got {len(output_calls)}" + output_call = output_calls[0] assert output_call["source"] == "OUTPUT" assert output_call["response"] is not None assert output_call["messages"] is None # OUTPUT calls don't need messages - + # Verify that the response content was masked # The streaming chunks should now contain the masked content full_content = "" for chunk in result_chunks: - if hasattr(chunk, 'choices') and chunk.choices: - if hasattr(chunk.choices[0], 'delta') and chunk.choices[0].delta.content: + if hasattr(chunk, "choices") and chunk.choices: + if ( + hasattr(chunk.choices[0], "delta") + and chunk.choices[0].delta.content + ): full_content += chunk.choices[0].delta.content - + # The content should be masked (contains {EMAIL} instead of john@example.com) - assert "{EMAIL}" in full_content, f"Expected masked content with {{EMAIL}}, got: {full_content}" - assert "john@example.com" not in full_content, f"Original email should be masked, got: {full_content}" - - print("✅ Post-call streaming hook test passed - OUTPUT source used for masking") + assert ( + "{EMAIL}" in full_content + ), f"Expected masked content with {{EMAIL}}, got: {full_content}" + assert ( + "john@example.com" not in full_content + ), f"Original email should be masked, got: {full_content}" + + print( + "✅ Post-call streaming hook test passed - OUTPUT source used for masking" + ) print(f"✅ Bedrock calls made: {[call['source'] for call in bedrock_calls]}") print(f"✅ Final masked content: {full_content}") @@ -1110,13 +1186,12 @@ async def test_bedrock_guardrail_blocked_action_shows_output_text(): from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth from fastapi import HTTPException - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) # Mock the Bedrock API response with BLOCKED action and output text @@ -1124,20 +1199,16 @@ async def test_bedrock_guardrail_blocked_action_shows_output_text(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [ + "outputs": [{"text": "this violates litellm corporate guardrail policy"}], + "assessments": [ { - "text": "this violates litellm corporate guardrail policy" + "topicPolicy": { + "topics": [ + {"name": "Sensitive Topic", "type": "DENY", "action": "BLOCKED"} + ] + } } ], - "assessments": [{ - "topicPolicy": { - "topics": [{ - "name": "Sensitive Topic", - "type": "DENY", - "action": "BLOCKED" - }] - } - }] } request_data = { @@ -1148,32 +1219,36 @@ async def test_bedrock_guardrail_blocked_action_shows_output_text(): } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # This should raise HTTPException due to BLOCKED action with pytest.raises(HTTPException) as exc_info: await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) - + # Verify the exception details exception = exc_info.value assert exception.status_code == 400 assert "detail" in exception.__dict__ - + # Check that the detail contains the expected structure detail = exception.detail assert isinstance(detail, dict) assert detail["error"] == "Violated guardrail policy" - + # Verify that the output text from both outputs is included expected_output_text = "this violates litellm corporate guardrail policy" assert detail["bedrock_guardrail_response"] == expected_output_text - - print("✅ BLOCKED action HTTPException test passed - output text properly included") + + print( + "✅ BLOCKED action HTTPException test passed - output text properly included" + ) @pytest.mark.asyncio @@ -1182,13 +1257,12 @@ async def test_bedrock_guardrail_blocked_action_empty_outputs(): from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth from fastapi import HTTPException - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) # Mock the Bedrock API response with BLOCKED action but no outputs @@ -1197,15 +1271,15 @@ async def test_bedrock_guardrail_blocked_action_empty_outputs(): mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", "outputs": [], # Empty outputs - "assessments": [{ - "contentPolicy": { - "filters": [{ - "type": "VIOLENCE", - "confidence": "HIGH", - "action": "BLOCKED" - }] + "assessments": [ + { + "contentPolicy": { + "filters": [ + {"type": "VIOLENCE", "confidence": "HIGH", "action": "BLOCKED"} + ] + } } - }] + ], } request_data = { @@ -1216,27 +1290,29 @@ async def test_bedrock_guardrail_blocked_action_empty_outputs(): } # Patch the async_handler.post method - with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # This should raise HTTPException due to BLOCKED action with pytest.raises(HTTPException) as exc_info: await guardrail.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) - + # Verify the exception details exception = exc_info.value assert exception.status_code == 400 - + # Check that the detail contains the expected structure with empty output text detail = exception.detail assert isinstance(detail, dict) assert detail["error"] == "Violated guardrail policy" assert detail["bedrock_guardrail_response"] == "" # Empty string for no outputs - + print("✅ BLOCKED action with empty outputs test passed") @@ -1246,15 +1322,15 @@ async def test_bedrock_guardrail_disable_exception_on_block_non_streaming(): from unittest.mock import AsyncMock, MagicMock, patch from litellm.proxy._types import UserAPIKeyAuth from fastapi import HTTPException - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Test 1: disable_exception_on_block=False (default) - should raise exception guardrail_default = BedrockGuardrail( guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT", - disable_exception_on_block=False + disable_exception_on_block=False, ) # Mock the Bedrock API response with BLOCKED action @@ -1262,18 +1338,16 @@ async def test_bedrock_guardrail_disable_exception_on_block_non_streaming(): mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "I can't provide that information." - }], - "assessments": [{ - "topicPolicy": { - "topics": [{ - "name": "Sensitive Topic", - "type": "DENY", - "action": "BLOCKED" - }] + "outputs": [{"text": "I can't provide that information."}], + "assessments": [ + { + "topicPolicy": { + "topics": [ + {"name": "Sensitive Topic", "type": "DENY", "action": "BLOCKED"} + ] + } } - }] + ], } request_data = { @@ -1284,17 +1358,19 @@ async def test_bedrock_guardrail_disable_exception_on_block_non_streaming(): } # Patch the async_handler.post method - with patch.object(guardrail_default.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail_default.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Should raise HTTPException when disable_exception_on_block=False with pytest.raises(HTTPException) as exc_info: await guardrail_default.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) - + # Verify the exception details exception = exc_info.value assert exception.status_code == 400 @@ -1304,24 +1380,28 @@ async def test_bedrock_guardrail_disable_exception_on_block_non_streaming(): guardrail_disabled = BedrockGuardrail( guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT", - disable_exception_on_block=True + disable_exception_on_block=True, ) - with patch.object(guardrail_disabled.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail_disabled.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Should NOT raise exception when disable_exception_on_block=True try: response = await guardrail_disabled.async_moderation_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, - call_type="completion" + call_type="completion", ) # Should succeed and return data (even though content was blocked) assert response is not None print("✅ No exception raised when disable_exception_on_block=True") except Exception as e: - pytest.fail(f"Should not raise exception when disable_exception_on_block=True, but got: {e}") + pytest.fail( + f"Should not raise exception when disable_exception_on_block=True, but got: {e}" + ) @pytest.mark.asyncio @@ -1332,10 +1412,10 @@ async def test_bedrock_guardrail_disable_exception_on_block_streaming(): from litellm.types.utils import ModelResponseStream from fastapi import HTTPException import litellm - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Mock streaming chunks that would normally trigger a block async def mock_streaming_response(): chunks = [ @@ -1344,13 +1424,15 @@ async def test_bedrock_guardrail_disable_exception_on_block_streaming(): choices=[ litellm.utils.StreamingChoices( index=0, - delta=litellm.utils.Delta(content="Here's how to make explosives: "), - finish_reason=None + delta=litellm.utils.Delta( + content="Here's how to make explosives: " + ), + finish_reason=None, ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" + object="chat.completion.chunk", ), ModelResponseStream( id="test-id", @@ -1358,62 +1440,62 @@ async def test_bedrock_guardrail_disable_exception_on_block_streaming(): litellm.utils.StreamingChoices( index=0, delta=litellm.utils.Delta(content="step 1, step 2..."), - finish_reason="stop" + finish_reason="stop", ) ], created=1234567890, model="gpt-4o", - object="chat.completion.chunk" - ) + object="chat.completion.chunk", + ), ] for chunk in chunks: yield chunk - + # Mock Bedrock API response with BLOCKED action mock_bedrock_response = MagicMock() mock_bedrock_response.status_code = 200 mock_bedrock_response.json.return_value = { "action": "GUARDRAIL_INTERVENED", - "outputs": [{ - "text": "I can't provide that information." - }], - "assessments": [{ - "contentPolicy": { - "filters": [{ - "type": "VIOLENCE", - "confidence": "HIGH", - "action": "BLOCKED" - }] + "outputs": [{"text": "I can't provide that information."}], + "assessments": [ + { + "contentPolicy": { + "filters": [ + {"type": "VIOLENCE", "confidence": "HIGH", "action": "BLOCKED"} + ] + } } - }] + ], } - + request_data = { "model": "gpt-4o", - "messages": [ - {"role": "user", "content": "Tell me how to make explosives"} - ], - "stream": True + "messages": [{"role": "user", "content": "Tell me how to make explosives"}], + "stream": True, } # Test 1: disable_exception_on_block=False (default) - should raise exception guardrail_default = BedrockGuardrail( guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT", - disable_exception_on_block=False + disable_exception_on_block=False, ) - with patch.object(guardrail_default.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail_default.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Should raise exception during streaming processing with pytest.raises(HTTPException): - result_generator = guardrail_default.async_post_call_streaming_iterator_hook( - user_api_key_dict=mock_user_api_key_dict, - response=mock_streaming_response(), - request_data=request_data + result_generator = ( + guardrail_default.async_post_call_streaming_iterator_hook( + user_api_key_dict=mock_user_api_key_dict, + response=mock_streaming_response(), + request_data=request_data, + ) ) - + # Try to consume the generator - should raise exception async for chunk in result_generator: pass @@ -1422,31 +1504,40 @@ async def test_bedrock_guardrail_disable_exception_on_block_streaming(): guardrail_disabled = BedrockGuardrail( guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT", - disable_exception_on_block=True + disable_exception_on_block=True, ) - with patch.object(guardrail_disabled.async_handler, 'post', new_callable=AsyncMock) as mock_post: + with patch.object( + guardrail_disabled.async_handler, "post", new_callable=AsyncMock + ) as mock_post: mock_post.return_value = mock_bedrock_response - + # Should NOT raise exception when disable_exception_on_block=True try: - result_generator = guardrail_disabled.async_post_call_streaming_iterator_hook( - user_api_key_dict=mock_user_api_key_dict, - response=mock_streaming_response(), - request_data=request_data + result_generator = ( + guardrail_disabled.async_post_call_streaming_iterator_hook( + user_api_key_dict=mock_user_api_key_dict, + response=mock_streaming_response(), + request_data=request_data, + ) ) - + # Consume the generator - should succeed without exceptions result_chunks = [] async for chunk in result_generator: result_chunks.append(chunk) - + # Should have received chunks back even though content was blocked assert len(result_chunks) > 0 - print("✅ Streaming completed without exception when disable_exception_on_block=True") - + print( + "✅ Streaming completed without exception when disable_exception_on_block=True" + ) + except Exception as e: - pytest.fail(f"Should not raise exception when disable_exception_on_block=True in streaming, but got: {e}") + pytest.fail( + f"Should not raise exception when disable_exception_on_block=True in streaming, but got: {e}" + ) + @pytest.mark.asyncio async def test_bedrock_guardrail_post_call_success_hook_no_output_text(): @@ -1455,16 +1546,15 @@ async def test_bedrock_guardrail_post_call_success_hook_no_output_text(): from litellm.proxy._types import UserAPIKeyAuth from litellm.types.utils import ModelResponseStream import litellm - + # Create proper mock objects mock_user_api_key_dict = UserAPIKeyAuth() - + # Create guardrail instance guardrail = BedrockGuardrail( - guardrailIdentifier="test-guardrail", - guardrailVersion="DRAFT" + guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT" ) - + # Create a ModelResponse with tool calls (no text content) # This simulates a response where the LLM is making a tool call mock_response = litellm.ModelResponse( @@ -1479,34 +1569,33 @@ async def test_bedrock_guardrail_post_call_success_hook_no_output_text(): litellm.utils.ChatCompletionMessageToolCall( id="tooluse_kZJMlvQmRJ6eAyJE5GIl7Q", function=litellm.utils.Function( - name="top_song", - arguments='{"sign": "WZPZ"}' + name="top_song", arguments='{"sign": "WZPZ"}' ), - type="function" + type="function", ) - ] + ], ), - finish_reason="tool_calls" + finish_reason="tool_calls", ) ], created=1234567890, model="gpt-4o", - object="chat.completion" + object="chat.completion", ) - + data = { "model": "gpt-4o", "messages": [ {"role": "user", "content": "Hello"}, ], - } + } mock_user_api_key_dict = UserAPIKeyAuth() result = await guardrail.async_post_call_success_hook( data=data, - response=mock_response, + response=mock_response, user_api_key_dict=mock_user_api_key_dict, ) # If no error is raised and result is None, then the test passes assert result is None - print("✅ No output text in response test passed") \ No newline at end of file + print("✅ No output text in response test passed") diff --git a/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py b/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py index c04f825e362..cc76be08178 100644 --- a/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py +++ b/tests/test_litellm/llms/openai/responses/test_openai_responses_guardrail_handler.py @@ -7,7 +7,7 @@ with guardrail transformations. import os import sys -from typing import Any, List, Optional, Tuple +from typing import Any, List, Literal, Optional, Tuple from unittest.mock import AsyncMock, MagicMock import pytest @@ -16,6 +16,8 @@ sys.path.insert( 0, os.path.abspath("../../../../../..") ) # Adds the parent directory to the system path +from fastapi import HTTPException + from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.llms import get_guardrail_translation_mapping from litellm.llms.openai.responses.guardrail_translation.handler import ( @@ -27,12 +29,27 @@ from litellm.types.utils import CallTypes class MockGuardrail(CustomGuardrail): - """Mock guardrail for testing that transforms text""" + """Mock guardrail for testing that transforms text for requests and blocks responses""" async def apply_guardrail( - self, texts: List[str], request_data: dict, input_type: str, **kwargs + self, + texts: List[str], + request_data: dict, + input_type: Literal["request", "response"], + logging_obj: Optional[Any] = None, + images: Optional[List[str]] = None, ) -> Tuple[List[str], Optional[List[str]]]: - """Append [GUARDRAILED] to text""" + """ + For requests: Append [GUARDRAILED] to text + For responses: Block by raising HTTPException (masking responses is no longer supported) + """ + if input_type == "response": + # Responses should be blocked, not masked + raise HTTPException( + status_code=400, + detail={"error": "Response blocked by guardrail", "texts": texts}, + ) + # For requests, we can still mask/transform return ([f"{text} [GUARDRAILED]" for text in texts], None) @@ -169,11 +186,15 @@ class TestOpenAIResponsesHandlerOutputProcessing: @pytest.mark.asyncio async def test_process_output_response_simple(self): - """Test processing simple output response""" + """Test processing simple output response - should block, not mask + + After unified_guardrail.py changes, responses can only be blocked/rejected, not masked. + This test verifies that the guardrail properly blocks responses. + """ handler = OpenAIResponsesHandler() guardrail = MockGuardrail(guardrail_name="test") - # Create a mock response + # Create a mock response with dict format (works with current handler) response = ResponsesAPIResponse( id="resp_123", created_at=1234567890, @@ -181,30 +202,36 @@ class TestOpenAIResponsesHandlerOutputProcessing: object="response", status="completed", output=[ - GenericResponseOutputItem( - type="message", - id="msg_123", - status="completed", - role="assistant", - content=[ - OutputText( - type="output_text", text="Hello user", annotations=None - ), + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "Hello user"}, ], - ) + } ], ) - result = await handler.process_output_response(response, guardrail) + # Response should be blocked, not masked + with pytest.raises(HTTPException) as exc_info: + await handler.process_output_response(response, guardrail) - assert result.output[0].content[0].text == "Hello user [GUARDRAILED]" + assert exc_info.value.status_code == 400 + assert "Response blocked by guardrail" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_process_output_response_multiple_items(self): - """Test processing output response with multiple output items""" + """Test processing output response with multiple output items - should block, not mask + + After unified_guardrail.py changes, responses can only be blocked/rejected, not masked. + This test verifies that the guardrail properly blocks responses with multiple items. + """ handler = OpenAIResponsesHandler() guardrail = MockGuardrail(guardrail_name="test") + # Use dict format (works with current handler) response = ResponsesAPIResponse( id="resp_123", created_at=1234567890, @@ -212,46 +239,45 @@ class TestOpenAIResponsesHandlerOutputProcessing: object="response", status="completed", output=[ - GenericResponseOutputItem( - type="message", - id="msg_123", - status="completed", - role="assistant", - content=[ - OutputText( - type="output_text", - text="First message", - annotations=None, - ), + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "First message"}, ], - ), - GenericResponseOutputItem( - type="message", - id="msg_124", - status="completed", - role="assistant", - content=[ - OutputText( - type="output_text", - text="Second message", - annotations=None, - ), + }, + { + "type": "message", + "id": "msg_124", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "Second message"}, ], - ), + }, ], ) - result = await handler.process_output_response(response, guardrail) + # Response should be blocked, not masked + with pytest.raises(HTTPException) as exc_info: + await handler.process_output_response(response, guardrail) - assert result.output[0].content[0].text == "First message [GUARDRAILED]" - assert result.output[1].content[0].text == "Second message [GUARDRAILED]" + assert exc_info.value.status_code == 400 + assert "Response blocked by guardrail" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_process_output_response_multiple_content_items(self): - """Test processing output response with multiple content items in one output""" + """Test processing output response with multiple content items - should block, not mask + + After unified_guardrail.py changes, responses can only be blocked/rejected, not masked. + This test verifies that the guardrail properly blocks responses with multiple content items. + """ handler = OpenAIResponsesHandler() guardrail = MockGuardrail(guardrail_name="test") + # Use dict format (works with current handler) response = ResponsesAPIResponse( id="resp_123", created_at=1234567890, @@ -259,27 +285,33 @@ class TestOpenAIResponsesHandlerOutputProcessing: object="response", status="completed", output=[ - GenericResponseOutputItem( - type="message", - id="msg_123", - status="completed", - role="assistant", - content=[ - OutputText(type="output_text", text="Part 1", annotations=None), - OutputText(type="output_text", text="Part 2", annotations=None), + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "Part 1"}, + {"type": "output_text", "text": "Part 2"}, ], - ) + } ], ) - result = await handler.process_output_response(response, guardrail) + # Response should be blocked, not masked + with pytest.raises(HTTPException) as exc_info: + await handler.process_output_response(response, guardrail) - assert result.output[0].content[0].text == "Part 1 [GUARDRAILED]" - assert result.output[0].content[1].text == "Part 2 [GUARDRAILED]" + assert exc_info.value.status_code == 400 + assert "Response blocked by guardrail" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_process_output_response_with_dict_format(self): - """Test processing output response where content items are dicts instead of OutputText objects""" + """Test processing output response with dict format - should block, not mask + + After unified_guardrail.py changes, responses can only be blocked/rejected, not masked. + This test verifies blocking works even when content items are dicts instead of OutputText objects. + """ handler = OpenAIResponsesHandler() guardrail = MockGuardrail(guardrail_name="test") @@ -303,9 +335,12 @@ class TestOpenAIResponsesHandlerOutputProcessing: ], ) - result = await handler.process_output_response(response, guardrail) + # Response should be blocked, not masked + with pytest.raises(HTTPException) as exc_info: + await handler.process_output_response(response, guardrail) - assert result.output[0]["content"][0]["text"] == "Hello from dict [GUARDRAILED]" + assert exc_info.value.status_code == 400 + assert "Response blocked by guardrail" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_process_output_response_no_text_content(self): diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py index 265605c1637..2b192dad5d5 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py @@ -381,10 +381,17 @@ class TestContentFilterGuardrail: assert result is not None assert result[1] == "aws_access_key" + @pytest.mark.skip( + reason="Masking in streaming responses is no longer supported after unified_guardrail.py changes. Only blocking/rejecting is supported for responses." + ) @pytest.mark.asyncio async def test_streaming_hook_mask(self): """ Test streaming hook with MASK action + + Note: After changes to unified_guardrail.py, masking responses to users + is no longer supported. This test is skipped as the feature is deprecated. + Only BLOCK actions (test_streaming_hook_block) are supported for streaming responses. """ from unittest.mock import AsyncMock @@ -431,7 +438,7 @@ class TestContentFilterGuardrail: user_api_key_dict = MagicMock() request_data = {} - # Process streaming response + # Process streaming response - no masking expected result_chunks = [] async for chunk in guardrail.async_post_call_streaming_iterator_hook( user_api_key_dict=user_api_key_dict, @@ -440,12 +447,8 @@ class TestContentFilterGuardrail: ): result_chunks.append(chunk) + # Chunks should pass through unchanged since masking is no longer supported assert len(result_chunks) == 2 - # First chunk should have email masked - assert "[EMAIL_REDACTED]" in result_chunks[0].choices[0].delta.content - assert "test@example.com" not in result_chunks[0].choices[0].delta.content - # Second chunk should be unchanged - assert result_chunks[1].choices[0].delta.content == " for more info" @pytest.mark.asyncio async def test_streaming_hook_block(self): From a4341ccf833c19f716e00a8fedc66fb257d5bcd6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 21:00:49 -0800 Subject: [PATCH 326/349] ci/cd changes for debugging --- .circleci/config.yml | 7 ++++++- .../e2e_ui_tests/login_to_ui.spec.ts | 4 ++-- .../require_auth_for_dashboard.spec.ts | 14 ++++++++++---- tests/proxy_admin_ui_tests/utils/login.ts | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a518628afb9..0adfd5be529 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3496,8 +3496,13 @@ jobs: command: | npx playwright test e2e_ui_tests/ --reporter=html --output=test-results no_output_timeout: 120m - - store_test_results: + - store_artifacts: path: test-results + destination: playwright-results + + - store_artifacts: + path: playwright-report + destination: playwright-report test_nonroot_image: machine: diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts index 793ae53b39a..b9c16299cc0 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts @@ -12,7 +12,7 @@ test("admin login test", async ({ page }) => { // Go to the specified URL await page.goto("http://localhost:4000/ui"); - page.screenshot({ path: "login_before.png" }); + await page.screenshot({ path: "test-results/login_before.png" }); // Enter "admin" in the username input field await page.fill('input[name="username"]', "admin"); @@ -20,7 +20,7 @@ test("admin login test", async ({ page }) => { // Enter "gm" in the password input field await page.fill('input[name="password"]', "gm"); - page.screenshot({ path: "login_after_inputs.png" }); + page.screenshot({ path: "test-results/login_after_inputs.png" }); // Optionally, you can add an assertion to verify the login button is enabled const loginButton = page.getByRole("button", { name: "Login" }); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts index 18bc91c0977..2b198d6ab7e 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts @@ -2,15 +2,19 @@ import { test, expect } from "@playwright/test"; test.describe("Authentication Checks", () => { - test("should redirect unauthenticated user from a protected page", async ({ page }) => { + test("should redirect unauthenticated user from a protected page", async ({ + page, + }) => { test.setTimeout(30000); page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); const protectedPageUrl = "http://localhost:4000/ui?page=llm-playground"; - const expectedRedirectUrl = "http://localhost:4000/sso/key/generate"; + const expectedRedirectUrl = "http://localhost:4000/ui/login"; - console.log(`Attempting to navigate to protected page: ${protectedPageUrl}`); + console.log( + `Attempting to navigate to protected page: ${protectedPageUrl}` + ); await page.goto(protectedPageUrl); @@ -20,7 +24,9 @@ test.describe("Authentication Checks", () => { await page.waitForURL(expectedRedirectUrl, { timeout: 10000 }); console.log(`Waited for URL. Current URL is now: ${page.url()}`); } catch (error) { - console.error(`Timeout waiting for URL: ${expectedRedirectUrl}. Current URL: ${page.url()}`); + console.error( + `Timeout waiting for URL: ${expectedRedirectUrl}. Current URL: ${page.url()}` + ); await page.screenshot({ path: "redirect-fail-screenshot.png" }); throw error; } diff --git a/tests/proxy_admin_ui_tests/utils/login.ts b/tests/proxy_admin_ui_tests/utils/login.ts index e8755089976..716efe58c76 100644 --- a/tests/proxy_admin_ui_tests/utils/login.ts +++ b/tests/proxy_admin_ui_tests/utils/login.ts @@ -13,7 +13,7 @@ export async function loginToUI(page: Page) { await page.fill('input[name="password"]', "gm"); console.log("Filled login credentials"); - const loginButton = page.locator('input[type="submit"]'); + const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); await loginButton.click(); console.log("Clicked login button"); From 32013f63a0e03f6fc198cba40dea7cf9ee48b117 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 3 Dec 2025 21:20:39 -0800 Subject: [PATCH 327/349] Guardrail API - support tool call checks on OpenAI `/chat/completions`, OpenAI `/responses`, Anthropic `/v1/messages` (#17459) * fix(unified_guardrail.py): correctly map a v1/messages call to the anthropic unified guardrail * fix: add more rigorous call type checks * fix(anthropic_endpoints/endpoints.py): initialize logging object at the beginning of endpoint ensures call id + trace id are emitted to guardrail api * feat(anthropic/chat/guardrail_translation): support streaming guardrails sample on every 5 chunks * fix(openai/chat/guardrail_translation): support openai streaming guardrails * fix: initial commit fixing output guardrails for responses api * feat(openai/responses/guardrail_translation): handler.py - fix output checks on responses api * fix(openai/responses/guardrail_translation/handler.py): ensure responses api guardrails work on streaming * test: update tests * test: update tests * fix: support multiple kinds of input to the guardrail api * feat(guardrail_translation/handler.py): support extracting tool calls from openai chat completions for guardrail api's * feat(generic_guardrail_api.py): support extracting + returning modified tool calls on generic_guardrails_api allows guardrail api to analyze tool call being sent to provider - to run any analysis on it * fix(guardrails.py): support anthropic /v1/messages tool calls * feat(responses_api/): extract tool calls for guardrail processing * docs(generic_guardrail_api.md): document tools param support * docs: generic_guardrail_api.md improve documentation --- .../mock_bedrock_guardrail_server.py | 1 + .../adding_provider/generic_guardrail_api.md | 87 +++ litellm/integrations/custom_guardrail.py | 17 +- .../chat/guardrail_translation/handler.py | 93 +++- .../rerank/guardrail_translation/handler.py | 5 +- .../chat/guardrail_translation/handler.py | 357 ++++++++---- .../guardrail_translation/handler.py | 15 +- .../guardrail_translation/handler.py | 5 +- .../guardrail_translation/handler.py | 96 +++- .../speech/guardrail_translation/handler.py | 5 +- .../guardrail_translation/handler.py | 5 +- .../guardrail_translation/handler.py | 8 +- .../proxy/guardrails/guardrail_endpoints.py | 10 +- .../guardrail_hooks/bedrock_guardrails.py | 20 +- .../guardrail_hooks/enkryptai/enkryptai.py | 18 +- .../generic_guardrail_api.py | 44 +- .../litellm_content_filter/content_filter.py | 28 +- .../guardrails/guardrail_hooks/presidio.py | 12 +- .../unified_guardrail/unified_guardrail.py | 1 + .../zscaler_ai_guard/zscaler_ai_guard.py | 17 +- litellm/types/guardrails.py | 7 + .../guardrail_hooks/generic_guardrail_api.py | 7 + .../test_bedrock_apply_guardrail.py | 25 +- .../test_litellm/llms/openai/chat/__init__.py | 0 .../chat/guardrail_translation/__init__.py | 0 .../guardrail_translation/test_handler.py | 518 ++++++++++++++++++ .../content_filter/test_content_filter.py | 19 +- .../test_generic_guardrail_api.py | 85 ++- .../guardrail_hooks/test_presidio.py | 4 +- 29 files changed, 1234 insertions(+), 275 deletions(-) create mode 100644 tests/test_litellm/llms/openai/chat/__init__.py create mode 100644 tests/test_litellm/llms/openai/chat/guardrail_translation/__init__.py create mode 100644 tests/test_litellm/llms/openai/chat/guardrail_translation/test_handler.py diff --git a/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py index c9f7f13f6aa..fd53ece6604 100644 --- a/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py +++ b/cookbook/mock_guardrail_server/mock_bedrock_guardrail_server.py @@ -426,6 +426,7 @@ This is a beta API. Please help us improve it. class LitellmBasicGuardrailRequest(BaseModel): texts: List[str] images: Optional[List[str]] = None + tools: Optional[List[dict]] = None request_data: Dict[str, Any] = Field(default_factory=dict) additional_provider_specific_params: Dict[str, Any] = Field(default_factory=dict) input_type: Literal["request", "response"] diff --git a/docs/my-website/docs/adding_provider/generic_guardrail_api.md b/docs/my-website/docs/adding_provider/generic_guardrail_api.md index 9392deeb5db..f8c07b25f9d 100644 --- a/docs/my-website/docs/adding_provider/generic_guardrail_api.md +++ b/docs/my-website/docs/adding_provider/generic_guardrail_api.md @@ -21,6 +21,20 @@ The **Generic Guardrail API** lets you integrate with LiteLLM **instantly** by i 5. **Custom Parameters** - Pass provider-specific params via config 6. **Full Control** - You own and maintain your guardrail API +## Supported Endpoints + +The Generic Guardrail API works with the following LiteLLM endpoints: + +- `/v1/chat/completions` - OpenAI Chat Completions +- `/v1/completions` - OpenAI Text Completions +- `/v1/responses` - OpenAI Responses API +- `/v1/images/generations` - OpenAI Image Generation +- `/v1/audio/transcriptions` - OpenAI Audio Transcriptions +- `/v1/audio/speech` - OpenAI Text-to-Speech +- `/v1/messages` - Anthropic Messages +- `/v1/rerank` - Cohere Rerank +- Pass-through endpoints + ## How It Works 1. LiteLLM extracts text and images from any request (chat messages, embeddings, image prompts, etc.) @@ -40,6 +54,21 @@ Implement `POST /beta/litellm_basic_guardrail_api` { "texts": ["extracted text from the request"], // array of text strings "images": ["base64_encoded_image_data"], // optional array of images + "tools": [ // optional array of tools (OpenAI ChatCompletionToolParam format) + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } + } + } + ], "request_data": { "user_api_key_hash": "hash of the litellm virtual key used", "user_api_key_alias": "alias of the litellm virtual key used", @@ -75,6 +104,49 @@ Implement `POST /beta/litellm_basic_guardrail_api` - `NONE` - Request proceeds unchanged - `GUARDRAIL_INTERVENED` - Request proceeds with modified texts/images (provide `texts` and/or `images` fields) +## Parameters + +### `tools` Parameter + +The `tools` parameter provides information about available function/tool definitions in the request. + +**Format:** OpenAI `ChatCompletionToolParam` format (see [OpenAI API reference](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools)) + +**Example:** +```json +{ + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"] + } + }, + "required": ["location"] + } + } +} +``` + +**Limitations:** +- **Input only:** Tools are only passed for `input_type="request"` (pre-call guardrails). Output/response guardrails do not currently receive tool information. +- **Supported endpoints:** The `tools` parameter is supported on: `/v1/chat/completions`, `/v1/responses`, and `/v1/messages`. Other endpoints do not have tool support. + +**Use cases:** +- Enforce tool permission policies (e.g., only allow certain users/teams to access specific tools) +- Validate tool schemas before sending to LLM +- Log tool usage for audit purposes +- Block sensitive tools based on user context + ## LiteLLM Configuration Add to `config.yaml`: @@ -138,6 +210,7 @@ app = FastAPI() class GuardrailRequest(BaseModel): texts: List[str] images: Optional[List[str]] = None + tools: Optional[List[Dict[str, Any]]] = None # OpenAI ChatCompletionToolParam format request_data: Dict[str, Any] input_type: str # "request" or "response" litellm_call_id: Optional[str] = None @@ -153,6 +226,8 @@ class GuardrailResponse(BaseModel): @app.post("/beta/litellm_basic_guardrail_api") async def apply_guardrail(request: GuardrailRequest): # Your guardrail logic here + + # Example: Check text content for text in request.texts: if "badword" in text.lower(): return GuardrailResponse( @@ -160,6 +235,18 @@ async def apply_guardrail(request: GuardrailRequest): blocked_reason="Content contains prohibited terms" ) + # Example: Check tools (if present in request) + if request.tools: + for tool in request.tools: + if tool.get("type") == "function": + function_name = tool.get("function", {}).get("name", "") + # Block sensitive tools + if function_name in ["delete_data", "access_admin_panel"]: + return GuardrailResponse( + action="BLOCKED", + blocked_reason=f"Tool '{function_name}' is not allowed" + ) + return GuardrailResponse(action="NONE") ``` diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 507a754a7e0..782f9460044 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -17,6 +17,7 @@ from litellm.caching import DualCache from litellm.integrations.custom_logger import CustomLogger from litellm.types.guardrails import ( DynamicGuardrailParams, + GenericGuardrailAPIInputs, GuardrailEventHooks, LitellmParams, Mode, @@ -449,20 +450,22 @@ class CustomGuardrail(CustomLogger): async def apply_guardrail( self, - texts: List[str], + inputs: GenericGuardrailAPIInputs, request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> GenericGuardrailAPIInputs: """ - Apply your guardrail logic to the given text + Apply your guardrail logic to the given inputs Args: - texts: The texts to apply the guardrail to - images: The images to apply the guardrail to + inputs: Dictionary containing: + - texts: List of texts to apply the guardrail to + - images: Optional list of images to apply the guardrail to + - tool_calls: Optional list of tool calls to apply the guardrail to request_data: The request data dictionary - containing user api key metadata (e.g. user_id, team_id, etc.) input_type: The type of input to apply the guardrail to - "request" or "response" + logging_obj: Optional logging object for tracking the guardrail execution Any of the custom guardrails can override this method to provide custom guardrail logic @@ -473,7 +476,7 @@ class CustomGuardrail(CustomLogger): - If the guardrail raises an exception """ - return texts, images + return inputs def _process_response( self, diff --git a/litellm/llms/anthropic/chat/guardrail_translation/handler.py b/litellm/llms/anthropic/chat/guardrail_translation/handler.py index e248e409de4..9b6511f151c 100644 --- a/litellm/llms/anthropic/chat/guardrail_translation/handler.py +++ b/litellm/llms/anthropic/chat/guardrail_translation/handler.py @@ -16,7 +16,13 @@ import json from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast from litellm._logging import verbose_proxy_logger +from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import ( + LiteLLMAnthropicMessagesAdapter, +) from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation +from litellm.types.guardrails import GenericGuardrailAPIInputs +from litellm.types.llms.anthropic import AllAnthropicToolsValues +from litellm.types.llms.openai import ChatCompletionToolParam if TYPE_CHECKING: from litellm.integrations.custom_guardrail import CustomGuardrail @@ -37,6 +43,10 @@ class AnthropicMessagesHandler(BaseTranslation): Methods can be overridden to customize behavior for different message formats. """ + def __init__(self): + super().__init__() + self.adapter = LiteLLMAnthropicMessagesAdapter() + async def process_input_messages( self, data: dict, @@ -47,11 +57,13 @@ class AnthropicMessagesHandler(BaseTranslation): Process input messages by applying guardrails to text content. """ messages = data.get("messages") + tools = data.get("tools", None) if messages is None: return data texts_to_check: List[str] = [] images_to_check: List[str] = [] + tools_to_check: List[ChatCompletionToolParam] = [] task_mappings: List[Tuple[int, Optional[int]]] = [] # Track (message_index, content_index) for each text # content_index is None for string content, int for list content @@ -66,18 +78,28 @@ class AnthropicMessagesHandler(BaseTranslation): task_mappings=task_mappings, ) + if tools is not None: + self._extract_input_tools( + tools=tools, + tools_to_check=tools_to_check, + ) + # Step 2: Apply guardrail to all texts in batch if texts_to_check: - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=data, - input_type="request", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + if tools_to_check: + inputs["tools"] = tools_to_check + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=data, + input_type="request", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 3: Map guardrail responses back to original message structure await self._apply_guardrail_responses_to_input( messages=messages, @@ -105,15 +127,17 @@ class AnthropicMessagesHandler(BaseTranslation): Override this method to customize text/image extraction logic. """ content = message.get("content", None) - if content is None: + tools = message.get("tools", None) + if content is None and tools is None: return - if isinstance(content, str): + ## CHECK FOR TEXT + IMAGES + if content is not None and isinstance(content, str): # Simple string content texts_to_check.append(content) task_mappings.append((msg_idx, None)) - elif isinstance(content, list): + elif content is not None and isinstance(content, list): # List content (e.g., multimodal with text and images) for content_idx, content_item in enumerate(content): # Extract text @@ -131,6 +155,22 @@ class AnthropicMessagesHandler(BaseTranslation): if data: images_to_check.append(data) + def _extract_input_tools( + self, + tools: List[Dict[str, Any]], + tools_to_check: List[ChatCompletionToolParam], + ) -> None: + """ + Extract tools from a message. + """ + ## CHECK FOR TOOLS + if tools is not None and isinstance(tools, list): + # TRANSFORM ANTHROPIC TOOLS TO OPENAI TOOLS + openai_tools = self.adapter.translate_anthropic_tools_to_openai( + tools=cast(List[AllAnthropicToolsValues], tools) + ) + tools_to_check.extend(openai_tools) + async def _apply_guardrail_responses_to_input( self, messages: List[Dict[str, Any]], @@ -224,16 +264,18 @@ class AnthropicMessagesHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=request_data, - input_type="response", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="response", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 3: Map guardrail responses back to original response structure await self._apply_guardrail_responses_to_output( response=response, @@ -260,14 +302,11 @@ class AnthropicMessagesHandler(BaseTranslation): Get the string so far, check the apply guardrail to the string so far, and return the list of responses so far. """ string_so_far = self.get_streaming_string_so_far(responses_so_far) - _, _ = ( - await guardrail_to_apply.apply_guardrail( # allow rejecting the response, if invalid - texts=[string_so_far], - request_data={}, - input_type="response", - logging_obj=litellm_logging_obj, - images=None, - ) + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( # allow rejecting the response, if invalid + inputs={"texts": [string_so_far]}, + request_data={}, + input_type="response", + logging_obj=litellm_logging_obj, ) return responses_so_far diff --git a/litellm/llms/cohere/rerank/guardrail_translation/handler.py b/litellm/llms/cohere/rerank/guardrail_translation/handler.py index e042295ab0b..6893a5991c3 100644 --- a/litellm/llms/cohere/rerank/guardrail_translation/handler.py +++ b/litellm/llms/cohere/rerank/guardrail_translation/handler.py @@ -49,12 +49,13 @@ class CohereRerankHandler(BaseTranslation): # Process query only query = data.get("query") if query is not None and isinstance(query, str): - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[query], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [query]}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) data["query"] = guardrailed_texts[0] if guardrailed_texts else query verbose_proxy_logger.debug( diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index 2b381e28bc0..76aa6f730c2 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -19,6 +19,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast import litellm from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation +from litellm.types.guardrails import GenericGuardrailAPIInputs +from litellm.types.llms.openai import ChatCompletionToolParam from litellm.types.utils import Choices, StreamingChoices if TYPE_CHECKING: @@ -52,38 +54,60 @@ class OpenAIChatCompletionsHandler(BaseTranslation): texts_to_check: List[str] = [] images_to_check: List[str] = [] - task_mappings: List[Tuple[int, Optional[int]]] = [] - # Track (message_index, content_index) for each text + tool_calls_to_check: List[ChatCompletionToolParam] = [] + text_task_mappings: List[Tuple[int, Optional[int]]] = [] + tool_call_task_mappings: List[Tuple[int, int]] = [] + # text_task_mappings: Track (message_index, content_index) for each text # content_index is None for string content, int for list content + # tool_call_task_mappings: Track (message_index, tool_call_index) for each tool call - # Step 1: Extract all text content and images + # Step 1: Extract all text content, images, and tool calls for msg_idx, message in enumerate(messages): - self._extract_input_text_and_images( + self._extract_inputs( message=message, msg_idx=msg_idx, texts_to_check=texts_to_check, images_to_check=images_to_check, - task_mappings=task_mappings, + tool_calls_to_check=tool_calls_to_check, + text_task_mappings=text_task_mappings, + tool_call_task_mappings=tool_call_task_mappings, ) - # Step 2: Apply guardrail to all texts in batch - if texts_to_check: - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=data, - input_type="request", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + # Step 2: Apply guardrail to all texts and tool calls in batch + if texts_to_check or tool_calls_to_check: + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + if tool_calls_to_check: + inputs["tool_calls"] = tool_calls_to_check # type: ignore + + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=data, + input_type="request", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + guardrailed_tool_calls = guardrailed_inputs.get("tools", []) + # Step 3: Map guardrail responses back to original message structure - await self._apply_guardrail_responses_to_input( - messages=messages, - responses=guardrailed_texts, - task_mappings=task_mappings, - ) + if guardrailed_texts and texts_to_check: + await self._apply_guardrail_responses_to_input_texts( + messages=messages, + responses=guardrailed_texts, + task_mappings=text_task_mappings, + ) + + # Step 4: Apply guardrailed tool calls back to messages + if guardrailed_tool_calls: + # Note: The guardrail may modify tool_calls_to_check in place + # or we may need to handle returned tool calls differently + await self._apply_guardrail_responses_to_input_tool_calls( + messages=messages, + tool_calls=guardrailed_tool_calls, # type: ignore + task_mappings=tool_call_task_mappings, + ) verbose_proxy_logger.debug( "OpenAI Chat Completions: Processed input messages: %s", messages @@ -91,61 +115,71 @@ class OpenAIChatCompletionsHandler(BaseTranslation): return data - def _extract_input_text_and_images( + def _extract_inputs( self, message: Dict[str, Any], msg_idx: int, texts_to_check: List[str], images_to_check: List[str], - task_mappings: List[Tuple[int, Optional[int]]], + tool_calls_to_check: List[ChatCompletionToolParam], + text_task_mappings: List[Tuple[int, Optional[int]]], + tool_call_task_mappings: List[Tuple[int, int]], ) -> None: """ - Extract text content and images from a message. + Extract text content, images, and tool calls from a message. - Override this method to customize text/image extraction logic. + Override this method to customize text/image/tool call extraction logic. """ content = message.get("content", None) - if content is None: - return + if content is not None: + if isinstance(content, str): + # Simple string content + texts_to_check.append(content) + text_task_mappings.append((msg_idx, None)) - if isinstance(content, str): - # Simple string content - texts_to_check.append(content) - task_mappings.append((msg_idx, None)) + elif isinstance(content, list): + # List content (e.g., multimodal with text and images) + for content_idx, content_item in enumerate(content): + # Extract text + text_str = content_item.get("text", None) + if text_str is not None: + texts_to_check.append(text_str) + text_task_mappings.append((msg_idx, int(content_idx))) - elif isinstance(content, list): - # List content (e.g., multimodal with text and images) - for content_idx, content_item in enumerate(content): - # Extract text - text_str = content_item.get("text", None) - if text_str is not None: - texts_to_check.append(text_str) - task_mappings.append((msg_idx, int(content_idx))) + # Extract images (image_url) + if content_item.get("type") == "image_url": + image_url = content_item.get("image_url", {}) + if isinstance(image_url, dict): + url = image_url.get("url") + if url: + images_to_check.append(url) - # Extract images (image_url) - if content_item.get("type") == "image_url": - image_url = content_item.get("image_url", {}) - if isinstance(image_url, dict): - url = image_url.get("url") - if url: - images_to_check.append(url) + # Extract tool calls (typically in assistant messages) + tool_calls = message.get("tools", None) + if tool_calls is not None and isinstance(tool_calls, list): + for tool_call_idx, tool_call in enumerate(tool_calls): + if isinstance(tool_call, dict): + # Add the full tool call object to the list + tool_calls_to_check.append(ChatCompletionToolParam(**tool_call)) + tool_call_task_mappings.append((msg_idx, int(tool_call_idx))) - async def _apply_guardrail_responses_to_input( + async def _apply_guardrail_responses_to_input_texts( self, messages: List[Dict[str, Any]], responses: List[str], task_mappings: List[Tuple[int, Optional[int]]], ) -> None: """ - Apply guardrail responses back to input messages. + Apply guardrail responses back to input message text content. - Override this method to customize how responses are applied. + Override this method to customize how text responses are applied. """ for task_idx, guardrail_response in enumerate(responses): mapping = task_mappings[task_idx] msg_idx = cast(int, mapping[0]) content_idx_optional = cast(Optional[int], mapping[1]) + # Handle content content = messages[msg_idx].get("content", None) if content is None: continue @@ -160,6 +194,31 @@ class OpenAIChatCompletionsHandler(BaseTranslation): "text" ] = guardrail_response + async def _apply_guardrail_responses_to_input_tool_calls( + self, + messages: List[Dict[str, Any]], + tool_calls: List[Dict[str, Any]], + task_mappings: List[Tuple[int, int]], + ) -> None: + """ + Apply guardrailed tool calls back to input messages. + + The guardrail may have modified the tool_calls list in place, + so we apply the modified tool calls back to the original messages. + + Override this method to customize how tool call responses are applied. + """ + for task_idx, (msg_idx, tool_call_idx) in enumerate(task_mappings): + if task_idx < len(tool_calls): + guardrailed_tool_call = tool_calls[task_idx] + message_tool_calls = messages[msg_idx].get("tool_calls", None) + if message_tool_calls is not None and isinstance( + message_tool_calls, list + ): + if tool_call_idx < len(message_tool_calls): + # Replace the tool call with the guardrailed version + message_tool_calls[tool_call_idx] = guardrailed_tool_call + async def process_output_response( self, response: "ModelResponse", @@ -193,21 +252,27 @@ class OpenAIChatCompletionsHandler(BaseTranslation): texts_to_check: List[str] = [] images_to_check: List[str] = [] - task_mappings: List[Tuple[int, Optional[int]]] = [] - # Track (choice_index, content_index) for each text + tool_calls_to_check: List[Dict[str, Any]] = [] + text_task_mappings: List[Tuple[int, Optional[int]]] = [] + tool_call_task_mappings: List[Tuple[int, int]] = [] + # text_task_mappings: Track (choice_index, content_index) for each text + # content_index is None for string content, int for list content + # tool_call_task_mappings: Track (choice_index, tool_call_index) for each tool call - # Step 1: Extract all text content and images from response choices + # Step 1: Extract all text content, images, and tool calls from response choices for choice_idx, choice in enumerate(response.choices): self._extract_output_text_and_images( choice=choice, choice_idx=choice_idx, texts_to_check=texts_to_check, images_to_check=images_to_check, - task_mappings=task_mappings, + tool_calls_to_check=tool_calls_to_check, + text_task_mappings=text_task_mappings, + tool_call_task_mappings=tool_call_task_mappings, ) - # Step 2: Apply guardrail to all texts in batch - if texts_to_check: + # Step 2: Apply guardrail to all texts and tool calls in batch + if texts_to_check or tool_calls_to_check: # Create a request_data dict with response info and user API key metadata request_data: dict = {"response": response} @@ -218,22 +283,36 @@ class OpenAIChatCompletionsHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=request_data, - input_type="response", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + if tool_calls_to_check: + inputs["tool_calls"] = tool_calls_to_check # type: ignore + + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="response", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 3: Map guardrail responses back to original response structure - await self._apply_guardrail_responses_to_output( - response=response, - responses=guardrailed_texts, - task_mappings=task_mappings, - ) + if guardrailed_texts and texts_to_check: + await self._apply_guardrail_responses_to_output_texts( + response=response, + responses=guardrailed_texts, + task_mappings=text_task_mappings, + ) + + # Step 4: Apply guardrailed tool calls back to response + if tool_calls_to_check: + await self._apply_guardrail_responses_to_output_tool_calls( + response=response, + tool_calls=tool_calls_to_check, + task_mappings=tool_call_task_mappings, + ) verbose_proxy_logger.debug( "OpenAI Chat Completions: Processed output response: %s", response @@ -334,16 +413,18 @@ class OpenAIChatCompletionsHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=request_data, - input_type="response", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="response", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 4: Apply guardrailed text back to all streaming chunks # For each choice, replace the combined text across all chunks await self._apply_guardrail_responses_to_output_streaming( @@ -363,7 +444,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): self, response: Union["ModelResponse", "ModelResponseStream"] ) -> bool: """ - Check if response has any text content to process. + Check if response has any text content or tool calls to process. Override this method to customize text content detection. """ @@ -372,15 +453,29 @@ class OpenAIChatCompletionsHandler(BaseTranslation): if isinstance(response, ModelResponse): for choice in response.choices: if isinstance(choice, litellm.Choices): + # Check for text content if choice.message.content and isinstance( choice.message.content, str ): return True + # Check for tool calls + if choice.message.tool_calls and isinstance( + choice.message.tool_calls, list + ): + if len(choice.message.tool_calls) > 0: + return True elif isinstance(response, ModelResponseStream): for choice in response.choices: if isinstance(choice, litellm.StreamingChoices): + # Check for text content if choice.delta.content and isinstance(choice.delta.content, str): return True + # Check for tool calls + if choice.delta.tool_calls and isinstance( + choice.delta.tool_calls, list + ): + if len(choice.delta.tool_calls) > 0: + return True return False def _extract_output_text_and_images( @@ -389,23 +484,28 @@ class OpenAIChatCompletionsHandler(BaseTranslation): choice_idx: int, texts_to_check: List[str], images_to_check: List[str], - task_mappings: List[Tuple[int, Optional[int]]], + tool_calls_to_check: List[Dict[str, Any]], + text_task_mappings: List[Tuple[int, Optional[int]]], + tool_call_task_mappings: List[Tuple[int, int]], ) -> None: """ - Extract text content and images from a response choice. + Extract text content, images, and tool calls from a response choice. - Override this method to customize text/image extraction logic. + Override this method to customize text/image/tool call extraction logic. """ verbose_proxy_logger.debug( "OpenAI Chat Completions: Processing choice: %s", choice ) - # Determine content source based on choice type + # Determine content source and tool calls based on choice type content = None + tool_calls = None if isinstance(choice, litellm.Choices): content = choice.message.content + tool_calls = choice.message.tool_calls elif isinstance(choice, litellm.StreamingChoices): content = choice.delta.content + tool_calls = choice.delta.tool_calls else: # Unknown choice type, skip processing return @@ -414,7 +514,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): if content and isinstance(content, str): # Simple string content texts_to_check.append(content) - task_mappings.append((choice_idx, None)) + text_task_mappings.append((choice_idx, None)) elif content and isinstance(content, list): # List content (e.g., multimodal response) @@ -423,7 +523,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): content_text = content_item.get("text") if content_text: texts_to_check.append(content_text) - task_mappings.append((choice_idx, int(content_idx))) + text_task_mappings.append((choice_idx, int(content_idx))) # Extract images if content_item.get("type") == "image_url": @@ -433,39 +533,108 @@ class OpenAIChatCompletionsHandler(BaseTranslation): if url: images_to_check.append(url) - async def _apply_guardrail_responses_to_output( + # Process tool calls if they exist + if tool_calls is not None and isinstance(tool_calls, list): + for tool_call_idx, tool_call in enumerate(tool_calls): + # Convert tool call to dict format for guardrail processing + tool_call_dict = self._convert_tool_call_to_dict(tool_call) + if tool_call_dict: + tool_calls_to_check.append(tool_call_dict) + tool_call_task_mappings.append((choice_idx, int(tool_call_idx))) + + def _convert_tool_call_to_dict( + self, tool_call: Union[Dict[str, Any], Any] + ) -> Optional[Dict[str, Any]]: + """ + Convert a tool call object to dictionary format. + + Tool calls can be either dict or object depending on the type. + """ + if isinstance(tool_call, dict): + return tool_call + elif hasattr(tool_call, "id") and hasattr(tool_call, "function"): + # Convert object to dict + function = tool_call.function + function_dict = {} + if hasattr(function, "name"): + function_dict["name"] = function.name + if hasattr(function, "arguments"): + function_dict["arguments"] = function.arguments + + tool_call_dict = { + "id": tool_call.id if hasattr(tool_call, "id") else None, + "type": tool_call.type if hasattr(tool_call, "type") else "function", + "function": function_dict, + } + return tool_call_dict + return None + + async def _apply_guardrail_responses_to_output_texts( self, response: "ModelResponse", responses: List[str], task_mappings: List[Tuple[int, Optional[int]]], ) -> None: """ - Apply guardrail responses back to output response. + Apply guardrail text responses back to output response. - Override this method to customize how responses are applied. + Override this method to customize how text responses are applied. """ for task_idx, guardrail_response in enumerate(responses): mapping = task_mappings[task_idx] choice_idx = cast(int, mapping[0]) content_idx_optional = cast(Optional[int], mapping[1]) - content = cast(Choices, response.choices[choice_idx]).message.content + choice = cast(Choices, response.choices[choice_idx]) + + # Handle content + content = choice.message.content if content is None: continue if isinstance(content, str) and content_idx_optional is None: # Replace string content with guardrail response - cast(Choices, response.choices[choice_idx]).message.content = ( - guardrail_response - ) + choice.message.content = guardrail_response elif isinstance(content, list) and content_idx_optional is not None: # Replace specific text item in list content - cast(Choices, response.choices[choice_idx]).message.content[ # type: ignore - content_idx_optional - ][ - "text" - ] = guardrail_response + choice.message.content[content_idx_optional]["text"] = guardrail_response # type: ignore + + async def _apply_guardrail_responses_to_output_tool_calls( + self, + response: "ModelResponse", + tool_calls: List[Dict[str, Any]], + task_mappings: List[Tuple[int, int]], + ) -> None: + """ + Apply guardrailed tool calls back to output response. + + The guardrail may have modified the tool_calls list in place, + so we apply the modified tool calls back to the original response. + + Override this method to customize how tool call responses are applied. + """ + for task_idx, (choice_idx, tool_call_idx) in enumerate(task_mappings): + if task_idx < len(tool_calls): + guardrailed_tool_call = tool_calls[task_idx] + choice = cast(Choices, response.choices[choice_idx]) + choice_tool_calls = choice.message.tool_calls + + if choice_tool_calls is not None and isinstance( + choice_tool_calls, list + ): + if tool_call_idx < len(choice_tool_calls): + # Update the tool call with guardrailed version + existing_tool_call = choice_tool_calls[tool_call_idx] + # Update object attributes (output responses always have typed objects) + if "function" in guardrailed_tool_call: + func_dict = guardrailed_tool_call["function"] + if "arguments" in func_dict: + existing_tool_call.function.arguments = func_dict[ + "arguments" + ] + if "name" in func_dict: + existing_tool_call.function.name = func_dict["name"] async def _apply_guardrail_responses_to_output_streaming( self, diff --git a/litellm/llms/openai/completion/guardrail_translation/handler.py b/litellm/llms/openai/completion/guardrail_translation/handler.py index f8b733567d3..73d08cfead4 100644 --- a/litellm/llms/openai/completion/guardrail_translation/handler.py +++ b/litellm/llms/openai/completion/guardrail_translation/handler.py @@ -53,12 +53,13 @@ class OpenAITextCompletionHandler(BaseTranslation): if isinstance(prompt, str): # Single string prompt - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[prompt], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [prompt]}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) data["prompt"] = guardrailed_texts[0] if guardrailed_texts else prompt verbose_proxy_logger.debug( @@ -79,12 +80,13 @@ class OpenAITextCompletionHandler(BaseTranslation): text_indices.append(idx) if texts_to_check: - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": texts_to_check}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) # Replace guardrailed texts back for guardrail_idx, prompt_idx in enumerate(text_indices): @@ -152,12 +154,13 @@ class OpenAITextCompletionHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": texts_to_check}, request_data=request_data, input_type="response", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) # Apply guardrailed texts back to choices for guardrail_idx, choice_idx in enumerate(choice_indices): diff --git a/litellm/llms/openai/image_generation/guardrail_translation/handler.py b/litellm/llms/openai/image_generation/guardrail_translation/handler.py index 53ee994c48f..842a64b1878 100644 --- a/litellm/llms/openai/image_generation/guardrail_translation/handler.py +++ b/litellm/llms/openai/image_generation/guardrail_translation/handler.py @@ -52,12 +52,13 @@ class OpenAIImageGenerationHandler(BaseTranslation): # Apply guardrail to the prompt if isinstance(prompt, str): - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[prompt], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [prompt]}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) data["prompt"] = guardrailed_texts[0] if guardrailed_texts else prompt verbose_proxy_logger.debug( diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index 7ef6576159e..9377eb4e193 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -28,12 +28,19 @@ Output: response.output is List[GenericResponseOutputItem] where each has: - text: str """ -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast + +from openai import BaseModel from openai import BaseModel from litellm._logging import verbose_proxy_logger from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation +from litellm.responses.litellm_completion_transformation.transformation import ( + LiteLLMCompletionResponsesConfig, +) +from litellm.types.guardrails import GenericGuardrailAPIInputs +from litellm.types.llms.openai import ChatCompletionToolParam from litellm.types.responses.main import GenericResponseOutputItem, OutputText if TYPE_CHECKING: @@ -65,17 +72,28 @@ class OpenAIResponsesHandler(BaseTranslation): Handles both string input and list of message objects. """ input_data: Optional[Union[str, "ResponseInputParam"]] = data.get("input") + tools_to_check: List[ChatCompletionToolParam] = [] if input_data is None: return data # Handle simple string input if isinstance(input_data, str): - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[input_data], + inputs = GenericGuardrailAPIInputs(texts=[input_data]) + + # Extract and transform tools if present + + if "tools" in data and data["tools"]: + self._extract_and_transform_tools(data["tools"], tools_to_check) + if tools_to_check: + inputs["tools"] = tools_to_check + + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) data["input"] = guardrailed_texts[0] if guardrailed_texts else input_data verbose_proxy_logger.debug("OpenAI Responses API: Processed string input") return data @@ -90,7 +108,7 @@ class OpenAIResponsesHandler(BaseTranslation): # Track (message_index, content_index) for each text # content_index is None for string content, int for list content - # Step 1: Extract all text content and images + # Step 1: Extract all text content, images, and tools for msg_idx, message in enumerate(input_data): self._extract_input_text_and_images( message=message, @@ -100,18 +118,26 @@ class OpenAIResponsesHandler(BaseTranslation): task_mappings=task_mappings, ) + # Extract and transform tools if present + if "tools" in data and data["tools"]: + self._extract_and_transform_tools(data["tools"], tools_to_check) + # Step 2: Apply guardrail to all texts in batch if texts_to_check: - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=data, - input_type="request", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + if tools_to_check: + inputs["tools"] = tools_to_check + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=data, + input_type="request", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 3: Map guardrail responses back to original input structure await self._apply_guardrail_responses_to_input( messages=input_data, @@ -125,6 +151,29 @@ class OpenAIResponsesHandler(BaseTranslation): return data + def _extract_and_transform_tools( + self, + tools: List[Dict[str, Any]], + tools_to_check: List[ChatCompletionToolParam], + ) -> None: + """ + Extract and transform tools from Responses API format to Chat Completion format. + + Uses the LiteLLM transformation function to convert Responses API tools + to Chat Completion tools that can be passed to guardrails. + """ + if tools is not None and isinstance(tools, list): + # Transform Responses API tools to Chat Completion tools + ( + transformed_tools, + _, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools # type: ignore + ) + tools_to_check.extend( + cast(List[ChatCompletionToolParam], transformed_tools) + ) + def _extract_input_text_and_images( self, message: Any, # Can be Dict[str, Any] or ResponseInputParam @@ -254,16 +303,18 @@ class OpenAIResponsesHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, guardrailed_images = ( - await guardrail_to_apply.apply_guardrail( - texts=texts_to_check, - request_data=request_data, - input_type="response", - images=images_to_check if images_to_check else None, - logging_obj=litellm_logging_obj, - ) + inputs = GenericGuardrailAPIInputs(texts=texts_to_check) + if images_to_check: + inputs["images"] = images_to_check + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="response", + logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) + # Step 3: Map guardrail responses back to original response structure await self._apply_guardrail_responses_to_output( response=response, @@ -288,12 +339,11 @@ class OpenAIResponsesHandler(BaseTranslation): Process output streaming response by applying guardrails to text content. """ string_so_far = self.get_streaming_string_so_far(responses_so_far) - guardrailed_text, _ = await guardrail_to_apply.apply_guardrail( - texts=[string_so_far], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [string_so_far]}, request_data={}, input_type="response", logging_obj=litellm_logging_obj, - images=None, ) return responses_so_far diff --git a/litellm/llms/openai/speech/guardrail_translation/handler.py b/litellm/llms/openai/speech/guardrail_translation/handler.py index 71edd4f2801..4c2f71477be 100644 --- a/litellm/llms/openai/speech/guardrail_translation/handler.py +++ b/litellm/llms/openai/speech/guardrail_translation/handler.py @@ -50,12 +50,13 @@ class OpenAITextToSpeechHandler(BaseTranslation): return data if isinstance(input_text, str): - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[input_text], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [input_text]}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) data["input"] = guardrailed_texts[0] if guardrailed_texts else input_text verbose_proxy_logger.debug( diff --git a/litellm/llms/openai/transcriptions/guardrail_translation/handler.py b/litellm/llms/openai/transcriptions/guardrail_translation/handler.py index 18678a9878b..ac416f42c81 100644 --- a/litellm/llms/openai/transcriptions/guardrail_translation/handler.py +++ b/litellm/llms/openai/transcriptions/guardrail_translation/handler.py @@ -88,12 +88,13 @@ class OpenAIAudioTranscriptionHandler(BaseTranslation): if user_metadata: request_data["litellm_metadata"] = user_metadata - guardrailed_texts, _ = await guardrail_to_apply.apply_guardrail( - texts=[original_text], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [original_text]}, request_data=request_data, input_type="response", logging_obj=litellm_logging_obj, ) + guardrailed_texts = guardrailed_inputs.get("texts", []) response.text = guardrailed_texts[0] if guardrailed_texts else original_text verbose_proxy_logger.debug( diff --git a/litellm/llms/pass_through/guardrail_translation/handler.py b/litellm/llms/pass_through/guardrail_translation/handler.py index ae96335cec3..0702086a578 100644 --- a/litellm/llms/pass_through/guardrail_translation/handler.py +++ b/litellm/llms/pass_through/guardrail_translation/handler.py @@ -118,8 +118,8 @@ class PassThroughEndpointHandler(BaseTranslation): return data # Apply guardrail (pass-through doesn't modify the text, just checks it) - await guardrail_to_apply.apply_guardrail( - texts=[text_to_check], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [text_to_check]}, request_data=data, input_type="request", logging_obj=litellm_logging_obj, @@ -178,8 +178,8 @@ class PassThroughEndpointHandler(BaseTranslation): request_data["litellm_metadata"] = user_metadata # Apply guardrail (pass-through doesn't modify the text, just checks it) - await guardrail_to_apply.apply_guardrail( - texts=[text_to_check], + guardrailed_inputs = await guardrail_to_apply.apply_guardrail( + inputs={"texts": [text_to_check]}, request_data=request_data, input_type="response", logging_obj=litellm_logging_obj, diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 395fdb249d3..bb78383ce44 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -1214,13 +1214,15 @@ async def apply_guardrail( detail=f"Guardrail '{request.guardrail_name}' not found. Please ensure the guardrail is configured in your LiteLLM proxy.", ) - response_text = await active_guardrail.apply_guardrail( - texts=[request.text], + guardrailed_inputs = await active_guardrail.apply_guardrail( + inputs={"texts": [request.text]}, request_data={}, input_type="request", - images=None, ) + response_text = guardrailed_inputs.get("texts", []) - return ApplyGuardrailResponse(response_text=response_text[0][0]) + return ApplyGuardrailResponse( + response_text=response_text[0] if response_text else request.text + ) except Exception as e: raise handle_exception_on_proxy(e) diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index 3932e1220b4..e0fb3192401 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -42,7 +42,7 @@ from litellm.llms.custom_httpx.http_handler import ( ) from litellm.proxy._types import UserAPIKeyAuth from litellm.secret_managers.main import get_secret_str -from litellm.types.guardrails import GuardrailEventHooks +from litellm.types.guardrails import GenericGuardrailAPIInputs, GuardrailEventHooks from litellm.types.llms.openai import AllMessageValues, ChatCompletionUserMessage from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( BedrockContentItem, @@ -54,6 +54,7 @@ from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.utils import ( CallTypes, CallTypesLiteral, @@ -1245,12 +1246,11 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): async def apply_guardrail( self, - texts: List[str], + inputs: "GenericGuardrailAPIInputs", request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> "GenericGuardrailAPIInputs": """ Apply Bedrock guardrail to a batch of texts for testing purposes. @@ -1258,17 +1258,18 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): It creates mock messages to test the guardrail functionality. Args: - texts: List of texts to analyze + inputs: Dictionary containing texts and optional images request_data: Request data dictionary for logging metadata input_type: Whether this is a "request" or "response" - images: Optional list of images (not processed separately) + logging_obj: Optional logging object Returns: - Tuple of (processed_texts, images) - texts may be masked, images unchanged + GenericGuardrailAPIInputs - processed_texts may be masked, images unchanged Raises: Exception: If content is blocked by Bedrock guardrail """ + texts = inputs.get("texts", []) try: verbose_proxy_logger.debug( f"Bedrock Guardrail: Applying guardrail to {len(texts)} text(s)" @@ -1279,6 +1280,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): mock_messages: List[AllMessageValues] = [ ChatCompletionUserMessage(role="user", content=text) for text in texts ] + request_messages = mock_messages filter_result = self._prepare_guardrail_messages_for_role( messages=request_messages @@ -1297,6 +1299,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): ) # Apply any masking that was applied by the guardrail + output_list = bedrock_response.get("output") if output_list: # If the guardrail returned modified content, use that @@ -1319,7 +1322,8 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): "Bedrock Guardrail: Successfully applied guardrail" ) - return masked_texts or texts, images + inputs["texts"] = masked_texts + return inputs except Exception as e: verbose_proxy_logger.error( diff --git a/litellm/proxy/guardrails/guardrail_hooks/enkryptai/enkryptai.py b/litellm/proxy/guardrails/guardrail_hooks/enkryptai/enkryptai.py index 49401ca7921..493d432eebb 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/enkryptai/enkryptai.py +++ b/litellm/proxy/guardrails/guardrail_hooks/enkryptai/enkryptai.py @@ -15,7 +15,6 @@ from typing import ( List, Literal, Optional, - Tuple, Union, ) @@ -30,7 +29,7 @@ from litellm.llms.custom_httpx.http_handler import ( httpxSpecialProvider, ) from litellm.proxy._types import UserAPIKeyAuth -from litellm.types.guardrails import GuardrailEventHooks +from litellm.types.guardrails import GenericGuardrailAPIInputs, GuardrailEventHooks from litellm.types.proxy.guardrails.guardrail_hooks.enkryptai import ( EnkryptAIProcessedResult, EnkryptAIResponse, @@ -481,27 +480,28 @@ class EnkryptAIGuardrails(CustomGuardrail): async def apply_guardrail( self, - texts: List[str], + inputs: "GenericGuardrailAPIInputs", request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> "GenericGuardrailAPIInputs": """ Apply EnkryptAI guardrail to a batch of texts. Args: - texts: List of texts to check for attacks + inputs: Dictionary containing texts and optional images request_data: Request data dictionary containing metadata input_type: Whether this is a "request" or "response" - images: Optional list of images (not used by EnkryptAI) + logging_obj: Optional logging object Returns: - Tuple of (texts, images) - texts unchanged if passed, images unchanged + GenericGuardrailAPIInputs - texts unchanged if passed, images unchanged Raises: ValueError: If any attacks are detected """ + texts = inputs.get("texts", []) + # Check each text for attacks for text in texts: result = await self._call_enkryptai_guardrails( @@ -517,7 +517,7 @@ class EnkryptAIGuardrails(CustomGuardrail): error_message = self._create_error_message(processed_result) raise ValueError(error_message) - return texts, images + return inputs async def async_post_call_streaming_iterator_hook( self, diff --git a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py index f806e8b4802..33912b8fcd9 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py +++ b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py @@ -14,7 +14,7 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, ) -from litellm.types.guardrails import GuardrailEventHooks +from litellm.types.guardrails import GenericGuardrailAPIInputs, GuardrailEventHooks from litellm.types.proxy.guardrails.guardrail_hooks.generic_guardrail_api import ( GenericGuardrailAPIMetadata, GenericGuardrailAPIRequest, @@ -144,22 +144,24 @@ class GenericGuardrailAPI(CustomGuardrail): async def apply_guardrail( self, - texts: List[str], + inputs: GenericGuardrailAPIInputs, request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> GenericGuardrailAPIInputs: """ - Apply the Generic Guardrail API to the given text. + Apply the Generic Guardrail API to the given inputs. This is the main method that gets called by the framework. Args: - texts: List of texts to check + inputs: Dictionary containing: + - texts: List of texts to check + - images: Optional list of images to check + - tool_calls: Optional list of tool calls to check request_data: Request data dictionary containing user_api_key_dict and other metadata input_type: Whether this is a "request" or "response" guardrail - images: Optional list of images to check + logging_obj: Optional logging object for tracking the guardrail execution Returns: Tuple of (processed texts, processed images) @@ -169,6 +171,11 @@ class GenericGuardrailAPI(CustomGuardrail): """ verbose_proxy_logger.debug("Generic Guardrail API: Applying guardrail to text") + # Extract texts and images from inputs + texts = inputs.get("texts", []) + images = inputs.get("images") + tools = inputs.get("tools") + # Use provided request_data or create an empty dict if request_data is None: request_data = {} @@ -193,6 +200,7 @@ class GenericGuardrailAPI(CustomGuardrail): texts=texts, request_data=user_metadata, images=images, + tools=tools, additional_provider_specific_params=additional_params, input_type=input_type, ) @@ -230,17 +238,19 @@ class GenericGuardrailAPI(CustomGuardrail): ) raise Exception(f"Content blocked by guardrail: {error_message}") - elif guardrail_response.action == "GUARDRAIL_INTERVENED": - # Content was modified by the guardrail - if guardrail_response.texts: - verbose_proxy_logger.debug("Generic Guardrail API modified text") - return guardrail_response.texts, guardrail_response.images - # Action is NONE or no modifications needed - return ( - guardrail_response.texts or texts, - guardrail_response.images or images, - ) + return_inputs = GenericGuardrailAPIInputs(texts=texts) + if guardrail_response.texts: + return_inputs["texts"] = guardrail_response.texts + if guardrail_response.images: + return_inputs["images"] = guardrail_response.images + elif images: + return_inputs["images"] = images + if guardrail_response.tools: + return_inputs["tools"] = guardrail_response.tools + elif tools: + return_inputs["tools"] = tools + return return_inputs except Exception as e: # Check if it's already an exception we raised diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py index 776b9d36d5e..6756b188847 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py @@ -27,6 +27,7 @@ from litellm.integrations.custom_guardrail import CustomGuardrail if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.guardrails import GenericGuardrailAPIInputs from litellm.proxy._types import UserAPIKeyAuth from litellm.types.guardrails import ( BlockedWord, @@ -304,12 +305,11 @@ class ContentFilterGuardrail(CustomGuardrail): async def apply_guardrail( self, - texts: List[str], + inputs: "GenericGuardrailAPIInputs", request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> "GenericGuardrailAPIInputs": """ Apply content filtering guardrail to a batch of texts. @@ -317,17 +317,19 @@ class ContentFilterGuardrail(CustomGuardrail): either blocking the request or masking the sensitive content. Args: - texts: List of texts to apply the guardrail to + inputs: Dictionary containing texts and optional images request_data: Request data dictionary for logging metadata input_type: Whether this is a "request" or "response" - images: Optional list of images (not processed) + logging_obj: Optional logging object Returns: - Tuple of (processed_texts, images) - texts may be masked, images unchanged + GenericGuardrailAPIInputs - processed_texts may be masked, images unchanged Raises: HTTPException: If sensitive content is detected and action is BLOCK """ + texts = inputs.get("texts", []) + verbose_proxy_logger.debug( f"ContentFilterGuardrail: Applying guardrail to {len(texts)} text(s)" ) @@ -386,7 +388,8 @@ class ContentFilterGuardrail(CustomGuardrail): verbose_proxy_logger.debug( "ContentFilterGuardrail: Guardrail applied successfully" ) - return processed_texts, images + inputs["texts"] = processed_texts + return inputs async def async_post_call_streaming_iterator_hook( self, @@ -423,12 +426,17 @@ class ContentFilterGuardrail(CustomGuardrail): if isinstance(choice.delta.content, str): # Check the chunk content using apply_guardrail try: - processed_content = await self.apply_guardrail( - texts=[choice.delta.content], + guardrailed_inputs = await self.apply_guardrail( + inputs={"texts": [choice.delta.content]}, input_type="response", - images=None, request_data=request_data, ) + processed_texts = guardrailed_inputs.get("texts", []) + processed_content = ( + processed_texts[0] + if processed_texts + else choice.delta.content + ) if processed_content != choice.delta.content: choice.delta.content = processed_content verbose_proxy_logger.debug( diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index 6b19685f07e..d183b688edd 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -29,9 +29,11 @@ import aiohttp import litellm # noqa: E401 from litellm import get_secret from litellm._logging import verbose_proxy_logger +from litellm.types.guardrails import GenericGuardrailAPIInputs if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm._uuid import uuid from litellm.caching.caching import DualCache from litellm.exceptions import BlockedPiiEntityError @@ -713,17 +715,18 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): async def apply_guardrail( self, - texts: List[str], + inputs: "GenericGuardrailAPIInputs", request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> "GenericGuardrailAPIInputs": """ UI will call this function to check: 1. If the connection to the guardrail is working 2. When Testing the guardrail with some text, this function will be called with the input text and returns a text after applying the guardrail """ + texts = inputs.get("texts", []) + new_texts = [] for text in texts: modified_text = await self.check_pii( @@ -733,7 +736,8 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): request_data=request_data or {}, ) new_texts.append(modified_text) - return new_texts, images + inputs["texts"] = new_texts + return inputs def update_in_memory_litellm_params(self, litellm_params: LitellmParams) -> None: """ diff --git a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py index d234dffc511..c16cb89b785 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py +++ b/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py @@ -193,6 +193,7 @@ class UnifiedLLMGuardrails(CustomLogger): "guardrail_to_apply", None ) + # Get sampling rate from guardrail config or optional_params, default to 5 sampling_rate = 5 if guardrail_to_apply is not None: diff --git a/litellm/proxy/guardrails/guardrail_hooks/zscaler_ai_guard/zscaler_ai_guard.py b/litellm/proxy/guardrails/guardrail_hooks/zscaler_ai_guard/zscaler_ai_guard.py index 562f0d40272..bebb87f8d21 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/zscaler_ai_guard/zscaler_ai_guard.py +++ b/litellm/proxy/guardrails/guardrail_hooks/zscaler_ai_guard/zscaler_ai_guard.py @@ -4,7 +4,7 @@ # # +-------------------------------------------------------------+ import os -from typing import TYPE_CHECKING, List, Literal, Optional, Tuple +from typing import TYPE_CHECKING, Literal, Optional from fastapi import HTTPException @@ -14,6 +14,7 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, ) +from litellm.types.guardrails import GenericGuardrailAPIInputs if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj @@ -71,27 +72,27 @@ class ZscalerAIGuard(CustomGuardrail): async def apply_guardrail( self, - texts: List[str], + inputs: "GenericGuardrailAPIInputs", request_data: dict, input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, - images: Optional[List[str]] = None, - ) -> Tuple[List[str], Optional[List[str]]]: + ) -> "GenericGuardrailAPIInputs": """ Apply Zscaler AI Guard guardrail to batch of texts. Args: - texts: List of texts to check + inputs: Dictionary containing texts and optional images request_data: Request data dictionary containing metadata input_type: Whether this is a "request" or "response" - images: Optional list of images (not used by Zscaler) + logging_obj: Optional logging object Returns: - Tuple of (processed_texts, images) - texts unchanged if passed, images unchanged + GenericGuardrailAPIInputs - texts unchanged if passed, images unchanged Raises: Exception: If content is blocked by Zscaler AI Guard """ + texts = inputs.get("texts", []) try: verbose_proxy_logger.debug(f"ZscalerAIGuard: Checking {len(texts)} text(s)") @@ -143,7 +144,7 @@ class ZscalerAIGuard(CustomGuardrail): raise e verbose_proxy_logger.debug("ZscalerAIGuard: Successfully applied guardrail.") - return texts, images + return inputs def extract_blocking_info(self, response): """ diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index 2c0b10ec67d..ea3a6cc4ede 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field from typing_extensions import Required, TypedDict +from litellm.types.llms.openai import ChatCompletionToolParam from litellm.types.proxy.guardrails.guardrail_hooks.enkryptai import ( EnkryptAIGuardrailConfigs, ) @@ -738,3 +739,9 @@ class PatchGuardrailRequest(BaseModel): guardrail_name: Optional[str] = None litellm_params: Optional[BaseLitellmParams] = None guardrail_info: Optional[Dict[str, Any]] = None + + +class GenericGuardrailAPIInputs(TypedDict, total=False): + texts: List[str] + images: List[str] + tools: List[ChatCompletionToolParam] diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py index d2acc3e3403..66823270e87 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, Field from typing_extensions import TypedDict +from litellm.types.llms.openai import ChatCompletionToolParam from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel @@ -59,6 +60,7 @@ class GenericGuardrailAPIRequest: litellm_trace_id: Optional[str], additional_provider_specific_params: Optional[Dict[str, Any]] = None, images: Optional[List[str]] = None, + tools: Optional[List[ChatCompletionToolParam]] = None, ): self.texts = texts self.request_data = request_data @@ -69,12 +71,14 @@ class GenericGuardrailAPIRequest: self.input_type = input_type self.litellm_call_id = litellm_call_id self.litellm_trace_id = litellm_trace_id + self.tools = tools def to_dict(self) -> dict: return { "texts": self.texts, "request_data": self.request_data, "images": self.images, + "tools": self.tools, "additional_provider_specific_params": self.additional_provider_specific_params, "input_type": self.input_type, "litellm_call_id": self.litellm_call_id, @@ -87,6 +91,7 @@ class GenericGuardrailAPIResponse: texts: Optional[List[str]] images: Optional[List[str]] + tools: Optional[List[ChatCompletionToolParam]] action: str blocked_reason: Optional[str] @@ -96,11 +101,13 @@ class GenericGuardrailAPIResponse: texts: Optional[List[str]] = None, blocked_reason: Optional[str] = None, images: Optional[List[str]] = None, + tools: Optional[List[ChatCompletionToolParam]] = None, ): self.action = action self.blocked_reason = blocked_reason self.texts = texts self.images = images + self.tools = tools @classmethod def from_dict(cls, data: dict) -> "GenericGuardrailAPIResponse": diff --git a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py index 203bd05c57c..8d98f56cd3a 100644 --- a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py +++ b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py @@ -39,11 +39,12 @@ async def test_bedrock_apply_guardrail_success(): mock_api_request.return_value = mock_response # Test the apply_guardrail method with new signature - result, _ = await guardrail.apply_guardrail( - texts=["This is a test message with some content"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["This is a test message with some content"]}, request_data={}, input_type="request", ) + result = guardrailed_inputs.get("texts", []) # Verify the result assert result == ["This is a test message with some content"] @@ -71,7 +72,9 @@ async def test_bedrock_apply_guardrail_blocked(): # Test the apply_guardrail method should raise an exception with pytest.raises(Exception) as exc_info: await guardrail.apply_guardrail( - texts=["This is blocked content"], request_data={}, input_type="request" + inputs={"texts": ["This is blocked content"]}, + request_data={}, + input_type="request", ) assert "Content blocked by Bedrock guardrail" in str(exc_info.value) @@ -100,11 +103,12 @@ async def test_bedrock_apply_guardrail_with_masking(): mock_api_request.return_value = mock_response # Test the apply_guardrail method with new signature - result, _ = await guardrail.apply_guardrail( - texts=["This is a test message with sensitive content"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["This is a test message with sensitive content"]}, request_data={}, input_type="request", ) + result = guardrailed_inputs.get("texts", []) # Verify the result contains the masked content assert result == ["This is a test message with [REDACTED] content"] @@ -130,7 +134,9 @@ async def test_bedrock_apply_guardrail_api_failure(): # Test the apply_guardrail method should raise an exception with pytest.raises(Exception) as exc_info: await guardrail.apply_guardrail( - texts=["This is a test message"], request_data={}, input_type="request" + inputs={"texts": ["This is a test message"]}, + request_data={}, + input_type="request", ) # The error message should contain the original exception @@ -215,11 +221,12 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable ) as mock_api: mock_api.return_value = {"action": "ALLOWED"} - result, _ = await guardrail.apply_guardrail( - texts=["latest question"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["latest question"]}, request_data=request_data, input_type="request", ) + result = guardrailed_inputs.get("texts", []) assert mock_api.called _, kwargs = mock_api.call_args @@ -250,7 +257,7 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable with pytest.raises(Exception, match="policy") as exc_info: await guardrail.apply_guardrail( - texts=["blocked"], + inputs={"texts": ["blocked"]}, request_data=request_data, input_type="request", ) diff --git a/tests/test_litellm/llms/openai/chat/__init__.py b/tests/test_litellm/llms/openai/chat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/openai/chat/guardrail_translation/__init__.py b/tests/test_litellm/llms/openai/chat/guardrail_translation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/openai/chat/guardrail_translation/test_handler.py b/tests/test_litellm/llms/openai/chat/guardrail_translation/test_handler.py new file mode 100644 index 00000000000..09f3f8dcf0f --- /dev/null +++ b/tests/test_litellm/llms/openai/chat/guardrail_translation/test_handler.py @@ -0,0 +1,518 @@ +""" +Unit tests for OpenAI Chat Completions Guardrail Translation Handler + +Tests the handler's ability to process input/output for Chat Completions API +with guardrail transformations, including tool calls. +""" + +import json +import os +import sys +from typing import Any, List, Literal, Optional, Tuple +from unittest.mock import AsyncMock, MagicMock + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../../../..") +) # Adds the parent directory to the system path + +from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.llms.openai.chat.guardrail_translation.handler import ( + OpenAIChatCompletionsHandler, +) +from litellm.types.guardrails import GenericGuardrailAPIInputs +from litellm.types.utils import ( + ChatCompletionMessageToolCall, + Choices, + Function, + Message, + ModelResponse, +) + + +class MockGuardrail(CustomGuardrail): + """Mock guardrail for testing that transforms text and tool calls""" + + def __init__(self, guardrail_name: str = "test"): + super().__init__(guardrail_name=guardrail_name) + self.last_inputs = None + self.last_request_data = None + self.tool_calls_modified = False + + async def apply_guardrail( + self, + inputs: GenericGuardrailAPIInputs, + request_data: dict, + input_type: Literal["request", "response"], + logging_obj: Optional[Any] = None, + ) -> Tuple[List[str], Optional[List[str]]]: + """Mock apply_guardrail that uppercases text and modifies tool calls""" + self.last_inputs = inputs + self.last_request_data = request_data + + # Return modified texts (uppercase for testing) + texts = inputs.get("texts", []) + modified_texts = [text.upper() for text in texts] + + # Modify tool calls in place if present + tool_calls = inputs.get("tool_calls", []) + if tool_calls: + self.tool_calls_modified = True + for tool_call in tool_calls: + if isinstance(tool_call, dict) and "function" in tool_call: + function = tool_call["function"] + if "arguments" in function: + # Modify arguments to uppercase JSON string + try: + args_dict = json.loads(function["arguments"]) + # Uppercase all string values + for key, value in args_dict.items(): + if isinstance(value, str): + args_dict[key] = value.upper() + function["arguments"] = json.dumps(args_dict) + except json.JSONDecodeError: + # If not JSON, just uppercase the string + function["arguments"] = function["arguments"].upper() + + return modified_texts, [] + + +class TestOpenAIChatCompletionsHandlerToolCallsInput: + """Test input processing with tool calls""" + + @pytest.mark.asyncio + async def test_extract_tool_calls_from_input_messages(self): + """Test that tool calls are extracted from input messages""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + # Create input data with tool calls (assistant message) + data = { + "messages": [ + {"role": "user", "content": "What's the weather in SF?"}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": json.dumps( + {"location": "San Francisco", "unit": "celsius"} + ), + }, + } + ], + }, + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify tool calls were extracted and passed to guardrail + assert guardrail.last_inputs is not None + assert "tool_calls" in guardrail.last_inputs + assert len(guardrail.last_inputs["tool_calls"]) == 1 + + tool_call = guardrail.last_inputs["tool_calls"][0] + assert tool_call["id"] == "call_123" + assert tool_call["function"]["name"] == "get_weather" + # Note: tool call arguments may already be modified by guardrail + # Check that it contains location parameter + assert "location" in tool_call["function"]["arguments"] + + # Verify tool call was modified by guardrail + assert guardrail.tool_calls_modified is True + + # Verify the message was updated with modified tool call + modified_tool_call = data["messages"][1]["tool_calls"][0] + args = json.loads(modified_tool_call["function"]["arguments"]) + assert args["location"] == "SAN FRANCISCO" # Should be uppercased + assert args["unit"] == "CELSIUS" # Should be uppercased + + @pytest.mark.asyncio + async def test_extract_tool_calls_and_text_from_input_messages(self): + """Test that both tool calls and text content are extracted""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + # Create input data with both text and tool calls + data = { + "messages": [ + {"role": "user", "content": "What's the weather?"}, + { + "role": "assistant", + "content": "Let me check that for you.", + "tool_calls": [ + { + "id": "call_456", + "type": "function", + "function": { + "name": "get_current_weather", + "arguments": json.dumps({"location": "Boston"}), + }, + } + ], + }, + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify both texts and tool calls were extracted + assert guardrail.last_inputs is not None + assert "texts" in guardrail.last_inputs + assert "tool_calls" in guardrail.last_inputs + + # Should have 2 texts (user message + assistant message) + assert len(guardrail.last_inputs["texts"]) == 2 + assert "What's the weather?" in guardrail.last_inputs["texts"] + assert "Let me check that for you." in guardrail.last_inputs["texts"] + + # Should have 1 tool call + assert len(guardrail.last_inputs["tool_calls"]) == 1 + assert ( + guardrail.last_inputs["tool_calls"][0]["function"]["name"] + == "get_current_weather" + ) + + # Verify text content was modified + assert data["messages"][0]["content"] == "WHAT'S THE WEATHER?" + assert data["messages"][1]["content"] == "LET ME CHECK THAT FOR YOU." + + # Verify tool call was modified + modified_tool_call = data["messages"][1]["tool_calls"][0] + args = json.loads(modified_tool_call["function"]["arguments"]) + assert args["location"] == "BOSTON" + + @pytest.mark.asyncio + async def test_extract_multiple_tool_calls_from_input(self): + """Test extraction of multiple tool calls""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + # Create input data with multiple tool calls + data = { + "messages": [ + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": json.dumps({"location": "NYC"}), + }, + }, + { + "id": "call_2", + "type": "function", + "function": { + "name": "get_time", + "arguments": json.dumps({"timezone": "EST"}), + }, + }, + ], + } + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify multiple tool calls were extracted + assert guardrail.last_inputs is not None + assert "tool_calls" in guardrail.last_inputs + assert len(guardrail.last_inputs["tool_calls"]) == 2 + + # Verify both tool calls + tool_calls = guardrail.last_inputs["tool_calls"] + assert tool_calls[0]["function"]["name"] == "get_weather" + assert tool_calls[1]["function"]["name"] == "get_time" + + # Verify both were modified + modified_tool_calls = data["messages"][0]["tool_calls"] + args1 = json.loads(modified_tool_calls[0]["function"]["arguments"]) + args2 = json.loads(modified_tool_calls[1]["function"]["arguments"]) + assert args1["location"] == "NYC" + assert args2["timezone"] == "EST" + + @pytest.mark.asyncio + async def test_tool_calls_separate_from_texts(self): + """Test that tool calls are passed as a separate parameter, not mixed with texts""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + data = { + "messages": [ + {"role": "user", "content": "Get weather for LA"}, + { + "role": "assistant", + "content": "Sure!", + "tool_calls": [ + { + "id": "call_xyz", + "type": "function", + "function": { + "name": "get_weather", + "arguments": json.dumps({"city": "Los Angeles"}), + }, + } + ], + }, + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify tool calls and texts are separate + assert guardrail.last_inputs is not None + texts = guardrail.last_inputs.get("texts", []) + tool_calls = guardrail.last_inputs.get("tool_calls", []) + + # Texts should only contain the content strings + assert len(texts) == 2 + assert "Get weather for LA" in texts + assert "Sure!" in texts + + # Tool call arguments should NOT be in texts + assert not any("Los Angeles" in text for text in texts) + + # Tool calls should be separate + assert len(tool_calls) == 1 + assert tool_calls[0]["function"]["name"] == "get_weather" + # Check that it contains city parameter (may be modified by guardrail) + assert "city" in tool_calls[0]["function"]["arguments"] + + @pytest.mark.asyncio + async def test_no_tool_calls_in_input(self): + """Test that messages without tool calls work correctly""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + # Create input data without tool calls + data = { + "messages": [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "Hi there!"}, + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify no tool calls were passed to guardrail + assert guardrail.last_inputs is not None + tool_calls = guardrail.last_inputs.get("tool_calls", []) + assert len(tool_calls) == 0 + + # Verify text was still processed + assert len(guardrail.last_inputs["texts"]) == 2 + assert data["messages"][0]["content"] == "HELLO" + assert data["messages"][1]["content"] == "HI THERE!" + + @pytest.mark.asyncio + async def test_empty_tool_calls_list(self): + """Test that empty tool_calls list is handled correctly""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + data = { + "messages": [ + {"role": "assistant", "content": "Hello", "tool_calls": []}, + ] + } + + # Process the input + await handler.process_input_messages(data, guardrail) + + # Verify empty tool_calls doesn't cause issues + assert guardrail.last_inputs is not None + tool_calls = guardrail.last_inputs.get("tool_calls", []) + assert len(tool_calls) == 0 + + +class TestOpenAIChatCompletionsHandlerToolCallsOutput: + """Test output processing with tool calls""" + + @pytest.mark.asyncio + async def test_extract_tool_calls_from_output_response(self): + """Test that tool calls are extracted from output responses""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + # Create a mock response with tool calls + response = ModelResponse( + id="chatcmpl-123", + created=1234567890, + model="gpt-4", + object="chat.completion", + choices=[ + Choices( + finish_reason="tool_calls", + index=0, + message=Message( + content=None, + role="assistant", + tool_calls=[ + ChatCompletionMessageToolCall( + id="call_789", + type="function", + function=Function( + name="search_database", + arguments=json.dumps({"query": "python tutorials"}), + ), + ) + ], + ), + ) + ], + ) + + # Process the output + await handler.process_output_response(response, guardrail) + + # Verify tool calls were extracted and passed to guardrail + assert guardrail.last_inputs is not None + assert "tool_calls" in guardrail.last_inputs + assert len(guardrail.last_inputs["tool_calls"]) == 1 + + tool_call = guardrail.last_inputs["tool_calls"][0] + assert tool_call["function"]["name"] == "search_database" + # Check that it contains query parameter (may be modified by guardrail) + assert "query" in tool_call["function"]["arguments"] + + # Verify tool call was modified in response + response_tool_call = response.choices[0].message.tool_calls[0] + args = json.loads(response_tool_call.function.arguments) + assert args["query"] == "PYTHON TUTORIALS" # Should be uppercased + + @pytest.mark.asyncio + async def test_extract_tool_calls_and_content_from_output(self): + """Test extraction of both content and tool calls from output""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + response = ModelResponse( + id="chatcmpl-456", + created=1234567890, + model="gpt-4", + object="chat.completion", + choices=[ + Choices( + finish_reason="tool_calls", + index=0, + message=Message( + content="I'll search for that information.", + role="assistant", + tool_calls=[ + ChatCompletionMessageToolCall( + id="call_999", + type="function", + function=Function( + name="web_search", + arguments=json.dumps( + {"keywords": "litellm documentation"} + ), + ), + ) + ], + ), + ) + ], + ) + + # Process the output + await handler.process_output_response(response, guardrail) + + # Verify both texts and tool calls were extracted + assert guardrail.last_inputs is not None + assert "texts" in guardrail.last_inputs + assert "tool_calls" in guardrail.last_inputs + + assert len(guardrail.last_inputs["texts"]) == 1 + assert "I'll search for that information." in guardrail.last_inputs["texts"] + + assert len(guardrail.last_inputs["tool_calls"]) == 1 + + # Verify both were modified + assert ( + response.choices[0].message.content == "I'LL SEARCH FOR THAT INFORMATION." + ) + response_tool_call = response.choices[0].message.tool_calls[0] + args = json.loads(response_tool_call.function.arguments) + assert args["keywords"] == "LITELLM DOCUMENTATION" + + @pytest.mark.asyncio + async def test_extract_multiple_tool_calls_from_output(self): + """Test extraction of multiple tool calls from output""" + handler = OpenAIChatCompletionsHandler() + guardrail = MockGuardrail() + + response = ModelResponse( + id="chatcmpl-789", + created=1234567890, + model="gpt-4", + object="chat.completion", + choices=[ + Choices( + finish_reason="tool_calls", + index=0, + message=Message( + content=None, + role="assistant", + tool_calls=[ + ChatCompletionMessageToolCall( + id="call_1", + type="function", + function=Function( + name="get_weather", + arguments=json.dumps({"location": "Tokyo"}), + ), + ), + ChatCompletionMessageToolCall( + id="call_2", + type="function", + function=Function( + name="get_news", + arguments=json.dumps({"topic": "technology"}), + ), + ), + ], + ), + ) + ], + ) + + # Process the output + await handler.process_output_response(response, guardrail) + + # Verify multiple tool calls were extracted + assert guardrail.last_inputs is not None + assert "tool_calls" in guardrail.last_inputs + assert len(guardrail.last_inputs["tool_calls"]) == 2 + + # Verify both tool calls + tool_calls = guardrail.last_inputs["tool_calls"] + assert tool_calls[0]["function"]["name"] == "get_weather" + assert tool_calls[1]["function"]["name"] == "get_news" + + # Verify both were modified + response_tool_calls = response.choices[0].message.tool_calls + args1 = json.loads(response_tool_calls[0].function.arguments) + args2 = json.loads(response_tool_calls[1].function.arguments) + assert args1["location"] == "TOKYO" + assert args2["topic"] == "TECHNOLOGY" + + +if __name__ == "__main__": + # Run the tests + pytest.main([__file__, "-v"]) diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py index 2b192dad5d5..b5c093fdf06 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py @@ -191,7 +191,9 @@ class TestContentFilterGuardrail: with pytest.raises(HTTPException) as exc_info: await guardrail.apply_guardrail( - texts=["My SSN is 123-45-6789"], request_data={}, input_type="request" + inputs={"texts": ["My SSN is 123-45-6789"]}, + request_data={}, + input_type="request", ) assert exc_info.value.status_code == 400 @@ -215,11 +217,12 @@ class TestContentFilterGuardrail: patterns=patterns, ) - result, _ = await guardrail.apply_guardrail( - texts=["Contact me at test@example.com"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["Contact me at test@example.com"]}, request_data={}, input_type="request", ) + result = guardrailed_inputs.get("texts", []) assert result is not None assert len(result) == 1 @@ -243,11 +246,12 @@ class TestContentFilterGuardrail: blocked_words=blocked_words, ) - result, _ = await guardrail.apply_guardrail( - texts=["This is PROPRIETARY information"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["This is PROPRIETARY information"]}, request_data={}, input_type="request", ) + result = guardrailed_inputs.get("texts", []) assert result is not None assert len(result) == 1 @@ -277,11 +281,12 @@ class TestContentFilterGuardrail: patterns=patterns, ) - result, _ = await guardrail.apply_guardrail( - texts=["Contact user@test.com or SSN: 123-45-6789"], + guardrailed_inputs = await guardrail.apply_guardrail( + inputs={"texts": ["Contact user@test.com or SSN: 123-45-6789"]}, request_data={}, input_type="request", ) + result = guardrailed_inputs.get("texts", []) assert result is not None assert len(result) == 1 diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_generic_guardrail_api.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_generic_guardrail_api.py index e6ebada4d2e..f3578fbcefa 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_generic_guardrail_api.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_generic_guardrail_api.py @@ -4,6 +4,7 @@ Tests for Generic Guardrail API integration This test file tests the Generic Guardrail API implementation, specifically focusing on metadata extraction and passing. """ + import os from unittest.mock import AsyncMock, MagicMock, patch @@ -108,9 +109,14 @@ class TestGenericGuardrailAPIConfiguration: headers={"Authorization": "Bearer test-key"}, additional_provider_specific_params={"custom_param": "value"}, ) - assert guardrail.api_base == "https://api.test.guardrail.com/beta/litellm_basic_guardrail_api" + assert ( + guardrail.api_base + == "https://api.test.guardrail.com/beta/litellm_basic_guardrail_api" + ) assert guardrail.headers == {"Authorization": "Bearer test-key"} - assert guardrail.additional_provider_specific_params == {"custom_param": "value"} + assert guardrail.additional_provider_specific_params == { + "custom_param": "value" + } def test_init_with_env_vars(self): """Test initialization with environment variables""" @@ -121,7 +127,10 @@ class TestGenericGuardrailAPIConfiguration: }, ): guardrail = GenericGuardrailAPI() - assert guardrail.api_base == "https://env.api.guardrail.com/beta/litellm_basic_guardrail_api" + assert ( + guardrail.api_base + == "https://env.api.guardrail.com/beta/litellm_basic_guardrail_api" + ) def test_init_without_api_base_raises_error(self): """Test that initialization without API base raises ValueError""" @@ -134,14 +143,20 @@ class TestGenericGuardrailAPIConfiguration: guardrail = GenericGuardrailAPI( api_base="https://api.test.guardrail.com/v1", ) - assert guardrail.api_base == "https://api.test.guardrail.com/v1/beta/litellm_basic_guardrail_api" + assert ( + guardrail.api_base + == "https://api.test.guardrail.com/v1/beta/litellm_basic_guardrail_api" + ) def test_api_base_not_duplicated(self): """Test that endpoint path is not duplicated if already present""" guardrail = GenericGuardrailAPI( api_base="https://api.test.guardrail.com/beta/litellm_basic_guardrail_api", ) - assert guardrail.api_base == "https://api.test.guardrail.com/beta/litellm_basic_guardrail_api" + assert ( + guardrail.api_base + == "https://api.test.guardrail.com/beta/litellm_basic_guardrail_api" + ) class TestMetadataExtraction: @@ -164,7 +179,7 @@ class TestMetadataExtraction: generic_guardrail.async_handler, "post", return_value=mock_response ) as mock_post: await generic_guardrail.apply_guardrail( - texts=["Who is Ishaan?"], + inputs=GenericGuardrailAPIInputs(texts=["Who is Ishaan?"]), request_data=mock_request_data_input, input_type="request", ) @@ -180,7 +195,10 @@ class TestMetadataExtraction: request_metadata = json_payload["request_data"] # Verify metadata was extracted from request_data["metadata"] - assert request_metadata["user_api_key_hash"] == "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b" + assert ( + request_metadata["user_api_key_hash"] + == "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b" + ) assert request_metadata["user_api_key_user_id"] == "default_user_id" assert request_metadata["user_api_key_user_email"] == "test@example.com" assert request_metadata["user_api_key_team_id"] == "test-team" @@ -192,7 +210,7 @@ class TestMetadataExtraction: """Test extracting metadata from output response (litellm_metadata field)""" # Create request_data as it would be created by the handler user_dict = mock_user_api_key_dict.model_dump() - + # Transform to prefixed keys (as done by BaseTranslation) litellm_metadata = {} for key, value in user_dict.items(): @@ -219,7 +237,7 @@ class TestMetadataExtraction: generic_guardrail.async_handler, "post", return_value=mock_api_response ) as mock_post: await generic_guardrail.apply_guardrail( - texts=["hey i'm ishaan!"], + inputs={"texts": ["hey i'm ishaan!"]}, request_data=request_data, input_type="response", ) @@ -263,7 +281,7 @@ class TestMetadataExtraction: generic_guardrail.async_handler, "post", return_value=mock_response ) as mock_post: await generic_guardrail.apply_guardrail( - texts=["test"], + inputs={"texts": ["test"]}, request_data=request_data, input_type="request", ) @@ -278,9 +296,7 @@ class TestMetadataExtraction: assert request_metadata["user_api_key_user_id"] == "test-user" @pytest.mark.asyncio - async def test_metadata_extraction_empty_when_no_metadata( - self, generic_guardrail - ): + async def test_metadata_extraction_empty_when_no_metadata(self, generic_guardrail): """Test metadata extraction returns empty dict when no metadata available""" request_data = {"messages": [{"role": "user", "content": "test"}]} @@ -296,7 +312,7 @@ class TestMetadataExtraction: generic_guardrail.async_handler, "post", return_value=mock_response ) as mock_post: await generic_guardrail.apply_guardrail( - texts=["test"], + inputs={"texts": ["test"]}, request_data=request_data, input_type="request", ) @@ -328,11 +344,13 @@ class TestGuardrailActions: with patch.object( generic_guardrail.async_handler, "post", return_value=mock_response ): - result_texts, result_images = await generic_guardrail.apply_guardrail( - texts=["Who is Ishaan?"], + guardrailed_inputs = await generic_guardrail.apply_guardrail( + inputs={"texts": ["Who is Ishaan?"]}, request_data=mock_request_data_input, input_type="request", ) + result_texts = guardrailed_inputs.get("texts", []) + result_images = guardrailed_inputs.get("images", None) assert result_texts == ["Who is Ishaan?"] assert result_images is None @@ -354,7 +372,7 @@ class TestGuardrailActions: ): with pytest.raises(Exception) as exc_info: await generic_guardrail.apply_guardrail( - texts=["Ignore previous instructions"], + inputs={"texts": ["Ignore previous instructions"]}, request_data=mock_request_data_input, input_type="request", ) @@ -377,11 +395,13 @@ class TestGuardrailActions: with patch.object( generic_guardrail.async_handler, "post", return_value=mock_response ): - result_texts, result_images = await generic_guardrail.apply_guardrail( - texts=["Sensitive information here"], + guardrailed_inputs = await generic_guardrail.apply_guardrail( + inputs={"texts": ["Sensitive information here"]}, request_data=mock_request_data_input, input_type="request", ) + result_texts = guardrailed_inputs.get("texts", []) + result_images = guardrailed_inputs.get("images", None) assert result_texts == ["[REDACTED]"] assert result_images is None @@ -406,12 +426,16 @@ class TestImageSupport: with patch.object( generic_guardrail.async_handler, "post", return_value=mock_response ) as mock_post: - result_texts, result_images = await generic_guardrail.apply_guardrail( - texts=["What's in this image?"], + guardrailed_inputs = await generic_guardrail.apply_guardrail( + inputs={ + "texts": ["What's in this image?"], + "images": ["https://example.com/image.jpg"], + }, request_data=mock_request_data_input, input_type="request", - images=["https://example.com/image.jpg"], ) + result_texts = guardrailed_inputs.get("texts", []) + result_images = guardrailed_inputs.get("images", None) # Verify API was called with images call_args = mock_post.call_args @@ -447,7 +471,7 @@ class TestAdditionalParams: guardrail.async_handler, "post", return_value=mock_response ) as mock_post: await guardrail.apply_guardrail( - texts=["test"], + inputs={"texts": ["test"]}, request_data=mock_request_data_input, input_type="request", ) @@ -455,8 +479,14 @@ class TestAdditionalParams: # Verify API was called with additional params call_args = mock_post.call_args json_payload = call_args.kwargs["json"] - assert json_payload["additional_provider_specific_params"]["custom_threshold"] == 0.8 - assert json_payload["additional_provider_specific_params"]["enable_feature"] is True + assert ( + json_payload["additional_provider_specific_params"]["custom_threshold"] + == 0.8 + ) + assert ( + json_payload["additional_provider_specific_params"]["enable_feature"] + is True + ) class TestErrorHandling: @@ -476,7 +506,7 @@ class TestErrorHandling: ): with pytest.raises(Exception) as exc_info: await generic_guardrail.apply_guardrail( - texts=["test"], + inputs={"texts": ["test"]}, request_data=mock_request_data_input, input_type="request", ) @@ -495,10 +525,9 @@ class TestErrorHandling: ): with pytest.raises(Exception) as exc_info: await generic_guardrail.apply_guardrail( - texts=["test"], + inputs={"texts": ["test"]}, request_data=mock_request_data_input, input_type="request", ) assert "Generic Guardrail API failed" in str(exc_info.value) - diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py index 3e19437fe83..a6b2ae5b3a0 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py @@ -571,7 +571,7 @@ async def test_presidio_sets_guardrail_information_in_request_data(): with patch.object(presidio, "check_pii", mock_check_pii): await presidio.apply_guardrail( - texts=["Test message"], + inputs={"texts": ["Test message"]}, request_data=request_data, input_type="request", ) @@ -623,7 +623,7 @@ async def test_request_data_flows_to_apply_guardrail(): with patch.object(presidio, "check_pii", mock_check_pii): result = await presidio.apply_guardrail( - texts=["Test message"], + inputs={"texts": ["Test message"]}, request_data=request_data, input_type="request", ) From 636f26d8a1641bf0871a5a6fffdf2fa06828f165 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 21:21:27 -0800 Subject: [PATCH 328/349] e2e changes --- tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts | 1 + .../e2e_ui_tests/require_auth_for_dashboard.spec.ts | 2 +- tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts | 4 ++++ .../e2e_ui_tests/view_internal_user.spec.ts | 3 +++ tests/proxy_admin_ui_tests/utils/login.ts | 2 ++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts index b9c16299cc0..c4a1dc77c33 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts @@ -11,6 +11,7 @@ import { test, expect } from "@playwright/test"; test("admin login test", async ({ page }) => { // Go to the specified URL await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); await page.screenshot({ path: "test-results/login_before.png" }); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts index 2b198d6ab7e..4e4bd2fcd93 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts @@ -10,7 +10,7 @@ test.describe("Authentication Checks", () => { page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); const protectedPageUrl = "http://localhost:4000/ui?page=llm-playground"; - const expectedRedirectUrl = "http://localhost:4000/ui/login"; + const expectedRedirectUrl = "http://localhost:4000/ui/login/"; console.log( `Attempting to navigate to protected page: ${protectedPageUrl}` diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts index a384248a5f7..66d0ad29658 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts @@ -21,8 +21,11 @@ test("user search test", async ({ page }) => { // Login first await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); console.log("Navigated to login page"); + page.screenshot({ path: "test-results/search_users_before_login.png" }); + // Wait for login form to be visible await page.waitForSelector('input[name="username"]', { timeout: 10000 }); console.log("Login form is visible"); @@ -128,6 +131,7 @@ test("user filter test", async ({ page }) => { // Login first await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); console.log("Navigated to login page"); // Wait for login form to be visible diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts index dd6f812df4a..a7843d52916 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts @@ -7,6 +7,9 @@ import { test, expect } from "@playwright/test"; test("view internal user page", async ({ page }) => { // Go to the specified URL await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); + + page.screenshot({ path: "test-results/view_internal_user_before_login.png" }); // Enter "admin" in the username input field await page.fill('input[name="username"]', "admin"); diff --git a/tests/proxy_admin_ui_tests/utils/login.ts b/tests/proxy_admin_ui_tests/utils/login.ts index 716efe58c76..5749006cdea 100644 --- a/tests/proxy_admin_ui_tests/utils/login.ts +++ b/tests/proxy_admin_ui_tests/utils/login.ts @@ -3,8 +3,10 @@ import { Page, expect } from "@playwright/test"; export async function loginToUI(page: Page) { // Login first await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); console.log("Navigated to login page"); + page.screenshot({ path: "test-results/login_utils_before.png" }); // Wait for login form to be visible await page.waitForSelector('input[name="username"]', { timeout: 10000 }); console.log("Login form is visible"); From adfbb1c30891ddaa8b09db92471d89c73303c0ea Mon Sep 17 00:00:00 2001 From: codgician <15964984+codgician@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:22:08 +0800 Subject: [PATCH 329/349] docs: document responses and embedding api for github copilot (#17456) --- README.md | 2 +- .../docs/providers/github_copilot.md | 42 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20483ce70c0..4f1a86a648f 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ curl 'http://0.0.0.0:4000/key/generate' \ | [Fireworks AI (`fireworks_ai`)](https://docs.litellm.ai/docs/providers/fireworks_ai) | ✅ | ✅ | ✅ | | | | | | | | | [FriendliAI (`friendliai`)](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | | | | | | | | | [Galadriel (`galadriel`)](https://docs.litellm.ai/docs/providers/galadriel) | ✅ | ✅ | ✅ | | | | | | | | -| [GitHub Copilot (`github_copilot`)](https://docs.litellm.ai/docs/providers/github_copilot) | ✅ | ✅ | ✅ | | | | | | | | +| [GitHub Copilot (`github_copilot`)](https://docs.litellm.ai/docs/providers/github_copilot) | ✅ | ✅ | ✅ | ✅ | | | | | | | | [GitHub Models (`github`)](https://docs.litellm.ai/docs/providers/github) | ✅ | ✅ | ✅ | | | | | | | | | [Google - PaLM](https://docs.litellm.ai/docs/providers/palm) | ✅ | ✅ | ✅ | | | | | | | | | [Google - Vertex AI (`vertex_ai`)](https://docs.litellm.ai/docs/providers/vertex) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | diff --git a/docs/my-website/docs/providers/github_copilot.md b/docs/my-website/docs/providers/github_copilot.md index 2ebe6eacb1c..306c9f949ec 100644 --- a/docs/my-website/docs/providers/github_copilot.md +++ b/docs/my-website/docs/providers/github_copilot.md @@ -15,7 +15,7 @@ https://docs.github.com/en/copilot |-------|-------| | Description | GitHub Copilot Chat API provides access to GitHub's AI-powered coding assistant. | | Provider Route on LiteLLM | `github_copilot/` | -| Supported Endpoints | `/chat/completions` | +| Supported Endpoints | `/chat/completions`, `/embeddings` | | API Reference | [GitHub Copilot docs](https://docs.github.com/en/copilot) | ## Authentication @@ -62,6 +62,34 @@ for chunk in stream: print(chunk.choices[0].delta.content, end="") ``` +### Responses + +For GPT Codex models, only responses API is supported. + +```python showLineNumbers title="GitHub Copilot Responses" +import litellm + +response = await litellm.aresponses( + model="github_copilot/gpt-5.1-codex", + input="Write a Python hello world", + max_output_tokens=500 +) + +print(response) +``` + +### Embedding + +```python showLineNumbers title="GitHub Copilot Embedding" +import litellm + +response = litellm.embedding( + model="github_copilot/text-embedding-3-small", + input=["good morning from litellm"] +) +print(response) +``` + ## Usage - LiteLLM Proxy Add the following to your LiteLLM Proxy configuration file: @@ -71,6 +99,16 @@ model_list: - model_name: github_copilot/gpt-4 litellm_params: model: github_copilot/gpt-4 + - model_name: github_copilot/gpt-5.1-codex + model_info: + mode: responses + litellm_params: + model: github_copilot/gpt-5.1-codex + - model_name: github_copilot/text-embedding-ada-002 + model_info: + mode: embedding + litellm_params: + model: github_copilot/text-embedding-ada-002 ``` Start your LiteLLM Proxy server: @@ -180,7 +218,7 @@ extra_headers = { "editor-version": "vscode/1.85.1", # Editor version "editor-plugin-version": "copilot/1.155.0", # Plugin version "Copilot-Integration-Id": "vscode-chat", # Integration ID - "user-agent": "GithubCopilot/1.155.0" # User agent + "user-agent": "GithubCopilot/1.155.0" # User agent } ``` From a6ce1189899912581714bd80cbf7785b20d1e093 Mon Sep 17 00:00:00 2001 From: Devaj Mody Date: Thu, 4 Dec 2025 00:23:09 -0500 Subject: [PATCH 330/349] fix(openai): add clear error message for empty LLM endpoint responses (#17445) Fixes #17209 - Add validation after response.parse() to detect empty/invalid responses - Raise clear OpenAIError with actionable message - Add 3 unit tests --- litellm/llms/openai/openai.py | 12 +++ .../llms/openai/test_openai_empty_response.py | 97 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/test_litellm/llms/openai/test_openai_empty_response.py diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 3282b7665c0..20842525e59 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -444,6 +444,11 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): else: headers = {} response = raw_response.parse() + if not hasattr(response, "model_dump"): + raise OpenAIError( + status_code=500, + message=f"Empty or invalid response from LLM endpoint. Received: {response!r}. Check the reverse proxy or model server configuration.", + ) return headers, response except openai.APITimeoutError as e: end_time = time.time() @@ -477,7 +482,14 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): else: headers = {} response = raw_response.parse() + if not hasattr(response, "model_dump"): + raise OpenAIError( + status_code=500, + message=f"Empty or invalid response from LLM endpoint. Received: {response!r}. Check the reverse proxy or model server configuration.", + ) return headers, response + except OpenAIError: + raise except Exception as e: if raw_response is not None: raise Exception( diff --git a/tests/test_litellm/llms/openai/test_openai_empty_response.py b/tests/test_litellm/llms/openai/test_openai_empty_response.py new file mode 100644 index 00000000000..fb42918f381 --- /dev/null +++ b/tests/test_litellm/llms/openai/test_openai_empty_response.py @@ -0,0 +1,97 @@ +""" +Test for issue #17209: Clearer error when LLM endpoint returns empty response +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../../../..")) + +from litellm.llms.openai.openai import OpenAIChatCompletion +from litellm.llms.openai.common_utils import OpenAIError + +class TestEmptyResponseHandling: + """Test that empty/invalid responses from LLM endpoints produce clear error messages""" + + def test_sync_empty_string_response_raises_clear_error(self): + """ + Test that when an OpenAI-compatible endpoint returns an empty string, + we get a clear error instead of "'str' object has no attribute 'model_dump'" + """ + openai_chat = OpenAIChatCompletion() + + # Mock the raw response to return an empty string from parse() + mock_raw_response = MagicMock() + mock_raw_response.headers = {} + mock_raw_response.parse.return_value = "" # Empty string response + + mock_client = MagicMock() + mock_client.chat.completions.with_raw_response.create.return_value = ( + mock_raw_response + ) + + with pytest.raises(OpenAIError) as exc_info: + openai_chat.make_sync_openai_chat_completion_request( + openai_client=mock_client, + data={"messages": [{"role": "user", "content": "test"}]}, + timeout=30, + logging_obj=MagicMock(), + ) + + assert "Empty or invalid response from LLM endpoint" in str(exc_info.value) + assert "Check the reverse proxy or model server configuration" in str( + exc_info.value + ) + + def test_sync_none_response_raises_clear_error(self): + """Test that None response also produces a clear error""" + openai_chat = OpenAIChatCompletion() + + mock_raw_response = MagicMock() + mock_raw_response.headers = {} + mock_raw_response.parse.return_value = None + + mock_client = MagicMock() + mock_client.chat.completions.with_raw_response.create.return_value = ( + mock_raw_response + ) + + with pytest.raises(OpenAIError) as exc_info: + openai_chat.make_sync_openai_chat_completion_request( + openai_client=mock_client, + data={"messages": [{"role": "user", "content": "test"}]}, + timeout=30, + logging_obj=MagicMock(), + ) + + assert "Empty or invalid response from LLM endpoint" in str(exc_info.value) + + def test_valid_response_passes_through(self): + """Test that a valid response with model_dump passes through correctly""" + openai_chat = OpenAIChatCompletion() + + # Create a mock response that has model_dump (like a real Pydantic model) + mock_response = MagicMock() + mock_response.model_dump.return_value = {"choices": []} + + mock_raw_response = MagicMock() + mock_raw_response.headers = {"x-request-id": "123"} + mock_raw_response.parse.return_value = mock_response + + mock_client = MagicMock() + mock_client.chat.completions.with_raw_response.create.return_value = ( + mock_raw_response + ) + + headers, response = openai_chat.make_sync_openai_chat_completion_request( + openai_client=mock_client, + data={"messages": [{"role": "user", "content": "test"}]}, + timeout=30, + logging_obj=MagicMock(), + ) + + assert response == mock_response + assert headers == {"x-request-id": "123"} From 06d42fbd3086fea30b623227c81a465c5942f422 Mon Sep 17 00:00:00 2001 From: Haiyi Date: Thu, 4 Dec 2025 16:25:06 +1100 Subject: [PATCH 331/349] Fix: Skip empty text blocks in Anthropic system messages (#17442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When translating system messages for the Anthropic API, empty text content blocks cause the error "messages: text content blocks must be non-empty". This fix skips empty string content and empty text blocks in list content to prevent this error. Fixes issue with Vertex AI Anthropic API calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- litellm/llms/anthropic/chat/transformation.py | 9 +- .../test_anthropic_chat_transformation.py | 87 ++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 4221dfacf34..bdc986ae27f 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -786,6 +786,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): valid_content: bool = False system_message_block = ChatCompletionSystemMessage(**message) if isinstance(system_message_block["content"], str): + # Skip empty text blocks - Anthropic API raises errors for empty text + if not system_message_block["content"]: + continue anthropic_system_message_content = AnthropicSystemMessageContent( type="text", text=system_message_block["content"], @@ -800,10 +803,14 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): valid_content = True elif isinstance(message["content"], list): for _content in message["content"]: + # Skip empty text blocks - Anthropic API raises errors for empty text + text_value = _content.get("text") + if _content.get("type") == "text" and not text_value: + continue anthropic_system_message_content = ( AnthropicSystemMessageContent( type=_content.get("type"), - text=_content.get("text"), + text=text_value, ) ) if "cache_control" in _content: diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 09c16add77a..6e05ca564fc 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -1290,10 +1290,95 @@ def test_effort_with_other_features(): litellm_params={}, headers={} ) - + # Verify all features are present assert "output_config" in result assert result["output_config"]["effort"] == "low" assert "tools" in result assert len(result["tools"]) > 0 assert "thinking" in result + + +def test_translate_system_message_skips_empty_string_content(): + """ + Test that translate_system_message skips system messages with empty string content. + + Fixes: Vertex AI Anthropic API error "messages: text content blocks must be non-empty" + """ + config = AnthropicConfig() + + # Test empty string content - should not produce any anthropic system message content + messages = [ + {"role": "system", "content": ""}, + {"role": "user", "content": "Hello"}, + ] + + result = config.translate_system_message(messages) + + # Empty system message should produce no anthropic content blocks + assert len(result) == 0 + + +def test_translate_system_message_skips_empty_list_content(): + """ + Test that translate_system_message skips empty text blocks in list content. + + Fixes: Vertex AI Anthropic API error "messages: text content blocks must be non-empty" + """ + config = AnthropicConfig() + + # Test list content with empty text block + messages = [ + {"role": "system", "content": [ + {"type": "text", "text": ""}, + {"type": "text", "text": "Valid content"}, + {"type": "text", "text": ""}, + ]}, + {"role": "user", "content": "Hello"}, + ] + + result = config.translate_system_message(messages) + + # Only non-empty text blocks should be included + assert len(result) == 1 + assert result[0]["text"] == "Valid content" + + +def test_translate_system_message_preserves_valid_content(): + """ + Test that translate_system_message preserves valid system message content. + """ + config = AnthropicConfig() + + # Test valid string content + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello"}, + ] + + result = config.translate_system_message(messages) + + assert len(result) == 1 + assert result[0]["type"] == "text" + assert result[0]["text"] == "You are a helpful assistant." + + +def test_translate_system_message_preserves_cache_control(): + """ + Test that translate_system_message preserves cache_control on valid content. + """ + config = AnthropicConfig() + + # Test list content with cache_control + messages = [ + {"role": "system", "content": [ + {"type": "text", "text": "Cached content", "cache_control": {"type": "ephemeral"}}, + ]}, + {"role": "user", "content": "Hello"}, + ] + + result = config.translate_system_message(messages) + + assert len(result) == 1 + assert result[0]["text"] == "Cached content" + assert result[0]["cache_control"] == {"type": "ephemeral"} From 18796235025767b84479811437cdf7d27e0c9279 Mon Sep 17 00:00:00 2001 From: Andrew Truong Date: Thu, 4 Dec 2025 00:26:22 -0500 Subject: [PATCH 332/349] feat: Basic Weave OTEL integration (#17439) * test * testfix --- litellm/__init__.py | 1 + litellm/integrations/opentelemetry.py | 5 + litellm/integrations/weave/__init__.py | 7 + litellm/integrations/weave/weave_otel.py | 329 ++++++++++++++++++ .../custom_logger_registry.py | 1 + litellm/litellm_core_utils/litellm_logging.py | 25 ++ litellm/types/integrations/weave_otel.py | 31 ++ litellm/types/utils.py | 4 + poetry.lock | 2 +- .../integrations/test_weave_otel.py | 147 ++++++++ 10 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 litellm/integrations/weave/__init__.py create mode 100644 litellm/integrations/weave/weave_otel.py create mode 100644 litellm/types/integrations/weave_otel.py create mode 100644 tests/test_litellm/integrations/test_weave_otel.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 3d40a9b15cf..fdae470fe6a 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -151,6 +151,7 @@ _custom_logger_compatible_callbacks_literal = Literal[ "mlflow", "langfuse", "langfuse_otel", + "weave_otel", "pagerduty", "humanloop", "gcs_pubsub", diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index 23bffc6c7e5..9f9d45d0e7d 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -1081,6 +1081,11 @@ class OpenTelemetry(CustomLogger): span, kwargs, response_obj ) return + elif self.callback_name == "weave_otel": + from litellm.integrations.weave.weave_otel import set_weave_otel_attributes + + set_weave_otel_attributes(span, kwargs, response_obj) + return from litellm.proxy._types import SpanAttributes optional_params = kwargs.get("optional_params", {}) diff --git a/litellm/integrations/weave/__init__.py b/litellm/integrations/weave/__init__.py new file mode 100644 index 00000000000..49af77b55e8 --- /dev/null +++ b/litellm/integrations/weave/__init__.py @@ -0,0 +1,7 @@ +""" +Weave (W&B) integration for LiteLLM via OpenTelemetry. +""" + +from litellm.integrations.weave.weave_otel import WeaveOtelLogger + +__all__ = ["WeaveOtelLogger"] diff --git a/litellm/integrations/weave/weave_otel.py b/litellm/integrations/weave/weave_otel.py new file mode 100644 index 00000000000..167deaf2cdc --- /dev/null +++ b/litellm/integrations/weave/weave_otel.py @@ -0,0 +1,329 @@ +from __future__ import annotations + +import base64 +import json +import os +from typing import TYPE_CHECKING, Any, Optional + +from opentelemetry.trace import Status, StatusCode +from typing_extensions import override + +from litellm._logging import verbose_logger +from litellm.integrations._types.open_inference import SpanAttributes as OpenInferenceSpanAttributes +from litellm.integrations.arize import _utils +from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig +from litellm.integrations.opentelemetry_utils.base_otel_llm_obs_attributes import ( + BaseLLMObsOTELAttributes, + safe_set_attribute, +) +from litellm.litellm_core_utils.safe_json_dumps import safe_dumps +from litellm.types.integrations.weave_otel import WeaveOtelConfig, WeaveSpanAttributes +from litellm.types.utils import StandardCallbackDynamicParams + +if TYPE_CHECKING: + from opentelemetry.trace import Span + + +# Weave OTEL endpoint +# Multi-tenant cloud: https://trace.wandb.ai/otel/v1/traces +# Dedicated cloud: https://.wandb.io/traces/otel/v1/traces +WEAVE_BASE_URL = "https://trace.wandb.ai" +WEAVE_OTEL_ENDPOINT = "/otel/v1/traces" + + +class WeaveLLMObsOTELAttributes(BaseLLMObsOTELAttributes): + """ + Weave-specific LLM observability OTEL attributes. + + Weave automatically maps attributes from multiple frameworks including + GenAI, OpenInference, Langfuse, and others. + """ + + @staticmethod + @override + def set_messages(span: "Span", kwargs: dict[str, Any]): + """Set input messages as span attributes using OpenInference conventions.""" + + messages = kwargs.get("messages") or [] + optional_params = kwargs.get("optional_params") or {} + + prompt = {"messages": messages} + functions = optional_params.get("functions") + tools = optional_params.get("tools") + if functions is not None: + prompt["functions"] = functions + if tools is not None: + prompt["tools"] = tools + safe_set_attribute(span, OpenInferenceSpanAttributes.INPUT_VALUE, json.dumps(prompt)) + + +def _set_weave_specific_attributes(span: Span, kwargs: dict[str, Any], response_obj: Any): + """ + Sets Weave-specific metadata attributes onto the OTEL span. + + Based on Weave's OTEL attribute mappings from: + https://github.com/wandb/weave/blob/master/weave/trace_server/opentelemetry/constants.py + """ + + # Extract all needed data upfront + litellm_params = kwargs.get("litellm_params") or {} + # optional_params = kwargs.get("optional_params") or {} + metadata = kwargs.get("metadata") or {} + model = kwargs.get("model") or "" + custom_llm_provider = litellm_params.get("custom_llm_provider") or "" + + # Weave supports a custom display name and will default to the model name if not provided. + display_name = metadata.get("display_name") + if not display_name and model: + if custom_llm_provider: + display_name = f"{custom_llm_provider}/{model}" + else: + display_name = model + if display_name: + display_name = display_name.replace("/", "__") + safe_set_attribute(span, WeaveSpanAttributes.DISPLAY_NAME.value, display_name) + + # Weave threads are OpenInference sessions. + if (session_id := metadata.get("session_id")) is not None: + if isinstance(session_id, (list, dict)): + session_id = safe_dumps(session_id) + safe_set_attribute(span, WeaveSpanAttributes.THREAD_ID.value, session_id) + safe_set_attribute(span, WeaveSpanAttributes.IS_TURN.value, True) + + # Response attributes are already set by _utils.set_attributes, + # but we override them here to better match Weave's expectations + if response_obj: + output_dict = None + if hasattr(response_obj, "model_dump"): + output_dict = response_obj.model_dump() + elif hasattr(response_obj, "get"): + output_dict = response_obj + + if output_dict: + safe_set_attribute(span, OpenInferenceSpanAttributes.OUTPUT_VALUE, safe_dumps(output_dict)) + + +def _get_weave_authorization_header(api_key: str) -> str: + """ + Get the authorization header for Weave OpenTelemetry. + + Weave uses Basic auth with format: api: + """ + auth_string = f"api:{api_key}" + auth_header = base64.b64encode(auth_string.encode()).decode() + return f"Basic {auth_header}" + + +def get_weave_otel_config() -> WeaveOtelConfig: + """ + Retrieves the Weave OpenTelemetry configuration based on environment variables. + + Environment Variables: + WANDB_API_KEY: Required. W&B API key for authentication. + WANDB_PROJECT_ID: Required. Project ID in format /. + WANDB_HOST: Optional. Custom Weave host URL. Defaults to cloud endpoint. + + Returns: + WeaveOtelConfig: A Pydantic model containing Weave OTEL configuration. + + Raises: + ValueError: If required environment variables are missing. + """ + api_key = os.getenv("WANDB_API_KEY") + project_id = os.getenv("WANDB_PROJECT_ID") + host = os.getenv("WANDB_HOST") + + if not api_key: + raise ValueError("WANDB_API_KEY must be set for Weave OpenTelemetry integration.") + + if not project_id: + raise ValueError( + "WANDB_PROJECT_ID must be set for Weave OpenTelemetry integration. Format: /" + ) + + if host: + if not host.startswith("http"): + host = "https://" + host + # Self-managed instances use a different path + endpoint = host.rstrip("/") + WEAVE_OTEL_ENDPOINT + verbose_logger.debug(f"Using Weave OTEL endpoint from host: {endpoint}") + else: + endpoint = WEAVE_BASE_URL + WEAVE_OTEL_ENDPOINT + verbose_logger.debug(f"Using Weave cloud endpoint: {endpoint}") + + # Weave uses Basic auth with format: api: + auth_header = _get_weave_authorization_header(api_key=api_key) + otlp_auth_headers = f"Authorization={auth_header},project_id={project_id}" + + # Set standard OTEL environment variables + os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint + os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = otlp_auth_headers + + return WeaveOtelConfig( + otlp_auth_headers=otlp_auth_headers, + endpoint=endpoint, + project_id=project_id, + protocol="otlp_http", + ) + + +def set_weave_otel_attributes(span: Span, kwargs: dict[str, Any], response_obj: Any): + """ + Sets OpenTelemetry span attributes for Weave observability. + Uses the same attribute setting logic as other OTEL integrations for consistency. + """ + _utils.set_attributes(span, kwargs, response_obj, WeaveLLMObsOTELAttributes) + _set_weave_specific_attributes(span=span, kwargs=kwargs, response_obj=response_obj) + + +class WeaveOtelLogger(OpenTelemetry): + """ + Weave (W&B) OpenTelemetry Logger for LiteLLM. + + Sends LLM traces to Weave via the OpenTelemetry Protocol (OTLP). + + Environment Variables: + WANDB_API_KEY: Required. Weights & Biases API key for authentication. + WANDB_PROJECT_ID: Required. Project ID in format /. + WANDB_HOST: Optional. Custom Weave host URL. Defaults to cloud endpoint. + + Usage: + litellm.callbacks = ["weave_otel"] + + Or manually: + from litellm.integrations.weave.weave_otel import WeaveOtelLogger + weave_logger = WeaveOtelLogger(callback_name="weave_otel") + litellm.callbacks = [weave_logger] + + Reference: + https://docs.wandb.ai/weave/guides/tracking/otel + """ + + def __init__( + self, + config: Optional[OpenTelemetryConfig] = None, + callback_name: Optional[str] = "weave_otel", + **kwargs, + ): + """ + Initialize WeaveOtelLogger. + + If config is not provided, automatically configures from environment variables + (WANDB_API_KEY, WANDB_PROJECT_ID, WANDB_HOST) via get_weave_otel_config(). + """ + if config is None: + # Auto-configure from Weave environment variables + weave_config = get_weave_otel_config() + + config = OpenTelemetryConfig( + exporter=weave_config.protocol, + endpoint=weave_config.endpoint, + headers=weave_config.otlp_auth_headers, + ) + + super().__init__(config=config, callback_name=callback_name, **kwargs) + + def _maybe_log_raw_request(self, kwargs, response_obj, start_time, end_time, parent_span): + """ + Override to skip creating the raw_gen_ai_request child span. + + For Weave, we only want a single span per LLM call. The parent span + already contains all the necessary attributes, so the child span + is redundant. + """ + pass + + def _start_primary_span( + self, + kwargs, + response_obj, + start_time, + end_time, + context, + parent_span=None, + ): + """ + Override to always create a child span instead of reusing the parent span. + + This ensures that wrapper spans (like "B", "C", "D", "E") remain separate + from the LiteLLM LLM call spans, creating proper nesting in Weave. + """ + + otel_tracer = self.get_tracer_to_use_for_request(kwargs) + # Always create a new child span, even if parent_span is provided + # This ensures wrapper spans remain separate from LLM call spans + span = otel_tracer.start_span( + name=self._get_span_name(kwargs), + start_time=self._to_ns(start_time), + context=context, + ) + span.set_status(Status(StatusCode.OK)) + self.set_attributes(span, kwargs, response_obj) + span.end(end_time=self._to_ns(end_time)) + return span + + def _handle_success(self, kwargs, response_obj, start_time, end_time): + """ + Override to prevent ending externally created parent spans. + + When wrapper spans (like "B", "C", "D", "E") are provided as parent spans, + they should be managed by the user code, not ended by LiteLLM. + """ + + verbose_logger.debug( + "Weave OpenTelemetry Logger: Logging kwargs: %s, OTEL config settings=%s", + kwargs, + self.config, + ) + ctx, parent_span = self._get_span_context(kwargs) + + # Always create a child span (handled by _start_primary_span override) + primary_span_parent = None + + # 1. Primary span + span = self._start_primary_span(kwargs, response_obj, start_time, end_time, ctx, primary_span_parent) + + # 2. Raw-request sub-span (skipped for Weave via _maybe_log_raw_request override) + self._maybe_log_raw_request(kwargs, response_obj, start_time, end_time, span) + + # 3. Guardrail span + self._create_guardrail_span(kwargs=kwargs, context=ctx) + + # 4. Metrics & cost recording + self._record_metrics(kwargs, response_obj, start_time, end_time) + + # 5. Semantic logs. + if self.config.enable_events: + self._emit_semantic_logs(kwargs, response_obj, span) + + # 6. Don't end parent span - it's managed by user code + # Since we always create a child span (never reuse parent), the parent span + # lifecycle is owned by the user. This prevents double-ending of wrapper spans + # like "B", "C", "D", "E" that users create and manage themselves. + + def construct_dynamic_otel_headers( + self, standard_callback_dynamic_params: StandardCallbackDynamicParams + ) -> dict | None: + """ + Construct dynamic Weave headers from standard callback dynamic params. + + This is used for team/key based logging. + + Returns: + dict: A dictionary of dynamic Weave headers + """ + dynamic_headers = {} + + dynamic_wandb_api_key = standard_callback_dynamic_params.get("wandb_api_key") + dynamic_weave_project_id = standard_callback_dynamic_params.get("weave_project_id") + + if dynamic_wandb_api_key: + auth_header = _get_weave_authorization_header( + api_key=dynamic_wandb_api_key, + ) + dynamic_headers["Authorization"] = auth_header + + if dynamic_weave_project_id: + dynamic_headers["project_id"] = dynamic_weave_project_id + + return dynamic_headers if dynamic_headers else None diff --git a/litellm/litellm_core_utils/custom_logger_registry.py b/litellm/litellm_core_utils/custom_logger_registry.py index 538ef6be283..fdc9f374553 100644 --- a/litellm/litellm_core_utils/custom_logger_registry.py +++ b/litellm/litellm_core_utils/custom_logger_registry.py @@ -75,6 +75,7 @@ class CustomLoggerRegistry: "langfuse_otel": OpenTelemetry, "arize_phoenix": OpenTelemetry, "langtrace": OpenTelemetry, + "weave_otel": OpenTelemetry, "mlflow": MlflowLogger, "langfuse": LangfusePromptManagement, "otel": OpenTelemetry, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 6440e5f6dee..2b52d58e29d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -3802,6 +3802,31 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 ) _in_memory_loggers.append(_otel_logger) return _otel_logger # type: ignore + elif logging_integration == "weave_otel": + from litellm.integrations.opentelemetry import ( + OpenTelemetryConfig, + ) + from litellm.integrations.weave.weave_otel import WeaveOtelLogger, get_weave_otel_config + + weave_otel_config = get_weave_otel_config() + + otel_config = OpenTelemetryConfig( + exporter=weave_otel_config.protocol, + endpoint=weave_otel_config.endpoint, + headers=weave_otel_config.otlp_auth_headers, + ) + + for callback in _in_memory_loggers: + if ( + isinstance(callback, WeaveOtelLogger) + and callback.callback_name == "weave_otel" + ): + return callback # type: ignore + _otel_logger = WeaveOtelLogger( + config=otel_config, callback_name="weave_otel" + ) + _in_memory_loggers.append(_otel_logger) + return _otel_logger # type: ignore elif logging_integration == "pagerduty": for callback in _in_memory_loggers: if isinstance(callback, PagerDutyAlerting): diff --git a/litellm/types/integrations/weave_otel.py b/litellm/types/integrations/weave_otel.py new file mode 100644 index 00000000000..5b40ff85340 --- /dev/null +++ b/litellm/types/integrations/weave_otel.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from enum import Enum +from typing import Literal + +from pydantic import BaseModel + + +class WeaveOtelConfig(BaseModel): + """Configuration for Weave OpenTelemetry integration.""" + + otlp_auth_headers: str | None = None + endpoint: str | None = None + project_id: str | None = None + protocol: Literal["otlp_grpc", "otlp_http"] = "otlp_http" + + +class WeaveSpanAttributes(str, Enum): + """ + Weave-specific span attributes for OpenTelemetry traces. + + Based on Weave's OTEL attribute mappings from: + https://github.com/wandb/weave/blob/master/weave/trace_server/opentelemetry/constants.py + """ + + DISPLAY_NAME = "wandb.display_name" + + # Thread organization, similar to OpenInference session_id. + THREAD_ID = "wandb.thread_id" + IS_TURN = "wandb.is_turn" + diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 3d92e78d494..65457fd3039 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2676,6 +2676,10 @@ class StandardCallbackDynamicParams(TypedDict, total=False): posthog_api_key: Optional[str] posthog_api_url: Optional[str] + # Weave (W&B) dynamic params + wandb_api_key: Optional[str] + weave_project_id: Optional[str] + # Logging settings turn_off_message_logging: Optional[bool] # when true will not log messages litellm_disabled_callbacks: Optional[List[str]] diff --git a/poetry.lock b/poetry.lock index 15140856781..711ab0bbeae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aiofiles" diff --git a/tests/test_litellm/integrations/test_weave_otel.py b/tests/test_litellm/integrations/test_weave_otel.py new file mode 100644 index 00000000000..440c0888512 --- /dev/null +++ b/tests/test_litellm/integrations/test_weave_otel.py @@ -0,0 +1,147 @@ +import os +from unittest.mock import MagicMock, patch + +import pytest + +from litellm.integrations.weave.weave_otel import ( + _set_weave_specific_attributes, + get_weave_otel_config, +) +from litellm.types.integrations.weave_otel import WeaveOtelConfig, WeaveSpanAttributes + + +def test_get_weave_otel_config(): + """Test config creation with required env vars and error cases for missing vars.""" + # Test successful config creation with required environment variables + with patch.dict( + os.environ, + { + "WANDB_API_KEY": "test_api_key", + "WANDB_PROJECT_ID": "test-entity/test-project", + }, + clear=True, + ): + config = get_weave_otel_config() + + assert isinstance(config, WeaveOtelConfig) + assert config.protocol == "otlp_http" + assert config.project_id == "test-entity/test-project" + assert config.otlp_auth_headers is not None + assert "Authorization=" in config.otlp_auth_headers + assert "project_id=test-entity/test-project" in config.otlp_auth_headers + assert config.endpoint == "https://trace.wandb.ai/otel/v1/traces" + + # Verify environment variables were set + assert os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] == "https://trace.wandb.ai/otel/v1/traces" + assert os.environ["OTEL_EXPORTER_OTLP_HEADERS"] == config.otlp_auth_headers + + # Test ValueError when WANDB_API_KEY is missing + with patch.dict(os.environ, {"WANDB_PROJECT_ID": "test-entity/test-project"}, clear=True): + with pytest.raises(ValueError, match="WANDB_API_KEY must be set"): + get_weave_otel_config() + + # Test ValueError when WANDB_PROJECT_ID is missing + with patch.dict(os.environ, {"WANDB_API_KEY": "test_api_key"}, clear=True): + with pytest.raises(ValueError, match="WANDB_PROJECT_ID must be set"): + get_weave_otel_config() + + +def test_get_weave_otel_config_with_custom_host(): + """Test config creation with custom WANDB_HOST.""" + # Test with host that already has https:// + with patch.dict( + os.environ, + { + "WANDB_API_KEY": "test_api_key", + "WANDB_PROJECT_ID": "test-entity/test-project", + "WANDB_HOST": "https://custom.wandb.io", + }, + clear=True, + ): + config = get_weave_otel_config() + assert config.endpoint == "https://custom.wandb.io/otel/v1/traces" + assert os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] == "https://custom.wandb.io/otel/v1/traces" + + # Test with host without http:// or https:// + with patch.dict( + os.environ, + { + "WANDB_API_KEY": "test_api_key", + "WANDB_PROJECT_ID": "test-entity/test-project", + "WANDB_HOST": "custom.wandb.io", + }, + clear=True, + ): + config = get_weave_otel_config() + assert config.endpoint == "https://custom.wandb.io/otel/v1/traces" + + # Test with host with trailing slash + with patch.dict( + os.environ, + { + "WANDB_API_KEY": "test_api_key", + "WANDB_PROJECT_ID": "test-entity/test-project", + "WANDB_HOST": "https://custom.wandb.io/", + }, + clear=True, + ): + config = get_weave_otel_config() + assert config.endpoint == "https://custom.wandb.io/otel/v1/traces" + + + + + +def test_set_weave_specific_attributes_display_name_from_metadata(): + """Test _set_weave_specific_attributes sets display_name from metadata.""" + mock_span = MagicMock() + kwargs = { + "metadata": {"display_name": "custom-display-name"}, + "model": "gpt-4", + } + + with patch("litellm.integrations.weave.weave_otel.safe_set_attribute") as mock_safe_set: + _set_weave_specific_attributes(mock_span, kwargs, None) + + # Should set display_name from metadata + mock_safe_set.assert_any_call( + mock_span, WeaveSpanAttributes.DISPLAY_NAME.value, "custom-display-name" + ) + + +def test_set_weave_specific_attributes_display_name_from_model(): + """Test _set_weave_specific_attributes sets display_name from model when not in metadata.""" + mock_span = MagicMock() + kwargs = { + "model": "openai/gpt-4o-mini", + "metadata": {}, + } + + with patch("litellm.integrations.weave.weave_otel.safe_set_attribute") as mock_safe_set: + _set_weave_specific_attributes(mock_span, kwargs, None) + + # Should set display_name from model + mock_safe_set.assert_any_call( + mock_span, WeaveSpanAttributes.DISPLAY_NAME.value, "openai__gpt-4o-mini" + ) + + + +def test_set_weave_specific_attributes_thread_id_and_is_turn(): + """Test _set_weave_specific_attributes sets thread_id and is_turn from session_id.""" + mock_span = MagicMock() + kwargs = { + "metadata": {"session_id": "session-123"}, + } + + with patch("litellm.integrations.weave.weave_otel.safe_set_attribute") as mock_safe_set: + _set_weave_specific_attributes(mock_span, kwargs, None) + + # Should set thread_id and is_turn + mock_safe_set.assert_any_call( + mock_span, WeaveSpanAttributes.THREAD_ID.value, "session-123" + ) + mock_safe_set.assert_any_call( + mock_span, WeaveSpanAttributes.IS_TURN.value, True + ) + From 6d831ffff4e279e859c733ed823ed7214da0ecea Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:28:13 -0300 Subject: [PATCH 333/349] fix(ocr): add missing ocr and aocr to CallTypes enum (#17435) Add `ocr` and `aocr` entries to the CallTypes enum to fix the ValueError that occurs when using the /v1/ocr endpoint with guardrails enabled. The OCR endpoint uses route_type="aocr", but the CallTypes enum was missing these values, causing guardrail hooks to fail when trying to instantiate CallTypes("aocr"). Fixes #17381 --- litellm/types/utils.py | 6 +++++ tests/test_litellm/test_main.py | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 65457fd3039..4861510da24 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -340,6 +340,12 @@ class CallTypes(str, Enum): generate_content_stream = "generate_content_stream" agenerate_content_stream = "agenerate_content_stream" + ######################################################### + # OCR Call Types + ######################################################### + ocr = "ocr" + aocr = "aocr" + ######################################################### # MCP Call Types ######################################################### diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index 71ede0958f5..d2416f8db8c 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -1461,3 +1461,43 @@ async def test_async_mock_completion_stream_with_model_response(): accumulated_content += chunk.choices[0].delta.content assert "This is an async test response" in accumulated_content or len(chunks) > 0 + + +class TestCallTypesOCR: + """Test that OCR call types are properly defined in CallTypes enum. + + Fixes https://github.com/BerriAI/litellm/issues/17381 + """ + + def test_ocr_call_type_exists(self): + """Test that CallTypes.ocr exists and has correct value.""" + from litellm.types.utils import CallTypes + + assert hasattr(CallTypes, "ocr") + assert CallTypes.ocr.value == "ocr" + + def test_aocr_call_type_exists(self): + """Test that CallTypes.aocr exists and has correct value.""" + from litellm.types.utils import CallTypes + + assert hasattr(CallTypes, "aocr") + assert CallTypes.aocr.value == "aocr" + + def test_ocr_call_type_from_string(self): + """Test that CallTypes can be constructed from 'ocr' string.""" + from litellm.types.utils import CallTypes + + call_type = CallTypes("ocr") + assert call_type == CallTypes.ocr + + def test_aocr_call_type_from_string(self): + """Test that CallTypes can be constructed from 'aocr' string. + + This is the actual use case that was failing - the OCR endpoint + uses route_type='aocr' and guardrails try to instantiate + CallTypes('aocr'). + """ + from litellm.types.utils import CallTypes + + call_type = CallTypes("aocr") + assert call_type == CallTypes.aocr From b05f6d3cfdb6c77be37e4a39eaa7dc2fb41a1f0a Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:31:13 -0300 Subject: [PATCH 334/349] fix(groq): check supports_response_schema before using json_tool_call workaround (#17438) Fixes issue where LiteLLM used the json_tool_call workaround for all Groq models, even those that support native json_schema (e.g., gpt-oss-120b, llama-4, kimi-k2). This caused errors when users passed their own tools alongside structured outputs. Changes: - Check `supports_response_schema()` before using the workaround - Only use json_tool_call workaround for models without native support - Add clear error message when using workaround with user-provided tools - Update model config to set `supports_response_schema: false` for models that don't support native json_schema - Add unit tests for structured output handling --- litellm/llms/groq/chat/transformation.py | 53 +++++-- ...odel_prices_and_context_window_backup.json | 36 ++--- model_prices_and_context_window.json | 36 ++--- tests/llm_translation/test_groq.py | 134 ++++++++++++++++++ 4 files changed, 209 insertions(+), 50 deletions(-) diff --git a/litellm/llms/groq/chat/transformation.py b/litellm/llms/groq/chat/transformation.py index 20e0d412edc..a75ecd8cc7b 100644 --- a/litellm/llms/groq/chat/transformation.py +++ b/litellm/llms/groq/chat/transformation.py @@ -218,22 +218,47 @@ class GroqChatConfig(OpenAILikeChatConfig): When using tools in this way: - https://docs.anthropic.com/en/docs/build-with-claude/tool-use#json-mode - You usually want to provide a single tool - You should set tool_choice (see Forcing tool use) to instruct the model to explicitly use that tool - - Remember that the model will pass the input to the tool, so the name of the tool and description should be from the model’s perspective. + - Remember that the model will pass the input to the tool, so the name of the tool and description should be from the model's perspective. + + Note: This workaround is only for models that don't support native json_schema. + Models like gpt-oss-120b, llama-4, kimi-k2 support native json_schema and should + pass response_format directly to Groq. + See: https://console.groq.com/docs/structured-outputs#supported-models """ if json_schema is not None: - _tool_choice = { - "type": "function", - "function": {"name": "json_tool_call"}, - } - _tool = self._create_json_tool_call_for_response_format( - json_schema=json_schema, - ) - optional_params["tools"] = [_tool] - optional_params["tool_choice"] = _tool_choice - optional_params["json_mode"] = True - non_default_params.pop( - "response_format", None - ) # only remove if it's a json_schema - handled via using groq's tool calling params. + # Check if model supports native response_schema + if not litellm.supports_response_schema( + model=model, custom_llm_provider="groq" + ): + # Check if user is also passing tools - this combination won't work + # See: https://console.groq.com/docs/structured-outputs + # "Streaming and tool use are not currently supported with Structured Outputs" + if "tools" in non_default_params: + raise litellm.BadRequestError( + message=f"Groq model '{model}' does not support native structured outputs. " + "LiteLLM uses a tool-calling workaround for structured outputs on this model, " + "which is incompatible with user-provided tools. " + "Either use a model that supports native structured outputs " + "(e.g., gpt-oss-120b, llama-4, kimi-k2), or remove the tools parameter. " + "See: https://console.groq.com/docs/structured-outputs#supported-models", + model=model, + llm_provider="groq", + ) + # Use workaround only for models without native support + _tool_choice = { + "type": "function", + "function": {"name": "json_tool_call"}, + } + _tool = self._create_json_tool_call_for_response_format( + json_schema=json_schema, + ) + optional_params["tools"] = [_tool] + optional_params["tool_choice"] = _tool_choice + optional_params["json_mode"] = True + non_default_params.pop( + "response_format", None + ) # only remove if it's a json_schema - handled via using groq's tool calling params. + # else: model supports native json_schema, let response_format pass through optional_params = super().map_openai_params( non_default_params, optional_params, model, drop_params ) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index dbaa60e0f1d..903b86484cd 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -16881,7 +16881,7 @@ "output_cost_per_token": 9.9e-07, "supports_function_calling": true, "supports_reasoning": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/distil-whisper-large-v3-en": { @@ -16900,7 +16900,7 @@ "mode": "chat", "output_cost_per_token": 7e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/gemma2-9b-it": { @@ -16912,7 +16912,7 @@ "mode": "chat", "output_cost_per_token": 2e-07, "supports_function_calling": false, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": false }, "groq/llama-3.1-405b-reasoning": { @@ -16924,7 +16924,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.1-70b-versatile": { @@ -16937,7 +16937,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.1-8b-instant": { @@ -16949,7 +16949,7 @@ "mode": "chat", "output_cost_per_token": 8e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-11b-text-preview": { @@ -16962,7 +16962,7 @@ "mode": "chat", "output_cost_per_token": 1.8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-11b-vision-preview": { @@ -16975,7 +16975,7 @@ "mode": "chat", "output_cost_per_token": 1.8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true, "supports_vision": true }, @@ -16989,7 +16989,7 @@ "mode": "chat", "output_cost_per_token": 4e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-3b-preview": { @@ -17002,7 +17002,7 @@ "mode": "chat", "output_cost_per_token": 6e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-90b-text-preview": { @@ -17015,7 +17015,7 @@ "mode": "chat", "output_cost_per_token": 9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-90b-vision-preview": { @@ -17028,7 +17028,7 @@ "mode": "chat", "output_cost_per_token": 9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true, "supports_vision": true }, @@ -17052,7 +17052,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-guard-3-8b": { @@ -17073,7 +17073,7 @@ "mode": "chat", "output_cost_per_token": 8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama3-groq-70b-8192-tool-use-preview": { @@ -17086,7 +17086,7 @@ "mode": "chat", "output_cost_per_token": 8.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama3-groq-8b-8192-tool-use-preview": { @@ -17099,7 +17099,7 @@ "mode": "chat", "output_cost_per_token": 1.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/meta-llama/llama-4-maverick-17b-128e-instruct": { @@ -17145,7 +17145,7 @@ "mode": "chat", "output_cost_per_token": 2.4e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/moonshotai/kimi-k2-instruct": { @@ -17221,7 +17221,7 @@ "output_cost_per_token": 5.9e-07, "supports_function_calling": true, "supports_reasoning": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/whisper-large-v3": { diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index dbaa60e0f1d..903b86484cd 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -16881,7 +16881,7 @@ "output_cost_per_token": 9.9e-07, "supports_function_calling": true, "supports_reasoning": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/distil-whisper-large-v3-en": { @@ -16900,7 +16900,7 @@ "mode": "chat", "output_cost_per_token": 7e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/gemma2-9b-it": { @@ -16912,7 +16912,7 @@ "mode": "chat", "output_cost_per_token": 2e-07, "supports_function_calling": false, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": false }, "groq/llama-3.1-405b-reasoning": { @@ -16924,7 +16924,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.1-70b-versatile": { @@ -16937,7 +16937,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.1-8b-instant": { @@ -16949,7 +16949,7 @@ "mode": "chat", "output_cost_per_token": 8e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-11b-text-preview": { @@ -16962,7 +16962,7 @@ "mode": "chat", "output_cost_per_token": 1.8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-11b-vision-preview": { @@ -16975,7 +16975,7 @@ "mode": "chat", "output_cost_per_token": 1.8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true, "supports_vision": true }, @@ -16989,7 +16989,7 @@ "mode": "chat", "output_cost_per_token": 4e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-3b-preview": { @@ -17002,7 +17002,7 @@ "mode": "chat", "output_cost_per_token": 6e-08, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-90b-text-preview": { @@ -17015,7 +17015,7 @@ "mode": "chat", "output_cost_per_token": 9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-3.2-90b-vision-preview": { @@ -17028,7 +17028,7 @@ "mode": "chat", "output_cost_per_token": 9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true, "supports_vision": true }, @@ -17052,7 +17052,7 @@ "mode": "chat", "output_cost_per_token": 7.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama-guard-3-8b": { @@ -17073,7 +17073,7 @@ "mode": "chat", "output_cost_per_token": 8e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama3-groq-70b-8192-tool-use-preview": { @@ -17086,7 +17086,7 @@ "mode": "chat", "output_cost_per_token": 8.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/llama3-groq-8b-8192-tool-use-preview": { @@ -17099,7 +17099,7 @@ "mode": "chat", "output_cost_per_token": 1.9e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/meta-llama/llama-4-maverick-17b-128e-instruct": { @@ -17145,7 +17145,7 @@ "mode": "chat", "output_cost_per_token": 2.4e-07, "supports_function_calling": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/moonshotai/kimi-k2-instruct": { @@ -17221,7 +17221,7 @@ "output_cost_per_token": 5.9e-07, "supports_function_calling": true, "supports_reasoning": true, - "supports_response_schema": true, + "supports_response_schema": false, "supports_tool_choice": true }, "groq/whisper-large-v3": { diff --git a/tests/llm_translation/test_groq.py b/tests/llm_translation/test_groq.py index fc84580404c..33c1a425870 100644 --- a/tests/llm_translation/test_groq.py +++ b/tests/llm_translation/test_groq.py @@ -9,6 +9,7 @@ import pytest # ) # noqa # ) # Adds the parent directory to the system path +import litellm from base_llm_unit_tests import BaseLLMChatTest from litellm.llms.groq.chat.transformation import GroqChatConfig @@ -30,3 +31,136 @@ class TestGroq(BaseLLMChatTest): """Test that reasoning_effort is in the list of supported parameters for Groq""" supported_params = GroqChatConfig().get_supported_openai_params(model=model) assert "reasoning_effort" in supported_params + + +class TestGroqStructuredOutputs: + """ + Tests for Groq structured outputs handling. + Related issues: + - https://github.com/BerriAI/litellm/issues/11001 + - https://github.com/openai/openai-agents-python/issues/2140 + """ + + def test_structured_output_with_tools_raises_error_for_non_native_models(self): + """ + Test that using structured outputs + tools with models that don't support + native json_schema raises a clear error message. + + Groq does not support structured outputs + tools together. + See: https://console.groq.com/docs/structured-outputs + "Streaming and tool use are not currently supported with Structured Outputs" + """ + config = GroqChatConfig() + + # Model that doesn't support native json_schema + model = "llama-3.3-70b-versatile" + + non_default_params = { + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "test", + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}}, + "required": ["name"] + } + } + }, + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "parameters": {"type": "object", "properties": {}} + } + } + ] + } + + with pytest.raises(litellm.BadRequestError) as exc_info: + config.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model=model, + drop_params=False, + ) + + assert "does not support native structured outputs" in str(exc_info.value) + assert "incompatible with user-provided tools" in str(exc_info.value) + + def test_structured_output_without_tools_uses_workaround_for_non_native_models(self): + """ + Test that structured outputs without tools works using the json_tool_call workaround + for models that don't support native json_schema. + """ + config = GroqChatConfig() + + model = "llama-3.3-70b-versatile" + + non_default_params = { + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "test", + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}}, + "required": ["name"] + } + } + } + } + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model=model, + drop_params=False, + ) + + # Should use the workaround (json_tool_call) + assert "tools" in result + assert len(result["tools"]) == 1 + assert result["tools"][0]["function"]["name"] == "json_tool_call" + assert result["tool_choice"]["function"]["name"] == "json_tool_call" + assert result.get("json_mode") is True + + def test_structured_output_passes_through_for_native_models(self): + """ + Test that structured outputs pass through directly for models that + support native json_schema (e.g., gpt-oss-120b). + """ + config = GroqChatConfig() + + # Model that supports native json_schema + model = "openai/gpt-oss-120b" + + non_default_params = { + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "test", + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}}, + "required": ["name"] + } + } + } + } + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model=model, + drop_params=False, + ) + + # Should NOT use the workaround - response_format should pass through + # The workaround sets json_mode=True, so if it's not set, we know it passed through + assert result.get("json_mode") is not True + # Should not have the json_tool_call tool + if "tools" in result: + tool_names = [t.get("function", {}).get("name") for t in result["tools"]] + assert "json_tool_call" not in tool_names From 664b384dce19d0189c26ace5189326d27308f1a6 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:31:49 -0300 Subject: [PATCH 335/349] fix(pricing): add missing Moonshot turbo models and fix incorrect pricing (#17432) Fixes #17417 - Add moonshot/kimi-k2-turbo-preview ($1.15/$8.00 input/output) - Add moonshot/kimi-k2-thinking-turbo ($1.15/$8.00 input/output) - Add moonshot/kimi-k2-0905-preview ($0.60/$2.50 input/output, 262K context) - Fix moonshot/kimi-thinking-preview pricing ($30/$30 -> $0.60/$2.50) - Fix wandb/moonshotai/Kimi-K2-Instruct absurd pricing ($135K/$400K -> $0.60/$2.50) --- model_prices_and_context_window.json | 53 +++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 903b86484cd..6cc6e7772af 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -18896,6 +18896,34 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2-0905-preview": { + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, + "moonshot/kimi-k2-turbo-preview": { + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 1.15e-06, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 2e-06, @@ -18953,14 +18981,15 @@ "supports_vision": true }, "moonshot/kimi-thinking-preview": { - "input_cost_per_token": 3e-05, + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 6e-07, "litellm_provider": "moonshot", "max_input_tokens": 131072, "max_output_tokens": 131072, "max_tokens": 131072, "mode": "chat", - "output_cost_per_token": 3e-05, - "source": "https://platform.moonshot.ai/docs/pricing", + "output_cost_per_token": 2.5e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", "supports_vision": true }, "moonshot/kimi-k2-thinking": { @@ -18977,6 +19006,20 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2-thinking-turbo": { + "cache_read_input_token_cost": 1.5e-7, + "input_cost_per_token": 1.15e-6, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 8e-6, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "moonshot/moonshot-v1-128k": { "input_cost_per_token": 2e-06, "litellm_provider": "moonshot", @@ -26470,8 +26513,8 @@ "max_tokens": 128000, "max_input_tokens": 128000, "max_output_tokens": 128000, - "input_cost_per_token": 0.135, - "output_cost_per_token": 0.4, + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.5e-06, "litellm_provider": "wandb", "mode": "chat" }, From 3134fc825ba26df90dd30854de831284b1f7b96c Mon Sep 17 00:00:00 2001 From: Daniel Klein <37589721+danielmklein@users.noreply.github.com> Date: Wed, 3 Dec 2025 23:34:50 -0600 Subject: [PATCH 336/349] fix: make bedrock converse messages respect modify_params as expected (#17427) --- .../prompt_templates/factory.py | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 262692d6d1a..04c8c235557 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -3446,8 +3446,25 @@ class BedrockConverseMessagesProcessor: @staticmethod def _initial_message_setup( messages: List, + model: str, + llm_provider: str, user_continue_message: Optional[ChatCompletionUserMessage] = None, ) -> List: + # gracefully handle base case of no messages at all + if len(messages) == 0: + if user_continue_message is not None: + messages.append(user_continue_message) + elif litellm.modify_params: + messages.append(DEFAULT_USER_CONTINUE_MESSAGE) + else: + raise litellm.BadRequestError( + message=BAD_MESSAGE_ERROR_STR + + "bedrock requires at least one non-system message", + model=model, + llm_provider=llm_provider, + ) + + # if initial message is assistant message if messages[0].get("role") is not None and messages[0]["role"] == "assistant": if user_continue_message is not None: messages.insert(0, user_continue_message) @@ -3475,18 +3492,8 @@ class BedrockConverseMessagesProcessor: contents: List[BedrockMessageBlock] = [] msg_i = 0 - ## BASE CASE ## - if len(messages) == 0: - raise litellm.BadRequestError( - message=BAD_MESSAGE_ERROR_STR - + "bedrock requires at least one non-system message", - model=model, - llm_provider=llm_provider, - ) - - # if initial message is assistant message messages = BedrockConverseMessagesProcessor._initial_message_setup( - messages, user_continue_message + messages, model, llm_provider, user_continue_message ) while msg_i < len(messages): @@ -3847,28 +3854,9 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 contents: List[BedrockMessageBlock] = [] msg_i = 0 - ## BASE CASE ## - if len(messages) == 0: - raise litellm.BadRequestError( - message=BAD_MESSAGE_ERROR_STR - + "bedrock requires at least one non-system message", - model=model, - llm_provider=llm_provider, - ) - - # if initial message is assistant message - if messages[0].get("role") is not None and messages[0]["role"] == "assistant": - if user_continue_message is not None: - messages.insert(0, user_continue_message) - elif litellm.modify_params: - messages.insert(0, DEFAULT_USER_CONTINUE_MESSAGE) - - # if final message is assistant message - if messages[-1].get("role") is not None and messages[-1]["role"] == "assistant": - if user_continue_message is not None: - messages.append(user_continue_message) - elif litellm.modify_params: - messages.append(DEFAULT_USER_CONTINUE_MESSAGE) + messages = BedrockConverseMessagesProcessor._initial_message_setup( + messages, model, llm_provider, user_continue_message + ) while msg_i < len(messages): user_content: List[BedrockContentBlock] = [] From 4710e772be8d6c1256ad7de041425d3816b64904 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 4 Dec 2025 11:08:57 +0530 Subject: [PATCH 337/349] Add support for bedrock qwen 2 imported model --- docs/my-website/docs/providers/bedrock.md | 2 +- .../docs/providers/bedrock_imported.md | 65 ++++ litellm/__init__.py | 3 + litellm/constants.py | 1 + .../amazon_qwen2_transformation.py | 98 ++++++ litellm/llms/bedrock/common_utils.py | 2 + .../test_amazon_qwen2_transformation.py | 292 ++++++++++++++++++ 7 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py create mode 100644 tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_amazon_qwen2_transformation.py diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 7fc7758b15c..17c0d38111d 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -7,7 +7,7 @@ ALL Bedrock models (Anthropic, Meta, Deepseek, Mistral, Amazon, etc.) are Suppor | Property | Details | |-------|-------| | Description | Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs). | -| Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models), [`bedrock/openai/`](./bedrock_imported.md#openai-compatible-imported-models-qwen-25-vl-etc) | +| Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models), [`bedrock/qwen2/`](./bedrock_imported.md#qwen2-imported-models), [`bedrock/openai/`](./bedrock_imported.md#openai-compatible-imported-models-qwen-25-vl-etc) | | Provider Doc | [Amazon Bedrock ↗](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) | | Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations` | | Rerank Endpoint | `/rerank` | diff --git a/docs/my-website/docs/providers/bedrock_imported.md b/docs/my-website/docs/providers/bedrock_imported.md index 8b0dd721c3c..0784f716925 100644 --- a/docs/my-website/docs/providers/bedrock_imported.md +++ b/docs/my-website/docs/providers/bedrock_imported.md @@ -203,6 +203,71 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ +### Qwen2 Imported Models + +| Property | Details | +|----------|---------| +| Provider Route | `bedrock/qwen2/{model_arn}` | +| Provider Documentation | [Bedrock Imported Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-import-model.html) | +| Note | Qwen2 and Qwen3 architectures are mostly similar. The main difference is in the response format: Qwen2 uses "text" field while Qwen3 uses "generation" field. | + + + + +```python +from litellm import completion +import os + +response = completion( + model="bedrock/qwen2/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen2-model", # bedrock/qwen2/{your-model-arn} + messages=[{"role": "user", "content": "Tell me a joke"}], + max_tokens=100, + temperature=0.7 +) +``` + + + + + +**1. Add to config** + +```yaml +model_list: + - model_name: Qwen2-72B + litellm_params: + model: bedrock/qwen2/arn:aws:bedrock:us-east-1:086734376398:imported-model/your-qwen2-model + +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING at http://0.0.0.0:4000 +``` + +**3. Test it!** + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "Qwen2-72B", # 👈 the 'model_name' in config + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ], + }' +``` + + + + ### OpenAI-Compatible Imported Models (Qwen 2.5 VL, etc.) Use this route for Bedrock imported models that follow the **OpenAI Chat Completions API spec**. This includes models like Qwen 2.5 VL that accept OpenAI-formatted messages with support for vision (images), tool calling, and other OpenAI features. diff --git a/litellm/__init__.py b/litellm/__init__.py index 3d40a9b15cf..03e4cee9ff3 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1163,6 +1163,9 @@ from .llms.bedrock.chat.invoke_transformations.amazon_ai21_transformation import from .llms.bedrock.chat.invoke_transformations.amazon_nova_transformation import ( AmazonInvokeNovaConfig, ) +from .llms.bedrock.chat.invoke_transformations.amazon_qwen2_transformation import ( + AmazonQwen2Config, +) from .llms.bedrock.chat.invoke_transformations.amazon_qwen3_transformation import ( AmazonQwen3Config, ) diff --git a/litellm/constants.py b/litellm/constants.py index 5ab56bcaa2f..fa9f1d527af 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -876,6 +876,7 @@ BEDROCK_INVOKE_PROVIDERS_LITERAL = Literal[ "nova", "deepseek_r1", "qwen3", + "qwen2", "twelvelabs", "openai", ] diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py new file mode 100644 index 00000000000..c532d8ea27c --- /dev/null +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py @@ -0,0 +1,98 @@ +""" +Handles transforming requests for `bedrock/invoke/{qwen2} models` + +Inherits from `AmazonQwen3Config` since Qwen2 and Qwen3 architectures are mostly similar. +The main difference is in the response format: Qwen2 uses "text" field while Qwen3 uses "generation" field. + +Qwen2 + Invoke API Tutorial: https://docs.aws.amazon.com/bedrock/latest/userguide/invoke-imported-model.html +""" + +from typing import Any, List, Optional + +import httpx + +from litellm.llms.bedrock.chat.invoke_transformations.amazon_qwen3_transformation import ( + AmazonQwen3Config, +) +from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( + LiteLLMLoggingObj, +) +from litellm.types.llms.openai import AllMessageValues +from litellm.types.utils import ModelResponse + + +class AmazonQwen2Config(AmazonQwen3Config): + """ + Config for sending `qwen2` requests to `/bedrock/invoke/` + + Inherits from AmazonQwen3Config since Qwen2 and Qwen3 architectures are mostly similar. + The main difference is in the response format: Qwen2 uses "text" field while Qwen3 uses "generation" field. + + Reference: https://docs.aws.amazon.com/bedrock/latest/userguide/invoke-imported-model.html + """ + + def transform_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ModelResponse: + """ + Transform Qwen2 Bedrock response to OpenAI format + + Qwen2 uses "text" field, but we also support "generation" field for compatibility. + """ + try: + if hasattr(raw_response, 'json'): + response_data = raw_response.json() + else: + response_data = raw_response + + # Extract the generated text - Qwen2 uses "text" field, but also support "generation" for compatibility + generated_text = response_data.get("generation", "") or response_data.get("text", "") + + # Clean up the response (remove assistant start token if present) + if generated_text.startswith("<|im_start|>assistant\n"): + generated_text = generated_text[len("<|im_start|>assistant\n"):] + if generated_text.endswith("<|im_end|>"): + generated_text = generated_text[:-len("<|im_end|>")] + + # Set the content in the existing model_response structure + if hasattr(model_response, 'choices') and len(model_response.choices) > 0: + choice = model_response.choices[0] + if hasattr(choice, 'message'): + choice.message.content = generated_text + choice.finish_reason = "stop" + else: + # Handle streaming choices + choice.delta.content = generated_text + choice.finish_reason = "stop" + + # Set usage information if available in response + if "usage" in response_data: + usage_data = response_data["usage"] + if hasattr(model_response, 'usage'): + model_response.usage.prompt_tokens = usage_data.get("prompt_tokens", 0) + model_response.usage.completion_tokens = usage_data.get("completion_tokens", 0) + model_response.usage.total_tokens = usage_data.get("total_tokens", 0) + + return model_response + + except Exception as e: + if logging_obj: + logging_obj.post_call( + input=messages, + api_key=api_key, + original_response=raw_response, + additional_args={"error": str(e)}, + ) + raise e + diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 03ca7a5ad29..21a78c30343 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -635,6 +635,8 @@ def get_bedrock_chat_config(model: str): return litellm.AmazonInvokeNovaConfig() elif bedrock_invoke_provider == "qwen3": return litellm.AmazonQwen3Config() + elif bedrock_invoke_provider == "qwen2": + return litellm.AmazonQwen2Config() elif bedrock_invoke_provider == "twelvelabs": return litellm.AmazonTwelveLabsPegasusConfig() else: diff --git a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_amazon_qwen2_transformation.py b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_amazon_qwen2_transformation.py new file mode 100644 index 00000000000..737e1279e65 --- /dev/null +++ b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_amazon_qwen2_transformation.py @@ -0,0 +1,292 @@ +import asyncio +import json +import os +import sys +from unittest.mock import Mock + +import pytest + +# Ensure the project root is on the import path so `litellm` can be imported when +# tests are executed from any working directory. +sys.path.insert(0, os.path.abspath("../../../../../..")) + +from litellm.llms.bedrock.chat.invoke_transformations.amazon_qwen2_transformation import ( + AmazonQwen2Config, +) +from litellm.types.utils import ModelResponse + + +def test_qwen2_get_supported_params(): + """Test that Qwen2 config returns correct supported parameters""" + config = AmazonQwen2Config() + params = config.get_supported_openai_params(model="qwen2/test-model") + + expected_params = ["max_tokens", "temperature", "top_p", "top_k", "stop", "stream"] + for param in expected_params: + assert param in params + + +def test_qwen2_map_openai_params(): + """Test that OpenAI parameters are correctly mapped to Qwen2 format""" + config = AmazonQwen2Config() + non_default_params = { + "max_tokens": 100, + "temperature": 0.7, + "top_p": 0.9, + "top_k": 40, + "stop": ["", "<|im_end|>"], + "stream": True + } + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="qwen2/test-model", + drop_params=False + ) + + assert result["max_tokens"] == 100 + assert result["temperature"] == 0.7 + assert result["top_p"] == 0.9 + assert result["top_k"] == 40 + assert result["stop"] == ["", "<|im_end|>"] + assert result["stream"] is True + + +def test_qwen2_convert_messages_to_prompt(): + """Test that messages are correctly converted to Qwen2 prompt format""" + config = AmazonQwen2Config() + + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello, how are you?"}, + {"role": "assistant", "content": "I'm doing well, thank you!"}, + {"role": "user", "content": "What's the weather like?"} + ] + + prompt = config._convert_messages_to_prompt(messages) + + expected_prompt = """<|im_start|>system +You are a helpful assistant.<|im_end|> +<|im_start|>user +Hello, how are you?<|im_end|> +<|im_start|>assistant +I'm doing well, thank you!<|im_end|> +<|im_start|>user +What's the weather like?<|im_end|> +<|im_start|>assistant +""" + + assert prompt == expected_prompt + + +def test_qwen2_transform_request(): + """Test that the request is correctly transformed to Qwen2 format""" + config = AmazonQwen2Config() + + messages = [ + {"role": "user", "content": "Hello, world!"} + ] + + optional_params = { + "max_tokens": 50, + "temperature": 0.8, + "top_p": 0.9 + } + + request_body = config.transform_request( + model="qwen2/test-model", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + assert "prompt" in request_body + assert request_body["max_gen_len"] == 50 + assert request_body["temperature"] == 0.8 + assert request_body["top_p"] == 0.9 + + # Check that the prompt contains the expected format + assert "<|im_start|>user" in request_body["prompt"] + assert "Hello, world!" in request_body["prompt"] + assert "<|im_end|>" in request_body["prompt"] + + +def test_qwen2_transform_response_with_text_field(): + """Test that Qwen2 response with 'text' field is correctly transformed to OpenAI format""" + config = AmazonQwen2Config() + + # Mock response data with 'text' field (Qwen2 format) + mock_response_data = { + "text": "<|im_start|>assistant\nHello! How can I help you today?<|im_end|>", + "usage": { + "prompt_tokens": 10, + "completion_tokens": 15, + "total_tokens": 25 + } + } + + # Mock the raw response + mock_raw_response = Mock() + mock_raw_response.json.return_value = mock_response_data + + model_response = ModelResponse() + messages = [{"role": "user", "content": "Hello!"}] + + result = config.transform_response( + model="qwen2/test-model", + messages=messages, + raw_response=mock_raw_response, + model_response=model_response, + logging_obj=Mock(), + optional_params={}, + litellm_params={}, + api_key="test-key", + request_data={}, + encoding=None + ) + + # Check that the response is correctly formatted + assert len(result.choices) == 1 + assert result.choices[0]["message"]["role"] == "assistant" + assert result.choices[0]["message"]["content"] == "Hello! How can I help you today?" + assert result.choices[0]["finish_reason"] == "stop" + + # Check usage information + assert result.usage["prompt_tokens"] == 10 + assert result.usage["completion_tokens"] == 15 + assert result.usage["total_tokens"] == 25 + + +def test_qwen2_transform_response_with_generation_field(): + """Test that Qwen2 response also supports 'generation' field for compatibility""" + config = AmazonQwen2Config() + + # Mock response data with 'generation' field (Qwen3 format, but Qwen2 should handle it) + mock_response_data = { + "generation": "<|im_start|>assistant\nHello! How can I help you today?<|im_end|>", + "usage": { + "prompt_tokens": 10, + "completion_tokens": 15, + "total_tokens": 25 + } + } + + # Mock the raw response + mock_raw_response = Mock() + mock_raw_response.json.return_value = mock_response_data + + model_response = ModelResponse() + messages = [{"role": "user", "content": "Hello!"}] + + result = config.transform_response( + model="qwen2/test-model", + messages=messages, + raw_response=mock_raw_response, + model_response=model_response, + logging_obj=Mock(), + optional_params={}, + litellm_params={}, + api_key="test-key", + request_data={}, + encoding=None + ) + + # Check that the response is correctly formatted + assert len(result.choices) == 1 + assert result.choices[0]["message"]["role"] == "assistant" + assert result.choices[0]["message"]["content"] == "Hello! How can I help you today?" + assert result.choices[0]["finish_reason"] == "stop" + + +def test_qwen2_transform_response_prefers_generation_over_text(): + """Test that Qwen2 prefers 'generation' field over 'text' when both are present""" + config = AmazonQwen2Config() + + # Mock response data with both fields + mock_response_data = { + "generation": "This is from generation field", + "text": "This is from text field", + "usage": { + "prompt_tokens": 10, + "completion_tokens": 15, + "total_tokens": 25 + } + } + + # Mock the raw response + mock_raw_response = Mock() + mock_raw_response.json.return_value = mock_response_data + + model_response = ModelResponse() + messages = [{"role": "user", "content": "Hello!"}] + + result = config.transform_response( + model="qwen2/test-model", + messages=messages, + raw_response=mock_raw_response, + model_response=model_response, + logging_obj=Mock(), + optional_params={}, + litellm_params={}, + api_key="test-key", + request_data={}, + encoding=None + ) + + # Should prefer 'generation' field + assert result.choices[0]["message"]["content"] == "This is from generation field" + + +def test_qwen2_transform_response_without_usage(): + """Test response transformation when usage information is not provided""" + config = AmazonQwen2Config() + + # Mock response data without usage + mock_response_data = { + "text": "Hello! How can I help you today?" + } + + # Mock the raw response + mock_raw_response = Mock() + mock_raw_response.json.return_value = mock_response_data + + model_response = ModelResponse() + messages = [{"role": "user", "content": "Hello!"}] + + result = config.transform_response( + model="qwen2/test-model", + messages=messages, + raw_response=mock_raw_response, + model_response=model_response, + logging_obj=Mock(), + optional_params={}, + litellm_params={}, + api_key="test-key", + request_data={}, + encoding=None + ) + + # Check that the response is correctly formatted + assert len(result.choices) == 1 + assert result.choices[0]["message"]["role"] == "assistant" + assert result.choices[0]["message"]["content"] == "Hello! How can I help you today?" + assert result.choices[0]["finish_reason"] == "stop" + + +def test_qwen2_provider_detection(): + """Test that Qwen2 provider is correctly detected from model names""" + from litellm.utils import ProviderConfigManager + from litellm.types.utils import LlmProviders + + # Test with qwen2/ prefix + config = ProviderConfigManager.get_provider_chat_config( + model="qwen2/arn:aws:bedrock:us-east-1:123456789012:imported-model/test-qwen2", + provider=LlmProviders.BEDROCK + ) + + assert config is not None + assert isinstance(config, AmazonQwen2Config) + From c634c08818cdaa66c05852de9d3fa4717a693c0c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 21:51:45 -0800 Subject: [PATCH 338/349] Find inputs by placeholder --- .../e2e_ui_tests/login_to_ui.spec.ts | 4 ++-- .../e2e_ui_tests/search_users.spec.ts | 16 ++++++++++------ .../e2e_ui_tests/view_internal_user.spec.ts | 4 ++-- tests/proxy_admin_ui_tests/utils/login.ts | 8 +++++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts index c4a1dc77c33..e5a397a6a66 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/login_to_ui.spec.ts @@ -16,10 +16,10 @@ test("admin login test", async ({ page }) => { await page.screenshot({ path: "test-results/login_before.png" }); // Enter "admin" in the username input field - await page.fill('input[name="username"]', "admin"); + await page.fill('input[placeholder="Enter your username"]', "admin"); // Enter "gm" in the password input field - await page.fill('input[name="password"]', "gm"); + await page.fill('input[placeholder="Enter your password"]', "gm"); page.screenshot({ path: "test-results/login_after_inputs.png" }); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts index 66d0ad29658..d72c44ab8cc 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts @@ -27,11 +27,13 @@ test("user search test", async ({ page }) => { page.screenshot({ path: "test-results/search_users_before_login.png" }); // Wait for login form to be visible - await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + await page.waitForSelector('input[placeholder="Enter your username"]', { + timeout: 10000, + }); console.log("Login form is visible"); - await page.fill('input[name="username"]', "admin"); - await page.fill('input[name="password"]', "gm"); + await page.fill('input[placeholder="Enter your username"]', "admin"); + await page.fill('input[placeholder="Enter your password"]', "gm"); console.log("Filled login credentials"); const loginButton = page.getByRole("button", { name: "Login" }); @@ -135,11 +137,13 @@ test("user filter test", async ({ page }) => { console.log("Navigated to login page"); // Wait for login form to be visible - await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + await page.waitForSelector('input[placeholder="Enter your username"]', { + timeout: 10000, + }); console.log("Login form is visible"); - await page.fill('input[name="username"]', "admin"); - await page.fill('input[name="password"]', "gm"); + await page.fill('input[placeholder="Enter your username"]', "admin"); + await page.fill('input[placeholder="Enter your password"]', "gm"); console.log("Filled login credentials"); const loginButton = page.getByRole("button", { name: "Login" }); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts index a7843d52916..2aae9e2bb54 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts @@ -12,10 +12,10 @@ test("view internal user page", async ({ page }) => { page.screenshot({ path: "test-results/view_internal_user_before_login.png" }); // Enter "admin" in the username input field - await page.fill('input[name="username"]', "admin"); + await page.fill('input[placeholder="Enter your username"]', "admin"); // Enter "gm" in the password input field - await page.fill('input[name="password"]', "gm"); + await page.fill('input[placeholder="Enter your password"]', "gm"); // Click the login button const loginButton = page.getByRole("button", { name: "Login" }); diff --git a/tests/proxy_admin_ui_tests/utils/login.ts b/tests/proxy_admin_ui_tests/utils/login.ts index 5749006cdea..25858d9f570 100644 --- a/tests/proxy_admin_ui_tests/utils/login.ts +++ b/tests/proxy_admin_ui_tests/utils/login.ts @@ -8,11 +8,13 @@ export async function loginToUI(page: Page) { page.screenshot({ path: "test-results/login_utils_before.png" }); // Wait for login form to be visible - await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + await page.waitForSelector('input[placeholder="Enter your username"]', { + timeout: 10000, + }); console.log("Login form is visible"); - await page.fill('input[name="username"]', "admin"); - await page.fill('input[name="password"]', "gm"); + await page.fill('input[placeholder="Enter your username"]', "admin"); + await page.fill('input[placeholder="Enter your password"]', "gm"); console.log("Filled login credentials"); const loginButton = page.getByRole("button", { name: "Login" }); From dc7c2b9b058a2fd3be3c2ac555f1dd3b6709a4a4 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 3 Dec 2025 21:59:45 -0800 Subject: [PATCH 339/349] Update docs to link agent hub (#17462) * Docs: Add AI Hub agent registry documentation Co-authored-by: krrishdholakia * Fix: Update AI Hub link in A2A documentation Co-authored-by: krrishdholakia --------- Co-authored-by: Cursor Agent --- docs/my-website/docs/a2a.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/my-website/docs/a2a.md b/docs/my-website/docs/a2a.md index 7851f50d88e..ef19e22dab3 100644 --- a/docs/my-website/docs/a2a.md +++ b/docs/my-website/docs/a2a.md @@ -196,3 +196,9 @@ LiteLLM follows the [A2A JSON-RPC 2.0 specification](https://github.com/google/A } } ``` + +## Agent Registry + +Want to create a central registry so your team can discover what agents are available within your company? + +Use the [AI Hub](./proxy/ai_hub) to make agents public and discoverable across your organization. This allows developers to browse available agents without needing to rebuild them. From 7519a3e30b640683111e19693bb14896c1e67aff Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 22:08:41 -0800 Subject: [PATCH 340/349] Change credentials to use react-query --- .../hooks/credentials/useCredentials.ts | 13 ++ .../ModelsAndEndpointsView.tsx | 23 +--- .../components/model_add/credentials.test.tsx | 111 ++++++++++++------ .../src/components/model_add/credentials.tsx | 31 ++--- 4 files changed, 101 insertions(+), 77 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.ts new file mode 100644 index 00000000000..aa0a6c2c9fb --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.ts @@ -0,0 +1,13 @@ +import { credentialListCall, CredentialsResponse } from "@/components/networking"; +import { useQuery } from "@tanstack/react-query"; +import { createQueryKeys } from "../common/queryKeysFactory"; + +const credentialsKeys = createQueryKeys("credentials"); + +export const useCredentials = (accessToken: string | null) => { + return useQuery({ + queryKey: credentialsKeys.list({}), + queryFn: async () => await credentialListCall(accessToken!), + enabled: Boolean(accessToken), + }); +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 4f33ba6585d..4cd1f549e69 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from "react"; import { Text, Grid, Col } from "@tremor/react"; import { useQueryClient } from "@tanstack/react-query"; -import { CredentialItem, credentialListCall, CredentialsResponse } from "@/components/networking"; +import { CredentialItem } from "@/components/networking"; import { handleAddModelSubmit } from "@/components/add_model/handle_add_model_submit"; @@ -23,6 +23,7 @@ import { allEndUsersCall, } from "@/components/networking"; import { useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; +import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials"; import { Form } from "antd"; import { Typography } from "antd"; import { RefreshIcon } from "@heroicons/react/outline"; @@ -134,8 +135,6 @@ const ModelsAndEndpointsView: React.FC = ({ const [allEndUsers, setAllEndUsers] = useState([]); - const [credentialsList, setCredentialsList] = useState([]); - // Model Group Alias state const [modelGroupAlias, setModelGroupAlias] = useState<{ [key: string]: string }>({}); @@ -160,21 +159,14 @@ const ModelsAndEndpointsView: React.FC = ({ isLoading: isLoadingModels, refetch: refetchModels, } = useModelsInfo(accessToken, userID, userRole); + const { data: credentialsResponse } = useCredentials(accessToken); + const credentialsList = credentialsResponse?.credentials || []; const setProviderModelsFn = (provider: Providers) => { const _providerModels = getProviderModels(provider, modelMap); setProviderModels(_providerModels); }; - const fetchCredentials = async (accessToken: string) => { - try { - const response: CredentialsResponse = await credentialListCall(accessToken); - setCredentialsList(response.credentials); - } catch (error) { - console.error("Error fetching credentials:", error); - } - }; - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -686,12 +678,7 @@ const ModelsAndEndpointsView: React.FC = ({ /> - + ({ + default: () => mockUseAuthorized(), +})); + +vi.mock("@/app/(dashboard)/hooks/credentials/useCredentials", () => ({ + useCredentials: () => mockUseCredentials(), +})); + +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }); + describe("CredentialsPanel", () => { it("should render", () => { - const fetchCredentials = vi.fn(() => Promise.resolve()); + mockUseAuthorized.mockReturnValue({ accessToken: "test-token" }); + mockUseCredentials.mockReturnValue({ + data: { credentials: [] }, + refetch: vi.fn(), + }); render( - , + + + , ); expect(screen.getByRole("button", { name: /add credential/i })).toBeInTheDocument(); }); - it("should call fetchCredentials when accessToken exists", async () => { - const fetchCredentials = vi.fn(() => Promise.resolve()); - - render( - , - ); - - await waitFor(() => { - expect(fetchCredentials).toHaveBeenCalledWith("test-token"); - }); - }); - it("should display provided credentials", () => { - const fetchCredentials = vi.fn(() => Promise.resolve()); const credentials: CredentialItem[] = [ { credential_name: "openai-key", @@ -49,30 +54,58 @@ describe("CredentialsPanel", () => { }, ]; + mockUseAuthorized.mockReturnValue({ accessToken: "test-token" }); + mockUseCredentials.mockReturnValue({ + data: { credentials }, + refetch: vi.fn(), + }); + render( - , + + + , ); expect(screen.getByText("openai-key")).toBeInTheDocument(); }); it("should display empty state when no credentials are provided", () => { - const fetchCredentials = vi.fn(() => Promise.resolve()); + mockUseAuthorized.mockReturnValue({ accessToken: "test-token" }); + mockUseCredentials.mockReturnValue({ + data: { credentials: [] }, + refetch: vi.fn(), + }); render( - , + + + , ); expect(screen.getByText("No credentials configured")).toBeInTheDocument(); }); + + it("should open add modal when add button is clicked", async () => { + mockUseAuthorized.mockReturnValue({ accessToken: "test-token" }); + mockUseCredentials.mockReturnValue({ + data: { credentials: [] }, + refetch: vi.fn(), + }); + + render( + + + , + ); + + const addButton = screen.getByRole("button", { name: /add credential/i }); + + act(() => { + fireEvent.click(addButton); + }); + + await waitFor(() => { + expect(screen.getByText("Add New Credential")).toBeInTheDocument(); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.tsx index 1bba14bef97..3887e340daa 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.tsx @@ -19,24 +19,22 @@ import { } from "@tremor/react"; import { Form } from "antd"; import { UploadProps } from "antd/es/upload"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import DeleteResourceModal from "../common_components/DeleteResourceModal"; import NotificationsManager from "../molecules/notifications_manager"; import AddCredentialsTab from "./AddCredentialModal"; import EditCredentialsModal from "./EditCredentialModal"; +import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; interface CredentialsPanelProps { - accessToken: string | null; uploadProps: UploadProps; - credentialList: CredentialItem[]; - fetchCredentials: (accessToken: string) => Promise; } -const CredentialsPanel: React.FC = ({ - accessToken, - uploadProps, - credentialList, - fetchCredentials, -}) => { +const CredentialsPanel: React.FC = ({ uploadProps }) => { + const { accessToken } = useAuthorized(); + const { data: credentialsResponse, refetch: refetchCredentials } = useCredentials(accessToken); + const credentialList = credentialsResponse?.credentials || []; + const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false); const [selectedCredential, setSelectedCredential] = useState(null); @@ -66,7 +64,7 @@ const CredentialsPanel: React.FC = ({ await credentialUpdateCall(accessToken, values.credential_name, newCredential); NotificationsManager.success("Credential updated successfully"); setIsUpdateModalOpen(false); - await fetchCredentials(accessToken); + await refetchCredentials(); }; const handleAddCredential = async (values: any) => { @@ -90,16 +88,9 @@ const CredentialsPanel: React.FC = ({ await credentialCreateCall(accessToken, newCredential); NotificationsManager.success("Credential added successfully"); setIsAddModalOpen(false); - await fetchCredentials(accessToken); + await refetchCredentials(); }; - useEffect(() => { - if (!accessToken) { - return; - } - fetchCredentials(accessToken); - }, [accessToken]); - const renderProviderBadge = (provider: string) => { const providerColors: Record = { openai: "blue", @@ -124,7 +115,7 @@ const CredentialsPanel: React.FC = ({ try { await credentialDeleteCall(accessToken, credentialToDelete.credential_name); NotificationsManager.success("Credential deleted successfully"); - await fetchCredentials(accessToken); + await refetchCredentials(); } catch (error) { NotificationsManager.error("Failed to delete credential"); } finally { From b8190c6b6dff54a499c8326e4c67976968af08a8 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 22:15:19 -0800 Subject: [PATCH 341/349] resolving build issues --- .../ModelsAndEndpointsView.tsx | 59 +++++++++---------- .../components/templates/model_dashboard.tsx | 7 +-- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 4cd1f549e69..8135580e29e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -1,48 +1,45 @@ -import React, { useState, useEffect, useRef } from "react"; -import { Text, Grid, Col } from "@tremor/react"; import { useQueryClient } from "@tanstack/react-query"; -import { CredentialItem } from "@/components/networking"; +import { Col, Grid, Text } from "@tremor/react"; +import React, { useEffect, useRef, useState } from "react"; import { handleAddModelSubmit } from "@/components/add_model/handle_add_model_submit"; +import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials"; +import { useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; +import { Team } from "@/components/key_team_helpers/key_list"; import CredentialsPanel from "@/components/model_add/credentials"; -import { getDisplayModelName } from "@/components/view_model/model_name_display"; -import { TabPanel, TabPanels, TabGroup, TabList, Tab, Icon } from "@tremor/react"; -import { DateRangePickerValue } from "@tremor/react"; import { - modelCostMap, - modelMetricsCall, - streamingModelMetricsCall, - modelExceptionsCall, - modelMetricsSlowResponsesCall, - getCallbacksCall, - setCallbacksCall, - modelSettingsCall, adminGlobalActivityExceptions, adminGlobalActivityExceptionsPerDeployment, allEndUsersCall, + getCallbacksCall, + modelCostMap, + modelExceptionsCall, + modelMetricsCall, + modelMetricsSlowResponsesCall, + modelSettingsCall, + setCallbacksCall, + streamingModelMetricsCall, } from "@/components/networking"; -import { useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; -import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials"; -import { Form } from "antd"; -import { Typography } from "antd"; -import { RefreshIcon } from "@heroicons/react/outline"; -import type { UploadProps } from "antd"; -import { Team } from "@/components/key_team_helpers/key_list"; -import TeamInfoView from "../../../components/team/team_info"; import { Providers, getPlaceholder, getProviderModels } from "@/components/provider_info_helpers"; -import ModelInfoView from "../../../components/model_info_view"; +import { getDisplayModelName } from "@/components/view_model/model_name_display"; +import { RefreshIcon } from "@heroicons/react/outline"; +import { DateRangePickerValue, Icon, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; +import type { UploadProps } from "antd"; +import { Form, Typography } from "antd"; import AddModelTab from "../../../components/add_model/add_model_tab"; +import ModelInfoView from "../../../components/model_info_view"; +import TeamInfoView from "../../../components/team/team_info"; -import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent"; -import PassThroughSettings from "../../../components/pass_through_settings"; -import ModelGroupAliasSettings from "../../../components/model_group_alias_settings"; -import { all_admin_roles } from "@/utils/roles"; -import NotificationsManager from "../../../components/molecules/notifications_manager"; import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab"; -import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab"; -import ModelRetrySettingsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelRetrySettingsTab"; import ModelAnalyticsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelAnalyticsTab/ModelAnalyticsTab"; +import ModelRetrySettingsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelRetrySettingsTab"; +import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab"; +import { all_admin_roles } from "@/utils/roles"; +import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent"; +import ModelGroupAliasSettings from "../../../components/model_group_alias_settings"; +import NotificationsManager from "../../../components/molecules/notifications_manager"; +import PassThroughSettings from "../../../components/pass_through_settings"; interface ModelDashboardProps { accessToken: string | null; @@ -678,7 +675,7 @@ const ModelsAndEndpointsView: React.FC = ({ /> - + = ({ /> - + Date: Wed, 3 Dec 2025 22:20:04 -0800 Subject: [PATCH 342/349] view_user_info debugging --- .../e2e_ui_tests/view_user_info.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 742dbd14d44..53c73593882 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -5,7 +5,9 @@ test.describe("User Info View", () => { test.beforeEach(async ({ page }) => { await loginToUI(page); // Navigate to users page + page.screenshot({ path: "test-results/view_user_info_before_nav.png" }); await page.goto("http://localhost:4000/ui?page=users"); + page.screenshot({ path: "test-results/view_user_info_after_nav.png" }); }); test("should display user info when clicking on user ID", async ({ @@ -16,9 +18,12 @@ test.describe("User Info View", () => { state: "hidden", timeout: 10000, }); + page.screenshot({ path: "test-results/view_user_info_after_loading.png" }); // Wait for users table to load await page.waitForSelector("table"); - + page.screenshot({ + path: "test-results/view_user_info_after_table_load.png", + }); // Get the first user ID cell const firstUserIdCell = page.locator( "table tbody tr:first-child td:first-child" From 19dc5d750c409a88a5b64e05505c10a89a8a7c56 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 23:10:32 -0800 Subject: [PATCH 343/349] Adding waits --- tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 53c73593882..cba7ddad7cc 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -18,6 +18,7 @@ test.describe("User Info View", () => { state: "hidden", timeout: 10000, }); + await page.waitForLoadState("networkidle"); page.screenshot({ path: "test-results/view_user_info_after_loading.png" }); // Wait for users table to load await page.waitForSelector("table"); @@ -33,6 +34,7 @@ test.describe("User Info View", () => { // Click on the user ID await firstUserIdCell.click(); + await page.waitForLoadState("networkidle"); // Check for tabs await expect(page.locator('button:has-text("Overview")')).toBeVisible({ From 0483336c53cfd6249d31bd44ba064a2fa6246689 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 23:21:09 -0800 Subject: [PATCH 344/349] change test to follow other logins --- .../e2e_ui_tests/view_user_info.spec.ts | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index cba7ddad7cc..22d37b19ecd 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -2,17 +2,37 @@ import { test, expect } from "@playwright/test"; import { loginToUI } from "../utils/login"; test.describe("User Info View", () => { - test.beforeEach(async ({ page }) => { - await loginToUI(page); - // Navigate to users page - page.screenshot({ path: "test-results/view_user_info_before_nav.png" }); - await page.goto("http://localhost:4000/ui?page=users"); - page.screenshot({ path: "test-results/view_user_info_after_nav.png" }); - }); - test("should display user info when clicking on user ID", async ({ page, }) => { + test.setTimeout(60000); + + // Enable console logging + page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); + + // Login first + await page.goto("http://localhost:4000/ui"); + await page.waitForLoadState("networkidle"); + console.log("Navigated to login page"); + + // Wait for login form to be visible + await page.waitForSelector('input[placeholder="Enter your username"]', { + timeout: 10000, + }); + console.log("Login form is visible"); + + await page.fill('input[placeholder="Enter your username"]', "admin"); + await page.fill('input[placeholder="Enter your password"]', "gm"); + console.log("Filled login credentials"); + + const loginButton = page.getByRole("button", { name: "Login" }); + await expect(loginButton).toBeEnabled(); + await loginButton.click(); + console.log("Clicked login button"); + + // Wait for navigation to complete and dashboard to load + await page.waitForLoadState("networkidle"); + await page.goto("http://localhost:4000/ui?page=users"); // Wait for loading state to disappear await page.waitForSelector('text="🚅 Loading users..."', { state: "hidden", From 59b32c28a860b0cdb43ee85c29983106441acefa Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 23:37:15 -0800 Subject: [PATCH 345/349] Change selector in test --- .../e2e_ui_tests/view_user_info.spec.ts | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index 22d37b19ecd..adda3088f12 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -5,40 +5,47 @@ test.describe("User Info View", () => { test("should display user info when clicking on user ID", async ({ page, }) => { - test.setTimeout(60000); - - // Enable console logging - page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); - - // Login first await page.goto("http://localhost:4000/ui"); await page.waitForLoadState("networkidle"); - console.log("Navigated to login page"); - // Wait for login form to be visible - await page.waitForSelector('input[placeholder="Enter your username"]', { - timeout: 10000, + page.screenshot({ + path: "test-results/view_user_info_before_login.png", }); - console.log("Login form is visible"); + // Enter "admin" in the username input field await page.fill('input[placeholder="Enter your username"]', "admin"); - await page.fill('input[placeholder="Enter your password"]', "gm"); - console.log("Filled login credentials"); + page.screenshot({ + path: "test-results/view_user_info_after_username_input.png", + }); + // Enter "gm" in the password input field + await page.fill('input[placeholder="Enter your password"]', "gm"); + page.screenshot({ + path: "test-results/view_user_info_after_password_input.png", + }); + + // Click the login button const loginButton = page.getByRole("button", { name: "Login" }); await expect(loginButton).toBeEnabled(); await loginButton.click(); - console.log("Clicked login button"); + page.screenshot({ + path: "test-results/view_user_info_after_login_button_click.png", + }); // Wait for navigation to complete and dashboard to load await page.waitForLoadState("networkidle"); - await page.goto("http://localhost:4000/ui?page=users"); + const tabElement = page.locator("span.ant-menu-title-content", { + hasText: "Internal User", + }); + await tabElement.click(); + page.screenshot({ + path: "test-results/view_user_info_after_internal_user_tab_click.png", + }); // Wait for loading state to disappear await page.waitForSelector('text="🚅 Loading users..."', { state: "hidden", timeout: 10000, }); - await page.waitForLoadState("networkidle"); page.screenshot({ path: "test-results/view_user_info_after_loading.png" }); // Wait for users table to load await page.waitForSelector("table"); From 5496e622c14eb3068d1292b2a8719119b021e762 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 3 Dec 2025 23:46:09 -0800 Subject: [PATCH 346/349] Unit tests --- ui/litellm-dashboard/tests/CreateKeyPage.expiredToken.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/tests/CreateKeyPage.expiredToken.test.tsx b/ui/litellm-dashboard/tests/CreateKeyPage.expiredToken.test.tsx index 9287e203182..c3c5ae59237 100644 --- a/ui/litellm-dashboard/tests/CreateKeyPage.expiredToken.test.tsx +++ b/ui/litellm-dashboard/tests/CreateKeyPage.expiredToken.test.tsx @@ -193,7 +193,7 @@ describe("CreateKeyPage auth behavior", () => { // Assert: we eventually redirect to SSO login (single replace, not assign/href) await waitFor(() => { - expect(window.location.replace).toHaveBeenCalledWith("https://example.com/sso/key/generate"); + expect(window.location.replace).toHaveBeenCalledWith("https://example.com/ui/login"); }); // And we attempted to clear the cookie (defensive deletion) From edd392b50d611829d05ee64e45fab1aee4f0bb8c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 4 Dec 2025 13:27:53 +0530 Subject: [PATCH 347/349] Add support for file content download for bedrock batches --- docs/my-website/docs/files_endpoints.md | 13 ++ .../docs/providers/bedrock_batches.md | 91 ++++++++ litellm/files/main.py | 17 +- litellm/llms/bedrock/files/handler.py | 206 ++++++++++++++++++ .../files/test_bedrock_files_integration.py | 110 ++++++++++ 5 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 litellm/llms/bedrock/files/handler.py create mode 100644 tests/test_litellm/llms/bedrock/files/test_bedrock_files_integration.py diff --git a/docs/my-website/docs/files_endpoints.md b/docs/my-website/docs/files_endpoints.md index fc0484e9219..30677c748a9 100644 --- a/docs/my-website/docs/files_endpoints.md +++ b/docs/my-website/docs/files_endpoints.md @@ -301,6 +301,17 @@ content = await litellm.afile_content( print("file content=", content) ``` +**Get File Content (Bedrock)** +```python +# For Bedrock batch output files stored in S3 +content = await litellm.afile_content( + file_id="s3://bucket-name/path/to/file.jsonl", # S3 URI or unified file ID + custom_llm_provider="bedrock", + aws_region_name="us-west-2" +) +print("file content=", content.text) +``` + @@ -313,4 +324,6 @@ print("file content=", content) ### [Vertex AI](./providers/vertex#batch-apis) +### [Bedrock](./providers/bedrock_batches#4-retrieve-batch-results) + ## [Swagger API Reference](https://litellm-api.up.railway.app/#/files) diff --git a/docs/my-website/docs/providers/bedrock_batches.md b/docs/my-website/docs/providers/bedrock_batches.md index a1116f41076..19446fda837 100644 --- a/docs/my-website/docs/providers/bedrock_batches.md +++ b/docs/my-website/docs/providers/bedrock_batches.md @@ -172,6 +172,97 @@ curl http://localhost:4000/v1/batches \ +### 4. Retrieve batch results + +Once the batch job is completed, download the results from S3: + + + + +```python showLineNumbers title="bedrock_batch.py" +... +# Wait for batch completion (check status periodically) +batch_status = client.batches.retrieve(batch_id=batch.id) + +if batch_status.status == "completed": + # Download the output file + result = client.files.content( + file_id=batch_status.output_file_id, + extra_headers={"custom-llm-provider": "bedrock"} + ) + + # Save or process the results + with open("batch_output.jsonl", "wb") as f: + f.write(result.content) + + # Parse JSONL results + for line in result.text.strip().split('\n'): + record = json.loads(line) + print(f"Record ID: {record['recordId']}") + print(f"Output: {record.get('modelOutput', {})}") +``` + + + + +```bash showLineNumbers title="Download Batch Results" +# First retrieve batch to get output_file_id +curl http://localhost:4000/v1/batches/batch_abc123 \ + -H "Authorization: Bearer sk-1234" + +# Then download the output file +curl http://localhost:4000/v1/files/{output_file_id}/content \ + -H "Authorization: Bearer sk-1234" \ + -H "custom-llm-provider: bedrock" \ + -o batch_output.jsonl +``` + + + + +```python showLineNumbers title="bedrock_batch.py" +import litellm +from litellm import file_content + +# Download using litellm directly (bypasses proxy managed files) +result = file_content( + file_id=batch_status.output_file_id, # Can be S3 URI or unified file ID + custom_llm_provider="bedrock", + aws_region_name="us-west-2", +) + +# Process results +print(result.text) +``` + + + + +**Output Format:** + +The batch output file is in JSONL format with each line containing: + +```json +{ + "recordId": "request-1", + "modelInput": { + "messages": [...], + "max_tokens": 1000 + }, + "modelOutput": { + "content": [...], + "id": "msg_abc123", + "model": "claude-3-5-sonnet-20240620-v1:0", + "role": "assistant", + "stop_reason": "end_turn", + "usage": { + "input_tokens": 15, + "output_tokens": 10 + } + } +} +``` + ## FAQ ### Where are my files written? diff --git a/litellm/files/main.py b/litellm/files/main.py index 71139001e52..9378715a472 100644 --- a/litellm/files/main.py +++ b/litellm/files/main.py @@ -18,6 +18,7 @@ from litellm import get_secret_str from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.llms.azure.files.handler import AzureOpenAIFilesAPI +from litellm.llms.bedrock.files.handler import BedrockFilesHandler from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler from litellm.llms.openai.openai import FileDeleted, FileObject, OpenAIFilesAPI @@ -47,6 +48,7 @@ base_llm_http_handler = BaseLLMHTTPHandler() openai_files_instance = OpenAIFilesAPI() azure_files_instance = AzureOpenAIFilesAPI() vertex_ai_files_instance = VertexAIFilesHandler() +bedrock_files_instance = BedrockFilesHandler() ################################################# @@ -755,7 +757,7 @@ def file_list( @client async def afile_content( file_id: str, - custom_llm_provider: Literal["openai", "azure", "vertex_ai", "hosted_vllm"] = "openai", + custom_llm_provider: Literal["openai", "azure", "vertex_ai", "bedrock", "hosted_vllm"] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -800,7 +802,7 @@ def file_content( file_id: str, model: Optional[str] = None, custom_llm_provider: Optional[ - Union[Literal["openai", "azure", "vertex_ai", "hosted_vllm"], str] + Union[Literal["openai", "azure", "vertex_ai", "bedrock", "hosted_vllm"], str] ] = None, extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, @@ -938,9 +940,18 @@ def file_content( timeout=timeout, max_retries=optional_params.max_retries, ) + elif custom_llm_provider == "bedrock": + response = bedrock_files_instance.file_content( + _is_async=_is_async, + file_content_request=_file_content_request, + api_base=optional_params.api_base, + optional_params=litellm_params_dict, + timeout=timeout, + max_retries=optional_params.max_retries, + ) else: raise litellm.exceptions.BadRequestError( - message="LiteLLM doesn't support {} for 'custom_llm_provider'. Supported providers are 'openai', 'azure', 'vertex_ai'.".format( + message="LiteLLM doesn't support {} for 'custom_llm_provider'. Supported providers are 'openai', 'azure', 'vertex_ai', 'bedrock'.".format( custom_llm_provider ), model="n/a", diff --git a/litellm/llms/bedrock/files/handler.py b/litellm/llms/bedrock/files/handler.py new file mode 100644 index 00000000000..d6177e090d5 --- /dev/null +++ b/litellm/llms/bedrock/files/handler.py @@ -0,0 +1,206 @@ +import asyncio +import base64 +from typing import Any, Coroutine, Optional, Tuple, Union + +import httpx + +from litellm import LlmProviders +from litellm.llms.custom_httpx.http_handler import get_async_httpx_client +from litellm.types.llms.openai import ( + FileContentRequest, + HttpxBinaryResponseContent, +) +from litellm.types.utils import SpecialEnums + +from ..base_aws_llm import BaseAWSLLM + + +class BedrockFilesHandler(BaseAWSLLM): + """ + Handles downloading files from S3 for Bedrock batch processing. + + This implementation downloads files from S3 buckets where Bedrock + stores batch output files. + """ + + def __init__(self): + super().__init__() + self.async_httpx_client = get_async_httpx_client( + llm_provider=LlmProviders.BEDROCK, + ) + + def _extract_s3_uri_from_file_id(self, file_id: str) -> str: + """ + Extract S3 URI from encoded file ID. + + The file ID can be in two formats: + 1. Base64-encoded unified file ID containing: llm_output_file_id,s3://bucket/path + 2. Direct S3 URI: s3://bucket/path + + Args: + file_id: Encoded file ID or direct S3 URI + + Returns: + S3 URI (e.g., "s3://bucket-name/path/to/file") + """ + # First, try to decode if it's a base64-encoded unified file ID + try: + # Add padding if needed + padded = file_id + "=" * (-len(file_id) % 4) + decoded = base64.urlsafe_b64decode(padded).decode() + + # Check if it's a unified file ID format + if decoded.startswith(SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value): + # Extract llm_output_file_id from the decoded string + if "llm_output_file_id," in decoded: + s3_uri = decoded.split("llm_output_file_id,")[1].split(";")[0] + return s3_uri + except Exception: + pass + + # If not base64 encoded or doesn't contain llm_output_file_id, assume it's already an S3 URI + if file_id.startswith("s3://"): + return file_id + + # If it doesn't start with s3://, assume it's a direct S3 URI and add the prefix + return f"s3://{file_id}" + + def _parse_s3_uri(self, s3_uri: str) -> Tuple[str, str]: + """ + Parse S3 URI to extract bucket name and object key. + + Args: + s3_uri: S3 URI (e.g., "s3://bucket-name/path/to/file") + + Returns: + Tuple of (bucket_name, object_key) + """ + if not s3_uri.startswith("s3://"): + raise ValueError(f"Invalid S3 URI format: {s3_uri}. Expected format: s3://bucket-name/path/to/file") + + # Remove 's3://' prefix + path = s3_uri[5:] + + if "/" in path: + bucket_name, object_key = path.split("/", 1) + else: + bucket_name = path + object_key = "" + + return bucket_name, object_key + + async def afile_content( + self, + file_content_request: FileContentRequest, + optional_params: dict, + timeout: Union[float, httpx.Timeout], + max_retries: Optional[int], + ) -> HttpxBinaryResponseContent: + """ + Download file content from S3 bucket for Bedrock files. + + Args: + file_content_request: Contains file_id (encoded or S3 URI) + optional_params: Optional parameters containing AWS credentials + timeout: Request timeout + max_retries: Max retry attempts + + Returns: + HttpxBinaryResponseContent: Binary content wrapped in compatible response format + """ + import boto3 + from botocore.credentials import Credentials + + file_id = file_content_request.get("file_id") + if not file_id: + raise ValueError("file_id is required in file_content_request") + + # Extract S3 URI from file ID + s3_uri = self._extract_s3_uri_from_file_id(file_id) + bucket_name, object_key = self._parse_s3_uri(s3_uri) + + # Get AWS credentials + aws_region_name = self._get_aws_region_name( + optional_params=optional_params, model="" + ) + credentials: Credentials = self.get_credentials( + aws_access_key_id=optional_params.get("aws_access_key_id"), + aws_secret_access_key=optional_params.get("aws_secret_access_key"), + aws_session_token=optional_params.get("aws_session_token"), + aws_region_name=aws_region_name, + aws_session_name=optional_params.get("aws_session_name"), + aws_profile_name=optional_params.get("aws_profile_name"), + aws_role_name=optional_params.get("aws_role_name"), + aws_web_identity_token=optional_params.get("aws_web_identity_token"), + aws_sts_endpoint=optional_params.get("aws_sts_endpoint"), + ) + + # Create S3 client + s3_client = boto3.client( + "s3", + aws_access_key_id=credentials.access_key, + aws_secret_access_key=credentials.secret_key, + aws_session_token=credentials.token, + region_name=aws_region_name, + ) + + # Download file from S3 + try: + response = s3_client.get_object(Bucket=bucket_name, Key=object_key) + file_content = response["Body"].read() + except Exception as e: + raise ValueError(f"Failed to download file from S3: {s3_uri}. Error: {str(e)}") + + # Create mock HTTP response + mock_response = httpx.Response( + status_code=200, + content=file_content, + headers={"content-type": "application/octet-stream"}, + request=httpx.Request(method="GET", url=s3_uri), + ) + + return HttpxBinaryResponseContent(response=mock_response) + + def file_content( + self, + _is_async: bool, + file_content_request: FileContentRequest, + api_base: Optional[str], + optional_params: dict, + timeout: Union[float, httpx.Timeout], + max_retries: Optional[int], + ) -> Union[ + HttpxBinaryResponseContent, Coroutine[Any, Any, HttpxBinaryResponseContent] + ]: + """ + Download file content from S3 bucket for Bedrock files. + Supports both sync and async operations. + + Args: + _is_async: Whether to run asynchronously + file_content_request: Contains file_id (encoded or S3 URI) + api_base: API base (unused for S3 operations) + optional_params: Optional parameters containing AWS credentials + timeout: Request timeout + max_retries: Max retry attempts + + Returns: + HttpxBinaryResponseContent or Coroutine: Binary content wrapped in compatible response format + """ + if _is_async: + return self.afile_content( + file_content_request=file_content_request, + optional_params=optional_params, + timeout=timeout, + max_retries=max_retries, + ) + else: + return asyncio.run( + self.afile_content( + file_content_request=file_content_request, + optional_params=optional_params, + timeout=timeout, + max_retries=max_retries, + ) + ) + diff --git a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_integration.py b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_integration.py new file mode 100644 index 00000000000..37a0daa1d50 --- /dev/null +++ b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_integration.py @@ -0,0 +1,110 @@ +""" +Test Bedrock files integration with main files API +""" + +import base64 +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +import litellm +from litellm.types.llms.openai import HttpxBinaryResponseContent +from litellm.types.utils import SpecialEnums + + +class TestBedrockFilesIntegration: + """Test integration of Bedrock files with main litellm API""" + + @pytest.mark.asyncio + async def test_litellm_afile_content_bedrock_provider_with_s3_uri(self): + """Test litellm.afile_content with bedrock provider using direct S3 URI""" + file_id = "s3://test-bucket/test-file.jsonl" + expected_content = b'{"recordId": "request-1", "modelInput": {}, "modelOutput": {}}' + + # Mock the bedrock_files_instance.file_content method + with patch( + "litellm.files.main.bedrock_files_instance.file_content", + new_callable=AsyncMock, + ) as mock_file_content: + # Create a mock HttpxBinaryResponseContent response + import httpx + + mock_response = httpx.Response( + status_code=200, + content=expected_content, + headers={"content-type": "application/octet-stream"}, + request=httpx.Request( + method="GET", url="s3://test-bucket/test-file.jsonl" + ), + ) + mock_file_content.return_value = HttpxBinaryResponseContent( + response=mock_response + ) + + # Call litellm.afile_content + result = await litellm.afile_content( + file_id=file_id, + custom_llm_provider="bedrock", + aws_region_name="us-west-2", + ) + + # Verify the result + assert isinstance(result, HttpxBinaryResponseContent) + assert result.response.content == expected_content + assert result.response.status_code == 200 + + # Verify the mock was called with correct parameters + mock_file_content.assert_called_once() + call_kwargs = mock_file_content.call_args.kwargs + assert call_kwargs["_is_async"] is True + assert call_kwargs["file_content_request"]["file_id"] == file_id + + @pytest.mark.asyncio + async def test_litellm_afile_content_bedrock_provider_with_unified_file_id(self): + """Test litellm.afile_content with bedrock provider using unified file ID""" + # Create a unified file ID + s3_uri = "s3://test-bucket/batch-outputs/output.jsonl" + unified_id = "test-unified-id-123" + model_id = "test-model-id-456" + + unified_file_id_str = f"litellm_proxy:application/json;unified_id,{unified_id};target_model_names,;llm_output_file_id,{s3_uri};llm_output_file_model_id,{model_id}" + encoded_file_id = base64.urlsafe_b64encode(unified_file_id_str.encode()).decode().rstrip("=") + + expected_content = b'{"recordId": "request-1", "modelInput": {}, "modelOutput": {}}' + + # Mock the bedrock_files_instance.file_content method + with patch( + "litellm.files.main.bedrock_files_instance.file_content", + new_callable=AsyncMock, + ) as mock_file_content: + # Create a mock HttpxBinaryResponseContent response + import httpx + + mock_response = httpx.Response( + status_code=200, + content=expected_content, + headers={"content-type": "application/octet-stream"}, + request=httpx.Request(method="GET", url=s3_uri), + ) + mock_file_content.return_value = HttpxBinaryResponseContent( + response=mock_response + ) + + # Call litellm.afile_content with unified file ID + result = await litellm.afile_content( + file_id=encoded_file_id, + custom_llm_provider="bedrock", + aws_region_name="us-west-2", + ) + + # Verify the result + assert isinstance(result, HttpxBinaryResponseContent) + assert result.response.content == expected_content + assert result.response.status_code == 200 + + # Verify the mock was called - the handler should extract S3 URI from unified file ID + mock_file_content.assert_called_once() + call_kwargs = mock_file_content.call_args.kwargs + assert call_kwargs["_is_async"] is True + # The handler extracts S3 URI from the unified file ID + assert call_kwargs["file_content_request"]["file_id"] == encoded_file_id From 6751badf3a1cecb094d489cb1518a969e0c607be Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 4 Dec 2025 21:49:33 +0530 Subject: [PATCH 348/349] fix: test_default_api_base for ragfow --- tests/local_testing/test_get_llm_provider.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/local_testing/test_get_llm_provider.py b/tests/local_testing/test_get_llm_provider.py index 261f9bd0eac..4df910bc73e 100644 --- a/tests/local_testing/test_get_llm_provider.py +++ b/tests/local_testing/test_get_llm_provider.py @@ -137,6 +137,9 @@ def test_default_api_base(): # Get the API base for the given provider if provider == "github_copilot": continue + # Skip ragflow as it requires specific model format: ragflow/chat/{id}/{model} or ragflow/agent/{id}/{model} + if provider == "ragflow": + continue _, _, _, api_base = _get_openai_compatible_provider_info( model=f"{provider}/*", api_base=None, api_key=None, dynamic_api_key=None ) From 8ee8cad4f1f8f472ed7031e668a983cd89e02c3b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 4 Dec 2025 09:26:55 -0800 Subject: [PATCH 349/349] feat - new model amazon.titan-image-generator-v2:0 (#17488) --- ...odel_prices_and_context_window_backup.json | 62 +++++++++++++++++-- model_prices_and_context_window.json | 9 +++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 903b86484cd..d02a01e3a67 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -425,6 +425,15 @@ "litellm_provider": "bedrock", "mode": "image_generation" }, + "amazon.titan-image-generator-v2:0": { + "input_cost_per_image": 0.0, + "output_cost_per_image": 0.008, + "output_cost_per_image_premium_image": 0.01, + "output_cost_per_image_above_1024_and_1024_pixels": 0.01, + "output_cost_per_image_above_1024_and_1024_pixels_and_premium_image": 0.012, + "litellm_provider": "bedrock", + "mode": "image_generation" + }, "twelvelabs.marengo-embed-2-7-v1:0": { "input_cost_per_token": 7e-05, "litellm_provider": "bedrock", @@ -18896,6 +18905,34 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2-0905-preview": { + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, + "moonshot/kimi-k2-turbo-preview": { + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 1.15e-06, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 2e-06, @@ -18953,14 +18990,15 @@ "supports_vision": true }, "moonshot/kimi-thinking-preview": { - "input_cost_per_token": 3e-05, + "cache_read_input_token_cost": 1.5e-07, + "input_cost_per_token": 6e-07, "litellm_provider": "moonshot", "max_input_tokens": 131072, "max_output_tokens": 131072, "max_tokens": 131072, "mode": "chat", - "output_cost_per_token": 3e-05, - "source": "https://platform.moonshot.ai/docs/pricing", + "output_cost_per_token": 2.5e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", "supports_vision": true }, "moonshot/kimi-k2-thinking": { @@ -18977,6 +19015,20 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2-thinking-turbo": { + "cache_read_input_token_cost": 1.5e-7, + "input_cost_per_token": 1.15e-6, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 8e-6, + "source": "https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "moonshot/moonshot-v1-128k": { "input_cost_per_token": 2e-06, "litellm_provider": "moonshot", @@ -26470,8 +26522,8 @@ "max_tokens": 128000, "max_input_tokens": 128000, "max_output_tokens": 128000, - "input_cost_per_token": 0.135, - "output_cost_per_token": 0.4, + "input_cost_per_token": 6e-07, + "output_cost_per_token": 2.5e-06, "litellm_provider": "wandb", "mode": "chat" }, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 6cc6e7772af..d02a01e3a67 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -425,6 +425,15 @@ "litellm_provider": "bedrock", "mode": "image_generation" }, + "amazon.titan-image-generator-v2:0": { + "input_cost_per_image": 0.0, + "output_cost_per_image": 0.008, + "output_cost_per_image_premium_image": 0.01, + "output_cost_per_image_above_1024_and_1024_pixels": 0.01, + "output_cost_per_image_above_1024_and_1024_pixels_and_premium_image": 0.012, + "litellm_provider": "bedrock", + "mode": "image_generation" + }, "twelvelabs.marengo-embed-2-7-v1:0": { "input_cost_per_token": 7e-05, "litellm_provider": "bedrock",