Skip to content

Commit 53e5ba4

Browse files
authored
Add Azure AD authentication support (openai#92)
* Add Azure AD authentication support * Add azure_ad code examples * Remove debug print statement * Bump version
1 parent d4f2d0f commit 53e5ba4

16 files changed

+215
-79
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ openai.api_base = "https://example-endpoint.openai.azure.com"
6565
openai.api_version = "2021-11-01-preview"
6666

6767
# create a completion
68-
completion = openai.Completion.create(engine="deployment-namme", prompt="Hello world")
68+
completion = openai.Completion.create(engine="deployment-name", prompt="Hello world")
6969

7070
# print the completion
7171
print(completion.choices[0].text)
7272

7373
# create a search and pass the deployment-name as the engine Id.
74-
search = openai.Engine(id="deployment-namme").search(documents=["White House", "hospital", "school"], query ="the president")
74+
search = openai.Engine(id="deployment-name").search(documents=["White House", "hospital", "school"], query ="the president")
7575

7676
# print the search
7777
print(search)
@@ -81,6 +81,27 @@ Please note that for the moment, the Microsoft Azure endpoints can only be used
8181
For a detailed example on how to use fine-tuning and other operations using Azure endpoints, please check out the following Jupyter notebook:
8282
[Using Azure fine-tuning](https://github.com/openai/openai-python/blob/main/examples/azure/finetuning.ipynb)
8383

84+
### Microsoft Azure Active Directory Authentication
85+
86+
In order to use Microsoft Active Directory to authenticate to your Azure endpoint, you need to set the api_type to "azure_ad" and pass the acquired credential token to api_key. The rest of the parameters need to be set as specified in the previous section.
87+
88+
89+
```python
90+
from azure.identity import DefaultAzureCredential
91+
import openai
92+
93+
# Request credential
94+
default_credential = DefaultAzureCredential()
95+
token = default_credential.get_token("https://cognitiveservices.azure.com")
96+
97+
# Setup parameters
98+
openai.api_type = "azure_ad"
99+
openai.api_key = token.token
100+
openai.api_base = "https://example-endpoint.openai.azure.com/"
101+
openai.api_version = "2022-03-01-preview"
102+
103+
# ...
104+
```
84105
### Command-line interface
85106

86107
This library additionally provides an `openai` command-line utility

examples/azure/finetuning.ipynb

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@
4040
"openai.api_version = '2022-03-01-preview' # this may change in the future"
4141
]
4242
},
43+
{
44+
"cell_type": "markdown",
45+
"metadata": {},
46+
"source": [
47+
"## Microsoft Active Directory Authentication\n",
48+
"Instead of key based authentication, you can use Active Directory to authenticate using credential tokens. Uncomment the next code section to use credential based authentication:"
49+
]
50+
},
51+
{
52+
"cell_type": "code",
53+
"execution_count": null,
54+
"metadata": {},
55+
"outputs": [],
56+
"source": [
57+
"\"\"\"\n",
58+
"from azure.identity import DefaultAzureCredential\n",
59+
"\n",
60+
"default_credential = DefaultAzureCredential()\n",
61+
"token = default_credential.get_token(\"https://cognitiveservices.azure.com\")\n",
62+
"\n",
63+
"openai.api_type = 'azure_ad'\n",
64+
"openai.api_key = token.token\n",
65+
"openai.api_version = '2022-03-01-preview' # this may change in the future\n",
66+
"\n",
67+
"\n",
68+
"openai.api_base = '' # Please add your endpoint here\n",
69+
"\"\"\""
70+
]
71+
},
4372
{
4473
"cell_type": "markdown",
4574
"metadata": {},
@@ -418,10 +447,10 @@
418447
],
419448
"metadata": {
420449
"interpreter": {
421-
"hash": "1efaa68c6557ae864f04a55d1c611eb06843d0ca160c97bf33f135c19475264d"
450+
"hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
422451
},
423452
"kernelspec": {
424-
"display_name": "Python 3.8.10 ('openai-env')",
453+
"display_name": "Python 3.8.10 64-bit",
425454
"language": "python",
426455
"name": "python3"
427456
},

openai/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
organization = os.environ.get("OPENAI_ORGANIZATION")
3333
api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
3434
api_type = os.environ.get("OPENAI_API_TYPE", "open_ai")
35-
api_version = "2022-03-01-preview" if api_type == "azure" else None
35+
api_version = "2022-03-01-preview" if api_type in (
36+
"azure", "azure_ad", "azuread") else None
3637
verify_ssl_certs = True # No effect. Certificates are always verified.
3738
proxy = None
3839
app_info = None

openai/api_resources/abstract/api_resource.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ def instance_url(http://github.com/self,%20operation=None):
4949
api_version = self.api_version or openai.api_version
5050
extn = quote_plus(id)
5151

52-
if self.typed_api_type == ApiType.AZURE:
52+
if self.typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
5353
if not api_version:
5454
raise error.InvalidRequestError(
5555
"An API version is required for the Azure API type."
5656
)
57-
57+
5858
if not operation:
5959
base = self.class_url()
6060
return "/%s%s/%s?api-version=%s" % (
@@ -72,13 +72,13 @@ def instance_url(http://github.com/self,%20operation=None):
7272
api_version
7373
)
7474

75-
7675
elif self.typed_api_type == ApiType.OPEN_AI:
7776
base = self.class_url()
7877
return "%s/%s" % (base, extn)
7978

8079
else:
81-
raise error.InvalidAPIType("Unsupported API type %s" % self.api_type)
80+
raise error.InvalidAPIType(
81+
"Unsupported API type %s" % self.api_type)
8282

8383
# The `method_` and `url_` arguments are suffixed with an underscore to
8484
# avoid conflicting with actual request parameters in `params`.
@@ -111,7 +111,7 @@ def _static_request(
111111

112112
@classmethod
113113
def _get_api_type_and_version(cls, api_type: str, api_version: str):
114-
typed_api_type = ApiType.from_str(api_type) if api_type else ApiType.from_str(openai.api_type)
114+
typed_api_type = ApiType.from_str(
115+
api_type) if api_type else ApiType.from_str(openai.api_type)
115116
typed_api_version = api_version or openai.api_version
116117
return (typed_api_type, typed_api_version)
117-

openai/api_resources/abstract/createable_api_resource.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ def create(
2424
api_version=api_version,
2525
organization=organization,
2626
)
27-
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)
27+
typed_api_type, api_version = cls._get_api_type_and_version(
28+
api_type, api_version)
2829

29-
if typed_api_type == ApiType.AZURE:
30+
if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
3031
base = cls.class_url()
31-
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version)
32+
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix,
33+
base, api_version)
3234
elif typed_api_type == ApiType.OPEN_AI:
3335
url = cls.class_url()
3436
else:
35-
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
37+
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
3638

3739
response, _, api_key = requestor.request(
3840
"post", url, params, request_id=request_id

openai/api_resources/abstract/deletable_api_resource.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,25 @@
44
from openai.api_resources.abstract.api_resource import APIResource
55
from openai.util import ApiType
66

7+
78
class DeletableAPIResource(APIResource):
89
@classmethod
910
def delete(cls, sid, api_type=None, api_version=None, **params):
1011
if isinstance(cls, APIResource):
11-
raise ValueError(".delete may only be called as a class method now.")
12+
raise ValueError(
13+
".delete may only be called as a class method now.")
1214

1315
base = cls.class_url()
1416
extn = quote_plus(sid)
1517

16-
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)
17-
if typed_api_type == ApiType.AZURE:
18-
url = "/%s%s/%s?api-version=%s" % (cls.azure_api_prefix, base, extn, api_version)
18+
typed_api_type, api_version = cls._get_api_type_and_version(
19+
api_type, api_version)
20+
if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
21+
url = "/%s%s/%s?api-version=%s" % (
22+
cls.azure_api_prefix, base, extn, api_version)
1923
elif typed_api_type == ApiType.OPEN_AI:
2024
url = "%s/%s" % (base, extn)
2125
else:
22-
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
23-
26+
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
27+
2428
return cls._static_request("delete", url, api_type=api_type, api_version=api_version, **params)

openai/api_resources/abstract/engine_api_resource.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ def class_url(http://github.com/%3C/div%3E%3C/code%3E%3C/div%3E%3C/td%3E%3C/tr%3E%3Ctr%20class=%22diff-line-row%22%3E%3Ctd%20data-grid-cell-id=%22diff-88f98c1b302c34afb660305fd5f70102d7f137f29e90813f0b5c438d92c6031d-29-29-0%22%20data-selected=%22false%22%20role=%22gridcell%22%20style=%22background-color:var(--bgColor-default);text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative diff-line-number-neutral left-side">29
29
# Namespaces are separated in object names with periods (.) and in URLs
3030
# with forward slashes (/), so replace the former with the latter.
3131
base = cls.OBJECT_NAME.replace(".", "/") # type: ignore
32-
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)
32+
typed_api_type, api_version = cls._get_api_type_and_version(
33+
api_type, api_version)
3334

34-
if typed_api_type == ApiType.AZURE:
35+
if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
3536
if not api_version:
3637
raise error.InvalidRequestError(
3738
"An API version is required for the Azure API type."
@@ -107,7 +108,8 @@ def create(
107108
)
108109

109110
if stream:
110-
assert not isinstance(response, OpenAIResponse) # must be an iterator
111+
# must be an iterator
112+
assert not isinstance(response, OpenAIResponse)
111113
return (
112114
util.convert_to_openai_object(
113115
line,
@@ -146,7 +148,7 @@ def instance_url(http://github.com/self):
146148
extn = quote_plus(id)
147149
params_connector = '?'
148150

149-
if self.typed_api_type == ApiType.AZURE:
151+
if self.typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
150152
api_version = self.api_version or openai.api_version
151153
if not api_version:
152154
raise error.InvalidRequestError(
@@ -163,13 +165,13 @@ def instance_url(http://github.com/self):
163165
)
164166
params_connector = '&'
165167

166-
167168
elif self.typed_api_type == ApiType.OPEN_AI:
168169
base = self.class_url(self.engine, self.api_type, self.api_version)
169170
url = "%s/%s" % (base, extn)
170171

171172
else:
172-
raise error.InvalidAPIType("Unsupported API type %s" % self.api_type)
173+
raise error.InvalidAPIType(
174+
"Unsupported API type %s" % self.api_type)
173175

174176
timeout = self.get("timeout")
175177
if timeout is not None:

openai/api_resources/abstract/listable_api_resource.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ def list(
2727
organization=organization,
2828
)
2929

30-
typed_api_type, api_version = cls._get_api_type_and_version(api_type, api_version)
30+
typed_api_type, api_version = cls._get_api_type_and_version(
31+
api_type, api_version)
3132

32-
if typed_api_type == ApiType.AZURE:
33+
if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
3334
base = cls.class_url()
34-
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version)
35+
url = "/%s%s?api-version=%s" % (cls.azure_api_prefix,
36+
base, api_version)
3537
elif typed_api_type == ApiType.OPEN_AI:
3638
url = cls.class_url()
3739
else:
38-
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
40+
raise error.InvalidAPIType('Unsupported API type %s' % api_type)
3941

4042
response, _, api_key = requestor.request(
4143
"get", url, params, request_id=request_id

openai/api_resources/deployment.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ def create(cls, *args, **kwargs):
1212
"""
1313
Creates a new deployment for the provided prompt and parameters.
1414
"""
15-
typed_api_type, _ = cls._get_api_type_and_version(kwargs.get("api_type", None), None)
16-
if typed_api_type != util.ApiType.AZURE:
17-
raise APIError("Deployment operations are only available for the Azure API type.")
15+
typed_api_type, _ = cls._get_api_type_and_version(
16+
kwargs.get("api_type", None), None)
17+
if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD):
18+
raise APIError(
19+
"Deployment operations are only available for the Azure API type.")
1820

1921
if kwargs.get("model", None) is None:
2022
raise InvalidRequestError(
@@ -28,9 +30,9 @@ def create(cls, *args, **kwargs):
2830
"Must provide a 'scale_settings' parameter to create a Deployment.",
2931
param="scale_settings",
3032
)
31-
33+
3234
if "scale_type" not in scale_settings or \
33-
(scale_settings["scale_type"].lower() == 'manual' and "capacity" not in scale_settings):
35+
(scale_settings["scale_type"].lower() == 'manual' and "capacity" not in scale_settings):
3436
raise InvalidRequestError(
3537
"The 'scale_settings' parameter contains invalid or incomplete values.",
3638
param="scale_settings",
@@ -40,24 +42,30 @@ def create(cls, *args, **kwargs):
4042

4143
@classmethod
4244
def list(cls, *args, **kwargs):
43-
typed_api_type, _ = cls._get_api_type_and_version(kwargs.get("api_type", None), None)
44-
if typed_api_type != util.ApiType.AZURE:
45-
raise APIError("Deployment operations are only available for the Azure API type.")
45+
typed_api_type, _ = cls._get_api_type_and_version(
46+
kwargs.get("api_type", None), None)
47+
if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD):
48+
raise APIError(
49+
"Deployment operations are only available for the Azure API type.")
4650

4751
return super().list(*args, **kwargs)
4852

4953
@classmethod
5054
def delete(cls, *args, **kwargs):
51-
typed_api_type, _ = cls._get_api_type_and_version(kwargs.get("api_type", None), None)
52-
if typed_api_type != util.ApiType.AZURE:
53-
raise APIError("Deployment operations are only available for the Azure API type.")
55+
typed_api_type, _ = cls._get_api_type_and_version(
56+
kwargs.get("api_type", None), None)
57+
if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD):
58+
raise APIError(
59+
"Deployment operations are only available for the Azure API type.")
5460

5561
return super().delete(*args, **kwargs)
5662

5763
@classmethod
5864
def retrieve(cls, *args, **kwargs):
59-
typed_api_type, _ = cls._get_api_type_and_version(kwargs.get("api_type", None), None)
60-
if typed_api_type != util.ApiType.AZURE:
61-
raise APIError("Deployment operations are only available for the Azure API type.")
65+
typed_api_type, _ = cls._get_api_type_and_version(
66+
kwargs.get("api_type", None), None)
67+
if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD):
68+
raise APIError(
69+
"Deployment operations are only available for the Azure API type.")
6270

6371
return super().retrieve(*args, **kwargs)

openai/api_resources/engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def generate(self, timeout=None, **params):
2828
util.log_info("Waiting for model to warm up", error=e)
2929

3030
def search(self, **params):
31-
if self.typed_api_type == ApiType.AZURE:
31+
if self.typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD):
3232
return self.request("post", self.instance_url("search"), params)
3333
elif self.typed_api_type == ApiType.OPEN_AI:
3434
return self.request("post", self.instance_url() + "/search", params)

0 commit comments

Comments
 (0)