Skip to content

Commit 62e2248

Browse files
Adding support for non-Azure openai instances (#507)
* Adding openai non-azure endpoint support * Editing infra files * Adding missing args for the prepdocs script * Rolling back indentation changes * rolling back indentiation edits * removing unnecessary parenthesis * Adding OPENAI_EMB_MODEL parameter to bicep config * Adding conditional args to reduce if statements * Rm notebooks again * OpenAI api type * Tests pass * Fix bicep issue * Prepdocs fix * Add main.bicep changes * Add main.bicep changes * Add missing api_type * Parameterize tests * Rm comma * Making chatGptModelName param conditional * Conditional system identity * Make Azure_openai_service conditional * fix inconsistencies across emb model param naming * Fix formatting after merge from main * Script fix --------- Co-authored-by: Pamela Fox <[email protected]> Co-authored-by: Pamela Fox <[email protected]>
1 parent 38dd16c commit 62e2248

File tree

37 files changed

+320
-79
lines changed

37 files changed

+320
-79
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The repo includes sample data so it's ready to try end to end. In this sample ap
4949
**IMPORTANT:** In order to deploy and run this example, you'll need:
5050

5151
* **Azure account**. If you're new to Azure, [get an Azure account for free](https://azure.microsoft.com/free/cognitive-search/) and you'll get some free Azure credits to get started.
52-
* **Azure subscription with access enabled for the Azure OpenAI service**. You can request access with [this form](https://aka.ms/oaiapply).
52+
* **Azure subscription with access enabled for the Azure OpenAI service**. You can request access with [this form](https://aka.ms/oaiapply). If your access request to Azure OpenAI service doesn't match the [acceptance criteria](https://learn.microsoft.com/legal/cognitive-services/openai/limited-access?context=%2Fazure%2Fcognitive-services%2Fopenai%2Fcontext%2Fcontext), you can use [OpenAI public API](https://platform.openai.com/docs/api-reference/introduction) instead. Learn [how to switch to an OpenAI instance](#switching-from-an-azure-openai-endpoint-to-an-openai-instance).
5353
* **Azure account permissions**: Your Azure account must have `Microsoft.Authorization/roleAssignments/write` permissions, such as [Role Based Access Control Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator-preview), [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator), or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner).
5454

5555
## Azure deployment
@@ -176,6 +176,20 @@ either you or they can follow these steps:
176176

177177
## Enabling optional features
178178

179+
#### Using a non-Azure OpenAI instance
180+
181+
To use an existing non-Azure OpenAI account, follow these steps before running `azd up`
182+
183+
1. Run `azd env set OPENAI_HOST openai`
184+
2. Run `azd env set OPENAI_ORGANIZATION {Your OpenAI organization}`
185+
3. Run `azd env set OPENAI_API_KEY {Your OpenAI API key}`
186+
4. Run `azd up`
187+
188+
You can retrieve your OpenAI key by checking [your user page](https://platform.openai.com/account/api-keys) and your organization by navigating to [your organization page](https://platform.openai.com/account/org-settings).
189+
Learn more about creating an OpenAI free trial at [this link](https://openai.com/pricing).
190+
Do *not* check your key into source control.
191+
192+
179193
### Enabling Application Insights
180194

181195
To enable Application Insights and the tracing of each request, along with the logging of errors, set the `AZURE_USE_APPLICATION_INSIGHTS` variable to true before running `azd up`
@@ -258,6 +272,17 @@ to production. Here are some things to consider:
258272
* [Revolutionize your Enterprise Data with ChatGPT: Next-gen Apps w/ Azure OpenAI and Cognitive Search](https://aka.ms/entgptsearchblog)
259273
* [Azure Cognitive Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search)
260274
* [Azure OpenAI Service](https://learn.microsoft.com/azure/cognitive-services/openai/overview)
275+
* [Comparing Azure OpenAI and OpenAI](https://learn.microsoft.com/en-gb/azure/cognitive-services/openai/overview#comparing-azure-openai-and-openai/)
276+
277+
## Clean up
278+
279+
To clean up all the resources created by this sample:
280+
281+
1. Run `azd down`
282+
2. When asked if you are sure you want to continue, enter `y`
283+
3. When asked if you want to permanently delete the resources, enter `y`
284+
285+
The resource group and all the resources will be deleted.
261286

262287
### Note
263288

app/backend/app.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ async def chat_stream():
139139

140140
@bp.before_request
141141
async def ensure_openai_token():
142+
if openai.api_type != "azure_ad":
143+
return
142144
openai_token = current_app.config[CONFIG_OPENAI_TOKEN]
143145
if openai_token.expires_on < time.time() + 60:
144146
openai_token = await current_app.config[CONFIG_CREDENTIAL].get_token(
@@ -155,10 +157,17 @@ async def setup_clients():
155157
AZURE_STORAGE_CONTAINER = os.environ["AZURE_STORAGE_CONTAINER"]
156158
AZURE_SEARCH_SERVICE = os.environ["AZURE_SEARCH_SERVICE"]
157159
AZURE_SEARCH_INDEX = os.environ["AZURE_SEARCH_INDEX"]
158-
AZURE_OPENAI_SERVICE = os.environ["AZURE_OPENAI_SERVICE"]
159-
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.environ["AZURE_OPENAI_CHATGPT_DEPLOYMENT"]
160-
AZURE_OPENAI_CHATGPT_MODEL = os.environ["AZURE_OPENAI_CHATGPT_MODEL"]
161-
AZURE_OPENAI_EMB_DEPLOYMENT = os.environ["AZURE_OPENAI_EMB_DEPLOYMENT"]
160+
# Shared by all OpenAI deployments
161+
OPENAI_HOST = os.getenv("OPENAI_HOST", "azure")
162+
OPENAI_CHATGPT_MODEL = os.environ["AZURE_OPENAI_CHATGPT_MODEL"]
163+
OPENAI_EMB_MODEL = os.getenv("AZURE_OPENAI_EMB_MODEL_NAME", "text-embedding-ada-002")
164+
# Used with Azure OpenAI deployments
165+
AZURE_OPENAI_SERVICE = os.getenv("AZURE_OPENAI_SERVICE")
166+
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT")
167+
AZURE_OPENAI_EMB_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMB_DEPLOYMENT")
168+
# Used only with non-Azure OpenAI deployments
169+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
170+
OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
162171

163172
KB_FIELDS_CONTENT = os.getenv("KB_FIELDS_CONTENT", "content")
164173
KB_FIELDS_SOURCEPAGE = os.getenv("KB_FIELDS_SOURCEPAGE", "sourcepage")
@@ -181,14 +190,19 @@ async def setup_clients():
181190
blob_container_client = blob_client.get_container_client(AZURE_STORAGE_CONTAINER)
182191

183192
# Used by the OpenAI SDK
184-
openai.api_base = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
185-
openai.api_version = "2023-05-15"
186-
openai.api_type = "azure_ad"
187-
openai_token = await azure_credential.get_token("https://cognitiveservices.azure.com/.default")
188-
openai.api_key = openai_token.token
189-
190-
# Store on app.config for later use inside requests
191-
current_app.config[CONFIG_OPENAI_TOKEN] = openai_token
193+
if OPENAI_HOST == "azure":
194+
openai.api_type = "azure_ad"
195+
openai.api_base = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
196+
openai.api_version = "2023-05-15"
197+
openai_token = await azure_credential.get_token("https://cognitiveservices.azure.com/.default")
198+
openai.api_key = openai_token.token
199+
# Store on app.config for later use inside requests
200+
current_app.config[CONFIG_OPENAI_TOKEN] = openai_token
201+
else:
202+
openai.api_type = "openai"
203+
openai.api_key = OPENAI_API_KEY
204+
openai.organization = OPENAI_ORGANIZATION
205+
192206
current_app.config[CONFIG_CREDENTIAL] = azure_credential
193207
current_app.config[CONFIG_BLOB_CONTAINER_CLIENT] = blob_container_client
194208

@@ -197,33 +211,43 @@ async def setup_clients():
197211
current_app.config[CONFIG_ASK_APPROACHES] = {
198212
"rtr": RetrieveThenReadApproach(
199213
search_client,
214+
OPENAI_HOST,
200215
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
201-
AZURE_OPENAI_CHATGPT_MODEL,
216+
OPENAI_CHATGPT_MODEL,
202217
AZURE_OPENAI_EMB_DEPLOYMENT,
218+
OPENAI_EMB_MODEL,
203219
KB_FIELDS_SOURCEPAGE,
204220
KB_FIELDS_CONTENT,
205221
),
206222
"rrr": ReadRetrieveReadApproach(
207223
search_client,
224+
OPENAI_HOST,
208225
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
226+
OPENAI_CHATGPT_MODEL,
209227
AZURE_OPENAI_EMB_DEPLOYMENT,
228+
OPENAI_EMB_MODEL,
210229
KB_FIELDS_SOURCEPAGE,
211230
KB_FIELDS_CONTENT,
212231
),
213232
"rda": ReadDecomposeAsk(
214233
search_client,
234+
OPENAI_HOST,
215235
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
236+
OPENAI_CHATGPT_MODEL,
216237
AZURE_OPENAI_EMB_DEPLOYMENT,
238+
OPENAI_EMB_MODEL,
217239
KB_FIELDS_SOURCEPAGE,
218240
KB_FIELDS_CONTENT,
219241
),
220242
}
221243
current_app.config[CONFIG_CHAT_APPROACHES] = {
222244
"rrr": ChatReadRetrieveReadApproach(
223245
search_client,
246+
OPENAI_HOST,
224247
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
225-
AZURE_OPENAI_CHATGPT_MODEL,
248+
OPENAI_CHATGPT_MODEL,
226249
AZURE_OPENAI_EMB_DEPLOYMENT,
250+
OPENAI_EMB_MODEL,
227251
KB_FIELDS_SOURCEPAGE,
228252
KB_FIELDS_CONTENT,
229253
)

app/backend/approaches/chatreadretrieveread.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,20 @@ class ChatReadRetrieveReadApproach:
5050
def __init__(
5151
self,
5252
search_client: SearchClient,
53+
openai_host: str,
5354
chatgpt_deployment: str,
5455
chatgpt_model: str,
5556
embedding_deployment: str,
57+
embedding_model: str,
5658
sourcepage_field: str,
5759
content_field: str,
5860
):
5961
self.search_client = search_client
62+
self.openai_host = openai_host
6063
self.chatgpt_deployment = chatgpt_deployment
6164
self.chatgpt_model = chatgpt_model
6265
self.embedding_deployment = embedding_deployment
66+
self.embedding_model = embedding_model
6367
self.sourcepage_field = sourcepage_field
6468
self.content_field = content_field
6569
self.chatgpt_token_limit = get_token_limit(chatgpt_model)
@@ -86,8 +90,9 @@ async def run_until_final_call(
8690
self.chatgpt_token_limit - len(user_q),
8791
)
8892

93+
chatgpt_args = {"deployment_id": self.chatgpt_deployment} if self.openai_host == "azure" else {}
8994
chat_completion = await openai.ChatCompletion.acreate(
90-
deployment_id=self.chatgpt_deployment,
95+
**chatgpt_args,
9196
model=self.chatgpt_model,
9297
messages=messages,
9398
temperature=0.0,
@@ -103,7 +108,8 @@ async def run_until_final_call(
103108

104109
# If retrieval mode includes vectors, compute an embedding for the query
105110
if has_vector:
106-
embedding = await openai.Embedding.acreate(engine=self.embedding_deployment, input=query_text)
111+
embedding_args = {"deployment_id": self.embedding_deployment} if self.openai_host == "azure" else {}
112+
embedding = await openai.Embedding.acreate(**embedding_args, model=self.embedding_model, input=query_text)
107113
query_vector = embedding["data"][0]["embedding"]
108114
else:
109115
query_vector = None
@@ -181,7 +187,7 @@ async def run_until_final_call(
181187
+ msg_to_display.replace("\n", "<br>"),
182188
}
183189
chat_coroutine = openai.ChatCompletion.acreate(
184-
deployment_id=self.chatgpt_deployment,
190+
**chatgpt_args,
185191
model=self.chatgpt_model,
186192
messages=messages,
187193
temperature=overrides.get("temperature") or 0.7,

app/backend/approaches/readdecomposeask.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from langchain.agents import AgentExecutor, Tool
88
from langchain.agents.react.base import ReActDocstoreAgent
99
from langchain.callbacks.manager import CallbackManager
10-
from langchain.llms.openai import AzureOpenAI
10+
from langchain.llms.openai import AzureOpenAI, OpenAI
1111
from langchain.prompts import BasePromptTemplate, PromptTemplate
1212
from langchain.tools.base import BaseTool
1313

@@ -20,16 +20,22 @@ class ReadDecomposeAsk(AskApproach):
2020
def __init__(
2121
self,
2222
search_client: SearchClient,
23+
openai_host: str,
2324
openai_deployment: str,
25+
openai_model: str,
2426
embedding_deployment: str,
27+
embedding_model: str,
2528
sourcepage_field: str,
2629
content_field: str,
2730
):
2831
self.search_client = search_client
2932
self.openai_deployment = openai_deployment
33+
self.openai_model = openai_model
3034
self.embedding_deployment = embedding_deployment
35+
self.embedding_model = embedding_model
3136
self.sourcepage_field = sourcepage_field
3237
self.content_field = content_field
38+
self.openai_host = openai_host
3339

3440
async def search(self, query_text: str, overrides: dict[str, Any]) -> tuple[list[str], str]:
3541
has_text = overrides.get("retrieval_mode") in ["text", "hybrid", None]
@@ -41,7 +47,8 @@ async def search(self, query_text: str, overrides: dict[str, Any]) -> tuple[list
4147

4248
# If retrieval mode includes vectors, compute an embedding for the query
4349
if has_vector:
44-
embedding = await openai.Embedding.acreate(engine=self.embedding_deployment, input=query_text)
50+
embedding_args = {"deployment_id": self.embedding_deployment} if self.openai_host == "azure" else {}
51+
embedding = await openai.Embedding.acreate(**embedding_args, model=self.embedding_model, input=query_text)
4552
query_vector = embedding["data"][0]["embedding"]
4653
else:
4754
query_vector = None
@@ -74,7 +81,7 @@ async def search(self, query_text: str, overrides: dict[str, Any]) -> tuple[list
7481
vector_fields="embedding" if query_vector else None,
7582
)
7683
if use_semantic_captions:
77-
results = [
84+
self.results = [
7885
doc[self.sourcepage_field] + ":" + nonewlines(" . ".join([c.text for c in doc["@search.captions"]]))
7986
async for doc in r
8087
]
@@ -114,11 +121,18 @@ async def search_and_store(q: str) -> Any:
114121
cb_handler = HtmlCallbackHandler()
115122
cb_manager = CallbackManager(handlers=[cb_handler])
116123

117-
llm = AzureOpenAI(
118-
deployment_name=self.openai_deployment,
119-
temperature=overrides.get("temperature") or 0.3,
120-
openai_api_key=openai.api_key,
121-
)
124+
if self.openai_host == "azure":
125+
llm = AzureOpenAI(
126+
deployment_name=self.openai_deployment,
127+
temperature=overrides.get("temperature", 0.3),
128+
openai_api_key=openai.api_key,
129+
)
130+
else:
131+
llm = OpenAI(
132+
model_name=self.openai_model,
133+
temperature=overrides.get("temperature", 0.3),
134+
openai_api_key=openai.api_key,
135+
)
122136
tools = [
123137
Tool(
124138
name="Search",

app/backend/approaches/readretrieveread.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from langchain.agents import AgentExecutor, Tool, ZeroShotAgent
77
from langchain.callbacks.manager import CallbackManager, Callbacks
88
from langchain.chains import LLMChain
9-
from langchain.llms.openai import AzureOpenAI
9+
from langchain.llms.openai import AzureOpenAI, OpenAI
1010

1111
from approaches.approach import AskApproach
1212
from langchainadapters import HtmlCallbackHandler
@@ -51,16 +51,22 @@ class ReadRetrieveReadApproach(AskApproach):
5151
def __init__(
5252
self,
5353
search_client: SearchClient,
54+
openai_host: str,
5455
openai_deployment: str,
56+
openai_model: str,
5557
embedding_deployment: str,
58+
embedding_model: str,
5659
sourcepage_field: str,
5760
content_field: str,
5861
):
5962
self.search_client = search_client
6063
self.openai_deployment = openai_deployment
64+
self.openai_model = openai_model
6165
self.embedding_deployment = embedding_deployment
66+
self.embedding_model = embedding_model
6267
self.sourcepage_field = sourcepage_field
6368
self.content_field = content_field
69+
self.openai_host = openai_host
6470

6571
async def retrieve(self, query_text: str, overrides: dict[str, Any]) -> Any:
6672
has_text = overrides.get("retrieval_mode") in ["text", "hybrid", None]
@@ -72,7 +78,8 @@ async def retrieve(self, query_text: str, overrides: dict[str, Any]) -> Any:
7278

7379
# If retrieval mode includes vectors, compute an embedding for the query
7480
if has_vector:
75-
embedding = await openai.Embedding.acreate(engine=self.embedding_deployment, input=query_text)
81+
embedding_args = {"deployment_id": self.embedding_deployment} if self.openai_host == "azure" else {}
82+
embedding = await openai.Embedding.acreate(**embedding_args, model=self.embedding_model, input=query_text)
7683
query_vector = embedding["data"][0]["embedding"]
7784
else:
7885
query_vector = None
@@ -143,11 +150,19 @@ async def retrieve_and_store(q: str) -> Any:
143150
suffix=overrides.get("prompt_template_suffix") or self.template_suffix,
144151
input_variables=["input", "agent_scratchpad"],
145152
)
146-
llm = AzureOpenAI(
147-
deployment_name=self.openai_deployment,
148-
temperature=overrides.get("temperature") or 0.3,
149-
openai_api_key=openai.api_key,
150-
)
153+
if self.openai_type == "azure":
154+
llm = AzureOpenAI(
155+
deployment_name=self.openai_deployment,
156+
temperature=overrides.get("temperature", 0.3),
157+
openai_api_key=openai.api_key,
158+
)
159+
else:
160+
llm = OpenAI(
161+
model_name=self.openai_model,
162+
temperature=overrides.get("temperature", 0.3),
163+
openai_api_key=openai.api_key,
164+
)
165+
151166
chain = LLMChain(llm=llm, prompt=prompt)
152167
agent_exec = AgentExecutor.from_agent_and_tools(
153168
agent=ZeroShotAgent(llm_chain=chain), tools=tools, verbose=True, callback_manager=cb_manager

app/backend/approaches/retrievethenread.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,19 @@ class RetrieveThenReadApproach(AskApproach):
4040
def __init__(
4141
self,
4242
search_client: SearchClient,
43-
openai_deployment: str,
43+
openai_host: str,
44+
chatgpt_deployment: str,
4445
chatgpt_model: str,
4546
embedding_deployment: str,
47+
embedding_model: str,
4648
sourcepage_field: str,
4749
content_field: str,
4850
):
4951
self.search_client = search_client
50-
self.openai_deployment = openai_deployment
52+
self.openai_host = openai_host
53+
self.chatgpt_deployment = chatgpt_deployment
5154
self.chatgpt_model = chatgpt_model
55+
self.embedding_model = embedding_model
5256
self.embedding_deployment = embedding_deployment
5357
self.sourcepage_field = sourcepage_field
5458
self.content_field = content_field
@@ -63,7 +67,8 @@ async def run(self, q: str, overrides: dict[str, Any]) -> dict[str, Any]:
6367

6468
# If retrieval mode includes vectors, compute an embedding for the query
6569
if has_vector:
66-
embedding = await openai.Embedding.acreate(engine=self.embedding_deployment, input=q)
70+
embedding_args = {"deployment_id": self.embedding_deployment} if self.openai_host == "azure" else {}
71+
embedding = await openai.Embedding.acreate(**embedding_args, model=self.embedding_model, input=q)
6772
query_vector = embedding["data"][0]["embedding"]
6873
else:
6974
query_vector = None
@@ -117,8 +122,9 @@ async def run(self, q: str, overrides: dict[str, Any]) -> dict[str, Any]:
117122
message_builder.append_message("user", self.question)
118123

119124
messages = message_builder.messages
125+
chatgpt_args = {"deployment_id": self.chatgpt_deployment} if self.openai_host == "azure" else {}
120126
chat_completion = await openai.ChatCompletion.acreate(
121-
deployment_id=self.openai_deployment,
127+
**chatgpt_args,
122128
model=self.chatgpt_model,
123129
messages=messages,
124130
temperature=overrides.get("temperature") or 0.3,

0 commit comments

Comments
 (0)