fix(observability): count only the status this proxy sheds load with

`_SHED_STATUSES` carried 503 alongside 429, but nothing ever marks a 503 as
shed by this proxy, so the entry was unreachable and read as though those
responses were counted.

Leaving it and marking the 503 paths would be wrong. Both of them are
fail-closed budget rejections, raised when spend cannot be verified against
Redis or the database. That is a dependency being unreachable, not this pod
running out of capacity, and the two call for opposite responses. Folding them
in would rebuild exactly the conflation that dropping provider 429s removed.

The status label stays, so a future rejection kind can be added deliberately
along with whatever marks it.
This commit is contained in:
Yucheng Zhu 2026-08-19 13:33:42 -07:00
parent d063d14371
commit 7c8c07532f
2 changed files with 10 additions and 7 deletions

View file

@ -83,10 +83,12 @@ class InFlightRequestsMiddleware:
return InFlightRequestsMiddleware._gauge
# Statuses the proxy uses when it declines to serve. A response only counts once
# the request is also marked as shed by this proxy, since litellm forwards
# upstream 429s with the same status.
_SHED_STATUSES: Final = frozenset({429, 503})
# The status this proxy uses when it declines load, and only counted once the
# request is also marked as shed here, since litellm forwards upstream 429s with
# the same status. The proxy's 503s are fail-closed budget rejections, a
# dependency being unreachable rather than this pod at capacity, so counting
# them would blur the throttle-vs-scale signal the metric exists to give.
_SHED_STATUSES: Final = frozenset({429})
def _record_shed_response(status: int) -> None:

View file

@ -98,10 +98,11 @@ def test_non_http_scopes_not_counted():
@pytest.mark.asyncio
@pytest.mark.parametrize("status, expected_calls", [(429, 1), (503, 1), (200, 0), (400, 0), (500, 0)])
@pytest.mark.parametrize("status, expected_calls", [(429, 1), (503, 0), (200, 0), (400, 0), (500, 0)])
async def test_only_shed_responses_the_proxy_itself_produced_are_counted(status, expected_calls):
"""A 500 is the proxy failing, not declining. Counting it would blur the
signal an operator uses to decide between throttling and scaling out."""
"""A 500 is the proxy failing, not declining, and the proxy's 503s are
fail-closed budget rejections raised when a dependency is unreachable.
Counting either would blur the throttle-vs-scale signal."""
from unittest.mock import MagicMock, patch
from litellm.proxy.common_utils.request_pressure_metrics import mark_request_shed_by_proxy