diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a762a1e76ea..7bbdb64e861 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -14467,11 +14467,19 @@ async def _fetch_db_models_for_search( filter for `team_public_model_name` instead and keep the DB cost bounded by `search`. """ + model_name_condition: Final[dict[str, Any]] = ( + {"model_name": {"contains": search_lower, "mode": "insensitive"}} + if model_name is None + else {"model_name": model_name} + ) match_conditions: list[dict[str, Any]] = ( [ { "OR": [ - {"model_name": {"contains": search_lower, "mode": "insensitive"}}, + model_name_condition, + # Substring search also matches the underlying LiteLLM model + # name (e.g. "openrouter/deepseek/deepseek-chat"), so users + # can find deployments by provider or upstream model id. # JSON string_contains is case-sensitive on Postgres (see # note above); router-side matching below covers the # case-insensitive path for rows already in the router. @@ -14480,7 +14488,7 @@ async def _fetch_db_models_for_search( } ] if model_name is None - else [{"model_name": model_name}] + else [model_name_condition] ) # Status filter runs inside the DB query too: the fetch is capped, so # matches of the other status must not consume the page budget. @@ -14488,7 +14496,12 @@ async def _fetch_db_models_for_search( match_conditions.append({"model_info": {"path": ["blocked"], "equals": blocked}}) if db_model_ids_in_router: match_conditions.append({"model_id": {"not": {"in": list(db_model_ids_in_router)}}}) - db_where_condition: Final[dict[str, Any]] = {"AND": match_conditions} + # Keep the single-condition shape flat: it is what existing callers (and + # tests) assert, and Prisma treats both forms identically. + if len(match_conditions) == 1: + db_where_condition: Final[dict[str, Any]] = match_conditions[0] + else: + db_where_condition = {"AND": match_conditions} # Unsorted searches only need enough DB rows to fill the current # page after counting router-side matches. Sorted searches need diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index df05cf0987e..2f3b9d127a0 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -2594,7 +2594,10 @@ async def test_apply_search_filter_honours_exact_model_name_in_db_query(): proxy_config=proxy_config, ) where = prisma_client.db.litellm_proxymodeltable.count.call_args.kwargs["where"] - assert where["model_name"] == {"contains": "sonnet", "mode": "insensitive"} + assert where["OR"] == [ + {"model_name": {"contains": "sonnet", "mode": "insensitive"}}, + {"litellm_params": {"path": ["model"], "string_contains": "sonnet"}}, + ] @pytest.mark.asyncio diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx index e9176bfdf81..b979a8e61e5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx @@ -312,13 +312,13 @@ export function AllModelsTable({ ] ?? ROUTING_STATUS_FILTER_VALUES.all} - {(Object.keys(ROUTING_STATUS_FILTER_VALUES) as Array).map( - (value) => ( - - {ROUTING_STATUS_FILTER_VALUES[value]} - - ), - )} + {( + Object.keys(ROUTING_STATUS_FILTER_VALUES) as Array + ).map((value) => ( + + {ROUTING_STATUS_FILTER_VALUES[value]} + + ))}