diff --git a/litellm/repositories/provider_latency.py b/litellm/repositories/provider_latency.py index ebfc52e9668..037eb7053cd 100644 --- a/litellm/repositories/provider_latency.py +++ b/litellm/repositories/provider_latency.py @@ -47,17 +47,8 @@ class _Recovery(BaseModel, frozen=True): recovered: bool -_PAIRS: Final = TypeAdapter(tuple[IssueModelPair, ...]) _COHORTS: Final = TypeAdapter(tuple[_CohortRow, ...]) _MEMBERS: Final = TypeAdapter(tuple[_Member, ...]) -_CANDIDATES: Final = """ -SELECT DISTINCT custom_llm_provider AS provider, model FROM "LiteLLM_DailyUserSpend" -WHERE date=$1 AND timed_requests>0 AND custom_llm_provider<>'' AND model<>'' - AND CASE WHEN octet_length(custom_llm_provider)<=1024 THEN length(custom_llm_provider)<=256 ELSE false END - AND CASE WHEN octet_length(model)<=4096 THEN length(model)<=1024 ELSE false END - AND ($2::text IS NULL OR (custom_llm_provider,model)>($2,$3)) -ORDER BY provider,model LIMIT $4 -""" def _source(scope: UsageAlertScope) -> tuple[str, str]: @@ -211,21 +202,6 @@ class ProviderLatencyRepository: ) ) - async def candidate_pairs( - self, day: date, cursor: IssueModelPair | None = None, limit: int = 100 - ) -> tuple[IssueModelPair, ...]: - if not 1 <= limit <= 100: - raise ValueError("Pair pages contain 1 to 100 candidates") - return _PAIRS.validate_python( - await self._database.query_raw( - _CANDIDATES, - day.isoformat(), - cursor.provider if cursor else None, - cursor.model if cursor else None, - limit, - ) - ) - async def latency_cohort( self, pairs: tuple[IssueModelPair, ...], day: date, rule: UsageAlertRuleInput ) -> ProviderLatencyEvidence | None: diff --git a/litellm/repositories/usage_alert_metrics.py b/litellm/repositories/usage_alert_metrics.py index 2f766df702d..3fac2ea7b54 100644 --- a/litellm/repositories/usage_alert_metrics.py +++ b/litellm/repositories/usage_alert_metrics.py @@ -1,7 +1,7 @@ """Bounded reads of the daily rollups used by the Usage dashboard.""" import json -from collections.abc import AsyncIterator, Mapping, Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass, replace from datetime import date, timedelta from typing import Final, Literal, Protocol, TypeAlias @@ -404,24 +404,3 @@ class UsageAlertMetricsRepository: raw[-1].entity_id if raw else cursor, entity_ids is not None or scope == "deployment" or len({row.entity_id for row in raw}) < _BATCH_SIZE, ) - - async def batches( - self, scope: Scope, target: date, baseline_days: int - ) -> AsyncIterator[tuple[DailyAlertMetrics, ...]]: - from litellm.proxy.db.db_span import db_span - from litellm.proxy.db.prisma_query_span import sql_relation - - start: Final = (target - timedelta(days=baseline_days)).isoformat() - query: Final = _query(scope) - cursor: str | None = None # rebind-ok: keyset pagination retains only the current page - while True: - async with db_span("daily_activity_query", sql_relation(query)): - rows: tuple[DailyAlertMetrics, ...] = _ROWS.validate_python( - await self._database.query_raw(query, start, target.isoformat(), cursor, _BATCH_SIZE) - ) - if not rows: - return - yield tuple(_reporting_id(scope, row) for row in rows) - cursor = rows[-1].entity_id - if scope == "deployment": - return diff --git a/tests/integration/database/test_provider_latency.py b/tests/integration/database/test_provider_latency.py index ec4986b7c29..05cd897c529 100644 --- a/tests/integration/database/test_provider_latency.py +++ b/tests/integration/database/test_provider_latency.py @@ -10,7 +10,7 @@ from litellm.types.proxy.usage_alerts import UsageAlertRuleInput @pytest.mark.asyncio(loop_scope="session") -async def test_provider_pair_pages_skip_oversized_names_and_continue(db: ManagementDatabase) -> None: +async def test_provider_lookup_skips_oversized_names_and_preserves_valid_models(db: ManagementDatabase) -> None: prefix = f"provider-width-{uuid4()}" day = date(2038, 1, 12) provider = prefix + "-valid" @@ -39,15 +39,15 @@ async def test_provider_pair_pages_skip_oversized_names_and_continue(db: Managem tuple(pair[1] for pair in pairs), ) expected = tuple(IssueModelPair(provider=p, model=m) for p, m in pairs[2:5]) - first = await repository.candidate_pairs(day, limit=1) - second = await repository.candidate_pairs(day, first[0], limit=1) - third = await repository.candidate_pairs(day, second[0], limit=1) - assert len(first) == len(second) == len(third) == 1 - assert frozenset((*first, *second, *third)) == frozenset(expected) - assert await repository.candidate_pairs(day, third[0], limit=1) == () - assert frozenset(await repository.provider_pairs(provider, day)) == frozenset(expected[:2]) - assert await repository.provider_pairs(wide_provider, day) == expected[2:] - assert await repository.provider_pairs("a" * 257, day) == () + first = await repository.provider_pairs(provider, day) + second = await repository.provider_pairs(wide_provider, day) + invalid = await repository.provider_pairs("a" * 257, day) + assert len(first) == 2 and len(second) == 1 and len(invalid) == 0 + assert frozenset((*first, *second)) == frozenset(expected) + assert await repository.provider_pairs(prefix + "-absent", day) == () + assert frozenset(first) == frozenset(expected[:2]) + assert second == expected[2:] + assert invalid == () finally: await db.prisma.writer_db.query_raw( 'DELETE FROM "LiteLLM_DailyUserSpend" WHERE id LIKE $1 RETURNING id', prefix + "%" @@ -81,9 +81,8 @@ async def test_provider_latency_requires_established_within_model_regressions(db FROM "LiteLLM_DailyUserSpend" WHERE custom_llm_provider=$1 RETURNING id""", prefix, ) - first = await repository.candidate_pairs(day, IssueModelPair(provider=prefix, model="0"), limit=1) - assert first == pairs[:1] - assert await repository.candidate_pairs(day, first[0], limit=1) == pairs[1:] + assert await repository.provider_pairs(prefix, day) == pairs + assert await repository.provider_pairs(prefix + "-other", day) == () evidence = await repository.latency_cohort(pairs, day, rule) assert evidence is not None assert evidence.cohort.model_pairs == pairs