This commit is contained in:
wadrzl55333 2026-09-23 14:52:53 +00:00 • committed by GitHub
commit e779abe9af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 142 additions and 2 deletions

View file

@ -6156,7 +6156,11 @@ class ProxyConfig:
)
# these are litellm callbacks - "langfuse", "sentry", "wandb"
else:
litellm.logging_callback_manager.add_litellm_success_callback(callback)
self._add_callback_from_db_to_in_memory_litellm_callbacks(
callback=callback,
event_types=["success"], # mutable-ok: mirrors the DB-config call site
existing_callbacks=litellm.success_callback,
)
if "prometheus" in callback:
from litellm.integrations.prometheus import (
PrometheusLogger,
@ -6183,7 +6187,11 @@ class ProxyConfig:
)
# these are litellm callbacks - "langfuse", "sentry", "wandb"
else:
litellm.logging_callback_manager.add_litellm_failure_callback(callback)
self._add_callback_from_db_to_in_memory_litellm_callbacks(
callback=callback,
event_types=["failure"], # mutable-ok: mirrors the DB-config call site
existing_callbacks=litellm.failure_callback,
)
print( # noqa: T201
f"{blue_color_code} Initialized Failure Callbacks - {litellm.failure_callback} {reset_color_code}"
)

View file

@ -618,6 +618,15 @@ def _add_custom_logger_callback_to_specific_event(callback: str, logging_event:
litellm.failure_callback.remove(callback) # remove the string from the callback list
if callback in litellm._async_failure_callback:
litellm._async_failure_callback.remove(callback) # remove the string from the callback list
else:
# Custom-logger class could not be initialized (e.g. missing env
# credentials). Fall back to string registration so standard-route
# logging still works; pass-through endpoints only reach instance
# callbacks, so they keep missing out in this degraded state.
if logging_event == "success":
litellm.logging_callback_manager.add_litellm_success_callback(callback)
else:
litellm.logging_callback_manager.add_litellm_failure_callback(callback)
def _custom_logger_class_exists_in_success_callbacks(

View file

@ -158,6 +158,129 @@ async def test_multiple_includes():
assert config["litellm_settings"]["callbacks"] == ["prometheus"]
@pytest.mark.asyncio
async def test_config_file_success_callback_registers_custom_logger_instance(monkeypatch):
"""Config-file success_callback must register a custom-logger instance, not just the
string.
Pass-through endpoints log through the async success path only; string registration
leaves langfuse out of both callback lists because
`_should_run_sync_callbacks_for_async_calls` filters strings in
`_known_custom_logger_compatible_callbacks`.
"""
import tempfile
import yaml
from litellm.integrations.langfuse.langfuse_prompt_management import (
LangfusePromptManagement,
)
monkeypatch.setattr(litellm, "success_callback", [])
monkeypatch.setattr(litellm, "_async_success_callback", [])
monkeypatch.setattr(litellm, "failure_callback", [])
monkeypatch.setattr(litellm, "_async_failure_callback", [])
# test_add_callbacks_from_db_config clears _known_custom_logger_compatible_callbacks
# without restoring it (and the conftest snapshot skips underscore attrs), so
# re-assert the precondition explicitly instead of depending on test order.
known_callbacks = list(getattr(litellm, "_known_custom_logger_compatible_callbacks", []))
if "langfuse" not in known_callbacks:
known_callbacks.append("langfuse")
monkeypatch.setattr(litellm, "_known_custom_logger_compatible_callbacks", known_callbacks)
# the custom-logger instance path needs env credentials; without them the
# config path falls back to the string and this test would not exercise
# the instance registration
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-lf-test")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-lf-test")
monkeypatch.setenv("LANGFUSE_HOST", "http://localhost:3000")
config_content = {
"litellm_settings": {
"success_callback": ["langfuse", "sentry"],
"failure_callback": ["langfuse"],
}
}
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as temp_file:
yaml.dump(config_content, temp_file)
temp_file_path = temp_file.name
try:
proxy_config = ProxyConfig()
await proxy_config.load_config(
router=None,
config_file_path=temp_file_path,
)
# custom-logger-compatible names are registered as instances, not strings
assert "langfuse" not in litellm.success_callback
# non-compatible names still take the string path
assert "sentry" in litellm.success_callback
num_langfuse_instances = sum(
isinstance(callback, LangfusePromptManagement) for callback in litellm._async_success_callback
)
assert num_langfuse_instances == 1
num_failure_instances = sum(
isinstance(callback, LangfusePromptManagement) for callback in litellm._async_failure_callback
)
assert num_failure_instances == 1
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_config_file_callback_falls_back_to_string_when_init_fails(monkeypatch):
"""When the custom-logger class cannot be initialized, config-file callbacks
must fall back to string registration so standard-route logging still works."""
import tempfile
import yaml
from litellm.integrations.langfuse.langfuse_prompt_management import (
LangfusePromptManagement,
)
monkeypatch.setattr(litellm, "success_callback", [])
monkeypatch.setattr(litellm, "_async_success_callback", [])
monkeypatch.setattr(litellm, "failure_callback", [])
monkeypatch.setattr(litellm, "_async_failure_callback", [])
# same precondition as the sibling instance test: langfuse must be a known
# custom-logger name, otherwise the config path skips the helper entirely
# and this test would not exercise the fallback branch
known_callbacks = list(getattr(litellm, "_known_custom_logger_compatible_callbacks", []))
if "langfuse" not in known_callbacks:
known_callbacks.append("langfuse")
monkeypatch.setattr(litellm, "_known_custom_logger_compatible_callbacks", known_callbacks)
monkeypatch.setattr(
"litellm.litellm_core_utils.litellm_logging._init_custom_logger_compatible_class",
lambda callback, internal_usage_cache=None, llm_router=None: None,
)
config_content = {
"litellm_settings": {
"success_callback": ["langfuse"],
"failure_callback": ["langfuse"],
}
}
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as temp_file:
yaml.dump(config_content, temp_file)
temp_file_path = temp_file.name
try:
proxy_config = ProxyConfig()
await proxy_config.load_config(
router=None,
config_file_path=temp_file_path,
)
# init failed -> fall back to the string path, standard route keeps logging
assert "langfuse" in litellm.success_callback
assert "langfuse" in litellm.failure_callback
assert not any(isinstance(callback, LangfusePromptManagement) for callback in litellm._async_success_callback)
assert not any(isinstance(callback, LangfusePromptManagement) for callback in litellm._async_failure_callback)
finally:
os.unlink(temp_file_path)
def test_add_callbacks_from_db_config():
"""Test that callbacks are added correctly and duplicates are prevented"""
# Setup