Skip to content
53 changes: 53 additions & 0 deletions litellm/integrations/galileo.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_async_httpx_client,
httpxSpecialProvider,
)
from litellm.types.integrations.base_health_check import IntegrationHealthCheckStatus

GALILEO_CLOUD_API_BASE_URL = "https://api.galileo.ai"
# Cap the in-memory buffer so persistent flush failures (e.g. Galileo
Expand Down Expand Up @@ -89,6 +90,58 @@ def _is_configured(self) -> bool:
return bool(self.api_key)
return bool(self.username and self.password)

async def async_health_check(self) -> IntegrationHealthCheckStatus:
try:
if not self.project_id:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message="GALILEO_PROJECT_ID environment variable not set",
)

if not self.base_url:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message="GALILEO_BASE_URL environment variable not set",
)

if self.use_v2_api:
if not self.api_key:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message="GALILEO_API_KEY environment variable not set",
)
elif not self.username or not self.password:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message=(
"GALILEO_USERNAME and GALILEO_PASSWORD environment "
"variables must be set"
),
)

if not await self._ensure_headers():
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message="Galileo authentication failed",
)

response = await self.async_httpx_handler.get(
url=f"{self.base_url}/current_user",
headers=self.headers,
)
if response.status_code >= 400:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message=(f"Galileo API returned HTTP {response.status_code}"),
)

return IntegrationHealthCheckStatus(status="healthy", error_message=None)
except Exception as e:
return IntegrationHealthCheckStatus(
status="unhealthy",
error_message=f"Galileo health check failed: {str(e)}",
)

async def async_set_galileo_headers(self) -> None:
galileo_login_response = await self.async_httpx_handler.post(
url=f"{self.base_url}/login",
Expand Down
15 changes: 15 additions & 0 deletions litellm/proxy/health_endpoints/_health_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def get_callback_identifier(callback):
"datadog_llm_observability",
"generic_api",
"arize",
"galileo",
"sqs",
],
str,
Expand Down Expand Up @@ -206,6 +207,7 @@ async def health_services_endpoint( # noqa: PLR0915
"datadog_llm_observability",
"generic_api",
"arize",
"galileo",
"sqs",
]:
raise HTTPException(
Expand Down Expand Up @@ -295,6 +297,19 @@ async def health_services_endpoint( # noqa: PLR0915
else "Arize is healthy"
),
}
elif service == "galileo":
from litellm.integrations.galileo import GalileoObserve

galileo_logger = GalileoObserve()
response = await galileo_logger.async_health_check()
Comment thread
Sameerlite marked this conversation as resolved.
Resolved
return {
"status": response["status"],
"message": (
response["error_message"]
if response["status"] == "unhealthy"
else "Galileo is healthy"
),
}
elif service == "langfuse":
from litellm.integrations.langfuse.langfuse import LangFuseLogger

Expand Down
39 changes: 39 additions & 0 deletions tests/test_litellm/integrations/test_galileo.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,45 @@ def test_galileo_get_ingest_request_legacy(monkeypatch):
assert payload["traces"][0]["input"] == "hi"


@pytest.mark.asyncio
async def test_galileo_async_health_check_success(galileo_v2_env):
logger = GalileoObserve()
current_user_resp = MagicMock()
current_user_resp.status_code = 200

with patch.object(
logger.async_httpx_handler, "get", new_callable=AsyncMock
) as mock_get:
mock_get.return_value = current_user_resp
result = await logger.async_health_check()

assert result["status"] == "healthy"
mock_get.assert_awaited_once_with(
url="https://api.galileo.ai/current_user",
headers={
"accept": "application/json",
"Content-Type": "application/json",
"Galileo-API-Key": "test-api-key",
},
)


@pytest.mark.asyncio
async def test_galileo_async_health_check_api_error(galileo_v2_env):
logger = GalileoObserve()
current_user_resp = MagicMock()
current_user_resp.status_code = 401

with patch.object(
logger.async_httpx_handler, "get", new_callable=AsyncMock
) as mock_get:
mock_get.return_value = current_user_resp
result = await logger.async_health_check()

assert result["status"] == "unhealthy"
assert "HTTP 401" in result["error_message"]


@pytest.mark.asyncio
async def test_galileo_ensure_headers_v2_missing_key(monkeypatch):
monkeypatch.delenv("GALILEO_API_KEY", raising=False)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,33 @@ def mock_reject_os_environ(params):
assert model_params.get("api_key") == "fake-key-A"


@pytest.mark.asyncio
@pytest.mark.parametrize(
"status,error_message",
[
("healthy", ""),
("unhealthy", "Galileo authentication failed"),
],
)
async def test_health_services_endpoint_galileo(status, error_message):
with patch("litellm.integrations.galileo.GalileoObserve") as MockGalileoObserve:
mock_instance = MagicMock()
mock_instance.async_health_check = AsyncMock(
return_value={"status": status, "error_message": error_message}
)
MockGalileoObserve.return_value = mock_instance

result = await health_services_endpoint(service="galileo")

if status == "healthy":
assert result["status"] == "healthy"
assert result["message"] == "Galileo is healthy"
else:
assert result["status"] == "unhealthy"
assert result["message"] == error_message
mock_instance.async_health_check.assert_awaited_once()


@pytest.mark.asyncio
async def test_health_services_endpoint_datadog_llm_observability():
"""
Expand Down
14 changes: 7 additions & 7 deletions ui/litellm-dashboard/src/lib/http/schema.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading