fix(proxy): initialize string success/failure callbacks at startup after config load (#38226)

* fix(proxy): eagerly initialize string callbacks

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): materialize string callbacks after load_config so later litellm_settings keys are applied

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): drop casts when snapshotting string callbacks so LIT006 stays at base

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
This commit is contained in:
devin-ai-integration[bot] 2026-09-08 13:00:14 -07:00 committed by GitHub
parent 77bfeb5708
commit d36e032241
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 0 deletions

View file

@ -177,6 +177,9 @@ from litellm.types.mcp import (
)
from litellm.types.proxy.policy_engine.pipeline_types import PipelineExecutionResult
from litellm.types.utils import LLMResponseTypes, LoggedLiteLLMParams
from litellm.utils import (
_add_custom_logger_callback_to_specific_event, # pyright: ignore[reportPrivateUsage] # only string-to-logger helper
)
if TYPE_CHECKING:
from mcp.types import CallToolResult
@ -857,6 +860,14 @@ class ProxyLogging:
litellm.logging_callback_manager.add_litellm_async_success_callback(callback)
litellm.logging_callback_manager.add_litellm_async_failure_callback(callback)
# Runs after load_config applied every litellm_settings key: logger __init__s read e.g. s3_callback_params
success_callbacks: Final = tuple(cb for cb in litellm.success_callback if isinstance(cb, str))
failure_callbacks: Final = tuple(cb for cb in litellm.failure_callback if isinstance(cb, str))
for callback in success_callbacks:
_add_custom_logger_callback_to_specific_event(callback, "success")
for callback in failure_callbacks:
_add_custom_logger_callback_to_specific_event(callback, "failure")
async def update_request_status(self, litellm_call_id: str, status: Literal["success", "fail"]):
# only use this if slack alerting is being used
if self.alerting is None:

View file

@ -3166,6 +3166,47 @@ async def test_custom_ui_sso_sign_in_handler_config_loading():
os.unlink(config_file_path)
@pytest.mark.asyncio
async def test_startup_initializes_string_callbacks_after_all_litellm_settings_load(tmp_path, monkeypatch):
from litellm.integrations.s3_v2 import S3Logger
from litellm.litellm_core_utils import litellm_logging
from litellm.proxy.proxy_server import ProxyConfig
from litellm.proxy.utils import ProxyLogging
config_file = tmp_path / "config.yaml"
config_file.write_text(
"model_list: []\n"
"litellm_settings:\n"
" success_callback:\n"
" - s3_v2\n"
" failure_callback:\n"
" - s3_v2\n"
" s3_callback_params:\n"
" s3_bucket_name: ordering-regression-bucket\n"
" s3_region_name: us-west-2\n"
)
monkeypatch.setattr(litellm, "success_callback", [])
monkeypatch.setattr(litellm, "_async_success_callback", [])
monkeypatch.setattr(litellm, "failure_callback", [])
monkeypatch.setattr(litellm, "_async_failure_callback", [])
monkeypatch.setattr(litellm, "callbacks", [])
monkeypatch.setattr(litellm, "s3_callback_params", None)
monkeypatch.setattr(litellm_logging, "_in_memory_loggers", [])
await ProxyConfig().load_config(router=MagicMock(), config_file_path=str(config_file))
ProxyLogging(user_api_key_cache=MagicMock())._init_litellm_callbacks(llm_router=None)
success_loggers = [cb for cb in litellm._async_success_callback if isinstance(cb, S3Logger)]
failure_loggers = [cb for cb in litellm._async_failure_callback if isinstance(cb, S3Logger)]
assert len(success_loggers) == 1
assert len(failure_loggers) == 1
assert success_loggers[0].s3_bucket_name == "ordering-regression-bucket"
assert success_loggers[0].s3_region_name == "us-west-2"
assert "s3_v2" not in litellm.success_callback
assert "s3_v2" not in litellm.failure_callback
@pytest.mark.asyncio
async def test_load_config_max_budget_env_var_coerced_to_float(tmp_path, monkeypatch):
"""