From ddac683ec658df2c9e5403aebf4d2191409762d6 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:28:58 -0700 Subject: [PATCH 1/2] fix(exceptions): keep internal_server_error as the public type of an upstream 500 PR #40243 started carrying the upstream error body on InternalServerError so the Responses response.failed event can report the provider's code and message, and openai's APIError.__init__ took the body's type along with it. The proxy then answered an OpenAI-compatible upstream 500 with type server_error while a 502 and a 503 kept internal_server_error, and the integration contract in test_observed_routing.py went red. Pin the type the way RateLimitError pins throttling_error, keeping the body. --- litellm/exceptions.py | 1 + .../test_exception_mapping_utils.py | 8 ++++++-- .../common_utils/test_openai_error_payload.py | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/litellm/exceptions.py b/litellm/exceptions.py index de9f5c692a1..14cc16452f0 100644 --- a/litellm/exceptions.py +++ b/litellm/exceptions.py @@ -787,6 +787,7 @@ class InternalServerError(openai.InternalServerError): super().__init__( self.message, response=self.response, body=body ) # Call the base class constructor with the parameters it needs + self.type = "internal_server_error" def __str__(self): _message = self.message diff --git a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py index 0970526956e..cfe7470fa76 100644 --- a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py @@ -1438,9 +1438,12 @@ def test_openai_compatible_vendor_400_keeps_body_but_not_headers(): @pytest.mark.parametrize( - ("status_code", "mapped_class"), [(429, litellm.RateLimitError), (500, litellm.InternalServerError)] + ("status_code", "mapped_class", "reported_type"), + [(429, litellm.RateLimitError, "throttling_error"), (500, litellm.InternalServerError, "internal_server_error")], ) -def test_openai_429_and_500_keep_body(status_code: int, mapped_class: type[openai.APIError]): +def test_openai_429_and_500_keep_body_but_report_litellm_type( + status_code: int, mapped_class: type[openai.APIError], reported_type: str +): with pytest.raises(mapped_class) as exc_info: exception_type( model="gpt-5.4-mini", @@ -1458,6 +1461,7 @@ def test_openai_429_and_500_keep_body(status_code: int, mapped_class: type[opena "code": str(status_code), "message": "upstream cannot complete this response", } + assert exc_info.value.type == reported_type def test_litellm_proxy_repeated_response_header_keeps_each_value(): diff --git a/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py b/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py index c09b8742b50..05bbe33d726 100644 --- a/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py +++ b/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py @@ -147,6 +147,22 @@ def test_a_status_carried_by_an_exception_drives_the_type_it_reports(): assert openai_error_type(exc, error_status_code(exc, 400)) == "permission_error" +def test_an_upstream_5xx_body_does_not_relabel_the_internal_server_error(): + """The upstream body rides along on the exception for the Responses ``response.failed`` + event, but a 500 keeps answering the proxy's own ``internal_server_error`` label.""" + from litellm.exceptions import InternalServerError + + carried = InternalServerError( + message="Controlled provider failure", + model="gpt-5.4-mini", + llm_provider="openai", + body={"message": "Controlled provider failure", "type": "server_error", "code": "500"}, + ) + + assert carried.body == {"message": "Controlled provider failure", "type": "server_error", "code": "500"} + assert openai_error_type(carried, error_status_code(carried, 400)) == "internal_server_error" + + def test_a_stringified_none_type_or_param_is_treated_as_absent(): from litellm.exceptions import BadRequestError From 5f6ffdc33324a9fe78c390cf7c47281a11ffb7fd Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:48:42 -0700 Subject: [PATCH 2/2] test: drop the docstring that restated the payload test's name --- .../proxy/common_utils/test_openai_error_payload.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py b/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py index 05bbe33d726..40bb84ff538 100644 --- a/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py +++ b/tests/test_litellm/proxy/common_utils/test_openai_error_payload.py @@ -148,8 +148,6 @@ def test_a_status_carried_by_an_exception_drives_the_type_it_reports(): def test_an_upstream_5xx_body_does_not_relabel_the_internal_server_error(): - """The upstream body rides along on the exception for the Responses ``response.failed`` - event, but a 500 keeps answering the proxy's own ``internal_server_error`` label.""" from litellm.exceptions import InternalServerError carried = InternalServerError(