Skip to content

Commit d8a1071

Browse files
Add aggregate spend by tag (#10071)
* 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
1 parent 47e811d commit d8a1071

File tree

11 files changed

+518
-326
lines changed

11 files changed

+518
-326
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- AlterTable
2+
ALTER TABLE "LiteLLM_DailyTeamSpend" ADD COLUMN "cache_creation_input_tokens" INTEGER NOT NULL DEFAULT 0,
3+
ADD COLUMN "cache_read_input_tokens" INTEGER NOT NULL DEFAULT 0;
4+
5+
-- CreateTable
6+
CREATE TABLE "LiteLLM_DailyTagSpend" (
7+
"id" TEXT NOT NULL,
8+
"tag" TEXT NOT NULL,
9+
"date" TEXT NOT NULL,
10+
"api_key" TEXT NOT NULL,
11+
"model" TEXT NOT NULL,
12+
"model_group" TEXT,
13+
"custom_llm_provider" TEXT,
14+
"prompt_tokens" INTEGER NOT NULL DEFAULT 0,
15+
"completion_tokens" INTEGER NOT NULL DEFAULT 0,
16+
"cache_read_input_tokens" INTEGER NOT NULL DEFAULT 0,
17+
"cache_creation_input_tokens" INTEGER NOT NULL DEFAULT 0,
18+
"spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0,
19+
"api_requests" INTEGER NOT NULL DEFAULT 0,
20+
"successful_requests" INTEGER NOT NULL DEFAULT 0,
21+
"failed_requests" INTEGER NOT NULL DEFAULT 0,
22+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23+
"updated_at" TIMESTAMP(3) NOT NULL,
24+
25+
CONSTRAINT "LiteLLM_DailyTagSpend_pkey" PRIMARY KEY ("id")
26+
);
27+
28+
-- CreateIndex
29+
CREATE UNIQUE INDEX "LiteLLM_DailyTagSpend_tag_key" ON "LiteLLM_DailyTagSpend"("tag");
30+
31+
-- CreateIndex
32+
CREATE INDEX "LiteLLM_DailyTagSpend_date_idx" ON "LiteLLM_DailyTagSpend"("date");
33+
34+
-- CreateIndex
35+
CREATE INDEX "LiteLLM_DailyTagSpend_tag_idx" ON "LiteLLM_DailyTagSpend"("tag");
36+
37+
-- CreateIndex
38+
CREATE INDEX "LiteLLM_DailyTagSpend_api_key_idx" ON "LiteLLM_DailyTagSpend"("api_key");
39+
40+
-- CreateIndex
41+
CREATE INDEX "LiteLLM_DailyTagSpend_model_idx" ON "LiteLLM_DailyTagSpend"("model");
42+
43+
-- CreateIndex
44+
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");
45+

litellm-proxy-extras/litellm_proxy_extras/schema.prisma

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,60 @@ model LiteLLM_DailyUserSpend {
342342
@@index([model])
343343
}
344344

345+
// Track daily team spend metrics per model and key
346+
model LiteLLM_DailyTeamSpend {
347+
id String @id @default(uuid())
348+
team_id String
349+
date String
350+
api_key String
351+
model String
352+
model_group String?
353+
custom_llm_provider String?
354+
prompt_tokens Int @default(0)
355+
completion_tokens Int @default(0)
356+
cache_read_input_tokens Int @default(0)
357+
cache_creation_input_tokens Int @default(0)
358+
spend Float @default(0.0)
359+
api_requests Int @default(0)
360+
successful_requests Int @default(0)
361+
failed_requests Int @default(0)
362+
created_at DateTime @default(now())
363+
updated_at DateTime @updatedAt
364+
365+
@@unique([team_id, date, api_key, model, custom_llm_provider])
366+
@@index([date])
367+
@@index([team_id])
368+
@@index([api_key])
369+
@@index([model])
370+
}
371+
372+
// Track daily team spend metrics per model and key
373+
model LiteLLM_DailyTagSpend {
374+
id String @id @default(uuid())
375+
tag String @unique
376+
date String
377+
api_key String
378+
model String
379+
model_group String?
380+
custom_llm_provider String?
381+
prompt_tokens Int @default(0)
382+
completion_tokens Int @default(0)
383+
cache_read_input_tokens Int @default(0)
384+
cache_creation_input_tokens Int @default(0)
385+
spend Float @default(0.0)
386+
api_requests Int @default(0)
387+
successful_requests Int @default(0)
388+
failed_requests Int @default(0)
389+
created_at DateTime @default(now())
390+
updated_at DateTime @updatedAt
391+
392+
@@unique([tag, date, api_key, model, custom_llm_provider])
393+
@@index([date])
394+
@@index([tag])
395+
@@index([api_key])
396+
@@index([model])
397+
}
398+
345399

346400
// Track the status of cron jobs running. Only allow one pod to run the job at a time
347401
model LiteLLM_CronJob {

litellm/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
REDIS_UPDATE_BUFFER_KEY = "litellm_spend_update_buffer"
2929
REDIS_DAILY_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_spend_update_buffer"
3030
REDIS_DAILY_TEAM_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_team_spend_update_buffer"
31+
REDIS_DAILY_TAG_SPEND_UPDATE_BUFFER_KEY = "litellm_daily_tag_spend_update_buffer"
3132
MAX_REDIS_BUFFER_DEQUEUE_COUNT = 100
3233
MAX_SIZE_IN_MEMORY_QUEUE = 10000
3334
MAX_IN_MEMORY_QUEUE_FLUSH_COUNT = 1000

litellm/proxy/_experimental/out/onboarding.html

Lines changed: 0 additions & 1 deletion
This file was deleted.

litellm/proxy/_types.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -650,9 +650,9 @@ class GenerateRequestBase(LiteLLMPydanticObjectBase):
650650
allowed_cache_controls: Optional[list] = []
651651
config: Optional[dict] = {}
652652
permissions: Optional[dict] = {}
653-
model_max_budget: Optional[dict] = (
654-
{}
655-
) # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {}
653+
model_max_budget: Optional[
654+
dict
655+
] = {} # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {}
656656

657657
model_config = ConfigDict(protected_namespaces=())
658658
model_rpm_limit: Optional[dict] = None
@@ -908,12 +908,12 @@ class NewCustomerRequest(BudgetNewRequest):
908908
alias: Optional[str] = None # human-friendly alias
909909
blocked: bool = False # allow/disallow requests for this end-user
910910
budget_id: Optional[str] = None # give either a budget_id or max_budget
911-
allowed_model_region: Optional[AllowedModelRegion] = (
912-
None # require all user requests to use models in this specific region
913-
)
914-
default_model: Optional[str] = (
915-
None # if no equivalent model in allowed region - default all requests to this model
916-
)
911+
allowed_model_region: Optional[
912+
AllowedModelRegion
913+
] = None # require all user requests to use models in this specific region
914+
default_model: Optional[
915+
str
916+
] = None # if no equivalent model in allowed region - default all requests to this model
917917

918918
@model_validator(mode="before")
919919
@classmethod
@@ -935,12 +935,12 @@ class UpdateCustomerRequest(LiteLLMPydanticObjectBase):
935935
blocked: bool = False # allow/disallow requests for this end-user
936936
max_budget: Optional[float] = None
937937
budget_id: Optional[str] = None # give either a budget_id or max_budget
938-
allowed_model_region: Optional[AllowedModelRegion] = (
939-
None # require all user requests to use models in this specific region
940-
)
941-
default_model: Optional[str] = (
942-
None # if no equivalent model in allowed region - default all requests to this model
943-
)
938+
allowed_model_region: Optional[
939+
AllowedModelRegion
940+
] = None # require all user requests to use models in this specific region
941+
default_model: Optional[
942+
str
943+
] = None # if no equivalent model in allowed region - default all requests to this model
944944

945945

946946
class DeleteCustomerRequest(LiteLLMPydanticObjectBase):
@@ -1076,9 +1076,9 @@ class BlockKeyRequest(LiteLLMPydanticObjectBase):
10761076

10771077
class AddTeamCallback(LiteLLMPydanticObjectBase):
10781078
callback_name: str
1079-
callback_type: Optional[Literal["success", "failure", "success_and_failure"]] = (
1080-
"success_and_failure"
1081-
)
1079+
callback_type: Optional[
1080+
Literal["success", "failure", "success_and_failure"]
1081+
] = "success_and_failure"
10821082
callback_vars: Dict[str, str]
10831083

10841084
@model_validator(mode="before")
@@ -1335,9 +1335,9 @@ class ConfigList(LiteLLMPydanticObjectBase):
13351335
stored_in_db: Optional[bool]
13361336
field_default_value: Any
13371337
premium_field: bool = False
1338-
nested_fields: Optional[List[FieldDetail]] = (
1339-
None # For nested dictionary or Pydantic fields
1340-
)
1338+
nested_fields: Optional[
1339+
List[FieldDetail]
1340+
] = None # For nested dictionary or Pydantic fields
13411341

13421342

13431343
class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
@@ -1604,9 +1604,9 @@ class LiteLLM_OrganizationMembershipTable(LiteLLMPydanticObjectBase):
16041604
budget_id: Optional[str] = None
16051605
created_at: datetime
16061606
updated_at: datetime
1607-
user: Optional[Any] = (
1608-
None # You might want to replace 'Any' with a more specific type if available
1609-
)
1607+
user: Optional[
1608+
Any
1609+
] = None # You might want to replace 'Any' with a more specific type if available
16101610
litellm_budget_table: Optional[LiteLLM_BudgetTable] = None
16111611

16121612
model_config = ConfigDict(protected_namespaces=())
@@ -2354,9 +2354,9 @@ class TeamModelDeleteRequest(BaseModel):
23542354
# Organization Member Requests
23552355
class OrganizationMemberAddRequest(OrgMemberAddRequest):
23562356
organization_id: str
2357-
max_budget_in_organization: Optional[float] = (
2358-
None # Users max budget within the organization
2359-
)
2357+
max_budget_in_organization: Optional[
2358+
float
2359+
] = None # Users max budget within the organization
23602360

23612361

23622362
class OrganizationMemberDeleteRequest(MemberDeleteRequest):
@@ -2545,9 +2545,9 @@ class ProviderBudgetResponse(LiteLLMPydanticObjectBase):
25452545
Maps provider names to their budget configs.
25462546
"""
25472547

2548-
providers: Dict[str, ProviderBudgetResponseObject] = (
2549-
{}
2550-
) # Dictionary mapping provider names to their budget configurations
2548+
providers: Dict[
2549+
str, ProviderBudgetResponseObject
2550+
] = {} # Dictionary mapping provider names to their budget configurations
25512551

25522552

25532553
class ProxyStateVariables(TypedDict):
@@ -2675,9 +2675,9 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase):
26752675
enforce_rbac: bool = False
26762676
roles_jwt_field: Optional[str] = None # v2 on role mappings
26772677
role_mappings: Optional[List[RoleMapping]] = None
2678-
object_id_jwt_field: Optional[str] = (
2679-
None # can be either user / team, inferred from the role mapping
2680-
)
2678+
object_id_jwt_field: Optional[
2679+
str
2680+
] = None # can be either user / team, inferred from the role mapping
26812681
scope_mappings: Optional[List[ScopeMapping]] = None
26822682
enforce_scope_based_access: bool = False
26832683
enforce_team_based_model_access: bool = False
@@ -2799,6 +2799,10 @@ class DailyUserSpendTransaction(BaseDailySpendTransaction):
27992799
user_id: str
28002800

28012801

2802+
class DailyTagSpendTransaction(BaseDailySpendTransaction):
2803+
tag: str
2804+
2805+
28022806
class DBSpendUpdateTransactions(TypedDict):
28032807
"""
28042808
Internal Data Structure for buffering spend updates in Redis or in memory before committing them to the database

0 commit comments

Comments
 (0)