Skip to content

Commit 5f1484b

Browse files
authored
Adding Openai v1.0.0+ support into data index (#33440)
* changes in ai-generative index * changes in resources. * spelling * adding tqdm * remove tqdm from required installation. * fix "Unknown word ( )" issue * Fixing Cspell complains
1 parent 20423ff commit 5f1484b

File tree

16 files changed

+263
-125
lines changed

16 files changed

+263
-125
lines changed

.vscode/cspell.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"sdk/batch/azure-batch/**",
3434
"sdk/ai/**/index/**",
3535
"sdk/ai/azure-ai-generative/tests/**",
36+
"sdk/ai/azure-ai-resources/azure/ai/resources/_index/_langchain/vendor/**",
3637
"sdk/cognitiveservices/azure-cognitiveservices-search-autosuggest/**",
3738
"sdk/cognitiveservices/azure-cognitiveservices-search-customimagesearch/**",
3839
"sdk/cognitiveservices/azure-cognitiveservices-search-customsearch/**",
@@ -1412,7 +1413,16 @@
14121413
{
14131414
"filename": "sdk/ai/azure-ai-resources/**",
14141415
"words": [
1415-
"etest"
1416+
"azuread",
1417+
"all-mpnet-base-v2",
1418+
"dtype",
1419+
"LLMRAG",
1420+
"pipelinerunid",
1421+
"pydantic",
1422+
"swigfaiss",
1423+
"tiktokens",
1424+
"tqdm",
1425+
"wasb"
14161426
]
14171427
},
14181428
{

sdk/ai/azure-ai-generative/azure/ai/generative/index/_embeddings/__init__.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ def _args_to_openai_embedder(arguments: dict):
4242

4343
if langchain_version > "0.0.154":
4444
embedder = OpenAIEmbeddings(
45-
openai_api_base=arguments.get("api_base", openai.api_base),
45+
openai_api_base=arguments.get("api_base", openai.api_base if hasattr(openai, "api_base") else openai.base_url),
4646
openai_api_type=arguments.get("api_type", openai.api_type),
4747
openai_api_version=arguments.get("api_version", openai.api_version),
4848
openai_api_key=arguments.get("api_key", openai.api_key),
4949
max_retries=100, # TODO: Make this configurable
5050
)
5151
else:
52-
openai.api_base = arguments.get("api_base", openai.api_base)
52+
if hasattr(openai, "api_base"):
53+
openai.api_base = arguments.get("api_base", openai.api_base)
54+
else:
55+
openai.base_url = arguments.get("api_base", openai.base_url)
5356
openai.api_type = arguments.get("api_type", openai.api_type)
5457
openai.api_version = arguments.get("api_version", openai.api_version)
5558
embedder = OpenAIEmbeddings(
@@ -85,9 +88,10 @@ def get_langchain_embeddings(embedding_kind: str, arguments: dict, credential: O
8588

8689
arguments = init_open_ai_from_config(arguments, credential=credential)
8790

91+
# In openai v1.0.0 and above, openai.api_base is replaced by openai.base_url
8892
embedder = OpenAIEmbedder(
8993
model=arguments.get("model"),
90-
api_base=arguments.get("api_base", openai.api_base),
94+
api_base=arguments.get("api_base", openai.api_base if hasattr(openai, "api_base") else openai.base_url),
9195
api_type=arguments.get("api_type", openai.api_type),
9296
api_version=arguments.get("api_version", openai.api_version),
9397
api_key=arguments.get("api_key", openai.api_key),
@@ -171,9 +175,10 @@ def get_embed_fn(embedding_kind: str, arguments: dict, credential: Optional[Toke
171175

172176
arguments = init_open_ai_from_config(arguments, credential=credential)
173177

178+
# In openai v1.0.0 and above, openai.api_base is replaced by openai.base_url
174179
embedder = OpenAIEmbedder(
175180
model=arguments.get("model"),
176-
api_base=arguments.get("api_base", openai.api_base),
181+
api_base=arguments.get("api_base", openai.api_base if hasattr(openai, "api_base") else openai.base_url),
177182
api_type=arguments.get("api_type", openai.api_type),
178183
api_version=arguments.get("api_version", openai.api_version),
179184
api_key=arguments.get("api_key", openai.api_key),
@@ -228,7 +233,7 @@ def get_query_embed_fn(embedding_kind: str, arguments: dict, credential: Optiona
228233

229234
embedder = OpenAIEmbedder(
230235
model=arguments.get("model"),
231-
api_base=arguments.get("api_base", openai.api_base),
236+
api_base=arguments.get("api_base", openai.api_base if hasattr(openai, "api_base") else openai.base_url),
232237
api_type=arguments.get("api_type", openai.api_type),
233238
api_version=arguments.get("api_version", openai.api_version),
234239
api_key=arguments.get("api_key", openai.api_key),
@@ -332,7 +337,9 @@ def get_embeddings(self) -> str:
332337
def open_embedding_file(cls, path) -> pa.Table:
333338
"""Open the embedding file and cache it."""
334339
if cls._last_opened_embeddings is None or cls._last_opened_embeddings[0] != path:
335-
logger.debug(f"caching embeddings file: \n{path}\n previous path cached was: \n{cls._last_opened_embeddings}")
340+
logger.debug(
341+
f"caching embeddings file: \n{path}\n previous path cached was: \n{cls._last_opened_embeddings}"
342+
)
336343
table = pq.read_table(path)
337344
cls._last_opened_embeddings = (path, table)
338345

@@ -460,7 +467,7 @@ def get_metadata(self):
460467
if "open_ai" in self.kind:
461468
if "api_base" not in arguments:
462469
import openai
463-
arguments["api_base"] = openai.api_base
470+
arguments["api_base"] = openai.api_base if hasattr(openai, "api_base") else openai.base_url
464471
if "api_key" in arguments:
465472
del arguments["api_key"]
466473
if "key" in arguments:

sdk/ai/azure-ai-generative/azure/ai/generative/index/_embeddings/openai.py

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# ---------------------------------------------------------
44
"""OpenAI Embeddings generation and management tools."""
5+
import os
56
import time
67
from typing import Any, Dict, List, Optional
78

89
from azure.ai.generative.index._utils.logging import get_logger
10+
from packaging import version
911

1012
logger = get_logger("embeddings.openai")
1113

@@ -17,7 +19,7 @@ def __init__(
1719
self,
1820
api_base: str,
1921
api_type: str,
20-
api_version: str = "2023-03-15-preview",
22+
api_version: str = None,
2123
api_key: Optional[str] = None,
2224
azure_credential: Optional[Any] = None,
2325
model: str = "text-embedding-ada-002",
@@ -31,8 +33,7 @@ def __init__(
3133
"""Initialize an OpenAI Embedding client."""
3234
self.api_base = api_base
3335
self.api_type = api_type
34-
self.api_version = api_version
35-
self.api_key = api_key
36+
self.api_key = api_key or os.getenv("AZURE_OPENAI_KEY") or ""
3637
# TODO: If azure_credential set, check api_type is azure or azure_ad and setup auth accordingly
3738
self.azure_credential = azure_credential
3839

@@ -44,7 +45,7 @@ def __init__(
4445
self._dynamic_batch_size = None
4546

4647
if max_retries is None:
47-
max_retries = 20
48+
max_retries = 10
4849
self.max_retries = max_retries
4950

5051
if model is None:
@@ -60,14 +61,67 @@ def __init__(
6061
self.embedding_ctx_length = embedding_ctx_length
6162

6263
self.show_progress_bar = show_progress_bar
64+
self.openai_passthrough_args = openai_passthrough_args or {}
6365

6466
try:
6567
import openai
6668
except ImportError as e:
6769
raise ImportError("Please install openai via `pip install openai`") from e
6870

69-
self.openai_passthrough_args = openai_passthrough_args or {}
70-
self.embedding_client = openai.Embedding
71+
if version.parse(openai.version.VERSION) >= version.parse("1.0.0"):
72+
self.openai_v1plus = True
73+
self.api_version = api_version if api_version else "2023-05-15"
74+
75+
if "azure" in self.api_type:
76+
client = openai.AzureOpenAI(
77+
api_key=self.api_key,
78+
api_version=self.api_version,
79+
azure_endpoint=self.api_base,
80+
)
81+
else:
82+
client = openai.OpenAI(
83+
api_key=self.api_key,
84+
base_url=self.api_base,
85+
)
86+
87+
self.embedding_client = client.embeddings
88+
89+
self._params = {
90+
"model": self.model,
91+
**self.openai_passthrough_args,
92+
}
93+
self._retry_exceptions = [
94+
openai._exceptions.APIStatusError,
95+
openai._exceptions.APITimeoutError,
96+
openai._exceptions.APIError,
97+
openai._exceptions.APIConnectionError,
98+
openai._exceptions.RateLimitError,
99+
openai._exceptions.InternalServerError,
100+
openai._exceptions.APIResponseValidationError,
101+
]
102+
self._RateLimitError = openai._exceptions.RateLimitError
103+
else:
104+
self.openai_v1plus = False
105+
self.api_version = api_version if api_version else "2023-03-15-preview"
106+
self.embedding_client = openai.Embeddings
107+
self._params = {
108+
"model": self.model,
109+
"api_base": self.api_base,
110+
"api_type": self.api_type,
111+
"api_version": self.api_version,
112+
"api_key": self.api_key,
113+
**self.openai_passthrough_args,
114+
}
115+
if self.deployment is not None:
116+
self._params["engine"] = self.deployment
117+
self._retry_exceptions = [
118+
openai.error.Timeout,
119+
openai.error.APIError,
120+
openai.error.APIConnectionError,
121+
openai.error.RateLimitError,
122+
openai.error.ServiceUnavailableError,
123+
]
124+
self._RateLimitError = openai.error.RateLimitError
71125

72126
self._statistics = {
73127
"num_retries": 0,
@@ -77,28 +131,11 @@ def __init__(
77131

78132
@property
79133
def _openai_client_params(self) -> dict:
80-
params = {
81-
"model": self.model,
82-
"api_base": self.api_base,
83-
"api_type": self.api_type,
84-
"api_version": self.api_version,
85-
"api_key": self.api_key,
86-
**self.openai_passthrough_args,
87-
}
88-
if self.deployment is not None:
89-
params["engine"] = self.deployment
90-
return params
134+
return self._params
91135

92136
@property
93137
def _retryable_openai_errors(self) -> List[Exception]:
94-
import openai
95-
return [
96-
openai.error.Timeout,
97-
openai.error.APIError,
98-
openai.error.APIConnectionError,
99-
openai.error.RateLimitError,
100-
openai.error.ServiceUnavailableError,
101-
]
138+
return self._retry_exceptions
102139

103140
def _dynamic_batch_size_embed_request(self, tokenized_texts: List[List[int]], **kwargs) -> dict:
104141
try:
@@ -142,17 +179,18 @@ def _dynamic_batch_size_embed_request(self, tokenized_texts: List[List[int]], **
142179

143180
def _embed_request(self, tokenized_texts: List[List[int]], **kwargs) -> dict:
144181
try:
145-
min_seconds = 4
146-
max_seconds = 10
147182
total_delay = 0
148183
last_exception = None
149184
for retry in range(self.max_retries):
150185
logger.info(f"Attempt {retry} to embed {len(tokenized_texts)} documents.")
151186
try:
152-
return self.embedding_client.create(
187+
response = self.embedding_client.create(
153188
input=tokenized_texts,
154189
**kwargs,
155190
)
191+
if self.openai_v1plus:
192+
response = {"object": "list", "data": [{"object": "embedding", "embedding": d.embedding} for d in response.data]}
193+
return response
156194
except Exception as e:
157195
err_msg = str(e)
158196
logger.warning(f"Error embedding: {err_msg}", exc_info=e)
@@ -161,16 +199,18 @@ def _embed_request(self, tokenized_texts: List[List[int]], **kwargs) -> dict:
161199
for retryable_error in self._retryable_openai_errors:
162200
if isinstance(e, retryable_error):
163201
retrying = True
164-
import openai
165-
166-
# Retry with retry-after set by openai for RateLimitError
167-
if isinstance(e, openai.error.RateLimitError) and "Retry-After" in e.headers:
168-
delay = int(e.headers["Retry-After"])
169-
logger.warning(f"OpenAI throws RateLimitError with Retry-After set to {delay}")
170-
# Retry with exponential backoff
171-
else:
172-
exp = 2 ** (retry - 1)
173-
delay = max(min(1 * exp, max_seconds), min_seconds)
202+
203+
# Retry with retry-after if found in RateLimitError
204+
if isinstance(e, self._RateLimitError):
205+
logger.warning(f"Retrying error type {type(e)}.")
206+
response_headers = e.headers if hasattr(e, "headers") else {}
207+
if "Retry-After" in response_headers:
208+
delay = int(response_headers["Retry-After"])
209+
logger.warning(f"OpenAI throws RateLimitError with Retry-After {delay} seconds.")
210+
else:
211+
# Wait for 1 minute as suggested by openai https://help.openai.com/en/articles/6897202-ratelimiterror
212+
logger.warning("Retry after 60 seconds.")
213+
delay = 60
174214
total_delay += delay
175215
logger.warning(f"Sleeping for {delay} seconds before retrying.")
176216
time.sleep(delay)

sdk/ai/azure-ai-generative/azure/ai/generative/index/_langchain/openai.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ def __call__(self, retry_state) -> bool:
5555
# Copied from https://github.com/hwchase17/langchain/blob/511c12dd3985ce682226371c12f8fa70d8c9a8e1/langchain/embeddings/openai.py#L34
5656
def _create_retry_decorator(embeddings):
5757
import openai
58+
from packaging import version
59+
60+
if version.parse(openai.version.VERSION) >= version.parse("1.0.0"):
61+
retry_exceptions = (
62+
retry_if_exception_type(openai._exceptions.APITimeoutError)
63+
| retry_if_exception_type(openai._exceptions.APIError)
64+
| retry_if_exception_type(openai._exceptions.APIConnectionError)
65+
| retry_if_exception_type(openai._exceptions.RateLimitError)
66+
| retry_if_exception_type(openai._exceptions.InternalServerError)
67+
| retry_if_exception_type(openai._exceptions.APIResponseValidationError)
68+
)
69+
else:
70+
retry_exceptions = (
71+
retry_if_exception_type(openai.error.Timeout)
72+
| retry_if_exception_type(openai.error.APIError)
73+
| retry_if_exception_type(openai.error.APIConnectionError)
74+
| retry_if_exception_type(openai.error.RateLimitError)
75+
| retry_if_exception_type(openai.error.ServiceUnavailableError)
76+
)
5877

5978
min_seconds = 4
6079
max_seconds = 10
@@ -65,13 +84,7 @@ def _create_retry_decorator(embeddings):
6584
# stop=stop_after_attempt(embeddings.max_retries),
6685
stop=stop_after_delay_that_works(max_seconds_retrying, activity_logger),
6786
wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds),
68-
retry=(
69-
retry_if_exception_type(openai.error.Timeout)
70-
| retry_if_exception_type(openai.error.APIError)
71-
| retry_if_exception_type(openai.error.APIConnectionError)
72-
| retry_if_exception_type(openai.error.RateLimitError)
73-
| retry_if_exception_type(openai.error.ServiceUnavailableError)
74-
),
87+
retry=retry_exceptions,
7588
before_sleep=_log_it,
7689
)
7790

sdk/ai/azure-ai-generative/azure/ai/generative/index/_mlindex.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ def as_langchain_vectorstore(self, credential: Optional[TokenCredential] = None)
155155
activity_logger.activity_info["embeddings_kind"] = self.embeddings_config.get("kind", "none")
156156
activity_logger.activity_info["embeddings_api_type"] = self.embeddings_config.get("api_type", "none")
157157

158+
langchain_pkg_version = pkg_version.parse(langchain_version)
159+
158160
if index_kind == "acs":
159161
from azure.ai.generative.index._indexes.azure_search import import_azure_search_or_so_help_me
160162

@@ -252,6 +254,11 @@ def as_langchain_vectorstore(self, credential: Optional[TokenCredential] = None)
252254
self.embeddings_config.copy()
253255
).as_langchain_embeddings(credential=credential)
254256

257+
258+
# langchain fix https://github.com/langchain-ai/langchain/pull/10823 released in 0.0.318
259+
if langchain_pkg_version >= pkg_version.parse("0.0.318"):
260+
embeddings = embeddings.embed_query
261+
255262
fs, uri = url_to_fs(self.base_uri)
256263

257264
with tempfile.TemporaryDirectory() as tmpdir:

sdk/ai/azure-ai-generative/azure/ai/generative/index/_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def init_open_ai_from_config(config: dict, credential: Optional[TokenCredential]
120120
logger.warning(f"Failed to get credential for ACS with {e}, falling back to env vars.")
121121
config["api_key"] = os.environ["OPENAI_API_KEY"]
122122
config["api_type"] = os.environ.get("OPENAI_API_TYPE", "azure")
123-
config["api_base"] = os.environ.get("OPENAI_API_BASE", openai.api_base)
123+
config["api_base"] = os.environ.get("OPENAI_API_BASE", openai.api_base if hasattr(openai, "api_base") else openai.base_url)
124124
config["api_version"] = os.environ.get("OPENAI_API_VERSION", openai.api_version)
125125
else:
126126
raise e

sdk/ai/azure-ai-generative/azure/ai/generative/index/_tasks/generate_qa.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ def get_model_config(llm_config: Dict[str, str], openai_api_type: str, openai_ap
7272

7373
# Only add base and version if using AOAI
7474
if model_config["api_type"] == "azure":
75-
openai.api_base = model_config["api_base"]
75+
# openai.api_base is replaced by openai.base_url in openai 1.x
76+
if hasattr(openai, "api_base"):
77+
openai.api_base = model_config["api_base"]
78+
else:
79+
openai.base_url = model_config["api_base"]
7680
openai.api_version = model_config["api_version"]
7781
return model_config
7882

sdk/ai/azure-ai-generative/azure/ai/generative/index/_utils/deployment.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ def infer_deployment(aoai_connection, model_name):
2222
openai.api_version = connection_metadata.get(
2323
"ApiVersion", connection_metadata.get("apiVersion", "2023-03-15-preview")
2424
)
25-
openai.api_base = get_target_from_connection(aoai_connection)
25+
api_base = get_target_from_connection(aoai_connection)
26+
if hasattr(openai, "api_base"):
27+
openai.api_base = api_base
28+
else:
29+
openai.base_url = api_base
2630
credential = connection_to_credential(aoai_connection)
2731
openai.api_key = credential.key if isinstance(credential, AzureKeyCredential) else credential.get_token().token
2832
deployment_list = convert_to_dict(
29-
Deployment.list(api_key=openai.api_key, api_base=openai.api_base, api_type=openai.api_type)
33+
Deployment.list(api_key=openai.api_key, api_base=api_base, api_type=openai.api_type)
3034
)
3135
for deployment in deployment_list["data"]:
3236
if deployment["model"] == model_name:

0 commit comments

Comments
 (0)