diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 73806dbbc..db65714e7 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -7,7 +7,7 @@ on: - dev - demo schedule: - - cron: '0 6,18 * * *' # Runs at 6:00 AM and 6:00 PM GMT + - cron: "0 6,18 * * *" # Runs at 6:00 AM and 6:00 PM GMT env: GPT_MIN_CAPACITY: 200 @@ -29,7 +29,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install ODBC Driver 18 for SQL Server run: | curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - @@ -81,7 +80,6 @@ jobs: - name: Install Bicep CLI run: az bicep install - - name: Set Deployment Region id: set_region run: | @@ -98,7 +96,6 @@ jobs: UNIQUE_RG_NAME="arg-${ACCL_NAME}-${SHORT_UUID}" echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV echo "Generated RESOURCE_GROUP_NAME: ${UNIQUE_RG_NAME}" - - name: Check and Create Resource Group id: check_create_rg run: | @@ -114,7 +111,6 @@ jobs: fi # Set output for other jobs echo "RESOURCE_GROUP_NAME=${{ env.RESOURCE_GROUP_NAME }}" >> $GITHUB_OUTPUT - - name: Generate Unique Solution Prefix id: generate_solution_prefix run: | @@ -137,7 +133,6 @@ jobs: else TAG="latest"; fi echo "IMAGE_TAG=$TAG" >> $GITHUB_ENV echo "Image Tag: $TAG" - - name: Deploy and extract values from deployment output id: get_output run: | @@ -149,19 +144,17 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} environmentName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ + --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ --query "properties.outputs" -o json) - - echo "Deployment output: $DEPLOY_OUTPUT" if [[ -z "$DEPLOY_OUTPUT" ]]; then echo "Error: Deployment output is empty. Please check the deployment logs." exit 1 fi - export AI_FOUNDARY_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_FOUNDRY_NAME.value') - echo "AI_FOUNDARY_NAME=$AI_FOUNDARY_NAME" >> $GITHUB_ENV + export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_FOUNDRY_RESOURCE_ID.value') + echo "AI_FOUNDRY_RESOURCE_ID=$AI_FOUNDRY_RESOURCE_ID" >> $GITHUB_ENV export SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_SEARCH_SERVICE_NAME.value') echo "SEARCH_SERVICE_NAME=$SEARCH_SERVICE_NAME" >> $GITHUB_ENV export COSMOS_DB_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.cosmosdB_ACCOUNT_NAME.value') @@ -172,8 +165,8 @@ jobs: echo "STORAGE_CONTAINER=$STORAGE_CONTAINER" >> $GITHUB_ENV export KEYVAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.keY_VAULT_NAME.value') echo "KEYVAULT_NAME=$KEYVAULT_NAME" >> $GITHUB_ENV - export SQL_SERVER=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_SERVER.value') - echo "SQL_SERVER=$SQL_SERVER" >> $GITHUB_ENV + export SQL_SERVER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_SERVER_NAME.value') + echo "SQL_SERVER_NAME=$SQL_SERVER_NAME" >> $GITHUB_ENV export SQL_DATABASE=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_DATABASE.value') echo "SQL_DATABASE=$SQL_DATABASE" >> $GITHUB_ENV export CLIENT_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.managedidentitY_WEBAPP_CLIENTID.value') @@ -182,8 +175,6 @@ jobs: echo "CLIENT_NAME=$CLIENT_NAME" >> $GITHUB_ENV export RG_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.resourcE_GROUP_NAME.value') echo "RG_NAME=$RG_NAME" >> $GITHUB_ENV - export RESOURCE_GROUP_NAME_FOUNDRY=$(echo "$DEPLOY_OUTPUT" | jq -r '.resourcE_GROUP_NAME_FOUNDRY.value') - echo "RESOURCE_GROUP_NAME_FOUNDRY=$RESOURCE_GROUP_NAME_FOUNDRY" >> $GITHUB_ENV WEBAPP_URL=$(echo $DEPLOY_OUTPUT | jq -r '.weB_APP_URL.value') echo "WEBAPP_URL=$WEBAPP_URL" >> $GITHUB_OUTPUT WEB_APP_NAME=$(echo $DEPLOY_OUTPUT | jq -r '.weB_APP_NAME.value') @@ -195,9 +186,7 @@ jobs: sleep 30 - - - - name: Deploy Infra and Import Sample Data + - name: Deploy Infra and Import Sample Data run: | set -e az account set --subscription "${{ secrets.AZURE_SUBSCRIPTION_ID }}" @@ -220,10 +209,9 @@ jobs: "" \ "${{ secrets.AZURE_CLIENT_ID }}" \ "${{ env.RG_NAME }}" \ - "${{ env.SQL_SERVER }}" \ - "${{ env.AI_FOUNDARY_NAME }}" \ + "${{ env.SQL_SERVER_NAME }}" \ "${{ env.SEARCH_SERVICE_NAME }}" \ - "${{ env.RESOURCE_GROUP_NAME_FOUNDRY }}" + "${{ env.AI_FOUNDRY_RESOURCE_ID}}" user_roles_json='[ @@ -232,13 +220,11 @@ jobs: ]' bash ./infra/scripts/add_user_scripts/create_sql_user_and_role.sh \ - "${{ env.SQL_SERVER }}.database.windows.net" \ + "${{ env.SQL_SERVER_NAME }}.database.windows.net" \ "${{ env.SQL_DATABASE }}" \ "$user_roles_json" \ "${{ secrets.AZURE_CLIENT_ID }}" - echo "=== Post-Deployment Script Completed Successfully ===" - - name: Get AI Services name and store in variable if: always() && steps.check_create_rg.outcome == 'success' @@ -262,7 +248,6 @@ jobs: run: | set -e echo "Listing all KeyVaults in the resource group ${{ env.RESOURCE_GROUP_NAME }}..." - # Get the list of KeyVaults in the specified resource group keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv) @@ -288,7 +273,6 @@ jobs: # Output the formatted array and save it to the job output echo "KEYVAULTS=$keyvault_array" >> $GITHUB_OUTPUT fi - - name: Set Deployment Status id: deployment_status if: always() @@ -312,7 +296,7 @@ jobs: secrets: inherit cleanup: - if: always() + if: always() needs: [deploy, e2e-test] runs-on: ubuntu-latest env: @@ -336,12 +320,10 @@ jobs: set -e echo "Checking if resource group exists..." echo "Resource group name: ${{ env.RESOURCE_GROUP_NAME }}" - if [ -z "${{ env.RESOURCE_GROUP_NAME }}" ]; then echo "Resource group name is empty. Skipping deletion." exit 0 fi - rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "true" ]; then echo "Resource group exists. Cleaning..." @@ -368,12 +350,9 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - # Convert the comma-separated string into an array IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults" - echo "List of resources to check: ${resources_to_check[@]}" - # Check if resource group still exists before listing resources rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "false" ]; then @@ -472,7 +451,6 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - # Convert the comma-separated string into an array IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults" @@ -506,7 +484,6 @@ jobs: if: failure() || needs.deploy.result == 'failure' || needs.e2e-test.result == 'failure' run: | RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - # Construct the email body EMAIL_BODY=$(cat <` | Sets the Azure region for resource deployment. | | `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | | `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | `` | Reuses an existing AI Foundry Project Resource Id instead of provisioning a new one. | diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index 59a6547cf..2af2e32f5 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -116,7 +116,7 @@ When you start the deployment, most parameters will have **default values**, but | **Embedding Model Capacity** | Set the capacity for **embedding models**. Choose based on usage and quota. | `80` | | **Image Tag** | The version of the Docker image to use (e.g., `latest`, `dev`, `hotfix`). | `latest` | | **Azure OpenAI API Version** | Set the API version for OpenAI model deployments. | `2025-04-01-preview` | -| **AZURE\_LOCATION** | Sets the Azure region for resource deployment. | `japaneast` | +| **AZURE_LOCATION** | Sets the Azure region for resource deployment. | `` | | **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID instead of creating a new one. | *(empty)* | | **Existing AI Foundry Project Resource ID** | To reuse an existing AI Foundry Project Resource ID instead of creating a new one. | *(empty)* | @@ -143,6 +143,13 @@ To adjust quota settings, follow these [steps](./AzureGPTQuotaSettings.md). Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) + +
+ + Reusing an Existing Azure AI Foundry Project + + Guide to get your [Existing Project ID](/docs/re-use-foundry-project.md) +
### Deploying with AZD diff --git a/docs/LocalSetupAndDeploy.md b/docs/LocalSetupAndDeploy.md index 6b7547e3e..7c2e92b87 100644 --- a/docs/LocalSetupAndDeploy.md +++ b/docs/LocalSetupAndDeploy.md @@ -13,7 +13,10 @@ Navigate to the `App` folder located in the `src` directory of the repository us ### 2. Configure Environment Variables - Copy the `.env.sample` file to a new file named `.env`. -- Update the `.env` file with the required values from your Azure resource group. +- Update the `.env` file with the required values from your Azure resource group in Azure Portal App Service environment variables. +- Alternatively, if resources were +provisioned using `azd provision` or `azd up`, a `.env` file is automatically generated in the `.azure//.env` +file. To get your `` run `azd env list` to see which env is default. ### 3. Start the Application - Run `start.cmd` (Windows) or `start.sh` (Linux/Mac) to: diff --git a/docs/images/re_use_foundry_project/azure_ai_foundry_list.png b/docs/images/re_use_foundry_project/azure_ai_foundry_list.png new file mode 100644 index 000000000..784bc85c7 Binary files /dev/null and b/docs/images/re_use_foundry_project/azure_ai_foundry_list.png differ diff --git a/docs/images/re_use_foundry_project/navigate_to_projects.png b/docs/images/re_use_foundry_project/navigate_to_projects.png new file mode 100644 index 000000000..11082c15c Binary files /dev/null and b/docs/images/re_use_foundry_project/navigate_to_projects.png differ diff --git a/docs/images/re_use_foundry_project/project_resource_id.png b/docs/images/re_use_foundry_project/project_resource_id.png new file mode 100644 index 000000000..7835ea9d3 Binary files /dev/null and b/docs/images/re_use_foundry_project/project_resource_id.png differ diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md new file mode 100644 index 000000000..a6713c035 --- /dev/null +++ b/docs/re-use-foundry-project.md @@ -0,0 +1,44 @@ +[← Back to *DEPLOYMENT* guide](/docs/DeploymentGuide.md#deployment-options--steps) + +# Reusing an Existing Azure AI Foundry Project +To configure your environment to use an existing Azure AI Foundry Project, follow these steps: +--- +### 1. Go to Azure Portal +Go to https://portal.azure.com + +### 2. Search for Azure AI Foundry +In the search bar at the top, type "Azure AI Foundry" and click on it. Then select the Foundry service instance where your project exists. + +![alt text](../docs/images/re_use_foundry_project/azure_ai_foundry_list.png) + +### 3. Navigate to Projects under Resource Management +On the left sidebar of the Foundry service blade: + +- Expand the Resource Management section +- Click on Projects (this refers to the active Foundry project tied to the service) + +### 4. Click on the Project +From the Projects view: Click on the project name to open its details + + Note: You will see only one project listed here, as each Foundry service maps to a single project in this accelerator + +![alt text](../docs/images/re_use_foundry_project/navigate_to_projects.png) + +### 5. Copy Resource ID +In the left-hand menu of the project blade: + +- Click on Properties under Resource Management +- Locate the Resource ID field +- Click on the copy icon next to the Resource ID value + +![alt text](../docs/images/re_use_foundry_project/project_resource_id.png) + +### 6. Set the Foundry Project Resource ID in Your Environment +Run the following command in your terminal +```bash +azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID '' +``` +Replace `` with the value obtained from Step 5. + +### 7. Continue Deployment +Proceed with the next steps in the [deployment guide](/documents/DeploymentGuide.md#deployment-options--steps). diff --git a/infra/core/database/cosmos/cosmos-role-assign.bicep b/infra/core/database/cosmos/cosmos-role-assign.bicep index 3949efef0..52cbed0d9 100644 --- a/infra/core/database/cosmos/cosmos-role-assign.bicep +++ b/infra/core/database/cosmos/cosmos-role-assign.bicep @@ -1,7 +1,12 @@ metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' + +@description('Required. Name of the Azure Cosmos DB account.') param accountName string +@description('Required. ID of the Cosmos DB SQL role definition.') param roleDefinitionId string + +@description('Otional. Principal ID to assign the role to.') param principalId string = '' resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { diff --git a/infra/core/database/cosmos/deploy_cosmos_db.bicep b/infra/core/database/cosmos/deploy_cosmos_db.bicep index 3925eeaeb..dae6d7bbe 100644 --- a/infra/core/database/cosmos/deploy_cosmos_db.bicep +++ b/infra/core/database/cosmos/deploy_cosmos_db.bicep @@ -1,14 +1,20 @@ -@minLength(3) -@maxLength(15) -@description('Solution Name') + +@description('Required. Name of the solution.') param solutionName string + +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name') -param accountName string = '${ solutionName }-cosmos' +@description('Otional. Name of the Cosmos DB account.') +param accountName string = '${solutionName}-cosmos' + +@description('Otional. Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' + +@description('Otional. Name of the Cosmos DB container.') param collectionName string = 'conversations' +@description('Otional. List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -17,9 +23,11 @@ param containers array = [ } ] +@description('Otional. API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' +@description('Otional. Resource tags to apply.') param tags object = {} resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { @@ -68,6 +76,7 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 ] } +@description('Details of the Cosmos DB account, database, and container.') output cosmosOutput object = { cosmosAccountName: cosmos.name cosmosDatabaseName: databaseName diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index a8797a154..86f210e1d 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -1,28 +1,56 @@ // Creates Azure dependent resources for Azure AI studio + +@description('Required. Solution Name') param solutionName string + +@description('Required. Solution Location') param solutionLocation string + +@description('Required. Contains Name of KeyVault.') param keyVaultName string + +@description('Required. Indicates the type of Deployment.') param deploymentType string -param gptModelName string + +@description('Optional. GPT Model Name') +param gptModelName string = 'gpt-4o-mini' + +@description('Required. Azure OepnAI API Version.') param azureOpenaiAPIVersion string + +@description('Required. Param to get Deployment Capacity.') param gptDeploymentCapacity int -param embeddingModel string -param embeddingDeploymentCapacity int + +@description('Optional. Embedding Model.') +param embeddingModel string = 'text-embedding-ada-002' + +@description('Optional. Info about Embedding Deployment Capacity.') +param embeddingDeploymentCapacity int = 80 + +@description('Optional. Existing Log Analytics WorkspaceID.') param existingLogAnalyticsWorkspaceId string = '' + +@description('Optional. Azure Existing AI Project ResourceID.') param azureExistingAIProjectResourceId string = '' +@description('Required. The name of the AI Foundry AI Project resource in Azure.') +param aiFoundryAiServicesAiProjectResourceName string + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + // Load the abbrevations file required to name the azure resources. -var abbrs = loadJsonContent('./abbreviations.json') +//var abbrs = loadJsonContent('./abbreviations.json') -var aiFoundryName = '${abbrs.ai.aiFoundry}${solutionName}' -var applicationInsightsName = '${abbrs.managementGovernance.applicationInsights}${solutionName}' +var aiFoundryName = 'aif-${solutionName}' +var applicationInsightsName = 'appi-${solutionName}' var keyvaultName = keyVaultName var location = solutionLocation //'eastus2' -var aiProjectName = '${abbrs.ai.aiFoundryProject}${solutionName}' +var aiProjectName = '${aiFoundryAiServicesAiProjectResourceName}-${solutionName}' var aiProjectFriendlyName = aiProjectName var aiProjectDescription = 'AI Foundry Project' -var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}' -var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}' +var aiSearchName = 'srch-${solutionName}' +var workspaceName = 'log-${solutionName}' var aiModelDeployments = [ { name: gptModelName @@ -72,6 +100,9 @@ var existingAIProjectName = !empty(azureExistingAIProjectResourceId) var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[2] : '' +var existingAIServicesName = !empty(azureExistingAIProjectResourceId) + ? split(azureExistingAIProjectResourceId, '/')[8] + : '' var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[4] : '' @@ -86,7 +117,7 @@ resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (!useExisting) { name: workspaceName location: location - tags: {} + tags: tags properties: { retentionInDays: 30 sku: { @@ -105,6 +136,7 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { publicNetworkAccessForQuery: 'Enabled' WorkspaceResourceId: useExisting ? existingLogAnalyticsWorkspace.id : logAnalytics.id } + tags: tags } resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -128,6 +160,7 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = i publicNetworkAccess: 'Enabled' disableLocalAuth: false } + tags: tags } resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -141,6 +174,12 @@ resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04 description: aiProjectDescription displayName: aiProjectFriendlyName } + tags: tags +} + +resource existingAiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if (!empty(azureExistingAIProjectResourceId)) { + name: existingAIFoundryName + scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) } @batchSize(1) @@ -190,6 +229,7 @@ resource aiSearch 'Microsoft.Search/searchServices@2025-02-01-preview' = { } semanticSearch: 'free' } + tags: tags } resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -225,15 +265,27 @@ resource cognitiveServicesOpenAIUser 'Microsoft.Authorization/roleDefinitions@20 name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' } -module assignOpenAIRoleToAISearch 'deploy_foundry_role_assignment.bicep' = { - name: 'assignOpenAIRoleToAISearch' +resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { + name: guid(resourceGroup().id, aiFoundry.id, cognitiveServicesOpenAIUser.id) + scope: aiFoundry + properties: { + principalId: aiSearch.identity.principalId + roleDefinitionId: cognitiveServicesOpenAIUser.id + principalType: 'ServicePrincipal' + } +} + +module assignOpenAIRoleToAISearchExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'assignOpenAIRoleToAISearchExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { roleDefinitionId: cognitiveServicesOpenAIUser.id roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName - aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName principalId: aiSearch.identity.principalId + aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName + aiModelDeployments: aiModelDeployments + tags:tags } } @@ -257,7 +309,7 @@ resource assignSearchIndexDataReaderToExistingAiProject 'Microsoft.Authorization scope: aiSearch properties: { roleDefinitionId: searchIndexDataReaderRoleDefinition.id - principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId + principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId principalType: 'ServicePrincipal' } } @@ -283,7 +335,7 @@ resource searchServiceContributorRoleAssignmentExisting 'Microsoft.Authorization scope: aiSearch properties: { roleDefinitionId: searchServiceContributorRoleDefinition.id - principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId + principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId principalType: 'ServicePrincipal' } } @@ -312,6 +364,7 @@ resource azureOpenAIApiVersionEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-0 properties: { value: azureOpenaiAPIVersion //'2024-07-18' } + tags:tags } resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -323,6 +376,7 @@ resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01- ? existingOpenAIEndpoint : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] } + tags:tags } resource azureOpenAIEmbeddingModelEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -331,6 +385,7 @@ resource azureOpenAIEmbeddingModelEntry 'Microsoft.KeyVault/vaults/secrets@2021- properties: { value: embeddingModel } + tags:tags } resource azureSearchServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -339,6 +394,7 @@ resource azureSearchServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021 properties: { value: 'https://${aiSearch.name}.search.windows.net' } + tags:tags } resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -347,32 +403,67 @@ resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-pre properties: { value: 'transcripts_index' } + tags:tags } - +@description('Contains Name of KeyVault.') output keyvaultName string = keyvaultName + +@description('Contains KeyVault ID.') output keyvaultId string = keyVault.id +@description('Contains AI Foundry ResourceGroup Name') output resourceGroupNameFoundry string = !empty(existingAIServiceResourceGroup) ? existingAIServiceResourceGroup : resourceGroup().name -output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint) + + @description('Contains Name of AI Foundry Project Endpoint.') + output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint) ? existingProjEndpoint : aiFoundryProject.properties.endpoints['AI Foundry API'] + +@description('Contains AI Endpoint.') output aoaiEndpoint string = !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint + +@description('Contains Name of AI Foundry.') output aiFoundryName string = !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName //aiServicesName_m +output aiFoundryId string = !empty(azureExistingAIProjectResourceId) ? existingAiFoundry.id : aiFoundry.id +@description('Contains AI Search Name.') output aiSearchName string = aiSearchName + +@description('Contains AI SearchID.') output aiSearchId string = aiSearch.id + +@description('Contains AI Search Target.') output aiSearchTarget string = 'https://${aiSearch.name}.search.windows.net' + +@description('Contains AI Search Service.') output aiSearchService string = aiSearch.name + +@description('Contains Name of AI Foundry Project.') output aiFoundryProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiFoundryProject.name +@description('Contains Application Insights ID.') output applicationInsightsId string = applicationInsights.id +@description('The Instrumentation Key for the Application Insights resource.') +output instrumentationKey string = applicationInsights.properties.InstrumentationKey + +@description('Contains Log Analytics Workspace Resource Name.') output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name + +@description('Contains Log Analytics Workspace ResourceGroup Name.') output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawResourceGroup : resourceGroup().name +@description('Contains Application Insights Connection String.') output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString +@description('Contains AI Search Foundry Connection Name.') output aiSearchFoundryConnectionName string = aiSearchConnectionName + +@description('Contains AI Foundry App Insights Connection Name.') +output aiAppInsightsFoundryConnectionName string = aiAppInsightConnectionName + +@description('Contains AI Model Deployments') +output aiModelDeployments array = aiModelDeployments diff --git a/infra/deploy_aifp_aisearch_connection.bicep b/infra/deploy_aifp_aisearch_connection.bicep index 0dec1b9bb..ee0424d33 100644 --- a/infra/deploy_aifp_aisearch_connection.bicep +++ b/infra/deploy_aifp_aisearch_connection.bicep @@ -1,8 +1,19 @@ +@description('Required. Existing AI Project Name') param existingAIProjectName string + +@description('Required. Existing AI Foundry Name') param existingAIFoundryName string + +@description('Required. AI Search Name') param aiSearchName string + +@description('Required. AI Search Resource ID') param aiSearchResourceId string + +@description('Required. AI Search Location') param aiSearchLocation string + +@description('Required. AI Search Connection Name') param aiSearchConnectionName string resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index fc0967ef6..21ce3d9e7 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -1,151 +1,178 @@ // ========== Key Vault ========== // targetScope = 'resourceGroup' -@description('Solution Location') +@description('Required. Solution Location') param solutionLocation string -@description('The pricing tier for the App Service plan') +@description('Optional. The pricing tier for the App Service plan') @allowed(['F1', 'D1', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3', 'P4', 'P0v3']) -param HostingPlanSku string = 'B2' +param hostingPlanSku string = 'B2' -param HostingPlanName string -param WebsiteName string +@description('Required. Name of App Service plan') +param hostingPlanName string + +@description('Required. Name of Web App') +param websiteName string + +@description('Specifies the application environment') +param appEnvironment string // @description('Name of Application Insights') // param ApplicationInsightsName string = '${ solutionName }-app-insights' -@description('Name of Azure Search Service') -param AzureSearchService string = '' +@description('Optional. Name of Azure Search Service') +param azureSearchService string = '' -@description('Name of Azure Search Index') -param AzureSearchIndex string = '' +@description('Optional. Name of Azure Search Index') +param azureSearchIndex string = '' -@description('Use semantic search') -param AzureSearchUseSemanticSearch string = 'False' +@description('Optional. Use semantic search') +param azureSearchUseSemanticSearch string = 'False' -@description('Semantic search config') -param AzureSearchSemanticSearchConfig string = 'default' +@description('Optional. Semantic search config') +param azureSearchSemanticSearchConfig string = 'default' -@description('Top K results') -param AzureSearchTopK string = '5' +@description('Optional. Top K results') +param azureSearchTopK string = '5' -@description('Enable in domain') -param AzureSearchEnableInDomain string = 'False' +@description('Optional. Enable in domain') +param azureSearchEnableInDomain string = 'False' -@description('Content columns') -param AzureSearchContentColumns string = 'content' +@description('Optional. Content columns') +param azureSearchContentColumns string = 'content' -@description('Filename column') -param AzureSearchFilenameColumn string = 'filename' +@description('Optional. Filename column') +param azureSearchFilenameColumn string = 'filename' -@description('Title column') -param AzureSearchTitleColumn string = 'client_id' +@description('Optional. Title column') +param azureSearchTitleColumn string = 'client_id' -@description('Url column') -param AzureSearchUrlColumn string = 'sourceurl' +@description('Optional. Url column') +param azureSearchUrlColumn string = 'sourceurl' -@description('Name of Azure OpenAI Resource') -param AzureOpenAIResource string +@description('Required. Name of Azure OpenAI Resource') +param azureOpenAIResource string -@description('Azure OpenAI Model Deployment Name') -param AzureOpenAIModel string +@description('Optional. Azure OpenAI Model Deployment Name') +param azureOpenAIModel string = 'gpt-4o-mini' -@description('Azure Open AI Endpoint') -param AzureOpenAIEndpoint string = '' +@description('Optional. Azure Open AI Endpoint') +param azureOpenAIEndpoint string = '' -@description('Azure OpenAI Temperature') -param AzureOpenAITemperature string = '0' +@description('Optional. Azure OpenAI Temperature') +param azureOpenAITemperature string = '0' -@description('Azure OpenAI Top P') -param AzureOpenAITopP string = '1' +@description('Optional. Azure OpenAI Top P') +param azureOpenAITopP string = '1' -@description('Azure OpenAI Max Tokens') -param AzureOpenAIMaxTokens string = '1000' +@description('Optional. Azure OpenAI Max Tokens') +param azureOpenAIMaxTokens string = '1000' -@description('Azure OpenAI Stop Sequence') -param AzureOpenAIStopSequence string = '\n' +@description('Optional. Azure OpenAI Stop Sequence') +param azureOpenAIStopSequence string = '\n' -@description('Azure OpenAI System Message') -param AzureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' +@description('Optional. Azure OpenAI System Message') +param azureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' -@description('Azure OpenAI Api Version') -param AzureOpenAIApiVersion string = '2024-02-15-preview' +@description('Optional. Azure OpenAI Api Version') +param azureOpenAIApiVersion string = '2024-02-15-preview' -@description('Whether or not to stream responses from Azure OpenAI') -param AzureOpenAIStream string = 'True' +@description('Optional. Whether or not to stream responses from Azure OpenAI') +param azureOpenAIStream string = 'True' -@description('Azure Search Query Type') +@description('Optional. Azure Search Query Type') @allowed(['simple', 'semantic', 'vector', 'vectorSimpleHybrid', 'vectorSemanticHybrid']) -param AzureSearchQueryType string = 'simple' +param azureSearchQueryType string = 'simple' -@description('Azure Search Vector Fields') -param AzureSearchVectorFields string = 'contentVector' +@description('Optional. Azure Search Vector Fields') +param azureSearchVectorFields string = 'contentVector' -@description('Azure Search Permitted Groups Field') -param AzureSearchPermittedGroupsField string = '' +@description('Optional. Azure Search Permitted Groups Field') +param azureSearchPermittedGroupsField string = '' -@description('Azure Search Strictness') +@description('Optional. Azure Search Strictness') @allowed(['1', '2', '3', '4', '5']) -param AzureSearchStrictness string = '3' +param azureSearchStrictness string = '3' -@description('Azure OpenAI Embedding Deployment Name') -param AzureOpenAIEmbeddingName string = '' +@description('Optional. Azure OpenAI Embedding Deployment Name') +param azureOpenAIEmbeddingName string = '' -@description('Azure Open AI Embedding Endpoint') -param AzureOpenAIEmbeddingEndpoint string = '' +@description('Optional. Azure Open AI Embedding Endpoint') +param azureOpenAIEmbeddingEndpoint string = '' -@description('Use Azure Function') +@description('Optional. Use Azure Function') param USE_INTERNAL_STREAM string = 'True' -@description('SQL Database Server Name') +@description('Optional. SQL Database Server Name') param SQLDB_SERVER string = '' -@description('SQL Database Name') +@description('Optional. SQL Database Name') param SQLDB_DATABASE string = '' -@description('Azure Cosmos DB Account') +@description('Optional. Azure Cosmos DB Account') param AZURE_COSMOSDB_ACCOUNT string = '' -@description('Azure Cosmos DB Conversations Container') +@description('Optional. Azure Cosmos DB Conversations Container') param AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = '' -@description('Azure Cosmos DB Database') +@description('Optional. Azure Cosmos DB Database') param AZURE_COSMOSDB_DATABASE string = '' -@description('Enable feedback in Cosmos DB') +@description('Optional. Enable feedback in Cosmos DB') param AZURE_COSMOSDB_ENABLE_FEEDBACK string = 'True' //@description('Power BI Embed URL') //param VITE_POWERBI_EMBED_URL string = '' +@description('Required. The container image tag to be deployed') param imageTag string +@description('Required. The resource ID of the user-assigned managed identity to be used by the deployed resources.') param userassignedIdentityId string + +@description('Required. The client ID of the user-assigned managed identity.') param userassignedIdentityClientId string + +@description('Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring.') param applicationInsightsId string +@description('Required. The endpoint URL of the Azure Cognitive Search service.') param azureSearchServiceEndpoint string -@description('Azure Function App SQL System Prompt') +@description('Required. Azure Function App SQL System Prompt') param sqlSystemPrompt string -@description('Azure Function App CallTranscript System Prompt') + +@description('Required. Azure Function App CallTranscript System Prompt') param callTranscriptSystemPrompt string -@description('Azure Function App Stream Text System Prompt') + +@description('Required. Azure Function App Stream Text System Prompt') param streamTextSystemPrompt string +@description('Required. AI Foundry project endpoint URL.') param aiFoundryProjectEndpoint string + +@description('Optional. Flag to enable AI project client.') param useAIProjectClientFlag string = 'false' +@description('Required. Name of the AI Foundry project.') param aiFoundryName string + +@description('Required. Application Insights connection string.') param applicationInsightsConnectionString string + +@description('Required. Connection name for Azure Cognitive Search.') param aiSearchProjectConnectionName string -// var WebAppImageName = 'DOCKER|byoaiacontainer.azurecr.io/byoaia-app:latest' +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +// var webAppImageName = 'DOCKER|byoaiacontainer.azurecr.io/byoaia-app:latest' -// var WebAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' +// var webAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' -var WebAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' +var webAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' +@description('Optional. Resource ID of the existing AI Foundry project.') param azureExistingAIProjectResourceId string = '' var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) @@ -157,22 +184,24 @@ var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) var existingAIServicesName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[8] : '' +var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' -resource HostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { - name: HostingPlanName +resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { + name: hostingPlanName location: solutionLocation sku: { - name: HostingPlanSku + name: hostingPlanSku } properties: { - name: HostingPlanName + name: hostingPlanName reserved: true } kind: 'linux' + tags: tags } -resource Website 'Microsoft.Web/sites@2020-06-01' = { - name: WebsiteName +resource website 'Microsoft.Web/sites@2020-06-01' = { + name: websiteName location: solutionLocation identity: { type: 'SystemAssigned, UserAssigned' @@ -181,12 +210,12 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } } properties: { - serverFarmId: HostingPlanName + serverFarmId: hostingPlanName siteConfig: { appSettings: [ { name: 'APP_ENV' - value: 'Prod' + value: appEnvironment } { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' @@ -198,107 +227,107 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_SEARCH_SERVICE' - value: AzureSearchService + value: azureSearchService } { name: 'AZURE_SEARCH_INDEX' - value: AzureSearchIndex + value: azureSearchIndex } { name: 'AZURE_SEARCH_USE_SEMANTIC_SEARCH' - value: AzureSearchUseSemanticSearch + value: azureSearchUseSemanticSearch } { name: 'AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG' - value: AzureSearchSemanticSearchConfig + value: azureSearchSemanticSearchConfig } { name: 'AZURE_SEARCH_TOP_K' - value: AzureSearchTopK + value: azureSearchTopK } { name: 'AZURE_SEARCH_ENABLE_IN_DOMAIN' - value: AzureSearchEnableInDomain + value: azureSearchEnableInDomain } { name: 'AZURE_SEARCH_CONTENT_COLUMNS' - value: AzureSearchContentColumns + value: azureSearchContentColumns } { name: 'AZURE_SEARCH_FILENAME_COLUMN' - value: AzureSearchFilenameColumn + value: azureSearchFilenameColumn } { name: 'AZURE_SEARCH_TITLE_COLUMN' - value: AzureSearchTitleColumn + value: azureSearchTitleColumn } { name: 'AZURE_SEARCH_URL_COLUMN' - value: AzureSearchUrlColumn + value: azureSearchUrlColumn } { name: 'AZURE_OPENAI_RESOURCE' - value: AzureOpenAIResource + value: azureOpenAIResource } { name: 'AZURE_OPENAI_MODEL' - value: AzureOpenAIModel + value: azureOpenAIModel } { name: 'AZURE_OPENAI_ENDPOINT' - value: AzureOpenAIEndpoint + value: azureOpenAIEndpoint } { name: 'AZURE_OPENAI_TEMPERATURE' - value: AzureOpenAITemperature + value: azureOpenAITemperature } { name: 'AZURE_OPENAI_TOP_P' - value: AzureOpenAITopP + value: azureOpenAITopP } { name: 'AZURE_OPENAI_MAX_TOKENS' - value: AzureOpenAIMaxTokens + value: azureOpenAIMaxTokens } { name: 'AZURE_OPENAI_STOP_SEQUENCE' - value: AzureOpenAIStopSequence + value: azureOpenAIStopSequence } { name: 'AZURE_OPENAI_SYSTEM_MESSAGE' - value: AzureOpenAISystemMessage + value: azureOpenAISystemMessage } { name: 'AZURE_OPENAI_PREVIEW_API_VERSION' - value: AzureOpenAIApiVersion + value: azureOpenAIApiVersion } { name: 'AZURE_OPENAI_STREAM' - value: AzureOpenAIStream + value: azureOpenAIStream } { name: 'AZURE_SEARCH_QUERY_TYPE' - value: AzureSearchQueryType + value: azureSearchQueryType } { name: 'AZURE_SEARCH_VECTOR_COLUMNS' - value: AzureSearchVectorFields + value: azureSearchVectorFields } { name: 'AZURE_SEARCH_PERMITTED_GROUPS_COLUMN' - value: AzureSearchPermittedGroupsField + value: azureSearchPermittedGroupsField } { name: 'AZURE_SEARCH_STRICTNESS' - value: AzureSearchStrictness + value: azureSearchStrictness } { name: 'AZURE_OPENAI_EMBEDDING_NAME' - value: AzureOpenAIEmbeddingName + value: azureOpenAIEmbeddingName } { name: 'AZURE_OPENAI_EMBEDDING_ENDPOINT' - value: AzureOpenAIEmbeddingEndpoint + value: azureOpenAIEmbeddingEndpoint } { name: 'SQLDB_SERVER' @@ -361,21 +390,22 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME' - value: AzureOpenAIModel + value: azureOpenAIModel } { name: 'AZURE_AI_AGENT_API_VERSION' - value: AzureOpenAIApiVersion + value: azureOpenAIApiVersion } { name: 'AZURE_SEARCH_CONNECTION_NAME' value: aiSearchProjectConnectionName } ] - linuxFxVersion: WebAppImageName + linuxFxVersion: webAppImageName } } - dependsOn: [HostingPlan] + tags: tags + dependsOn: [hostingPlan] } // resource ApplicationInsights 'Microsoft.Insights/components@2020-02-02' = { @@ -395,14 +425,14 @@ resource contributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRol } module cosmosUserRole 'core/database/cosmos/cosmos-role-assign.bicep' = { - name: 'cosmos-sql-user-role-${WebsiteName}' + name: 'cosmos-sql-user-role-${websiteName}' params: { accountName: AZURE_COSMOSDB_ACCOUNT roleDefinitionId: contributorRoleDefinition.id - principalId: Website.identity.principalId + principalId: website.identity.principalId } dependsOn: [ - Website + website ] } @@ -417,16 +447,31 @@ resource aiUserRoleDefinitionFoundry 'Microsoft.Authorization/roleDefinitions@20 name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' } -module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = { - name: 'assignAiUserRoleToAiProject' +resource assignAiUserRoleToAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { + name: guid(resourceGroup().id, aiFoundry.id, aiUserRoleDefinitionFoundry.id) + // scope: aiProject + properties: { + principalId: website.identity.principalId + roleDefinitionId: aiUserRoleDefinitionFoundry.id + principalType: 'ServicePrincipal' + } +} + +module assignAiUserRoleToAiProjectExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'assignAiUserRoleToAiProjectExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { - principalId: Website.identity.principalId + principalId: website.identity.principalId roleDefinitionId: aiUserRoleDefinitionFoundry.id - roleAssignmentName: guid(Website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) + roleAssignmentName: guid(website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName + aiProjectName: existingAIProjectName + tags: tags } } -output webAppUrl string = 'https://${WebsiteName}.azurewebsites.net' -output webAppName string = WebsiteName +@description('URL of the deployed web application.') +output webAppUrl string = 'https://${websiteName}.azurewebsites.net' + +@description('Name of the deployed web application.') +output webAppName string = websiteName diff --git a/infra/deploy_cosmos_db.bicep b/infra/deploy_cosmos_db.bicep index 4a3f29198..58eacf26c 100644 --- a/infra/deploy_cosmos_db.bicep +++ b/infra/deploy_cosmos_db.bicep @@ -1,10 +1,19 @@ + +@minLength(3) +@maxLength(20) +@description('Required. Solution location.') param solutionLocation string -@description('Name') +@description('Required. Name of the Azure Cosmos DB account.') param cosmosDBName string + +@description('Optional. Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' + +@description('Optional.Name of the Cosmos DB container (collection).') param collectionName string = 'conversations' +@description('Optional. List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -13,9 +22,11 @@ param containers array = [ } ] +@description('Optional. The API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' +@description('Optional. Tags to be applied to the resources.') param tags object = {} resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { @@ -47,7 +58,7 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 properties: { resource: { id: databaseName } } - + tags: tags resource list 'containers' = [for container in containers: { name: container.name properties: { @@ -63,7 +74,11 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 cosmos ] } - +@description('Name of the Cosmos DB account.') output cosmosAccountName string = cosmos.name + +@description('Name of the Cosmos DB database.') output cosmosDatabaseName string = databaseName + +@description('Name of the Cosmos DB container.') output cosmosContainerName string = collectionName diff --git a/infra/deploy_foundry_model_role_assignment.bicep b/infra/deploy_foundry_model_role_assignment.bicep new file mode 100644 index 000000000..b4da8843c --- /dev/null +++ b/infra/deploy_foundry_model_role_assignment.bicep @@ -0,0 +1,63 @@ +@description('Optional. Principal ID to assign the role to.') +param principalId string = '' + +@description('Required. ID of the role definition to assign.') +param roleDefinitionId string + +@description('Optional. Name of the role assignment.') +param roleAssignmentName string = '' + +@description('Required. Name of the AI Foundry resource.') +param aiFoundryName string + +@description('Optional. Name of the AI project.') +param aiProjectName string = '' + +@description('Optional. List of AI model deployments.') +param aiModelDeployments array = [] + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiFoundryName +} + +// Call the model deployments module +@batchSize(1) +resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { + parent: aiServices + name: aiModeldeployment.name + properties: { + model: { + format: 'OpenAI' + name: aiModeldeployment.model + } + raiPolicyName: aiModeldeployment.raiPolicyName + } + sku: { + name: aiModeldeployment.sku.name + capacity: aiModeldeployment.sku.capacity + } + tags : tags +}] + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { + name: aiProjectName + parent: aiServices +} + +resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: roleAssignmentName + scope: aiServices + properties: { + roleDefinitionId: roleDefinitionId + principalId: principalId + principalType: 'ServicePrincipal' + } +} +@description('Principal ID of the AI Services resource.') +output aiServicesPrincipalId string = aiServices.identity.principalId + +@description('Principal ID of the AI Project resource if defined.') +output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep deleted file mode 100644 index 13b215850..000000000 --- a/infra/deploy_foundry_role_assignment.bicep +++ /dev/null @@ -1,27 +0,0 @@ -param principalId string = '' -param roleDefinitionId string -param roleAssignmentName string = '' -param aiFoundryName string -param aiProjectName string = '' - -resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: aiFoundryName -} - -resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { - name: aiProjectName - parent: aiServices -} - -resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: roleAssignmentName - scope: aiServices - properties: { - roleDefinitionId: roleDefinitionId - principalId: principalId - principalType: 'ServicePrincipal' - } -} - -output aiServicesPrincipalId string = aiServices.identity.principalId -output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/deploy_keyvault.bicep b/infra/deploy_keyvault.bicep index 0878f84bc..d8141baa8 100644 --- a/infra/deploy_keyvault.bicep +++ b/infra/deploy_keyvault.bicep @@ -1,60 +1,64 @@ // ========== Key Vault ========== // targetScope = 'resourceGroup' -@minLength(3) -@maxLength(15) -@description('Solution Name') +@description('Required. Solution Name') param solutionName string -@description('Solution Location') +@description('Required. Solution Location') param solutionLocation string +@description('Optional. Current UTC timestamp.') param utc string = utcNow() -@description('Name') +@description('Required. Name of the Azure Key Vault.') param kvName string -@description('Create Mode') +@description('Optional. Specifies the create mode for the resource.') param createMode string = 'default' -@description('Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') +@description('Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') param enableForDeployment bool = true -@description('Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') +@description('Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') param enableForDiskEncryption bool = true -@description('Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') +@description('Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') param enableForTemplateDeployment bool = true -@description('Enable RBAC Authorization. Property that controls how data actions are authorized.') +@description('Optional. Enable RBAC Authorization. Property that controls how data actions are authorized.') param enableRBACAuthorization bool = true -@description('Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90.') +@description('Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90.') param softDeleteRetentionInDays int = 7 -@description('Public Network Access, Property to specify whether the vault will accept traffic from public internet.') +@description('Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet.') @allowed([ 'enabled' 'disabled' ]) param publicNetworkAccess string = 'enabled' -@description('SKU') +@description('Optional. SKU') @allowed([ 'standard' 'premium' ]) param sku string = 'standard' -@description('Vault URI. The URI of the vault for performing operations on keys and secrets.') +@description('Optional. Vault URI. The URI of the vault for performing operations on keys and secrets.') var vaultUri = 'https://${ kvName }.vault.azure.net/' +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: kvName location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -111,6 +115,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { } } +@description('Name of the Key Vault.') output keyvaultName string = keyVault.name + +@description('Resource ID of the Key Vault.') output keyvaultId string = keyVault.id diff --git a/infra/deploy_managed_identity.bicep b/infra/deploy_managed_identity.bicep index 7fecc336c..16746b2e7 100644 --- a/infra/deploy_managed_identity.bicep +++ b/infra/deploy_managed_identity.bicep @@ -3,19 +3,23 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(15) -@description('Solution Name') +@description('Required. Name of the solution.') param solutionName string -@description('Solution Location') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name') +@description('Required. Name of the managed identity.') param miName string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: miName location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -40,6 +44,7 @@ resource managedIdentityWebApp 'Microsoft.ManagedIdentity/userAssignedIdentities name: '${miName}-webapp' location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -90,6 +95,7 @@ resource managedIdentityWebApp 'Microsoft.ManagedIdentity/userAssignedIdentities // } // } +@description('Details of the managed identity resource.') output managedIdentityOutput object = { id: managedIdentity.id objectId: managedIdentity.properties.principalId @@ -97,6 +103,7 @@ output managedIdentityOutput object = { name: miName } +@description('Details of the managed identity for the web app.') output managedIdentityWebAppOutput object = { id: managedIdentityWebApp.id objectId: managedIdentityWebApp.properties.principalId diff --git a/infra/deploy_sql_db.bicep b/infra/deploy_sql_db.bicep index 669ddb31c..e2a964b2c 100644 --- a/infra/deploy_sql_db.bicep +++ b/infra/deploy_sql_db.bicep @@ -1,17 +1,26 @@ +@description('Required. Deployment location for the solution.') param solutionLocation string + +@description('Required. Name of the Azure Key Vault.') param keyVaultName string + +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string + +@description('Required. Name of the managed identity.') param managedIdentityName string -@description('The name of the SQL logical server.') +@description('Required. The name of the SQL logical server.') param serverName string -@description('The name of the SQL Database.') +@description('Required. The name of the SQL Database.') param sqlDBName string -@description('Location for all resources.') +@description('Required. Location for all resources.') param location string = solutionLocation +@description('Optional. Tags to be applied to the resources.') +param tags object = {} resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { name: serverName @@ -30,6 +39,7 @@ resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { azureADOnlyAuthentication: true } } + tags: tags } resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { @@ -68,6 +78,7 @@ resource sqlDB 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { readScale: 'Disabled' zoneRedundant: false } + tags: tags } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { @@ -80,6 +91,7 @@ resource sqldbServerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' properties: { value: '${serverName}.database.windows.net' } + tags: tags } resource sqldbDatabaseEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -88,8 +100,11 @@ resource sqldbDatabaseEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-previe properties: { value: sqlDBName } + tags: tags } - +@description('Name of the SQL logical server.') output sqlServerName string = serverName + +@description('Name of the SQL database.') output sqlDbName string = sqlDBName // output sqlDbUser string = administratorLogin diff --git a/infra/deploy_storage_account.bicep b/infra/deploy_storage_account.bicep index f9f8f9f1a..9f269d39b 100644 --- a/infra/deploy_storage_account.bicep +++ b/infra/deploy_storage_account.bicep @@ -1,15 +1,21 @@ // ========== Storage Account ========== // targetScope = 'resourceGroup' -@description('Solution Location') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name') +@description('Required. Name of the storage account.') param saName string +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string + +@description('Required. Name of the Azure Key Vault.') param keyVaultName string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource storageAccounts_resource 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: saName location: solutionLocation @@ -45,6 +51,7 @@ resource storageAccounts_resource 'Microsoft.Storage/storageAccounts@2022-09-01' accessTier: 'Hot' allowSharedKeyAccess: false } + tags : tags } resource storageAccounts_default 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01' = { @@ -103,6 +110,7 @@ resource adlsAccountNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-prev properties: { value: saName } + tags : tags } resource adlsAccountContainerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -111,7 +119,11 @@ resource adlsAccountContainerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01 properties: { value: 'data' } + tags : tags } +@description('Name of the storage account.') output storageName string = saName + +@description('Name of the default storage container.') output storageContainer string = 'data' diff --git a/infra/main.bicep b/infra/main.bicep index 705259f56..c1c4906e8 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -3,20 +3,20 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(20) -@description('A unique prefix for all resources in this deployment. This should be 3-20 characters long:') -param environmentName string +@description('Required. A unique prefix for all resources in this deployment. This should be 3-20 characters long:') +param solutionName string = 'clientadvisor' -@description('Optional: Existing Log Analytics Workspace Resource ID') +@description('Optional. Existing Log Analytics Workspace Resource ID') param existingLogAnalyticsWorkspaceId string = '' -@description('Use this parameter to use an existing AI project resource ID') +@description('Optional. Use this parameter to use an existing AI project resource ID') param azureExistingAIProjectResourceId string = '' -@description('CosmosDB Location') +@description('Optional. CosmosDB Location') param cosmosLocation string = 'eastus2' @minLength(1) -@description('GPT model deployment type:') +@description('Optional. GPT model deployment type:') @allowed([ 'Standard' 'GlobalStandard' @@ -24,42 +24,53 @@ param cosmosLocation string = 'eastus2' param deploymentType string = 'GlobalStandard' @minLength(1) -@description('Name of the GPT model to deploy:') +@description('Optional. Name of the GPT model to deploy:') @allowed([ 'gpt-4o-mini' ]) param gptModelName string = 'gpt-4o-mini' +@description('Optional. API version for the Azure OpenAI service.') param azureOpenaiAPIVersion string = '2025-04-01-preview' @minValue(10) -@description('Capacity of the GPT deployment:') +@description('Optional. 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 gptDeploymentCapacity int = 200 @minLength(1) -@description('Name of the Text Embedding model to deploy:') +@description('Optional. Name of the Text Embedding model to deploy:') @allowed([ 'text-embedding-ada-002' ]) param embeddingModel string = 'text-embedding-ada-002' @minValue(10) -@description('Capacity of the Embedding Model deployment') +@description('Optional. Capacity of the Embedding Model deployment') param embeddingDeploymentCapacity int = 80 // @description('Fabric Workspace Id if you have one, else leave it empty. ') // param fabricWorkspaceId string +@description('The Docker image tag to use for the application deployment.') param imageTag string = 'latest' //restricting to these regions because assistants api for gpt-4o-mini is available only in these regions -@allowed(['australiaeast','eastus', 'eastus2','francecentral','japaneast','swedencentral','uksouth', 'westus', 'westus3']) +@allowed([ + 'australiaeast' + 'eastus' + 'eastus2' + 'francecentral' + 'japaneast' + 'swedencentral' + 'uksouth' + 'westus' + 'westus3' +]) // @description('Azure OpenAI Location') // param AzureOpenAILocation string = 'eastus2' - @metadata({ - azd:{ + azd: { type: 'location' usageName: [ 'OpenAI.GlobalStandard.gpt-4o-mini,200' @@ -67,23 +78,63 @@ param imageTag string = 'latest' ] } }) -@description('Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') +@description('Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') param aiDeploymentsLocation string -@description('Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') +@description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') param AZURE_LOCATION string = '' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION -var uniqueId = toLower(uniqueString(environmentName, subscription().id, solutionLocation, resourceGroup().name)) -var solutionPrefix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' +//var solutionSuffix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' + +@maxLength(5) +@description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') +param solutionUniqueToken string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) + +var solutionSuffix= toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) // Load the abbrevations file required to name the azure resources. -var abbrs = loadJsonContent('./abbreviations.json') +//var abbrs = loadJsonContent('./abbreviations.json') //var resourceGroupLocation = resourceGroup().location //var solutionLocation = resourceGroupLocation // var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/' +var hostingPlanName = 'asp-${solutionSuffix}' +var websiteName = 'app-${solutionSuffix}' +var appEnvironment = 'Prod' +var azureSearchIndex = 'transcripts_index' +var azureSearchUseSemanticSearch = 'True' +var azureSearchSemanticSearchConfig = 'my-semantic-config' +var azureSearchTopK = '5' +var azureSearchContentColumns = 'content' +var azureSearchFilenameColumn = 'chunk_id' +var azureSearchTitleColumn = 'client_id' +var azureSearchUrlColumn = 'sourceurl' +var azureOpenAITemperature = '0' +var azureOpenAITopP = '1' +var azureOpenAIMaxTokens = '1000' +var azureOpenAIStopSequence = '\n' +var azureOpenAISystemMessage = '''You are a helpful Wealth Advisor assistant''' +var azureOpenAIStream = 'True' +var azureSearchQueryType = 'simple' +var azureSearchVectorFields = 'contentVector' +var azureSearchPermittedGroupsField = '' +var azureSearchStrictness = '3' +var azureSearchEnableInDomain = 'False' // Set to 'True' if you want to enable in-domain search +var azureCosmosDbEnableFeedback = 'True' +var useInternalStream = 'True' +var useAIProjectClientFlag = 'False' +var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' + var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for tables and columns provided below: 1. Table: Clients Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents @@ -120,11 +171,17 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response. Always send clientId as '{client_id}'.''' +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} + +var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' properties: { tags: { + ...tags TemplateName: 'Client Advisor' } } @@ -134,9 +191,10 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { module managedIdentityModule 'deploy_managed_identity.bicep' = { name: 'deploy_managed_identity' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: solutionLocation - miName: '${abbrs.security.managedIdentity}${solutionPrefix}' + miName: 'id-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -145,10 +203,11 @@ module managedIdentityModule 'deploy_managed_identity.bicep' = { module keyvaultModule 'deploy_keyvault.bicep' = { name: 'deploy_keyvault' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: solutionLocation managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - kvName: '${abbrs.security.keyVault}${solutionPrefix}' + kvName: 'kv-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -157,7 +216,7 @@ module keyvaultModule 'deploy_keyvault.bicep' = { module aifoundry 'deploy_ai_foundry.bicep' = { name: 'deploy_ai_foundry' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: aiDeploymentsLocation keyVaultName: keyvaultModule.outputs.keyvaultName deploymentType: deploymentType @@ -168,6 +227,8 @@ module aifoundry 'deploy_ai_foundry.bicep' = { embeddingDeploymentCapacity: embeddingDeploymentCapacity existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId azureExistingAIProjectResourceId: azureExistingAIProjectResourceId + aiFoundryAiServicesAiProjectResourceName : aiFoundryAiServicesAiProjectResourceName + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -177,7 +238,8 @@ module cosmosDBModule 'deploy_cosmos_db.bicep' = { name: 'deploy_cosmos_db' params: { solutionLocation: cosmosLocation - cosmosDBName: '${abbrs.databases.cosmosDBDatabase}${solutionPrefix}' + cosmosDBName: 'cosmos-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -188,8 +250,9 @@ module storageAccountModule 'deploy_storage_account.bicep' = { params: { solutionLocation: solutionLocation managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - saName: '${abbrs.storage.storageAccount}${solutionPrefix}' + saName: 'st${solutionSuffix}' keyVaultName: keyvaultModule.outputs.keyvaultName + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -202,8 +265,9 @@ module sqlDBModule 'deploy_sql_db.bicep' = { keyVaultName: keyvaultModule.outputs.keyvaultName managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId managedIdentityName: managedIdentityModule.outputs.managedIdentityOutput.name - serverName: '${abbrs.databases.sqlDatabaseServer}${solutionPrefix}' - sqlDBName: '${abbrs.databases.sqlDatabase}${solutionPrefix}' + serverName: 'sql-${solutionSuffix}' + sqlDBName: 'sqldb-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -219,40 +283,41 @@ module appserviceModule 'deploy_app_service.bicep' = { name: 'deploy_app_service' params: { solutionLocation: solutionLocation - HostingPlanName: '${abbrs.compute.appServicePlan}${solutionPrefix}' - WebsiteName: '${abbrs.compute.webApp}${solutionPrefix}' - AzureSearchService: aifoundry.outputs.aiSearchService - AzureSearchIndex: 'transcripts_index' - AzureSearchUseSemanticSearch: 'True' - AzureSearchSemanticSearchConfig: 'my-semantic-config' - AzureSearchTopK: '5' - AzureSearchContentColumns: 'content' - AzureSearchFilenameColumn: 'chunk_id' - AzureSearchTitleColumn: 'client_id' - AzureSearchUrlColumn: 'sourceurl' - AzureOpenAIResource: aifoundry.outputs.aiFoundryName - AzureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint - AzureOpenAIModel: gptModelName - AzureOpenAITemperature: '0' - AzureOpenAITopP: '1' - AzureOpenAIMaxTokens: '1000' - AzureOpenAIStopSequence: '' - AzureOpenAISystemMessage: '''You are a helpful Wealth Advisor assistant''' - AzureOpenAIApiVersion: azureOpenaiAPIVersion - AzureOpenAIStream: 'True' - AzureSearchQueryType: 'simple' - AzureSearchVectorFields: 'contentVector' - AzureSearchPermittedGroupsField: '' - AzureSearchStrictness: '3' - AzureOpenAIEmbeddingName: embeddingModel - AzureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint - USE_INTERNAL_STREAM: 'True' - SQLDB_SERVER: '${sqlDBModule.outputs.sqlServerName}.database.windows.net' + hostingPlanName: hostingPlanName + websiteName: websiteName + appEnvironment: appEnvironment + azureSearchService: aifoundry.outputs.aiSearchService + azureSearchIndex: azureSearchIndex + azureSearchUseSemanticSearch: azureSearchUseSemanticSearch + azureSearchSemanticSearchConfig: azureSearchSemanticSearchConfig + azureSearchTopK: azureSearchTopK + azureSearchContentColumns: azureSearchContentColumns + azureSearchFilenameColumn: azureSearchFilenameColumn + azureSearchTitleColumn: azureSearchTitleColumn + azureSearchUrlColumn: azureSearchUrlColumn + azureOpenAIResource: aifoundry.outputs.aiFoundryName + azureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint + azureOpenAIModel: gptModelName + azureOpenAITemperature: azureOpenAITemperature + azureOpenAITopP: azureOpenAITopP + azureOpenAIMaxTokens: azureOpenAIMaxTokens + azureOpenAIStopSequence: azureOpenAIStopSequence + azureOpenAISystemMessage: azureOpenAISystemMessage + azureOpenAIApiVersion: azureOpenaiAPIVersion + azureOpenAIStream: azureOpenAIStream + azureSearchQueryType: azureSearchQueryType + azureSearchVectorFields: azureSearchVectorFields + azureSearchPermittedGroupsField: azureSearchPermittedGroupsField + azureSearchStrictness: azureSearchStrictness + azureOpenAIEmbeddingName: embeddingModel + azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint + USE_INTERNAL_STREAM: useInternalStream + SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName AZURE_COSMOSDB_ACCOUNT: cosmosDBModule.outputs.cosmosAccountName AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDBModule.outputs.cosmosContainerName AZURE_COSMOSDB_DATABASE: cosmosDBModule.outputs.cosmosDatabaseName - AZURE_COSMOSDB_ENABLE_FEEDBACK: 'True' + AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback //VITE_POWERBI_EMBED_URL: 'TBD' imageTag: imageTag userassignedIdentityClientId: managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId @@ -268,21 +333,180 @@ module appserviceModule 'deploy_app_service.bicep' = { applicationInsightsConnectionString: aifoundry.outputs.applicationInsightsConnectionString azureExistingAIProjectResourceId: azureExistingAIProjectResourceId aiSearchProjectConnectionName: aifoundry.outputs.aiSearchFoundryConnectionName + tags: tags } scope: resourceGroup(resourceGroup().name) } +@description('URL of the deployed web application.') output WEB_APP_URL string = appserviceModule.outputs.webAppUrl + +@description('Name of the storage account.') output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName + +@description('Name of the storage container.') output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer + +@description('Name of the Key Vault.') output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName + +@description('Name of the Cosmos DB account.') output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName + +@description('Name of the resource group.') output RESOURCE_GROUP_NAME string = resourceGroup().name -output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry -output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName + +@description('The resource ID of the AI Foundry instance.') +output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId + +@description('Name of the SQL Database server.') +output SQLDB_SERVER_NAME string = sqlDBModule.outputs.sqlServerName + +@description('Name of the SQL Database.') output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName + +@description('Name of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name + +@description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId -output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName +@description('Name of the AI Search service.') output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService + +@description('Name of the deployed web application.') output WEB_APP_NAME string = appserviceModule.outputs.webAppName +@description('Specifies the current application environment.') +output APP_ENV string = appEnvironment + +@description('The Application Insights instrumentation key.') +output APPINSIGHTS_INSTRUMENTATIONKEY string = aifoundry.outputs.instrumentationKey + +@description('The Application Insights connection string.') +output APPLICATIONINSIGHTS_CONNECTION_STRING string = aifoundry.outputs.applicationInsightsConnectionString + +@description('The API version used for the Azure AI Agent service.') +output AZURE_AI_AGENT_API_VERSION string = azureOpenaiAPIVersion + +@description('The endpoint URL of the Azure AI Agent project.') +output AZURE_AI_AGENT_ENDPOINT string = aifoundry.outputs.aiFoundryProjectEndpoint + +@description('The deployment name of the GPT model for the Azure AI Agent.') +output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName + +@description('The endpoint URL of the Azure AI Search service.') +output AZURE_AI_SEARCH_ENDPOINT string = aifoundry.outputs.aiSearchTarget + +@description('The system prompt used for call transcript processing in Azure Functions.') +output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt + +@description('The name of the Azure Cosmos DB account.') +output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.cosmosAccountName + +@description('The name of the Azure Cosmos DB container for storing conversations.') +output AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = cosmosDBModule.outputs.cosmosContainerName + +@description('The name of the Azure Cosmos DB database.') +output AZURE_COSMOSDB_DATABASE string = cosmosDBModule.outputs.cosmosDatabaseName + +@description('Indicates whether feedback is enabled in Azure Cosmos DB.') +output AZURE_COSMOSDB_ENABLE_FEEDBACK string = azureCosmosDbEnableFeedback + +@description('The endpoint URL for the Azure OpenAI Embedding model.') +output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aifoundry.outputs.aoaiEndpoint + +@description('The name of the Azure OpenAI Embedding model.') +output AZURE_OPENAI_EMBEDDING_NAME string = embeddingModel + +@description('The endpoint URL for the Azure OpenAI service.') +output AZURE_OPENAI_ENDPOINT string = aifoundry.outputs.aoaiEndpoint + +@description('The maximum number of tokens for Azure OpenAI responses.') +output AZURE_OPENAI_MAX_TOKENS string = azureOpenAIMaxTokens + +@description('The name of the Azure OpenAI GPT model.') +output AZURE_OPENAI_MODEL string = gptModelName + +@description('The preview API version for Azure OpenAI.') +output AZURE_OPENAI_PREVIEW_API_VERSION string = azureOpenaiAPIVersion + +@description('The Azure OpenAI resource name.') +output AZURE_OPENAI_RESOURCE string = aifoundry.outputs.aiFoundryName + +@description('The stop sequence(s) for Azure OpenAI responses.') +output AZURE_OPENAI_STOP_SEQUENCE string = azureOpenAIStopSequence + +@description('Indicates whether streaming is enabled for Azure OpenAI responses.') +output AZURE_OPENAI_STREAM string = azureOpenAIStream + +@description('The system prompt for streaming text responses in Azure Functions.') +output AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT string = functionAppStreamTextSystemPrompt + +@description('The system message for Azure OpenAI requests.') +output AZURE_OPENAI_SYSTEM_MESSAGE string = azureOpenAISystemMessage + +@description('The temperature setting for Azure OpenAI responses.') +output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature + +@description('The Top-P setting for Azure OpenAI responses.') +output AZURE_OPENAI_TOP_P string = azureOpenAITopP + +@description('The name of the Azure AI Search connection.') +output AZURE_SEARCH_CONNECTION_NAME string = aifoundry.outputs.aiSearchFoundryConnectionName + +@description('The columns in Azure AI Search that contain content.') +output AZURE_SEARCH_CONTENT_COLUMNS string = azureSearchContentColumns + +@description('Indicates whether in-domain filtering is enabled for Azure AI Search.') +output AZURE_SEARCH_ENABLE_IN_DOMAIN string = azureSearchEnableInDomain + +@description('The filename column used in Azure AI Search.') +output AZURE_SEARCH_FILENAME_COLUMN string = azureSearchFilenameColumn + +@description('The name of the Azure AI Search index.') +output AZURE_SEARCH_INDEX string = azureSearchIndex + +@description('The permitted groups field used in Azure AI Search.') +output AZURE_SEARCH_PERMITTED_GROUPS_COLUMN string = azureSearchPermittedGroupsField + +@description('The query type for Azure AI Search.') +output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType + +@description('The semantic search configuration name in Azure AI Search.') +output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig + +@description('The name of the Azure AI Search service.') +output AZURE_SEARCH_SERVICE string = aifoundry.outputs.aiSearchService + +@description('The strictness setting for Azure AI Search semantic ranking.') +output AZURE_SEARCH_STRICTNESS string = azureSearchStrictness + +@description('The title column used in Azure AI Search.') +output AZURE_SEARCH_TITLE_COLUMN string = azureSearchTitleColumn + +@description('The number of top results (K) to return from Azure AI Search.') +output AZURE_SEARCH_TOP_K string = azureSearchTopK + +@description('The URL column used in Azure AI Search.') +output AZURE_SEARCH_URL_COLUMN string = azureSearchUrlColumn + +@description('Indicates whether semantic search is used in Azure AI Search.') +output AZURE_SEARCH_USE_SEMANTIC_SEARCH string = azureSearchUseSemanticSearch + +@description('The vector fields used in Azure AI Search.') +output AZURE_SEARCH_VECTOR_COLUMNS string = azureSearchVectorFields + +@description('The system prompt for SQL queries in Azure Functions.') +output AZURE_SQL_SYSTEM_PROMPT string = functionAppSqlPrompt + +@description('The fully qualified domain name (FQDN) of the Azure SQL Server.') +output SQLDB_SERVER string = sqlServerFqdn + +@description('The client ID of the managed identity for the web application.') +output SQLDB_USER_MID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId + +@description('Indicates whether the AI Project Client should be used.') +output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag + +@description('Indicates whether the internal stream should be used.') +output USE_INTERNAL_STREAM string = useInternalStream + diff --git a/infra/main.json b/infra/main.json index 02d2d31ea..5ec0bea04 100644 --- a/infra/main.json +++ b/infra/main.json @@ -4,38 +4,39 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "3253509031453285119" + "version": "0.37.4.10188", + "templateHash": "4697605839482939008" } }, "parameters": { - "environmentName": { + "solutionName": { "type": "string", + "defaultValue": "clientadvisor", "minLength": 3, "maxLength": 20, "metadata": { - "description": "A unique prefix for all resources in this deployment. This should be 3-20 characters long:" + "description": "Required. A unique prefix for all resources in this deployment. This should be 3-20 characters long:" } }, "existingLogAnalyticsWorkspaceId": { "type": "string", "defaultValue": "", "metadata": { - "description": "Optional: Existing Log Analytics Workspace Resource ID" + "description": "Optional. Existing Log Analytics Workspace Resource ID" } }, "azureExistingAIProjectResourceId": { "type": "string", "defaultValue": "", "metadata": { - "description": "Use this parameter to use an existing AI project resource ID" + "description": "Optional. Use this parameter to use an existing AI project resource ID" } }, "cosmosLocation": { "type": "string", "defaultValue": "eastus2", "metadata": { - "description": "CosmosDB Location" + "description": "Optional. CosmosDB Location" } }, "deploymentType": { @@ -47,7 +48,7 @@ ], "minLength": 1, "metadata": { - "description": "GPT model deployment type:" + "description": "Optional. GPT model deployment type:" } }, "gptModelName": { @@ -58,19 +59,22 @@ ], "minLength": 1, "metadata": { - "description": "Name of the GPT model to deploy:" + "description": "Optional. Name of the GPT model to deploy:" } }, "azureOpenaiAPIVersion": { "type": "string", - "defaultValue": "2025-04-01-preview" + "defaultValue": "2025-04-01-preview", + "metadata": { + "description": "Optional. API version for the Azure OpenAI service." + } }, "gptDeploymentCapacity": { "type": "int", "defaultValue": 200, "minValue": 10, "metadata": { - "description": "Capacity of the GPT deployment:" + "description": "Optional. Capacity of the GPT deployment:" } }, "embeddingModel": { @@ -81,7 +85,7 @@ ], "minLength": 1, "metadata": { - "description": "Name of the Text Embedding model to deploy:" + "description": "Optional. Name of the Text Embedding model to deploy:" } }, "embeddingDeploymentCapacity": { @@ -89,12 +93,15 @@ "defaultValue": 80, "minValue": 10, "metadata": { - "description": "Capacity of the Embedding Model deployment" + "description": "Optional. Capacity of the Embedding Model deployment" } }, "imageTag": { "type": "string", - "defaultValue": "latest" + "defaultValue": "latest", + "metadata": { + "description": "The Docker image tag to use for the application deployment." + } }, "aiDeploymentsLocation": { "type": "string", @@ -117,254 +124,67 @@ "OpenAI.GlobalStandard.text-embedding-ada-002,80" ] }, - "description": "Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." + "description": "Rquired. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." } }, "AZURE_LOCATION": { "type": "string", "defaultValue": "", "metadata": { - "description": "Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." + "description": "Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." + } + }, + "solutionUniqueToken": { + "type": "string", + "defaultValue": "[substring(uniqueString(subscription().id, resourceGroup().name, parameters('solutionName')), 0, 5)]", + "maxLength": 5, + "metadata": { + "description": "Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name." } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Resources/resourceGroups@2025-04-01#properties/tags" + }, + "description": "Optional. The tags to apply to all deployed Azure resources." + }, + "defaultValue": {} } }, "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiFoundry": "aif-", - "aiFoundryProject": "aifp-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", - "uniqueId": "[toLower(uniqueString(parameters('environmentName'), subscription().id, variables('solutionLocation'), resourceGroup().name))]", - "solutionPrefix": "[format('ca{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", - "abbrs": "[variables('$fxv#0')]", + "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", + "hostingPlanName": "[format('asp-{0}', variables('solutionSuffix'))]", + "websiteName": "[format('app-{0}', variables('solutionSuffix'))]", + "appEnvironment": "Prod", + "azureSearchIndex": "transcripts_index", + "azureSearchUseSemanticSearch": "True", + "azureSearchSemanticSearchConfig": "my-semantic-config", + "azureSearchTopK": "5", + "azureSearchContentColumns": "content", + "azureSearchFilenameColumn": "chunk_id", + "azureSearchTitleColumn": "client_id", + "azureSearchUrlColumn": "sourceurl", + "azureOpenAITemperature": "0", + "azureOpenAITopP": "1", + "azureOpenAIMaxTokens": "1000", + "azureOpenAIStopSequence": "\n", + "azureOpenAISystemMessage": "You are a helpful Wealth Advisor assistant", + "azureOpenAIStream": "True", + "azureSearchQueryType": "simple", + "azureSearchVectorFields": "contentVector", + "azureSearchPermittedGroupsField": "", + "azureSearchStrictness": "3", + "azureSearchEnableInDomain": "False", + "azureCosmosDbEnableFeedback": "True", + "useInternalStream": "True", + "useAIProjectClientFlag": "False", "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n Only return the generated SQL query. Do not return anything else.", "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'." + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", + "aiFoundryAiServicesAiProjectResourceName": "[format('proj-{0}', variables('solutionSuffix'))]" }, "resources": [ { @@ -372,9 +192,7 @@ "apiVersion": "2021-04-01", "name": "default", "properties": { - "tags": { - "TemplateName": "Client Advisor" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('TemplateName', 'Client Advisor')))]" } }, { @@ -389,13 +207,16 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[variables('solutionLocation')]" }, "miName": { - "value": "[format('{0}{1}', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))]" + "value": "[format('id-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -404,8 +225,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "17366528426252264029" + "version": "0.37.4.10188", + "templateHash": "6639330323573416008" } }, "parameters": { @@ -414,19 +235,26 @@ "minLength": 3, "maxLength": 15, "metadata": { - "description": "Solution Name" + "description": "Required. Name of the solution." } }, "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Deployment location for the solution." } }, "miName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the managed identity." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." } } }, @@ -436,10 +264,7 @@ "apiVersion": "2023-01-31", "name": "[parameters('miName')]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" }, { "type": "Microsoft.Authorization/roleAssignments", @@ -459,15 +284,15 @@ "apiVersion": "2023-01-31", "name": "[format('{0}-webapp', parameters('miName'))]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" } ], "outputs": { "managedIdentityOutput": { "type": "object", + "metadata": { + "description": "Details of the managed identity resource." + }, "value": { "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]", "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]", @@ -477,6 +302,9 @@ }, "managedIdentityWebAppOutput": { "type": "object", + "metadata": { + "description": "Details of the managed identity for the web app." + }, "value": { "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName')))]", "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName'))), '2023-01-31').principalId]", @@ -500,7 +328,7 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[variables('solutionLocation')]" @@ -509,7 +337,10 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" }, "kvName": { - "value": "[format('{0}{1}', variables('abbrs').security.keyVault, variables('solutionPrefix'))]" + "value": "[format('kv-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -518,75 +349,76 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "18360475517235523175" + "version": "0.37.4.10188", + "templateHash": "12303448646842336311" } }, "parameters": { "solutionName": { "type": "string", - "minLength": 3, - "maxLength": 15, "metadata": { - "description": "Solution Name" + "description": "Required. Solution Name" } }, "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Solution Location" } }, "utc": { "type": "string", - "defaultValue": "[utcNow()]" + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Current UTC timestamp." + } }, "kvName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the Azure Key Vault." } }, "createMode": { "type": "string", "defaultValue": "default", "metadata": { - "description": "Create Mode" + "description": "Optional. Specifies the create mode for the resource." } }, "enableForDeployment": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." + "description": "Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." } }, "enableForDiskEncryption": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." + "description": "Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." } }, "enableForTemplateDeployment": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault." + "description": "Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault." } }, "enableRBACAuthorization": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enable RBAC Authorization. Property that controls how data actions are authorized." + "description": "Optional. Enable RBAC Authorization. Property that controls how data actions are authorized." } }, "softDeleteRetentionInDays": { "type": "int", "defaultValue": 7, "metadata": { - "description": "Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90." + "description": "Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90." } }, "publicNetworkAccess": { @@ -597,7 +429,7 @@ "disabled" ], "metadata": { - "description": "Public Network Access, Property to specify whether the vault will accept traffic from public internet." + "description": "Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet." } }, "sku": { @@ -608,11 +440,21 @@ "premium" ], "metadata": { - "description": "SKU" + "description": "Optional. SKU" } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "variables": { @@ -624,10 +466,7 @@ "apiVersion": "2022-07-01", "name": "[parameters('kvName')]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - }, + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]", "properties": { "accessPolicies": [ { @@ -679,10 +518,16 @@ "outputs": { "keyvaultName": { "type": "string", + "metadata": { + "description": "Name of the Key Vault." + }, "value": "[parameters('kvName')]" }, "keyvaultId": { "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault." + }, "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('kvName'))]" } } @@ -704,7 +549,7 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[parameters('aiDeploymentsLocation')]" @@ -735,6 +580,12 @@ }, "azureExistingAIProjectResourceId": { "value": "[parameters('azureExistingAIProjectResourceId')]" + }, + "aiFoundryAiServicesAiProjectResourceName": { + "value": "[variables('aiFoundryAiServicesAiProjectResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -743,287 +594,106 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "1343961496887433815" + "version": "0.37.4.10188", + "templateHash": "651349839825117270" } }, "parameters": { "solutionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Solution Name" + } }, "solutionLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Solution Location" + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Contains Name of KeyVault." + } }, "deploymentType": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Indicates the type of Deployment." + } }, "gptModelName": { - "type": "string" + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Optional. GPT Model Name" + } }, "azureOpenaiAPIVersion": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Azure OepnAI API Version." + } }, "gptDeploymentCapacity": { - "type": "int" + "type": "int", + "metadata": { + "description": "Required. Param to get Deployment Capacity." + } }, "embeddingModel": { - "type": "string" + "type": "string", + "defaultValue": "text-embedding-ada-002", + "metadata": { + "description": "Optional. Embedding Model." + } }, "embeddingDeploymentCapacity": { - "type": "int" + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Optional. Info about Embedding Deployment Capacity." + } }, "existingLogAnalyticsWorkspaceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Existing Log Analytics WorkspaceID." + } }, "azureExistingAIProjectResourceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Azure Existing AI Project ResourceID." + } + }, + "aiFoundryAiServicesAiProjectResourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of the AI Foundry AI Project resource in Azure." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiFoundry": "aif-", - "aiFoundryProject": "aifp-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, - "abbrs": "[variables('$fxv#0')]", - "aiFoundryName": "[format('{0}{1}', variables('abbrs').ai.aiFoundry, parameters('solutionName'))]", - "applicationInsightsName": "[format('{0}{1}', variables('abbrs').managementGovernance.applicationInsights, parameters('solutionName'))]", + "aiFoundryName": "[format('aif-{0}', parameters('solutionName'))]", + "applicationInsightsName": "[format('appi-{0}', parameters('solutionName'))]", "keyvaultName": "[parameters('keyVaultName')]", "location": "[parameters('solutionLocation')]", - "aiProjectName": "[format('{0}{1}', variables('abbrs').ai.aiFoundryProject, parameters('solutionName'))]", + "aiProjectName": "[format('{0}-{1}', parameters('aiFoundryAiServicesAiProjectResourceName'), parameters('solutionName'))]", "aiProjectFriendlyName": "[variables('aiProjectName')]", "aiProjectDescription": "AI Foundry Project", - "aiSearchName": "[format('{0}{1}', variables('abbrs').ai.aiSearch, parameters('solutionName'))]", - "workspaceName": "[format('{0}{1}', variables('abbrs').managementGovernance.logAnalyticsWorkspace, parameters('solutionName'))]", + "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]", + "workspaceName": "[format('log-{0}', parameters('solutionName'))]", "aiModelDeployments": [ { "name": "[parameters('gptModelName')]", @@ -1053,6 +723,7 @@ "existingAIFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]", "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], '')]", + "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], '')]", "aiSearchConnectionName": "[format('foundry-search-connection-{0}', parameters('solutionName'))]", "aiAppInsightConnectionName": "[format('foundry-app-insights-connection-{0}', parameters('solutionName'))]" @@ -1064,7 +735,7 @@ "apiVersion": "2023-09-01", "name": "[variables('workspaceName')]", "location": "[variables('location')]", - "tags": {}, + "tags": "[parameters('tags')]", "properties": { "retentionInDays": 30, "sku": { @@ -1084,6 +755,7 @@ "publicNetworkAccessForQuery": "Enabled", "WorkspaceResourceId": "[if(variables('useExisting'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingLawSubscription'), variables('existingLawResourceGroup')), 'Microsoft.OperationalInsights/workspaces', variables('existingLawName')), resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')))]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" ] @@ -1111,7 +783,8 @@ }, "publicNetworkAccess": "Enabled", "disableLocalAuth": false - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", @@ -1126,6 +799,7 @@ "description": "[variables('aiProjectDescription')]", "displayName": "[variables('aiProjectFriendlyName')]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" ] @@ -1185,7 +859,8 @@ } }, "semanticSearch": "free" - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", @@ -1208,6 +883,22 @@ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" ] }, + { + "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('aiFoundryName'))]", + "name": "[guid(resourceGroup().id, resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]", + "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" + ] + }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", "type": "Microsoft.Authorization/roleAssignments", @@ -1232,12 +923,12 @@ "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'), 'Existing')]", "properties": { "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch'), '2022-09-01').outputs.aiProjectPrincipalId.value]", + "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch')]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" ] }, { @@ -1264,12 +955,12 @@ "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), 'Existing')]", "properties": { "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch'), '2022-09-01').outputs.aiProjectPrincipalId.value]", + "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch')]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" ] }, { @@ -1301,7 +992,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]", "properties": { "value": "[parameters('azureOpenaiAPIVersion')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1310,6 +1002,7 @@ "properties": { "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" ] @@ -1320,7 +1013,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-EMBEDDING-MODEL')]", "properties": { "value": "[parameters('embeddingModel')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1329,6 +1023,7 @@ "properties": { "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" ] @@ -1339,7 +1034,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-INDEX')]", "properties": { "value": "transcripts_index" - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", @@ -1379,28 +1075,46 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11938772240348515861" + "version": "0.37.4.10188", + "templateHash": "6038840175458269917" } }, "parameters": { "existingAIProjectName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Existing AI Project Name" + } }, "existingAIFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Existing AI Foundry Name" + } }, "aiSearchName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Name" + } }, "aiSearchResourceId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Resource ID" + } }, "aiSearchLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Location" + } }, "aiSearchConnectionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Connection Name" + } } }, "resources": [ @@ -1428,9 +1142,10 @@ ] }, { + "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assignOpenAIRoleToAISearch", + "name": "assignOpenAIRoleToAISearchExisting", "subscriptionId": "[variables('existingAIServiceSubscription')]", "resourceGroup": "[variables('existingAIServiceResourceGroup')]", "properties": { @@ -1446,9 +1161,15 @@ "value": "[guid(resourceGroup().id, resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), 'openai-foundry')]" }, "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIFoundryName')), createObject('value', variables('aiFoundryName')))]", - "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", "principalId": { "value": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]" + }, + "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", + "aiModelDeployments": { + "value": "[variables('aiModelDeployments')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1457,31 +1178,84 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10199364008784095733" + "version": "0.37.4.10188", + "templateHash": "14256377996349985323" } }, "parameters": { "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Principal ID to assign the role to." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the role definition to assign." + } }, "roleAssignmentName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the role assignment." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry resource." + } }, "aiProjectName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the AI project." + } + }, + "aiModelDeployments": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of AI model deployments." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ + { + "copy": { + "name": "aiServicesDeployments", + "count": "[length(parameters('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(parameters('aiModelDeployments')))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('aiModelDeployments')[copyIndex()].model]" + }, + "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "sku": { + "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", + "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" + }, + "tags": "[parameters('tags')]" + }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", @@ -1497,10 +1271,16 @@ "outputs": { "aiServicesPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Services resource." + }, "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" }, "aiProjectPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Project resource if defined." + }, "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" } } @@ -1514,67 +1294,140 @@ "outputs": { "keyvaultName": { "type": "string", + "metadata": { + "description": "Contains Name of KeyVault." + }, "value": "[variables('keyvaultName')]" }, "keyvaultId": { "type": "string", + "metadata": { + "description": "Contains KeyVault ID." + }, "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" }, "resourceGroupNameFoundry": { "type": "string", + "metadata": { + "description": "Contains AI Foundry ResourceGroup Name" + }, "value": "[if(not(empty(variables('existingAIServiceResourceGroup'))), variables('existingAIServiceResourceGroup'), resourceGroup().name)]" }, "aiFoundryProjectEndpoint": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry Project Endpoint." + }, "value": "[if(not(empty(variables('existingProjEndpoint'))), variables('existingProjEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API'])]" }, "aoaiEndpoint": { "type": "string", + "metadata": { + "description": "Contains AI Endpoint." + }, "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" }, "aiFoundryName": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry." + }, "value": "[if(not(empty(variables('existingAIFoundryName'))), variables('existingAIFoundryName'), variables('aiFoundryName'))]" }, + "aiFoundryId": { + "type": "string", + "value": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', variables('existingAIFoundryName')), resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')))]" + }, "aiSearchName": { "type": "string", + "metadata": { + "description": "Contains AI Search Name." + }, "value": "[variables('aiSearchName')]" }, "aiSearchId": { "type": "string", + "metadata": { + "description": "Contains AI SearchID." + }, "value": "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" }, "aiSearchTarget": { "type": "string", + "metadata": { + "description": "Contains AI Search Target." + }, "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" }, "aiSearchService": { "type": "string", + "metadata": { + "description": "Contains AI Search Service." + }, "value": "[variables('aiSearchName')]" }, "aiFoundryProjectName": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry Project." + }, "value": "[if(not(empty(variables('existingAIProjectName'))), variables('existingAIProjectName'), variables('aiProjectName'))]" }, "applicationInsightsId": { "type": "string", + "metadata": { + "description": "Contains Application Insights ID." + }, "value": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" }, + "instrumentationKey": { + "type": "string", + "metadata": { + "description": "The Instrumentation Key for the Application Insights resource." + }, + "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').InstrumentationKey]" + }, "logAnalyticsWorkspaceResourceName": { "type": "string", + "metadata": { + "description": "Contains Log Analytics Workspace Resource Name." + }, "value": "[if(variables('useExisting'), variables('existingLawName'), variables('workspaceName'))]" }, "logAnalyticsWorkspaceResourceGroup": { "type": "string", + "metadata": { + "description": "Contains Log Analytics Workspace ResourceGroup Name." + }, "value": "[if(variables('useExisting'), variables('existingLawResourceGroup'), resourceGroup().name)]" }, "applicationInsightsConnectionString": { "type": "string", + "metadata": { + "description": "Contains Application Insights Connection String." + }, "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').ConnectionString]" }, "aiSearchFoundryConnectionName": { "type": "string", + "metadata": { + "description": "Contains AI Search Foundry Connection Name." + }, "value": "[variables('aiSearchConnectionName')]" + }, + "aiAppInsightsFoundryConnectionName": { + "type": "string", + "metadata": { + "description": "Contains AI Foundry App Insights Connection Name." + }, + "value": "[variables('aiAppInsightConnectionName')]" + }, + "aiModelDeployments": { + "type": "array", + "metadata": { + "description": "Contains AI Model Deployments" + }, + "value": "[variables('aiModelDeployments')]" } } } @@ -1598,7 +1451,10 @@ "value": "[parameters('cosmosLocation')]" }, "cosmosDBName": { - "value": "[format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix'))]" + "value": "[format('cosmos-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1607,27 +1463,38 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "8645494058104543506" + "version": "0.37.4.10188", + "templateHash": "7830346183750605706" } }, "parameters": { "solutionLocation": { - "type": "string" + "type": "string", + "minLength": 3, + "maxLength": 20, + "metadata": { + "description": "Required. Solution location." + } }, "cosmosDBName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the Azure Cosmos DB account." } }, "databaseName": { "type": "string", - "defaultValue": "db_conversation_history" + "defaultValue": "db_conversation_history", + "metadata": { + "description": "Optional. Name of the Cosmos DB database." + } }, "collectionName": { "type": "string", - "defaultValue": "conversations" + "defaultValue": "conversations", + "metadata": { + "description": "Optional.Name of the Cosmos DB container (collection)." + } }, "containers": { "type": "array", @@ -1637,7 +1504,10 @@ "id": "[parameters('collectionName')]", "partitionKey": "/userId" } - ] + ], + "metadata": { + "description": "Optional. List of Cosmos DB containers to be created." + } }, "kind": { "type": "string", @@ -1646,11 +1516,17 @@ "GlobalDocumentDB", "MongoDB", "Parse" - ] + ], + "metadata": { + "description": "Optional. The API kind of the Cosmos DB account." + } }, "tags": { "type": "object", - "defaultValue": {} + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -1716,6 +1592,7 @@ "id": "[parameters('databaseName')]" } }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]" ] @@ -1724,14 +1601,23 @@ "outputs": { "cosmosAccountName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, "value": "[parameters('cosmosDBName')]" }, "cosmosDatabaseName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB database." + }, "value": "[parameters('databaseName')]" }, "cosmosContainerName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB container." + }, "value": "[parameters('collectionName')]" } } @@ -1756,10 +1642,13 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" }, "saName": { - "value": "[format('{0}{1}', variables('abbrs').storage.storageAccount, variables('solutionPrefix'))]" + "value": "[format('st{0}', variables('solutionSuffix'))]" }, "keyVaultName": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1768,28 +1657,41 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15740867049667651602" + "version": "0.37.4.10188", + "templateHash": "3164769802823369019" } }, "parameters": { "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Deployment location for the solution." } }, "saName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the storage account." } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Key Vault." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -1829,7 +1731,8 @@ }, "accessTier": "Hot", "allowSharedKeyAccess": false - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Storage/storageAccounts/blobServices", @@ -1878,7 +1781,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-NAME')]", "properties": { "value": "[parameters('saName')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1886,16 +1790,23 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-CONTAINER')]", "properties": { "value": "data" - } + }, + "tags": "[parameters('tags')]" } ], "outputs": { "storageName": { "type": "string", + "metadata": { + "description": "Name of the storage account." + }, "value": "[parameters('saName')]" }, "storageContainer": { "type": "string", + "metadata": { + "description": "Name of the default storage container." + }, "value": "data" } } @@ -1930,10 +1841,13 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.name]" }, "serverName": { - "value": "[format('{0}{1}', variables('abbrs').databases.sqlDatabaseServer, variables('solutionPrefix'))]" + "value": "[format('sql-{0}', variables('solutionSuffix'))]" }, "sqlDBName": { - "value": "[format('{0}{1}', variables('abbrs').databases.sqlDatabase, variables('solutionPrefix'))]" + "value": "[format('sqldb-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1942,42 +1856,61 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "17137003456218736893" + "version": "0.37.4.10188", + "templateHash": "16739666735476206580" } }, "parameters": { "solutionLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Deployment location for the solution." + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Key Vault." + } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } }, "managedIdentityName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity." + } }, "serverName": { "type": "string", "metadata": { - "description": "The name of the SQL logical server." + "description": "Required. The name of the SQL logical server." } }, "sqlDBName": { "type": "string", "metadata": { - "description": "The name of the SQL Database." + "description": "Required. The name of the SQL Database." } }, "location": { "type": "string", "defaultValue": "[parameters('solutionLocation')]", "metadata": { - "description": "Location for all resources." + "description": "Required. Location for all resources." } - } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + } }, "resources": [ { @@ -1998,7 +1931,8 @@ "administratorType": "ActiveDirectory", "azureADOnlyAuthentication": true } - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Sql/servers/firewallRules", @@ -2043,6 +1977,7 @@ "readScale": "Disabled", "zoneRedundant": false }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" ] @@ -2053,7 +1988,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-SERVER')]", "properties": { "value": "[format('{0}.database.windows.net', parameters('serverName'))]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -2061,16 +1997,23 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-DATABASE')]", "properties": { "value": "[parameters('sqlDBName')]" - } + }, + "tags": "[parameters('tags')]" } ], "outputs": { "sqlServerName": { "type": "string", + "metadata": { + "description": "Name of the SQL logical server." + }, "value": "[parameters('serverName')]" }, "sqlDbName": { "type": "string", + "metadata": { + "description": "Name of the SQL database." + }, "value": "[parameters('sqlDBName')]" } } @@ -2095,89 +2038,92 @@ "solutionLocation": { "value": "[variables('solutionLocation')]" }, - "HostingPlanName": { - "value": "[format('{0}{1}', variables('abbrs').compute.appServicePlan, variables('solutionPrefix'))]" + "hostingPlanName": { + "value": "[variables('hostingPlanName')]" + }, + "websiteName": { + "value": "[variables('websiteName')]" }, - "WebsiteName": { - "value": "[format('{0}{1}', variables('abbrs').compute.webApp, variables('solutionPrefix'))]" + "appEnvironment": { + "value": "[variables('appEnvironment')]" }, - "AzureSearchService": { + "azureSearchService": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, - "AzureSearchIndex": { - "value": "transcripts_index" + "azureSearchIndex": { + "value": "[variables('azureSearchIndex')]" }, - "AzureSearchUseSemanticSearch": { - "value": "True" + "azureSearchUseSemanticSearch": { + "value": "[variables('azureSearchUseSemanticSearch')]" }, - "AzureSearchSemanticSearchConfig": { - "value": "my-semantic-config" + "azureSearchSemanticSearchConfig": { + "value": "[variables('azureSearchSemanticSearchConfig')]" }, - "AzureSearchTopK": { - "value": "5" + "azureSearchTopK": { + "value": "[variables('azureSearchTopK')]" }, - "AzureSearchContentColumns": { - "value": "content" + "azureSearchContentColumns": { + "value": "[variables('azureSearchContentColumns')]" }, - "AzureSearchFilenameColumn": { - "value": "chunk_id" + "azureSearchFilenameColumn": { + "value": "[variables('azureSearchFilenameColumn')]" }, - "AzureSearchTitleColumn": { - "value": "client_id" + "azureSearchTitleColumn": { + "value": "[variables('azureSearchTitleColumn')]" }, - "AzureSearchUrlColumn": { - "value": "sourceurl" + "azureSearchUrlColumn": { + "value": "[variables('azureSearchUrlColumn')]" }, - "AzureOpenAIResource": { + "azureOpenAIResource": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" }, - "AzureOpenAIEndpoint": { + "azureOpenAIEndpoint": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, - "AzureOpenAIModel": { + "azureOpenAIModel": { "value": "[parameters('gptModelName')]" }, - "AzureOpenAITemperature": { - "value": "0" + "azureOpenAITemperature": { + "value": "[variables('azureOpenAITemperature')]" }, - "AzureOpenAITopP": { - "value": "1" + "azureOpenAITopP": { + "value": "[variables('azureOpenAITopP')]" }, - "AzureOpenAIMaxTokens": { - "value": "1000" + "azureOpenAIMaxTokens": { + "value": "[variables('azureOpenAIMaxTokens')]" }, - "AzureOpenAIStopSequence": { - "value": "" + "azureOpenAIStopSequence": { + "value": "[variables('azureOpenAIStopSequence')]" }, - "AzureOpenAISystemMessage": { - "value": "You are a helpful Wealth Advisor assistant" + "azureOpenAISystemMessage": { + "value": "[variables('azureOpenAISystemMessage')]" }, - "AzureOpenAIApiVersion": { + "azureOpenAIApiVersion": { "value": "[parameters('azureOpenaiAPIVersion')]" }, - "AzureOpenAIStream": { - "value": "True" + "azureOpenAIStream": { + "value": "[variables('azureOpenAIStream')]" }, - "AzureSearchQueryType": { - "value": "simple" + "azureSearchQueryType": { + "value": "[variables('azureSearchQueryType')]" }, - "AzureSearchVectorFields": { - "value": "contentVector" + "azureSearchVectorFields": { + "value": "[variables('azureSearchVectorFields')]" }, - "AzureSearchPermittedGroupsField": { - "value": "" + "azureSearchPermittedGroupsField": { + "value": "[variables('azureSearchPermittedGroupsField')]" }, - "AzureSearchStrictness": { - "value": "3" + "azureSearchStrictness": { + "value": "[variables('azureSearchStrictness')]" }, - "AzureOpenAIEmbeddingName": { + "azureOpenAIEmbeddingName": { "value": "[parameters('embeddingModel')]" }, - "AzureOpenAIEmbeddingEndpoint": { + "azureOpenAIEmbeddingEndpoint": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, "USE_INTERNAL_STREAM": { - "value": "True" + "value": "[variables('useInternalStream')]" }, "SQLDB_SERVER": { "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" @@ -2195,7 +2141,7 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { - "value": "True" + "value": "[variables('azureCosmosDbEnableFeedback')]" }, "imageTag": { "value": "[parameters('imageTag')]" @@ -2235,6 +2181,9 @@ }, "aiSearchProjectConnectionName": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -2243,18 +2192,18 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "7899619253922538038" + "version": "0.37.4.10188", + "templateHash": "1598037199910826757" } }, "parameters": { "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Solution Location" } }, - "HostingPlanSku": { + "hostingPlanSku": { "type": "string", "defaultValue": "B2", "allowedValues": [ @@ -2273,154 +2222,167 @@ "P0v3" ], "metadata": { - "description": "The pricing tier for the App Service plan" + "description": "Optional. The pricing tier for the App Service plan" + } + }, + "hostingPlanName": { + "type": "string", + "metadata": { + "description": "Required. Name of App Service plan" } }, - "HostingPlanName": { - "type": "string" + "websiteName": { + "type": "string", + "metadata": { + "description": "Required. Name of Web App" + } }, - "WebsiteName": { - "type": "string" + "appEnvironment": { + "type": "string", + "metadata": { + "description": "Specifies the application environment" + } }, - "AzureSearchService": { + "azureSearchService": { "type": "string", "defaultValue": "", "metadata": { - "description": "Name of Azure Search Service" + "description": "Optional. Name of Azure Search Service" } }, - "AzureSearchIndex": { + "azureSearchIndex": { "type": "string", "defaultValue": "", "metadata": { - "description": "Name of Azure Search Index" + "description": "Optional. Name of Azure Search Index" } }, - "AzureSearchUseSemanticSearch": { + "azureSearchUseSemanticSearch": { "type": "string", "defaultValue": "False", "metadata": { - "description": "Use semantic search" + "description": "Optional. Use semantic search" } }, - "AzureSearchSemanticSearchConfig": { + "azureSearchSemanticSearchConfig": { "type": "string", "defaultValue": "default", "metadata": { - "description": "Semantic search config" + "description": "Optional. Semantic search config" } }, - "AzureSearchTopK": { + "azureSearchTopK": { "type": "string", "defaultValue": "5", "metadata": { - "description": "Top K results" + "description": "Optional. Top K results" } }, - "AzureSearchEnableInDomain": { + "azureSearchEnableInDomain": { "type": "string", "defaultValue": "False", "metadata": { - "description": "Enable in domain" + "description": "Optional. Enable in domain" } }, - "AzureSearchContentColumns": { + "azureSearchContentColumns": { "type": "string", "defaultValue": "content", "metadata": { - "description": "Content columns" + "description": "Optional. Content columns" } }, - "AzureSearchFilenameColumn": { + "azureSearchFilenameColumn": { "type": "string", "defaultValue": "filename", "metadata": { - "description": "Filename column" + "description": "Optional. Filename column" } }, - "AzureSearchTitleColumn": { + "azureSearchTitleColumn": { "type": "string", "defaultValue": "client_id", "metadata": { - "description": "Title column" + "description": "Optional. Title column" } }, - "AzureSearchUrlColumn": { + "azureSearchUrlColumn": { "type": "string", "defaultValue": "sourceurl", "metadata": { - "description": "Url column" + "description": "Optional. Url column" } }, - "AzureOpenAIResource": { + "azureOpenAIResource": { "type": "string", "metadata": { - "description": "Name of Azure OpenAI Resource" + "description": "Required. Name of Azure OpenAI Resource" } }, - "AzureOpenAIModel": { + "azureOpenAIModel": { "type": "string", + "defaultValue": "gpt-4o-mini", "metadata": { - "description": "Azure OpenAI Model Deployment Name" + "description": "Optional. Azure OpenAI Model Deployment Name" } }, - "AzureOpenAIEndpoint": { + "azureOpenAIEndpoint": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Open AI Endpoint" + "description": "Optional. Azure Open AI Endpoint" } }, - "AzureOpenAITemperature": { + "azureOpenAITemperature": { "type": "string", "defaultValue": "0", "metadata": { - "description": "Azure OpenAI Temperature" + "description": "Optional. Azure OpenAI Temperature" } }, - "AzureOpenAITopP": { + "azureOpenAITopP": { "type": "string", "defaultValue": "1", "metadata": { - "description": "Azure OpenAI Top P" + "description": "Optional. Azure OpenAI Top P" } }, - "AzureOpenAIMaxTokens": { + "azureOpenAIMaxTokens": { "type": "string", "defaultValue": "1000", "metadata": { - "description": "Azure OpenAI Max Tokens" + "description": "Optional. Azure OpenAI Max Tokens" } }, - "AzureOpenAIStopSequence": { + "azureOpenAIStopSequence": { "type": "string", "defaultValue": "\n", "metadata": { - "description": "Azure OpenAI Stop Sequence" + "description": "Optional. Azure OpenAI Stop Sequence" } }, - "AzureOpenAISystemMessage": { + "azureOpenAISystemMessage": { "type": "string", "defaultValue": "You are an AI assistant that helps people find information.", "metadata": { - "description": "Azure OpenAI System Message" + "description": "Optional. Azure OpenAI System Message" } }, - "AzureOpenAIApiVersion": { + "azureOpenAIApiVersion": { "type": "string", "defaultValue": "2024-02-15-preview", "metadata": { - "description": "Azure OpenAI Api Version" + "description": "Optional. Azure OpenAI Api Version" } }, - "AzureOpenAIStream": { + "azureOpenAIStream": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Whether or not to stream responses from Azure OpenAI" + "description": "Optional. Whether or not to stream responses from Azure OpenAI" } }, - "AzureSearchQueryType": { + "azureSearchQueryType": { "type": "string", "defaultValue": "simple", "allowedValues": [ @@ -2431,24 +2393,24 @@ "vectorSemanticHybrid" ], "metadata": { - "description": "Azure Search Query Type" + "description": "Optional. Azure Search Query Type" } }, - "AzureSearchVectorFields": { + "azureSearchVectorFields": { "type": "string", "defaultValue": "contentVector", "metadata": { - "description": "Azure Search Vector Fields" + "description": "Optional. Azure Search Vector Fields" } }, - "AzureSearchPermittedGroupsField": { + "azureSearchPermittedGroupsField": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Search Permitted Groups Field" + "description": "Optional. Azure Search Permitted Groups Field" } }, - "AzureSearchStrictness": { + "azureSearchStrictness": { "type": "string", "defaultValue": "3", "allowedValues": [ @@ -2459,151 +2421,193 @@ "5" ], "metadata": { - "description": "Azure Search Strictness" + "description": "Optional. Azure Search Strictness" } }, - "AzureOpenAIEmbeddingName": { + "azureOpenAIEmbeddingName": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure OpenAI Embedding Deployment Name" + "description": "Optional. Azure OpenAI Embedding Deployment Name" } }, - "AzureOpenAIEmbeddingEndpoint": { + "azureOpenAIEmbeddingEndpoint": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Open AI Embedding Endpoint" + "description": "Optional. Azure Open AI Embedding Endpoint" } }, "USE_INTERNAL_STREAM": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Use Azure Function" + "description": "Optional. Use Azure Function" } }, "SQLDB_SERVER": { "type": "string", "defaultValue": "", "metadata": { - "description": "SQL Database Server Name" + "description": "Optional. SQL Database Server Name" } }, "SQLDB_DATABASE": { "type": "string", "defaultValue": "", "metadata": { - "description": "SQL Database Name" + "description": "Optional. SQL Database Name" } }, "AZURE_COSMOSDB_ACCOUNT": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Account" + "description": "Optional. Azure Cosmos DB Account" } }, "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Conversations Container" + "description": "Optional. Azure Cosmos DB Conversations Container" } }, "AZURE_COSMOSDB_DATABASE": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Database" + "description": "Optional. Azure Cosmos DB Database" } }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Enable feedback in Cosmos DB" + "description": "Optional. Enable feedback in Cosmos DB" } }, "imageTag": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The container image tag to be deployed" + } }, "userassignedIdentityId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The resource ID of the user-assigned managed identity to be used by the deployed resources." + } }, "userassignedIdentityClientId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The client ID of the user-assigned managed identity." + } }, "applicationInsightsId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring." + } }, "azureSearchServiceEndpoint": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The endpoint URL of the Azure Cognitive Search service." + } }, "sqlSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App SQL System Prompt" + "description": "Required. Azure Function App SQL System Prompt" } }, "callTranscriptSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App CallTranscript System Prompt" + "description": "Required. Azure Function App CallTranscript System Prompt" } }, "streamTextSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App Stream Text System Prompt" + "description": "Required. Azure Function App Stream Text System Prompt" } }, "aiFoundryProjectEndpoint": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Foundry project endpoint URL." + } }, "useAIProjectClientFlag": { "type": "string", - "defaultValue": "false" + "defaultValue": "false", + "metadata": { + "description": "Optional. Flag to enable AI project client." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry project." + } }, "applicationInsightsConnectionString": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Application Insights connection string." + } }, "aiSearchProjectConnectionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Connection name for Azure Cognitive Search." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } }, "azureExistingAIProjectResourceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the existing AI Foundry project." + } } }, "variables": { - "WebAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", + "webAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], subscription().subscriptionId)]", "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], resourceGroup().name)]", - "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]" + "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", + "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]" }, "resources": [ { "type": "Microsoft.Web/serverfarms", "apiVersion": "2020-06-01", - "name": "[parameters('HostingPlanName')]", + "name": "[parameters('hostingPlanName')]", "location": "[parameters('solutionLocation')]", "sku": { - "name": "[parameters('HostingPlanSku')]" + "name": "[parameters('hostingPlanSku')]" }, "properties": { - "name": "[parameters('HostingPlanName')]", + "name": "[parameters('hostingPlanName')]", "reserved": true }, - "kind": "linux" + "kind": "linux", + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Web/sites", "apiVersion": "2020-06-01", - "name": "[parameters('WebsiteName')]", + "name": "[parameters('websiteName')]", "location": "[parameters('solutionLocation')]", "identity": { "type": "SystemAssigned, UserAssigned", @@ -2612,12 +2616,12 @@ } }, "properties": { - "serverFarmId": "[parameters('HostingPlanName')]", + "serverFarmId": "[parameters('hostingPlanName')]", "siteConfig": { "appSettings": [ { "name": "APP_ENV", - "value": "Prod" + "value": "[parameters('appEnvironment')]" }, { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", @@ -2629,107 +2633,107 @@ }, { "name": "AZURE_SEARCH_SERVICE", - "value": "[parameters('AzureSearchService')]" + "value": "[parameters('azureSearchService')]" }, { "name": "AZURE_SEARCH_INDEX", - "value": "[parameters('AzureSearchIndex')]" + "value": "[parameters('azureSearchIndex')]" }, { "name": "AZURE_SEARCH_USE_SEMANTIC_SEARCH", - "value": "[parameters('AzureSearchUseSemanticSearch')]" + "value": "[parameters('azureSearchUseSemanticSearch')]" }, { "name": "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG", - "value": "[parameters('AzureSearchSemanticSearchConfig')]" + "value": "[parameters('azureSearchSemanticSearchConfig')]" }, { "name": "AZURE_SEARCH_TOP_K", - "value": "[parameters('AzureSearchTopK')]" + "value": "[parameters('azureSearchTopK')]" }, { "name": "AZURE_SEARCH_ENABLE_IN_DOMAIN", - "value": "[parameters('AzureSearchEnableInDomain')]" + "value": "[parameters('azureSearchEnableInDomain')]" }, { "name": "AZURE_SEARCH_CONTENT_COLUMNS", - "value": "[parameters('AzureSearchContentColumns')]" + "value": "[parameters('azureSearchContentColumns')]" }, { "name": "AZURE_SEARCH_FILENAME_COLUMN", - "value": "[parameters('AzureSearchFilenameColumn')]" + "value": "[parameters('azureSearchFilenameColumn')]" }, { "name": "AZURE_SEARCH_TITLE_COLUMN", - "value": "[parameters('AzureSearchTitleColumn')]" + "value": "[parameters('azureSearchTitleColumn')]" }, { "name": "AZURE_SEARCH_URL_COLUMN", - "value": "[parameters('AzureSearchUrlColumn')]" + "value": "[parameters('azureSearchUrlColumn')]" }, { "name": "AZURE_OPENAI_RESOURCE", - "value": "[parameters('AzureOpenAIResource')]" + "value": "[parameters('azureOpenAIResource')]" }, { "name": "AZURE_OPENAI_MODEL", - "value": "[parameters('AzureOpenAIModel')]" + "value": "[parameters('azureOpenAIModel')]" }, { "name": "AZURE_OPENAI_ENDPOINT", - "value": "[parameters('AzureOpenAIEndpoint')]" + "value": "[parameters('azureOpenAIEndpoint')]" }, { "name": "AZURE_OPENAI_TEMPERATURE", - "value": "[parameters('AzureOpenAITemperature')]" + "value": "[parameters('azureOpenAITemperature')]" }, { "name": "AZURE_OPENAI_TOP_P", - "value": "[parameters('AzureOpenAITopP')]" + "value": "[parameters('azureOpenAITopP')]" }, { "name": "AZURE_OPENAI_MAX_TOKENS", - "value": "[parameters('AzureOpenAIMaxTokens')]" + "value": "[parameters('azureOpenAIMaxTokens')]" }, { "name": "AZURE_OPENAI_STOP_SEQUENCE", - "value": "[parameters('AzureOpenAIStopSequence')]" + "value": "[parameters('azureOpenAIStopSequence')]" }, { "name": "AZURE_OPENAI_SYSTEM_MESSAGE", - "value": "[parameters('AzureOpenAISystemMessage')]" + "value": "[parameters('azureOpenAISystemMessage')]" }, { "name": "AZURE_OPENAI_PREVIEW_API_VERSION", - "value": "[parameters('AzureOpenAIApiVersion')]" + "value": "[parameters('azureOpenAIApiVersion')]" }, { "name": "AZURE_OPENAI_STREAM", - "value": "[parameters('AzureOpenAIStream')]" + "value": "[parameters('azureOpenAIStream')]" }, { "name": "AZURE_SEARCH_QUERY_TYPE", - "value": "[parameters('AzureSearchQueryType')]" + "value": "[parameters('azureSearchQueryType')]" }, { "name": "AZURE_SEARCH_VECTOR_COLUMNS", - "value": "[parameters('AzureSearchVectorFields')]" + "value": "[parameters('azureSearchVectorFields')]" }, { "name": "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN", - "value": "[parameters('AzureSearchPermittedGroupsField')]" + "value": "[parameters('azureSearchPermittedGroupsField')]" }, { "name": "AZURE_SEARCH_STRICTNESS", - "value": "[parameters('AzureSearchStrictness')]" + "value": "[parameters('azureSearchStrictness')]" }, { "name": "AZURE_OPENAI_EMBEDDING_NAME", - "value": "[parameters('AzureOpenAIEmbeddingName')]" + "value": "[parameters('azureOpenAIEmbeddingName')]" }, { "name": "AZURE_OPENAI_EMBEDDING_ENDPOINT", - "value": "[parameters('AzureOpenAIEmbeddingEndpoint')]" + "value": "[parameters('azureOpenAIEmbeddingEndpoint')]" }, { "name": "SQLDB_SERVER", @@ -2789,28 +2793,43 @@ }, { "name": "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME", - "value": "[parameters('AzureOpenAIModel')]" + "value": "[parameters('azureOpenAIModel')]" }, { "name": "AZURE_AI_AGENT_API_VERSION", - "value": "[parameters('AzureOpenAIApiVersion')]" + "value": "[parameters('azureOpenAIApiVersion')]" }, { "name": "AZURE_SEARCH_CONNECTION_NAME", "value": "[parameters('aiSearchProjectConnectionName')]" } ], - "linuxFxVersion": "[variables('WebAppImageName')]" + "linuxFxVersion": "[variables('webAppImageName')]" } }, + "tags": "[parameters('tags')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" + ] + }, + { + "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceGroup().id, extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]", + "principalType": "ServicePrincipal" + }, "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('HostingPlanName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('cosmos-sql-user-role-{0}', parameters('WebsiteName'))]", + "name": "[format('cosmos-sql-user-role-{0}', parameters('websiteName'))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2824,7 +2843,7 @@ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[0], split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[1])]" }, "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]" + "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" } }, "template": { @@ -2833,21 +2852,30 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9287160422728403181" + "version": "0.37.4.10188", + "templateHash": "13822905633352095375" }, "description": "Creates a SQL role assignment under an Azure Cosmos DB account." }, "parameters": { "accountName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Cosmos DB account." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the Cosmos DB SQL role definition." + } }, "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Otional. Principal ID to assign the role to." + } } }, "resources": [ @@ -2865,13 +2893,14 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] }, { + "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assignAiUserRoleToAiProject", + "name": "assignAiUserRoleToAiProjectExisting", "subscriptionId": "[variables('existingAIServiceSubscription')]", "resourceGroup": "[variables('existingAIServiceResourceGroup')]", "properties": { @@ -2881,15 +2910,21 @@ "mode": "Incremental", "parameters": { "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]" + "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" }, "roleDefinitionId": { "value": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]" }, "roleAssignmentName": { - "value": "[guid(parameters('WebsiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" + "value": "[guid(parameters('websiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" + }, + "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]", + "aiProjectName": { + "value": "[variables('existingAIProjectName')]" }, - "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]" + "tags": { + "value": "[parameters('tags')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2897,31 +2932,84 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10199364008784095733" + "version": "0.37.4.10188", + "templateHash": "14256377996349985323" } }, "parameters": { "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Principal ID to assign the role to." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the role definition to assign." + } }, "roleAssignmentName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the role assignment." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry resource." + } }, "aiProjectName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the AI project." + } + }, + "aiModelDeployments": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of AI model deployments." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ + { + "copy": { + "name": "aiServicesDeployments", + "count": "[length(parameters('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(parameters('aiModelDeployments')))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('aiModelDeployments')[copyIndex()].model]" + }, + "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "sku": { + "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", + "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" + }, + "tags": "[parameters('tags')]" + }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", @@ -2937,28 +3025,40 @@ "outputs": { "aiServicesPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Services resource." + }, "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" }, "aiProjectPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Project resource if defined." + }, "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" } } } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] } ], "outputs": { "webAppUrl": { "type": "string", - "value": "[format('https://{0}.azurewebsites.net', parameters('WebsiteName'))]" + "metadata": { + "description": "URL of the deployed web application." + }, + "value": "[format('https://{0}.azurewebsites.net', parameters('websiteName'))]" }, "webAppName": { "type": "string", - "value": "[parameters('WebsiteName')]" + "metadata": { + "description": "Name of the deployed web application." + }, + "value": "[parameters('websiteName')]" } } } @@ -2974,59 +3074,409 @@ "outputs": { "WEB_APP_URL": { "type": "string", + "metadata": { + "description": "URL of the deployed web application." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppUrl.value]" }, "STORAGE_ACCOUNT_NAME": { "type": "string", + "metadata": { + "description": "Name of the storage account." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageName.value]" }, "STORAGE_CONTAINER_NAME": { "type": "string", + "metadata": { + "description": "Name of the storage container." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageContainer.value]" }, "KEY_VAULT_NAME": { "type": "string", + "metadata": { + "description": "Name of the Key Vault." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" }, "COSMOSDB_ACCOUNT_NAME": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" }, "RESOURCE_GROUP_NAME": { "type": "string", + "metadata": { + "description": "Name of the resource group." + }, "value": "[resourceGroup().name]" }, - "RESOURCE_GROUP_NAME_FOUNDRY": { + "AI_FOUNDRY_RESOURCE_ID": { "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.resourceGroupNameFoundry.value]" + "metadata": { + "description": "The resource ID of the AI Foundry instance." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryId.value]" }, - "SQLDB_SERVER": { + "SQLDB_SERVER_NAME": { "type": "string", + "metadata": { + "description": "Name of the SQL Database server." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value]" }, "SQLDB_DATABASE": { "type": "string", + "metadata": { + "description": "Name of the SQL Database." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlDbName.value]" }, "MANAGEDIDENTITY_WEBAPP_NAME": { "type": "string", + "metadata": { + "description": "Name of the managed identity used by the web app." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.name]" }, "MANAGEDIDENTITY_WEBAPP_CLIENTID": { "type": "string", + "metadata": { + "description": "Client ID of the managed identity used by the web app." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" }, - "AI_FOUNDRY_NAME": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" - }, "AI_SEARCH_SERVICE_NAME": { "type": "string", + "metadata": { + "description": "Name of the AI Search service." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, "WEB_APP_NAME": { "type": "string", + "metadata": { + "description": "Name of the deployed web application." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppName.value]" + }, + "APP_ENV": { + "type": "string", + "metadata": { + "description": "Specifies the current application environment." + }, + "value": "[variables('appEnvironment')]" + }, + "APPINSIGHTS_INSTRUMENTATIONKEY": { + "type": "string", + "metadata": { + "description": "The Application Insights instrumentation key." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "metadata": { + "description": "The Application Insights connection string." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_AI_AGENT_API_VERSION": { + "type": "string", + "metadata": { + "description": "The API version used for the Azure AI Agent service." + }, + "value": "[parameters('azureOpenaiAPIVersion')]" + }, + "AZURE_AI_AGENT_ENDPOINT": { + "type": "string", + "metadata": { + "description": "The endpoint URL of the Azure AI Agent project." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryProjectEndpoint.value]" + }, + "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": { + "type": "string", + "metadata": { + "description": "The deployment name of the GPT model for the Azure AI Agent." + }, + "value": "[parameters('gptModelName')]" + }, + "AZURE_AI_SEARCH_ENDPOINT": { + "type": "string", + "metadata": { + "description": "The endpoint URL of the Azure AI Search service." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchTarget.value]" + }, + "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": { + "type": "string", + "metadata": { + "description": "The system prompt used for call transcript processing in Azure Functions." + }, + "value": "[variables('functionAppCallTranscriptSystemPrompt')]" + }, + "AZURE_COSMOSDB_ACCOUNT": { + "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB account." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" + }, + "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { + "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB container for storing conversations." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosContainerName.value]" + }, + "AZURE_COSMOSDB_DATABASE": { + "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB database." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" + }, + "AZURE_COSMOSDB_ENABLE_FEEDBACK": { + "type": "string", + "metadata": { + "description": "Indicates whether feedback is enabled in Azure Cosmos DB." + }, + "value": "[variables('azureCosmosDbEnableFeedback')]" + }, + "AZURE_OPENAI_EMBEDDING_ENDPOINT": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI Embedding model." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + }, + "AZURE_OPENAI_EMBEDDING_NAME": { + "type": "string", + "metadata": { + "description": "The name of the Azure OpenAI Embedding model." + }, + "value": "[parameters('embeddingModel')]" + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI service." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + }, + "AZURE_OPENAI_MAX_TOKENS": { + "type": "string", + "metadata": { + "description": "The maximum number of tokens for Azure OpenAI responses." + }, + "value": "[variables('azureOpenAIMaxTokens')]" + }, + "AZURE_OPENAI_MODEL": { + "type": "string", + "metadata": { + "description": "The name of the Azure OpenAI GPT model." + }, + "value": "[parameters('gptModelName')]" + }, + "AZURE_OPENAI_PREVIEW_API_VERSION": { + "type": "string", + "metadata": { + "description": "The preview API version for Azure OpenAI." + }, + "value": "[parameters('azureOpenaiAPIVersion')]" + }, + "AZURE_OPENAI_RESOURCE": { + "type": "string", + "metadata": { + "description": "The Azure OpenAI resource name." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" + }, + "AZURE_OPENAI_STOP_SEQUENCE": { + "type": "string", + "metadata": { + "description": "The stop sequence(s) for Azure OpenAI responses." + }, + "value": "[variables('azureOpenAIStopSequence')]" + }, + "AZURE_OPENAI_STREAM": { + "type": "string", + "metadata": { + "description": "Indicates whether streaming is enabled for Azure OpenAI responses." + }, + "value": "[variables('azureOpenAIStream')]" + }, + "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT": { + "type": "string", + "metadata": { + "description": "The system prompt for streaming text responses in Azure Functions." + }, + "value": "[variables('functionAppStreamTextSystemPrompt')]" + }, + "AZURE_OPENAI_SYSTEM_MESSAGE": { + "type": "string", + "metadata": { + "description": "The system message for Azure OpenAI requests." + }, + "value": "[variables('azureOpenAISystemMessage')]" + }, + "AZURE_OPENAI_TEMPERATURE": { + "type": "string", + "metadata": { + "description": "The temperature setting for Azure OpenAI responses." + }, + "value": "[variables('azureOpenAITemperature')]" + }, + "AZURE_OPENAI_TOP_P": { + "type": "string", + "metadata": { + "description": "The Top-P setting for Azure OpenAI responses." + }, + "value": "[variables('azureOpenAITopP')]" + }, + "AZURE_SEARCH_CONNECTION_NAME": { + "type": "string", + "metadata": { + "description": "The name of the Azure AI Search connection." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + }, + "AZURE_SEARCH_CONTENT_COLUMNS": { + "type": "string", + "metadata": { + "description": "The columns in Azure AI Search that contain content." + }, + "value": "[variables('azureSearchContentColumns')]" + }, + "AZURE_SEARCH_ENABLE_IN_DOMAIN": { + "type": "string", + "metadata": { + "description": "Indicates whether in-domain filtering is enabled for Azure AI Search." + }, + "value": "[variables('azureSearchEnableInDomain')]" + }, + "AZURE_SEARCH_FILENAME_COLUMN": { + "type": "string", + "metadata": { + "description": "The filename column used in Azure AI Search." + }, + "value": "[variables('azureSearchFilenameColumn')]" + }, + "AZURE_SEARCH_INDEX": { + "type": "string", + "metadata": { + "description": "The name of the Azure AI Search index." + }, + "value": "[variables('azureSearchIndex')]" + }, + "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN": { + "type": "string", + "metadata": { + "description": "The permitted groups field used in Azure AI Search." + }, + "value": "[variables('azureSearchPermittedGroupsField')]" + }, + "AZURE_SEARCH_QUERY_TYPE": { + "type": "string", + "metadata": { + "description": "The query type for Azure AI Search." + }, + "value": "[variables('azureSearchQueryType')]" + }, + "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG": { + "type": "string", + "metadata": { + "description": "The semantic search configuration name in Azure AI Search." + }, + "value": "[variables('azureSearchSemanticSearchConfig')]" + }, + "AZURE_SEARCH_SERVICE": { + "type": "string", + "metadata": { + "description": "The name of the Azure AI Search service." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" + }, + "AZURE_SEARCH_STRICTNESS": { + "type": "string", + "metadata": { + "description": "The strictness setting for Azure AI Search semantic ranking." + }, + "value": "[variables('azureSearchStrictness')]" + }, + "AZURE_SEARCH_TITLE_COLUMN": { + "type": "string", + "metadata": { + "description": "The title column used in Azure AI Search." + }, + "value": "[variables('azureSearchTitleColumn')]" + }, + "AZURE_SEARCH_TOP_K": { + "type": "string", + "metadata": { + "description": "The number of top results (K) to return from Azure AI Search." + }, + "value": "[variables('azureSearchTopK')]" + }, + "AZURE_SEARCH_URL_COLUMN": { + "type": "string", + "metadata": { + "description": "The URL column used in Azure AI Search." + }, + "value": "[variables('azureSearchUrlColumn')]" + }, + "AZURE_SEARCH_USE_SEMANTIC_SEARCH": { + "type": "string", + "metadata": { + "description": "Indicates whether semantic search is used in Azure AI Search." + }, + "value": "[variables('azureSearchUseSemanticSearch')]" + }, + "AZURE_SEARCH_VECTOR_COLUMNS": { + "type": "string", + "metadata": { + "description": "The vector fields used in Azure AI Search." + }, + "value": "[variables('azureSearchVectorFields')]" + }, + "AZURE_SQL_SYSTEM_PROMPT": { + "type": "string", + "metadata": { + "description": "The system prompt for SQL queries in Azure Functions." + }, + "value": "[variables('functionAppSqlPrompt')]" + }, + "SQLDB_SERVER": { + "type": "string", + "metadata": { + "description": "The fully qualified domain name (FQDN) of the Azure SQL Server." + }, + "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" + }, + "SQLDB_USER_MID": { + "type": "string", + "metadata": { + "description": "The client ID of the managed identity for the web application." + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" + }, + "USE_AI_PROJECT_CLIENT": { + "type": "string", + "metadata": { + "description": "Indicates whether the AI Project Client should be used." + }, + "value": "[variables('useAIProjectClientFlag')]" + }, + "USE_INTERNAL_STREAM": { + "type": "string", + "metadata": { + "description": "Indicates whether the internal stream should be used." + }, + "value": "[variables('useInternalStream')]" } } } \ No newline at end of file diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 10822f1f7..8fe1de47a 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -2,7 +2,7 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { - "environmentName": { + "solutionName": { "value": "${AZURE_ENV_NAME}" }, "cosmosLocation": { @@ -37,6 +37,11 @@ }, "azureExistingAIProjectResourceId": { "value": "${AZURE_EXISTING_AI_PROJECT_RESOURCE_ID}" + }, + "tags": { + "value": { + "solutionName": "${AZURE_ENV_NAME}" + } } } } \ No newline at end of file diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index 7b6213eee..aa8b3e291 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -10,18 +10,14 @@ sqlServerName="$6" SqlDatabaseName="$7" webAppManagedIdentityClientId="$8" webAppManagedIdentityDisplayName="$9" -aiFoundryName="${10}" -aiSearchName="${11}" -resourceGroupNameFoundry="${12}" +aiSearchName="${10}" +aif_resource_id="${11}" # get parameters from azd env, if not provided if [ -z "$resourceGroupName" ]; then resourceGroupName=$(azd env get-value RESOURCE_GROUP_NAME) fi -if [ -z "$resourceGroupNameFoundry" ]; then - resourceGroupNameFoundry=$(azd env get-value RESOURCE_GROUP_NAME_FOUNDRY) -fi if [ -z "$cosmosDbAccountName" ]; then cosmosDbAccountName=$(azd env get-value COSMOSDB_ACCOUNT_NAME) @@ -40,7 +36,7 @@ if [ -z "$keyvaultName" ]; then fi if [ -z "$sqlServerName" ]; then - sqlServerName=$(azd env get-value SQLDB_SERVER) + sqlServerName=$(azd env get-value SQLDB_SERVER_NAME) fi if [ -z "$SqlDatabaseName" ]; then @@ -55,19 +51,19 @@ if [ -z "$webAppManagedIdentityDisplayName" ]; then webAppManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_WEBAPP_NAME) fi -if [ -z "$aiFoundryName" ]; then - aiFoundryName=$(azd env get-value AI_FOUNDRY_NAME) -fi - if [ -z "$aiSearchName" ]; then aiSearchName=$(azd env get-value AI_SEARCH_SERVICE_NAME) fi +if [ -z "$aif_resource_id" ]; then + aif_resource_id=$(azd env get-value AI_FOUNDRY_RESOURCE_ID) +fi + azSubscriptionId=$(azd env get-value AZURE_SUBSCRIPTION_ID) # Check if all required arguments are provided -if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiFoundryName" ] || [ -z "$aiSearchName" ] || [ -z "$resourceGroupNameFoundry" ]; then - echo "Usage: $0 " +if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then + echo "Usage: $0 " exit 1 fi @@ -149,7 +145,7 @@ echo "copy_kb_files.sh completed successfully." # Call run_create_index_scripts.sh echo "Running run_create_index_scripts.sh" -bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiFoundryName" "$aiSearchName" "$resourceGroupNameFoundry" +bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiSearchName" "$aif_resource_id" if [ $? -ne 0 ]; then echo "Error: run_create_index_scripts.sh failed." exit 1 diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index fcf775723..ad4878d43 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -6,9 +6,8 @@ baseUrl="$2" managedIdentityClientId="$3" resourceGroupName="$4" sqlServerName="$5" -aiFoundryName="$6" -aiSearchName="$7" -resourceGroupNameFoundry="$8" +aiSearchName="$6" +aif_resource_id="$7" echo "Script Started" @@ -69,9 +68,6 @@ fi ### Assign Azure AI User role to the signed in user ### -echo "Getting Azure AI Foundry resource id" -aif_resource_id=$(az cognitiveservices account show --name $aiFoundryName --resource-group $resourceGroupNameFoundry --query id --output tsv) - # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --assignee $signed_user_id --query "[].roleDefinitionId" -o tsv) diff --git a/src/App/app.py b/src/App/app.py index 3eaa8b7c1..515eb1342 100644 --- a/src/App/app.py +++ b/src/App/app.py @@ -82,17 +82,25 @@ async def startup(): app.wealth_advisor_agent = await AgentFactory.get_wealth_advisor_agent() logging.info("Wealth Advisor Agent initialized during application startup") app.search_agent = await AgentFactory.get_search_agent() - logging.info( - "Call Transcript Search Agent initialized during application startup" - ) + logging.info("Call Transcript Search Agent initialized during application startup") + app.sql_agent = await AgentFactory.get_sql_agent() + logging.info("SQL Agent initialized during application startup") @app.after_serving async def shutdown(): - await AgentFactory.delete_all_agent_instance() - app.wealth_advisor_agent = None - app.search_agent = None - app.sql_agent = None - logging.info("Agents cleaned up during application shutdown") + try: + logging.info("Application shutdown initiated...") + await AgentFactory.delete_all_agent_instance() + if hasattr(app, 'wealth_advisor_agent'): + app.wealth_advisor_agent = None + if hasattr(app, 'search_agent'): + app.search_agent = None + if hasattr(app, 'sql_agent'): + app.sql_agent = None + logging.info("Agents cleaned up successfully") + except Exception as e: + logging.error(f"Error during shutdown: {e}") + logging.exception("Detailed error during shutdown") # app.secret_key = secrets.token_hex(16) # app.session_interface = SecureCookieSessionInterface() diff --git a/src/App/backend/agents/agent_factory.py b/src/App/backend/agents/agent_factory.py index 6bae33808..d1f3956b8 100644 --- a/src/App/backend/agents/agent_factory.py +++ b/src/App/backend/agents/agent_factory.py @@ -7,6 +7,7 @@ """ import asyncio +import logging from typing import Optional from azure.ai.projects import AIProjectClient @@ -26,6 +27,7 @@ class AgentFactory: _lock = asyncio.Lock() _wealth_advisor_agent: Optional[AzureAIAgent] = None _search_agent: Optional[dict] = None + _sql_agent: Optional[dict] = None @classmethod async def get_wealth_advisor_agent(cls): @@ -94,18 +96,67 @@ async def delete_all_agent_instance(cls): Delete the singleton AzureAIAgent instances if it exists. """ async with cls._lock: - if cls._wealth_advisor_agent is not None: - await cls._wealth_advisor_agent.client.agents.delete_agent( - cls._wealth_advisor_agent.id - ) - cls._wealth_advisor_agent = None + logging.info("Starting agent deletion process...") + # Delete Wealth Advisor Agent + if cls._wealth_advisor_agent is not None: + try: + agent_id = cls._wealth_advisor_agent.id + logging.info(f"Deleting wealth advisor agent: {agent_id}") + if hasattr(cls._wealth_advisor_agent, 'client') and cls._wealth_advisor_agent.client: + await cls._wealth_advisor_agent.client.agents.delete_agent(agent_id) + logging.info("Wealth advisor agent deleted successfully") + else: + logging.warning("Wealth advisor agent client is None") + except Exception as e: + logging.error(f"Error deleting wealth advisor agent: {e}") + logging.exception("Detailed wealth advisor agent deletion error") + finally: + cls._wealth_advisor_agent = None + + # Delete Search Agent if cls._search_agent is not None: - cls._search_agent["client"].agents.delete_agent( - cls._search_agent["agent"].id - ) - cls._search_agent["client"].close() - cls._search_agent = None + try: + agent_id = cls._search_agent['agent'].id + logging.info(f"Deleting search agent: {agent_id}") + if cls._search_agent.get("client") and hasattr(cls._search_agent["client"], "agents"): + # AIProjectClient.agents.delete_agent is synchronous, don't await it + cls._search_agent["client"].agents.delete_agent(agent_id) + logging.info("Search agent deleted successfully") + + # Close the client if it has a close method + if hasattr(cls._search_agent["client"], "close"): + cls._search_agent["client"].close() + else: + logging.warning("Search agent client is None or invalid") + except Exception as e: + logging.error(f"Error deleting search agent: {e}") + logging.exception("Detailed search agent deletion error") + finally: + cls._search_agent = None + + # Delete SQL Agent + if cls._sql_agent is not None: + try: + agent_id = cls._sql_agent['agent'].id + logging.info(f"Deleting SQL agent: {agent_id}") + if cls._sql_agent.get("client") and hasattr(cls._sql_agent["client"], "agents"): + # AIProjectClient.agents.delete_agent is synchronous, don't await it + cls._sql_agent["client"].agents.delete_agent(agent_id) + logging.info("SQL agent deleted successfully") + + # Close the client if it has a close method + if hasattr(cls._sql_agent["client"], "close"): + cls._sql_agent["client"].close() + else: + logging.warning("SQL agent client is None or invalid") + except Exception as e: + logging.error(f"Error deleting SQL agent: {e}") + logging.exception("Detailed SQL agent deletion error") + finally: + cls._sql_agent = None + + logging.info("Agent deletion process completed") @classmethod async def get_sql_agent(cls) -> dict: @@ -114,7 +165,7 @@ async def get_sql_agent(cls) -> dict: This agent is used to generate T-SQL queries from natural language input. """ async with cls._lock: - if not hasattr(cls, "_sql_agent") or cls._sql_agent is None: + if cls._sql_agent is None: agent_instructions = config.SQL_SYSTEM_PROMPT or """ You are an expert assistant in generating T-SQL queries based on user questions. diff --git a/src/App/backend/plugins/chat_with_data_plugin.py b/src/App/backend/plugins/chat_with_data_plugin.py index 86b064aac..605ca5d42 100644 --- a/src/App/backend/plugins/chat_with_data_plugin.py +++ b/src/App/backend/plugins/chat_with_data_plugin.py @@ -42,9 +42,14 @@ async def get_SQL_Response( if not input or not input.strip(): return "Error: Query input is required" + thread = None try: + # TEMPORARY: Use AgentFactory directly to debug the issue + logging.info(f"Using AgentFactory directly for SQL agent for ClientId: {ClientId}") from backend.agents.agent_factory import AgentFactory agent_info = await AgentFactory.get_sql_agent() + + logging.info(f"SQL agent retrieved: {agent_info is not None}") agent = agent_info["agent"] project_client = agent_info["client"] @@ -73,23 +78,27 @@ async def get_SQL_Response( role=MessageRole.AGENT ) sql_query = message.text.value.strip() if message else None + logging.info(f"Generated SQL query: {sql_query}") if not sql_query: return "No SQL query was generated." # Clean up triple backticks (if any) sql_query = sql_query.replace("```sql", "").replace("```", "") + logging.info(f"Cleaned SQL query: {sql_query}") # Execute the query conn = get_connection() cursor = conn.cursor() cursor.execute(sql_query) rows = cursor.fetchall() + logging.info(f"Query returned {len(rows)} rows") if not rows: result = "No data found for that client." else: result = "\n".join(str(row) for row in rows) + logging.info(f"Result preview: {result[:200]}...") conn.close() @@ -97,6 +106,14 @@ async def get_SQL_Response( except Exception as e: logging.exception("Error in get_SQL_Response") return f"Error retrieving SQL data: {str(e)}" + finally: + if thread: + try: + logging.info(f"Attempting to delete thread {thread.id}") + await project_client.agents.threads.delete(thread.id) + logging.info(f"Thread {thread.id} deleted successfully") + except Exception as e: + logging.error(f"Error deleting thread {thread.id}: {str(e)}") @kernel_function( name="ChatWithCallTranscripts", @@ -115,12 +132,13 @@ async def get_answers_from_calltranscripts( if not question or not question.strip(): return "Error: Question input is required" + thread = None try: response_text = "" from backend.agents.agent_factory import AgentFactory - agent_info: dict = await AgentFactory.get_search_agent() + agent_info = await AgentFactory.get_search_agent() agent: Agent = agent_info["agent"] project_client: AIProjectClient = agent_info["client"] @@ -191,7 +209,11 @@ async def get_answers_from_calltranscripts( finally: if thread: - project_client.agents.threads.delete(thread.id) + try: + await project_client.agents.threads.delete(thread.id) + logging.info(f"Thread {thread.id} deleted successfully") + except Exception as e: + logging.error(f"Error deleting thread {thread.id}: {str(e)}") if not response_text.strip(): return "No data found for that client."