mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix: match exact class in callback dedup so a custom subclass does not block a built-in logger (#34804)
This commit is contained in:
parent
2b7e01bb7e
commit
a7e665620b
2 changed files with 98 additions and 7 deletions
|
|
@ -558,10 +558,10 @@ def _custom_logger_class_exists_in_success_callbacks(
|
|||
e.g if `LangfusePromptManagement` is passed in, it will return True if an instance of `LangfusePromptManagement` exists in litellm.success_callback or litellm._async_success_callback
|
||||
|
||||
Prevents double adding a custom logger callback to the litellm callbacks
|
||||
|
||||
Matches on the exact class; an instance of a subclass does not count as registered
|
||||
"""
|
||||
return any(
|
||||
isinstance(cb, type(callback_class)) for cb in litellm.success_callback + litellm._async_success_callback
|
||||
)
|
||||
return any(type(cb) is type(callback_class) for cb in litellm.success_callback + litellm._async_success_callback)
|
||||
|
||||
|
||||
def _custom_logger_class_exists_in_failure_callbacks(
|
||||
|
|
@ -573,10 +573,10 @@ def _custom_logger_class_exists_in_failure_callbacks(
|
|||
e.g if `LangfusePromptManagement` is passed in, it will return True if an instance of `LangfusePromptManagement` exists in litellm.failure_callback or litellm._async_failure_callback
|
||||
|
||||
Prevents double adding a custom logger callback to the litellm callbacks
|
||||
|
||||
Matches on the exact class; an instance of a subclass does not count as registered
|
||||
"""
|
||||
return any(
|
||||
isinstance(cb, type(callback_class)) for cb in litellm.failure_callback + litellm._async_failure_callback
|
||||
)
|
||||
return any(type(cb) is type(callback_class) for cb in litellm.failure_callback + litellm._async_failure_callback)
|
||||
|
||||
|
||||
def get_request_guardrails(kwargs: Dict[str, Any]) -> List[str]:
|
||||
|
|
@ -766,7 +766,7 @@ def function_setup(
|
|||
llm_router=None, # type: ignore
|
||||
)
|
||||
if callback is None or any(
|
||||
isinstance(cb, type(callback)) for cb in litellm._async_success_callback
|
||||
type(cb) is type(callback) for cb in litellm._async_success_callback
|
||||
): # don't double add a callback
|
||||
continue
|
||||
if callback not in litellm.input_callback:
|
||||
|
|
|
|||
|
|
@ -4916,3 +4916,94 @@ def test_is_prompt_caching_valid_prompt_explicit_min_token_count_overrides_model
|
|||
is_prompt_caching_valid_prompt(model="claude-opus-4-8", messages=PROMPT_CACHE_MESSAGES, min_token_count=8192)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
def test_custom_logger_guards_ignore_subclass_instances(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Regression LIT-4392: the success/failure existence guards used isinstance, so a user
|
||||
subclass of a built-in logger already promoted into the callback lists made the guard
|
||||
report the built-in itself as registered and the configured logger was silently skipped.
|
||||
The exact-class assertions must hold alongside the subclass assertions: the guards still
|
||||
have to dedup a second instance of the same class, only a subclass must stop matching."""
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.utils import (
|
||||
_custom_logger_class_exists_in_failure_callbacks,
|
||||
_custom_logger_class_exists_in_success_callbacks,
|
||||
)
|
||||
|
||||
class BuiltinLogger(CustomLogger):
|
||||
pass
|
||||
|
||||
class UserSubclassLogger(BuiltinLogger):
|
||||
pass
|
||||
|
||||
builtin_instance = BuiltinLogger()
|
||||
|
||||
monkeypatch.setattr(litellm, "success_callback", [UserSubclassLogger()])
|
||||
monkeypatch.setattr(litellm, "failure_callback", [UserSubclassLogger()])
|
||||
monkeypatch.setattr(litellm, "_async_success_callback", [])
|
||||
monkeypatch.setattr(litellm, "_async_failure_callback", [])
|
||||
assert _custom_logger_class_exists_in_success_callbacks(builtin_instance) is False
|
||||
assert _custom_logger_class_exists_in_failure_callbacks(builtin_instance) is False
|
||||
|
||||
monkeypatch.setattr(litellm, "success_callback", [BuiltinLogger()])
|
||||
monkeypatch.setattr(litellm, "failure_callback", [BuiltinLogger()])
|
||||
assert _custom_logger_class_exists_in_success_callbacks(builtin_instance) is True
|
||||
assert _custom_logger_class_exists_in_failure_callbacks(builtin_instance) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_s3_v2_success_callback_registers_alongside_user_subclass(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Regression LIT-4392: with a user S3Logger subclass registered via litellm_settings.callbacks
|
||||
and success_callback ["s3_v2"], the built-in s3_v2 logger was never added and S3 logs were
|
||||
silently dropped while requests kept returning 200."""
|
||||
from litellm.integrations.s3_v2 import S3Logger
|
||||
from litellm.utils import _add_custom_logger_callback_to_specific_event
|
||||
|
||||
class UserS3Logger(S3Logger):
|
||||
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
pass
|
||||
|
||||
user_logger = UserS3Logger()
|
||||
monkeypatch.setattr(litellm, "success_callback", [user_logger, "s3_v2"])
|
||||
monkeypatch.setattr(litellm, "_async_success_callback", [user_logger])
|
||||
monkeypatch.setattr(litellm, "failure_callback", [])
|
||||
monkeypatch.setattr(litellm, "_async_failure_callback", [])
|
||||
|
||||
_add_custom_logger_callback_to_specific_event("s3_v2", "success")
|
||||
|
||||
assert any(type(cb) is S3Logger for cb in litellm.success_callback)
|
||||
assert any(type(cb) is S3Logger for cb in litellm._async_success_callback)
|
||||
assert "s3_v2" not in litellm.success_callback
|
||||
assert user_logger in litellm.success_callback
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_builtin_string_callback_registers_when_subclass_already_active(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Regression LIT-4392, litellm.callbacks path: the inline dedup in function_setup also
|
||||
matched subclass instances, so a built-in name in litellm.callbacks was dropped whenever a
|
||||
user subclass was already promoted into _async_success_callback."""
|
||||
from litellm.integrations.s3_v2 import S3Logger
|
||||
|
||||
class UserS3Logger(S3Logger):
|
||||
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
pass
|
||||
|
||||
user_logger = UserS3Logger()
|
||||
monkeypatch.setattr(litellm, "callbacks", ["s3_v2"])
|
||||
monkeypatch.setattr(litellm, "input_callback", [])
|
||||
monkeypatch.setattr(litellm, "success_callback", [user_logger])
|
||||
monkeypatch.setattr(litellm, "failure_callback", [])
|
||||
monkeypatch.setattr(litellm, "_async_success_callback", [user_logger])
|
||||
monkeypatch.setattr(litellm, "_async_failure_callback", [])
|
||||
|
||||
await litellm.acompletion(
|
||||
model="gpt-5.6",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
mock_response="ok",
|
||||
)
|
||||
|
||||
assert any(type(cb) is S3Logger for cb in litellm._async_success_callback)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue