feat(pointfive): answer the ui health check with a liveness ping

This commit is contained in:
Yinon Kahta 2026-08-27 14:12:22 +03:00
parent d3b49f62f6
commit 323dddcb43
3 changed files with 111 additions and 2 deletions

View file

@ -182,6 +182,7 @@ services = (
"arize",
"galileo",
"newrelic",
"pointfive",
"sqs",
]
| str
@ -269,6 +270,7 @@ async def health_services_endpoint(
"arize",
"galileo",
"newrelic",
"pointfive",
"sqs",
]:
raise HTTPException(
@ -293,7 +295,7 @@ async def health_services_endpoint(
service == "openmeter"
or service == "braintrust"
or service == "generic_api"
or (service_in_success_callbacks and service != "langfuse")
or (service_in_success_callbacks and service not in ("langfuse", "pointfive"))
):
_ = await litellm.acompletion(
model="openai/litellm-mock-response-model",
@ -385,6 +387,27 @@ async def health_services_endpoint(
),
}
elif service == "pointfive":
if not _is_proxy_admin(user_api_key_dict):
non_admin_detail: Final[_ServiceTestErrorDetail] = {
"error": "Only proxy admins can trigger the PointFive liveness ping."
}
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=non_admin_detail)
from litellm.integrations.pointfive import PointFiveLogger
try:
pointfive_logger: Final = PointFiveLogger(start_periodic_flush=False)
except ValueError as missing_key:
# No key configured is the answer the operator asked for, not a server error.
no_key: Final[_ServiceTestSuccessResponse] = {"status": "unhealthy", "message": str(missing_key)}
return no_key
response = await pointfive_logger.async_health_check()
pointfive_health: Final[_ServiceTestSuccessResponse] = {
"status": response["status"],
"message": (response["error_message"] if response["status"] == "unhealthy" else "PointFive is healthy")
or "PointFive is healthy",
}
return pointfive_health
if service == "webhook":
user_info: Final = CallInfo(
token=user_api_key_dict.token or "",

View file

@ -1056,6 +1056,35 @@ async def test_health_services_endpoint_galileo(status, error_message):
mock_instance.async_health_check.assert_awaited_once()
@pytest.mark.asyncio
@pytest.mark.parametrize(
"status,error_message",
[
("healthy", ""),
("unhealthy", "PointFive authentication failed"),
],
)
async def test_health_services_endpoint_pointfive(monkeypatch, status, error_message):
import litellm.integrations.pointfive as pointfive_package
mock_instance = MagicMock()
mock_instance.async_health_check = AsyncMock(return_value={"status": status, "error_message": error_message})
logger_class = MagicMock(return_value=mock_instance)
monkeypatch.setattr(pointfive_package, "PointFiveLogger", logger_class)
result = await health_services_endpoint(user_api_key_dict=_pointfive_admin(), service="pointfive")
if status == "healthy":
assert result["status"] == "healthy"
assert result["message"] == "PointFive is healthy"
else:
assert result["status"] == "unhealthy"
assert result["message"] == error_message
mock_instance.async_health_check.assert_awaited_once()
# A check that left the periodic flush running would leak a flusher per press of the ui test button.
logger_class.assert_called_once_with(start_periodic_flush=False)
@pytest.mark.asyncio
async def test_health_services_endpoint_datadog_llm_observability():
"""
@ -2928,3 +2957,60 @@ def test_test_model_connection_accepts_image_edit_mode(monkeypatch):
assert response.status_code == 200, response.text
assert response.json()["status"] == "success"
def _pointfive_admin() -> UserAPIKeyAuth:
return UserAPIKeyAuth(token="admin-token", user_id="admin-user", user_role=LitellmUserRoles.PROXY_ADMIN)
@pytest.mark.asyncio
async def test_health_services_endpoint_pointfive_without_a_key_is_unhealthy_not_a_server_error(monkeypatch):
"""
The logger refuses to start without an api key.
That refusal is the answer the operator asked for, so it has to come back as an
unhealthy result rather than a 500 from the endpoint.
"""
import litellm.integrations.pointfive as pointfive_package
def refuse(**_):
raise ValueError("pointfive logging requires an api key. Set POINTFIVE_API_KEY")
monkeypatch.setattr(pointfive_package, "PointFiveLogger", refuse)
result = await health_services_endpoint(user_api_key_dict=_pointfive_admin(), service="pointfive")
assert result["status"] == "unhealthy"
assert "requires an api key" in result["message"]
@pytest.mark.asyncio
@pytest.mark.parametrize(
"role",
[
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
LitellmUserRoles.TEAM,
LitellmUserRoles.CUSTOMER,
],
)
async def test_health_services_endpoint_pointfive_blocks_non_admin(monkeypatch, role):
"""
The ping travels on the proxy-wide PointFive credential and stamps liveness at PointFive.
A tenant key must not be able to keep an integration looking alive, or read back
account-level authentication failures through it.
"""
import litellm.integrations.pointfive as pointfive_package
from litellm.proxy._types import ProxyException
logger_class = MagicMock()
monkeypatch.setattr(pointfive_package, "PointFiveLogger", logger_class)
with pytest.raises(ProxyException) as raised:
await health_services_endpoint(
user_api_key_dict=UserAPIKeyAuth(token="t", user_id="u", user_role=role), service="pointfive"
)
assert str(raised.value.code) == "403"
logger_class.assert_not_called()

View file

@ -47944,7 +47944,7 @@ export interface operations {
parameters: {
query: {
/** @description Specify the service being hit. */
service: ("slack_budget_alerts" | "langfuse" | "langfuse_otel" | "slack" | "ms_teams" | "openmeter" | "webhook" | "email" | "braintrust" | "datadog" | "datadog_llm_observability" | "generic_api" | "arize" | "galileo" | "newrelic" | "sqs") | string;
service: ("slack_budget_alerts" | "langfuse" | "langfuse_otel" | "slack" | "ms_teams" | "openmeter" | "webhook" | "email" | "braintrust" | "datadog" | "datadog_llm_observability" | "generic_api" | "arize" | "galileo" | "newrelic" | "pointfive" | "sqs") | string;
};
header?: never;
path?: never;