Skip to content

Commit f546304

Browse files
Merge branch 'psl-pk-foundry-exp' into dev
2 parents 2e90eb2 + 03cd5f1 commit f546304

10 files changed

+424
-175
lines changed

documents/CustomizingAzdParameters.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ By default this template will use the environment name as the prefix to prevent
2323
| `AZURE_OPENAI_EMBEDDING_MODEL_CAPACITY` | integer | `80` | Sets the capacity for the embedding model deployment. |
2424
| `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | `<Existing Workspace Id>` | Reuses an existing Log Analytics Workspace instead of creating a new one. |
2525
| `USE_LOCAL_BUILD` | string | `false` | Indicates whether to use a local container build for deployment. |
26+
| `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | `<Existing AI Project resource Id>` | Reuses an existing AIFoundry and AIFoundryProject instead of creating a new one. |
2627

2728

2829

infra/abbreviations.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
"speechService": "spch-",
2020
"translator": "trsl-",
2121
"aiHub": "aih-",
22-
"aiHubProject": "aihp-"
22+
"aiHubProject": "aihp-",
23+
"aiFoundry": "aif-",
24+
"aiFoundryProject": "aifp-"
2325
},
2426
"analytics": {
2527
"analysisServicesServer": "as",

infra/deploy_ai_foundry.bicep

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ param embeddingModel string
1212
param embeddingDeploymentCapacity int
1313
param managedIdentityObjectId string
1414
param existingLogAnalyticsWorkspaceId string = ''
15+
param azureExistingAIProjectResourceId string = ''
1516

1617
var abbrs = loadJsonContent('./abbreviations.json')
1718
var aiServicesName = '${abbrs.ai.aiServices}${solutionName}'
@@ -21,7 +22,7 @@ var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${soluti
2122
var applicationInsightsName = '${abbrs.managementGovernance.applicationInsights}${solutionName}'
2223
var keyvaultName = '${abbrs.security.keyVault}${solutionName}'
2324
var location = solutionLocation //'eastus2'
24-
var aiProjectName = '${abbrs.ai.aiHubProject}${solutionName}'
25+
var aiProjectName = '${abbrs.ai.aiFoundryProject}${solutionName}'
2526
var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}'
2627

2728
var aiModelDeployments = [
@@ -51,6 +52,13 @@ var existingLawSubscription = useExisting ? split(existingLogAnalyticsWorkspaceI
5152
var existingLawResourceGroup = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[4] : ''
5253
var existingLawName = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[8] : ''
5354

55+
var existingOpenAIEndpoint = !empty(azureExistingAIProjectResourceId) ? format('https://{0}.openai.azure.com/', split(azureExistingAIProjectResourceId, '/')[8]) : ''
56+
var existingProjEndpoint = !empty(azureExistingAIProjectResourceId) ? format('https://{0}.services.ai.azure.com/api/projects/{1}', split(azureExistingAIProjectResourceId, '/')[8], split(azureExistingAIProjectResourceId, '/')[10]) : ''
57+
var existingAIServicesName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[8] : ''
58+
var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : ''
59+
var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[2] : ''
60+
var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[4] : ''
61+
5462
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
5563
name: keyVaultName
5664
}
@@ -84,7 +92,7 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
8492
}
8593
}
8694

87-
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
95+
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
8896
name: aiServicesName
8997
location: location
9098
sku: {
@@ -131,7 +139,7 @@ resource aiServices_CU 'Microsoft.CognitiveServices/accounts@2025-04-01-preview'
131139
}
132140

133141
@batchSize(1)
134-
resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: {
142+
resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (empty(azureExistingAIProjectResourceId)) {
135143
parent: aiServices //aiServices_m
136144
name: aiModeldeployment.name
137145
properties: {
@@ -172,7 +180,7 @@ resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = {
172180
}
173181
}
174182

175-
resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
183+
resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
176184
parent: aiServices
177185
name: aiProjectName
178186
location: solutionLocation
@@ -183,14 +191,13 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-pre
183191
properties: {}
184192
}
185193

186-
resource project_connection_azureai_search 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = {
187-
name: 'myVectorStoreProjectConnectionName'
194+
resource aiproject_aisearch_connection_new 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
195+
name: 'myVectorStoreProjectConnectionName-${solutionName}'
188196
parent: aiProject
189197
properties: {
190198
category: 'CognitiveSearch'
191199
target: 'https://${aiSearchName}.search.windows.net'
192200
authType: 'AAD'
193-
//useWorkspaceManagedIdentity: false
194201
isSharedToAll: true
195202
metadata: {
196203
ApiType: 'Azure'
@@ -200,6 +207,19 @@ resource project_connection_azureai_search 'Microsoft.CognitiveServices/accounts
200207
}
201208
}
202209

210+
module existing_AIProject_SearchConnectionModule 'deploy_aifp_aisearch_connection.bicep' = if (!empty(azureExistingAIProjectResourceId)) {
211+
name: 'aiProjectSearchConnectionDeployment'
212+
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
213+
params: {
214+
existingAIProjectName: existingAIProjectName
215+
existingAIServicesName: existingAIServicesName
216+
aiSearchName: aiSearchName
217+
aiSearchResourceId: aiSearch.id
218+
aiSearchLocation: aiSearch.location
219+
solutionName: solutionName
220+
}
221+
}
222+
203223
resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
204224
name: '53ca6127-db72-4b80-b1b0-d745d6d5456d'
205225
}
@@ -213,6 +233,17 @@ resource aiUserAccessFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01
213233
}
214234
}
215235

236+
module assignAiUserRoleToManagedIdentity 'deploy_foundry_role_assignment.bicep' = if(!empty(azureExistingAIProjectResourceId)) {
237+
name: 'assignAiUserRoleToManagedIdentity'
238+
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
239+
params: {
240+
roleDefinitionId: aiUser.id
241+
roleAssignmentName: guid(managedIdentityObjectId, aiServices.id, aiUser.id)
242+
aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiServicesName
243+
userassignedIdentityId: managedIdentityObjectId
244+
}
245+
}
246+
216247
resource tenantIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
217248
parent: keyVault
218249
name: 'TENANT-ID'
@@ -237,14 +268,6 @@ resource azureOpenAIInferenceKey 'Microsoft.KeyVault/vaults/secrets@2021-11-01-p
237268
}
238269
}
239270

240-
resource azureOpenAIApiKeyEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
241-
parent: keyVault
242-
name: 'AZURE-OPENAI-KEY'
243-
properties: {
244-
value: aiServices.listKeys().key1 //aiServices_m.listKeys().key1
245-
}
246-
}
247-
248271
resource azureOpenAIDeploymentModel 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
249272
parent: keyVault
250273
name: 'AZURE-OPENAI-DEPLOYMENT-MODEL'
@@ -265,15 +288,7 @@ resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-
265288
parent: keyVault
266289
name: 'AZURE-OPENAI-ENDPOINT'
267290
properties: {
268-
value: aiServices.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint
269-
}
270-
}
271-
272-
resource azureAIProjectConnectionStringEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
273-
parent: keyVault
274-
name: 'AZURE-AI-PROJECT-CONN-STRING'
275-
properties: {
276-
value: '${aiProjectName};${subscription().subscriptionId};${resourceGroup().name};${aiProject.name}'
291+
value: !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiServices.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint
277292
}
278293
}
279294

@@ -337,15 +352,7 @@ resource cogServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-p
337352
parent: keyVault
338353
name: 'COG-SERVICES-ENDPOINT'
339354
properties: {
340-
value: aiServices.properties.endpoint
341-
}
342-
}
343-
344-
resource cogServiceKeyEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
345-
parent: keyVault
346-
name: 'COG-SERVICES-KEY'
347-
properties: {
348-
value: aiServices.listKeys().key1
355+
value: !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiServices.properties.endpoints['OpenAI Language Model Instance API']
349356
}
350357
}
351358

@@ -383,22 +390,19 @@ resource azureLocatioEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview
383390

384391
output keyvaultName string = keyvaultName
385392
output keyvaultId string = keyVault.id
386-
output aiServicesTarget string = aiServices.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint
387-
output aiServicesName string = aiServicesName //aiServicesName_m
388-
output aiServicesId string = aiServices.id //aiServices_m.id
393+
output aiServicesTarget string = !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiServices.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint
394+
output aiServicesName string = !empty(existingAIServicesName) ? existingAIServicesName : aiServicesName
389395

390396
output aiSearchName string = aiSearchName
391397
output aiSearchId string = aiSearch.id
392398
output aiSearchTarget string = 'https://${aiSearch.name}.search.windows.net'
393399
output aiSearchService string = aiSearch.name
394-
output aiProjectName string = aiProject.name
400+
output aiProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiProject.name
395401

396402
output applicationInsightsId string = applicationInsights.id
397403
output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name
398404
output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawResourceGroup : resourceGroup().name
399405
output logAnalyticsWorkspaceSubscription string = useExisting ? existingLawSubscription : subscription().subscriptionId
400406

401-
output azureOpenAIKeyName string = azureOpenAIApiKeyEntry.name
402-
403-
output projectEndpoint string = aiProject.properties.endpoints['AI Foundry API']
407+
output projectEndpoint string = !empty(existingProjEndpoint) ? existingProjEndpoint : aiProject.properties.endpoints['AI Foundry API']
404408
output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
param existingAIProjectName string
2+
param existingAIServicesName string
3+
param aiSearchName string
4+
param aiSearchResourceId string
5+
param aiSearchLocation string
6+
param solutionName string
7+
8+
resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = {
9+
name: '${existingAIServicesName}/${existingAIProjectName}/myVectorStoreProjectConnectionName-${solutionName}'
10+
properties: {
11+
category: 'CognitiveSearch'
12+
target: 'https://${aiSearchName}.search.windows.net'
13+
authType: 'AAD'
14+
isSharedToAll: true
15+
metadata: {
16+
ApiType: 'Azure'
17+
ResourceId: aiSearchResourceId
18+
location: aiSearchLocation
19+
}
20+
}
21+
}

infra/deploy_backend_docker.bicep

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ param solutionLocation string
99
param appSettings object = {}
1010
param appServicePlanId string
1111
param userassignedIdentityId string
12-
param aiProjectName string
1312
param keyVaultName string
1413
param aiServicesName string
1514
param useLocalBuild string
15+
param azureExistingAIProjectResourceId string = ''
16+
var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[2] : subscription().subscriptionId
17+
var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[4] : resourceGroup().name
18+
var existingAIServicesName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[8] : ''
1619

1720
var imageName = 'DOCKER|${acrName}.azurecr.io/km-api:${imageTag}'
18-
//var name = '${solutionName}-api'
1921
param name string
2022
var reactAppLayoutConfig ='''{
2123
"appConfig": {
@@ -121,24 +123,7 @@ resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-
121123

122124
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = {
123125
name: aiServicesName
124-
}
125-
126-
resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = {
127-
parent: aiServices
128-
name: aiProjectName
129-
}
130-
131-
resource aiDeveloper 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
132-
name: '64702f94-c441-49e6-a78b-ef80e0188fee'
133-
}
134-
135-
resource aiDeveloperAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
136-
name: guid(appService.name, aiProject.id, aiDeveloper.id)
137-
scope: aiProject
138-
properties: {
139-
roleDefinitionId: aiDeveloper.id
140-
principalId: appService.outputs.identityPrincipalId
141-
}
126+
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
142127
}
143128

144129
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
@@ -162,21 +147,14 @@ resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing =
162147
name: '53ca6127-db72-4b80-b1b0-d745d6d5456d'
163148
}
164149

165-
resource aiUserAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
166-
name: guid(appService.name, aiProject.id, aiUser.id)
167-
scope: aiProject
168-
properties: {
169-
roleDefinitionId: aiUser.id
150+
module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)){
151+
name: 'assignAiUserRoleToAiProject'
152+
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
153+
params: {
170154
principalId: appService.outputs.identityPrincipalId
171-
}
172-
}
173-
174-
resource aiUserAccessFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
175-
name: guid(appService.name, aiServices.id, aiUser.id)
176-
scope: aiServices
177-
properties: {
178155
roleDefinitionId: aiUser.id
179-
principalId: appService.outputs.identityPrincipalId
156+
roleAssignmentName: guid(appService.name, aiServices.id, aiUser.id)
157+
aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiServicesName
180158
}
181159
}
182160

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
param principalId string = ''
2+
param roleDefinitionId string
3+
param roleAssignmentName string = ''
4+
param aiServicesName string
5+
param userassignedIdentityId string = ''
6+
7+
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = {
8+
name: aiServicesName
9+
}
10+
11+
resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiServicesName) && !empty(principalId)) {
12+
name: roleAssignmentName
13+
scope: aiServices
14+
properties: {
15+
roleDefinitionId: roleDefinitionId
16+
principalId: principalId
17+
}
18+
}
19+
20+
resource roleAssignmentToManagedIdentity 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(userassignedIdentityId)) {
21+
name: roleAssignmentName
22+
scope: aiServices
23+
properties: {
24+
roleDefinitionId: roleDefinitionId
25+
principalId: userassignedIdentityId
26+
principalType: 'ServicePrincipal'
27+
}
28+
}

infra/main.bicep

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ param environmentName string
99
@description('Optional: Existing Log Analytics Workspace Resource ID')
1010
param existingLogAnalyticsWorkspaceId string = ''
1111

12+
@description('Use this parameter to use an existing AI project resource ID')
13+
param azureExistingAIProjectResourceId string = ''
14+
1215
@minLength(1)
1316
@description('Location for the Content Understanding service deployment:')
1417
@allowed(['swedencentral', 'australiaeast'])
@@ -116,11 +119,13 @@ module aifoundry 'deploy_ai_foundry.bicep' = {
116119
embeddingDeploymentCapacity: embeddingDeploymentCapacity
117120
managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId
118121
existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId
122+
azureExistingAIProjectResourceId: azureExistingAIProjectResourceId
119123

120124
}
121125
scope: resourceGroup(resourceGroup().name)
122126
}
123127

128+
124129
// ========== Storage account module ========== //
125130
module storageAccount 'deploy_storage_account.bicep' = {
126131
name: 'deploy_storage_account'
@@ -204,10 +209,10 @@ module backend_docker 'deploy_backend_docker.bicep' = {
204209
appServicePlanId: hostingplan.outputs.name
205210
applicationInsightsId: aifoundry.outputs.applicationInsightsId
206211
userassignedIdentityId: managedIdentityModule.outputs.managedIdentityBackendAppOutput.id
207-
aiProjectName: aifoundry.outputs.aiProjectName
208212
keyVaultName: kvault.outputs.keyvaultName
209213
aiServicesName: aifoundry.outputs.aiServicesName
210214
useLocalBuild: useLocalBuildLower
215+
azureExistingAIProjectResourceId: azureExistingAIProjectResourceId
211216
appSettings: {
212217
AZURE_OPENAI_DEPLOYMENT_MODEL: gptModelName
213218
AZURE_OPENAI_ENDPOINT: aifoundry.outputs.aiServicesTarget

infra/main.bicepparam

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ param imageTag = readEnvironmentVariable('AZURE_ENV_IMAGETAG', 'latest')
1414
param embeddingDeploymentCapacity = int(readEnvironmentVariable('AZURE_OPENAI_EMBEDDING_MODEL_CAPACITY', '80'))
1515
param existingLogAnalyticsWorkspaceId = readEnvironmentVariable('AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID', '')
1616
param useLocalBuild = readEnvironmentVariable('USE_LOCAL_BUILD', 'false')
17+
param azureExistingAIProjectResourceId = readEnvironmentVariable('AZURE_EXISTING_AI_PROJECT_RESOURCE_ID', '')

0 commit comments

Comments
 (0)