mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-25 02:34:29 +00:00
Litellm dev 04 18 2025 p2 (#10157)
* fix(proxy/_types.py): allow internal user to call api playground * fix(new_usage.tsx): cleanup tag based usage - only show for proxy admin not clear what tags internal user should be allowed to see * fix(team_endpoints.py): allow internal user view spend for teams they belong to * fix(team_endpoints.py): return team alias on `/team/daily/activity` API allows displaying team alias on ui * fix: fix linting error * fix(entity_usage.tsx): allow viewing top keys by team * fix(entity_usage.tsx): show alias, if available in breakdown allows entity alias to be easily displayed * Show usage by key (on all up, team, and tag usage dashboards) (#10152) * fix(entity_usage.tsx): allow user to select team in team usage tab * fix(new_usage.tsx): load all tags for filtering * fix(tag_management_endpoints.py): return dynamic tags from db on `/tag/list` * fix(litellm_pre_call_utils.py): support x-litellm-tags even if tag based routing not enabled * fix(new_usage.tsx): show breakdown of usage by api key on dashboard helpful when looking at spend by team * fix(networking.tsx): exclude litellm-dashboard team id's from calls adds noisy ui tokens to key activity * fix(new_usage.tsx): allow user to see activity by key on main tab * feat(internal_user_endpoints.py): refactor to use common_daily_activity function reuses same logic across teams/keys/tags Allows returning team_alias in api_keys consistently * fix(leftnav.tsx): swap old usage with new usage tab * fix(entity_usage.tsx): show breakdown of teams in daily spend chart * style(new_usage.tsx): show global usage tab if user is admin / has admin view * fix(new_usage.tsx): add disclaimer for new usage dashboard * fix(new_usage.tsx): fix linting error * Allow filtering usage dashboard by team + tag (#10150) * fix(entity_usage.tsx): allow user to select team in team usage tab * fix(new_usage.tsx): load all tags for filtering * fix(tag_management_endpoints.py): return dynamic tags from db on `/tag/list` * fix(litellm_pre_call_utils.py): support x-litellm-tags even if tag based routing not enabled * fix: fix linting error
This commit is contained in:
parent
b9756bf006
commit
ef6ac42658
16 changed files with 429 additions and 287 deletions
|
@ -287,6 +287,7 @@ class LiteLLMRoutes(enum.Enum):
|
|||
"/v1/models",
|
||||
# token counter
|
||||
"/utils/token_counter",
|
||||
"/utils/transform_request",
|
||||
# rerank
|
||||
"/rerank",
|
||||
"/v1/rerank",
|
||||
|
@ -462,6 +463,7 @@ class LiteLLMRoutes(enum.Enum):
|
|||
"/team/member_delete",
|
||||
"/team/permissions_list",
|
||||
"/team/permissions_update",
|
||||
"/team/daily/activity",
|
||||
"/model/new",
|
||||
"/model/update",
|
||||
"/model/delete",
|
||||
|
@ -650,9 +652,9 @@ class GenerateRequestBase(LiteLLMPydanticObjectBase):
|
|||
allowed_cache_controls: Optional[list] = []
|
||||
config: Optional[dict] = {}
|
||||
permissions: Optional[dict] = {}
|
||||
model_max_budget: Optional[dict] = (
|
||||
{}
|
||||
) # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {}
|
||||
model_max_budget: Optional[
|
||||
dict
|
||||
] = {} # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {}
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
model_rpm_limit: Optional[dict] = None
|
||||
|
@ -911,12 +913,12 @@ class NewCustomerRequest(BudgetNewRequest):
|
|||
alias: Optional[str] = None # human-friendly alias
|
||||
blocked: bool = False # allow/disallow requests for this end-user
|
||||
budget_id: Optional[str] = None # give either a budget_id or max_budget
|
||||
allowed_model_region: Optional[AllowedModelRegion] = (
|
||||
None # require all user requests to use models in this specific region
|
||||
)
|
||||
default_model: Optional[str] = (
|
||||
None # if no equivalent model in allowed region - default all requests to this model
|
||||
)
|
||||
allowed_model_region: Optional[
|
||||
AllowedModelRegion
|
||||
] = None # require all user requests to use models in this specific region
|
||||
default_model: Optional[
|
||||
str
|
||||
] = None # if no equivalent model in allowed region - default all requests to this model
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
@ -938,12 +940,12 @@ class UpdateCustomerRequest(LiteLLMPydanticObjectBase):
|
|||
blocked: bool = False # allow/disallow requests for this end-user
|
||||
max_budget: Optional[float] = None
|
||||
budget_id: Optional[str] = None # give either a budget_id or max_budget
|
||||
allowed_model_region: Optional[AllowedModelRegion] = (
|
||||
None # require all user requests to use models in this specific region
|
||||
)
|
||||
default_model: Optional[str] = (
|
||||
None # if no equivalent model in allowed region - default all requests to this model
|
||||
)
|
||||
allowed_model_region: Optional[
|
||||
AllowedModelRegion
|
||||
] = None # require all user requests to use models in this specific region
|
||||
default_model: Optional[
|
||||
str
|
||||
] = None # if no equivalent model in allowed region - default all requests to this model
|
||||
|
||||
|
||||
class DeleteCustomerRequest(LiteLLMPydanticObjectBase):
|
||||
|
@ -1079,9 +1081,9 @@ class BlockKeyRequest(LiteLLMPydanticObjectBase):
|
|||
|
||||
class AddTeamCallback(LiteLLMPydanticObjectBase):
|
||||
callback_name: str
|
||||
callback_type: Optional[Literal["success", "failure", "success_and_failure"]] = (
|
||||
"success_and_failure"
|
||||
)
|
||||
callback_type: Optional[
|
||||
Literal["success", "failure", "success_and_failure"]
|
||||
] = "success_and_failure"
|
||||
callback_vars: Dict[str, str]
|
||||
|
||||
@model_validator(mode="before")
|
||||
|
@ -1339,9 +1341,9 @@ class ConfigList(LiteLLMPydanticObjectBase):
|
|||
stored_in_db: Optional[bool]
|
||||
field_default_value: Any
|
||||
premium_field: bool = False
|
||||
nested_fields: Optional[List[FieldDetail]] = (
|
||||
None # For nested dictionary or Pydantic fields
|
||||
)
|
||||
nested_fields: Optional[
|
||||
List[FieldDetail]
|
||||
] = None # For nested dictionary or Pydantic fields
|
||||
|
||||
|
||||
class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
|
||||
|
@ -1609,9 +1611,9 @@ class LiteLLM_OrganizationMembershipTable(LiteLLMPydanticObjectBase):
|
|||
budget_id: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
user: Optional[Any] = (
|
||||
None # You might want to replace 'Any' with a more specific type if available
|
||||
)
|
||||
user: Optional[
|
||||
Any
|
||||
] = None # You might want to replace 'Any' with a more specific type if available
|
||||
litellm_budget_table: Optional[LiteLLM_BudgetTable] = None
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
@ -2359,9 +2361,9 @@ class TeamModelDeleteRequest(BaseModel):
|
|||
# Organization Member Requests
|
||||
class OrganizationMemberAddRequest(OrgMemberAddRequest):
|
||||
organization_id: str
|
||||
max_budget_in_organization: Optional[float] = (
|
||||
None # Users max budget within the organization
|
||||
)
|
||||
max_budget_in_organization: Optional[
|
||||
float
|
||||
] = None # Users max budget within the organization
|
||||
|
||||
|
||||
class OrganizationMemberDeleteRequest(MemberDeleteRequest):
|
||||
|
@ -2550,9 +2552,9 @@ class ProviderBudgetResponse(LiteLLMPydanticObjectBase):
|
|||
Maps provider names to their budget configs.
|
||||
"""
|
||||
|
||||
providers: Dict[str, ProviderBudgetResponseObject] = (
|
||||
{}
|
||||
) # Dictionary mapping provider names to their budget configurations
|
||||
providers: Dict[
|
||||
str, ProviderBudgetResponseObject
|
||||
] = {} # Dictionary mapping provider names to their budget configurations
|
||||
|
||||
|
||||
class ProxyStateVariables(TypedDict):
|
||||
|
@ -2680,9 +2682,9 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase):
|
|||
enforce_rbac: bool = False
|
||||
roles_jwt_field: Optional[str] = None # v2 on role mappings
|
||||
role_mappings: Optional[List[RoleMapping]] = None
|
||||
object_id_jwt_field: Optional[str] = (
|
||||
None # can be either user / team, inferred from the role mapping
|
||||
)
|
||||
object_id_jwt_field: Optional[
|
||||
str
|
||||
] = None # can be either user / team, inferred from the role mapping
|
||||
scope_mappings: Optional[List[ScopeMapping]] = None
|
||||
enforce_scope_based_access: bool = False
|
||||
enforce_team_based_model_access: bool = False
|
||||
|
|
|
@ -433,14 +433,13 @@ class LiteLLMProxyRequestSetup:
|
|||
) -> Optional[List[str]]:
|
||||
tags = None
|
||||
|
||||
if llm_router and llm_router.enable_tag_filtering is True:
|
||||
# Check request headers for tags
|
||||
if "x-litellm-tags" in headers:
|
||||
if isinstance(headers["x-litellm-tags"], str):
|
||||
_tags = headers["x-litellm-tags"].split(",")
|
||||
tags = [tag.strip() for tag in _tags]
|
||||
elif isinstance(headers["x-litellm-tags"], list):
|
||||
tags = headers["x-litellm-tags"]
|
||||
# Check request headers for tags
|
||||
if "x-litellm-tags" in headers:
|
||||
if isinstance(headers["x-litellm-tags"], str):
|
||||
_tags = headers["x-litellm-tags"].split(",")
|
||||
tags = [tag.strip() for tag in _tags]
|
||||
elif isinstance(headers["x-litellm-tags"], list):
|
||||
tags = headers["x-litellm-tags"]
|
||||
# Check request body for tags
|
||||
if "tags" in data and isinstance(data["tags"], list):
|
||||
tags = data["tags"]
|
||||
|
|
|
@ -39,6 +39,7 @@ def update_breakdown_metrics(
|
|||
provider_metadata: Dict[str, Dict[str, Any]],
|
||||
api_key_metadata: Dict[str, Dict[str, Any]],
|
||||
entity_id_field: Optional[str] = None,
|
||||
entity_metadata_field: Optional[Dict[str, dict]] = None,
|
||||
) -> BreakdownMetrics:
|
||||
"""Updates breakdown metrics for a single record using the existing update_metrics function"""
|
||||
|
||||
|
@ -74,7 +75,8 @@ def update_breakdown_metrics(
|
|||
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),
|
||||
), # Add any api_key-specific metadata here
|
||||
)
|
||||
breakdown.api_keys[record.api_key].metrics = update_metrics(
|
||||
|
@ -87,7 +89,10 @@ def update_breakdown_metrics(
|
|||
if entity_value:
|
||||
if entity_value not in breakdown.entities:
|
||||
breakdown.entities[entity_value] = MetricWithMetadata(
|
||||
metrics=SpendMetrics(), metadata={}
|
||||
metrics=SpendMetrics(),
|
||||
metadata=entity_metadata_field.get(entity_value, {})
|
||||
if entity_metadata_field
|
||||
else {},
|
||||
)
|
||||
breakdown.entities[entity_value].metrics = update_metrics(
|
||||
breakdown.entities[entity_value].metrics, record
|
||||
|
@ -101,12 +106,14 @@ async def get_daily_activity(
|
|||
table_name: str,
|
||||
entity_id_field: str,
|
||||
entity_id: Optional[Union[str, List[str]]],
|
||||
entity_metadata_field: Optional[Dict[str, dict]],
|
||||
start_date: Optional[str],
|
||||
end_date: Optional[str],
|
||||
model: Optional[str],
|
||||
api_key: Optional[str],
|
||||
page: int,
|
||||
page_size: int,
|
||||
exclude_entity_ids: Optional[List[str]] = None,
|
||||
) -> SpendAnalyticsPaginatedResponse:
|
||||
"""Common function to get daily activity for any entity type."""
|
||||
if prisma_client is None:
|
||||
|
@ -139,6 +146,10 @@ async def get_daily_activity(
|
|||
where_conditions[entity_id_field] = {"in": entity_id}
|
||||
else:
|
||||
where_conditions[entity_id_field] = entity_id
|
||||
if exclude_entity_ids:
|
||||
where_conditions.setdefault(entity_id_field, {})["not"] = {
|
||||
"in": exclude_entity_ids
|
||||
}
|
||||
|
||||
# Get total count for pagination
|
||||
total_count = await getattr(prisma_client.db, table_name).count(
|
||||
|
@ -170,7 +181,10 @@ async def get_daily_activity(
|
|||
where={"token": {"in": list(api_keys)}}
|
||||
)
|
||||
api_key_metadata.update(
|
||||
{k.token: {"key_alias": k.key_alias} for k in key_records}
|
||||
{
|
||||
k.token: {"key_alias": k.key_alias, "team_id": k.team_id}
|
||||
for k in key_records
|
||||
}
|
||||
)
|
||||
|
||||
# Process results
|
||||
|
@ -198,6 +212,7 @@ async def get_daily_activity(
|
|||
provider_metadata,
|
||||
api_key_metadata,
|
||||
entity_id_field=entity_id_field,
|
||||
entity_metadata_field=entity_metadata_field,
|
||||
)
|
||||
|
||||
# Update total metrics
|
||||
|
|
|
@ -4,11 +4,19 @@ from litellm.proxy._types import (
|
|||
GenerateKeyRequest,
|
||||
LiteLLM_ManagementEndpoint_MetadataFields_Premium,
|
||||
LiteLLM_TeamTable,
|
||||
LitellmUserRoles,
|
||||
UserAPIKeyAuth,
|
||||
)
|
||||
from litellm.proxy.utils import _premium_user_check
|
||||
|
||||
|
||||
def _user_has_admin_view(user_api_key_dict: UserAPIKeyAuth) -> bool:
|
||||
return (
|
||||
user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN
|
||||
or user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY
|
||||
)
|
||||
|
||||
|
||||
def _is_user_team_admin(
|
||||
user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable
|
||||
) -> bool:
|
||||
|
|
|
@ -25,6 +25,8 @@ from litellm._logging import verbose_proxy_logger
|
|||
from litellm.litellm_core_utils.duration_parser import duration_in_seconds
|
||||
from litellm.proxy._types import *
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.management_endpoints.common_daily_activity import get_daily_activity
|
||||
from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
generate_key_helper_fn,
|
||||
prepare_metadata_fields,
|
||||
|
@ -1382,136 +1384,22 @@ async def get_user_daily_activity(
|
|||
)
|
||||
|
||||
try:
|
||||
# Build filter conditions
|
||||
where_conditions: Dict[str, Any] = {
|
||||
"date": {
|
||||
"gte": start_date,
|
||||
"lte": end_date,
|
||||
}
|
||||
}
|
||||
entity_id: Optional[str] = None
|
||||
if not _user_has_admin_view(user_api_key_dict):
|
||||
entity_id = user_api_key_dict.user_id
|
||||
|
||||
if model:
|
||||
where_conditions["model"] = model
|
||||
if api_key:
|
||||
where_conditions["api_key"] = api_key
|
||||
|
||||
if (
|
||||
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
|
||||
|
||||
# Get total count for pagination
|
||||
total_count = await prisma_client.db.litellm_dailyuserspend.count(
|
||||
where=where_conditions
|
||||
)
|
||||
|
||||
# Fetch paginated results
|
||||
daily_spend_data = await prisma_client.db.litellm_dailyuserspend.find_many(
|
||||
where=where_conditions,
|
||||
order=[
|
||||
{"date": "desc"},
|
||||
],
|
||||
skip=(page - 1) * page_size,
|
||||
take=page_size,
|
||||
)
|
||||
|
||||
daily_spend_data_pydantic_list = [
|
||||
LiteLLM_DailyUserSpend(**record.model_dump()) for record in daily_spend_data
|
||||
]
|
||||
|
||||
# Get all unique API keys from the spend data
|
||||
api_keys = set()
|
||||
for record in daily_spend_data_pydantic_list:
|
||||
if record.api_key:
|
||||
api_keys.add(record.api_key)
|
||||
|
||||
# Fetch key aliases in bulk
|
||||
|
||||
api_key_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
model_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
provider_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
if api_keys:
|
||||
key_records = await prisma_client.db.litellm_verificationtoken.find_many(
|
||||
where={"token": {"in": list(api_keys)}}
|
||||
)
|
||||
api_key_metadata.update(
|
||||
{k.token: {"key_alias": k.key_alias} for k in key_records}
|
||||
)
|
||||
# Process results
|
||||
results = []
|
||||
total_metrics = SpendMetrics()
|
||||
|
||||
# Group data by date and other dimensions
|
||||
|
||||
grouped_data: Dict[str, Dict[str, Any]] = {}
|
||||
for record in daily_spend_data_pydantic_list:
|
||||
date_str = record.date
|
||||
if date_str not in grouped_data:
|
||||
grouped_data[date_str] = {
|
||||
"metrics": SpendMetrics(),
|
||||
"breakdown": BreakdownMetrics(),
|
||||
}
|
||||
|
||||
# Update metrics
|
||||
grouped_data[date_str]["metrics"] = update_metrics(
|
||||
grouped_data[date_str]["metrics"], record
|
||||
)
|
||||
# Update breakdowns
|
||||
grouped_data[date_str]["breakdown"] = update_breakdown_metrics(
|
||||
grouped_data[date_str]["breakdown"],
|
||||
record,
|
||||
model_metadata,
|
||||
provider_metadata,
|
||||
api_key_metadata,
|
||||
)
|
||||
|
||||
# Update total metrics
|
||||
total_metrics.spend += record.spend
|
||||
total_metrics.prompt_tokens += record.prompt_tokens
|
||||
total_metrics.completion_tokens += record.completion_tokens
|
||||
total_metrics.total_tokens += (
|
||||
record.prompt_tokens + record.completion_tokens
|
||||
)
|
||||
total_metrics.cache_read_input_tokens += record.cache_read_input_tokens
|
||||
total_metrics.cache_creation_input_tokens += (
|
||||
record.cache_creation_input_tokens
|
||||
)
|
||||
total_metrics.api_requests += record.api_requests
|
||||
total_metrics.successful_requests += record.successful_requests
|
||||
total_metrics.failed_requests += record.failed_requests
|
||||
|
||||
# Convert grouped data to response format
|
||||
for date_str, data in grouped_data.items():
|
||||
results.append(
|
||||
DailySpendData(
|
||||
date=datetime.strptime(date_str, "%Y-%m-%d").date(),
|
||||
metrics=data["metrics"],
|
||||
breakdown=data["breakdown"],
|
||||
)
|
||||
)
|
||||
|
||||
# Sort results by date
|
||||
results.sort(key=lambda x: x.date, reverse=True)
|
||||
|
||||
return SpendAnalyticsPaginatedResponse(
|
||||
results=results,
|
||||
metadata=DailySpendMetadata(
|
||||
total_spend=total_metrics.spend,
|
||||
total_prompt_tokens=total_metrics.prompt_tokens,
|
||||
total_completion_tokens=total_metrics.completion_tokens,
|
||||
total_tokens=total_metrics.total_tokens,
|
||||
total_api_requests=total_metrics.api_requests,
|
||||
total_successful_requests=total_metrics.successful_requests,
|
||||
total_failed_requests=total_metrics.failed_requests,
|
||||
total_cache_read_input_tokens=total_metrics.cache_read_input_tokens,
|
||||
total_cache_creation_input_tokens=total_metrics.cache_creation_input_tokens,
|
||||
page=page,
|
||||
total_pages=-(-total_count // page_size), # Ceiling division
|
||||
has_more=(page * page_size) < total_count,
|
||||
),
|
||||
return await get_daily_activity(
|
||||
prisma_client=prisma_client,
|
||||
table_name="litellm_dailyuserspend",
|
||||
entity_id_field="user_id",
|
||||
entity_id=entity_id,
|
||||
entity_metadata_field=None,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
@ -12,7 +12,7 @@ All /tag management endpoints
|
|||
|
||||
import datetime
|
||||
import json
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
|
@ -25,6 +25,7 @@ from litellm.proxy.management_endpoints.common_daily_activity import (
|
|||
get_daily_activity,
|
||||
)
|
||||
from litellm.types.tag_management import (
|
||||
LiteLLM_DailyTagSpendTable,
|
||||
TagConfig,
|
||||
TagDeleteRequest,
|
||||
TagInfoRequest,
|
||||
|
@ -301,6 +302,7 @@ async def info_tag(
|
|||
"/tag/list",
|
||||
tags=["tag management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_model=List[TagConfig],
|
||||
)
|
||||
async def list_tags(
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
|
@ -314,9 +316,33 @@ async def list_tags(
|
|||
raise HTTPException(status_code=500, detail="Database not connected")
|
||||
|
||||
try:
|
||||
## QUERY STORED TAGS ##
|
||||
tags_config = await _get_tags_config(prisma_client)
|
||||
list_of_tags = list(tags_config.values())
|
||||
return list_of_tags
|
||||
|
||||
## QUERY DYNAMIC TAGS ##
|
||||
dynamic_tags = await prisma_client.db.litellm_dailytagspend.find_many(
|
||||
distinct=["tag"],
|
||||
)
|
||||
|
||||
dynamic_tags_list = [
|
||||
LiteLLM_DailyTagSpendTable(**dynamic_tag.model_dump())
|
||||
for dynamic_tag in dynamic_tags
|
||||
]
|
||||
|
||||
dynamic_tag_config = [
|
||||
TagConfig(
|
||||
name=tag.tag,
|
||||
description="This is just a spend tag that was passed dynamically in a request. It does not control any LLM models.",
|
||||
models=None,
|
||||
created_at=tag.created_at.isoformat(),
|
||||
updated_at=tag.updated_at.isoformat(),
|
||||
)
|
||||
for tag in dynamic_tags_list
|
||||
if tag.tag not in tags_config
|
||||
]
|
||||
|
||||
return list_of_tags + dynamic_tag_config
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
@ -400,6 +426,7 @@ async def get_tag_daily_activity(
|
|||
table_name="litellm_dailytagspend",
|
||||
entity_id_field="tag",
|
||||
entity_id=tag_list,
|
||||
entity_metadata_field=None,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
|
|
|
@ -56,11 +56,13 @@ from litellm.proxy._types import (
|
|||
from litellm.proxy.auth.auth_checks import (
|
||||
allowed_route_check_inside_route,
|
||||
get_team_object,
|
||||
get_user_object,
|
||||
)
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.management_endpoints.common_utils import (
|
||||
_is_user_team_admin,
|
||||
_set_object_metadata_field,
|
||||
_user_has_admin_view,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.tag_management_endpoints import (
|
||||
get_daily_activity,
|
||||
|
@ -2091,7 +2093,6 @@ async def update_team_member_permissions(
|
|||
"/team/daily/activity",
|
||||
response_model=SpendAnalyticsPaginatedResponse,
|
||||
tags=["team management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
)
|
||||
async def get_team_daily_activity(
|
||||
team_ids: Optional[str] = None,
|
||||
|
@ -2101,6 +2102,8 @@ async def get_team_daily_activity(
|
|||
api_key: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
exclude_team_ids: Optional[str] = None,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
Get daily activity for specific teams or all teams.
|
||||
|
@ -2113,20 +2116,80 @@ async def get_team_daily_activity(
|
|||
api_key (Optional[str]): Filter by API key.
|
||||
page (int): Page number for pagination.
|
||||
page_size (int): Number of items per page.
|
||||
|
||||
exclude_team_ids (Optional[str]): Comma-separated list of team IDs to exclude.
|
||||
Returns:
|
||||
SpendAnalyticsPaginatedResponse: Paginated response containing daily activity data.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
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},
|
||||
)
|
||||
|
||||
# Convert comma-separated tags string to list if provided
|
||||
team_ids_list = team_ids.split(",") if team_ids else None
|
||||
exclude_team_ids_list: Optional[List[str]] = None
|
||||
|
||||
if exclude_team_ids:
|
||||
exclude_team_ids_list = (
|
||||
exclude_team_ids.split(",") if exclude_team_ids else None
|
||||
)
|
||||
|
||||
if not _user_has_admin_view(user_api_key_dict):
|
||||
user_info = await get_user_object(
|
||||
user_id=user_api_key_dict.user_id,
|
||||
prisma_client=prisma_client,
|
||||
user_id_upsert=False,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
parent_otel_span=user_api_key_dict.parent_otel_span,
|
||||
proxy_logging_obj=proxy_logging_obj,
|
||||
)
|
||||
if user_info is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={
|
||||
"error": "User= {} not found".format(user_api_key_dict.user_id)
|
||||
},
|
||||
)
|
||||
if team_ids_list is None:
|
||||
team_ids_list = user_info.teams
|
||||
else:
|
||||
# check if all team_ids are in user_info.teams
|
||||
for team_id in team_ids_list:
|
||||
if team_id not in user_info.teams:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={
|
||||
"error": "User does not belong to Team= {}. Call `/user/info` to see user's teams".format(
|
||||
team_id
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
## Fetch team aliases
|
||||
where_condition = {}
|
||||
if team_ids_list:
|
||||
where_condition["team_id"] = {"in": list(team_ids_list)}
|
||||
team_aliases = await prisma_client.db.litellm_teamtable.find_many(
|
||||
where=where_condition
|
||||
)
|
||||
team_alias_metadata = {
|
||||
t.team_id: {"team_alias": t.team_alias} for t in team_aliases
|
||||
}
|
||||
|
||||
return await get_daily_activity(
|
||||
prisma_client=prisma_client,
|
||||
table_name="litellm_dailyteamspend",
|
||||
entity_id_field="team_id",
|
||||
entity_id=team_ids_list,
|
||||
entity_metadata_field=team_alias_metadata,
|
||||
exclude_entity_ids=exclude_team_ids_list,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
|
|
|
@ -39,6 +39,7 @@ class KeyMetadata(BaseModel):
|
|||
"""Metadata for a key"""
|
||||
|
||||
key_alias: Optional[str] = None
|
||||
team_id: Optional[str] = None
|
||||
|
||||
|
||||
class KeyMetricWithMetadata(MetricBase):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
@ -30,3 +31,23 @@ class TagDeleteRequest(BaseModel):
|
|||
|
||||
class TagInfoRequest(BaseModel):
|
||||
names: List[str]
|
||||
|
||||
|
||||
class LiteLLM_DailyTagSpendTable(BaseModel):
|
||||
id: str
|
||||
tag: str
|
||||
date: str
|
||||
api_key: str
|
||||
model: str
|
||||
model_group: Optional[str]
|
||||
custom_llm_provider: Optional[str]
|
||||
prompt_tokens: int
|
||||
completion_tokens: int
|
||||
cache_read_input_tokens: int
|
||||
cache_creation_input_tokens: int
|
||||
spend: float
|
||||
api_requests: int
|
||||
successful_requests: int
|
||||
failed_requests: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue