diff --git a/litellm/models/performance_alert.py b/litellm/models/performance_alert.py index c07397b0ae1..1773aa460dc 100644 --- a/litellm/models/performance_alert.py +++ b/litellm/models/performance_alert.py @@ -17,8 +17,6 @@ SPEND_MULTIPLIER: Final = 3 SPEND_MIN_INCREASE_USD: Final = 50 TRAFFIC_SPIKE_MULTIPLIER: Final = 10 TRAFFIC_SPIKE_MIN_INCREASE: Final = 1000 -TRAFFIC_DROP_RATIO: Final = 0.2 -TRAFFIC_DROP_MIN_BASELINE: Final = 500 LATENCY_MULTIPLIER: Final = 3 LATENCY_MIN_INCREASE_MS: Final = 500 LATENCY_MIN_TIMED_REQUESTS: Final = 100 @@ -45,7 +43,6 @@ class ResourceWindow(BaseModel): base_timed: int = Field(ge=0, le=MAX_ALERT_ID) active_days: int = Field(ge=0, le=BASELINE_DAYS) timed_days: int = Field(ge=0, le=BASELINE_DAYS) - exists: bool class SweepState(BaseModel): @@ -187,20 +184,14 @@ def _spend_spike(window: ResourceWindow) -> tuple[float, float] | None: return (window.spend, baseline) if triggered else None -def _traffic_change(window: ResourceWindow) -> tuple[float, float] | None: +def _traffic_spike(window: ResourceWindow) -> tuple[float, float] | None: baseline: Final = window.base_requests / BASELINE_DAYS spike: Final = ( window.active_days >= MIN_ACTIVE_DAYS and window.requests >= TRAFFIC_SPIKE_MULTIPLIER * baseline and window.requests - baseline >= TRAFFIC_SPIKE_MIN_INCREASE ) - drop: Final = ( - window.exists - and window.active_days == BASELINE_DAYS - and baseline >= TRAFFIC_DROP_MIN_BASELINE - and window.requests < TRAFFIC_DROP_RATIO * baseline - ) - return (window.requests, baseline) if spike or drop else None + return (window.requests, baseline) if spike else None def _slower_responses(window: ResourceWindow) -> tuple[float, float] | None: @@ -231,7 +222,7 @@ def _more_failures(window: ResourceWindow) -> tuple[float, float] | None: def detect(scope: PerformanceScope, window: ResourceWindow, day: date) -> tuple[AlertDetection, ...]: checks: Final[tuple[tuple[AlertType, tuple[float, float] | None, int], ...]] = ( ("spend", _spend_spike(window), window.requests), - ("traffic", _traffic_change(window), window.requests), + ("traffic", _traffic_spike(window), window.requests), ("latency", _slower_responses(window), window.timed), ("reliability", _more_failures(window), window.requests), ) diff --git a/litellm/repositories/performance_alert_repository.py b/litellm/repositories/performance_alert_repository.py index 5902a02dc0a..6d55670e0b8 100644 --- a/litellm/repositories/performance_alert_repository.py +++ b/litellm/repositories/performance_alert_repository.py @@ -80,33 +80,18 @@ class _Source: resource_id: LiteralString requests: LiteralString filter: LiteralString - exists: LiteralString _SOURCES: Final[Mapping[PerformanceScope, _Source]] = { - "gateway": _Source('"LiteLLM_DailyModelUsage"', "'gateway'", "request_count", "", "true"), - "model": _Source('"LiteLLM_DailyModelUsage"', "model_group", "request_count", "", "true"), - "team": _Source( - '"LiteLLM_DailyTeamSpend"', - "team_id", - "api_requests", - "", - 'EXISTS (SELECT 1 FROM "LiteLLM_TeamTable" t WHERE t.team_id = page.resource_id AND NOT t.blocked)', - ), - "user": _Source( - '"LiteLLM_DailyUserSpend"', - "user_id", - "api_requests", - "", - 'EXISTS (SELECT 1 FROM "LiteLLM_UserTable" u WHERE u.user_id = page.resource_id)', - ), + "gateway": _Source('"LiteLLM_DailyModelUsage"', "'gateway'", "request_count", ""), + "model": _Source('"LiteLLM_DailyModelUsage"', "model_group", "request_count", ""), + "team": _Source('"LiteLLM_DailyTeamSpend"', "team_id", "api_requests", ""), + "user": _Source('"LiteLLM_DailyUserSpend"', "user_id", "api_requests", ""), "key": _Source( '"LiteLLM_DailyUserSpend"', "api_key", "api_requests", " AND api_key ~ '^[0-9a-f]{64}$'", - 'EXISTS (SELECT 1 FROM "LiteLLM_VerificationToken" k WHERE k.token = page.resource_id' - " AND k.blocked IS NOT TRUE AND (k.expires IS NULL OR k.expires > now()))", ), } @@ -127,8 +112,7 @@ def _page_sql(scope: PerformanceScope, wrapped: bool) -> LiteralString: if scope == "gateway" else f" GROUP BY {source.resource_id} ORDER BY {source.resource_id}" ) - return f"""WITH page AS ( - SELECT {source.resource_id} AS resource_id, + return f"""SELECT {source.resource_id} AS resource_id, COALESCE(SUM(spend) FILTER (WHERE date = $1), 0)::float8 AS spend, COALESCE(SUM(spend) FILTER (WHERE date < $1), 0)::float8 AS base_spend, COALESCE(SUM({source.requests}) FILTER (WHERE date = $1), 0)::bigint AS requests, @@ -143,9 +127,7 @@ def _page_sql(scope: PerformanceScope, wrapped: bool) -> LiteralString: COUNT(DISTINCT date) FILTER (WHERE date < $1 AND timed_requests > 0) AS timed_days FROM {source.table} WHERE date BETWEEN $2 AND $1{where}{group} - LIMIT $3::int -) -SELECT page.*, {source.exists} AS exists FROM page ORDER BY resource_id""" + LIMIT $3::int""" def _next_id_sql(scope: PerformanceScope, wrapped: bool) -> LiteralString: diff --git a/tests/integration/database/test_performance_alerts.py b/tests/integration/database/test_performance_alerts.py index 64a18e09fda..aea5bfa03a5 100644 --- a/tests/integration/database/test_performance_alerts.py +++ b/tests/integration/database/test_performance_alerts.py @@ -157,6 +157,7 @@ async def test_a_full_sweep_saves_one_alert_per_real_change_and_nothing_for_norm write_rows('INSERT INTO "LiteLLM_UserTable" (user_id) VALUES (%s)', (f"{p}-user-stopped",)) _seed("user", f"{p}-user-stopped", STEADY, None) _seed("user", f"{p}-user-deleted", STEADY, None) + _seed("user", f"{p}-user-busy", STEADY, Day(requests=20_000, timed=20_000, response_ms=2_000_000)) _seed("key", fixture.key("slow"), STEADY, Day(response_ms=1_000_000)) _seed("model", f"{p}-model", STEADY, Day(failed=100)) @@ -170,7 +171,7 @@ async def test_a_full_sweep_saves_one_alert_per_real_change_and_nothing_for_norm assert int(str(age["seconds"])) < 300 assert fixture.saved() == { (f"{p}-team-spike", "spend"): ("team", "open"), - (f"{p}-user-stopped", "traffic"): ("user", "open"), + (f"{p}-user-busy", "traffic"): ("user", "open"), (fixture.key("slow"), "latency"): ("key", "open"), (f"{p}-model", "reliability"): ("model", "open"), } diff --git a/tests/unit/models/test_performance_alert.py b/tests/unit/models/test_performance_alert.py index 84b40d37c1c..3d76b2bed79 100644 --- a/tests/unit/models/test_performance_alert.py +++ b/tests/unit/models/test_performance_alert.py @@ -5,6 +5,8 @@ import pytest from litellm.models.alert import AlertDetection, MetricEvidence from litellm.models.performance_alert import ( + SCOPES, + PerformanceScope, ResourceWindow, SweepState, advance, @@ -29,7 +31,6 @@ STEADY: Final = { "base_timed": 7000, "active_days": 7, "timed_days": 7, - "exists": True, } @@ -45,6 +46,19 @@ def test_steady_usage_raises_nothing() -> None: assert _types() == () +@pytest.mark.parametrize("scope", SCOPES) +@pytest.mark.parametrize("requests", (0, 100)) +def test_quiet_or_idle_resources_do_not_raise_traffic_alerts(scope: PerformanceScope, requests: int) -> None: + window: Final = _window( + resource_id="a" * 64 if scope == "key" else "resource-a", + requests=requests, + failed=0, + response_ms=requests * 100, + timed=requests, + ) + assert detect(scope, window, DAY) == () + + @pytest.mark.parametrize( ("changes", "expected"), ( @@ -57,11 +71,6 @@ def test_steady_usage_raises_nothing() -> None: ({"requests": 1100, "failed": 0, "response_ms": 110_000, "timed": 1100, "base_requests": 700}, ("traffic",)), ({"requests": 1099, "failed": 0, "response_ms": 109_900, "timed": 1099, "base_requests": 700}, ()), ({"requests": 50_000, "failed": 0, "active_days": 3}, ()), - ({"requests": 99, "failed": 0, "timed": 99, "response_ms": 9900, "base_requests": 3500}, ("traffic",)), - ({"requests": 100, "failed": 0, "timed": 100, "response_ms": 10_000, "base_requests": 3500}, ()), - ({"requests": 0, "failed": 0, "timed": 0, "response_ms": 0, "base_requests": 3500, "active_days": 6}, ()), - ({"requests": 0, "failed": 0, "timed": 0, "response_ms": 0, "base_requests": 3500, "exists": False}, ()), - ({"requests": 0, "failed": 0, "timed": 0, "response_ms": 0, "base_requests": 3493}, ()), ({"response_ms": 600_000}, ("latency",)), ({"response_ms": 599_000}, ()), ({"response_ms": 60_000_000, "timed": 99}, ()), diff --git a/tests/unit/repositories/test_performance_alert_repository.py b/tests/unit/repositories/test_performance_alert_repository.py index 6210ff5c9d1..f3f818a215e 100644 --- a/tests/unit/repositories/test_performance_alert_repository.py +++ b/tests/unit/repositories/test_performance_alert_repository.py @@ -112,7 +112,6 @@ def spend_window() -> Mapping[str, object]: "base_timed": 0, "active_days": 7, "timed_days": 0, - "exists": True, }