mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(proxy): merge UI-added callbacks with config.yaml callbacks
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
f8375780fe
commit
badefacfe6
2 changed files with 69 additions and 0 deletions
|
|
@ -3518,6 +3518,41 @@ _DB_OVERLAY_REMOTE_MODULE_LIST_FIELDS: Dict[str, Tuple[str, ...]] = {
|
|||
),
|
||||
}
|
||||
|
||||
_ADDITIVE_LITELLM_SETTINGS_LIST_FIELDS: tuple[str, ...] = (
|
||||
"callbacks",
|
||||
"success_callback",
|
||||
"failure_callback",
|
||||
"service_callback",
|
||||
"audit_log_callbacks",
|
||||
)
|
||||
|
||||
|
||||
def _callback_dedup_key(callback: Any) -> Any:
|
||||
return callback.lower() if isinstance(callback, str) else id(callback)
|
||||
|
||||
|
||||
def _union_callback_lists(config_value: Any, db_value: Any) -> Any:
|
||||
"""Union a config.yaml callback list with the DB-overlay one, config entries
|
||||
first, deduped case-insensitively for plain string callback names."""
|
||||
if not isinstance(config_value, list) or not isinstance(db_value, list):
|
||||
return db_value
|
||||
seen = frozenset(_callback_dedup_key(callback) for callback in config_value)
|
||||
# mutable-ok: config consumers isinstance-check these callback entries for `list`
|
||||
return [*config_value, *(cb for cb in db_value if _callback_dedup_key(cb) not in seen)]
|
||||
|
||||
|
||||
def _merge_additive_litellm_settings(current_config: dict[str, Any], db_param_value: dict[str, Any]) -> None:
|
||||
"""Fold the config.yaml callback lists into the DB-overlay ones, in place.
|
||||
|
||||
The DB row only holds the callbacks added from the Admin UI, so letting it
|
||||
replace the config.yaml lists drops every YAML-declared logger."""
|
||||
config_litellm_settings = current_config.get("litellm_settings")
|
||||
if not isinstance(config_litellm_settings, dict):
|
||||
return
|
||||
for field in _ADDITIVE_LITELLM_SETTINGS_LIST_FIELDS:
|
||||
if field in db_param_value:
|
||||
db_param_value[field] = _union_callback_lists(config_litellm_settings.get(field), db_param_value[field])
|
||||
|
||||
|
||||
def _is_remote_module_url(value: Any) -> bool:
|
||||
return isinstance(value, str) and (value.startswith("s3://") or value.startswith("gcs://"))
|
||||
|
|
@ -6048,6 +6083,8 @@ class ProxyConfig:
|
|||
if key in LITELLM_SETTINGS_SAFE_DB_OVERRIDES: # params that are safe to override with db values
|
||||
setattr(litellm, key, value)
|
||||
|
||||
_merge_additive_litellm_settings(current_config, db_param_value)
|
||||
|
||||
# If param doesn't exist in config, add it
|
||||
if param_name not in current_config:
|
||||
current_config[param_name] = db_param_value
|
||||
|
|
|
|||
|
|
@ -5813,6 +5813,38 @@ def test_get_config_normalizes_string_callbacks(monkeypatch):
|
|||
assert "datadog" in success_and_failure_callbacks
|
||||
|
||||
|
||||
def test_update_config_fields_unions_callbacks_from_config_and_db():
|
||||
"""Issue #12118: callbacks added from the Admin UI land in the DB litellm_settings
|
||||
row, which previously replaced the config.yaml callback lists on config load, so
|
||||
only the UI-added loggers ran."""
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
proxy_config = ProxyConfig()
|
||||
|
||||
updated = proxy_config._update_config_fields(
|
||||
current_config={
|
||||
"litellm_settings": {
|
||||
"success_callback": ["langfuse"],
|
||||
"failure_callback": ["sentry"],
|
||||
"callbacks": ["otel"],
|
||||
"drop_params": True,
|
||||
}
|
||||
},
|
||||
param_name="litellm_settings",
|
||||
db_param_value={
|
||||
"success_callback": ["datadog", "Langfuse"],
|
||||
"failure_callback": ["datadog"],
|
||||
"callbacks": ["prometheus"],
|
||||
},
|
||||
)
|
||||
|
||||
ls = updated["litellm_settings"]
|
||||
assert ls["success_callback"] == ["langfuse", "datadog"]
|
||||
assert ls["failure_callback"] == ["sentry", "datadog"]
|
||||
assert ls["callbacks"] == ["otel", "prometheus"]
|
||||
assert ls["drop_params"] is True
|
||||
|
||||
|
||||
def test_deep_merge_dicts_skips_none_and_empty_lists(monkeypatch):
|
||||
"""
|
||||
Test that _update_config_fields deep merge skips None values and empty lists.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue