From fb6cc67b8a6f836c9132df60774aa05662ec1f9e Mon Sep 17 00:00:00 2001 From: GavinZ Date: Mon, 26 Oct 2026 12:44:23 +0800 Subject: [PATCH 1/2] fix(proxy): map non-standard SSE error codes (>=600) to 502 for failure handling `_parse_event_data_for_error` only returned codes in the 100-599 range, so vendor-specific codes (ZAI/ZhipuAI 1302 rate limit, DashScope throttling) were silently dropped. The proxy then treated the SSE chunk as normal content: the client got a 200 with an embedded error and router fallback/cooldown never triggered. Map any integer code >= 600 to 502 so failure handling fires. Codes below 100 keep the previous None + diagnostic-warning behaviour, and the original vendor code stays in the log. The parametrized test is updated accordingly (600 and 1302 now expect 502; code 99 stays at None). Upstream moved this test file from tests/test_litellm/proxy/ to tests/unit/proxy/, so the change is applied at the new path. --- litellm/proxy/common_request_processing.py | 38 ++++++++++++++++--- .../proxy/test_common_request_processing.py | 12 ++++-- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index cec176ac559..e2be7e954af 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -907,11 +907,39 @@ async def _parse_event_data_for_error(event_line: str | bytes) -> int | None: ) # Not a valid integer string, treat as if no valid code was found for this check - # 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_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) + if error_code is not None: + if 100 <= error_code <= 599: + # Standard HTTP status code + return error_code + elif error_code >= 600: + # Vendor-specific error codes outside the standard HTTP + # range (e.g., ZAI/ZhipuAI 1302 rate limit, DashScope + # throttling codes). Previously these were silently + # dropped (returned None), causing the proxy to treat + # the SSE chunk as normal content — the error was + # forwarded to the client as a 200 response, and router + # fallback/cooldown never triggered. + # + # Map to 502 (Bad Gateway) so that failure handling + # (retry, fallback, cooldown) fires correctly. The + # original vendor code is preserved in the warning log + # for debugging. + verbose_proxy_logger.warning( + f"SSE error code {error_code} is outside standard HTTP range " + f"(100-599); mapping to 502 for failure handling" + ) + return 502 + else: + # Integer code below 100 (or negative) — likely corrupt data + # but keep the original behaviour of returning None while + # emitting the diagnostic warning (preserved from the + # original elif error_code_raw is not None: branch). + verbose_proxy_logger.warning(f"Error has invalid or non-convertible code: {error_code}") + elif error_code_raw is not None: + # error_code could not be coerced to int from the raw value + # (e.g., a non-numeric string). Log the original raw value + # for debugging and fall through to return None. + verbose_proxy_logger.warning(f"Error has invalid or non-convertible code: {error_code_raw}") except (orjson.JSONDecodeError, json.JSONDecodeError): # not a known error chunk pass diff --git a/tests/unit/proxy/test_common_request_processing.py b/tests/unit/proxy/test_common_request_processing.py index 82dd3479321..24cdcd4042a 100644 --- a/tests/unit/proxy/test_common_request_processing.py +++ b/tests/unit/proxy/test_common_request_processing.py @@ -2108,11 +2108,15 @@ class TestCommonRequestProcessingHelpers: ( 'data: {"error": {"code": 99, "message": "too low"}}', None, - ), # Integer code too low + ), # Integer code too low (< 100, likely invalid data) ( - 'data: {"error": {"code": 600, "message": "too high"}}', - None, - ), # Integer code too high + 'data: {"error": {"code": 600, "message": "vendor error"}}', + 502, + ), # Non-standard code >= 600 → mapped to 502 for failure handling + ( + 'data: {"error": {"code": 1302, "message": "rate limit"}}', + 502, + ), # Vendor-specific rate limit code (ZAI/ZhipuAI) → mapped to 502 ( 'data: {"id": "123", "content": "hello"}', None, From 57f7564f84bc65d70d5cc2960e29d9be323435a7 Mon Sep 17 00:00:00 2001 From: GavinZ Date: Mon, 26 Oct 2026 15:12:32 +0800 Subject: [PATCH 2/2] fix(proxy): use %-style lazy logging args in SSE error mapping tests/unit/test_logging.py::test_logging_calls_do_not_build_their_message_eagerly flags f-string logging calls in litellm/proxy/common_request_processing.py (lines 927/937/942). Pass the values as %s arguments instead so the message is only built when the log record is emitted. --- litellm/proxy/common_request_processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index e2be7e954af..87c777b1c4d 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -925,8 +925,8 @@ async def _parse_event_data_for_error(event_line: str | bytes) -> int | None: # original vendor code is preserved in the warning log # for debugging. verbose_proxy_logger.warning( - f"SSE error code {error_code} is outside standard HTTP range " - f"(100-599); mapping to 502 for failure handling" + "SSE error code %s is outside standard HTTP range (100-599); mapping to 502 for failure handling", + error_code, ) return 502 else: @@ -934,12 +934,12 @@ async def _parse_event_data_for_error(event_line: str | bytes) -> int | None: # but keep the original behaviour of returning None while # emitting the diagnostic warning (preserved from the # original elif error_code_raw is not None: branch). - verbose_proxy_logger.warning(f"Error has invalid or non-convertible code: {error_code}") + verbose_proxy_logger.warning("Error has invalid or non-convertible code: %s", error_code) elif error_code_raw is not None: # error_code could not be coerced to int from the raw value # (e.g., a non-numeric string). Log the original raw value # for debugging and fall through to return None. - verbose_proxy_logger.warning(f"Error has invalid or non-convertible code: {error_code_raw}") + verbose_proxy_logger.warning("Error has invalid or non-convertible code: %s", error_code_raw) except (orjson.JSONDecodeError, json.JSONDecodeError): # not a known error chunk pass