mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-25 10:44:24 +00:00
Add new /tag/daily/activity
endpoint + Add tag dashboard to UI (#10073)
* feat: initial commit adding daily tag spend table to db * feat(db_spend_update_writer.py): correctly log tag spend transactions * build(schema.prisma): add new tag table to root * build: add new migration file * feat(common_daily_activity.py): add `/tag/daily/activity` API endpoint allows viewing daily spend by tag * feat(tag_management_endpoints.py): support comma separated list of tags + tag breakdown metric allows querying multiple tags + knowing what tags are driving spend * feat(entity_usage.tsx): initial commit adding tag based usage to litellm dashboard brings back tag based usage tracking to UI at 1m+ spend logs * feat(entity_usage.tsx): add top api key view to ui * feat(entity_usage.tsx): add tag table to ui * feat(entity_usage.tsx): allow filtering by tag * refactor(entity_usage.tsx): reorder components * build(ui/): fix linting error * fix: fix ruff checks * fix(schema.prisma): drop uniqueness requirement on tag allows dailytagspend to have multiple rows with the same tag * build(schema.prisma): drop uniqueness requirement on tag in dailytagspend allows tag agg. view to work on multiple rows with same tag * build(schema.prisma): drop tag uniqueness requirement
This commit is contained in:
parent
3985c808b8
commit
8a17398ea1
11 changed files with 1205 additions and 340 deletions
|
@ -14,9 +14,8 @@ These are members of a Team on LiteLLM
|
|||
import asyncio
|
||||
import traceback
|
||||
import uuid
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, TypedDict, Union, cast
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
|
||||
import fastapi
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
|
||||
|
@ -33,6 +32,17 @@ from litellm.proxy.management_endpoints.key_management_endpoints import (
|
|||
from litellm.proxy.management_helpers.audit_logs import create_audit_log_for_update
|
||||
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
|
||||
from litellm.proxy.utils import handle_exception_on_proxy
|
||||
from litellm.types.proxy.management_endpoints.common_daily_activity import (
|
||||
BreakdownMetrics,
|
||||
DailySpendData,
|
||||
DailySpendMetadata,
|
||||
KeyMetadata,
|
||||
KeyMetricWithMetadata,
|
||||
LiteLLM_DailyUserSpend,
|
||||
MetricWithMetadata,
|
||||
SpendAnalyticsPaginatedResponse,
|
||||
SpendMetrics,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -82,9 +92,9 @@ def _update_internal_new_user_params(data_json: dict, data: NewUserRequest) -> d
|
|||
data_json["user_id"] = str(uuid.uuid4())
|
||||
auto_create_key = data_json.pop("auto_create_key", True)
|
||||
if auto_create_key is False:
|
||||
data_json["table_name"] = (
|
||||
"user" # only create a user, don't create key if 'auto_create_key' set to False
|
||||
)
|
||||
data_json[
|
||||
"table_name"
|
||||
] = "user" # only create a user, don't create key if 'auto_create_key' set to False
|
||||
|
||||
is_internal_user = False
|
||||
if data.user_role and data.user_role.is_internal_user_role:
|
||||
|
@ -651,9 +661,9 @@ def _update_internal_user_params(data_json: dict, data: UpdateUserRequest) -> di
|
|||
"budget_duration" not in non_default_values
|
||||
): # applies internal user limits, if user role updated
|
||||
if is_internal_user and litellm.internal_user_budget_duration is not None:
|
||||
non_default_values["budget_duration"] = (
|
||||
litellm.internal_user_budget_duration
|
||||
)
|
||||
non_default_values[
|
||||
"budget_duration"
|
||||
] = litellm.internal_user_budget_duration
|
||||
duration_s = duration_in_seconds(
|
||||
duration=non_default_values["budget_duration"]
|
||||
)
|
||||
|
@ -964,13 +974,13 @@ async def get_users(
|
|||
"in": user_id_list, # Now passing a list of strings as required by Prisma
|
||||
}
|
||||
|
||||
users: Optional[List[LiteLLM_UserTable]] = (
|
||||
await prisma_client.db.litellm_usertable.find_many(
|
||||
where=where_conditions,
|
||||
skip=skip,
|
||||
take=page_size,
|
||||
order={"created_at": "desc"},
|
||||
)
|
||||
users: Optional[
|
||||
List[LiteLLM_UserTable]
|
||||
] = await prisma_client.db.litellm_usertable.find_many(
|
||||
where=where_conditions,
|
||||
skip=skip,
|
||||
take=page_size,
|
||||
order={"created_at": "desc"},
|
||||
)
|
||||
|
||||
# Get total count of user rows
|
||||
|
@ -1225,13 +1235,13 @@ async def ui_view_users(
|
|||
}
|
||||
|
||||
# Query users with pagination and filters
|
||||
users: Optional[List[BaseModel]] = (
|
||||
await prisma_client.db.litellm_usertable.find_many(
|
||||
where=where_conditions,
|
||||
skip=skip,
|
||||
take=page_size,
|
||||
order={"created_at": "desc"},
|
||||
)
|
||||
users: Optional[
|
||||
List[BaseModel]
|
||||
] = await prisma_client.db.litellm_usertable.find_many(
|
||||
where=where_conditions,
|
||||
skip=skip,
|
||||
take=page_size,
|
||||
order={"created_at": "desc"},
|
||||
)
|
||||
|
||||
if not users:
|
||||
|
@ -1244,111 +1254,6 @@ async def ui_view_users(
|
|||
raise HTTPException(status_code=500, detail=f"Error searching users: {str(e)}")
|
||||
|
||||
|
||||
class GroupByDimension(str, Enum):
|
||||
DATE = "date"
|
||||
MODEL = "model"
|
||||
API_KEY = "api_key"
|
||||
TEAM = "team"
|
||||
ORGANIZATION = "organization"
|
||||
MODEL_GROUP = "model_group"
|
||||
PROVIDER = "custom_llm_provider"
|
||||
|
||||
|
||||
class SpendMetrics(BaseModel):
|
||||
spend: float = Field(default=0.0)
|
||||
prompt_tokens: int = Field(default=0)
|
||||
completion_tokens: int = Field(default=0)
|
||||
cache_read_input_tokens: int = Field(default=0)
|
||||
cache_creation_input_tokens: int = Field(default=0)
|
||||
total_tokens: int = Field(default=0)
|
||||
successful_requests: int = Field(default=0)
|
||||
failed_requests: int = Field(default=0)
|
||||
api_requests: int = Field(default=0)
|
||||
|
||||
|
||||
class MetricBase(BaseModel):
|
||||
metrics: SpendMetrics
|
||||
|
||||
|
||||
class MetricWithMetadata(MetricBase):
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class KeyMetadata(BaseModel):
|
||||
"""Metadata for a key"""
|
||||
|
||||
key_alias: Optional[str] = None
|
||||
|
||||
|
||||
class KeyMetricWithMetadata(MetricBase):
|
||||
"""Base class for metrics with additional metadata"""
|
||||
|
||||
metadata: KeyMetadata = Field(default_factory=KeyMetadata)
|
||||
|
||||
|
||||
class BreakdownMetrics(BaseModel):
|
||||
"""Breakdown of spend by different dimensions"""
|
||||
|
||||
models: Dict[str, MetricWithMetadata] = Field(
|
||||
default_factory=dict
|
||||
) # model -> {metrics, metadata}
|
||||
providers: Dict[str, MetricWithMetadata] = Field(
|
||||
default_factory=dict
|
||||
) # provider -> {metrics, metadata}
|
||||
api_keys: Dict[str, KeyMetricWithMetadata] = Field(
|
||||
default_factory=dict
|
||||
) # api_key -> {metrics, metadata}
|
||||
|
||||
|
||||
class DailySpendData(BaseModel):
|
||||
date: date
|
||||
metrics: SpendMetrics
|
||||
breakdown: BreakdownMetrics = Field(default_factory=BreakdownMetrics)
|
||||
|
||||
|
||||
class DailySpendMetadata(BaseModel):
|
||||
total_spend: float = Field(default=0.0)
|
||||
total_prompt_tokens: int = Field(default=0)
|
||||
total_completion_tokens: int = Field(default=0)
|
||||
total_tokens: int = Field(default=0)
|
||||
total_api_requests: int = Field(default=0)
|
||||
total_successful_requests: int = Field(default=0)
|
||||
total_failed_requests: int = Field(default=0)
|
||||
total_cache_read_input_tokens: int = Field(default=0)
|
||||
total_cache_creation_input_tokens: int = Field(default=0)
|
||||
page: int = Field(default=1)
|
||||
total_pages: int = Field(default=1)
|
||||
has_more: bool = Field(default=False)
|
||||
|
||||
|
||||
class SpendAnalyticsPaginatedResponse(BaseModel):
|
||||
results: List[DailySpendData]
|
||||
metadata: DailySpendMetadata = Field(default_factory=DailySpendMetadata)
|
||||
|
||||
|
||||
class LiteLLM_DailyUserSpend(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
date: str
|
||||
api_key: str
|
||||
model: str
|
||||
model_group: Optional[str] = None
|
||||
custom_llm_provider: Optional[str] = None
|
||||
prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
cache_read_input_tokens: int = 0
|
||||
cache_creation_input_tokens: int = 0
|
||||
spend: float = 0.0
|
||||
api_requests: int = 0
|
||||
successful_requests: int = 0
|
||||
failed_requests: int = 0
|
||||
|
||||
|
||||
class GroupedData(TypedDict):
|
||||
metrics: SpendMetrics
|
||||
breakdown: BreakdownMetrics
|
||||
|
||||
|
||||
def update_metrics(
|
||||
group_metrics: SpendMetrics, record: LiteLLM_DailyUserSpend
|
||||
) -> SpendMetrics:
|
||||
|
@ -1494,9 +1399,9 @@ async def get_user_daily_activity(
|
|||
user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN
|
||||
and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY
|
||||
):
|
||||
where_conditions["user_id"] = (
|
||||
user_api_key_dict.user_id
|
||||
) # only allow access to own data
|
||||
where_conditions[
|
||||
"user_id"
|
||||
] = user_api_key_dict.user_id # only allow access to own data
|
||||
|
||||
# Get total count for pagination
|
||||
total_count = await prisma_client.db.litellm_dailyuserspend.count(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue