diff --git a/litellm/exceptions.py b/litellm/exceptions.py index aea2c7c0c54..ce8bb6fa182 100644 --- a/litellm/exceptions.py +++ b/litellm/exceptions.py @@ -145,12 +145,15 @@ class BadRequestError(openai.BadRequestError): # type: ignore if ( response is not None and isinstance(response, httpx.Response) - and hasattr(response, "request") - and response.request is not None ): - self.response = response - else: - self.response = _get_minimal_error_response() + # Check if response has a request attribute safely + # httpx.Response.request is a property that raises RuntimeError if _request is None + # We check the private _request attribute to avoid triggering the property getter + if hasattr(response, "_request") and getattr(response, "_request", None) is not None: + self.response = response + else: + # Response doesn't have a valid request, use minimal error response + self.response = _get_minimal_error_response() super().__init__( self.message, response=self.response, body=body ) # Call the base class constructor with the parameters it needs diff --git a/tests/local_testing/test_exceptions.py b/tests/local_testing/test_exceptions.py index 987c213d5ca..4cc2723ace8 100644 --- a/tests/local_testing/test_exceptions.py +++ b/tests/local_testing/test_exceptions.py @@ -1347,6 +1347,49 @@ def test_context_window_exceeded_error_from_litellm_proxy(): extract_and_raise_litellm_exception(**args) +def test_bad_request_error_with_response_without_request(): + """ + Test that BadRequestError handles Response objects without a request attribute. + + This simulates a real scenario where a Response is created without a request + (e.g., in tests or when manually creating error responses), and we need to + ensure it doesn't raise RuntimeError when the exception is created. + """ + from httpx import Response + from litellm.litellm_core_utils.exception_mapping_utils import ( + extract_and_raise_litellm_exception, + ) + + # Create a Response without a request (simulates the scenario that was failing) + response_without_request = Response(status_code=400, text="Bad Request") + + + # Test that extract_and_raise_litellm_exception can handle this + args = { + "response": response_without_request, + "error_str": "Error code: 400 - {'error': {'message': 'litellm.BadRequestError: Invalid request parameters', 'type': None, 'param': None, 'code': '400'}}", + "model": "gpt-3.5-turbo", + "custom_llm_provider": "openai", + } + + # This should raise BadRequestError without RuntimeError + with pytest.raises(litellm.BadRequestError) as exc_info: + extract_and_raise_litellm_exception(**args) + + # Verify the exception was created successfully + error = exc_info.value + assert error is not None + assert error.model == "gpt-3.5-turbo" + assert error.llm_provider == "openai" + + # Verify the exception has a response (should be minimal error response) + assert error.response is not None + # The response should have a request (minimal error response has one) + assert getattr(error.response, "_request", None) is not None + # Should be able to access request property without RuntimeError + assert error.response.request is not None + + @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.parametrize("stream_mode", [True, False]) @pytest.mark.parametrize("model", ["gpt-4.1-nano"]) # "gpt-4o-mini",