Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions litellm/proxy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,14 @@ def _enrich_http_exception_with_guardrail_context(exc: BaseException, callback:
detail.setdefault("guardrail_mode", event_hook)


def _is_client_error_exception(exc: Exception) -> bool:
if isinstance(exc, HTTPException):
return exc.status_code < 500
if isinstance(exc, ProxyException):
return not (exc.code.isdigit() and int(exc.code) >= 500)
return False


def _exception_changes_request_flow(exc: BaseException) -> bool:
"""
True for guardrail exceptions the proxy turns into an alternate request flow
Expand Down Expand Up @@ -2885,9 +2893,7 @@ async def post_call_failure_hook(

### ALERTING ###
await self.update_request_status(litellm_call_id=request_data.get("litellm_call_id", ""), status="fail")
if AlertType.llm_exceptions in self.alert_types and not isinstance(
original_exception, (HTTPException, ProxyException)
):
if AlertType.llm_exceptions in self.alert_types and not _is_client_error_exception(original_exception):
"""
Just alert on LLM API exceptions. Do not alert on user errors

Expand Down
50 changes: 41 additions & 9 deletions tests/test_litellm/proxy/test_proxy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from litellm.types.guardrails import GuardrailEventHooks


from unittest.mock import MagicMock, patch
from unittest.mock import AsyncMock, MagicMock, patch

from litellm.proxy.utils import get_custom_url, join_paths

Expand Down Expand Up @@ -1303,12 +1303,10 @@ class TestPostCallFailureHookLLMExceptionAlerting:
"""The llm_exceptions alert is for infra / LLM-API failures, not user
errors (https://github.com/BerriAI/litellm/issues/3395). Already-normalized
client errors must be excluded so a guardrail content-policy block never
pages on-call. ProxyException is such an error; before LIT-3751 only
HTTPException was excluded, so AIM blocks paged as if the LLM API failed."""
pages on-call. 5xx proxy errors still alert."""
Comment thread
greptile-apps[bot] marked this conversation as resolved.

async def _alerted(self, exc) -> bool:
async def _alerted(self, exc: Exception) -> AsyncMock:
import asyncio
from unittest.mock import AsyncMock

from litellm.proxy._types import AlertType, UserAPIKeyAuth

Expand All @@ -1325,7 +1323,7 @@ async def _alerted(self, exc) -> bool:
user_api_key_dict=UserAPIKeyAuth(),
)
await asyncio.sleep(0) # let the fire-and-forget alert task run
return alerting_handler.called
return alerting_handler

@pytest.mark.asyncio
async def test_proxy_exception_does_not_alert(self):
Expand All @@ -1338,15 +1336,49 @@ async def test_proxy_exception_does_not_alert(self):
code=400,
openai_code="content_policy_violation",
)
assert await self._alerted(exc) is False
assert (await self._alerted(exc)).called is False

@pytest.mark.asyncio
async def test_http_exception_does_not_alert(self):
assert await self._alerted(HTTPException(status_code=400, detail="blocked")) is False
assert (await self._alerted(HTTPException(status_code=400, detail="blocked"))).called is False

@pytest.mark.asyncio
async def test_genuine_llm_api_error_still_alerts(self):
assert await self._alerted(Exception("upstream 503")) is True
assert (await self._alerted(Exception("upstream 503"))).called is True

@pytest.mark.asyncio
async def test_http_exception_5xx_alerts(self):
alerting_handler = await self._alerted(
HTTPException(
status_code=502,
detail={
"error": "Headroom compression service returned an error",
"status_code": 503,
"guardrail_name": "headroom-compression-global",
},
)
)
assert alerting_handler.called is True
assert "headroom-compression-global" in alerting_handler.call_args.kwargs["message"]

@pytest.mark.asyncio
async def test_proxy_exception_5xx_alerts(self):
from litellm.proxy._types import ProxyException

alerting_handler = await self._alerted(
ProxyException(
message="guardrail backend down",
type="internal_server_error",
param=None,
code=503,
)
)
assert alerting_handler.called is True

@pytest.mark.asyncio
async def test_http_exception_429_does_not_alert(self):
alerting_handler = await self._alerted(HTTPException(status_code=429, detail="rate limited"))
assert alerting_handler.called is False


class TestPostCallFailureHookProxyExceptionLogging:
Expand Down
Loading