From 404914c63afac209589b2d703619ec5e86eb4e1e Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 18 Jun 2024 00:02:56 +0000 Subject: [PATCH 1/3] Changes needed for workshop --- .env.sample | 4 +- README.md | 2 +- infra/main.bicep | 80 ++++++++++++++++++------------- infra/main.parameters.json | 6 +++ infra/web.bicep | 3 ++ src/fastapi_app/openai_clients.py | 32 +++++++++---- src/frontend/index.html | 2 +- 7 files changed, 82 insertions(+), 47 deletions(-) diff --git a/.env.sample b/.env.sample index e48823c2..16f833ca 100644 --- a/.env.sample +++ b/.env.sample @@ -13,9 +13,9 @@ OPENAI_EMBED_HOST=azure # You also need to `azd auth login` if running this locally AZURE_OPENAI_ENDPOINT=https://YOUR-AZURE-OPENAI-SERVICE-NAME.openai.azure.com AZURE_OPENAI_VERSION=2024-03-01-preview -AZURE_OPENAI_CHAT_DEPLOYMENT=chat +AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo AZURE_OPENAI_CHAT_MODEL=gpt-35-turbo -AZURE_OPENAI_EMBED_DEPLOYMENT=embed +AZURE_OPENAI_EMBED_DEPLOYMENT=text-embedding-ada-002 AZURE_OPENAI_EMBED_MODEL=text-embedding-ada-002 AZURE_OPENAI_EMBED_MODEL_DIMENSIONS=1536 # Needed for OpenAI.com: diff --git a/README.md b/README.md index 9186d053..7f9b0da7 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Since the local app uses OpenAI models, you should first deploy it for the optim There must be an initial build of static assets before running the backend, since the backend serves static files from the `src/static` directory. -3. Run the FastAPI backend (with hot reloading): +3. Run the FastAPI backend (with hot reloading). This should be run from the root of the project: ```shell python3 -m uvicorn fastapi_app:create_app --factory --reload diff --git a/infra/main.bicep b/infra/main.bicep index bb1bee6a..abea355d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -32,7 +32,7 @@ param principalId string = '' param openAILocation string @description('Name of the OpenAI resource group. If not specified, the resource group name will be generated.') -param openAiResourceGroupName string = '' +param openAIResourceGroupName string = '' @description('Whether to deploy Azure OpenAI resources') param deployAzureOpenAI bool = true @@ -47,15 +47,22 @@ param chatDeploymentName string = '' // https://learn.microsoft.com/azure/ai-services/openai/concepts/models#gpt-4-and-gpt-4-turbo-preview-models param chatDeploymentVersion string = '' -param azureOpenAiAPIVersion string = '2024-03-01-preview' +param azureOpenAIAPIVersion string = '2024-03-01-preview' +@secure() +param azureOpenAIKey string = '' +@description('Azure OpenAI endpoint to use, if not using the one deployed here.') +param azureOpenAIEndpoint string = '' + +@description('Whether to use Azure OpenAI (either deployed here or elsewhere) or OpenAI.com') +var useAzureOpenAI = deployAzureOpenAI || !empty(azureOpenAIEndpoint) @description('Capacity of the GPT deployment') // You can increase this, but capacity is limited per model/region, so you will get errors if you go over // https://learn.microsoft.com/en-us/azure/ai-services/openai/quotas-limits param chatDeploymentCapacity int = 0 var chatConfig = { - modelName: !empty(chatModelName) ? chatModelName : deployAzureOpenAI ? 'gpt-35-turbo' : 'gpt-3.5-turbo' - deploymentName: !empty(chatDeploymentName) ? chatDeploymentName : 'chat' + modelName: !empty(chatModelName) ? chatModelName : (useAzureOpenAI ? 'gpt-35-turbo' : 'gpt-3.5-turbo') + deploymentName: !empty(chatDeploymentName) ? chatDeploymentName : 'gpt-35-turbo' deploymentVersion: !empty(chatDeploymentVersion) ? chatDeploymentVersion : '0125' deploymentCapacity: chatDeploymentCapacity != 0 ? chatDeploymentCapacity : 30 } @@ -68,7 +75,7 @@ param embedDimensions int = 0 var embedConfig = { modelName: !empty(embedModelName) ? embedModelName : 'text-embedding-ada-002' - deploymentName: !empty(embedDeploymentName) ? embedDeploymentName : 'embed' + deploymentName: !empty(embedDeploymentName) ? embedDeploymentName : 'text-embedding-ada-002' deploymentVersion: !empty(embedDeploymentVersion) ? embedDeploymentVersion : '2' deploymentCapacity: embedDeploymentCapacity != 0 ? embedDeploymentCapacity : 30 dimensions: embedDimensions != 0 ? embedDimensions : 1536 @@ -183,64 +190,71 @@ module web 'web.bicep' = { } { name: 'OPENAI_CHAT_HOST' - value: deployAzureOpenAI ? 'azure' : 'openaicom' + value: useAzureOpenAI ? 'azure' : 'openaicom' } { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT' - value: deployAzureOpenAI ? chatConfig.deploymentName : '' + value: useAzureOpenAI ? chatConfig.deploymentName : '' } { name: 'AZURE_OPENAI_CHAT_MODEL' - value: deployAzureOpenAI ? chatConfig.modelName : '' + value: useAzureOpenAI ? chatConfig.modelName : '' } { name: 'OPENAICOM_CHAT_MODEL' - value: deployAzureOpenAI ? '' : 'gpt-3.5-turbo' + value: useAzureOpenAI ? '' : 'gpt-3.5-turbo' } { name: 'OPENAI_EMBED_HOST' - value: deployAzureOpenAI ? 'azure' : 'openaicom' + value: useAzureOpenAI ? 'azure' : 'openaicom' } { name: 'OPENAICOM_EMBED_MODEL_DIMENSIONS' - value: deployAzureOpenAI ? '' : '1536' + value: useAzureOpenAI ? '' : '1536' } { name: 'OPENAICOM_EMBED_MODEL' - value: deployAzureOpenAI ? '' : 'text-embedding-ada-002' + value: useAzureOpenAI ? '' : 'text-embedding-ada-002' } { name: 'AZURE_OPENAI_EMBED_MODEL' - value: deployAzureOpenAI ? embedConfig.modelName : '' + value: useAzureOpenAI ? embedConfig.modelName : '' } { name: 'AZURE_OPENAI_EMBED_DEPLOYMENT' - value: deployAzureOpenAI ? embedConfig.deploymentName : '' + value: useAzureOpenAI ? embedConfig.deploymentName : '' } { name: 'AZURE_OPENAI_EMBED_MODEL_DIMENSIONS' - value: deployAzureOpenAI ? string(embedConfig.dimensions) : '' + value: useAzureOpenAI ? string(embedConfig.dimensions) : '' } { name: 'AZURE_OPENAI_ENDPOINT' - value: deployAzureOpenAI ? openAi.outputs.endpoint : '' + value: useAzureOpenAI ? (deployAzureOpenAI ? openAI.outputs.endpoint : azureOpenAIEndpoint) : '' } { name: 'AZURE_OPENAI_VERSION' - value: deployAzureOpenAI ? azureOpenAiAPIVersion : '' + value: useAzureOpenAI ? azureOpenAIAPIVersion : '' + } + { + name: 'AZURE_OPENAI_KEY' + secretRef: 'azure-openai-key' } ] + secrets: { + 'azure-openai-key': azureOpenAIKey + } } } -resource openAiResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' existing = - if (!empty(openAiResourceGroupName)) { - name: !empty(openAiResourceGroupName) ? openAiResourceGroupName : resourceGroup.name +resource openAIResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' existing = + if (!empty(openAIResourceGroupName)) { + name: !empty(openAIResourceGroupName) ? openAIResourceGroupName : resourceGroup.name } -module openAi 'core/ai/cognitiveservices.bicep' = { +module openAI 'core/ai/cognitiveservices.bicep' = if (deployAzureOpenAI) { name: 'openai' - scope: openAiResourceGroup + scope: openAIResourceGroup params: { name: '${prefix}-openai' location: openAILocation @@ -279,9 +293,9 @@ module openAi 'core/ai/cognitiveservices.bicep' = { } // USER ROLES -module openAiRoleUser 'core/security/role.bicep' = +module openAIRoleUser 'core/security/role.bicep' = if (empty(runningOnGh)) { - scope: openAiResourceGroup + scope: openAIResourceGroup name: 'openai-role-user' params: { principalId: principalId @@ -291,8 +305,8 @@ module openAiRoleUser 'core/security/role.bicep' = } // Backend roles -module openAiRoleBackend 'core/security/role.bicep' = { - scope: openAiResourceGroup +module openAIRoleBackend 'core/security/role.bicep' = { + scope: openAIResourceGroup name: 'openai-role-backend' params: { principalId: web.outputs.SERVICE_WEB_IDENTITY_PRINCIPAL_ID @@ -314,13 +328,13 @@ output SERVICE_WEB_NAME string = web.outputs.SERVICE_WEB_NAME output SERVICE_WEB_URI string = web.outputs.SERVICE_WEB_URI output SERVICE_WEB_IMAGE_NAME string = web.outputs.SERVICE_WEB_IMAGE_NAME -output AZURE_OPENAI_ENDPOINT string = deployAzureOpenAI ? openAi.outputs.endpoint : '' -output AZURE_OPENAI_VERSION string = deployAzureOpenAI ? azureOpenAiAPIVersion : '' -output AZURE_OPENAI_CHAT_DEPLOYMENT string = deployAzureOpenAI ? chatConfig.deploymentName : '' -output AZURE_OPENAI_EMBED_DEPLOYMENT string = deployAzureOpenAI ? embedConfig.deploymentName : '' -output AZURE_OPENAI_CHAT_MODEL string = deployAzureOpenAI ? chatConfig.modelName : '' -output AZURE_OPENAI_EMBED_MODEL string = deployAzureOpenAI ? embedConfig.modelName : '' -output AZURE_OPENAI_EMBED_MODEL_DIMENSIONS int = deployAzureOpenAI ? embedConfig.dimensions : 0 +output AZURE_OPENAI_ENDPOINT string = useAzureOpenAI ? (deployAzureOpenAI ? openAI.outputs.endpoint : azureOpenAIEndpoint) : '' +output AZURE_OPENAI_VERSION string = useAzureOpenAI ? azureOpenAIAPIVersion : '' +output AZURE_OPENAI_CHAT_DEPLOYMENT string = useAzureOpenAI ? chatConfig.deploymentName : '' +output AZURE_OPENAI_EMBED_DEPLOYMENT string = useAzureOpenAI ? embedConfig.deploymentName : '' +output AZURE_OPENAI_CHAT_MODEL string = useAzureOpenAI ? chatConfig.modelName : '' +output AZURE_OPENAI_EMBED_MODEL string = useAzureOpenAI ? embedConfig.modelName : '' +output AZURE_OPENAI_EMBED_MODEL_DIMENSIONS int = useAzureOpenAI ? embedConfig.dimensions : 0 output POSTGRES_HOST string = postgresServer.outputs.POSTGRES_DOMAIN_NAME output POSTGRES_USERNAME string = postgresEntraAdministratorName diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 1f67a8b8..04122069 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -20,6 +20,12 @@ "deployAzureOpenAI": { "value": "${DEPLOY_AZURE_OPENAI=true}" }, + "azureOpenAIKey": { + "value": "${AZURE_OPENAI_KEY}" + }, + "azureOpenAIEndpoint": { + "value": "${AZURE_OPENAI_ENDPOINT}" + }, "chatModelName":{ "value": "${AZURE_OPENAI_CHAT_MODEL}" }, diff --git a/infra/web.bicep b/infra/web.bicep index 6a6c4145..6419b38a 100644 --- a/infra/web.bicep +++ b/infra/web.bicep @@ -8,6 +8,8 @@ param exists bool param identityName string param serviceName string = 'web' param environmentVariables array = [] +@secure() +param secrets object resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: identityName @@ -37,6 +39,7 @@ module app 'core/host/container-app-upsert.bicep' = { } ] ) + secrets: secrets targetPort: 8000 } } diff --git a/src/fastapi_app/openai_clients.py b/src/fastapi_app/openai_clients.py index a3fcb1cd..73d7267a 100644 --- a/src/fastapi_app/openai_clients.py +++ b/src/fastapi_app/openai_clients.py @@ -10,16 +10,21 @@ async def create_openai_chat_client(azure_credential): OPENAI_CHAT_HOST = os.getenv("OPENAI_CHAT_HOST") if OPENAI_CHAT_HOST == "azure": - logger.info("Authenticating to OpenAI using Azure Identity...") - - token_provider = azure.identity.get_bearer_token_provider( - azure_credential, "https://cognitiveservices.azure.com/.default" - ) + client_args = {} + if api_key := os.getenv("AZURE_OPENAI_KEY"): + logger.info("Authenticating to Azure OpenAI using API key...") + client_args["api_key"] = api_key + else: + logger.info("Authenticating to Azure OpenAI using Azure Identity...") + token_provider = azure.identity.get_bearer_token_provider( + azure_credential, "https://cognitiveservices.azure.com/.default" + ) + client_args["azure_ad_token_provider"] = token_provider openai_chat_client = openai.AsyncAzureOpenAI( api_version=os.getenv("AZURE_OPENAI_VERSION"), azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), - azure_ad_token_provider=token_provider, azure_deployment=os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT"), + **client_args, ) openai_chat_model = os.getenv("AZURE_OPENAI_CHAT_MODEL") elif OPENAI_CHAT_HOST == "ollama": @@ -40,14 +45,21 @@ async def create_openai_chat_client(azure_credential): async def create_openai_embed_client(azure_credential): OPENAI_EMBED_HOST = os.getenv("OPENAI_EMBED_HOST") if OPENAI_EMBED_HOST == "azure": - token_provider = azure.identity.get_bearer_token_provider( - azure_credential, "https://cognitiveservices.azure.com/.default" - ) + client_args = {} + if api_key := os.getenv("AZURE_OPENAI_KEY"): + logger.info("Authenticating to Azure OpenAI using API key...") + client_args["api_key"] = api_key + else: + logger.info("Authenticating to Azure OpenAI using Azure Identity...") + token_provider = azure.identity.get_bearer_token_provider( + azure_credential, "https://cognitiveservices.azure.com/.default" + ) + client_args["azure_ad_token_provider"] = token_provider openai_embed_client = openai.AsyncAzureOpenAI( api_version=os.getenv("AZURE_OPENAI_VERSION"), azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), - azure_ad_token_provider=token_provider, azure_deployment=os.getenv("AZURE_OPENAI_EMBED_DEPLOYMENT"), + **client_args, ) openai_embed_model = os.getenv("AZURE_OPENAI_EMBED_MODEL") openai_embed_dimensions = os.getenv("AZURE_OPENAI_EMBED_DIMENSIONS") diff --git a/src/frontend/index.html b/src/frontend/index.html index a7aa7e9e..a9527a01 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -4,7 +4,7 @@ - GPT + Enterprise data | Sample + RAG on PostgreSQL
From bbaeac806b49128e926a608c36ea5844fd78fbcb Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 18 Jun 2024 22:11:40 +0000 Subject: [PATCH 2/3] Case for key being empty --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index abea355d..35876cee 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -242,7 +242,7 @@ module web 'web.bicep' = { } ] secrets: { - 'azure-openai-key': azureOpenAIKey + 'azure-openai-key': empty(azureOpenAIKey) ? 'no-key-provided' : azureOpenAIKey } } } From f06cc326a550127ff8aa2d18b4ec790272421fb0 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 19 Jun 2024 03:04:27 +0000 Subject: [PATCH 3/3] Optional key --- infra/main.bicep | 168 ++++++++++++++++++++++++----------------------- 1 file changed, 87 insertions(+), 81 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 35876cee..58f4db04 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -152,6 +152,91 @@ module containerApps 'core/host/container-apps.bicep' = { // Web frontend var webAppName = replace('${take(prefix, 19)}-ca', '--', '-') var webAppIdentityName = '${prefix}-id-web' +var webAppEnv = [ + { + name: 'POSTGRES_HOST' + value: postgresServer.outputs.POSTGRES_DOMAIN_NAME + } + { + name: 'POSTGRES_USERNAME' + value: webAppIdentityName + } + { + name: 'POSTGRES_DATABASE' + value: postgresDatabaseName + } + { + name: 'POSTGRES_SSL' + value: 'require' + } + { + name: 'RUNNING_IN_PRODUCTION' + value: 'true' + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: monitoring.outputs.applicationInsightsConnectionString + } + { + name: 'OPENAI_CHAT_HOST' + value: useAzureOpenAI ? 'azure' : 'openaicom' + } + { + name: 'AZURE_OPENAI_CHAT_DEPLOYMENT' + value: useAzureOpenAI ? chatConfig.deploymentName : '' + } + { + name: 'AZURE_OPENAI_CHAT_MODEL' + value: useAzureOpenAI ? chatConfig.modelName : '' + } + { + name: 'OPENAICOM_CHAT_MODEL' + value: useAzureOpenAI ? '' : 'gpt-3.5-turbo' + } + { + name: 'OPENAI_EMBED_HOST' + value: useAzureOpenAI ? 'azure' : 'openaicom' + } + { + name: 'OPENAICOM_EMBED_MODEL_DIMENSIONS' + value: useAzureOpenAI ? '' : '1536' + } + { + name: 'OPENAICOM_EMBED_MODEL' + value: useAzureOpenAI ? '' : 'text-embedding-ada-002' + } + { + name: 'AZURE_OPENAI_EMBED_MODEL' + value: useAzureOpenAI ? embedConfig.modelName : '' + } + { + name: 'AZURE_OPENAI_EMBED_DEPLOYMENT' + value: useAzureOpenAI ? embedConfig.deploymentName : '' + } + { + name: 'AZURE_OPENAI_EMBED_MODEL_DIMENSIONS' + value: useAzureOpenAI ? string(embedConfig.dimensions) : '' + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: useAzureOpenAI ? (deployAzureOpenAI ? openAI.outputs.endpoint : azureOpenAIEndpoint) : '' + } + { + name: 'AZURE_OPENAI_VERSION' + value: useAzureOpenAI ? azureOpenAIAPIVersion : '' + } +] +var webAppEnvWithSecret = !empty(azureOpenAIKey) ? union(webAppEnv, [ + { + name: 'AZURE_OPENAI_KEY' + secretRef: 'azure-openai-key' + } +]) : webAppEnv + +var secrets = !empty(azureOpenAIKey) ? { + 'azure-openai-key': azureOpenAIKey +} : {} + module web 'web.bicep' = { name: 'web' scope: resourceGroup @@ -163,87 +248,8 @@ module web 'web.bicep' = { containerAppsEnvironmentName: containerApps.outputs.environmentName containerRegistryName: containerApps.outputs.registryName exists: webAppExists - environmentVariables: [ - { - name: 'POSTGRES_HOST' - value: postgresServer.outputs.POSTGRES_DOMAIN_NAME - } - { - name: 'POSTGRES_USERNAME' - value: webAppIdentityName - } - { - name: 'POSTGRES_DATABASE' - value: postgresDatabaseName - } - { - name: 'POSTGRES_SSL' - value: 'require' - } - { - name: 'RUNNING_IN_PRODUCTION' - value: 'true' - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: monitoring.outputs.applicationInsightsConnectionString - } - { - name: 'OPENAI_CHAT_HOST' - value: useAzureOpenAI ? 'azure' : 'openaicom' - } - { - name: 'AZURE_OPENAI_CHAT_DEPLOYMENT' - value: useAzureOpenAI ? chatConfig.deploymentName : '' - } - { - name: 'AZURE_OPENAI_CHAT_MODEL' - value: useAzureOpenAI ? chatConfig.modelName : '' - } - { - name: 'OPENAICOM_CHAT_MODEL' - value: useAzureOpenAI ? '' : 'gpt-3.5-turbo' - } - { - name: 'OPENAI_EMBED_HOST' - value: useAzureOpenAI ? 'azure' : 'openaicom' - } - { - name: 'OPENAICOM_EMBED_MODEL_DIMENSIONS' - value: useAzureOpenAI ? '' : '1536' - } - { - name: 'OPENAICOM_EMBED_MODEL' - value: useAzureOpenAI ? '' : 'text-embedding-ada-002' - } - { - name: 'AZURE_OPENAI_EMBED_MODEL' - value: useAzureOpenAI ? embedConfig.modelName : '' - } - { - name: 'AZURE_OPENAI_EMBED_DEPLOYMENT' - value: useAzureOpenAI ? embedConfig.deploymentName : '' - } - { - name: 'AZURE_OPENAI_EMBED_MODEL_DIMENSIONS' - value: useAzureOpenAI ? string(embedConfig.dimensions) : '' - } - { - name: 'AZURE_OPENAI_ENDPOINT' - value: useAzureOpenAI ? (deployAzureOpenAI ? openAI.outputs.endpoint : azureOpenAIEndpoint) : '' - } - { - name: 'AZURE_OPENAI_VERSION' - value: useAzureOpenAI ? azureOpenAIAPIVersion : '' - } - { - name: 'AZURE_OPENAI_KEY' - secretRef: 'azure-openai-key' - } - ] - secrets: { - 'azure-openai-key': empty(azureOpenAIKey) ? 'no-key-provided' : azureOpenAIKey - } + environmentVariables: webAppEnvWithSecret + secrets: secrets } }