mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-14 16:25:29 +00:00
new deep merge behavior when processing config
This commit is contained in:
@@ -2885,6 +2885,20 @@ class ProxyConfig:
|
||||
dict: Updated configuration dictionary
|
||||
"""
|
||||
|
||||
def _deep_merge_dicts(dst: dict, src: dict) -> None:
|
||||
"""
|
||||
Deep-merge src into dst, skipping None values from src.
|
||||
On conflicts, src (DB) wins.
|
||||
"""
|
||||
for k, v in src.items():
|
||||
if v is None:
|
||||
# Preserve existing config when DB value is None (matches prior behavior)
|
||||
continue
|
||||
if isinstance(v, dict) and isinstance(dst.get(k), dict):
|
||||
_deep_merge_dicts(dst[k], v)
|
||||
else:
|
||||
dst[k] = v
|
||||
|
||||
if param_name == "environment_variables":
|
||||
decrypted_env_vars = self._decrypt_and_set_db_env_variables(db_param_value)
|
||||
current_config.setdefault("environment_variables", {}).update(
|
||||
@@ -2905,13 +2919,10 @@ class ProxyConfig:
|
||||
return current_config
|
||||
|
||||
# For dictionary values, update only non-none values
|
||||
if isinstance(current_config[param_name], dict):
|
||||
# Only keep non None values from db_param_value
|
||||
non_none_values = {k: v for k, v in db_param_value.items() if v is not None}
|
||||
|
||||
# Update the config with non-none values
|
||||
current_config[param_name].update(non_none_values)
|
||||
if isinstance(current_config[param_name], dict) and isinstance(db_param_value, dict):
|
||||
_deep_merge_dicts(current_config[param_name], db_param_value)
|
||||
else:
|
||||
# Non-dict or mismatched types: DB value replaces config (unchanged behavior)
|
||||
current_config[param_name] = db_param_value
|
||||
|
||||
return current_config
|
||||
|
||||
@@ -1029,6 +1029,74 @@ def test_update_config_fields():
|
||||
assert team_config["langfuse_secret"] == "my-fake-secret"
|
||||
|
||||
|
||||
def test_update_config_fields_deep_merge_db_wins():
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
proxy_config = ProxyConfig()
|
||||
|
||||
current_config = {
|
||||
"router_settings": {
|
||||
"routing_mode": "cost_optimized",
|
||||
"model_group_alias": {
|
||||
# Existing alias with older model + different hidden flag
|
||||
"claude-sonnet-4": {
|
||||
"model": "claude-sonnet-4-20240219",
|
||||
"hidden": True,
|
||||
},
|
||||
# An extra alias that should remain untouched unless DB overrides it
|
||||
"legacy-sonnet": {
|
||||
"model": "claude-2.1",
|
||||
"hidden": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
db_param_value = {
|
||||
"model_group_alias": {
|
||||
# Conflict: DB should win (both 'model' and 'hidden')
|
||||
"claude-sonnet-4": {
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"hidden": False,
|
||||
},
|
||||
# New alias to be added by the merge
|
||||
"claude-sonnet-latest": {
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"hidden": True,
|
||||
},
|
||||
# Demonstrate that None values from DB are skipped (preserve existing)
|
||||
"legacy-sonnet": {
|
||||
"hidden": None # should not clobber current True
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
updated = proxy_config._update_config_fields(
|
||||
current_config=current_config,
|
||||
param_name="router_settings",
|
||||
db_param_value=db_param_value,
|
||||
)
|
||||
|
||||
rs = updated["router_settings"]
|
||||
aliases = rs["model_group_alias"]
|
||||
|
||||
# DB wins on conflicts (deep) for existing alias
|
||||
assert aliases["claude-sonnet-4"]["model"] == "claude-sonnet-4-20250514"
|
||||
assert aliases["claude-sonnet-4"]["hidden"] is False
|
||||
|
||||
# New alias introduced by DB is present with its values
|
||||
assert "claude-sonnet-latest" in aliases
|
||||
assert aliases["claude-sonnet-latest"]["model"] == "claude-sonnet-4-20250514"
|
||||
assert aliases["claude-sonnet-latest"]["hidden"] is True
|
||||
|
||||
# None in DB does not overwrite existing values
|
||||
assert aliases["legacy-sonnet"]["model"] == "claude-2.1"
|
||||
assert aliases["legacy-sonnet"]["hidden"] is True
|
||||
|
||||
# Unrelated router_settings keys are preserved
|
||||
assert rs["routing_mode"] == "cost_optimized"
|
||||
|
||||
|
||||
def test_update_config_fields_default_internal_user_params(monkeypatch):
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
|
||||
Reference in New Issue
Block a user