build(ui/): Allow empty values in daily agg table + reintroduce 'unassigned' teams in spend tracking
This commit is contained in:
parent
066a502b89
commit
0017d5f1db
@ -0,0 +1,9 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiteLLM_DailyTagSpend" ALTER COLUMN "tag" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiteLLM_DailyTeamSpend" ALTER COLUMN "team_id" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiteLLM_DailyUserSpend" ALTER COLUMN "user_id" DROP NOT NULL;
|
||||
|
||||
@ -356,7 +356,7 @@ model LiteLLM_AuditLog {
|
||||
// Track daily user spend metrics per model and key
|
||||
model LiteLLM_DailyUserSpend {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
user_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -383,7 +383,7 @@ model LiteLLM_DailyUserSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTeamSpend {
|
||||
id String @id @default(uuid())
|
||||
team_id String
|
||||
team_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -410,7 +410,7 @@ model LiteLLM_DailyTeamSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTagSpend {
|
||||
id String @id @default(uuid())
|
||||
tag String
|
||||
tag String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
|
||||
@ -444,7 +444,7 @@ class DBSpendUpdateWriter:
|
||||
proxy_logging_obj=proxy_logging_obj,
|
||||
daily_spend_transactions=daily_team_spend_update_transactions,
|
||||
)
|
||||
|
||||
|
||||
daily_tag_spend_update_transactions = (
|
||||
await self.redis_update_buffer.get_all_daily_tag_spend_update_transactions_from_redis_buffer()
|
||||
)
|
||||
@ -859,8 +859,6 @@ class DBSpendUpdateWriter:
|
||||
async with prisma_client.db.batch_() as batcher:
|
||||
for _, transaction in transactions_to_process.items():
|
||||
entity_id = transaction.get(entity_id_field)
|
||||
if not entity_id:
|
||||
continue
|
||||
|
||||
# Construct the where clause dynamically
|
||||
where_clause = {
|
||||
|
||||
@ -86,17 +86,19 @@ def update_breakdown_metrics(
|
||||
# Update entity-specific metrics if entity_id_field is provided
|
||||
if entity_id_field:
|
||||
entity_value = getattr(record, entity_id_field, None)
|
||||
if entity_value:
|
||||
if entity_value not in breakdown.entities:
|
||||
breakdown.entities[entity_value] = MetricWithMetadata(
|
||||
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
|
||||
entity_value = (
|
||||
entity_value if entity_value else "Unassigned"
|
||||
) # allow for null entity_id_field
|
||||
if entity_value not in breakdown.entities:
|
||||
breakdown.entities[entity_value] = MetricWithMetadata(
|
||||
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
|
||||
)
|
||||
|
||||
return breakdown
|
||||
|
||||
|
||||
@ -356,7 +356,7 @@ model LiteLLM_AuditLog {
|
||||
// Track daily user spend metrics per model and key
|
||||
model LiteLLM_DailyUserSpend {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
user_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -383,7 +383,7 @@ model LiteLLM_DailyUserSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTeamSpend {
|
||||
id String @id @default(uuid())
|
||||
team_id String
|
||||
team_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -410,7 +410,7 @@ model LiteLLM_DailyTeamSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTagSpend {
|
||||
id String @id @default(uuid())
|
||||
tag String
|
||||
tag String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
|
||||
@ -204,7 +204,7 @@ model LiteLLM_VerificationToken {
|
||||
model_spend Json @default("{}")
|
||||
model_max_budget Json @default("{}")
|
||||
budget_id String?
|
||||
organization_id String? // deprecated param, as user can be part of multiple organizations. Check LiteLLM_OrganizationMembership instead.
|
||||
organization_id String?
|
||||
object_permission_id String?
|
||||
created_at DateTime? @default(now()) @map("created_at")
|
||||
created_by String?
|
||||
@ -356,7 +356,7 @@ model LiteLLM_AuditLog {
|
||||
// Track daily user spend metrics per model and key
|
||||
model LiteLLM_DailyUserSpend {
|
||||
id String @id @default(uuid())
|
||||
user_id String
|
||||
user_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -383,7 +383,7 @@ model LiteLLM_DailyUserSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTeamSpend {
|
||||
id String @id @default(uuid())
|
||||
team_id String
|
||||
team_id String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -410,7 +410,7 @@ model LiteLLM_DailyTeamSpend {
|
||||
// Track daily team spend metrics per model and key
|
||||
model LiteLLM_DailyTagSpend {
|
||||
id String @id @default(uuid())
|
||||
tag String
|
||||
tag String?
|
||||
date String
|
||||
api_key String
|
||||
model String
|
||||
@ -463,9 +463,9 @@ model LiteLLM_ManagedFileTable {
|
||||
@@index([unified_file_id])
|
||||
}
|
||||
|
||||
model LiteLLM_ManagedObjectTable { // for batches or finetuning jobs which use managed files
|
||||
model LiteLLM_ManagedObjectTable { // for batches or finetuning jobs which use the
|
||||
id String @id @default(uuid())
|
||||
unified_object_id String @unique // The base64 encoded unified object ID
|
||||
unified_object_id String @unique // The base64 encoded unified file ID
|
||||
model_object_id String @unique // the id returned by the backend API provider
|
||||
file_object Json // Stores the OpenAIFileObject
|
||||
file_purpose String // either 'batch' or 'fine-tune'
|
||||
|
||||
@ -65,3 +65,73 @@ async def test_daily_spend_tracking_with_disabled_spend_logs():
|
||||
assert call_args["payload"]["spend"] == 0.1
|
||||
assert call_args["payload"]["model"] == "gpt-4"
|
||||
assert call_args["payload"]["custom_llm_provider"] == "openai"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_daily_spend_with_null_entity_id():
|
||||
"""
|
||||
Test that table.upsert is called even when entity_id is null
|
||||
|
||||
Ensures 'global view' has all daily spend transactions
|
||||
"""
|
||||
# Setup
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_batcher = MagicMock()
|
||||
mock_table = MagicMock()
|
||||
mock_prisma_client.db.batch_.return_value.__aenter__.return_value = mock_batcher
|
||||
mock_batcher.litellm_dailyuserspend = mock_table
|
||||
|
||||
# Create a transaction with null entity_id
|
||||
daily_spend_transactions = {
|
||||
"test_key": {
|
||||
"user_id": None, # null entity_id
|
||||
"date": "2024-01-01",
|
||||
"api_key": "test-api-key",
|
||||
"model": "gpt-4",
|
||||
"custom_llm_provider": "openai",
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 20,
|
||||
"spend": 0.1,
|
||||
"api_requests": 1,
|
||||
"successful_requests": 1,
|
||||
"failed_requests": 0,
|
||||
}
|
||||
}
|
||||
|
||||
# Call the method
|
||||
await DBSpendUpdateWriter._update_daily_spend(
|
||||
n_retry_times=1,
|
||||
prisma_client=mock_prisma_client,
|
||||
proxy_logging_obj=MagicMock(),
|
||||
daily_spend_transactions=daily_spend_transactions,
|
||||
entity_type="user",
|
||||
entity_id_field="user_id",
|
||||
table_name="litellm_dailyuserspend",
|
||||
unique_constraint_name="user_id_date_api_key_model_custom_llm_provider",
|
||||
)
|
||||
|
||||
# Verify that table.upsert was called
|
||||
mock_table.upsert.assert_called_once()
|
||||
|
||||
# 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"]
|
||||
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"
|
||||
|
||||
# Verify the create data contains null entity_id
|
||||
create_data = call_args["data"]["create"]
|
||||
assert create_data["user_id"] is None
|
||||
assert create_data["date"] == "2024-01-01"
|
||||
assert create_data["api_key"] == "test-api-key"
|
||||
assert create_data["model"] == "gpt-4"
|
||||
assert create_data["custom_llm_provider"] == "openai"
|
||||
assert create_data["prompt_tokens"] == 10
|
||||
assert create_data["completion_tokens"] == 20
|
||||
assert create_data["spend"] == 0.1
|
||||
assert create_data["api_requests"] == 1
|
||||
assert create_data["successful_requests"] == 1
|
||||
assert create_data["failed_requests"] == 0
|
||||
|
||||
@ -255,9 +255,11 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%" }} className="p-8">
|
||||
{all_admin_roles.includes(userRole || "") ?
|
||||
<Text className="text-sm text-gray-500 mb-4">
|
||||
This is the new usage dashboard. <br/> You may see empty data, as these use <a href="https://github.com/BerriAI/litellm/blob/6de348125208dd4be81ff0e5813753df2fbe9735/schema.prisma#L320" className="text-blue-500 hover:text-blue-700 ml-1">new aggregate tables</a> to allow UI to work at 1M+ spend logs. To access the old dashboard, go to Experimental {'>'} Old Usage.
|
||||
Note: If you see key/model-level inconsistencies between Global View and Team Usage, it's because the Global View was missing spend when user_id = null, prior to v1.71.2. <a href="https://github.com/BerriAI/litellm/issues/10876" className="text-blue-500 hover:text-blue-700 ml-1">Learn more here</a>.
|
||||
</Text>
|
||||
: null}
|
||||
<TabGroup>
|
||||
<TabList variant="solid" className="mt-1">
|
||||
{all_admin_roles.includes(userRole || "") ? <Tab>Global Usage</Tab> : <Tab>Your Usage</Tab>}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user