mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix: use ConfigRepository.get_param() in _add_router_settings_from_db_config
Router settings stored via /config/update were double-serialized as JSON strings in the LiteLLM_Config.param_value Json column. On reload, _add_router_settings_from_db_config used raw table.find_first() which bypassed ConfigRepository.get_param()'s string-to-dict parsing, causing isinstance(param_value, dict) to fail and settings like model_group_alias to be silently dropped. Fixes #31836 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
70eb4e5d00
commit
3496249d79
4 changed files with 47 additions and 18 deletions
|
|
@ -5282,9 +5282,8 @@ class ProxyConfig:
|
|||
4. Update router settings
|
||||
"""
|
||||
if llm_router is not None and prisma_client is not None:
|
||||
db_router_settings = await ConfigRepository(prisma_client).table.find_first(
|
||||
where={"param_name": "router_settings"}
|
||||
)
|
||||
db_router_param = await ConfigRepository(prisma_client).get_param("router_settings")
|
||||
db_param_value = db_router_param.param_value if db_router_param is not None else None
|
||||
|
||||
config_router_settings = config_data.get("router_settings", {})
|
||||
|
||||
|
|
@ -5292,16 +5291,15 @@ class ProxyConfig:
|
|||
if (
|
||||
config_router_settings is not None
|
||||
and isinstance(config_router_settings, dict)
|
||||
and db_router_settings is not None
|
||||
and isinstance(db_router_settings.param_value, dict)
|
||||
and isinstance(db_param_value, dict)
|
||||
):
|
||||
from litellm.utils import _update_dictionary
|
||||
|
||||
combined_router_settings = _update_dictionary(config_router_settings, db_router_settings.param_value)
|
||||
combined_router_settings = _update_dictionary(config_router_settings, db_param_value)
|
||||
elif config_router_settings is not None and isinstance(config_router_settings, dict):
|
||||
combined_router_settings = config_router_settings
|
||||
elif db_router_settings is not None and isinstance(db_router_settings.param_value, dict):
|
||||
combined_router_settings = db_router_settings.param_value
|
||||
elif isinstance(db_param_value, dict):
|
||||
combined_router_settings = db_param_value
|
||||
|
||||
if combined_router_settings:
|
||||
llm_router.update_settings(**combined_router_settings)
|
||||
|
|
|
|||
|
|
@ -1368,7 +1368,7 @@ async def test_ProxyConfig__add_router_settings_from_db_config_updates_router():
|
|||
fake_router = MagicMock()
|
||||
fake_router.update_settings = MagicMock()
|
||||
fake_prisma = MagicMock()
|
||||
fake_prisma.db.litellm_config.find_first = AsyncMock(
|
||||
fake_prisma.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
param_value={"timeout": 30, "retries": 2, "fallbacks": []}
|
||||
)
|
||||
|
|
@ -1393,6 +1393,34 @@ async def test_ProxyConfig__add_router_settings_from_db_config_updates_router():
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ProxyConfig__add_router_settings_from_db_config_handles_json_string():
|
||||
"""Regression for #31836: param_value stored as a JSON string must be
|
||||
deserialized before being applied to the router."""
|
||||
import json
|
||||
|
||||
pc = ProxyConfig()
|
||||
fake_router = MagicMock()
|
||||
fake_router.update_settings = MagicMock()
|
||||
fake_prisma = MagicMock()
|
||||
fake_prisma.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
param_value=json.dumps(
|
||||
{"model_group_alias": {"codex/gpt-5.5:auto": "codex/gpt-5.5"}}
|
||||
)
|
||||
)
|
||||
)
|
||||
await pc._add_router_settings_from_db_config(
|
||||
config_data={},
|
||||
llm_router=fake_router,
|
||||
prisma_client=fake_prisma,
|
||||
)
|
||||
assert fake_router.update_settings.called
|
||||
assert fake_router.update_settings.call_args.kwargs == {
|
||||
"model_group_alias": {"codex/gpt-5.5:auto": "codex/gpt-5.5"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ProxyConfig__add_router_settings_from_db_config_none_router_noop():
|
||||
pc = ProxyConfig()
|
||||
|
|
|
|||
|
|
@ -3776,7 +3776,7 @@ async def test_add_router_settings_from_db_config_merge_logic():
|
|||
|
||||
# Mock prisma client
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=mock_db_config
|
||||
)
|
||||
|
||||
|
|
@ -3787,8 +3787,8 @@ async def test_add_router_settings_from_db_config_merge_logic():
|
|||
prisma_client=mock_prisma_client,
|
||||
)
|
||||
|
||||
# Verify find_first was called with correct parameters
|
||||
mock_prisma_client.db.litellm_config.find_first.assert_called_once_with(
|
||||
# Verify find_unique was called with correct parameters
|
||||
mock_prisma_client.db.litellm_config.find_unique.assert_called_once_with(
|
||||
where={"param_name": "router_settings"}
|
||||
)
|
||||
|
||||
|
|
@ -3853,7 +3853,7 @@ async def test_add_router_settings_from_db_config_edge_cases():
|
|||
|
||||
# Test Case 3: DB returns None (no router_settings in DB)
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(return_value=None)
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(return_value=None)
|
||||
|
||||
config_data = {"router_settings": {"routing_strategy": "usage-based"}}
|
||||
|
||||
|
|
@ -3870,7 +3870,7 @@ async def test_add_router_settings_from_db_config_edge_cases():
|
|||
# Test Case 4: Config has no router_settings
|
||||
mock_db_config = MagicMock()
|
||||
mock_db_config.param_value = {"db_setting": "db_value"}
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=mock_db_config
|
||||
)
|
||||
|
||||
|
|
@ -3885,7 +3885,7 @@ async def test_add_router_settings_from_db_config_edge_cases():
|
|||
mock_router.reset_mock()
|
||||
|
||||
# Test Case 5: Both config and DB router_settings are None/empty
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(return_value=None)
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(return_value=None)
|
||||
|
||||
await proxy_config._add_router_settings_from_db_config(
|
||||
config_data={}, llm_router=mock_router, prisma_client=mock_prisma_client
|
||||
|
|
@ -3896,8 +3896,8 @@ async def test_add_router_settings_from_db_config_edge_cases():
|
|||
|
||||
# Test Case 6: DB config exists but param_value is not a dict
|
||||
mock_db_config_invalid = MagicMock()
|
||||
mock_db_config_invalid.param_value = "not_a_dict"
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(
|
||||
mock_db_config_invalid.param_value = 42
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=mock_db_config_invalid
|
||||
)
|
||||
|
||||
|
|
@ -3951,7 +3951,7 @@ async def test_add_router_settings_shallow_merge_behavior():
|
|||
}
|
||||
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_prisma_client.db.litellm_config.find_first = AsyncMock(
|
||||
mock_prisma_client.db.litellm_config.find_unique = AsyncMock(
|
||||
return_value=mock_db_config
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@ class _FakeConfigTable:
|
|||
async def find_first(self, where):
|
||||
return self.rows.get(where["param_name"])
|
||||
|
||||
async def find_unique(self, where):
|
||||
return self.rows.get(where["param_name"])
|
||||
|
||||
async def upsert(self, where, data):
|
||||
name = where["param_name"]
|
||||
raw = (data["update"] if name in self.rows else data["create"])["param_value"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue