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