mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge pull request #18775 from BerriAI/litellm_fs_callback_fix
[Fix] Normalize Proxy Config Callback
This commit is contained in:
commit
864e8c6543
2 changed files with 105 additions and 2 deletions
|
|
@ -3402,8 +3402,8 @@ class ProxyConfig:
|
|||
|
||||
def _deep_merge_dicts(dst: dict, src: dict) -> None:
|
||||
"""
|
||||
Deep-merge src into dst, skipping None values from src.
|
||||
On conflicts, src (DB) wins.
|
||||
Deep-merge src into dst, skipping None values and empty lists from src.
|
||||
On conflicts, src (DB) wins, but empty lists are treated as "no value" and don't overwrite.
|
||||
"""
|
||||
stack = [(dst, src)]
|
||||
while stack:
|
||||
|
|
@ -3412,6 +3412,9 @@ class ProxyConfig:
|
|||
if v is None:
|
||||
# Preserve existing config when DB value is None (matches prior behavior)
|
||||
continue
|
||||
# Skip empty lists - treat them as "no value" to preserve file config
|
||||
if isinstance(v, list) and len(v) == 0:
|
||||
continue
|
||||
if isinstance(v, dict) and isinstance(d.get(k), dict):
|
||||
stack.append((d[k], v))
|
||||
else:
|
||||
|
|
@ -9762,6 +9765,18 @@ async def get_config(): # noqa: PLR0915
|
|||
_failure_callbacks = _litellm_settings.get("failure_callback", [])
|
||||
_success_and_failure_callbacks = _litellm_settings.get("callbacks", [])
|
||||
|
||||
# Normalize string callbacks to lists
|
||||
def normalize_callback(callback):
|
||||
if isinstance(callback, str):
|
||||
return [callback]
|
||||
elif callback is None:
|
||||
return []
|
||||
return callback
|
||||
|
||||
_success_callbacks = normalize_callback(_success_callbacks)
|
||||
_failure_callbacks = normalize_callback(_failure_callbacks)
|
||||
_success_and_failure_callbacks = normalize_callback(_success_and_failure_callbacks)
|
||||
|
||||
_data_to_return = []
|
||||
"""
|
||||
[
|
||||
|
|
|
|||
|
|
@ -3036,3 +3036,91 @@ def test_get_image_root_case_uses_current_dir(monkeypatch):
|
|||
|
||||
# Verify FileResponse was called
|
||||
assert mock_file_response.called, "FileResponse should be called"
|
||||
|
||||
|
||||
def test_get_config_normalizes_string_callbacks(monkeypatch):
|
||||
"""
|
||||
Test that /get/config/callbacks normalizes string callbacks to lists.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import app, proxy_config, user_api_key_auth
|
||||
|
||||
config_data = {
|
||||
"litellm_settings": {
|
||||
"success_callback": "langfuse",
|
||||
"failure_callback": None,
|
||||
"callbacks": ["prometheus", "datadog"],
|
||||
},
|
||||
"general_settings": {},
|
||||
"environment_variables": {},
|
||||
}
|
||||
|
||||
mock_router = MagicMock()
|
||||
mock_router.get_settings.return_value = {}
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router)
|
||||
monkeypatch.setattr(
|
||||
proxy_config, "get_config", AsyncMock(return_value=config_data)
|
||||
)
|
||||
|
||||
original_overrides = app.dependency_overrides.copy()
|
||||
app.dependency_overrides[user_api_key_auth] = lambda: MagicMock()
|
||||
|
||||
client = TestClient(app)
|
||||
try:
|
||||
response = client.get("/get/config/callbacks")
|
||||
finally:
|
||||
app.dependency_overrides = original_overrides
|
||||
|
||||
assert response.status_code == 200
|
||||
callbacks = response.json()["callbacks"]
|
||||
|
||||
success_callbacks = [cb["name"] for cb in callbacks if cb.get("type") == "success"]
|
||||
failure_callbacks = [cb["name"] for cb in callbacks if cb.get("type") == "failure"]
|
||||
success_and_failure_callbacks = [
|
||||
cb["name"] for cb in callbacks if cb.get("type") == "success_and_failure"
|
||||
]
|
||||
|
||||
assert "langfuse" in success_callbacks
|
||||
assert len(failure_callbacks) == 0
|
||||
assert "prometheus" in success_and_failure_callbacks
|
||||
assert "datadog" in success_and_failure_callbacks
|
||||
|
||||
|
||||
def test_deep_merge_dicts_skips_none_and_empty_lists(monkeypatch):
|
||||
"""
|
||||
Test that _update_config_fields deep merge skips None values and empty lists.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
proxy_config = ProxyConfig()
|
||||
|
||||
current_config = {
|
||||
"general_settings": {
|
||||
"max_parallel_requests": 10,
|
||||
"allowed_models": ["gpt-3.5-turbo", "gpt-4"],
|
||||
"nested": {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
db_param_value = {
|
||||
"max_parallel_requests": None,
|
||||
"allowed_models": [],
|
||||
"new_key": "new_value",
|
||||
"nested": {
|
||||
"key1": "updated_value1",
|
||||
"key3": "value3",
|
||||
},
|
||||
}
|
||||
|
||||
result = proxy_config._update_config_fields(
|
||||
current_config, "general_settings", db_param_value
|
||||
)
|
||||
|
||||
assert result["general_settings"]["max_parallel_requests"] == 10
|
||||
assert result["general_settings"]["allowed_models"] == ["gpt-3.5-turbo", "gpt-4"]
|
||||
assert result["general_settings"]["new_key"] == "new_value"
|
||||
assert result["general_settings"]["nested"]["key1"] == "updated_value1"
|
||||
assert result["general_settings"]["nested"]["key2"] == "value2"
|
||||
assert result["general_settings"]["nested"]["key3"] == "value3"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue