diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 7e1989b0aab..cbcd13d2ba3 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -754,6 +754,12 @@ async def _parse_event_data_for_error(event_line: str | bytes) -> int | None: # Ensure error_code is a valid HTTP status code if error_code is not None and 100 <= error_code <= 599: return error_code + elif error_code is not None: + verbose_proxy_logger.warning( + "Error code is outside the HTTP status range; mapping to 502: %s", + error_code, + ) + return status.HTTP_502_BAD_GATEWAY elif error_code_raw is not None: # Log if original code was present but not valid verbose_proxy_logger.warning("Error has invalid or non-convertible code: %s", error_code_raw) except (orjson.JSONDecodeError, json.JSONDecodeError): diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 7b74e69685c..f2bf327b841 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -2066,11 +2066,11 @@ class TestCommonRequestProcessingHelpers: ), # Invalid string code ( 'data: {"error": {"code": 99, "message": "too low"}}', - None, + 502, ), # Integer code too low ( 'data: {"error": {"code": 600, "message": "too high"}}', - None, + 502, ), # Integer code too high ( 'data: {"id": "123", "content": "hello"}', diff --git a/tests/test_litellm/test_sse_vendor_error_codes.py b/tests/test_litellm/test_sse_vendor_error_codes.py new file mode 100644 index 00000000000..0e9985c4419 --- /dev/null +++ b/tests/test_litellm/test_sse_vendor_error_codes.py @@ -0,0 +1,38 @@ +import pytest + +from litellm.proxy.common_request_processing import _parse_event_data_for_error + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("code", "expected"), + [ + (0, 502), + (99, 502), + (100, 100), + (400, 400), + (599, 599), + (600, 502), + (1302, 502), + ("4001", 502), + ], +) +async def test_parse_event_maps_numeric_vendor_error_codes(code, expected): + event = f'data: {{"error": {{"code": {code!r}, "message": "provider error"}}}}' + if isinstance(code, str): + event = event.replace("'", '"') + + assert await _parse_event_data_for_error(event) == expected + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "event", + [ + 'data: {"error": {"code": "not-a-number"}}', + 'data: {"error": {"message": "missing code"}}', + "data: [DONE]", + ], +) +async def test_parse_event_ignores_missing_or_non_numeric_error_codes(event): + assert await _parse_event_data_for_error(event) is None