From 562afb208d586cdb944a0052dec430a12f2618af Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 4 Dec 2025 12:30:08 -0800 Subject: [PATCH 1/5] v0 customer usage, pending tests + extras version bump --- .../litellm_proxy_extras/schema.prisma | 28 ++++ litellm/constants.py | 1 + litellm/proxy/_types.py | 2 + litellm/proxy/db/db_spend_update_writer.py | 120 +++++++++++++++++- .../redis_update_buffer.py | 36 ++++++ .../customer_endpoints.py | 80 +++++++++++- litellm/proxy/schema.prisma | 28 ++++ schema.prisma | 28 ++++ 8 files changed, 319 insertions(+), 4 deletions(-) diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index 2883dfc4b8..4d4a127f7e 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -462,6 +462,34 @@ model LiteLLM_DailyOrganizationSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily end user (customer) spend metrics per model and key +model LiteLLM_DailyEndUserSpend { + id String @id @default(uuid()) + end_user_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([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([end_user_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 fa9f1d527a..ededd35001 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -149,6 +149,7 @@ 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_END_USER_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_end_user_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 53a8627bc8..b8731bd2e5 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3615,6 +3615,8 @@ class DailyOrganizationSpendTransaction(BaseDailySpendTransaction): class DailyUserSpendTransaction(BaseDailySpendTransaction): user_id: str +class DailyEndUserSpendTransaction(BaseDailySpendTransaction): + end_user_id: str class DailyTagSpendTransaction(BaseDailySpendTransaction): request_id: Optional[str] diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 6c9289e3ff..715a6ebd25 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -25,6 +25,7 @@ from litellm.proxy._types import ( DailyTagSpendTransaction, DailyOrganizationSpendTransaction, DailyTeamSpendTransaction, + DailyEndUserSpendTransaction, DailyUserSpendTransaction, DBSpendUpdateTransactions, Litellm_EntityType, @@ -65,6 +66,7 @@ class DBSpendUpdateWriter: self.spend_update_queue = SpendUpdateQueue() self.daily_spend_update_queue = DailySpendUpdateQueue() self.daily_team_spend_update_queue = DailySpendUpdateQueue() + self.daily_end_user_spend_update_queue = DailySpendUpdateQueue() self.daily_org_spend_update_queue = DailySpendUpdateQueue() self.daily_tag_spend_update_queue = DailySpendUpdateQueue() @@ -182,6 +184,13 @@ class DBSpendUpdateWriter: ) ) + asyncio.create_task( + self.add_spend_log_transaction_to_daily_end_user_transaction( + payload=payload, + prisma_client=prisma_client, + ) + ) + asyncio.create_task( self.add_spend_log_transaction_to_daily_team_transaction( payload=payload, @@ -475,6 +484,7 @@ class DBSpendUpdateWriter: 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_end_user_spend_update_queue=self.daily_end_user_spend_update_queue, daily_tag_spend_update_queue=self.daily_tag_spend_update_queue, ) @@ -538,6 +548,16 @@ class DBSpendUpdateWriter: proxy_logging_obj=proxy_logging_obj, daily_spend_transactions=daily_tag_spend_update_transactions, ) + daily_end_user_spend_update_transactions = ( + await self.redis_update_buffer.get_all_daily_end_user_spend_update_transactions_from_redis_buffer() + ) + if daily_end_user_spend_update_transactions is not None: + await DBSpendUpdateWriter.update_daily_end_user_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + daily_spend_transactions=daily_end_user_spend_update_transactions, + ) except Exception as e: verbose_proxy_logger.error(f"Error committing spend updates: {e}") finally: @@ -627,6 +647,20 @@ class DBSpendUpdateWriter: daily_spend_transactions=daily_tag_spend_update_transactions, ) + ################## Daily End-User Spend Update Transactions ################## + # Aggregate all in memory daily end-user spend transactions and commit to db + daily_end_user_spend_update_transactions = cast( + Dict[str, DailyEndUserSpendTransaction], + await self.daily_end_user_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions(), + ) + + await DBSpendUpdateWriter.update_daily_end_user_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + daily_spend_transactions=daily_end_user_spend_update_transactions, + ) + async def _commit_spend_updates_to_db( # noqa: PLR0915 self, prisma_client: PrismaClient, @@ -990,6 +1024,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, DailyEndUserSpendTransaction], + entity_type: Literal["end_user"], + entity_id_field: str, + table_name: str, + unique_constraint_name: str, + ) -> None: + ... + @overload @staticmethod async def _update_daily_spend( @@ -1015,14 +1063,15 @@ class DBSpendUpdateWriter: Dict[str, DailyTeamSpendTransaction], Dict[str, DailyTagSpendTransaction], Dict[str, DailyOrganizationSpendTransaction], + Dict[str, DailyEndUserSpendTransaction], ], - entity_type: Literal["user", "team", "org", "tag"], + entity_type: Literal["user", "team", "org", "tag", "end_user"], entity_id_field: str, table_name: str, unique_constraint_name: str, ) -> None: """ - Generic function to update daily spend for any entity type (user, team, org, tag) + Generic function to update daily spend for any entity type (user, team, org, tag, end_user) """ from litellm.proxy.utils import _raise_failed_update_spend_exception @@ -1267,6 +1316,27 @@ class DBSpendUpdateWriter: unique_constraint_name="organization_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", ) + @staticmethod + async def update_daily_end_user_spend( + n_retry_times: int, + prisma_client: PrismaClient, + proxy_logging_obj: ProxyLogging, + daily_spend_transactions: Dict[str, DailyEndUserSpendTransaction], + ): + """ + Batch job to update LiteLLM_DailyEndUserSpend 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="end_user", + entity_id_field="end_user_id", + table_name="litellm_dailyenduserspend", + unique_constraint_name="end_user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + ) + @staticmethod async def update_daily_tag_spend( n_retry_times: int, @@ -1292,7 +1362,7 @@ class DBSpendUpdateWriter: self, payload: Union[dict, SpendLogsPayload], prisma_client: PrismaClient, - type: Literal["user", "team", "org", "request_tags"] = "user", + type: Literal["user", "team", "org", "request_tags", "end_user"] = "user", ) -> Optional[BaseDailySpendTransaction]: common_expected_keys = ["startTime", "api_key"] if type == "user": @@ -1303,6 +1373,8 @@ class DBSpendUpdateWriter: expected_keys = ["organization_id", *common_expected_keys] elif type == "request_tags": expected_keys = ["request_tags", *common_expected_keys] + elif type == "end_user": + expected_keys = ["end_user_id", *common_expected_keys] else: raise ValueError(f"Invalid type: {type}") if not all(key in payload for key in expected_keys): @@ -1474,6 +1546,48 @@ class DBSpendUpdateWriter: update={daily_transaction_key: daily_transaction} ) + async def add_spend_log_transaction_to_daily_end_user_transaction( + self, + payload: SpendLogsPayload, + prisma_client: Optional[PrismaClient] = None, + ) -> None: + if prisma_client is None: + verbose_proxy_logger.debug( + "prisma_client is None. Skipping writing spend logs to db." + ) + return + + end_user_id = payload.get("end_user") + if end_user_id is None or end_user_id == "": + verbose_proxy_logger.debug( + "end_user is None or empty for request. Skipping incrementing end user spend." + ) + return + + payload_with_end_user_id = cast( + SpendLogsPayload, + { + **payload, + "end_user_id": end_user_id, + }, + ) + + base_daily_transaction = ( + await self._common_add_spend_log_transaction_to_daily_transaction( + payload_with_end_user_id, prisma_client, "end_user" + ) + ) + if base_daily_transaction is None: + return + + daily_transaction_key = f"{end_user_id}_{base_daily_transaction['date']}_{payload_with_end_user_id['api_key']}_{payload_with_end_user_id['model']}_{payload_with_end_user_id['custom_llm_provider']}" + daily_transaction = DailyEndUserSpendTransaction( + end_user_id=end_user_id, **base_daily_transaction + ) + await self.daily_end_user_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 921fd9701b..e3b20d7266 100644 --- a/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py +++ b/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py @@ -16,6 +16,7 @@ from litellm.constants import ( REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY, REDIS_DAILY_TEAM_SPEND_UPDATE_BUFFER_KEY, REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY, + REDIS_DAILY_END_USER_SPEND_UPDATE_BUFFER_KEY, REDIS_UPDATE_BUFFER_KEY, ) from litellm.litellm_core_utils.safe_json_dumps import safe_dumps @@ -24,6 +25,7 @@ from litellm.proxy._types import ( DailyTeamSpendTransaction, DailyUserSpendTransaction, DailyOrganizationSpendTransaction, + DailyEndUserSpendTransaction, DBSpendUpdateTransactions, ) from litellm.proxy.db.db_transaction_queue.base_update_queue import service_logger_obj @@ -107,6 +109,7 @@ class RedisUpdateBuffer: daily_spend_update_queue: DailySpendUpdateQueue, daily_team_spend_update_queue: DailySpendUpdateQueue, daily_org_spend_update_queue: DailySpendUpdateQueue, + daily_end_user_spend_update_queue: DailySpendUpdateQueue, daily_tag_spend_update_queue: DailySpendUpdateQueue, ): """ @@ -172,6 +175,9 @@ class RedisUpdateBuffer: daily_org_spend_update_transactions = ( await daily_org_spend_update_queue.flush_and_get_aggregated_daily_spend_update_transactions() ) + daily_end_user_spend_update_transactions = ( + await daily_end_user_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() ) @@ -207,6 +213,12 @@ class RedisUpdateBuffer: service_type=ServiceTypes.REDIS_DAILY_SPEND_UPDATE_QUEUE, ) + await self._store_transactions_in_redis( + transactions=daily_end_user_spend_update_transactions, + redis_key=REDIS_DAILY_END_USER_SPEND_UPDATE_BUFFER_KEY, + service_type=ServiceTypes.REDIS_DAILY_END_USER_SPEND_UPDATE_QUEUE, + ) + await self._store_transactions_in_redis( transactions=daily_tag_spend_update_transactions, redis_key=REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY, @@ -365,6 +377,30 @@ class RedisUpdateBuffer: ), ) + async def get_all_daily_end_user_spend_update_transactions_from_redis_buffer( + self, + ) -> Optional[Dict[str, DailyEndUserSpendTransaction]]: + """ + Gets all the daily end-user 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_END_USER_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, DailyEndUserSpendTransaction], + 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/customer_endpoints.py b/litellm/proxy/management_endpoints/customer_endpoints.py index 3afbbdd5a4..9ff0fe6e59 100644 --- a/litellm/proxy/management_endpoints/customer_endpoints.py +++ b/litellm/proxy/management_endpoints/customer_endpoints.py @@ -20,6 +20,10 @@ 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.utils import handle_exception_on_proxy +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() @@ -673,4 +677,78 @@ async def list_end_user( str(e) ) ) - raise handle_exception_on_proxy(e) \ No newline at end of file + raise handle_exception_on_proxy(e) + +@router.get( + "/customer/daily/activity", + tags=["Customer Management"], + dependencies=[Depends(user_api_key_auth)], + response_model=SpendAnalyticsPaginatedResponse, +) +@router.get( + "/end_user/daily/activity", + tags=["Customer Management"], + include_in_schema=False, + dependencies=[Depends(user_api_key_auth)], +) +async def get_customer_daily_activity( + end_user_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_end_user_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, + ) + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, + ) + + # Parse comma-separated ids + end_user_ids_list = end_user_ids.split(",") if end_user_ids else None + exclude_end_user_ids_list: Optional[List[str]] = None + if exclude_end_user_ids: + exclude_end_user_ids_list = ( + exclude_end_user_ids.split(",") if exclude_end_user_ids else None + ) + + + # Fetch organization aliases for metadata + where_condition = {} + if end_user_ids_list: + where_condition["user_id"] = {"in": list(end_user_ids_list)} + end_user_aliases = await prisma_client.db.litellm_endusertable.find_many( + where=where_condition + ) + end_user_alias_metadata = { + e.user_id: {"alias": e.alias} + for e in end_user_aliases + } + + # Query daily activity for organizations + return await get_daily_activity( + prisma_client=prisma_client, + table_name="litellm_dailyenduserspend", + entity_id_field="end_user_id", + entity_id=end_user_ids_list, + entity_metadata_field=end_user_alias_metadata, + exclude_entity_ids=exclude_end_user_ids_list, + start_date=start_date, + end_date=end_date, + model=model, + api_key=api_key, + page=page, + page_size=page_size, + ) \ No newline at end of file diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 2883dfc4b8..4d4a127f7e 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -462,6 +462,34 @@ model LiteLLM_DailyOrganizationSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily end user (customer) spend metrics per model and key +model LiteLLM_DailyEndUserSpend { + id String @id @default(uuid()) + end_user_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([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([end_user_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 2883dfc4b8..4d4a127f7e 100644 --- a/schema.prisma +++ b/schema.prisma @@ -462,6 +462,34 @@ model LiteLLM_DailyOrganizationSpend { @@index([mcp_namespaced_tool_name]) } +// Track daily end user (customer) spend metrics per model and key +model LiteLLM_DailyEndUserSpend { + id String @id @default(uuid()) + end_user_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([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@index([date]) + @@index([end_user_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()) From 2e65c464ade1a0dd5e86a2902f2e2fe1b01f1168 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 4 Dec 2025 12:36:15 -0800 Subject: [PATCH 2/5] Adding tests --- .../proxy/db/test_db_spend_update_writer.py | 75 +++++++++++++- .../test_customer_endpoints.py | 98 ++++++++++++++++++- 2 files changed, 171 insertions(+), 2 deletions(-) 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 181d21b44f..db6c318357 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 @@ -572,4 +572,77 @@ async def test_add_spend_log_transaction_to_daily_org_transaction_skips_when_org org_id=None, ) - writer.daily_org_spend_update_queue.add_update.assert_not_called() \ No newline at end of file + writer.daily_org_spend_update_queue.add_update.assert_not_called() + + +@pytest.mark.asyncio +async def test_add_spend_log_transaction_to_daily_end_user_transaction_injects_end_user_id_and_queues_update(): + writer = DBSpendUpdateWriter() + mock_prisma = MagicMock() + mock_prisma.get_request_status = MagicMock(return_value="success") + + end_user_id = "end-user-xyz" + payload = { + "request_id": "req-1", + "user": "test-user", + "end_user": end_user_id, + "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_end_user_spend_update_queue.add_update = AsyncMock() + + await writer.add_spend_log_transaction_to_daily_end_user_transaction( + payload=payload, + prisma_client=mock_prisma, + ) + + writer.daily_end_user_spend_update_queue.add_update.assert_called_once() + + call_args = writer.daily_end_user_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"{end_user_id}_2024-01-01_test-key_gpt-4_openai" + assert transaction["end_user_id"] == end_user_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_end_user_transaction_skips_when_end_user_id_missing(): + 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_end_user_spend_update_queue.add_update = AsyncMock() + + await writer.add_spend_log_transaction_to_daily_end_user_transaction( + payload=payload, + prisma_client=mock_prisma, + ) + + writer.daily_end_user_spend_update_queue.add_update.assert_not_called() \ No newline at end of file diff --git a/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py index 86a6ceec25..25ff6f8942 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py @@ -1,4 +1,4 @@ -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import FastAPI, HTTPException, Request, status @@ -301,3 +301,99 @@ def test_customer_endpoints_error_schema_consistency(mock_prisma_client, mock_us for key in ["message", "type", "code"]: assert isinstance(error1[key], str), f"error1[{key}] should be a string" assert isinstance(error2[key], str), f"error2[{key}] should be a string" + + +@pytest.mark.asyncio +async def test_get_customer_daily_activity_admin_param_passing(monkeypatch): + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints import customer_endpoints + from litellm.proxy.management_endpoints.customer_endpoints import ( + get_customer_daily_activity, + ) + + mock_prisma_client = AsyncMock() + mock_prisma_client.db.litellm_endusertable.find_many = AsyncMock( + return_value=[] + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + mocked_response = MagicMock(name="SpendAnalyticsPaginatedResponse") + get_daily_activity_mock = AsyncMock(return_value=mocked_response) + monkeypatch.setattr( + customer_endpoints, "get_daily_activity", get_daily_activity_mock + ) + + auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin1") + result = await get_customer_daily_activity( + end_user_ids="end-user-1,end-user-2", + start_date="2024-01-01", + end_date="2024-01-31", + model="gpt-4", + api_key="test-key", + page=2, + page_size=5, + exclude_end_user_ids="end-user-3", + user_api_key_dict=auth, + ) + + get_daily_activity_mock.assert_awaited_once() + kwargs = get_daily_activity_mock.call_args.kwargs + assert kwargs["table_name"] == "litellm_dailyenduserspend" + assert kwargs["entity_id_field"] == "end_user_id" + assert kwargs["entity_id"] == ["end-user-1", "end-user-2"] + assert kwargs["exclude_entity_ids"] == ["end-user-3"] + 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_customer_daily_activity_with_end_user_aliases(monkeypatch): + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints import customer_endpoints + from litellm.proxy.management_endpoints.customer_endpoints import ( + get_customer_daily_activity, + ) + + mock_prisma_client = AsyncMock() + mock_end_user1 = MagicMock() + mock_end_user1.user_id = "end-user-1" + mock_end_user1.alias = "Customer One" + mock_end_user2 = MagicMock() + mock_end_user2.user_id = "end-user-2" + mock_end_user2.alias = "Customer Two" + + mock_prisma_client.db.litellm_endusertable.find_many = AsyncMock( + return_value=[mock_end_user1, mock_end_user2] + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + mocked_response = MagicMock(name="SpendAnalyticsPaginatedResponse") + get_daily_activity_mock = AsyncMock(return_value=mocked_response) + monkeypatch.setattr( + customer_endpoints, "get_daily_activity", get_daily_activity_mock + ) + + auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin1") + await get_customer_daily_activity( + end_user_ids="end-user-1,end-user-2", + start_date="2024-01-01", + end_date="2024-01-31", + model=None, + api_key=None, + page=1, + page_size=10, + exclude_end_user_ids=None, + user_api_key_dict=auth, + ) + + kwargs = get_daily_activity_mock.call_args.kwargs + assert kwargs["entity_metadata_field"] == { + "end-user-1": {"alias": "Customer One"}, + "end-user-2": {"alias": "Customer Two"}, + } From 5439f03bfcb74175633cf57a3d9b75af3854fbd6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 4 Dec 2025 12:56:43 -0800 Subject: [PATCH 3/5] =?UTF-8?q?bump:=20version=200.4.9=20=E2=86=92=200.4.1?= =?UTF-8?q?0?= 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 6bc576e4f3..cc8a92b9c6 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.9" +version = "0.4.10" 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.9" +version = "0.4.10" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/pyproject.toml b/pyproject.toml index 81e31a5ea8..da31bc9d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ websockets = {version = "^15.0.1", 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.9", optional = true} +litellm-proxy-extras = {version = "0.4.10", 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 b61428588d..ac1eba2f4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,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.9 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.10 # 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 183437795042392776c3ef6e8c798cfeafebb9c7 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 4 Dec 2025 12:57:06 -0800 Subject: [PATCH 4/5] Adding migration --- .../migration.sql | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20251204124859_add_end_user_spend_table/migration.sql diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251204124859_add_end_user_spend_table/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251204124859_add_end_user_spend_table/migration.sql new file mode 100644 index 0000000000..c4234785c5 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251204124859_add_end_user_spend_table/migration.sql @@ -0,0 +1,42 @@ +-- CreateTable +CREATE TABLE "LiteLLM_DailyEndUserSpend" ( + "id" TEXT NOT NULL, + "end_user_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_DailyEndUserSpend_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_date_idx" ON "LiteLLM_DailyEndUserSpend"("date"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_end_user_id_idx" ON "LiteLLM_DailyEndUserSpend"("end_user_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_api_key_idx" ON "LiteLLM_DailyEndUserSpend"("api_key"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_model_idx" ON "LiteLLM_DailyEndUserSpend"("model"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_mcp_namespaced_tool_name_idx" ON "LiteLLM_DailyEndUserSpend"("mcp_namespaced_tool_name"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyEndUserSpend_end_user_id_date_api_key_model_cu_key" ON "LiteLLM_DailyEndUserSpend"("end_user_id", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name"); + From b12ccb1a7acb21567f46e3b149651bf13fb6502a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 4 Dec 2025 13:11:20 -0800 Subject: [PATCH 5/5] Publish proxy extras --- ...litellm_proxy_extras-0.4.10-py3-none-any.whl | Bin 0 -> 40415 bytes .../dist/litellm_proxy_extras-0.4.10.tar.gz | Bin 0 -> 18851 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10.tar.gz diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..ce4e805663a67d925d8007a8a098125eb98a0cf7 GIT binary patch literal 40415 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 literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.10.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a4e218ee2fac4ef1cd3ebbeddd774e5596707a85 GIT binary patch literal 18851 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 YOr;N3pXyV69?s|g10(g}%mDZT0IGmedjJ3c literal 0 HcmV?d00001