From e20ff3436e8246e8428d7882b325f8f04179b639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Fri, 10 Mar 2023 14:13:08 +0100 Subject: [PATCH 01/17] WIP --- openai/__init__.py | 4 +-- openai/api_requestor.py | 31 ++++++++++++++++ openai/api_resources/abstract/api_resource.py | 1 + openai/api_resources/image.py | 36 +++++++++++++------ openai/openai_response.py | 7 ++++ 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/openai/__init__.py b/openai/__init__.py index 879fe33d04..c801e8bac8 100644 --- a/openai/__init__.py +++ b/openai/__init__.py @@ -34,9 +34,9 @@ organization = os.environ.get("OPENAI_ORGANIZATION") api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") api_type = os.environ.get("OPENAI_API_TYPE", "open_ai") -api_version = ( +api_version = os.environ.get("OPENAI_API_VERSION", ( "2022-12-01" if api_type in ("azure", "azure_ad", "azuread") else None -) +)) verify_ssl_certs = True # No effect. Certificates are always verified. proxy = None app_info = None diff --git a/openai/api_requestor.py b/openai/api_requestor.py index 04b65fdcdb..b218334501 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -1,5 +1,6 @@ import asyncio import json +import time import platform import sys import threading @@ -144,6 +145,36 @@ def format_app_info(cls, info): if info["url"]: str += " (%s)" % (info["url"],) return str + + def poll( + self, + method, + url, + until, + params = None, + headers = None, + interval = None + ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + response, b, api_key = self.request(method, url, params, headers) + while not until(response): + time.sleep(interval or response.retry_after or 1) + response, b, api_key = self.request(method, url) + return response, b, api_key + + async def apoll( + self, + method, + url, + until, + params = None, + headers = None, + interval = None + ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + response, b, api_key = await self.arequest(method, url, params, headers) + while not until(response): + await asyncio.sleep(interval or response.retry_after or 1) + response, b, api_key = await self.arequest(method, url) + return response, b, api_key @overload def request( diff --git a/openai/api_resources/abstract/api_resource.py b/openai/api_resources/abstract/api_resource.py index 53a7dec799..70b46e7f84 100644 --- a/openai/api_resources/abstract/api_resource.py +++ b/openai/api_resources/abstract/api_resource.py @@ -10,6 +10,7 @@ class APIResource(OpenAIObject): api_prefix = "" azure_api_prefix = "openai" + azure_dalle_prefix = "dalle" azure_deployments_prefix = "deployments" @classmethod diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 39a5b6f616..1bde2880c0 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -4,14 +4,22 @@ import openai from openai import api_requestor, util from openai.api_resources.abstract import APIResource +from openai.error import APIError class Image(APIResource): OBJECT_NAME = "images" @classmethod - def _get_url(cls, action): - return cls.class_url() + f"/{action}" + def _get_url(cls, openai_action, azure_action, api_type, api_version): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + return f"/{cls.azure_dalle_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" + else: + return cls.class_url() + f"/{openai_action}" + + @classmethod + def _get_azure_operations_url(cls, operation_id, api_version): + return "/%s/operations/%s?api-version=%s" % (cls.azure_dalle_prefix, operation_id, api_version) @classmethod def create( @@ -31,12 +39,16 @@ def create( organization=organization, ) - _, api_version = cls._get_api_type_and_version(api_type, api_version) + api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = requestor.request( - "post", cls._get_url("generations"), params + "post", cls._get_url("generations", "generate", api_type=api_type, api_version=api_version), params ) + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + url = cls._get_azure_operations_url(response.data['id'], api_version) + response, _, api_key = requestor.poll("get", url, until=lambda response: response.data["status"] not in ["NotStarted", "Running"]) + return util.convert_to_openai_object( response, api_key, api_version, organization ) @@ -60,12 +72,16 @@ async def acreate( organization=organization, ) - _, api_version = cls._get_api_type_and_version(api_type, api_version) + api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = await requestor.arequest( - "post", cls._get_url("generations"), params + "post", cls._get_url("generations", "generate", api_type=api_type, api_version=api_version), params ) + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + url = cls._get_azure_operations_url(response.data['id'], api_version) + response, _, api_key = requestor.poll("get", url, until=lambda response: response.data["status"] not in ["NotStarted", "Running"]) + return util.convert_to_openai_object( response, api_key, api_version, organization ) @@ -88,9 +104,9 @@ def _prepare_create_variation( api_version=api_version, organization=organization, ) - _, api_version = cls._get_api_type_and_version(api_type, api_version) + api_type, api_version = cls._get_api_type_and_version(api_type, api_version) - url = cls._get_url("variations") + url = cls._get_url("variations", None, api_type=api_type, api_version=api_version) files: List[Any] = [] for key, value in params.items(): @@ -171,9 +187,9 @@ def _prepare_create_edit( api_version=api_version, organization=organization, ) - _, api_version = cls._get_api_type_and_version(api_type, api_version) + api_type, api_version = cls._get_api_type_and_version(api_type, api_version) - url = cls._get_url("edits") + url = cls._get_url("edits", None, api_type=api_type, api_version=api_version) files: List[Any] = [] for key, value in params.items(): diff --git a/openai/openai_response.py b/openai/openai_response.py index 9954247319..75ff3cc07d 100644 --- a/openai/openai_response.py +++ b/openai/openai_response.py @@ -9,6 +9,13 @@ def __init__(self, data, headers): @property def request_id(self) -> Optional[str]: return self._headers.get("request-id") + + @property + def retry_after(self) -> Optional[str]: + try: + return int(self._headers.get("retry-after")) + except ValueError: + return None @property def organization(self) -> Optional[str]: From ab699b89855c49012ada4906abc3baceb6def7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Tue, 14 Mar 2023 15:36:36 +0100 Subject: [PATCH 02/17] WIP --- openai/api_resources/image.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 1bde2880c0..151b7c04cd 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -4,12 +4,25 @@ import openai from openai import api_requestor, util from openai.api_resources.abstract import APIResource -from openai.error import APIError +from openai.util import ApiType class Image(APIResource): OBJECT_NAME = "images" + _azure_preview_version = "2022-11-23-preview" + + @classmethod + def _get_api_type_and_version( + cls, api_type = None, api_version = None + ): + api_type, base_api_version = super()._get_api_type_and_version() + if api_type in (ApiType.AZURE, ApiType.AZURE_AD): + # This override is only temporary: DallE and GPT endpoint versioning is currently out of sync but will be aligned soon. + return (api_type, api_version or Image._azure_preview_version) + else: + return (api_type, base_api_version) + @classmethod def _get_url(cls, openai_action, azure_action, api_type, api_version): if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): @@ -19,7 +32,7 @@ def _get_url(cls, openai_action, azure_action, api_type, api_version): @classmethod def _get_azure_operations_url(cls, operation_id, api_version): - return "/%s/operations/%s?api-version=%s" % (cls.azure_dalle_prefix, operation_id, api_version) + return f"/{cls.azure_dalle_prefix}/operations/{operation_id}?api-version={api_version}" @classmethod def create( From a076188da9dd8b53bc83527bf40f78abfcf1c0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Tue, 14 Mar 2023 15:39:47 +0100 Subject: [PATCH 03/17] Fix type --- openai/openai_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/openai_response.py b/openai/openai_response.py index 75ff3cc07d..0b61a646de 100644 --- a/openai/openai_response.py +++ b/openai/openai_response.py @@ -11,7 +11,7 @@ def request_id(self) -> Optional[str]: return self._headers.get("request-id") @property - def retry_after(self) -> Optional[str]: + def retry_after(self) -> Optional[int]: try: return int(self._headers.get("retry-after")) except ValueError: From 25e118f72cf4399aa45bdab01f34cec698a9b516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Tue, 14 Mar 2023 16:03:56 +0100 Subject: [PATCH 04/17] Fixes --- openai/api_requestor.py | 12 ++++++++++-- openai/api_resources/image.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index b218334501..f01856c0f3 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -153,8 +153,12 @@ def poll( until, params = None, headers = None, - interval = None + interval = None, + delay = None ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + if delay: + time.sleep(delay) + response, b, api_key = self.request(method, url, params, headers) while not until(response): time.sleep(interval or response.retry_after or 1) @@ -168,8 +172,12 @@ async def apoll( until, params = None, headers = None, - interval = None + interval = None, + delay = None ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + if delay: + await asyncio.sleep(delay) + response, b, api_key = await self.arequest(method, url, params, headers) while not until(response): await asyncio.sleep(interval or response.retry_after or 1) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 151b7c04cd..9548c6d941 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -60,7 +60,11 @@ def create( if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): url = cls._get_azure_operations_url(response.data['id'], api_version) - response, _, api_key = requestor.poll("get", url, until=lambda response: response.data["status"] not in ["NotStarted", "Running"]) + response, _, api_key = requestor.poll( + "get", url, + until=lambda response: response.data["status"] not in ["NotStarted", "Running"], + delay=response.retry_after + ) return util.convert_to_openai_object( response, api_key, api_version, organization @@ -93,7 +97,11 @@ async def acreate( if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): url = cls._get_azure_operations_url(response.data['id'], api_version) - response, _, api_key = requestor.poll("get", url, until=lambda response: response.data["status"] not in ["NotStarted", "Running"]) + response, _, api_key = await requestor.apoll( + "get", url, + until=lambda response: response.data["status"] not in ["NotStarted", "Running"], + delay=response.retry_after + ) return util.convert_to_openai_object( response, api_key, api_version, organization From 9903e4874d27b1c762bf28c654832063b79dee48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Fri, 17 Mar 2023 17:24:08 +0100 Subject: [PATCH 05/17] Revert api_version override --- openai/api_resources/image.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 9548c6d941..4af3bd8f03 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -4,25 +4,11 @@ import openai from openai import api_requestor, util from openai.api_resources.abstract import APIResource -from openai.util import ApiType class Image(APIResource): OBJECT_NAME = "images" - _azure_preview_version = "2022-11-23-preview" - - @classmethod - def _get_api_type_and_version( - cls, api_type = None, api_version = None - ): - api_type, base_api_version = super()._get_api_type_and_version() - if api_type in (ApiType.AZURE, ApiType.AZURE_AD): - # This override is only temporary: DallE and GPT endpoint versioning is currently out of sync but will be aligned soon. - return (api_type, api_version or Image._azure_preview_version) - else: - return (api_type, base_api_version) - @classmethod def _get_url(cls, openai_action, azure_action, api_type, api_version): if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): From 1f66fb6da9a8e6105be2f94bf8943fa46b2f6d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Thu, 23 Mar 2023 16:01:54 +0100 Subject: [PATCH 06/17] Fix --- openai/api_resources/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 4af3bd8f03..4d3de96b56 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -14,7 +14,7 @@ def _get_url(cls, openai_action, azure_action, api_type, api_version): if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): return f"/{cls.azure_dalle_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" else: - return cls.class_url() + f"/{openai_action}" + return f"{cls.class_url()}/{openai_action}" @classmethod def _get_azure_operations_url(cls, operation_id, api_version): From 71c3fd81450943798dd39ea0a648eb9684c77102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Sat, 25 Mar 2023 22:14:39 +0100 Subject: [PATCH 07/17] Remove azure_dalle_prefix --- openai/api_resources/abstract/api_resource.py | 1 - openai/api_resources/image.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openai/api_resources/abstract/api_resource.py b/openai/api_resources/abstract/api_resource.py index 70b46e7f84..53a7dec799 100644 --- a/openai/api_resources/abstract/api_resource.py +++ b/openai/api_resources/abstract/api_resource.py @@ -10,7 +10,6 @@ class APIResource(OpenAIObject): api_prefix = "" azure_api_prefix = "openai" - azure_dalle_prefix = "dalle" azure_deployments_prefix = "deployments" @classmethod diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 4d3de96b56..946bf335cd 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -12,13 +12,13 @@ class Image(APIResource): @classmethod def _get_url(cls, openai_action, azure_action, api_type, api_version): if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): - return f"/{cls.azure_dalle_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" + return f"/{cls.azure_api_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" else: return f"{cls.class_url()}/{openai_action}" @classmethod def _get_azure_operations_url(cls, operation_id, api_version): - return f"/{cls.azure_dalle_prefix}/operations/{operation_id}?api-version={api_version}" + return f"/{cls.azure_api_prefix}/operations/{operation_id}?api-version={api_version}" @classmethod def create( From 985329fba99c64c0362c98db5588812679c45c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCrtz?= Date: Sat, 25 Mar 2023 22:34:55 +0100 Subject: [PATCH 08/17] Use operation-location header --- openai/api_resources/image.py | 14 +++++--------- openai/openai_response.py | 4 ++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 946bf335cd..8c5b808b65 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -15,10 +15,6 @@ def _get_url(cls, openai_action, azure_action, api_type, api_version): return f"/{cls.azure_api_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" else: return f"{cls.class_url()}/{openai_action}" - - @classmethod - def _get_azure_operations_url(cls, operation_id, api_version): - return f"/{cls.azure_api_prefix}/operations/{operation_id}?api-version={api_version}" @classmethod def create( @@ -45,9 +41,9 @@ def create( ) if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): - url = cls._get_azure_operations_url(response.data['id'], api_version) + requestor.api_base = "" # operation_location is a full url response, _, api_key = requestor.poll( - "get", url, + "get", response.operation_location, until=lambda response: response.data["status"] not in ["NotStarted", "Running"], delay=response.retry_after ) @@ -81,10 +77,10 @@ async def acreate( "post", cls._get_url("generations", "generate", api_type=api_type, api_version=api_version), params ) - if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): - url = cls._get_azure_operations_url(response.data['id'], api_version) + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + requestor.api_base = "" # operation_location is a full url response, _, api_key = await requestor.apoll( - "get", url, + "get", response.operation_location, until=lambda response: response.data["status"] not in ["NotStarted", "Running"], delay=response.retry_after ) diff --git a/openai/openai_response.py b/openai/openai_response.py index 0b61a646de..828f98cf34 100644 --- a/openai/openai_response.py +++ b/openai/openai_response.py @@ -16,6 +16,10 @@ def retry_after(self) -> Optional[int]: return int(self._headers.get("retry-after")) except ValueError: return None + + @property + def operation_location(self) -> Optional[str]: + return self._headers.get("operation-location") @property def organization(self) -> Optional[str]: From a0d0447e9eb329eaf3a71145cbdc0384ceb99739 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Fri, 5 May 2023 14:50:05 -0700 Subject: [PATCH 09/17] updates to match the latest azure version --- openai/api_requestor.py | 26 +++++++++++++++++++++++--- openai/api_resources/image.py | 18 +++++++++--------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index fe3152b458..b384a2a767 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -10,6 +10,7 @@ from typing import ( AsyncGenerator, AsyncIterator, + Callable, Dict, Iterator, Optional, @@ -145,12 +146,22 @@ def format_app_info(cls, info): if info["url"]: str += " (%s)" % (info["url"],) return str - + + def __check_polling_response(self, response: OpenAIResponse, predicate: Callable[[OpenAIResponse], bool]): + if predicate(response): + message, code = (None, None) if 'error' not in response.data else ( + response.data['error']['message'] if 'message' in response.data['error'] else None, + response.data['error']['code'] if 'code' in response.data['error'] else None + ) + message = message if message is not None else 'Operation failed' + raise error.OpenAIError(message=message, code=code) + def poll( self, method, url, until, + failed, params = None, headers = None, interval = None, @@ -160,16 +171,21 @@ def poll( time.sleep(delay) response, b, api_key = self.request(method, url, params, headers) + self.__check_polling_response(response, failed) while not until(response): time.sleep(interval or response.retry_after or 1) response, b, api_key = self.request(method, url) + self.__check_polling_response(response, failed) + + response.data = response.data['result'] return response, b, api_key - + async def apoll( self, method, url, until, + failed, params = None, headers = None, interval = None, @@ -177,11 +193,15 @@ async def apoll( ) -> Tuple[Iterator[OpenAIResponse], bool, str]: if delay: await asyncio.sleep(delay) - + response, b, api_key = await self.arequest(method, url, params, headers) + self.__check_polling_response(response, failed) while not until(response): await asyncio.sleep(interval or response.retry_after or 1) response, b, api_key = await self.arequest(method, url) + self.__check_polling_response(response, failed) + + response.data = response.data['result'] return response, b, api_key @overload diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 8c5b808b65..97d13f3319 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -11,8 +11,8 @@ class Image(APIResource): @classmethod def _get_url(cls, openai_action, azure_action, api_type, api_version): - if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): - return f"/{cls.azure_api_prefix}{cls.class_url()}/{azure_action}?api-version={api_version}" + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD) and azure_action is not None: + return f"/{cls.azure_api_prefix}{cls.class_url()}/{openai_action}:{azure_action}?api-version={api_version}" else: return f"{cls.class_url()}/{openai_action}" @@ -37,15 +37,15 @@ def create( api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = requestor.request( - "post", cls._get_url("generations", "generate", api_type=api_type, api_version=api_version), params + "post", cls._get_url("generations", "submit", api_type=api_type, api_version=api_version), params ) if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): requestor.api_base = "" # operation_location is a full url response, _, api_key = requestor.poll( "get", response.operation_location, - until=lambda response: response.data["status"] not in ["NotStarted", "Running"], - delay=response.retry_after + until=lambda response: response.data["status"] not in ["notRunning", "running"], + failed=lambda response: response.data['status'] in [ 'canceled', 'failed', 'deleted'] ) return util.convert_to_openai_object( @@ -74,15 +74,15 @@ async def acreate( api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = await requestor.arequest( - "post", cls._get_url("generations", "generate", api_type=api_type, api_version=api_version), params + "post", cls._get_url("generations", "submit", api_type=api_type, api_version=api_version), params ) - if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): requestor.api_base = "" # operation_location is a full url response, _, api_key = await requestor.apoll( "get", response.operation_location, - until=lambda response: response.data["status"] not in ["NotStarted", "Running"], - delay=response.retry_after + until=lambda response: response.data["status"] not in ["notRunning", "running"], + failed=lambda response: response.data['status'] in [ 'canceled', 'failed', 'deleted'] ) return util.convert_to_openai_object( From d4c4de4efbd27eaf467e3519951fea726a239ee5 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Wed, 10 May 2023 16:49:37 -0700 Subject: [PATCH 10/17] Addressing feedback. --- openai/api_resources/image.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 97d13f3319..734790dd2c 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -10,11 +10,11 @@ class Image(APIResource): OBJECT_NAME = "images" @classmethod - def _get_url(cls, openai_action, azure_action, api_type, api_version): + def _get_url(cls, action, azure_action, api_type, api_version): if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD) and azure_action is not None: - return f"/{cls.azure_api_prefix}{cls.class_url()}/{openai_action}:{azure_action}?api-version={api_version}" + return f"/{cls.azure_api_prefix}{cls.class_url()}/{action}:{azure_action}?api-version={api_version}" else: - return f"{cls.class_url()}/{openai_action}" + return f"{cls.class_url()}/{action}" @classmethod def create( @@ -37,7 +37,7 @@ def create( api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = requestor.request( - "post", cls._get_url("generations", "submit", api_type=api_type, api_version=api_version), params + "post", cls._get_url("generations", azure_action="submit", api_type=api_type, api_version=api_version), params ) if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): @@ -74,7 +74,7 @@ async def acreate( api_type, api_version = cls._get_api_type_and_version(api_type, api_version) response, _, api_key = await requestor.arequest( - "post", cls._get_url("generations", "submit", api_type=api_type, api_version=api_version), params + "post", cls._get_url("generations", azure_action="submit", api_type=api_type, api_version=api_version), params ) if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): @@ -109,7 +109,7 @@ def _prepare_create_variation( ) api_type, api_version = cls._get_api_type_and_version(api_type, api_version) - url = cls._get_url("variations", None, api_type=api_type, api_version=api_version) + url = cls._get_url("variations", azure_action=None, api_type=api_type, api_version=api_version) files: List[Any] = [] for key, value in params.items(): @@ -192,7 +192,7 @@ def _prepare_create_edit( ) api_type, api_version = cls._get_api_type_and_version(api_type, api_version) - url = cls._get_url("edits", None, api_type=api_type, api_version=api_version) + url = cls._get_url("edits", azure_action=None, api_type=api_type, api_version=api_version) files: List[Any] = [] for key, value in params.items(): From 19ad6ecbc9f899980b311aa6a544371372378fa9 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Thu, 11 May 2023 08:11:13 -0700 Subject: [PATCH 11/17] removing deleted state check --- openai/api_resources/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 734790dd2c..7d45af52d8 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -45,7 +45,7 @@ def create( response, _, api_key = requestor.poll( "get", response.operation_location, until=lambda response: response.data["status"] not in ["notRunning", "running"], - failed=lambda response: response.data['status'] in [ 'canceled', 'failed', 'deleted'] + failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] ) return util.convert_to_openai_object( @@ -82,7 +82,7 @@ async def acreate( response, _, api_key = await requestor.apoll( "get", response.operation_location, until=lambda response: response.data["status"] not in ["notRunning", "running"], - failed=lambda response: response.data['status'] in [ 'canceled', 'failed', 'deleted'] + failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] ) return util.convert_to_openai_object( From 0691a679f25c86072d581902c738a3ab9ef3a3d2 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Tue, 23 May 2023 18:23:21 -0700 Subject: [PATCH 12/17] addressing feedback --- openai/api_requestor.py | 18 +++++++++--------- openai/api_resources/image.py | 8 ++++---- openai/openai_response.py | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index d161b97833..774c09b277 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -151,7 +151,7 @@ def format_app_info(cls, info): str += " (%s)" % (info["url"],) return str - def __check_polling_response(self, response: OpenAIResponse, predicate: Callable[[OpenAIResponse], bool]): + def _check_polling_response(self, response: OpenAIResponse, predicate: Callable[[OpenAIResponse], bool]): if predicate(response): message, code = (None, None) if 'error' not in response.data else ( response.data['error']['message'] if 'message' in response.data['error'] else None, @@ -160,7 +160,7 @@ def __check_polling_response(self, response: OpenAIResponse, predicate: Callable message = message if message is not None else 'Operation failed' raise error.OpenAIError(message=message, code=code) - def poll( + def _poll( self, method, url, @@ -175,16 +175,16 @@ def poll( time.sleep(delay) response, b, api_key = self.request(method, url, params, headers) - self.__check_polling_response(response, failed) + self._check_polling_response(response, failed) while not until(response): time.sleep(interval or response.retry_after or 1) - response, b, api_key = self.request(method, url) - self.__check_polling_response(response, failed) + response, b, api_key = self.request(method, url, params, headers) + self._check_polling_response(response, failed) response.data = response.data['result'] return response, b, api_key - async def apoll( + async def _apoll( self, method, url, @@ -199,11 +199,11 @@ async def apoll( await asyncio.sleep(delay) response, b, api_key = await self.arequest(method, url, params, headers) - self.__check_polling_response(response, failed) + self._check_polling_response(response, failed) while not until(response): await asyncio.sleep(interval or response.retry_after or 1) - response, b, api_key = await self.arequest(method, url) - self.__check_polling_response(response, failed) + response, b, api_key = await self.arequest(method, url, params, headers) + self._check_polling_response(response, failed) response.data = response.data['result'] return response, b, api_key diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 7d45af52d8..2e3748cfa2 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -42,9 +42,9 @@ def create( if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): requestor.api_base = "" # operation_location is a full url - response, _, api_key = requestor.poll( + response, _, api_key = requestor._poll( "get", response.operation_location, - until=lambda response: response.data["status"] not in ["notRunning", "running"], + until=lambda response: response.data["status"] in ["succeeded"], failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] ) @@ -79,9 +79,9 @@ async def acreate( if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): requestor.api_base = "" # operation_location is a full url - response, _, api_key = await requestor.apoll( + response, _, api_key = await requestor._apoll( "get", response.operation_location, - until=lambda response: response.data["status"] not in ["notRunning", "running"], + until=lambda response: response.data["status"] in ["succeeded"], failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] ) diff --git a/openai/openai_response.py b/openai/openai_response.py index 828f98cf34..d2230b1540 100644 --- a/openai/openai_response.py +++ b/openai/openai_response.py @@ -9,14 +9,14 @@ def __init__(self, data, headers): @property def request_id(self) -> Optional[str]: return self._headers.get("request-id") - + @property def retry_after(self) -> Optional[int]: try: return int(self._headers.get("retry-after")) - except ValueError: + except TypeError: return None - + @property def operation_location(self) -> Optional[str]: return self._headers.get("operation-location") From 2cbe7ea74fb13e1fc5bf72aa28f6fe1454333b78 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Wed, 24 May 2023 11:05:30 -0700 Subject: [PATCH 13/17] Exceptions if unsupported APIs are called. --- openai/api_resources/image.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 2e3748cfa2..77810b9fed 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -2,7 +2,7 @@ from typing import Any, List import openai -from openai import api_requestor, util +from openai import api_requestor, error, util from openai.api_resources.abstract import APIResource @@ -128,6 +128,9 @@ def create_variation( organization=None, **params, ): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType("Variations are not supported by the Azure OpenAI API yet.") + requestor, url, files = cls._prepare_create_variation( image, api_key, @@ -155,6 +158,9 @@ async def acreate_variation( organization=None, **params, ): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType("Variations are not supported by the Azure OpenAI API yet.") + requestor, url, files = cls._prepare_create_variation( image, api_key, @@ -214,6 +220,9 @@ def create_edit( organization=None, **params, ): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType("Edits are not supported by the Azure OpenAI API yet.") + requestor, url, files = cls._prepare_create_edit( image, mask, @@ -243,6 +252,9 @@ async def acreate_edit( organization=None, **params, ): + if api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType("Edits are not supported by the Azure OpenAI API yet.") + requestor, url, files = cls._prepare_create_edit( image, mask, From 110d4fdaabfbce9f96a4a3f8369dfdd75f0b9f85 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Thu, 25 May 2023 09:19:29 -0700 Subject: [PATCH 14/17] updating default delay to 10s + removing unused state from check. --- openai/api_requestor.py | 4 ++-- openai/api_resources/image.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index 774c09b277..2994a6b621 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -177,7 +177,7 @@ def _poll( response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) while not until(response): - time.sleep(interval or response.retry_after or 1) + time.sleep(interval or response.retry_after or 10) response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) @@ -201,7 +201,7 @@ async def _apoll( response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed) while not until(response): - await asyncio.sleep(interval or response.retry_after or 1) + await asyncio.sleep(interval or response.retry_after or 10) response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 77810b9fed..e6542cdb18 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -45,7 +45,7 @@ def create( response, _, api_key = requestor._poll( "get", response.operation_location, until=lambda response: response.data["status"] in ["succeeded"], - failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] + failed=lambda response: response.data['status'] in [ 'failed' ] ) return util.convert_to_openai_object( @@ -82,7 +82,7 @@ async def acreate( response, _, api_key = await requestor._apoll( "get", response.operation_location, until=lambda response: response.data["status"] in ["succeeded"], - failed=lambda response: response.data['status'] in [ 'canceled', 'failed'] + failed=lambda response: response.data['status'] in [ 'failed' ] ) return util.convert_to_openai_object( From 3df081421d2b8fbd427cce9128d804bb1c7f8e35 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Tue, 6 Jun 2023 12:20:14 -0700 Subject: [PATCH 15/17] feedback --- openai/api_requestor.py | 19 ++++++++++--------- openai/api_resources/image.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index 2994a6b621..ca0262d57b 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -152,13 +152,12 @@ def format_app_info(cls, info): return str def _check_polling_response(self, response: OpenAIResponse, predicate: Callable[[OpenAIResponse], bool]): - if predicate(response): - message, code = (None, None) if 'error' not in response.data else ( - response.data['error']['message'] if 'message' in response.data['error'] else None, - response.data['error']['code'] if 'code' in response.data['error'] else None - ) - message = message if message is not None else 'Operation failed' - raise error.OpenAIError(message=message, code=code) + if not predicate(response): + return + error_data = response.data['error'] + message = error_data.get('message', 'Operation failed') + code = error_data.get('code') + raise error.OpenAIError(message=message, code=code) def _poll( self, @@ -176,7 +175,8 @@ def _poll( response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) - while not until(response): + start_time = time.time() + while not until(response) and time.time() - start_time < TIMEOUT_SECS: time.sleep(interval or response.retry_after or 10) response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) @@ -200,7 +200,8 @@ async def _apoll( response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed) - while not until(response): + start_time = time.time() + while not until(response) and time.time() - start_time < TIMEOUT_SECS: await asyncio.sleep(interval or response.retry_after or 10) response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index e6542cdb18..0cd6222fd3 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -44,7 +44,7 @@ def create( requestor.api_base = "" # operation_location is a full url response, _, api_key = requestor._poll( "get", response.operation_location, - until=lambda response: response.data["status"] in ["succeeded"], + until=lambda response: response.data['status'] in [ 'succeeded' ], failed=lambda response: response.data['status'] in [ 'failed' ] ) From a4636fc2e08679a3599efc9246ca0bce1281b266 Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Tue, 6 Jun 2023 13:09:32 -0700 Subject: [PATCH 16/17] missing update --- openai/api_resources/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/api_resources/image.py b/openai/api_resources/image.py index 0cd6222fd3..1522923510 100644 --- a/openai/api_resources/image.py +++ b/openai/api_resources/image.py @@ -81,7 +81,7 @@ async def acreate( requestor.api_base = "" # operation_location is a full url response, _, api_key = await requestor._apoll( "get", response.operation_location, - until=lambda response: response.data["status"] in ["succeeded"], + until=lambda response: response.data['status'] in [ 'succeeded' ], failed=lambda response: response.data['status'] in [ 'failed' ] ) From 62e2c99a98acf9c91165b4f7b7439b383b3af12d Mon Sep 17 00:00:00 2001 From: Gerardo Lecaros Date: Tue, 6 Jun 2023 14:06:23 -0700 Subject: [PATCH 17/17] updating to raise an exception in case of a timeout --- openai/api_requestor.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openai/api_requestor.py b/openai/api_requestor.py index ca0262d57b..e71eaed3a3 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -176,7 +176,10 @@ def _poll( response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) start_time = time.time() - while not until(response) and time.time() - start_time < TIMEOUT_SECS: + while not until(response): + if time.time() - start_time > TIMEOUT_SECS: + raise error.Timeout("Operation polling timed out.") + time.sleep(interval or response.retry_after or 10) response, b, api_key = self.request(method, url, params, headers) self._check_polling_response(response, failed) @@ -201,7 +204,10 @@ async def _apoll( response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed) start_time = time.time() - while not until(response) and time.time() - start_time < TIMEOUT_SECS: + while not until(response): + if time.time() - start_time > TIMEOUT_SECS: + raise error.Timeout("Operation polling timed out.") + await asyncio.sleep(interval or response.retry_after or 10) response, b, api_key = await self.arequest(method, url, params, headers) self._check_polling_response(response, failed)