diff --git a/azure.yaml b/azure.yaml index 467fbbddc..6e6291d1e 100644 --- a/azure.yaml +++ b/azure.yaml @@ -5,6 +5,10 @@ environment: name: conversation-knowledge-mining location: eastus name: conversation-knowledge-mining + +requiredVersions: + azd: ">= 1.15.0" + metadata: template: conversation-knowledge-mining@1.0 diff --git a/documents/DeploymentGuide.md b/documents/DeploymentGuide.md index 5a0c17c41..bdd4dabb8 100644 --- a/documents/DeploymentGuide.md +++ b/documents/DeploymentGuide.md @@ -80,7 +80,7 @@ If you're not using one of the above options for opening the project, then you'l 1. Make sure the following tools are installed: - [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.5) (v7.0+) - available for Windows, macOS, and Linux. - - [Azure Developer CLI (azd)](https://aka.ms/install-azd) + - [Azure Developer CLI (azd)](https://aka.ms/install-azd) (v1.15.0+) - version - [Python 3.9+](https://www.python.org/downloads/) - [Docker Desktop](https://www.docker.com/products/docker-desktop/) - [Git](https://git-scm.com/downloads) diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index ee378fea5..9a1b6b557 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -1,7 +1,7 @@ // Creates Azure dependent resources for Azure AI studio param solutionName string param solutionLocation string -param keyVaultName string +param keyVaultName string='' param cuLocation string param deploymentType string param gptModelName string @@ -10,7 +10,7 @@ param azureOpenAIApiVersion string param gptDeploymentCapacity int param embeddingModel string param embeddingDeploymentCapacity int -param managedIdentityObjectId string +param managedIdentityObjectId string='' param existingLogAnalyticsWorkspaceId string = '' param azureExistingAIProjectResourceId string = '' @@ -116,6 +116,15 @@ resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = } } +module existing_aiServicesModule 'existing_foundry_project.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'existing_foundry_project' + scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) + params: { + aiServicesName: existingAIServicesName + aiProjectName: existingAIProjectName + } +} + resource aiServices_CU 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { name: aiServicesName_cu location: location_cu @@ -225,6 +234,7 @@ resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' } + resource assignFoundryRoleToMI 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { name: guid(resourceGroup().id, aiServices.id, aiUser.id) scope: aiServices @@ -234,7 +244,6 @@ resource assignFoundryRoleToMI 'Microsoft.Authorization/roleAssignments@2022-04- principalType: 'ServicePrincipal' } } - module assignFoundryRoleToMIExisting 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { name: 'assignFoundryRoleToMI' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) @@ -244,6 +253,15 @@ module assignFoundryRoleToMIExisting 'deploy_foundry_role_assignment.bicep' = if aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiServicesName aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName principalId: managedIdentityObjectId + aiLocation: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.location : solutionLocation + aiKind: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.kind : 'AIServices' + aiSkuName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.skuName : 'S0' + customSubDomainName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.customSubDomainName : aiServicesName + publicNetworkAccess: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.publicNetworkAccess : 'Enabled' + enableSystemAssignedIdentity: true + defaultNetworkAction: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.defaultNetworkAction : 'Allow' + vnetRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.vnetRules : [] + ipRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.ipRules : [] } } @@ -270,6 +288,15 @@ module assignOpenAIRoleToAISearch 'deploy_foundry_role_assignment.bicep' = { aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiServicesName aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName principalId: aiSearch.identity.principalId + aiLocation: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.location : solutionLocation + aiKind: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.kind : 'AIServices' + aiSkuName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.skuName : 'S0' + customSubDomainName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.customSubDomainName : aiServicesName + publicNetworkAccess: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.publicNetworkAccess : 'Enabled' + enableSystemAssignedIdentity: true + defaultNetworkAction: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.defaultNetworkAction : 'Allow' + vnetRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.vnetRules : [] + ipRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.ipRules : [] } } @@ -470,7 +497,6 @@ resource azureLocatioEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview value: solutionLocation } } - output keyvaultName string = keyvaultName output keyvaultId string = keyVault.id output aiServicesTarget string = !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiServices.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint diff --git a/infra/deploy_backend_docker.bicep b/infra/deploy_backend_docker.bicep index 9354448d1..607e8f28c 100644 --- a/infra/deploy_backend_docker.bicep +++ b/infra/deploy_backend_docker.bicep @@ -14,9 +14,11 @@ param aiServicesName string param useLocalBuild string param azureExistingAIProjectResourceId string = '' param aiSearchName string +param aideploymentsLocation string var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[2] : subscription().subscriptionId var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[4] : resourceGroup().name var existingAIServicesName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[8] : '' +var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' var imageName = 'DOCKER|${acrName}.azurecr.io/km-api:${imageTag}' param name string @@ -165,6 +167,15 @@ resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' } +module existing_aiServicesModule 'existing_foundry_project.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'existing_foundry_project' + scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) + params: { + aiServicesName: existingAIServicesName + aiProjectName: existingAIProjectName + } +} + module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = { name: 'assignAiUserRoleToAiProject' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) @@ -173,6 +184,16 @@ module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = { roleDefinitionId: aiUser.id roleAssignmentName: guid(appService.name, aiServices.id, aiUser.id) aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiServicesName + aiProjectName: !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' + aiLocation: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.location : aideploymentsLocation + aiKind: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.kind : 'AIServices' + aiSkuName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.skuName : 'S0' + customSubDomainName: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.customSubDomainName : aiServicesName + publicNetworkAccess: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.publicNetworkAccess : 'Enabled' + enableSystemAssignedIdentity: true + defaultNetworkAction: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.defaultNetworkAction : 'Allow' + vnetRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.vnetRules : [] + ipRules: !empty(azureExistingAIProjectResourceId) ? existing_aiServicesModule.outputs.ipRules : [] } } diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep index d559b2a98..1fca921c9 100644 --- a/infra/deploy_foundry_role_assignment.bicep +++ b/infra/deploy_foundry_role_assignment.bicep @@ -3,16 +3,52 @@ param roleDefinitionId string param roleAssignmentName string = '' param aiServicesName string param aiProjectName string = '' +param aiLocation string='' +param aiKind string='' +param aiSkuName string='' +param enableSystemAssignedIdentity bool = true +param customSubDomainName string = '' +param publicNetworkAccess string = '' +param defaultNetworkAction string +param vnetRules array = [] +param ipRules array = [] -resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { +// AI Services with Identity (enabled only if flag is true) +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (enableSystemAssignedIdentity) { name: aiServicesName + location: aiLocation + kind: aiKind + sku: { + name: aiSkuName + } + identity: { + type: 'SystemAssigned' + } + properties: { + allowProjectManagement: true + customSubDomainName: customSubDomainName + networkAcls: { + defaultAction: defaultNetworkAction + virtualNetworkRules: vnetRules + ipRules: ipRules + } + publicNetworkAccess: publicNetworkAccess + + } } -resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { +// AI Project with Identity (only if name provided and flag is true) +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (!empty(aiProjectName) && enableSystemAssignedIdentity) { name: aiProjectName parent: aiServices + location: aiLocation + identity: { + type: 'SystemAssigned' + } + properties: {} } +// Role Assignment to AI Services resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: roleAssignmentName scope: aiServices @@ -22,5 +58,6 @@ resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-0 } } +// Outputs output aiServicesPrincipalId string = aiServices.identity.principalId -output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' +output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/existing_foundry_project.bicep b/infra/existing_foundry_project.bicep new file mode 100644 index 000000000..bad110cf6 --- /dev/null +++ b/infra/existing_foundry_project.bicep @@ -0,0 +1,35 @@ +@description('Name of the existing Azure AI Services account') +param aiServicesName string + +@description('Name of the existing AI Project under the AI Services account') +param aiProjectName string + +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiServicesName +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = { + name: aiProjectName + parent: aiServices +} + + + +// Outputs: AI Services Account +output location string = aiServices.location +output skuName string = aiServices.sku.name +output kind string = aiServices.kind +output allowProjectManagement bool = aiServices.properties.allowProjectManagement +output customSubDomainName string = aiServices.properties.customSubDomainName +output publicNetworkAccess string = aiServices.properties.publicNetworkAccess +output defaultNetworkAction string = aiServices.properties.networkAcls.defaultAction +output ipRules array = aiServices.properties.networkAcls.ipRules +output vnetRules array = aiServices.properties.networkAcls.virtualNetworkRules + +// Outputs: AI Project + +output projectLocation string = aiProject.location +output projectKind string = aiProject.kind +output projectProvisioningState string = aiProject.properties.provisioningState +// output projectDisplayName string = aiProject.properties.displayName +// output projectDescription string = aiProject.properties.description diff --git a/infra/main.bicep b/infra/main.bicep index a1f36a6ac..2127c38f9 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -134,7 +134,7 @@ module aifoundry 'deploy_ai_foundry.bicep' = { embeddingDeploymentCapacity: embeddingDeploymentCapacity managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId - azureExistingAIProjectResourceId: azureExistingAIProjectResourceId + azureExistingAIProjectResourceId: azureExistingAIProjectResourceId } scope: resourceGroup(resourceGroup().name) @@ -219,6 +219,7 @@ module backend_docker 'deploy_backend_docker.bicep' = { params: { name: 'api-${solutionPrefix}' solutionLocation: solutionLocation + aideploymentsLocation: aiDeploymentsLocation imageTag: imageTag acrName: acrName appServicePlanId: hostingplan.outputs.name