From 34e1b75222b08f09f4176cc9519cea72169abef5 Mon Sep 17 00:00:00 2001 From: BinYi LIU Date: Mon, 2 Nov 2026 19:41:25 -0800 Subject: [PATCH] fix(responses): map streamed flex_unavailable errors to 429 not 500 When OpenAI has no flex capacity, a service_tier flex request fails with type resource_unavailable / code flex_unavailable. Non-streamed, this arrives as HTTP 429 and litellm raises RateLimitError. Streamed /v1/responses delivers the same error as an in-stream event, and neither field was in _ERROR_CODE_HTTP_STATUS, so it defaulted to 500 InternalServerError. The HTTP status then depended only on stream: one provider outage looked like a 429 or a 500. The 500 reads as a server error in metrics and alerting, and callers that retry on 429 do not retry it. Map both resource_unavailable and flex_unavailable to 429 so the streamed path matches the non-streamed path. Fixes #49736 --- litellm/responses/streaming_iterator.py | 2 ++ .../test_streaming_iterator_error_events.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index 6a82304b76a..a18a4269200 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -235,6 +235,8 @@ _ERROR_CODE_HTTP_STATUS: Final[Mapping[str, int]] = MappingProxyType( "server_error": 500, "rate_limit_exceeded": 429, "insufficient_quota": 429, + "resource_unavailable": 429, + "flex_unavailable": 429, "vector_store_timeout": 504, "invalid_prompt": 400, "invalid_image": 400, diff --git a/tests/unit/responses/test_streaming_iterator_error_events.py b/tests/unit/responses/test_streaming_iterator_error_events.py index 7445070bd3a..c5cb0ea63b4 100644 --- a/tests/unit/responses/test_streaming_iterator_error_events.py +++ b/tests/unit/responses/test_streaming_iterator_error_events.py @@ -75,6 +75,24 @@ def _make_error_chunk(error_type: str, code: str, message: str = "err") -> Error return ErrorEvent(type=ResponsesAPIStreamEvents.ERROR, sequence_number=0, error=error_obj) +@pytest.mark.parametrize( + "error_type,error_code", + ( + ("resource_unavailable", "flex_unavailable"), + ("resource_unavailable", None), + (None, "flex_unavailable"), + ), +) +def test_streamed_flex_unavailable_maps_to_429(error_type: str | None, error_code: str | None) -> None: + # OpenAI returns 429 for flex-capacity exhaustion on non-streamed requests; the streamed + # /v1/responses error event must map to 429 as well, not fall through to 500 (#49736). + assert _status_code_for_error_fields(error_type, error_code) == 429 + + +def test_streamed_unrelated_server_error_still_maps_to_500() -> None: + assert _status_code_for_error_fields("server_error", "server_error") == 500 + + @pytest.mark.parametrize("provider", ("openai", "azure", "bedrock")) @pytest.mark.parametrize("header", ("x-amzn-RequestId", "x-request-id", None)) @pytest.mark.parametrize(