From d17387e2e105bcbb42cd40333d9834cec38c8a44 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:59:34 -0700 Subject: [PATCH] fix(proxy): fall back on empty-string model_group in aggregated usage SQL --- .../management_endpoints/common_daily_activity.py | 8 ++++---- .../test_common_daily_activity.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/management_endpoints/common_daily_activity.py b/litellm/proxy/management_endpoints/common_daily_activity.py index 9bd358289b3..8a5a31710cf 100644 --- a/litellm/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/proxy/management_endpoints/common_daily_activity.py @@ -575,11 +575,11 @@ def _build_aggregated_sql_query( date, api_key, model, - COALESCE(model_group, model) AS model_group, + COALESCE(NULLIF(model_group, ''), model) AS model_group, custom_llm_provider, mcp_namespaced_tool_name, endpoint, - GROUPING(date, api_key, model, COALESCE(model_group, model), + GROUPING(date, api_key, model, COALESCE(NULLIF(model_group, ''), model), custom_llm_provider, mcp_namespaced_tool_name, endpoint) AS group_level, SUM(spend)::float AS spend, @@ -600,8 +600,8 @@ def _build_aggregated_sql_query( (date, api_key), (date, model), (date, model, api_key), - (date, COALESCE(model_group, model)), - (date, COALESCE(model_group, model), api_key), + (date, COALESCE(NULLIF(model_group, ''), model)), + (date, COALESCE(NULLIF(model_group, ''), model), api_key), (date, custom_llm_provider), (date, custom_llm_provider, api_key), (date, mcp_namespaced_tool_name), diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index 5aee7ff0236..c2a0d34a915 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -911,12 +911,15 @@ class TestBuildAggregatedSqlQuery: assert "api_key = $5" in sql def test_model_group_rollups_fall_back_to_model_name(self): - """Aggregated model_groups rollups must coalesce NULL model_group to model. + """Aggregated model_groups rollups must fall back to model for group-less rows. The (date, model_group) grouping level cannot recover the model column after the fact (it is rolled up), so the fallback has to happen in SQL; without it, group-less rows silently vanish from the model_groups - breakdown that the usage UI now renders by default. + breakdown that the usage UI now renders by default. Group-less rows are + stored as empty strings, not NULL (spend_tracking_utils defaults + model_group to ""), so a plain COALESCE is not enough: the fallback must + be NULLIF-wrapped to catch both """ sql, _ = _build_aggregated_sql_query( table_name="litellm_dailyuserspend", @@ -929,13 +932,15 @@ class TestBuildAggregatedSqlQuery: ) normalized = " ".join(sql.split()) - assert "COALESCE(model_group, model) AS model_group" in normalized + fallback = "COALESCE(NULLIF(model_group, ''), model)" + assert f"{fallback} AS model_group" in normalized assert ( - "GROUPING(date, api_key, model, COALESCE(model_group, model), " + f"GROUPING(date, api_key, model, {fallback}, " "custom_llm_provider, mcp_namespaced_tool_name, endpoint) AS group_level" in normalized ) - assert "(date, COALESCE(model_group, model)), (date, COALESCE(model_group, model), api_key)," in normalized + assert f"(date, {fallback}), (date, {fallback}, api_key)," in normalized assert "(date, model_group)" not in normalized + assert "COALESCE(model_group, model)" not in normalized @pytest.mark.asyncio