new deep merge behavior when processing config

This commit is contained in:
Achintya Rajan
2025-10-08 14:41:41 -07:00
parent 0b6b69cd5b
commit ce8a520a86
2 changed files with 85 additions and 6 deletions
+17 -6
View File
@@ -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