fix: return empty data array instead of 500 when no models configured (#18556)

- /v2/model/info now returns {"data": []} when llm_router is None or model_list is empty
- /model_group/info now returns {"data": []} when llm_model_list is None or empty
- Fixes UI crash on fresh installs with STORE_MODEL_IN_DB=True
- Added 4 unit tests for empty model list scenarios
This commit is contained in:
Alex Gertz
2026-01-02 23:36:05 +05:30
committed by GitHub
parent 687adc6024
commit 3bd309f808
2 changed files with 161 additions and 15 deletions
+6 -15
View File
@@ -7322,13 +7322,9 @@ async def model_info_v2(
"""
global llm_model_list, general_settings, user_config_file_path, proxy_config, llm_router
if llm_router is None:
raise HTTPException(
status_code=500,
detail={
"error": f"No model list passed, models router={llm_router}. You can add a model through the config.yaml or on the LiteLLM Admin UI."
},
)
# Return empty data array when no models are configured (graceful handling for fresh installs)
if llm_router is None or not llm_router.model_list:
return {"data": []}
if prisma_client is None:
raise HTTPException(
@@ -8228,14 +8224,9 @@ async def model_group_info(
"""
global llm_model_list, general_settings, user_config_file_path, proxy_config, llm_router
if llm_model_list is None:
raise HTTPException(
status_code=500, detail={"error": "LLM Model List not loaded in"}
)
if llm_router is None:
raise HTTPException(
status_code=500, detail={"error": "LLM Router is not loaded in"}
)
# Return empty data array when no models are configured (graceful handling for fresh installs)
if llm_model_list is None or llm_router is None or not llm_model_list:
return {"data": []}
from litellm.proxy.utils import get_available_models_for_user
@@ -0,0 +1,155 @@
"""
Tests for graceful handling of empty model list scenarios.
These tests verify that /v2/model/info and /model_group/info endpoints
return empty data arrays instead of 500 errors when no models are configured.
"""
import os
import sys
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from fastapi.testclient import TestClient
sys.path.insert(
0, os.path.abspath("../../..")
) # Adds the parent directory to the system-path
from litellm.proxy.proxy_server import app
@pytest.fixture
def client():
"""Create a test client for the FastAPI app."""
return TestClient(app)
class TestEmptyModelListHandling:
"""Test suite for empty model list scenarios."""
def test_v2_model_info_returns_empty_data_when_router_is_none(
self, client, monkeypatch
):
"""
Test that /v2/model/info returns {"data": []} instead of 500
when llm_router is None.
"""
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_model_list", None)
monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None)
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
with patch(
"litellm.proxy.auth.user_api_key_auth.user_api_key_auth",
return_value=MagicMock(
user_id="test-user",
team_id=None,
team_models=[],
models=[],
user_role="proxy_admin",
),
):
response = client.get(
"/v2/model/info",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 200
assert response.json() == {"data": []}
def test_v2_model_info_returns_empty_data_when_model_list_empty(
self, client, monkeypatch
):
"""
Test that /v2/model/info returns {"data": []} instead of 500
when llm_router exists but model_list is empty.
"""
mock_router = MagicMock()
mock_router.model_list = []
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_model_list", [])
monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None)
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
with patch(
"litellm.proxy.auth.user_api_key_auth.user_api_key_auth",
return_value=MagicMock(
user_id="test-user",
team_id=None,
team_models=[],
models=[],
user_role="proxy_admin",
),
):
response = client.get(
"/v2/model/info",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 200
assert response.json() == {"data": []}
def test_model_group_info_returns_empty_data_when_model_list_none(
self, client, monkeypatch
):
"""
Test that /model_group/info returns {"data": []} instead of 500
when llm_model_list is None.
"""
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_model_list", None)
monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None)
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
with patch(
"litellm.proxy.auth.user_api_key_auth.user_api_key_auth",
return_value=MagicMock(
user_id="test-user",
team_id=None,
team_models=[],
models=[],
user_role="proxy_admin",
),
):
response = client.get(
"/model_group/info",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 200
assert response.json() == {"data": []}
def test_model_group_info_returns_empty_data_when_model_list_empty(
self, client, monkeypatch
):
"""
Test that /model_group/info returns {"data": []} instead of 500
when llm_model_list is empty.
"""
mock_router = MagicMock()
mock_router.model_list = []
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_model_list", [])
monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None)
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
with patch(
"litellm.proxy.auth.user_api_key_auth.user_api_key_auth",
return_value=MagicMock(
user_id="test-user",
team_id=None,
team_models=[],
models=[],
user_role="proxy_admin",
),
):
response = client.get(
"/model_group/info",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 200
assert response.json() == {"data": []}