Skip to content

Add support for fine-tuning and files using the Azure API. #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ __pycache__
build
*.egg
.vscode/settings.json
.ipynb_checkpoints
.ipynb_checkpoints
.vscode/launch.json
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ search = openai.Engine(id="deployment-namme").search(documents=["White House", "
print(search)
```

Please note that for the moment, the Microsoft Azure endpoints can only be used for completion and search operations.
Please note that for the moment, the Microsoft Azure endpoints can only be used for completion, search and fine-tuning operations.

### Command-line interface

Expand Down
6 changes: 5 additions & 1 deletion openai/api_requestor.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def request(

def handle_error_response(self, rbody, rcode, resp, rheaders, stream_error=False):
try:
error_data = resp["error"]
error_data = resp["error"] if self.api_type == ApiType.OPEN_AI else resp
except (KeyError, TypeError):
raise error.APIError(
"Invalid response object from API: %r (HTTP response code "
Expand Down Expand Up @@ -333,6 +333,10 @@ def _interpret_response(
def _interpret_response_line(
self, rbody, rcode, rheaders, stream: bool
) -> OpenAIResponse:
# HTTP 204 response code does not have any content in the body.
if rcode == 204:
return OpenAIResponse(None, rheaders)

if rcode == 503:
raise error.ServiceUnavailableError(
"The server is overloaded or not ready yet.",
Expand Down
31 changes: 24 additions & 7 deletions openai/api_resources/abstract/api_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

class APIResource(OpenAIObject):
api_prefix = ""
azure_api_prefix = "openai/deployments"
azure_api_prefix = "openai"
azure_deployments_prefix = "deployments"

@classmethod
def retrieve(cls, id, api_key=None, request_id=None, **params):
Expand Down Expand Up @@ -46,27 +47,34 @@ def instance_url(self, operation=None):
"id",
)
api_version = self.api_version or openai.api_version
extn = quote_plus(id)

if self.typed_api_type == ApiType.AZURE:
if not api_version:
raise error.InvalidRequestError(
"An API version is required for the Azure API type."
)

if not operation:
raise error.InvalidRequestError(
"The request needs an operation (eg: 'search') for the Azure OpenAI API type."
base = self.class_url()
return "/%s%s/%s?api-version=%s" % (
self.azure_api_prefix,
base,
extn,
api_version
)
extn = quote_plus(id)
return "/%s/%s/%s?api-version=%s" % (

return "/%s/%s/%s/%s?api-version=%s" % (
self.azure_api_prefix,
self.azure_deployments_prefix,
extn,
operation,
api_version,
api_version
)


elif self.typed_api_type == ApiType.OPEN_AI:
base = self.class_url()
extn = quote_plus(id)
return "%s/%s" % (base, extn)

else:
Expand All @@ -81,6 +89,7 @@ def _static_request(
url_,
api_key=None,
api_base=None,
api_type=None,
request_id=None,
api_version=None,
organization=None,
Expand All @@ -91,10 +100,18 @@ def _static_request(
api_version=api_version,
organization=organization,
api_base=api_base,
api_type=api_type
)
response, _, api_key = requestor.request(
method_, url_, params, request_id=request_id
)
return util.convert_to_openai_object(
response, api_key, api_version, organization
)

@classmethod
def _get_api_type_and_version(cls, api_type: str, api_version: str):
typed_api_type = ApiType.from_str(api_type) if api_type else ApiType.from_str(openai.api_type)
typed_api_version = api_version or openai.api_version
return (typed_api_type, typed_api_version)

16 changes: 14 additions & 2 deletions openai/api_resources/abstract/createable_api_resource.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from openai import api_requestor, util
from openai import api_requestor, util, error
from openai.api_resources.abstract.api_resource import APIResource
from openai.util import ApiType


class CreateableAPIResource(APIResource):
Expand All @@ -10,6 +11,7 @@ def create(
cls,
api_key=None,
api_base=None,
api_type=None,
request_id=None,
api_version=None,
organization=None,
Expand All @@ -18,10 +20,20 @@ def create(
requestor = api_requestor.APIRequestor(
api_key,
api_base=api_base,
api_type=api_type,
api_version=api_version,
organization=organization,
)
url = cls.class_url()
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)

if typed_api_type == ApiType.AZURE:
base = cls.class_url()
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version)
elif typed_api_type == ApiType.OPEN_AI:
url = cls.class_url()
else:
raise error.InvalidAPIType('Unsupported API type %s' % api_type)

response, _, api_key = requestor.request(
"post", url, params, request_id=request_id
)
Expand Down
20 changes: 16 additions & 4 deletions openai/api_resources/abstract/deletable_api_resource.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
from urllib.parse import quote_plus

from openai import error
from openai.api_resources.abstract.api_resource import APIResource

from openai.util import ApiType

class DeletableAPIResource(APIResource):
@classmethod
def delete(cls, sid, **params):
def delete(cls, sid, api_type=None, api_version=None, **params):
if isinstance(cls, APIResource):
raise ValueError(".delete may only be called as a class method now.")
url = "%s/%s" % (cls.class_url(), quote_plus(sid))
return cls._static_request("delete", url, **params)

base = cls.class_url()
extn = quote_plus(sid)

typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)
if typed_api_type == ApiType.AZURE:
url = "/%s%s/%s?api-version=%s" % (cls.azure_api_prefix, base, extn, api_version)
elif typed_api_type == ApiType.OPEN_AI:
url = "%s/%s" % (base, extn)
else:
raise error.InvalidAPIType('Unsupported API type %s' % api_type)

return cls._static_request("delete", url, api_type=api_type, api_version=api_version, **params)
27 changes: 12 additions & 15 deletions openai/api_resources/abstract/engine_api_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
class EngineAPIResource(APIResource):
engine_required = True
plain_old_data = False
azure_api_prefix = "openai/deployments"

def __init__(self, engine: Optional[str] = None, **kwargs):
super().__init__(engine=engine, **kwargs)
Expand All @@ -30,12 +29,7 @@ def class_url(
# Namespaces are separated in object names with periods (.) and in URLs
# with forward slashes (/), so replace the former with the latter.
base = cls.OBJECT_NAME.replace(".", "/") # type: ignore
typed_api_type = (
ApiType.from_str(api_type)
if api_type
else ApiType.from_str(openai.api_type)
)
api_version = api_version or openai.api_version
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)

if typed_api_type == ApiType.AZURE:
if not api_version:
Expand All @@ -47,11 +41,12 @@ def class_url(
"You must provide the deployment name in the 'engine' parameter to access the Azure OpenAI service"
)
extn = quote_plus(engine)
return "/%s/%s/%ss?api-version=%s" % (
return "/%s/%s/%s/%ss?api-version=%s" % (
cls.azure_api_prefix,
cls.azure_deployments_prefix,
extn,
base,
api_version,
api_version
)

elif typed_api_type == ApiType.OPEN_AI:
Expand Down Expand Up @@ -148,27 +143,29 @@ def instance_url(self):
"id",
)

params_connector = "?"
extn = quote_plus(id)
params_connector = '?'

if self.typed_api_type == ApiType.AZURE:
api_version = self.api_version or openai.api_version
if not api_version:
raise error.InvalidRequestError(
"An API version is required for the Azure API type."
)
extn = quote_plus(id)
base = self.OBJECT_NAME.replace(".", "/")
url = "/%s/%s/%ss/%s?api-version=%s" % (
url = "/%s/%s/%s/%ss/%s?api-version=%s" % (
self.azure_api_prefix,
self.azure_deployments_prefix,
self.engine,
base,
extn,
api_version,
api_version
)
params_connector = "&"
params_connector = '&'


elif self.typed_api_type == ApiType.OPEN_AI:
base = self.class_url(self.engine, self.api_type, self.api_version)
extn = quote_plus(id)
url = "%s/%s" % (base, extn)

else:
Expand Down
17 changes: 15 additions & 2 deletions openai/api_resources/abstract/listable_api_resource.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from openai import api_requestor, util
from openai import api_requestor, util, error
from openai.api_resources.abstract.api_resource import APIResource
from openai.util import ApiType


class ListableAPIResource(APIResource):
Expand All @@ -15,15 +16,27 @@ def list(
api_version=None,
organization=None,
api_base=None,
api_type=None,
**params,
):
requestor = api_requestor.APIRequestor(
api_key,
api_base=api_base or cls.api_base(),
api_version=api_version,
api_type=api_type,
organization=organization,
)
url = cls.class_url()

typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)

if typed_api_type == ApiType.AZURE:
base = cls.class_url()
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version)
elif typed_api_type == ApiType.OPEN_AI:
url = cls.class_url()
else:
raise error.InvalidAPIType('Unsupported API type %s' % api_type)

response, _, api_key = requestor.request(
"get", url, params, request_id=request_id
)
Expand Down
Loading