diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17-py3-none-any.whl new file mode 100644 index 0000000000..9f8a8b0393 Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17-py3-none-any.whl differ diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17.tar.gz new file mode 100644 index 0000000000..37c3d3f263 Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.17.tar.gz differ diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19-py3-none-any.whl new file mode 100644 index 0000000000..471ddce912 Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19-py3-none-any.whl differ diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19.tar.gz new file mode 100644 index 0000000000..290c4bfeef Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.19.tar.gz differ diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260106155622_add_endpoint_to_daily_activity_tables/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260106155622_add_endpoint_to_daily_activity_tables/migration.sql new file mode 100644 index 0000000000..4ed7feb9ca --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260106155622_add_endpoint_to_daily_activity_tables/migration.sql @@ -0,0 +1,72 @@ +-- DropIndex +DROP INDEX "LiteLLM_DailyAgentSpend_agent_id_date_api_key_model_custom__key"; + +-- DropIndex +DROP INDEX "LiteLLM_DailyEndUserSpend_end_user_id_date_api_key_model_cu_key"; + +-- DropIndex +DROP INDEX "LiteLLM_DailyOrganizationSpend_organization_id_date_api_key_key"; + +-- DropIndex +DROP INDEX "LiteLLM_DailyTagSpend_tag_date_api_key_model_custom_llm_pro_key"; + +-- DropIndex +DROP INDEX "LiteLLM_DailyTeamSpend_team_id_date_api_key_model_custom_ll_key"; + +-- DropIndex +DROP INDEX "LiteLLM_DailyUserSpend_user_id_date_api_key_model_custom_ll_key"; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyAgentSpend" ADD COLUMN "endpoint" TEXT; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyEndUserSpend" ADD COLUMN "endpoint" TEXT; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyOrganizationSpend" ADD COLUMN "endpoint" TEXT; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyTagSpend" ADD COLUMN "endpoint" TEXT; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyTeamSpend" ADD COLUMN "endpoint" TEXT; + +-- AlterTable +ALTER TABLE "LiteLLM_DailyUserSpend" ADD COLUMN "endpoint" TEXT; + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyAgentSpend_endpoint_idx" ON "LiteLLM_DailyAgentSpend"("endpoint"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyAgentSpend_agent_id_date_api_key_model_custom__key" ON "LiteLLM_DailyAgentSpend"("agent_id", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name", "endpoint"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyEndUserSpend_endpoint_idx" ON "LiteLLM_DailyEndUserSpend"("endpoint"); + +-- 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", "endpoint"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyOrganizationSpend_endpoint_idx" ON "LiteLLM_DailyOrganizationSpend"("endpoint"); + +-- 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", "endpoint"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyTagSpend_endpoint_idx" ON "LiteLLM_DailyTagSpend"("endpoint"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyTagSpend_tag_date_api_key_model_custom_llm_pro_key" ON "LiteLLM_DailyTagSpend"("tag", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name", "endpoint"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyTeamSpend_endpoint_idx" ON "LiteLLM_DailyTeamSpend"("endpoint"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyTeamSpend_team_id_date_api_key_model_custom_ll_key" ON "LiteLLM_DailyTeamSpend"("team_id", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name", "endpoint"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_endpoint_idx" ON "LiteLLM_DailyUserSpend"("endpoint"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyUserSpend_user_id_date_api_key_model_custom_ll_key" ON "LiteLLM_DailyUserSpend"("user_id", "date", "api_key", "model", "custom_llm_provider", "mcp_namespaced_tool_name", "endpoint"); + diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index e565135bbc..c9de8d9c29 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -422,6 +422,7 @@ model LiteLLM_DailyUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -433,12 +434,13 @@ model LiteLLM_DailyUserSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily organization spend metrics per model and key @@ -451,6 +453,7 @@ model LiteLLM_DailyOrganizationSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -462,12 +465,13 @@ model LiteLLM_DailyOrganizationSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([organization_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily end user (customer) spend metrics per model and key @@ -480,6 +484,7 @@ model LiteLLM_DailyEndUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -490,12 +495,13 @@ model LiteLLM_DailyEndUserSpend { 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]) + @@unique([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([end_user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily agent spend metrics per model and key @@ -508,6 +514,7 @@ model LiteLLM_DailyAgentSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -518,12 +525,13 @@ model LiteLLM_DailyAgentSpend { failed_requests BigInt @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([agent_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -536,6 +544,7 @@ model LiteLLM_DailyTeamSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -547,12 +556,13 @@ model LiteLLM_DailyTeamSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([team_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -566,6 +576,7 @@ model LiteLLM_DailyTagSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -577,12 +588,13 @@ model LiteLLM_DailyTagSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([tag]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index 384a4cce0a..487cef29c3 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.18" +version = "0.4.19" 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.18" +version = "0.4.19" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index cff3bee1ca..7a01f4db6f 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3735,6 +3735,7 @@ class BaseDailySpendTransaction(TypedDict): model_group: Optional[str] mcp_namespaced_tool_name: Optional[str] custom_llm_provider: Optional[str] + endpoint: Optional[str] # token count metrics prompt_tokens: int diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 5c5cd7c19f..429e56c805 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -42,6 +42,7 @@ from litellm.proxy.db.db_transaction_queue.daily_spend_update_queue import ( from litellm.proxy.db.db_transaction_queue.pod_lock_manager import PodLockManager from litellm.proxy.db.db_transaction_queue.redis_update_buffer import RedisUpdateBuffer from litellm.proxy.db.db_transaction_queue.spend_update_queue import SpendUpdateQueue +from litellm.proxy.route_llm_request import ROUTE_ENDPOINT_MAPPING if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient, ProxyLogging @@ -1205,6 +1206,7 @@ class DBSpendUpdateWriter: "mcp_namespaced_tool_name" ) or "", + "endpoint": transaction.get("endpoint") or "", } } @@ -1225,6 +1227,7 @@ class DBSpendUpdateWriter: "custom_llm_provider": transaction.get( "custom_llm_provider" ), + "endpoint": transaction.get("endpoint"), "prompt_tokens": transaction["prompt_tokens"], "completion_tokens": transaction["completion_tokens"], "spend": transaction["spend"], @@ -1287,6 +1290,9 @@ class DBSpendUpdateWriter: if entity_type == "tag" and "request_id" in transaction: update_data["request_id"] = transaction.get("request_id") + # Add endpoint to update_data so existing rows get their endpoint field updated + update_data["endpoint"] = transaction.get("endpoint") or "" + table.upsert( where=where_clause, data={ @@ -1347,7 +1353,7 @@ class DBSpendUpdateWriter: entity_type="user", entity_id_field="user_id", table_name="litellm_dailyuserspend", - unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) @staticmethod @@ -1368,7 +1374,7 @@ class DBSpendUpdateWriter: entity_type="team", entity_id_field="team_id", table_name="litellm_dailyteamspend", - unique_constraint_name="team_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + unique_constraint_name="team_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) @staticmethod @@ -1389,7 +1395,7 @@ class DBSpendUpdateWriter: 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", + unique_constraint_name="organization_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) @staticmethod @@ -1410,7 +1416,7 @@ class DBSpendUpdateWriter: 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", + unique_constraint_name="end_user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) @staticmethod @@ -1431,7 +1437,7 @@ class DBSpendUpdateWriter: entity_type="agent", entity_id_field="agent_id", table_name="litellm_dailyagentspend", - unique_constraint_name="agent_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + unique_constraint_name="agent_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) @staticmethod @@ -1452,7 +1458,7 @@ class DBSpendUpdateWriter: entity_type="tag", entity_id_field="tag", table_name="litellm_dailytagspend", - unique_constraint_name="tag_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name", + unique_constraint_name="tag_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) async def _common_add_spend_log_transaction_to_daily_transaction( @@ -1513,6 +1519,12 @@ class DBSpendUpdateWriter: ) return None try: + # Map call_type to endpoint using ROUTE_ENDPOINT_MAPPING + call_type = payload.get("call_type", None) + endpoint = None + if call_type: + endpoint = ROUTE_ENDPOINT_MAPPING.get(call_type, None) + daily_transaction = BaseDailySpendTransaction( date=date, api_key=payload["api_key"], @@ -1520,6 +1532,7 @@ class DBSpendUpdateWriter: model_group=payload.get("model_group", None), mcp_namespaced_tool_name=payload.get("mcp_namespaced_tool_name", None), custom_llm_provider=payload.get("custom_llm_provider", None), + endpoint=endpoint, prompt_tokens=payload["prompt_tokens"], completion_tokens=payload["completion_tokens"], spend=payload["spend"], @@ -1563,7 +1576,8 @@ class DBSpendUpdateWriter: if base_daily_transaction is None: return - daily_transaction_key = f"{payload['user']}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + daily_transaction_key = f"{payload['user']}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}_{endpoint_str}" daily_transaction = DailyUserSpendTransaction( user_id=payload["user"], **base_daily_transaction ) @@ -1595,7 +1609,8 @@ class DBSpendUpdateWriter: ) return - daily_transaction_key = f"{payload['team_id']}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + daily_transaction_key = f"{payload['team_id']}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}_{endpoint_str}" daily_transaction = DailyTeamSpendTransaction( team_id=payload["team_id"], **base_daily_transaction ) @@ -1637,7 +1652,8 @@ class DBSpendUpdateWriter: 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']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + 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']}_{endpoint_str}" daily_transaction = DailyOrganizationSpendTransaction( organization_id=org_id, **base_daily_transaction ) @@ -1679,7 +1695,8 @@ class DBSpendUpdateWriter: 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']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + 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']}_{endpoint_str}" daily_transaction = DailyEndUserSpendTransaction( end_user_id=end_user_id, **base_daily_transaction ) @@ -1723,7 +1740,8 @@ class DBSpendUpdateWriter: ) if base_daily_transaction is None: return - daily_transaction_key = f"{payload['agent_id']}_{base_daily_transaction['date']}_{payload_with_agent_id['api_key']}_{payload_with_agent_id['model']}_{payload_with_agent_id['custom_llm_provider']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + daily_transaction_key = f"{payload['agent_id']}_{base_daily_transaction['date']}_{payload_with_agent_id['api_key']}_{payload_with_agent_id['model']}_{payload_with_agent_id['custom_llm_provider']}_{endpoint_str}" daily_transaction = DailyAgentSpendTransaction( agent_id=payload['agent_id'], **base_daily_transaction ) @@ -1763,7 +1781,8 @@ class DBSpendUpdateWriter: else: raise ValueError(f"Invalid request_tags: {payload['request_tags']}") for tag in request_tags: - daily_transaction_key = f"{tag}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}" + endpoint_str = base_daily_transaction.get("endpoint") or "" + daily_transaction_key = f"{tag}_{base_daily_transaction['date']}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}_{endpoint_str}" daily_transaction = DailyTagSpendTransaction( tag=tag, **base_daily_transaction, request_id=payload["request_id"] ) diff --git a/litellm/proxy/management_endpoints/common_daily_activity.py b/litellm/proxy/management_endpoints/common_daily_activity.py index cd28cbb714..f52abf86b9 100644 --- a/litellm/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/proxy/management_endpoints/common_daily_activity.py @@ -227,6 +227,41 @@ def update_breakdown_metrics( ) ) + # Update endpoint breakdown + if record.endpoint: + if record.endpoint not in breakdown.endpoints: + breakdown.endpoints[record.endpoint] = MetricWithMetadata( + metrics=SpendMetrics(), + metadata={}, + ) + breakdown.endpoints[record.endpoint].metrics = update_metrics( + breakdown.endpoints[record.endpoint].metrics, record + ) + + # Update API key breakdown for this endpoint + if record.api_key not in breakdown.endpoints[record.endpoint].api_key_breakdown: + breakdown.endpoints[record.endpoint].api_key_breakdown[record.api_key] = ( + KeyMetricWithMetadata( + metrics=SpendMetrics(), + metadata=KeyMetadata( + key_alias=api_key_metadata.get(record.api_key, {}).get( + "key_alias", None + ), + team_id=api_key_metadata.get(record.api_key, {}).get( + "team_id", None + ), + ), + ) + ) + breakdown.endpoints[record.endpoint].api_key_breakdown[record.api_key].metrics = ( + update_metrics( + breakdown.endpoints[record.endpoint] + .api_key_breakdown[record.api_key] + .metrics, + record, + ) + ) + # Update api key breakdown if record.api_key not in breakdown.api_keys: breakdown.api_keys[record.api_key] = KeyMetricWithMetadata( diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index e565135bbc..c9de8d9c29 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -422,6 +422,7 @@ model LiteLLM_DailyUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -433,12 +434,13 @@ model LiteLLM_DailyUserSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily organization spend metrics per model and key @@ -451,6 +453,7 @@ model LiteLLM_DailyOrganizationSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -462,12 +465,13 @@ model LiteLLM_DailyOrganizationSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([organization_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily end user (customer) spend metrics per model and key @@ -480,6 +484,7 @@ model LiteLLM_DailyEndUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -490,12 +495,13 @@ model LiteLLM_DailyEndUserSpend { 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]) + @@unique([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([end_user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily agent spend metrics per model and key @@ -508,6 +514,7 @@ model LiteLLM_DailyAgentSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -518,12 +525,13 @@ model LiteLLM_DailyAgentSpend { failed_requests BigInt @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([agent_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -536,6 +544,7 @@ model LiteLLM_DailyTeamSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -547,12 +556,13 @@ model LiteLLM_DailyTeamSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([team_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -566,6 +576,7 @@ model LiteLLM_DailyTagSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -577,12 +588,13 @@ model LiteLLM_DailyTagSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([tag]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } diff --git a/litellm/types/proxy/management_endpoints/common_daily_activity.py b/litellm/types/proxy/management_endpoints/common_daily_activity.py index 08ffc5d097..948401fb8b 100644 --- a/litellm/types/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/types/proxy/management_endpoints/common_daily_activity.py @@ -68,6 +68,9 @@ class BreakdownMetrics(BaseModel): providers: Dict[str, MetricWithMetadata] = Field( default_factory=dict ) # provider -> {metrics, metadata} + endpoints: Dict[str, MetricWithMetadata] = Field( + default_factory=dict + ) # endpoint -> {metrics, metadata} api_keys: Dict[str, KeyMetricWithMetadata] = Field( default_factory=dict ) # api_key -> {metrics, metadata} diff --git a/pyproject.toml b/pyproject.toml index a3ee4afa4b..91a82f2fcb 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.18", optional = true} +litellm-proxy-extras = {version = "0.4.19", optional = true} rich = {version = "13.7.1", optional = true} litellm-enterprise = {version = "0.1.27", optional = true} diskcache = {version = "^5.6.1", optional = true} diff --git a/requirements.txt b/requirements.txt index fc2a1e764d..23fa43433a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,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.18 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.19 # for proxy extras - e.g. prisma migrations llm-sandbox==0.3.31 # for skill execution in sandbox ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.1 # for env diff --git a/schema.prisma b/schema.prisma index e565135bbc..c9de8d9c29 100644 --- a/schema.prisma +++ b/schema.prisma @@ -422,6 +422,7 @@ model LiteLLM_DailyUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -433,12 +434,13 @@ model LiteLLM_DailyUserSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily organization spend metrics per model and key @@ -451,6 +453,7 @@ model LiteLLM_DailyOrganizationSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -462,12 +465,13 @@ model LiteLLM_DailyOrganizationSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([organization_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily end user (customer) spend metrics per model and key @@ -480,6 +484,7 @@ model LiteLLM_DailyEndUserSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -490,12 +495,13 @@ model LiteLLM_DailyEndUserSpend { 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]) + @@unique([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([end_user_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily agent spend metrics per model and key @@ -508,6 +514,7 @@ model LiteLLM_DailyAgentSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -518,12 +525,13 @@ model LiteLLM_DailyAgentSpend { failed_requests BigInt @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([agent_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -536,6 +544,7 @@ model LiteLLM_DailyTeamSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -547,12 +556,13 @@ model LiteLLM_DailyTeamSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([team_id]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } // Track daily team spend metrics per model and key @@ -566,6 +576,7 @@ model LiteLLM_DailyTagSpend { model_group String? custom_llm_provider String? mcp_namespaced_tool_name String? + endpoint String? prompt_tokens BigInt @default(0) completion_tokens BigInt @default(0) cache_read_input_tokens BigInt @default(0) @@ -577,12 +588,13 @@ model LiteLLM_DailyTagSpend { created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name]) + @@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint]) @@index([date]) @@index([tag]) @@index([api_key]) @@index([model]) @@index([mcp_namespaced_tool_name]) + @@index([endpoint]) } 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 e9d2313ece..72403b0ba7 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 @@ -107,7 +107,7 @@ async def test_update_daily_spend_with_null_entity_id(): entity_type="user", entity_id_field="user_id", table_name="litellm_dailyuserspend", - unique_constraint_name="user_id_date_api_key_model_custom_llm_provider", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) # Verify that table.upsert was called @@ -115,12 +115,14 @@ async def test_update_daily_spend_with_null_entity_id(): # Verify the where clause contains null entity_id call_args = mock_table.upsert.call_args[1] - where_clause = call_args["where"]["user_id_date_api_key_model_custom_llm_provider"] + where_clause = call_args["where"]["user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint"] assert where_clause["user_id"] is None assert where_clause["date"] == "2024-01-01" assert where_clause["api_key"] == "test-api-key" assert where_clause["model"] == "gpt-4" assert where_clause["custom_llm_provider"] == "openai" + assert where_clause["mcp_namespaced_tool_name"] == "" + assert where_clause["endpoint"] == "" # Verify the create data contains null entity_id create_data = call_args["data"]["create"] @@ -129,6 +131,8 @@ async def test_update_daily_spend_with_null_entity_id(): assert create_data["api_key"] == "test-api-key" assert create_data["model"] == "gpt-4" assert create_data["custom_llm_provider"] == "openai" + assert create_data["mcp_namespaced_tool_name"] == "" + assert create_data["endpoint"] is None assert create_data["prompt_tokens"] == 10 assert create_data["completion_tokens"] == 20 assert create_data["spend"] == 0.1 @@ -171,13 +175,14 @@ async def test_update_daily_spend_sorting(): } upsert_calls.append(call( where={ - "user_id_date_api_key_model_custom_llm_provider": { + "user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint": { "user_id": f"user{i+11}", # user11 ... user60, sorted order "date": "2024-01-01", "api_key": "test-api-key", "model": "gpt-4", "custom_llm_provider": "openai", "mcp_namespaced_tool_name": "", + "endpoint": "", } }, data={ @@ -189,6 +194,7 @@ async def test_update_daily_spend_sorting(): "model_group": None, "mcp_namespaced_tool_name": "", "custom_llm_provider": "openai", + "endpoint": None, "prompt_tokens": 10, "completion_tokens": 20, "spend": 0.1, @@ -203,6 +209,7 @@ async def test_update_daily_spend_sorting(): "api_requests": {"increment": 1}, "successful_requests": {"increment": 1}, "failed_requests": {"increment": 0}, + "endpoint": "", }, }, )) @@ -216,7 +223,7 @@ async def test_update_daily_spend_sorting(): entity_type="user", entity_id_field="user_id", table_name="litellm_dailyuserspend", - unique_constraint_name="user_id_date_api_key_model_custom_llm_provider", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) # Verify that table.upsert was called @@ -372,7 +379,7 @@ async def test_update_daily_spend_with_none_values_in_sorting_fields(): entity_type="user", entity_id_field="user_id", table_name="litellm_dailyuserspend", - unique_constraint_name="user_id_date_api_key_model_custom_llm_provider", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", ) # Verify that table.upsert was called (should be called 5 times, once for each transaction) @@ -588,7 +595,7 @@ async def test_add_spend_log_transaction_to_daily_org_transaction_injects_org_id 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 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" @@ -665,7 +672,7 @@ async def test_add_spend_log_transaction_to_daily_end_user_transaction_injects_e 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 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" @@ -741,7 +748,7 @@ async def test_add_spend_log_transaction_to_daily_agent_transaction_injects_agen update_dict = call_args["update"] assert len(update_dict) == 1 for key, transaction in update_dict.items(): - assert key == f"{agent_id}_2024-01-01_test-key_gpt-4_openai" + assert key == f"{agent_id}_2024-01-01_test-key_gpt-4_openai_" assert transaction["agent_id"] == agent_id assert transaction["date"] == "2024-01-01" assert transaction["api_key"] == "test-key" @@ -780,4 +787,55 @@ async def test_add_spend_log_transaction_to_daily_agent_transaction_skips_when_a prisma_client=mock_prisma, ) - writer.daily_agent_spend_update_queue.add_update.assert_not_called() \ No newline at end of file + writer.daily_agent_spend_update_queue.add_update.assert_not_called() + + +@pytest.mark.asyncio +async def test_endpoint_field_is_correctly_mapped_from_call_type(): + """ + Test that the endpoint field is correctly mapped from call_type using ROUTE_ENDPOINT_MAPPING. + Verifies that when call_type is provided, the endpoint is set in the transaction and included in the key. + """ + writer = DBSpendUpdateWriter() + mock_prisma = MagicMock() + mock_prisma.get_request_status = MagicMock(return_value="success") + + payload = { + "request_id": "req-endpoint-test", + "user": "test-user", + "call_type": "acompletion", # Maps to "/chat/completions" + "startTime": "2024-01-01T12:00:00", + "api_key": "test-key", + "model": "gpt-4", + "custom_llm_provider": "openai", + "model_group": "gpt-4-group", + "prompt_tokens": 100, + "completion_tokens": 50, + "spend": 0.15, + "metadata": '{"usage_object": {}}', + } + + writer.daily_spend_update_queue.add_update = AsyncMock() + + await writer.add_spend_log_transaction_to_daily_user_transaction( + payload=payload, + prisma_client=mock_prisma, + ) + + writer.daily_spend_update_queue.add_update.assert_called_once() + + call_args = writer.daily_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(): + # Verify endpoint is included in the key + assert key == f"test-user_2024-01-01_test-key_gpt-4_openai_/chat/completions" + + # Verify endpoint is set in the transaction + assert transaction["endpoint"] == "/chat/completions" + assert transaction["user_id"] == "test-user" + assert transaction["date"] == "2024-01-01" + assert transaction["api_key"] == "test-key" + assert transaction["model"] == "gpt-4" + assert transaction["custom_llm_provider"] == "openai" \ No newline at end of file diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index bbdc4b1edf..93457631d2 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -12,6 +12,7 @@ from litellm.proxy.management_endpoints.common_daily_activity import ( _is_user_agent_tag, compute_tag_metadata_totals, get_daily_activity, + get_daily_activity_aggregated, ) @@ -124,3 +125,86 @@ def test_compute_tag_metadata_totals(): result = compute_tag_metadata_totals([]) assert result.spend == 0.0 assert result.prompt_tokens == 0 + + +@pytest.mark.asyncio +async def test_get_daily_activity_aggregated_with_endpoint_breakdown(): + """Test that endpoint breakdown is included in aggregated daily activity.""" + # Mock PrismaClient + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + + # Create mock records with endpoint fields + class MockRecord: + def __init__(self, date, endpoint, api_key, model, spend, prompt_tokens, completion_tokens): + self.date = date + self.endpoint = endpoint + self.api_key = api_key + self.model = model + self.model_group = None + self.custom_llm_provider = "openai" + self.mcp_namespaced_tool_name = None + self.spend = spend + self.prompt_tokens = prompt_tokens + self.completion_tokens = completion_tokens + self.total_tokens = prompt_tokens + completion_tokens + self.cache_read_input_tokens = 0 + self.cache_creation_input_tokens = 0 + self.api_requests = 1 + self.successful_requests = 1 + self.failed_requests = 0 + + mock_records = [ + MockRecord("2024-01-01", "/v1/chat/completions", "key-1", "gpt-4", 10.0, 100, 50), + MockRecord("2024-01-01", "/v1/chat/completions", "key-1", "gpt-4", 5.0, 50, 25), + MockRecord("2024-01-01", "/v1/embeddings", "key-2", "text-embedding-ada-002", 3.0, 30, 0), + ] + + # Mock the table methods + mock_table = MagicMock() + mock_table.find_many = AsyncMock(return_value=mock_records) + mock_prisma.db.litellm_dailyuserspend = mock_table + mock_prisma.db.litellm_verificationtoken = MagicMock() + mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + + # Call the function + result = await get_daily_activity_aggregated( + prisma_client=mock_prisma, + table_name="litellm_dailyuserspend", + entity_id_field="user_id", + entity_id=None, + entity_metadata_field=None, + start_date="2024-01-01", + end_date="2024-01-01", + model=None, + api_key=None, + ) + + # Verify the results + assert len(result.results) == 1 + daily_data = result.results[0] + assert daily_data.date.strftime("%Y-%m-%d") == "2024-01-01" + + # Verify endpoint breakdown exists + assert "endpoints" in daily_data.breakdown.model_fields + assert len(daily_data.breakdown.endpoints) == 2 + + # Verify /v1/chat/completions endpoint breakdown + assert "/v1/chat/completions" in daily_data.breakdown.endpoints + chat_endpoint = daily_data.breakdown.endpoints["/v1/chat/completions"] + assert chat_endpoint.metrics.spend == 15.0 # 10.0 + 5.0 + assert chat_endpoint.metrics.prompt_tokens == 150 # 100 + 50 + assert chat_endpoint.metrics.completion_tokens == 75 # 50 + 25 + + # Verify /v1/embeddings endpoint breakdown + assert "/v1/embeddings" in daily_data.breakdown.endpoints + embeddings_endpoint = daily_data.breakdown.endpoints["/v1/embeddings"] + assert embeddings_endpoint.metrics.spend == 3.0 + assert embeddings_endpoint.metrics.prompt_tokens == 30 + assert embeddings_endpoint.metrics.completion_tokens == 0 + + # Verify API key breakdowns within endpoints + assert "key-1" in chat_endpoint.api_key_breakdown + assert chat_endpoint.api_key_breakdown["key-1"].metrics.spend == 15.0 + assert "key-2" in embeddings_endpoint.api_key_breakdown + assert embeddings_endpoint.api_key_breakdown["key-2"].metrics.spend == 3.0