mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(proxy): reconcile interception callbacks when config clears the list
The DB overlay merge treats an empty list as "no value" so it never overwrites the file config. Clearing the last entry from litellm_settings.callbacks therefore reaches _add_callbacks_from_db_config as an absent key, not as [], and reconciliation was skipped: the interception logger kept running (and kept paying for searches) after the capability was revoked. Reconciliation now also runs when the merged config lists no callbacks at all. To keep a transient config-load failure from being read as "nothing is configured", _update_llm_router only feeds the callback path once get_config() has actually returned, so an empty dict from the failure branch can no longer uninstall a working callback.
This commit is contained in:
parent
037660b16b
commit
0e918025c6
2 changed files with 39 additions and 7 deletions
|
|
@ -5453,9 +5453,11 @@ class ProxyConfig:
|
|||
|
||||
# Load config separately so a timeout here doesn't block model loading
|
||||
config_data: dict = {}
|
||||
config_loaded = False
|
||||
search_tools = None
|
||||
try:
|
||||
config_data = await proxy_config.get_config()
|
||||
config_loaded = True
|
||||
search_tools = self.parse_search_tools(config_data)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.warning(
|
||||
|
|
@ -5509,7 +5511,8 @@ class ProxyConfig:
|
|||
llm_model_list = llm_router.get_model_list()
|
||||
|
||||
# check if user set any callbacks in Config Table
|
||||
self._add_callbacks_from_db_config(config_data)
|
||||
if config_loaded:
|
||||
self._add_callbacks_from_db_config(config_data)
|
||||
|
||||
# router settings
|
||||
await self._add_router_settings_from_db_config(
|
||||
|
|
@ -5576,8 +5579,9 @@ class ProxyConfig:
|
|||
existing_callbacks=litellm.failure_callback,
|
||||
)
|
||||
|
||||
if callbacks is not None and isinstance(callbacks, list):
|
||||
for callback in callbacks:
|
||||
if callbacks is None or isinstance(callbacks, list):
|
||||
configured_callbacks = callbacks or ()
|
||||
for callback in configured_callbacks:
|
||||
if isinstance(callback, str) and install_config_parameterized_callback(
|
||||
callback=callback,
|
||||
litellm_settings=litellm_settings,
|
||||
|
|
@ -5589,7 +5593,7 @@ class ProxyConfig:
|
|||
event_types=["success", "failure"],
|
||||
existing_callbacks=litellm.callbacks,
|
||||
)
|
||||
uninstall_deconfigured_parameterized_callbacks(callbacks)
|
||||
uninstall_deconfigured_parameterized_callbacks(configured_callbacks)
|
||||
|
||||
def _encrypt_env_variables(self, environment_variables: dict, new_encryption_key: str | None = None) -> dict:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2025,10 +2025,11 @@ def test_ProxyConfig__add_callbacks_from_db_config_uninstalls_logger_when_callba
|
|||
[
|
||||
{},
|
||||
{"litellm_settings": {}},
|
||||
{"litellm_settings": {"callbacks": []}},
|
||||
{"litellm_settings": {"success_callback": ["langfuse"]}},
|
||||
],
|
||||
)
|
||||
def test_ProxyConfig__add_callbacks_from_db_config_keeps_logger_when_config_omits_callbacks(
|
||||
def test_ProxyConfig__add_callbacks_from_db_config_uninstalls_logger_when_config_omits_callbacks(
|
||||
monkeypatch, config_without_callbacks
|
||||
):
|
||||
from litellm.integrations.websearch_interception.handler import (
|
||||
|
|
@ -2039,10 +2040,37 @@ def test_ProxyConfig__add_callbacks_from_db_config_keeps_logger_when_config_omit
|
|||
pc = ProxyConfig()
|
||||
|
||||
pc._add_callbacks_from_db_config(_websearch_db_config("tavily-search", ["bedrock"]))
|
||||
installed_before = [cb for cb in litellm.callbacks if isinstance(cb, WebSearchInterceptionLogger)]
|
||||
|
||||
pc._add_callbacks_from_db_config(config_without_callbacks)
|
||||
|
||||
installed = [cb for cb in litellm.callbacks if isinstance(cb, WebSearchInterceptionLogger)]
|
||||
assert len(installed) == 1
|
||||
remaining = [cb for cb in litellm.callbacks if isinstance(cb, WebSearchInterceptionLogger)]
|
||||
snapshot = {"installed_before": len(installed_before), "remaining": len(remaining)}
|
||||
assert snapshot == {"installed_before": 1, "remaining": 0}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ProxyConfig__update_llm_router_keeps_logger_when_config_load_fails(monkeypatch):
|
||||
from litellm.integrations.websearch_interception.handler import (
|
||||
WebSearchInterceptionLogger,
|
||||
)
|
||||
|
||||
_reset_callback_lists(monkeypatch)
|
||||
pc = ProxyConfig()
|
||||
pc._add_callbacks_from_db_config(_websearch_db_config("tavily-search", ["bedrock"]))
|
||||
installed_before = [cb for cb in litellm.callbacks if isinstance(cb, WebSearchInterceptionLogger)]
|
||||
|
||||
class _FailingProxyConfig:
|
||||
async def get_config(self) -> dict:
|
||||
raise TimeoutError("transient DB timeout")
|
||||
|
||||
monkeypatch.setattr(litellm.proxy.proxy_server, "proxy_config", _FailingProxyConfig())
|
||||
|
||||
await pc._update_llm_router(new_models=[], proxy_logging_obj=MagicMock())
|
||||
|
||||
remaining = [cb for cb in litellm.callbacks if isinstance(cb, WebSearchInterceptionLogger)]
|
||||
snapshot = {"installed_before": len(installed_before), "remaining": len(remaining)}
|
||||
assert snapshot == {"installed_before": 1, "remaining": 1}
|
||||
|
||||
|
||||
def test_ProxyConfig__add_callbacks_from_db_config_bad_config_raises():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue