mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
Fix health endpoints
This commit is contained in:
parent
66c7233f61
commit
b7c45991d8
2 changed files with 177 additions and 1 deletions
|
|
@ -32,6 +32,7 @@ from litellm.proxy.health_check import (
|
|||
run_with_timeout,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret
|
||||
from litellm.litellm_core_utils.custom_logger_registry import CustomLoggerRegistry
|
||||
|
||||
#### Health ENDPOINTS ####
|
||||
|
||||
|
|
@ -106,6 +107,35 @@ def _resolve_os_environ_variables(params: dict) -> dict:
|
|||
return resolved_root
|
||||
|
||||
|
||||
def get_callback_identifier(callback):
|
||||
"""
|
||||
Get the callback identifier string, handling both strings and objects.
|
||||
|
||||
This function extracts a string identifier from a callback, which can be:
|
||||
- A string (returned as-is)
|
||||
- An object with a callback_name attribute
|
||||
- An object registered in CustomLoggerRegistry
|
||||
- Falls back to callback_name() helper function
|
||||
|
||||
Args:
|
||||
callback: The callback to identify (can be str or object)
|
||||
|
||||
Returns:
|
||||
str: The callback identifier string
|
||||
"""
|
||||
if isinstance(callback, str):
|
||||
return callback
|
||||
if hasattr(callback, 'callback_name') and callback.callback_name:
|
||||
return callback.callback_name
|
||||
if hasattr(callback, '__class__'):
|
||||
callback_strs = CustomLoggerRegistry.get_all_callback_strs_from_class_type(callback.__class__)
|
||||
if hasattr(callback, 'callback_name') and callback.callback_name in callback_strs:
|
||||
return callback.callback_name
|
||||
if callback_strs:
|
||||
return callback_strs[0]
|
||||
return callback_name(callback)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
services = Union[
|
||||
Literal[
|
||||
|
|
@ -203,11 +233,24 @@ async def health_services_endpoint( # noqa: PLR0915
|
|||
},
|
||||
)
|
||||
|
||||
service_in_success_callbacks = False
|
||||
if service in litellm.success_callback:
|
||||
service_in_success_callbacks = True
|
||||
else:
|
||||
for cb in litellm.success_callback:
|
||||
if hasattr(cb, 'callback_name') and cb.callback_name == service:
|
||||
service_in_success_callbacks = True
|
||||
break
|
||||
cb_id = get_callback_identifier(cb)
|
||||
if cb_id == service:
|
||||
service_in_success_callbacks = True
|
||||
break
|
||||
|
||||
if (
|
||||
service == "openmeter"
|
||||
or service == "braintrust"
|
||||
or service == "generic_api"
|
||||
or (service in litellm.success_callback and service != "langfuse")
|
||||
or (service_in_success_callbacks and service != "langfuse")
|
||||
):
|
||||
_ = await litellm.acompletion(
|
||||
model="openai/litellm-mock-response-model",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from prisma.errors import ClientNotConnectedError, HTTPClientClosedError, Prisma
|
|||
from litellm.proxy.health_endpoints._health_endpoints import (
|
||||
_db_health_readiness_check,
|
||||
db_health_cache,
|
||||
get_callback_identifier,
|
||||
health_license_endpoint,
|
||||
health_services_endpoint,
|
||||
)
|
||||
|
|
@ -478,3 +479,135 @@ def test_health_readiness(proxy_client):
|
|||
f"Unexpected db status: {db_status}"
|
||||
|
||||
print("="*60 + "\n")
|
||||
|
||||
|
||||
def test_get_callback_identifier_string_and_object_with_callback_name():
|
||||
"""
|
||||
Test get_callback_identifier with string callbacks and objects with callback_name attribute.
|
||||
|
||||
Covers:
|
||||
- String callback (returned as-is)
|
||||
- Object with callback_name attribute
|
||||
- Object with empty/None callback_name (should fall through to other checks)
|
||||
"""
|
||||
from litellm.proxy.health_endpoints._health_endpoints import get_callback_identifier
|
||||
|
||||
# Test 1: String callback should be returned as-is
|
||||
assert get_callback_identifier("datadog") == "datadog"
|
||||
assert get_callback_identifier("langfuse") == "langfuse"
|
||||
|
||||
# Test 2: Object with callback_name attribute
|
||||
class MockCallbackWithName:
|
||||
def __init__(self, name):
|
||||
self.callback_name = name
|
||||
|
||||
callback_obj = MockCallbackWithName("custom_callback")
|
||||
assert get_callback_identifier(callback_obj) == "custom_callback"
|
||||
|
||||
# Test 3: Object with empty callback_name should fall through
|
||||
callback_obj_empty = MockCallbackWithName("")
|
||||
# This should fall through to CustomLoggerRegistry or callback_name() fallback
|
||||
# We'll verify it doesn't return empty string
|
||||
result = get_callback_identifier(callback_obj_empty)
|
||||
assert result != "" # Should not return empty string
|
||||
assert isinstance(result, str) # Should still return a string
|
||||
|
||||
|
||||
def test_get_callback_identifier_custom_logger_registry_and_fallback():
|
||||
"""
|
||||
Test get_callback_identifier with CustomLoggerRegistry lookup and fallback scenarios.
|
||||
|
||||
Covers:
|
||||
- Object registered in CustomLoggerRegistry
|
||||
- Object with callback_name that matches registry entry
|
||||
- Fallback to callback_name() helper function
|
||||
"""
|
||||
from litellm.proxy.health_endpoints._health_endpoints import get_callback_identifier
|
||||
from litellm.litellm_core_utils.custom_logger_registry import CustomLoggerRegistry
|
||||
|
||||
# Test 1: Object registered in CustomLoggerRegistry (without callback_name attribute)
|
||||
# Mock a class that's registered in the registry
|
||||
class MockRegisteredLogger:
|
||||
pass
|
||||
|
||||
# Mock the registry to return callback strings for our mock class
|
||||
with patch.object(
|
||||
CustomLoggerRegistry,
|
||||
'get_all_callback_strs_from_class_type',
|
||||
return_value=['mock_logger']
|
||||
):
|
||||
mock_instance = MockRegisteredLogger()
|
||||
result = get_callback_identifier(mock_instance)
|
||||
assert result == "mock_logger"
|
||||
|
||||
# Test 2: Object with callback_name that matches registry entry
|
||||
class MockCallbackWithMatchingName:
|
||||
def __init__(self):
|
||||
self.callback_name = "matched_name"
|
||||
|
||||
callback_with_matching = MockCallbackWithMatchingName()
|
||||
# Mock registry to return list containing the matching name
|
||||
with patch.object(
|
||||
CustomLoggerRegistry,
|
||||
'get_all_callback_strs_from_class_type',
|
||||
return_value=['matched_name', 'other_name']
|
||||
):
|
||||
result = get_callback_identifier(callback_with_matching)
|
||||
assert result == "matched_name"
|
||||
|
||||
# Test 3: Object with falsy callback_name (empty string), should use registry
|
||||
class MockCallbackWithEmptyName:
|
||||
def __init__(self):
|
||||
self.callback_name = "" # Empty string is falsy
|
||||
|
||||
callback_empty = MockCallbackWithEmptyName()
|
||||
# Mock registry to return list - should use first registry entry since callback_name is falsy
|
||||
with patch.object(
|
||||
CustomLoggerRegistry,
|
||||
'get_all_callback_strs_from_class_type',
|
||||
return_value=['registry_name']
|
||||
):
|
||||
result = get_callback_identifier(callback_empty)
|
||||
assert result == "registry_name"
|
||||
|
||||
# Test 3b: Object with truthy callback_name not in registry - returns callback_name immediately
|
||||
# (This tests that truthy callback_name takes precedence over registry)
|
||||
class MockCallbackWithNonMatchingName:
|
||||
def __init__(self):
|
||||
self.callback_name = "non_matching"
|
||||
|
||||
callback_non_matching = MockCallbackWithNonMatchingName()
|
||||
# Even if registry has different values, truthy callback_name is returned first
|
||||
with patch.object(
|
||||
CustomLoggerRegistry,
|
||||
'get_all_callback_strs_from_class_type',
|
||||
return_value=['registry_name']
|
||||
):
|
||||
result = get_callback_identifier(callback_non_matching)
|
||||
# Should return callback_name because it's truthy (checked before registry)
|
||||
assert result == "non_matching"
|
||||
|
||||
# Test 4: Object not in registry, falls back to callback_name() helper
|
||||
class UnregisteredCallback:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
unregistered = UnregisteredCallback()
|
||||
# Mock registry to return empty list (not registered)
|
||||
with patch.object(
|
||||
CustomLoggerRegistry,
|
||||
'get_all_callback_strs_from_class_type',
|
||||
return_value=[]
|
||||
):
|
||||
result = get_callback_identifier(unregistered)
|
||||
# Should fall back to callback_name() which returns __class__.__name__
|
||||
assert result == "UnregisteredCallback"
|
||||
|
||||
# Test 5: Function callback (not a class instance)
|
||||
def my_callback_function():
|
||||
pass
|
||||
|
||||
# Function won't have __class__, so it will skip registry check and go to callback_name()
|
||||
result = get_callback_identifier(my_callback_function)
|
||||
# Should fall back to callback_name() which returns __name__
|
||||
assert result == "my_callback_function"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue