Skip to content

Commit aa02563

Browse files
Upgrade OpenAI SDK to v1 (#1017)
* Update version ranges for 1.3.5 openai lib * Update the embeddings library in scripts to use OpenAI 1.3.5 remove some redundant methods * Update the embedding response to use a typed model * Rewrite test_prepdocs to patch out OpenAI v1 models and responses * Update approaches to use new APIs * Update backend service and read approaches to use new SDK * Fix get_search_query. Update RRR approach tests * Update search manager tests * Change patching for app tests * Use deployment ID only in the constructor of the Azure OpenAI Client object and remove it from the approach constructors (and all the logic that went with it) * Explicitly include aiohttp in prepdocs requirements * Use two clients because the new SDK doesn't support a deployment name in the chat or embeddings methods * Ruff ruff * Simplify typing constructor * Update types for message history * Convert RRR to dict before returning * Bend the rules of physics to get mypy to pass * Run black over scripts again * Fix content filtering, update snapshot tests, implement pydantic models for streaming responses. * Update the snapshots with the new required fields for chunked completions. Update the iterator to pass pydantic model validation * Force keyword arguments as the list of arguments is long and complicated * Refactor to have a single client object * Drop argument * Type the chat message builder with pydantic * Rebuild requirements from merge conflicts * Update formatting * Fix issue with follow-up questions * Simplify content check * Don't use deployment field for non azure * Update requirements.in * Remove upper bound * Remove dependabot constraint * Merge the clients again * Fix test_app client name * Inline the ternary statement to pick either a model or deployment name for the OpenAI SDK calls --------- Co-authored-by: Pamela Fox <[email protected]> Co-authored-by: Pamela Fox <[email protected]>
1 parent da24cf4 commit aa02563

File tree

55 files changed

+877
-402
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+877
-402
lines changed

.github/dependabot.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,3 @@ updates:
3232
- "*"
3333
ignore:
3434
- dependency-name: azure-search-documents
35-
- dependency-name: openai[datalib]

app/backend/app.py

Lines changed: 46 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@
33
import logging
44
import mimetypes
55
import os
6-
import time
76
from pathlib import Path
87
from typing import AsyncGenerator
98

10-
import aiohttp
11-
import openai
129
from azure.core.exceptions import ResourceNotFoundError
13-
from azure.identity.aio import DefaultAzureCredential
10+
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
1411
from azure.monitor.opentelemetry import configure_azure_monitor
1512
from azure.search.documents.aio import SearchClient
1613
from azure.storage.blob.aio import BlobServiceClient
14+
from openai import APIError, AsyncAzureOpenAI, AsyncOpenAI
1715
from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor
1816
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
17+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
1918
from quart import (
2019
Blueprint,
2120
Quart,
@@ -33,13 +32,12 @@
3332
from approaches.retrievethenread import RetrieveThenReadApproach
3433
from core.authentication import AuthenticationHelper
3534

36-
CONFIG_OPENAI_TOKEN = "openai_token"
37-
CONFIG_CREDENTIAL = "azure_credential"
3835
CONFIG_ASK_APPROACH = "ask_approach"
3936
CONFIG_CHAT_APPROACH = "chat_approach"
4037
CONFIG_BLOB_CONTAINER_CLIENT = "blob_container_client"
4138
CONFIG_AUTH_CLIENT = "auth_client"
4239
CONFIG_SEARCH_CLIENT = "search_client"
40+
CONFIG_OPENAI_CLIENT = "openai_client"
4341
ERROR_MESSAGE = """The app encountered an error processing your request.
4442
If you are an administrator of the app, view the full error in the logs. See aka.ms/appservice-logs for more information.
4543
Error type: {error_type}
@@ -102,14 +100,14 @@ async def content_file(path: str):
102100

103101

104102
def error_dict(error: Exception) -> dict:
105-
if isinstance(error, openai.error.InvalidRequestError) and error.code == "content_filter":
103+
if isinstance(error, APIError) and error.code == "content_filter":
106104
return {"error": ERROR_MESSAGE_FILTER}
107105
return {"error": ERROR_MESSAGE.format(error_type=type(error))}
108106

109107

110108
def error_response(error: Exception, route: str, status_code: int = 500):
111109
logging.exception("Exception in %s: %s", route, error)
112-
if isinstance(error, openai.error.InvalidRequestError) and error.code == "content_filter":
110+
if isinstance(error, APIError) and error.code == "content_filter":
113111
status_code = 400
114112
return jsonify(error_dict(error)), status_code
115113

@@ -124,12 +122,9 @@ async def ask():
124122
context["auth_claims"] = await auth_helper.get_auth_claims_if_enabled(request.headers)
125123
try:
126124
approach = current_app.config[CONFIG_ASK_APPROACH]
127-
# Workaround for: https://github.com/openai/openai-python/issues/371
128-
async with aiohttp.ClientSession() as s:
129-
openai.aiosession.set(s)
130-
r = await approach.run(
131-
request_json["messages"], context=context, session_state=request_json.get("session_state")
132-
)
125+
r = await approach.run(
126+
request_json["messages"], context=context, session_state=request_json.get("session_state")
127+
)
133128
return jsonify(r)
134129
except Exception as error:
135130
return error_response(error, "/ask")
@@ -178,19 +173,6 @@ def auth_setup():
178173
return jsonify(auth_helper.get_auth_setup_for_client())
179174

180175

181-
@bp.before_request
182-
async def ensure_openai_token():
183-
if openai.api_type != "azure_ad":
184-
return
185-
openai_token = current_app.config[CONFIG_OPENAI_TOKEN]
186-
if openai_token.expires_on < time.time() + 60:
187-
openai_token = await current_app.config[CONFIG_CREDENTIAL].get_token(
188-
"https://cognitiveservices.azure.com/.default"
189-
)
190-
current_app.config[CONFIG_OPENAI_TOKEN] = openai_token
191-
openai.api_key = openai_token.token
192-
193-
194176
@bp.before_app_serving
195177
async def setup_clients():
196178
# Replace these with your own values, either in environment variables or directly here
@@ -204,8 +186,8 @@ async def setup_clients():
204186
OPENAI_EMB_MODEL = os.getenv("AZURE_OPENAI_EMB_MODEL_NAME", "text-embedding-ada-002")
205187
# Used with Azure OpenAI deployments
206188
AZURE_OPENAI_SERVICE = os.getenv("AZURE_OPENAI_SERVICE")
207-
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT")
208-
AZURE_OPENAI_EMB_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMB_DEPLOYMENT")
189+
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT") if OPENAI_HOST == "azure" else None
190+
AZURE_OPENAI_EMB_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMB_DEPLOYMENT") if OPENAI_HOST == "azure" else None
209191
# Used only with non-Azure OpenAI deployments
210192
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
211193
OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
@@ -250,50 +232,53 @@ async def setup_clients():
250232
blob_container_client = blob_client.get_container_client(AZURE_STORAGE_CONTAINER)
251233

252234
# Used by the OpenAI SDK
235+
openai_client: AsyncOpenAI
236+
253237
if OPENAI_HOST == "azure":
254-
openai.api_type = "azure_ad"
255-
openai.api_base = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
256-
openai.api_version = "2023-07-01-preview"
257-
openai_token = await azure_credential.get_token("https://cognitiveservices.azure.com/.default")
258-
openai.api_key = openai_token.token
238+
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
259239
# Store on app.config for later use inside requests
260-
current_app.config[CONFIG_OPENAI_TOKEN] = openai_token
240+
openai_client = AsyncAzureOpenAI(
241+
api_version="2023-07-01-preview",
242+
azure_endpoint=f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com",
243+
azure_ad_token_provider=token_provider,
244+
)
261245
else:
262-
openai.api_type = "openai"
263-
openai.api_key = OPENAI_API_KEY
264-
openai.organization = OPENAI_ORGANIZATION
246+
openai_client = AsyncOpenAI(
247+
api_key=OPENAI_API_KEY,
248+
organization=OPENAI_ORGANIZATION,
249+
)
265250

266-
current_app.config[CONFIG_CREDENTIAL] = azure_credential
251+
current_app.config[CONFIG_OPENAI_CLIENT] = openai_client
267252
current_app.config[CONFIG_SEARCH_CLIENT] = search_client
268253
current_app.config[CONFIG_BLOB_CONTAINER_CLIENT] = blob_container_client
269254
current_app.config[CONFIG_AUTH_CLIENT] = auth_helper
270255

271256
# Various approaches to integrate GPT and external knowledge, most applications will use a single one of these patterns
272257
# or some derivative, here we include several for exploration purposes
273258
current_app.config[CONFIG_ASK_APPROACH] = RetrieveThenReadApproach(
274-
search_client,
275-
OPENAI_HOST,
276-
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
277-
OPENAI_CHATGPT_MODEL,
278-
AZURE_OPENAI_EMB_DEPLOYMENT,
279-
OPENAI_EMB_MODEL,
280-
KB_FIELDS_SOURCEPAGE,
281-
KB_FIELDS_CONTENT,
282-
AZURE_SEARCH_QUERY_LANGUAGE,
283-
AZURE_SEARCH_QUERY_SPELLER,
259+
search_client=search_client,
260+
openai_client=openai_client,
261+
chatgpt_model=OPENAI_CHATGPT_MODEL,
262+
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
263+
embedding_model=OPENAI_EMB_MODEL,
264+
embedding_deployment=AZURE_OPENAI_EMB_DEPLOYMENT,
265+
sourcepage_field=KB_FIELDS_SOURCEPAGE,
266+
content_field=KB_FIELDS_CONTENT,
267+
query_language=AZURE_SEARCH_QUERY_LANGUAGE,
268+
query_speller=AZURE_SEARCH_QUERY_SPELLER,
284269
)
285270

286271
current_app.config[CONFIG_CHAT_APPROACH] = ChatReadRetrieveReadApproach(
287-
search_client,
288-
OPENAI_HOST,
289-
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
290-
OPENAI_CHATGPT_MODEL,
291-
AZURE_OPENAI_EMB_DEPLOYMENT,
292-
OPENAI_EMB_MODEL,
293-
KB_FIELDS_SOURCEPAGE,
294-
KB_FIELDS_CONTENT,
295-
AZURE_SEARCH_QUERY_LANGUAGE,
296-
AZURE_SEARCH_QUERY_SPELLER,
272+
search_client=search_client,
273+
openai_client=openai_client,
274+
chatgpt_model=OPENAI_CHATGPT_MODEL,
275+
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
276+
embedding_model=OPENAI_EMB_MODEL,
277+
embedding_deployment=AZURE_OPENAI_EMB_DEPLOYMENT,
278+
sourcepage_field=KB_FIELDS_SOURCEPAGE,
279+
content_field=KB_FIELDS_CONTENT,
280+
query_language=AZURE_SEARCH_QUERY_LANGUAGE,
281+
query_speller=AZURE_SEARCH_QUERY_SPELLER,
297282
)
298283

299284

@@ -305,6 +290,8 @@ def create_app():
305290
configure_azure_monitor()
306291
# This tracks HTTP requests made by aiohttp:
307292
AioHttpClientInstrumentor().instrument()
293+
# This tracks HTTP requests made by httpx/openai:
294+
HTTPXClientInstrumentor().instrument()
308295
# This middleware tracks app route requests:
309296
app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) # type: ignore[method-assign]
310297

0 commit comments

Comments
 (0)