Skip to content

Commit 55f63be

Browse files
authored
Add support for specifying an Azure OpenAI Key (#1746)
* Support for proxy with key * Add tests for coverage * Mypy, my nemesis * Increase cov threshold to 87
1 parent fc515ba commit 55f63be

File tree

5 files changed

+50
-22
lines changed

5 files changed

+50
-22
lines changed

.github/workflows/python-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
run: black . --check --verbose
5959
- name: Run Python tests
6060
if: runner.os != 'Windows'
61-
run: python3 -m pytest -s -vv --cov --cov-fail-under=86
61+
run: python3 -m pytest -s -vv --cov --cov-fail-under=87
6262
- name: Run E2E tests with Playwright
6363
id: e2e
6464
if: runner.os != 'Windows'

app/backend/app.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -526,20 +526,22 @@ async def setup_clients():
526526
current_app.config[CONFIG_CREDENTIAL] = azure_credential
527527

528528
if OPENAI_HOST.startswith("azure"):
529-
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
529+
api_version = os.getenv("AZURE_OPENAI_API_VERSION") or "2024-03-01-preview"
530530

531531
if OPENAI_HOST == "azure_custom":
532532
endpoint = os.environ["AZURE_OPENAI_CUSTOM_URL"]
533533
else:
534534
endpoint = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
535535

536-
api_version = os.getenv("AZURE_OPENAI_API_VERSION") or "2024-03-01-preview"
537-
538-
openai_client = AsyncAzureOpenAI(
539-
api_version=api_version,
540-
azure_endpoint=endpoint,
541-
azure_ad_token_provider=token_provider,
542-
)
536+
if api_key := os.getenv("AZURE_OPENAI_API_KEY"):
537+
openai_client = AsyncAzureOpenAI(api_version=api_version, azure_endpoint=endpoint, api_key=api_key)
538+
else:
539+
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
540+
openai_client = AsyncAzureOpenAI(
541+
api_version=api_version,
542+
azure_endpoint=endpoint,
543+
azure_ad_token_provider=token_provider,
544+
)
543545
elif OPENAI_HOST == "local":
544546
openai_client = AsyncOpenAI(
545547
base_url=os.environ["OPENAI_BASE_URL"],

infra/main.bicep

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ param appServiceSkuName string // Set in main.parameters.json
4343
@allowed([ 'azure', 'openai', 'azure_custom' ])
4444
param openAiHost string // Set in main.parameters.json
4545
param isAzureOpenAiHost bool = startsWith(openAiHost, 'azure')
46+
param deployAzureOpenAi bool = openAiHost == 'azure'
4647
param azureOpenAiCustomUrl string = ''
4748
param azureOpenAiApiVersion string = ''
48-
49+
@secure()
50+
param azureOpenAiApiKey string = ''
4951
param openAiServiceName string = ''
5052
param openAiResourceGroupName string = ''
5153

@@ -66,6 +68,7 @@ param openAiResourceGroupLocation string
6668

6769
param openAiSkuName string = 'S0'
6870

71+
@secure()
6972
param openAiApiKey string = ''
7073
param openAiApiOrganization string = ''
7174

@@ -305,17 +308,18 @@ module backend 'core/host/appservice.bicep' = {
305308
USE_SPEECH_OUTPUT_AZURE: useSpeechOutputAzure
306309
// Shared by all OpenAI deployments
307310
OPENAI_HOST: openAiHost
308-
AZURE_OPENAI_CUSTOM_URL: azureOpenAiCustomUrl
309-
AZURE_OPENAI_API_VERSION: azureOpenAiApiVersion
310311
AZURE_OPENAI_EMB_MODEL_NAME: embedding.modelName
311312
AZURE_OPENAI_EMB_DIMENSIONS: embedding.dimensions
312313
AZURE_OPENAI_CHATGPT_MODEL: chatGpt.modelName
313314
AZURE_OPENAI_GPT4V_MODEL: gpt4vModelName
314315
// Specific to Azure OpenAI
315-
AZURE_OPENAI_SERVICE: isAzureOpenAiHost ? openAi.outputs.name : ''
316+
AZURE_OPENAI_SERVICE: isAzureOpenAiHost && deployAzureOpenAi ? openAi.outputs.name : ''
316317
AZURE_OPENAI_CHATGPT_DEPLOYMENT: chatGpt.deploymentName
317318
AZURE_OPENAI_EMB_DEPLOYMENT: embedding.deploymentName
318319
AZURE_OPENAI_GPT4V_DEPLOYMENT: useGPT4V ? gpt4vDeploymentName : ''
320+
AZURE_OPENAI_API_VERSION: azureOpenAiApiVersion
321+
AZURE_OPENAI_API_KEY: azureOpenAiApiKey
322+
AZURE_OPENAI_CUSTOM_URL: azureOpenAiCustomUrl
319323
// Used only with non-Azure OpenAI deployments
320324
OPENAI_API_KEY: openAiApiKey
321325
OPENAI_ORGANIZATION: openAiApiOrganization
@@ -387,7 +391,7 @@ var openAiDeployments = concat(defaultOpenAiDeployments, useGPT4V ? [
387391
}
388392
] : [])
389393

390-
module openAi 'core/ai/cognitiveservices.bicep' = if (isAzureOpenAiHost) {
394+
module openAi 'core/ai/cognitiveservices.bicep' = if (isAzureOpenAiHost && deployAzureOpenAi) {
391395
name: 'openai'
392396
scope: openAiResourceGroup
393397
params: {
@@ -532,7 +536,7 @@ module userStorage 'core/storage/storage-account.bicep' = if (useUserUpload) {
532536
// USER ROLES
533537
var principalType = empty(runningOnGh) && empty(runningOnAdo) ? 'User' : 'ServicePrincipal'
534538

535-
module openAiRoleUser 'core/security/role.bicep' = if (isAzureOpenAiHost) {
539+
module openAiRoleUser 'core/security/role.bicep' = if (isAzureOpenAiHost && deployAzureOpenAi) {
536540
scope: openAiResourceGroup
537541
name: 'openai-role-user'
538542
params: {
@@ -624,7 +628,7 @@ module searchSvcContribRoleUser 'core/security/role.bicep' = {
624628
}
625629

626630
// SYSTEM IDENTITIES
627-
module openAiRoleBackend 'core/security/role.bicep' = if (isAzureOpenAiHost) {
631+
module openAiRoleBackend 'core/security/role.bicep' = if (isAzureOpenAiHost && deployAzureOpenAi) {
628632
scope: openAiResourceGroup
629633
name: 'openai-role-backend'
630634
params: {
@@ -634,7 +638,7 @@ module openAiRoleBackend 'core/security/role.bicep' = if (isAzureOpenAiHost) {
634638
}
635639
}
636640

637-
module openAiRoleSearchService 'core/security/role.bicep' = if (isAzureOpenAiHost && useIntegratedVectorization) {
641+
module openAiRoleSearchService 'core/security/role.bicep' = if (isAzureOpenAiHost && deployAzureOpenAi && useIntegratedVectorization) {
638642
scope: openAiResourceGroup
639643
name: 'openai-role-searchservice'
640644
params: {
@@ -829,17 +833,13 @@ output AZURE_OPENAI_CHATGPT_MODEL string = chatGpt.modelName
829833
output AZURE_OPENAI_GPT4V_MODEL string = gpt4vModelName
830834

831835
// Specific to Azure OpenAI
832-
output AZURE_OPENAI_SERVICE string = isAzureOpenAiHost ? openAi.outputs.name : ''
836+
output AZURE_OPENAI_SERVICE string = isAzureOpenAiHost && deployAzureOpenAi ? openAi.outputs.name : ''
833837
output AZURE_OPENAI_API_VERSION string = isAzureOpenAiHost ? azureOpenAiApiVersion : ''
834838
output AZURE_OPENAI_RESOURCE_GROUP string = isAzureOpenAiHost ? openAiResourceGroup.name : ''
835839
output AZURE_OPENAI_CHATGPT_DEPLOYMENT string = isAzureOpenAiHost ? chatGpt.deploymentName : ''
836840
output AZURE_OPENAI_EMB_DEPLOYMENT string = isAzureOpenAiHost ? embedding.deploymentName : ''
837841
output AZURE_OPENAI_GPT4V_DEPLOYMENT string = isAzureOpenAiHost ? gpt4vDeploymentName : ''
838842

839-
// Used only with non-Azure OpenAI deployments
840-
output OPENAI_API_KEY string = (openAiHost == 'openai') ? openAiApiKey : ''
841-
output OPENAI_ORGANIZATION string = (openAiHost == 'openai') ? openAiApiOrganization : ''
842-
843843
output AZURE_SPEECH_SERVICE_ID string = useSpeechOutputAzure ? speech.outputs.id : ''
844844
output AZURE_SPEECH_SERVICE_LOCATION string = useSpeechOutputAzure ? speech.outputs.location : ''
845845

infra/main.parameters.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@
125125
"azureOpenAiApiVersion":{
126126
"value": "${AZURE_OPENAI_API_VERSION}"
127127
},
128+
"azureOpenAiApiKey":{
129+
"value": "${AZURE_OPENAI_API_KEY}"
130+
},
128131
"openAiApiKey": {
129132
"value": "${OPENAI_API_KEY}"
130133
},

tests/test_app_config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,29 @@ async def test_app_local_openai(monkeypatch, minimal_env):
2828
assert quart_app.config[app.CONFIG_OPENAI_CLIENT].base_url == "http://localhost:5000"
2929

3030

31+
@pytest.mark.asyncio
32+
async def test_app_azure_custom_key(monkeypatch, minimal_env):
33+
monkeypatch.setenv("OPENAI_HOST", "azure_custom")
34+
monkeypatch.setenv("AZURE_OPENAI_CUSTOM_URL", "http://azureapi.com/api/v1")
35+
monkeypatch.setenv("AZURE_OPENAI_API_KEY", "azure-api-key")
36+
37+
quart_app = app.create_app()
38+
async with quart_app.test_app():
39+
assert quart_app.config[app.CONFIG_OPENAI_CLIENT].api_key == "azure-api-key"
40+
assert quart_app.config[app.CONFIG_OPENAI_CLIENT].base_url == "http://azureapi.com/api/v1/openai/"
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_app_azure_custom_identity(monkeypatch, minimal_env):
45+
monkeypatch.setenv("OPENAI_HOST", "azure_custom")
46+
monkeypatch.setenv("AZURE_OPENAI_CUSTOM_URL", "http://azureapi.com/api/v1")
47+
48+
quart_app = app.create_app()
49+
async with quart_app.test_app():
50+
assert quart_app.config[app.CONFIG_OPENAI_CLIENT].api_key == "<missing API key>"
51+
assert quart_app.config[app.CONFIG_OPENAI_CLIENT].base_url == "http://azureapi.com/api/v1/openai/"
52+
53+
3154
@pytest.mark.asyncio
3255
async def test_app_config_default(monkeypatch, minimal_env):
3356
quart_app = app.create_app()

0 commit comments

Comments
 (0)