Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CustomizingAzdParameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ By default this template will use the environment name as the prefix to prevent
| `AZURE_ENV_IMAGETAG` | string | `latest` | Set the Image tag Like (allowed values: latest, dev, hotfix) |
| `AZURE_ENV_EMBEDDING_MODEL_CAPACITY` | integer | `80` | Sets the capacity for the embedding model 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 creating a new one. |

| `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | Guid to get your existing AI Foundry Project resource ID | Reuses an existing AIFoundry and AIFoundryProject instead of creating a new one. |


## How to Set a Parameter
Expand Down
2 changes: 1 addition & 1 deletion docs/DeploymentGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Once you've opened the project in [Codespaces](#github-codespaces), [Dev Contain
```
If you don't have azd env then you need to pass parameters along with the command. Then the command will look like the following:
```shell
bash ./infra/scripts/process_sample_data.sh <Storage-Account-name> <Storage-Account-container-name> <Key-Vault-name> <CosmosDB-Account-name> <Resource-Group-name> <aiFoundryResourceName> <aiSearchResourceName>
bash ./infra/scripts/process_sample_data.sh <Storage-Account-name> <Storage-Account-container-name> <Key-Vault-name> <CosmosDB-Account-name> <Resource-Group-name> <AI-Foundry-Name> <AI-Foundry-Resource-Group-Name> <Search-Service-Name>
```

6. Open the [Azure Portal](https://portal.azure.com/), go to the deployed resource group, find the App Service and get the app URL from `Default domain`.
Expand Down
144 changes: 114 additions & 30 deletions infra/deploy_ai_foundry.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ param embeddingModel string
param embeddingDeploymentCapacity int
param managedIdentityObjectId string
param existingLogAnalyticsWorkspaceId string = ''
param azureExistingAIProjectResourceId string = ''

var abbrs = loadJsonContent('./abbreviations.json')

Expand All @@ -23,13 +24,38 @@ var aiProjectFriendlyName = aiProjectName
var aiProjectDescription = 'AI Foundry Project'
var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}'
var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}'
var aiSearchConnectionName = 'myVectorStoreProjectConnectionName-${solutionName}'
// var aiSearchConnectionName = 'myVectorStoreProjectConnectionName-${solutionName}'

var useExisting = !empty(existingLogAnalyticsWorkspaceId)
var existingLawSubscription = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[2] : ''
var existingLawResourceGroup = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[4] : ''
var existingLawName = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[8] : ''

var existingOpenAIEndpoint = !empty(azureExistingAIProjectResourceId)
? format('https://{0}.openai.azure.com/', split(azureExistingAIProjectResourceId, '/')[8])
: ''
var existingProjEndpoint = !empty(azureExistingAIProjectResourceId)
? format(
'https://{0}.services.ai.azure.com/api/projects/{1}',
split(azureExistingAIProjectResourceId, '/')[8],
split(azureExistingAIProjectResourceId, '/')[10]
)
: ''
var existingAIFoundryName = !empty(azureExistingAIProjectResourceId)
? split(azureExistingAIProjectResourceId, '/')[8]
: ''
var existingAIProjectName = !empty(azureExistingAIProjectResourceId)
? split(azureExistingAIProjectResourceId, '/')[10]
: ''
var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId)
? split(azureExistingAIProjectResourceId, '/')[2]
: ''
var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId)
? split(azureExistingAIProjectResourceId, '/')[4]
: ''
var aiSearchConnectionName = 'foundry-search-connection-${solutionName}'
var aiAppInsightConnectionName = 'foundry-app-insights-connection-${solutionName}'

var aiModelDeployments = [
{
name: gptModelName
Expand Down Expand Up @@ -86,7 +112,12 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
}
}

resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
resource existingAiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if (!empty(azureExistingAIProjectResourceId)) {
name: existingAIFoundryName
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
}

resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
name: aiFoundryName
location: location
sku: {
Expand All @@ -109,7 +140,7 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
}
}

resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
parent: aiFoundry
name: aiProjectName
location: location
Expand All @@ -124,7 +155,7 @@ resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04

@batchSize(1)
resource aiFModelDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [
for aiModeldeployment in aiModelDeployments: {
for aiModeldeployment in aiModelDeployments: if (empty(azureExistingAIProjectResourceId)) {
parent: aiFoundry
name: aiModeldeployment.name
properties: {
Expand Down Expand Up @@ -166,7 +197,7 @@ resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = {
}
}

resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = {
resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) {
name: aiSearchConnectionName
parent: aiFoundryProject
properties: {
Expand All @@ -182,48 +213,87 @@ resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/project
}
}

module existing_AIProject_SearchConnectionModule 'deploy_aifp_aisearch_connection.bicep' = if (!empty(azureExistingAIProjectResourceId)) {
name: 'aiProjectSearchConnectionDeployment'
scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup)
params: {
existingAIProjectName: existingAIProjectName
existingAIFoundryName: existingAIFoundryName
aiSearchName: aiSearchName
aiSearchResourceId: aiSearch.id
aiSearchLocation: aiSearch.location
aiSearchConnectionName: aiSearchConnectionName
}
}

resource cognitiveServicesOpenAIUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
}

resource assignCognitiveRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, aiFoundryProject.id, cognitiveServicesOpenAIUser.id)
scope: aiFoundry
properties: {
module assignOpenAIRoleToAISearch 'deploy_foundry_role_assignment.bicep' = {
name: 'assignOpenAIRoleToAISearch'
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
principalType: 'ServicePrincipal'
}
}

resource searchIndexDataReader 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
@description('This is the built-in Search Index Data Reader role.')
resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
scope: aiSearch
name: '1407120a-92aa-4202-b7e9-c0e197c71c8f'
}

resource assignSearchIndexDataReaderToAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, aiFoundryProject.id, searchIndexDataReader.id)
resource searchIndexDataReaderRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) {
name: guid(aiSearch.id, aiFoundryProject.id, searchIndexDataReaderRoleDefinition.id)
scope: aiSearch
properties: {
roleDefinitionId: searchIndexDataReaderRoleDefinition.id
principalId: aiFoundryProject.identity.principalId
roleDefinitionId: searchIndexDataReader.id
principalType: 'ServicePrincipal'
}
}
resource assignSearchIndexDataReaderToExistingAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(azureExistingAIProjectResourceId)) {
name: guid(resourceGroup().id, existingAIProjectName, searchIndexDataReaderRoleDefinition.id, 'Existing')
scope: aiSearch
properties: {
roleDefinitionId: searchIndexDataReaderRoleDefinition.id
principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId
principalType: 'ServicePrincipal'
}
}

resource searchServiceContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
@description('This is the built-in Search Service Contributor role.')
resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
scope: aiSearch
name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0'
}

resource assignSearchServiceContributorToAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, aiFoundryProject.id, searchServiceContributor.id)
resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) {
name: guid(aiSearch.id, aiFoundryProject.id, searchServiceContributorRoleDefinition.id)
scope: aiSearch
properties: {
roleDefinitionId: searchServiceContributorRoleDefinition.id
principalId: aiFoundryProject.identity.principalId
roleDefinitionId: searchServiceContributor.id
principalType: 'ServicePrincipal'
}
}

resource searchServiceContributorRoleAssignmentExisting 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(azureExistingAIProjectResourceId)) {
name: guid(resourceGroup().id, existingAIProjectName, searchServiceContributorRoleDefinition.id, 'Existing')
scope: aiSearch
properties: {
roleDefinitionId: searchServiceContributorRoleDefinition.id
principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId
principalType: 'ServicePrincipal'
}
}


resource tenantIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'TENANT-ID'
Expand All @@ -232,6 +302,7 @@ resource tenantIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' =
}
}


resource azureOpenAIDeploymentModel 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'AZURE-OPEN-AI-DEPLOYMENT-MODEL'
Expand All @@ -252,9 +323,11 @@ resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-
parent: keyVault
name: 'AZURE-OPENAI-ENDPOINT'
properties: {
value: aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint
value: !empty(existingOpenAIEndpoint)
? existingOpenAIEndpoint
: aiFoundry.properties.endpoints['OpenAI Language Model Instance API']
}
}
}

resource azureOpenAIEmbeddingDeploymentModel 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
Expand Down Expand Up @@ -292,23 +365,27 @@ resource cogServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-p
parent: keyVault
name: 'COG-SERVICES-ENDPOINT'
properties: {
value: aiFoundry.properties.endpoint
value: !empty(existingOpenAIEndpoint)
? existingOpenAIEndpoint
: aiFoundry.properties.endpoint
}
}

resource cogServiceKeyEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'COG-SERVICES-KEY'
properties: {
value: aiFoundry.listKeys().key1
value: !empty(existingOpenAIEndpoint)
? existingAiFoundry.listKeys().key1
: aiFoundry.listKeys().key1
}
}

resource cogServiceNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'COG-SERVICES-NAME'
properties: {
value: aiFoundryName
value: !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName
}
}

Expand Down Expand Up @@ -339,19 +416,26 @@ resource azureLocatioEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview
output keyvaultName string = keyvaultName
output keyvaultId string = keyVault.id

output aiServicesTarget string = aiFoundry.properties.endpoint //aiServices_m.properties.endpoint
output aiServicesName string = aiFoundryName //aiServicesName_m
output aiServicesId string = aiFoundry.id //aiServices_m.id
// output aiServicesTarget string = aiFoundry.properties.endpoint //aiServices_m.properties.endpoint
// output aiServicesName string = aiFoundryName //aiServicesName_m
// output aiServicesId string = aiFoundry.id //aiServices_m.id

output aiSearchName string = aiSearchName
output aiSearchId string = aiSearch.id
output aiSearchTarget string = 'https://${aiSearch.name}.search.windows.net'
output aiSearchService string = aiSearch.name
output aiSearchConnectionName string = aiSearchConnectionName
output aiFoundryProjectName string = aiFoundryProject.name
output aiFoundryProjectEndpoint string = aiFoundryProject.properties.endpoints['AI Foundry API']
output aoaiEndpoint string = aiFoundry.properties.endpoints['OpenAI Language Model Instance API']
output aiFoundryName string = aiFoundryName
output aiFoundryProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiFoundryProject.name
// output aiFoundryProjectEndpoint string = aiFoundryProject.properties.endpoints['AI Foundry API']
output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint)
? existingProjEndpoint
: aiFoundryProject.properties.endpoints['AI Foundry API']
// output aoaiEndpoint string = aiFoundry.properties.endpoints['OpenAI Language Model Instance API']
output aoaiEndpoint string = !empty(existingOpenAIEndpoint)
? existingOpenAIEndpoint
: aiFoundry.properties.endpoints['OpenAI Language Model Instance API']
output aiFoundryName string = !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName
output aiFoundryRgName string = !empty(existingAIServiceResourceGroup) ? existingAIServiceResourceGroup : resourceGroup().name

output applicationInsightsId string = applicationInsights.id
output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name
Expand Down
21 changes: 21 additions & 0 deletions infra/deploy_aifp_aisearch_connection.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
param existingAIProjectName string
param existingAIFoundryName string
param aiSearchName string
param aiSearchResourceId string
param aiSearchLocation string
param aiSearchConnectionName string

resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = {
name: '${existingAIFoundryName}/${existingAIProjectName}/${aiSearchConnectionName}'
properties: {
category: 'CognitiveSearch'
target: 'https://${aiSearchName}.search.windows.net'
authType: 'AAD'
isSharedToAll: true
metadata: {
ApiType: 'Azure'
ResourceId: aiSearchResourceId
location: aiSearchLocation
}
}
}
Loading
Loading