diff --git a/.github/scripts/build_agent_setup_bicep.py b/.github/scripts/build_agent_setup_bicep.py new file mode 100755 index 00000000..edb285b2 --- /dev/null +++ b/.github/scripts/build_agent_setup_bicep.py @@ -0,0 +1,63 @@ +import sys +from pathlib import Path +from azure.cli.core import get_default_cli +from typing import List, Union + + +def run_az_command(*args: Union[str, Path]) -> None: + """Runs an Azure CLI command using the Azure CLI Python SDK.""" + cli = get_default_cli() + command = list(args) + exit_code = cli.invoke(command) + + if exit_code != 0: + print(f"❌ Failed to execute: {' '.join(command)}") + sys.exit(exit_code) + + +def get_main_bicep_files(modified_files: List[str]) -> List[Path]: + """Finds unique folders with modified files and ensures 'main.bicep' exists in each.""" + modified_folders = {Path(f).parent for f in modified_files} + return [folder / "main.bicep" for folder in modified_folders if (folder / "main.bicep").exists()] + + +def build_bicep_file(bicep_file: Path) -> None: + """Builds a Bicep file using Azure CLI Python SDK and ensures azuredeploy.json is created.""" + output_file = bicep_file.with_name("azuredeploy.json") + + print(f"πŸ”Ή Building Bicep: {bicep_file} -> {output_file}") + + # Run az bicep build using Azure CLI SDK + run_az_command("bicep", "build", "--file", str(bicep_file), "--outfile", str(output_file)) + + # Verify if azuredeploy.json was created + if not output_file.exists(): + print(f"❌ Build succeeded, but {output_file} was not created!") + sys.exit(1) + + print(f"βœ… Successfully built: {bicep_file} -> {output_file}") + + +def main() -> None: + """Main script execution.""" + print("πŸš€ Running Bicep build using Azure CLI SDK...") + + # Get modified Bicep files from pre-commit + modified_files = [Path(f) for f in sys.argv[1:]] + + if not modified_files: + print("βœ… No modified Bicep files detected. Skipping build.") + sys.exit(0) + + # Run Bicep build on each modified file + bicep_files = get_main_bicep_files(modified_files) + + for bicep_file in bicep_files: + build_bicep_file(bicep_file) + + print("πŸŽ‰ All Bicep files successfully built!") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d893294f..fa4ddd8e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,3 +40,14 @@ repos: entry: python .github/scripts/detect_azure_secrets.py language: python types: [file] + - id: bicep-build + name: Regenerate azuredeploy.json for Agent samples + description: "Automatically build Bicep files into azuredeploy.json before commit" + entry: python .github/scripts/build_agent_setup_bicep.py + types: [text] + files: ^scenarios/Agents/setup/.*\.bicep$ + language: python + require_serial: true + pass_filenames: true + additional_dependencies: + - azure-cli diff --git a/dev-requirements.txt b/dev-requirements.txt index db7587d7..8eb983fe 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,4 +4,5 @@ python-dotenv ~= 1.0 pytest ~= 8.0 pytest-iovis[papermill] == 0.1.0 ipykernel ~= 6.0 +azure-cli ~= 2.69 .infra/pytest_plugins/changed_samples diff --git a/scenarios/Agents/README.md b/scenarios/Agents/README.md new file mode 100644 index 00000000..e69de29b diff --git a/scenarios/Agents/media/deploytoazure.svg b/scenarios/Agents/media/deploytoazure.svg new file mode 100644 index 00000000..418df500 --- /dev/null +++ b/scenarios/Agents/media/deploytoazure.svg @@ -0,0 +1,50 @@ + + + + + + + + diff --git a/scenarios/Agents/media/visualizebutton.svg b/scenarios/Agents/media/visualizebutton.svg new file mode 100644 index 00000000..3c106a0a --- /dev/null +++ b/scenarios/Agents/media/visualizebutton.svg @@ -0,0 +1,53 @@ + + + + + + + + diff --git a/scenarios/Agents/setup/basic-agent-identity/README.md b/scenarios/Agents/setup/basic-agent-identity/README.md new file mode 100644 index 00000000..d7040f3a --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/README.md @@ -0,0 +1,47 @@ +--- +description: This set of templates demonstrates how to set up Azure AI Agent Service with the basic setup using managed identity authetication for the AI Service/AOAI connection. Agents use multi-tenant search and storage resources fully managed by Microsoft. You won’t have visibility or control over these underlying Azure resources. +page_type: sample +products: +- azure +- azure-resource-manager +urlFragment: basic-agent-identity +languages: +- bicep +- json +--- +# Basic Agent Setup Identity + +![Azure Public Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/PublicLastTestDate.svg) +![Azure Public Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/PublicDeployment.svg) + +![Azure US Gov Last Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/FairfaxLastTestDate.svg) +![Azure US Gov Last Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/FairfaxDeployment.svg) + +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/BestPracticeResult.svg) +![Cred Scan Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/CredScanResult.svg) + +![Bicep Version](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-identity/BicepVersion.svg) + +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fquickstarts%2Fmicrosoft.azure-ai-agent-service%2Fbasic-agent-identity%2Fazuredeploy.json) + +[![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.svg?sanitize=true)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fquickstarts%2Fmicrosoft.azure-ai-agent-service%2Fbasic-agent-identity%2Fazuredeploy.json) + +Resources for the hub, project, storage account, and AI Services will be created for you. The AI Services account will be connected to your project/hub and a gpt-4o-mini model will be deployed in the eastus region. A Microsoft-managed key vault will be used by default. + +Optional use an existing AI Services resource by providing the full arm resource id in the parameters file: + +- aiServiceAccountResourceId:/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{serviceName} + +If you want to use an existing Azure OpenAI resource, you will need to update the `aiServiceAccountResourceId` and the `aiServiceKind` parameter in the parameters file. The `aiServiceKind` parameter should be set to `AzureOpenAI`. + +## Resources + +| Provider and type | Description | +| - | - | +| `Microsoft.Resources/resourceGroups` | The resource group all resources get deployed into | +| `Microsoft.Storage/storageAccounts` | An Azure Storage instance associated to the Azure Machine Learning workspace | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI hub (Azure Machine Learning RP workspace of kind 'hub') | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI project (Azure Machine Learning RP workspace of kind 'project') | +| `Microsoft.CognitiveServices/accounts` | An Azure AI Services as the model-as-a-service endpoint provider (allowed kinds: 'AIServices' and 'OpenAI') | +| `Microsoft.CognitiveServices/accounts/deployments` | A gpt-4o-mini model is deployed | +`Tags: ` \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-identity/azuredeploy.json b/scenarios/Agents/setup/basic-agent-identity/azuredeploy.json new file mode 100644 index 00000000..d5ce931f --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/azuredeploy.json @@ -0,0 +1,820 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "9343374115382580767" + } + }, + "parameters": { + "aiHubName": { + "type": "string", + "defaultValue": "basic-hub", + "minLength": 2, + "maxLength": 12, + "metadata": { + "description": "Name for the AI resource and used to derive name of dependent resources." + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "Agents basic hub resource", + "metadata": { + "description": "Friendly name for your Azure AI resource" + } + }, + "aiHubDescription": { + "type": "string", + "defaultValue": "A basic hub resource required for the agent setup.", + "metadata": { + "description": "Description of your Azure AI resource dispayed in AI studio" + } + }, + "aiProjectName": { + "type": "string", + "defaultValue": "basic-project", + "metadata": { + "description": "Name for the project" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "Agents basic project resource", + "metadata": { + "description": "Friendly name for your Azure AI project resource" + } + }, + "aiProjectDescription": { + "type": "string", + "defaultValue": "A basic project resource required for the agent setup.", + "metadata": { + "description": "Description of your Azure AI project resource dispayed in AI studio" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region used for the deployment of all resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Set of tags to apply to all resources." + } + }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "2024-07-18", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "defaultValue": "GlobalStandard", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "defaultValue": 50, + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created." + } + }, + "aiServiceAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiServiceKind": { + "type": "string", + "defaultValue": "AIServices", + "metadata": { + "description": "AI Service Account kind: either OpenAI or AIServices" + } + }, + "storageName": { + "type": "string", + "defaultValue": "agent-storage", + "metadata": { + "description": "Name of the storage account" + } + }, + "aiServicesName": { + "type": "string", + "defaultValue": "agent-ai-services", + "metadata": { + "description": "Name of the Azure AI Services account" + } + }, + "deploymentTimestamp": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddHHmmss')]" + } + }, + "variables": { + "name": "[toLower(format('{0}', parameters('aiHubName')))]", + "projectName": "[toLower(format('{0}', parameters('aiProjectName')))]", + "uniqueSuffix": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]", + "aiServiceExists": "[not(equals(parameters('aiServiceAccountResourceId'), ''))]", + "aiServiceParts": "[split(parameters('aiServiceAccountResourceId'), '/')]", + "aiServiceAccountSubscriptionId": "[if(variables('aiServiceExists'), variables('aiServiceParts')[2], subscription().subscriptionId)]", + "aiServiceAccountResourceGroupName": "[if(variables('aiServiceExists'), variables('aiServiceParts')[4], resourceGroup().name)]", + "differentModelLocation": "[if(not(equals(parameters('modelLocation'), '')), parameters('modelLocation'), parameters('location'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[format('{0}{1}', parameters('aiServicesName'), variables('uniqueSuffix'))]" + }, + "aiServiceAccountResourceId": { + "value": "[parameters('aiServiceAccountResourceId')]" + }, + "storageName": { + "value": "[format('{0}{1}', parameters('storageName'), variables('uniqueSuffix'))]" + }, + "keyvaultName": { + "value": "[format('kv-{0}-{1}', variables('name'), variables('uniqueSuffix'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" + }, + "modelLocation": { + "value": "[variables('differentModelLocation')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "13463183484961318850" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "AI services name" + } + }, + "keyvaultName": { + "type": "string", + "metadata": { + "description": "The name of the Key Vault" + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + }, + "aiServiceAccountResourceId": { + "type": "string", + "metadata": { + "description": "The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "noZRSRegions": { + "type": "array", + "defaultValue": [ + "southindia", + "westus" + ] + }, + "sku": { + "type": "object", + "defaultValue": "[if(contains(parameters('noZRSRegions'), parameters('location')), createObject('name', 'Standard_GRS'), createObject('name', 'Standard_ZRS'))]" + } + }, + "variables": { + "storageNameCleaned": "[replace(parameters('storageName'), '-', '')]", + "aiServiceExists": "[not(empty(parameters('aiServiceAccountResourceId')))]", + "aiServiceParts": "[split(parameters('aiServiceAccountResourceId'), '/')]" + }, + "resources": [ + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-10-01", + "name": "[parameters('aiServicesName')]", + "location": "[parameters('modelLocation')]", + "sku": { + "name": "S0" + }, + "kind": "AIServices", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[toLower(format('{0}', toLower(parameters('aiServicesName'))))]", + "apiProperties": { + "statisticsEnabled": false + }, + "publicNetworkAccess": "Enabled" + } + }, + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-05-01", + "name": "[variables('storageNameCleaned')]", + "location": "[parameters('location')]", + "kind": "StorageV2", + "sku": "[parameters('sku')]", + "properties": { + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Allow", + "virtualNetworkRules": [] + }, + "allowSharedKeyAccess": false + } + }, + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyvaultName')]", + "location": "[parameters('location')]", + "properties": { + "tenantId": "[subscription().tenantId]", + "sku": { + "family": "A", + "name": "standard" + }, + "enableSoftDelete": true, + "enabledForTemplateDeployment": true, + "accessPolicies": [] + } + } + ], + "outputs": { + "aiServicesName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[8], parameters('aiServicesName'))]" + }, + "aiservicesID": { + "type": "string", + "value": "[if(variables('aiServiceExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]" + }, + "aiservicesTarget": { + "type": "string", + "value": "[if(variables('aiServiceExists'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), '2024-10-01').endpoint, reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-10-01').endpoint)]" + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[4], resourceGroup().name)]" + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[2], subscription().subscriptionId)]" + }, + "storageId": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" + }, + "keyvaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiHubName": { + "value": "[format('ai-{0}-{1}', variables('name'), variables('uniqueSuffix'))]" + }, + "aiHubFriendlyName": { + "value": "[parameters('aiHubFriendlyName')]" + }, + "aiHubDescription": { + "value": "[parameters('aiHubDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "keyVaultId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.keyvaultId.value]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiServiceKind": { + "value": "[parameters('aiServiceKind')]" + }, + "aiServiceAccountResourceGroupName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountResourceGroupName.value]" + }, + "aiServiceAccountSubscriptionId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountSubscriptionId.value]" + }, + "storageAccountId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.storageId.value]" + }, + "aiServicesId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesID.value]" + }, + "aiServicesTarget": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesTarget.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15558621331071845672" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiHubName')]", + "metadata": { + "description": "AI hub display name" + } + }, + "aiHubDescription": { + "type": "string", + "metadata": { + "description": "AI hub description" + } + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account resource for storing experimentation outputs" + } + }, + "aiServicesId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services resource" + } + }, + "aiServicesTarget": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services endpoint" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name AI Services resource" + } + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource Group name of the AI Services resource" + } + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "metadata": { + "description": "Subscription ID of the AI Services resource" + } + }, + "aiServiceKind": { + "type": "string", + "metadata": { + "description": "AI Service Account kind: either OpenAI or AIServices" + } + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the key vault resource for storing connection strings" + } + } + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-connection-AIServices', parameters('aiHubName')))]", + "properties": { + "category": "[parameters('aiServiceKind')]", + "target": "[parameters('aiServicesTarget')]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiServicesId')]", + "Location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiServiceAccountSubscriptionId'), parameters('aiServiceAccountResourceGroupName')), 'Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2023-05-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('aiHubName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiHubFriendlyName')]", + "description": "[parameters('aiHubDescription')]", + "keyVault": "[parameters('keyVaultId')]", + "storageAccount": "[parameters('storageAccountId')]", + "systemDatastoresAuthMode": "identity" + }, + "kind": "hub" + } + ], + "outputs": { + "aiHubID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiProjectName": { + "value": "[format('ai-{0}-{1}', variables('projectName'), variables('uniqueSuffix'))]" + }, + "aiProjectFriendlyName": { + "value": "[parameters('aiProjectFriendlyName')]" + }, + "aiProjectDescription": { + "value": "[parameters('aiProjectDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiHubId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiHubID.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "4069116357119294478" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI Project name" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiProjectName')]", + "metadata": { + "description": "AI Project display name" + } + }, + "aiProjectDescription": { + "type": "string", + "metadata": { + "description": "AI Project description" + } + }, + "aiHubId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Hub resource" + } + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "projectConnectionString": "[format('{0}.api.azureml.ms;{1};{2};{3}', parameters('location'), variables('subscriptionId'), variables('resourceGroupName'), parameters('aiProjectName'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('aiProjectName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('ProjectConnectionString', variables('projectConnectionString')))]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiProjectFriendlyName')]", + "description": "[parameters('aiProjectDescription')]", + "systemDatastoresAuthMode": "identity", + "hubResourceId": "[parameters('aiHubId')]" + }, + "kind": "project" + } + ], + "outputs": { + "aiProjectName": { + "type": "string", + "value": "[parameters('aiProjectName')]" + }, + "aiProjectResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + }, + "aiProjectPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview', 'full').identity.principalId]" + }, + "aiProjectWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview').workspaceId]" + }, + "projectConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview', 'full').tags.ProjectConnectionString]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-service-role-assignments-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "subscriptionId": "[variables('aiServiceAccountSubscriptionId')]", + "resourceGroup": "[variables('aiServiceAccountResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "16462974087505781196" + } + }, + "parameters": { + "aiServicesName": { + "type": "string" + }, + "aiProjectPrincipalId": { + "type": "string" + }, + "aiProjectId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" + ] + } + ], + "outputs": { + "PROJECT_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.projectConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-identity/azuredeploy.parameters.json b/scenarios/Agents/setup/basic-agent-identity/azuredeploy.parameters.json new file mode 100644 index 00000000..0c429356 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/azuredeploy.parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "aiHubName": { + "value": "basic-hub" + }, + "aiProjectName": { + "value": "basic-project" + }, + "modelName": { + "value": "gpt-4o-mini" + }, + "modelFormat": { + "value": "OpenAI" + }, + "modelVersion": { + "value": "2024-07-18" + }, + "modelSkuName": { + "value": "GlobalStandard" + }, + "modelCapacity": { + "value": 50 + }, + "modelLocation": { + "value": "" + }, + "storageName": { + "value": "agent-storage" + }, + "aiServicesName": { + "value": "agent-ai-services" + }, + "aiServiceAccountResourceId":{ + "value": "" + }, + "aiServiceKind": { + "value": "AIServices" + } + } + } \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-identity/main.bicep b/scenarios/Agents/setup/basic-agent-identity/main.bicep new file mode 100644 index 00000000..04d7ee95 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/main.bicep @@ -0,0 +1,145 @@ +// Execute this main file to depoy Azure AI studio resources in the basic agent configuraiton wit Managed Identity + +// Parameters +@minLength(2) +@maxLength(12) +@description('Name for the AI resource and used to derive name of dependent resources.') +param aiHubName string = 'basic-hub' + +@description('Friendly name for your Azure AI resource') +param aiHubFriendlyName string = 'Agents basic hub resource' + +@description('Description of your Azure AI resource dispayed in AI studio') +param aiHubDescription string = 'A basic hub resource required for the agent setup.' + +@description('Name for the project') +param aiProjectName string = 'basic-project' + +@description('Friendly name for your Azure AI project resource') +param aiProjectFriendlyName string = 'Agents basic project resource' + +@description('Description of your Azure AI project resource dispayed in AI studio') +param aiProjectDescription string = 'A basic project resource required for the agent setup.' + +@description('Azure region used for the deployment of all resources.') +param location string = resourceGroup().location + +@description('Set of tags to apply to all resources.') +param tags object = {} + +@description('Model name for deployment') +param modelName string = 'gpt-4o-mini' + +@description('Model format for deployment') +param modelFormat string = 'OpenAI' + +@description('Model version for deployment') +param modelVersion string = '2024-07-18' + +@description('Model deployment SKU name') +param modelSkuName string = 'GlobalStandard' + +@description('Model deployment capacity') +param modelCapacity int = 50 + +@description('Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created.') +param modelLocation string = '' + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string = '' + +@description('AI Service Account kind: either OpenAI or AIServices') +param aiServiceKind string = 'AIServices' + +// Variables +var name = toLower('${aiHubName}') +var projectName = toLower('${aiProjectName}') + +@description('Name of the storage account') +param storageName string = 'agent-storage' + +@description('Name of the Azure AI Services account') +param aiServicesName string = 'agent-ai-services' + +// Create a short, unique suffix, that will be unique to each resource group +// var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 4) +param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') +var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) + +var aiServiceExists = aiServiceAccountResourceId != '' + +var aiServiceParts = split(aiServiceAccountResourceId, '/') +var aiServiceAccountSubscriptionId = aiServiceExists ? aiServiceParts[2] : subscription().subscriptionId +var aiServiceAccountResourceGroupName = aiServiceExists ? aiServiceParts[4] : resourceGroup().name + +var differentModelLocation = modelLocation != '' ? modelLocation : location + +// Dependent resources for the Azure Machine Learning workspace +module aiDependencies 'modules-basic/basic-dependent-resources.bicep' = { + name: 'dependencies-${name}-${uniqueSuffix}-deployment' + params: { + aiServicesName: '${aiServicesName}${uniqueSuffix}' + aiServiceAccountResourceId: aiServiceAccountResourceId + storageName: '${storageName}${uniqueSuffix}' + keyvaultName: 'kv-${name}-${uniqueSuffix}' + location: location + + // Model deployment parameters + modelName: modelName + modelFormat: modelFormat + modelVersion: modelVersion + modelSkuName: modelSkuName + modelCapacity: modelCapacity + modelLocation: differentModelLocation + } +} + +module aiHub 'modules-basic/basic-ai-hub-identity.bicep' = { + name: 'ai-${name}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiHubName: 'ai-${name}-${uniqueSuffix}' + aiHubFriendlyName: aiHubFriendlyName + aiHubDescription: aiHubDescription + location: location + keyVaultId: aiDependencies.outputs.keyvaultId + + tags: tags + + // dependent resources + aiServicesName: aiDependencies.outputs.aiServicesName + aiServiceKind: aiServiceKind + aiServiceAccountResourceGroupName: aiDependencies.outputs.aiServiceAccountResourceGroupName + aiServiceAccountSubscriptionId: aiDependencies.outputs.aiServiceAccountSubscriptionId + storageAccountId: aiDependencies.outputs.storageId + aiServicesId: aiDependencies.outputs.aiservicesID + aiServicesTarget: aiDependencies.outputs.aiservicesTarget + } +} + +module aiProject 'modules-basic/basic-ai-project-identity.bicep' = { + name: 'ai-${projectName}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiProjectName: 'ai-${projectName}-${uniqueSuffix}' + aiProjectFriendlyName: aiProjectFriendlyName + aiProjectDescription: aiProjectDescription + location: location + tags: tags + + // dependent resources + aiHubId: aiHub.outputs.aiHubID + } +} + +module aiServiceRoleAssignments 'modules-basic/ai-service-role-assignments.bicep' = { + name: 'ai-service-role-assignments-${projectName}-${uniqueSuffix}-deployment' + scope: resourceGroup(aiServiceAccountSubscriptionId, aiServiceAccountResourceGroupName) + params: { + aiServicesName: aiDependencies.outputs.aiServicesName + aiProjectPrincipalId: aiProject.outputs.aiProjectPrincipalId + aiProjectId: aiProject.outputs.aiProjectResourceId + } +} + +output PROJECT_CONNECTION_STRING string = aiProject.outputs.projectConnectionString diff --git a/scenarios/Agents/setup/basic-agent-identity/metadata.json b/scenarios/Agents/setup/basic-agent-identity/metadata.json new file mode 100644 index 00000000..799b86d8 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/metadata.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://aka.ms/azure-quickstart-templates-metadata-schema#", + "type": "QuickStart", + "itemDisplayName": "Basic Agent Setup Identity", + "description": "This set of templates demonstrates how to set up Azure AI Agent Service with the basic setup using managed identity authetication for the AI Service/AOAI connection. Agents use multi-tenant search and storage resources fully managed by Microsoft. You won’t have visibility or control over these underlying Azure resources.", + "summary": "This set of templates demonstrates how to use Azure AI Agent Service with the basic setup and managed identity.", + "githubUsername": "fosteramanda", + "dateUpdated": "2025-02-10", + "environments": [ + "AzureCloud" + ] +} \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-identity/modules-basic/ai-service-role-assignments.bicep b/scenarios/Agents/setup/basic-agent-identity/modules-basic/ai-service-role-assignments.bicep new file mode 100644 index 00000000..9286eb71 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/modules-basic/ai-service-role-assignments.bicep @@ -0,0 +1,54 @@ +param aiServicesName string +param aiProjectPrincipalId string +param aiProjectId string + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = { + name: aiServicesName + scope: resourceGroup() +} + +resource cognitiveServicesContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' + scope: resourceGroup() + +} + +resource cognitiveServicesContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01'= { + scope: aiServices + name: guid(aiServices.id, cognitiveServicesContributorRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesContributorRole.id + principalType: 'ServicePrincipal' + } +} + + +resource cognitiveServicesOpenAIUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + scope: resourceGroup() +} +resource cognitiveServicesOpenAIUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectId, cognitiveServicesOpenAIUserRole.id, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesOpenAIUserRole.id + principalType: 'ServicePrincipal' + } +} + +resource cognitiveServicesUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'a97b65f3-24c7-4388-baec-2e87135dc908' + scope: resourceGroup() +} + +resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectId, cognitiveServicesUserRole.id, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesUserRole.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-hub-identity.bicep b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-hub-identity.bicep new file mode 100644 index 00000000..744446ae --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-hub-identity.bicep @@ -0,0 +1,82 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI hub name') +param aiHubName string + +@description('AI hub display name') +param aiHubFriendlyName string = aiHubName + +@description('AI hub description') +param aiHubDescription string + +@description('Resource ID of the storage account resource for storing experimentation outputs') +param storageAccountId string + +@description('Resource ID of the AI Services resource') +param aiServicesId string + +@description('Resource ID of the AI Services endpoint') +param aiServicesTarget string + +@description('Name AI Services resource') +param aiServicesName string + +@description('Resource Group name of the AI Services resource') +param aiServiceAccountResourceGroupName string + +@description('Subscription ID of the AI Services resource') +param aiServiceAccountSubscriptionId string + +@description('AI Service Account kind: either OpenAI or AIServices') +param aiServiceKind string + +@description('Resource ID of the key vault resource for storing connection strings') +param keyVaultId string + +resource aiServices 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { + name: aiServicesName + scope: resourceGroup(aiServiceAccountSubscriptionId, aiServiceAccountResourceGroupName) +} + +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' = { + name: aiHubName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiHubFriendlyName + description: aiHubDescription + + // dependent resources + keyVault: keyVaultId + storageAccount: storageAccountId + systemDatastoresAuthMode: 'identity' + } + kind: 'hub' + + resource aiServicesConnection 'connections@2024-10-01-preview' = { + name: '${aiHubName}-connection-AIServices' + properties: { + category: aiServiceKind + target: aiServicesTarget + authType: 'AAD' + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiServicesId + Location: aiServices.location + } + } + } + +} +output aiHubID string = aiHub.id diff --git a/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-project-identity.bicep b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-project-identity.bicep new file mode 100644 index 00000000..9cec3d22 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-ai-project-identity.bicep @@ -0,0 +1,54 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string + +@description('Resource ID of the AI Hub resource') +param aiHubId string + +//for constructing project connection string +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name + +var projectConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${aiProjectName}' + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' = { + name: aiProjectName + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString + }) + identity: { + type: 'SystemAssigned' + } + + properties: { + // organization + friendlyName: aiProjectFriendlyName + description: aiProjectDescription + systemDatastoresAuthMode: 'identity' + + // dependent resources + hubResourceId: aiHubId + + } + kind: 'project' +} + +output aiProjectName string = aiProject.name +output aiProjectResourceId string = aiProject.id +output aiProjectPrincipalId string = aiProject.identity.principalId +output aiProjectWorkspaceId string = aiProject.properties.workspaceId +output projectConnectionString string = aiProject.tags.ProjectConnectionString diff --git a/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-dependent-resources.bicep b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-dependent-resources.bicep new file mode 100644 index 00000000..8ed9778c --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-identity/modules-basic/basic-dependent-resources.bicep @@ -0,0 +1,120 @@ +@description('Azure region of the deployment') +param location string = resourceGroup().location + +@description('AI services name') +param aiServicesName string + +@description('The name of the Key Vault') +param keyvaultName string + +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('Model/AI Resource deployment location') +param modelLocation string + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string + +@description('Name of the storage account') +param storageName string + +var storageNameCleaned = replace(storageName, '-', '') + +var aiServiceExists = !empty(aiServiceAccountResourceId) + +var aiServiceParts = split(aiServiceAccountResourceId, '/') + +resource existingAIServiceAccount 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = if (aiServiceExists) { + name: aiServiceParts[8] + scope: resourceGroup(aiServiceParts[2], aiServiceParts[4]) +} + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-10-01' = if(!aiServiceExists) { + name: aiServicesName + location: modelLocation + sku: { + name: 'S0' + } + kind: 'AIServices' // or 'OpenAI' + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower('${toLower(aiServicesName)}') + apiProperties: { + statisticsEnabled: false + } + publicNetworkAccess: 'Enabled' + } +} + +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01'= if(!aiServiceExists) { + parent: aiServices + name: modelName + sku : { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model:{ + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +param noZRSRegions array = ['southindia', 'westus'] +param sku object = contains(noZRSRegions, location) ? { name: 'Standard_GRS' } : { name: 'Standard_ZRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageNameCleaned + location: location + kind: 'StorageV2' + sku: sku + properties: { + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + publicNetworkAccess: 'Enabled' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + virtualNetworkRules: [] + } + allowSharedKeyAccess: false + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyvaultName + location: location + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + enableSoftDelete: true + enabledForTemplateDeployment: true + accessPolicies: [] + } +} + +output aiServicesName string = aiServiceExists ? existingAIServiceAccount.name : aiServicesName +output aiservicesID string = aiServiceExists ? existingAIServiceAccount.id : aiServices.id +output aiservicesTarget string = aiServiceExists ? existingAIServiceAccount.properties.endpoint : aiServices.properties.endpoint +output aiServiceAccountResourceGroupName string = aiServiceExists ? aiServiceParts[4] : resourceGroup().name +output aiServiceAccountSubscriptionId string = aiServiceExists ? aiServiceParts[2] : subscription().subscriptionId + +output storageId string = storage.id +output keyvaultId string = keyVault.id diff --git a/scenarios/Agents/setup/basic-agent-keys/README.md b/scenarios/Agents/setup/basic-agent-keys/README.md new file mode 100644 index 00000000..2ad56dbb --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/README.md @@ -0,0 +1,39 @@ +--- +description: This set of templates demonstrates how to set up Azure AI Agent Service with the basic setup using API keys authetication for the AI Service/AOAI connection. Agents use multi-tenant search and storage resources fully managed by Microsoft. You won’t have visibility or control over these underlying Azure resources. +page_type: sample +products: +- azure +- azure-resource-manager +urlFragment: basic-agent-keys +languages: +- bicep +- json +--- +# Basic Agent Setup API Keys + +![Azure Public Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/PublicLastTestDate.svg) +![Azure Public Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/PublicDeployment.svg) + +![Azure US Gov Last Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/FairfaxLastTestDate.svg) +![Azure US Gov Last Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/FairfaxDeployment.svg) + +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/BestPracticeResult.svg) +![Cred Scan Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/CredScanResult.svg) + +![Bicep Version](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/basic-agent-keys/BicepVersion.svg) + +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fquickstarts%2Fmicrosoft.azure-ai-agent-service%2Fbasic-agent-keys%2Fazuredeploy.json) + +[![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.svg?sanitize=true)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fquickstarts%2Fmicrosoft.azure-ai-agent-service%2Fbasic-agent-keys%2Fazuredeploy.json) + +Resources for the hub, project, storage account, and AI Services will be created for you. The AI Services account will be connected to your project/hub and a gpt-4o-mini model will be deployed in the eastus region. A Microsoft-managed key vault will be used by default. +## Resources + +| Provider and type | Description | +| - | - | +| `Microsoft.Resources/resourceGroups` | The resource group all resources get deployed into | +| `Microsoft.Storage/storageAccounts` | An Azure Storage instance associated to the Azure Machine Learning workspace | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI hub (Azure Machine Learning RP workspace of kind 'hub') | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI project (Azure Machine Learning RP workspace of kind 'project') | +| `Microsoft.CognitiveServices/accounts` | An Azure AI Services as the model-as-a-service endpoint provider (allowed kinds: 'AIServices' and 'OpenAI') | +| `Microsoft.CognitiveServices/accounts/deployments` | A gpt-4o-mini model is deployed |`Tags: ` \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-keys/azuredeploy.json b/scenarios/Agents/setup/basic-agent-keys/azuredeploy.json new file mode 100644 index 00000000..f387e6b9 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/azuredeploy.json @@ -0,0 +1,662 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "13003776003463905332" + } + }, + "parameters": { + "aiHubName": { + "type": "string", + "defaultValue": "standard-hub", + "minLength": 2, + "maxLength": 12, + "metadata": { + "description": "Name for the AI resource and used to derive name of dependent resources." + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "Agents standard hub resource", + "metadata": { + "description": "Friendly name for your Azure AI resource" + } + }, + "aiHubDescription": { + "type": "string", + "defaultValue": "A standard hub resource required for the agent setup.", + "metadata": { + "description": "Description of your Azure AI resource dispayed in AI studio" + } + }, + "aiProjectName": { + "type": "string", + "defaultValue": "standard-project", + "metadata": { + "description": "Name for the project" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "Agents standard project resource", + "metadata": { + "description": "Friendly name for your Azure AI resource" + } + }, + "aiProjectDescription": { + "type": "string", + "defaultValue": "A standard project resource required for the agent setup.", + "metadata": { + "description": "Description of your Azure AI resource dispayed in AI studio" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region used for the deployment of all resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Set of tags to apply to all resources." + } + }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "2024-07-18", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "defaultValue": "GlobalStandard", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "defaultValue": 50, + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "defaultValue": "eastus", + "metadata": { + "description": "Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created." + } + }, + "storageName": { + "type": "string", + "defaultValue": "agent-storage", + "metadata": { + "description": "Name of the storage account" + } + }, + "aiServicesName": { + "type": "string", + "defaultValue": "agent-ai-services", + "metadata": { + "description": "Name of the Azure AI Services account" + } + }, + "deploymentTimestamp": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddHHmmss')]" + } + }, + "variables": { + "name": "[toLower(format('{0}', parameters('aiHubName')))]", + "projectName": "[toLower(format('{0}', parameters('aiProjectName')))]", + "uniqueSuffix": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[format('{0}{1}', parameters('aiServicesName'), variables('uniqueSuffix'))]" + }, + "storageName": { + "value": "[format('{0}{1}', parameters('storageName'), variables('uniqueSuffix'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" + }, + "modelLocation": { + "value": "[parameters('modelLocation')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "17168316501525543267" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "AI services name" + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + }, + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "storageSkuName": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_ZRS", + "Standard_GRS", + "Standard_GZRS", + "Standard_RAGRS", + "Standard_RAGZRS", + "Premium_LRS", + "Premium_ZRS" + ], + "metadata": { + "description": "Storage SKU" + } + } + }, + "variables": { + "storageNameCleaned": "[replace(parameters('storageName'), '-', '')]" + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiServicesName')]", + "location": "[parameters('modelLocation')]", + "sku": { + "name": "S0" + }, + "kind": "AIServices", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[toLower(format('{0}', toLower(parameters('aiServicesName'))))]", + "apiProperties": { + "statisticsEnabled": false + }, + "publicNetworkAccess": "Enabled" + } + }, + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-06-01-preview", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-05-01", + "name": "[variables('storageNameCleaned')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('storageSkuName')]" + }, + "kind": "StorageV2", + "properties": { + "accessTier": "Hot", + "allowBlobPublicAccess": false, + "allowCrossTenantReplication": false, + "allowSharedKeyAccess": true, + "encryption": { + "keySource": "Microsoft.Storage", + "requireInfrastructureEncryption": false, + "services": { + "blob": { + "enabled": true, + "keyType": "Account" + }, + "file": { + "enabled": true, + "keyType": "Account" + }, + "queue": { + "enabled": true, + "keyType": "Service" + }, + "table": { + "enabled": true, + "keyType": "Service" + } + } + }, + "isHnsEnabled": false, + "isNfsV3Enabled": false, + "keyPolicy": { + "keyExpirationPeriodInDays": 7 + }, + "largeFileSharesState": "Disabled", + "minimumTlsVersion": "TLS1_2", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny" + }, + "supportsHttpsTrafficOnly": true + } + } + ], + "outputs": { + "aiservicesID": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + }, + "aiservicesTarget": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-06-01-preview').endpoint]" + }, + "storageId": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiHubName": { + "value": "[format('ai-{0}-{1}', variables('name'), variables('uniqueSuffix'))]" + }, + "aiHubFriendlyName": { + "value": "[parameters('aiHubFriendlyName')]" + }, + "aiHubDescription": { + "value": "[parameters('aiHubDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "modelLocation": { + "value": "[parameters('modelLocation')]" + }, + "storageAccountId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.storageId.value]" + }, + "aiServicesId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesID.value]" + }, + "aiServicesTarget": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesTarget.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "7674042249048853943" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiHubName')]", + "metadata": { + "description": "AI hub display name" + } + }, + "aiHubDescription": { + "type": "string", + "metadata": { + "description": "AI hub description" + } + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account resource for storing experimentation outputs" + } + }, + "aiServicesId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services resource" + } + }, + "aiServicesTarget": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services endpoint" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + } + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-connection-AIServices', parameters('aiHubName')))]", + "properties": { + "category": "AIServices", + "target": "[parameters('aiServicesTarget')]", + "authType": "ApiKey", + "isSharedToAll": true, + "credentials": { + "key": "[format('{0}', listKeys(parameters('aiServicesId'), '2022-10-01').key1)]" + }, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiServicesId')]", + "Location": "[parameters('modelLocation')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-07-01-preview", + "name": "[parameters('aiHubName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiHubFriendlyName')]", + "description": "[parameters('aiHubDescription')]", + "storageAccount": "[parameters('storageAccountId')]" + }, + "kind": "hub" + } + ], + "outputs": { + "aiHubID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiProjectName": { + "value": "[format('ai-{0}-{1}', variables('projectName'), variables('uniqueSuffix'))]" + }, + "aiProjectFriendlyName": { + "value": "[parameters('aiProjectFriendlyName')]" + }, + "aiProjectDescription": { + "value": "[parameters('aiProjectDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiHubId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiHubID.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "2043470846642794431" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI Project name" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiProjectName')]", + "metadata": { + "description": "AI Project display name" + } + }, + "aiProjectDescription": { + "type": "string", + "metadata": { + "description": "AI Project description" + } + }, + "aiHubId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Hub resource" + } + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "projectConnectionString": "[format('{0}.api.azureml.ms;{1};{2};{3}', parameters('location'), variables('subscriptionId'), variables('resourceGroupName'), parameters('aiProjectName'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('aiProjectName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('ProjectConnectionString', variables('projectConnectionString')))]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiProjectFriendlyName')]", + "description": "[parameters('aiProjectDescription')]", + "hubResourceId": "[parameters('aiHubId')]" + }, + "kind": "project" + } + ], + "outputs": { + "aiProjectID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + }, + "projectConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2023-08-01-preview', 'full').tags.ProjectConnectionString]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + } + ], + "outputs": { + "PROJECT_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.projectConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-keys/azuredeploy.parameters.json b/scenarios/Agents/setup/basic-agent-keys/azuredeploy.parameters.json new file mode 100644 index 00000000..da79d145 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/azuredeploy.parameters.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "aiHubName": { + "value": "standard-hub" + }, + "aiProjectName": { + "value": "standard-project" + }, + "modelName": { + "value": "gpt-4o-mini" + }, + "modelFormat": { + "value": "OpenAI" + }, + "modelVersion": { + "value": "2024-07-18" + }, + "modelSkuName": { + "value": "GlobalStandard" + }, + "modelCapacity": { + "value": 50 + }, + "modelLocation": { + "value": "eastus" + }, + "storageName": { + "value": "agent-storage" + }, + "aiServicesName": { + "value": "agent-ai-services" + } + } + } \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-keys/main.bicep b/scenarios/Agents/setup/basic-agent-keys/main.bicep new file mode 100644 index 00000000..39b73a9e --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/main.bicep @@ -0,0 +1,115 @@ +// Execute this main file to depoy Azure AI studio resources in the basic security configuraiton + +// Parameters +@minLength(2) +@maxLength(12) +@description('Name for the AI resource and used to derive name of dependent resources.') +param aiHubName string = 'standard-hub' + +@description('Friendly name for your Azure AI resource') +param aiHubFriendlyName string = 'Agents standard hub resource' + +@description('Description of your Azure AI resource dispayed in AI studio') +param aiHubDescription string = 'A standard hub resource required for the agent setup.' + +@description('Name for the project') +param aiProjectName string = 'standard-project' + +@description('Friendly name for your Azure AI resource') +param aiProjectFriendlyName string = 'Agents standard project resource' + +@description('Description of your Azure AI resource dispayed in AI studio') +param aiProjectDescription string = 'A standard project resource required for the agent setup.' + +@description('Azure region used for the deployment of all resources.') +param location string = resourceGroup().location + +@description('Set of tags to apply to all resources.') +param tags object = {} + +@description('Model name for deployment') +param modelName string = 'gpt-4o-mini' + +@description('Model format for deployment') +param modelFormat string = 'OpenAI' + +@description('Model version for deployment') +param modelVersion string = '2024-07-18' + +@description('Model deployment SKU name') +param modelSkuName string = 'GlobalStandard' + +@description('Model deployment capacity') +param modelCapacity int = 50 + +@description('Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created.') +param modelLocation string = 'eastus' + +// Variables +var name = toLower('${aiHubName}') +var projectName = toLower('${aiProjectName}') + +@description('Name of the storage account') +param storageName string = 'agent-storage' + +@description('Name of the Azure AI Services account') +param aiServicesName string = 'agent-ai-services' + +// Create a short, unique suffix, that will be unique to each resource group +// var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 4) +param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') +var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) + +// Dependent resources for the Azure Machine Learning workspace +module aiDependencies 'modules-basic/basic-dependent-resources.bicep' = { + name: 'dependencies-${name}-${uniqueSuffix}-deployment' + params: { + aiServicesName: '${aiServicesName}${uniqueSuffix}' + storageName: '${storageName}${uniqueSuffix}' + location: location + tags: tags + + // Model deployment parameters + modelName: modelName + modelFormat: modelFormat + modelVersion: modelVersion + modelSkuName: modelSkuName + modelCapacity: modelCapacity + modelLocation: modelLocation + } +} + +module aiHub 'modules-basic/basic-ai-hub-keys.bicep' = { + name: 'ai-${name}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiHubName: 'ai-${name}-${uniqueSuffix}' + aiHubFriendlyName: aiHubFriendlyName + aiHubDescription: aiHubDescription + location: location + tags: tags + + // dependent resources + modelLocation: modelLocation + storageAccountId: aiDependencies.outputs.storageId + aiServicesId: aiDependencies.outputs.aiservicesID + aiServicesTarget: aiDependencies.outputs.aiservicesTarget + } +} + +module aiProject 'modules-basic/basic-ai-project-keys.bicep' = { + name: 'ai-${projectName}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiProjectName: 'ai-${projectName}-${uniqueSuffix}' + aiProjectFriendlyName: aiProjectFriendlyName + aiProjectDescription: aiProjectDescription + location: location + tags: tags + + // dependent resources + aiHubId: aiHub.outputs.aiHubID + } +} + +output PROJECT_CONNECTION_STRING string = aiProject.outputs.projectConnectionString diff --git a/scenarios/Agents/setup/basic-agent-keys/metadata.json b/scenarios/Agents/setup/basic-agent-keys/metadata.json new file mode 100644 index 00000000..ec510563 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/metadata.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://aka.ms/azure-quickstart-templates-metadata-schema#", + "type": "QuickStart", + "itemDisplayName": "Basic Agent Setup API Keys", + "description": "This set of templates demonstrates how to set up Azure AI Agent Service with the basic setup using API keys authetication for the AI Service/AOAI connection. Agents use multi-tenant search and storage resources fully managed by Microsoft. You won’t have visibility or control over these underlying Azure resources.", + "summary": "This set of templates demonstrates how to use Azure AI Agent Service with the basic setup and API keys.", + "githubUsername": "fosteramanda", + "dateUpdated": "2024-12-04", + "environments": [ + "AzureCloud" + ] +} \ No newline at end of file diff --git a/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-hub-keys.bicep b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-hub-keys.bicep new file mode 100644 index 00000000..7f639abf --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-hub-keys.bicep @@ -0,0 +1,67 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI hub name') +param aiHubName string + +@description('AI hub display name') +param aiHubFriendlyName string = aiHubName + +@description('AI hub description') +param aiHubDescription string + +@description('Resource ID of the storage account resource for storing experimentation outputs') +param storageAccountId string + +@description('Resource ID of the AI Services resource') +param aiServicesId string + +@description('Resource ID of the AI Services endpoint') +param aiServicesTarget string + +@description('Model/AI Resource deployment location') +param modelLocation string + +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-07-01-preview' = { + name: aiHubName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiHubFriendlyName + description: aiHubDescription + + // dependent resources + storageAccount: storageAccountId + } + kind: 'hub' + + resource aiServicesConnection 'connections@2024-07-01-preview' = { + name: '${aiHubName}-connection-AIServices' + properties: { + category: 'AIServices' + target: aiServicesTarget + // useWorkspaceManagedIdentity: true + authType: 'ApiKey' + isSharedToAll: true + credentials: { + key: '${listKeys(aiServicesId, '2022-10-01').key1}' + } + metadata: { + ApiType: 'Azure' + ResourceId: aiServicesId + Location: modelLocation + } + } + } +} + +output aiHubID string = aiHub.id diff --git a/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-project-keys.bicep b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-project-keys.bicep new file mode 100644 index 00000000..6f073896 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-ai-project-keys.bicep @@ -0,0 +1,48 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string + +@description('Resource ID of the AI Hub resource') +param aiHubId string + +//for constructing project connection string +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name +var projectConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${aiProjectName}' + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' = { + name: aiProjectName + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString + }) + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiProjectFriendlyName + description: aiProjectDescription + + // dependent resources + hubResourceId: aiHubId + + } + kind: 'project' +} + +output aiProjectID string = aiProject.id +output projectConnectionString string = aiProject.tags.ProjectConnectionString diff --git a/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-dependent-resources.bicep b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-dependent-resources.bicep new file mode 100644 index 00000000..e048f685 --- /dev/null +++ b/scenarios/Agents/setup/basic-agent-keys/modules-basic/basic-dependent-resources.bicep @@ -0,0 +1,134 @@ +@description('Azure region of the deployment') +param location string = resourceGroup().location + +@description('Tags to add to the resources') +param tags object = {} + +@description('AI services name') +param aiServicesName string + +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('Model/AI Resource deployment location') +param modelLocation string + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = { + name: aiServicesName + location: modelLocation + sku: { + name: 'S0' + } + kind: 'AIServices' // or 'OpenAI' + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower('${toLower(aiServicesName)}') + apiProperties: { + statisticsEnabled: false + } + publicNetworkAccess: 'Enabled' + } +} + +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-06-01-preview'= { + parent: aiServices + name: modelName + sku : { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model:{ + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +@description('Name of the storage account') +param storageName string + +@allowed([ + 'Standard_LRS' + 'Standard_ZRS' + 'Standard_GRS' + 'Standard_GZRS' + 'Standard_RAGRS' + 'Standard_RAGZRS' + 'Premium_LRS' + 'Premium_ZRS' +]) + +@description('Storage SKU') +param storageSkuName string = 'Standard_LRS' + +var storageNameCleaned = replace(storageName, '-', '') + +resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageNameCleaned + location: location + tags: tags + sku: { + name: storageSkuName + } + kind: 'StorageV2' + properties: { + accessTier: 'Hot' + allowBlobPublicAccess: false + allowCrossTenantReplication: false + allowSharedKeyAccess: true + encryption: { + keySource: 'Microsoft.Storage' + requireInfrastructureEncryption: false + services: { + blob: { + enabled: true + keyType: 'Account' + } + file: { + enabled: true + keyType: 'Account' + } + queue: { + enabled: true + keyType: 'Service' + } + table: { + enabled: true + keyType: 'Service' + } + } + } + isHnsEnabled: false + isNfsV3Enabled: false + keyPolicy: { + keyExpirationPeriodInDays: 7 + } + largeFileSharesState: 'Disabled' + minimumTlsVersion: 'TLS1_2' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + } + supportsHttpsTrafficOnly: true + } +} + +output aiservicesID string = aiServices.id +output aiservicesTarget string = aiServices.properties.endpoint +output storageId string = storage.id diff --git a/scenarios/Agents/setup/network-secured-agent/README.md b/scenarios/Agents/setup/network-secured-agent/README.md new file mode 100644 index 00000000..a52edd3f --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/README.md @@ -0,0 +1,221 @@ +--- +description: This set of templates demonstrates how to set up Azure AI Agent Service with virtual network isolation using User Managed Identity authetication for the AI Service/AOAI connection and private network links to connect the agent to your secure data. +page_type: sample +products: +- azure +- azure-resource-manager +urlFragment: network-secured-agent +languages: +- bicep +- json +--- + +# Network-Secured Azure AI Agent Infrastructure with User Managed Identity + +![Azure Public Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/PublicLastTestDate.svg) +![Azure Public Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/PublicDeployment.svg) + +![Azure US Gov Last Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/FairfaxLastTestDate.svg) +![Azure US Gov Last Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/FairfaxDeployment.svg) + +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/BestPracticeResult.svg) +![Cred Scan Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/CredScanResult.svg) + +![Bicep Version](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/network-secured-agent/BicepVersion.svg) + +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fdharakumarmsft%2Fazureai-samples%2Fscenario%2FAgents%2Fscenarios%2FAgents%2Fsetup%2Fnetwork-secured-agent%2Fazuredeploy.json) + +[![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.svg?sanitize=true)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fquickstarts%2Fmicrosoft.azure-ai-agent-service%2Fnetwork-secured-agent%2Fazuredeploy.json) + +This infrastructure-as-code (IaC) solution deploys a network-secured Azure AI agent environment with private networking, managed identities, and role-based access control (RBAC). + +## Architecture Overview + +### Network Security Design + +The deployment creates an isolated network environment: + +- **Virtual Network (172.16.0.0/16)** + - Customer Hub Subnet (172.16.0.0/24): Hosts private endpoints + - Agents Subnet (172.16.101.0/24): For azure ai agent workloads + +- **Private Endpoints** + - AI Services + - AI Search + - Key Vault + - Storage Account + +- **Private DNS Zones** + - privatelink.azureml.ms + - privatelink.search.windows.net + - privatelink.blob.core.windows.net + +### Core Components + +1. **AI Hub** + - Central orchestration point + - Manages service connections + - Network-isolated capability hosts + +2. **AI Project** + - Workspace configuration + - Service integration + - Agent deployment + +3. **Supporting Services** + - Azure AI Services + - Azure AI Search + - Key Vault + - Storage Account + +## Security Features + +### Authentication & Authorization + +- **Managed Identity** + - Zero-trust security model + - No credential storage + - Platform-managed rotation + +- **Role Assignments** + - AI Services: Administrator, OpenAI User + - AI Search: Index Data Contributor, Service Contributor + - Key Vault: Contributor, Secrets Officer + - Storage: Blob Data Owner, Queue Data Contributor + +### Network Security + +- Public network access disabled +- Private endpoints for all services +- Service endpoints for Azure services +- Network ACLs with deny by default + +## Deployment Options + +### 1. Infrastructure as Code (Bicep) +```bash +az deployment group create \ + --template-file main.bicep \ + --parameters @parameters.json +``` +Features: +- Declarative approach +- Native Azure integration +- Easy to version control +- Clear resource dependencies + +## Module Structure + +``` +modules-network-secured/ +β”œβ”€β”€ ai-search-role-assignments.bicep # AI Search RBAC configuration +β”œβ”€β”€ ai-search-service.bicep # AI Search deployment +β”œβ”€β”€ ai-service-role-assignments.bicep # AI Services RBAC configuration +β”œβ”€β”€ cognitive-services-role-assignments.bicep # OpenAI permissions +β”œβ”€β”€ keyvault-role-assignments.bicep # Key Vault RBAC configuration +β”œβ”€β”€ network-secured-ai-hub.bicep # AI Hub deployment +β”œβ”€β”€ network-secured-ai-project.bicep # AI Project deployment +β”œβ”€β”€ network-secured-dependent-resources.bicep # Core infrastructure +β”œβ”€β”€ network-secured-identity.bicep # Managed identity +β”œβ”€β”€ private-endpoint-and-dns.bicep # Network security +└── storage-role-assignments.bicep # Storage RBAC configuration +``` + +## Role Assignments + +The deployment configures the following RBAC permissions: + +### AI Services +- Azure AI Administrator (b78c5d69-af96-48a3-bf8d-a8b4d589de94) + * Full access to manage AI resources + * Model deployment permissions + * Security settings management + +### AI Search +- Search Index Data Contributor (8ebe5a00-799e-43f5-93ac-243d3dce84a7) + * Read/write access to indexes + * Query and update operations +- Search Service Contributor (7ca78c08-252a-4471-8644-bb5ff32d4ba0) + * Service management access + * Configuration changes + +### Key Vault +- Key Vault Contributor (f25e0fa2-a7c8-4377-a976-54943a77a395) + * Manage vault properties + * Cannot access secrets +- Key Vault Secrets Officer (b86a8fe4-44ce-4948-aee5-eccb2c155cd7) + * Full secrets access + * Manage secret metadata + +### Storage +- Storage Blob Data Owner (b7e6dc6d-f1e8-4753-8033-0f276bb0955b) + * Full blob access + * Container management +- Storage Queue Data Contributor (974c5e8b-45b9-4653-ba55-5f855dd0fb88) + * Queue operations + * Message management + +## Networking Details + +### Private Endpoints +Each service is deployed with a private endpoint in the Customer Hub subnet: + +```plaintext +AI Services: account +AI Search: searchService +Storage: blob +``` + +### DNS Configuration +Private DNS zones are created and linked to the VNet: + +```plaintext +AI Services: privatelink.azureml.ms +AI Search: privatelink.search.windows.net +Storage: privatelink.blob.core.windows.net +``` + +## Security Considerations + +1. **Network Isolation** + - No public internet exposure + - Private endpoint access only + - Network ACLs with deny-by-default + +2. **Authentication** + - Managed identity authentication + - No stored credentials + - AAD integration + +3. **Authorization** + - Granular RBAC assignments + - Principle of least privilege + - Service-specific roles + +4. **Monitoring** + - Diagnostic settings enabled + - Activity logging + - Network monitoring + +## Maintenance + +### Regular Tasks +1. Review role assignments +2. Monitor network security +3. Check service health +4. Update configurations as needed + +### Troubleshooting +1. Verify private endpoint connectivity +2. Check DNS resolution +3. Validate role assignments +4. Review network security groups + +## References + +- [Azure AI Services Documentation](https://learn.microsoft.com/en-us/azure/ai-services/) +- [Private Endpoint Documentation](https://learn.microsoft.com/en-us/azure/private-link/) +- [RBAC Documentation](https://learn.microsoft.com/en-us/azure/role-based-access-control/) +- [Network Security Best Practices](https://learn.microsoft.com/en-us/azure/security/fundamentals/network-best-practices) + +`Tags: ` diff --git a/scenarios/Agents/setup/network-secured-agent/azuredeploy.json b/scenarios/Agents/setup/network-secured-agent/azuredeploy.json new file mode 100644 index 00000000..752e0de6 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/azuredeploy.json @@ -0,0 +1,2095 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15513953893093752510" + } + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "network-secured-agent" + }, + "deploymentTimestamp": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddHHmmss')]" + }, + "uniqueSuffix": { + "type": "string", + "defaultValue": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]" + }, + "defaultAiHubName": { + "type": "string", + "defaultValue": "hub-demo", + "minLength": 2, + "maxLength": 12, + "metadata": { + "description": "Name for the AI resource and used to derive name of dependent resources." + } + }, + "defaultAiHubFriendlyName": { + "type": "string", + "defaultValue": "Agents Hub resource", + "metadata": { + "description": "Friendly name for your Hub resource" + } + }, + "defaultAiHubDescription": { + "type": "string", + "defaultValue": "This is an example AI resource for use in Azure AI Studio.", + "metadata": { + "description": "Description of your Azure AI resource displayed in AI studio" + } + }, + "defaultAiProjectName": { + "type": "string", + "defaultValue": "project-demo", + "metadata": { + "description": "Name for the AI project resources." + } + }, + "defaultAiProjectFriendlyName": { + "type": "string", + "defaultValue": "Agents Project resource", + "metadata": { + "description": "Friendly name for your Azure AI resource" + } + }, + "defaultAiProjectDescription": { + "type": "string", + "defaultValue": "This is an example AI Project resource for use in Azure AI Studio.", + "metadata": { + "description": "Description of your Azure AI resource displayed in AI studio" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region used for the deployment of all resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Set of tags to apply to all resources." + } + }, + "defaultAiSearchName": { + "type": "string", + "defaultValue": "agent-ai-search", + "metadata": { + "description": "Name of the Azure AI Search account" + } + }, + "defaultCapabilityHostName": { + "type": "string", + "defaultValue": "caphost1", + "metadata": { + "description": "Name for capabilityHost." + } + }, + "defaultStorageName": { + "type": "string", + "defaultValue": "agentstorage", + "metadata": { + "description": "Name of the storage account" + } + }, + "defaultAiServicesName": { + "type": "string", + "defaultValue": "agent-ai-service", + "metadata": { + "description": "Name of the Azure AI Services account" + } + }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "2024-07-18", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "defaultValue": "GlobalStandard", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "defaultValue": 50, + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "defaultValue": "eastus", + "metadata": { + "description": "Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created." + } + }, + "aisKind": { + "type": "string", + "defaultValue": "AIServices", + "metadata": { + "description": "AI service kind, values can be \"OpenAI\" or \"AIService\"" + } + }, + "aiServiceAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The AI Service Account name. This is an optional field, and if not provided, the resource will be created. The resource should exist in same resource group" + } + }, + "aiSearchServiceName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The AI Search Service name. This is an optional field, and if not provided, the resource will be created.The resource should exist in same resource group must be Public Network Disabled" + } + }, + "userAssignedIdentityDefaultName": { + "type": "string", + "defaultValue": "[format('secured-agents-identity-{0}', parameters('uniqueSuffix'))]", + "metadata": { + "description": "The name of User Assigned Identity" + } + } + }, + "variables": { + "keyVaultOverride": "", + "userAssignedIdentityOverride": "", + "uaiName": "[if(equals(variables('userAssignedIdentityOverride'), ''), parameters('userAssignedIdentityDefaultName'), variables('userAssignedIdentityOverride'))]", + "keyVaultName": "[if(empty(variables('keyVaultOverride')), format('kv-{0}-{1}', parameters('defaultAiHubName'), parameters('uniqueSuffix')), variables('keyVaultOverride'))]", + "aiServiceName": "[if(empty(parameters('aiServiceAccountName')), format('{0}{1}', parameters('defaultAiServicesName'), parameters('uniqueSuffix')), parameters('aiServiceAccountName'))]", + "aiSearchName": "[if(empty(parameters('aiSearchServiceName')), format('{0}{1}', parameters('defaultAiSearchName'), parameters('uniqueSuffix')), parameters('aiSearchServiceName'))]", + "storageNameClean": "[format('{0}{1}', parameters('defaultStorageName'), parameters('uniqueSuffix'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "userAssignedIdentityName": { + "value": "[variables('uaiName')]" + }, + "uaiExists": { + "value": "[not(equals(variables('userAssignedIdentityOverride'), ''))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15872236774552793157" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region for resource deployment" + } + }, + "userAssignedIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the user-assigned managed identity" + } + }, + "uaiExists": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Flag indicating if the identity already exists" + } + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-07-31-preview", + "name": "[parameters('userAssignedIdentityName')]", + "location": "[parameters('location')]" + } + ], + "outputs": { + "uaiName": { + "type": "string", + "value": "[if(parameters('uaiExists'), parameters('userAssignedIdentityName'), parameters('userAssignedIdentityName'))]" + }, + "uaiId": { + "type": "string", + "value": "[if(parameters('uaiExists'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')))]" + }, + "uaiPrincipalId": { + "type": "string", + "value": "[if(parameters('uaiExists'), reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').principalId, reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').principalId)]" + }, + "uaiClientId": { + "type": "string", + "value": "[if(parameters('uaiExists'), reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').clientId, reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').clientId)]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "suffix": { + "value": "[parameters('uniqueSuffix')]" + }, + "storageName": { + "value": "[variables('storageNameClean')]" + }, + "keyvaultName": { + "value": "[variables('keyVaultName')]" + }, + "aiServicesName": { + "value": "[variables('aiServiceName')]" + }, + "aiSearchName": { + "value": "[variables('aiSearchName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "aisKind": { + "value": "[parameters('aisKind')]" + }, + "aiServicesExists": { + "value": "[not(empty(parameters('aiServiceAccountName')))]" + }, + "aiSearchExists": { + "value": "[not(empty(parameters('aiSearchServiceName')))]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" + }, + "modelLocation": { + "value": "[parameters('modelLocation')]" + }, + "userAssignedIdentityName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.uaiName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "2515541218015614919" + } + }, + "parameters": { + "storageExists": { + "type": "bool", + "defaultValue": false + }, + "keyvaultExists": { + "type": "bool", + "defaultValue": false + }, + "aiServicesExists": { + "type": "bool", + "defaultValue": false + }, + "aiSearchExists": { + "type": "bool", + "defaultValue": false + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to add to the resources" + } + }, + "suffix": { + "type": "string", + "metadata": { + "description": "Unique suffix for resource names" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "AI services name" + } + }, + "keyvaultName": { + "type": "string", + "metadata": { + "description": "The name of the Key Vault" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "The name of the AI Search resource" + } + }, + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + }, + "aisKind": { + "type": "string", + "metadata": { + "description": "The Kind of AI Service, can be \"OpenAI\" or \"AIService\"" + } + }, + "vnetName": { + "type": "string", + "defaultValue": "[format('agents-vnet-{0}', parameters('suffix'))]", + "metadata": { + "description": "The name of the virtual network" + } + }, + "agentsSubnetName": { + "type": "string", + "defaultValue": "[format('agents-subnet-{0}', parameters('suffix'))]", + "metadata": { + "description": "The name of Agents Subnet for container apps" + } + }, + "cxSubnetName": { + "type": "string", + "defaultValue": "[format('hub-subnet-{0}', parameters('suffix'))]", + "metadata": { + "description": "The name of Customer Hub subnet for private endpoints" + } + }, + "userAssignedIdentityName": { + "type": "string" + }, + "noZRSRegions": { + "type": "array", + "defaultValue": [ + "southindia", + "westus" + ] + }, + "sku": { + "type": "object", + "defaultValue": "[if(contains(parameters('noZRSRegions'), parameters('location')), createObject('name', 'Standard_GRS'), createObject('name', 'Standard_ZRS'))]" + } + }, + "variables": { + "cxSubnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('cxSubnetName'))]", + "agentSubnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('agentsSubnetName'))]", + "storageNameCleaned": "[if(parameters('storageExists'), parameters('storageName'), replace(parameters('storageName'), '-', ''))]", + "aiServiceParts": "[if(parameters('aiServicesExists'), split(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '/'), split(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '/'))]", + "acsParts": "[if(parameters('aiSearchExists'), split(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiSearchName')), '/'), split(resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')), '/'))]", + "storageParts": "[if(parameters('storageExists'), split(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '/'), split(resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned')), '/'))]" + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-07-31-preview", + "name": "[parameters('userAssignedIdentityName')]", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-05-01", + "name": "[parameters('vnetName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "172.16.0.0/16" + ] + }, + "subnets": [ + { + "name": "[parameters('cxSubnetName')]", + "properties": { + "addressPrefix": "172.16.0.0/24", + "serviceEndpoints": [ + { + "service": "Microsoft.KeyVault", + "locations": [ + "[parameters('location')]" + ] + }, + { + "service": "Microsoft.Storage", + "locations": [ + "[parameters('location')]" + ] + }, + { + "service": "Microsoft.CognitiveServices", + "locations": [ + "[parameters('modelLocation')]" + ] + } + ] + } + }, + { + "name": "[parameters('agentsSubnetName')]", + "properties": { + "addressPrefix": "172.16.101.0/24", + "delegations": [ + { + "name": "Microsoft.app/environments", + "properties": { + "serviceName": "Microsoft.app/environments" + } + } + ] + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]" + ] + }, + { + "condition": "[not(parameters('keyvaultExists'))]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyvaultName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "createMode": "default", + "enabledForDeployment": true, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": true, + "enableSoftDelete": true, + "enableRbacAuthorization": true, + "enablePurgeProtection": true, + "publicNetworkAccess": "Disabled", + "accessPolicies": [ + { + "tenantId": "[subscription().tenantId]", + "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').principalId]", + "permissions": { + "secrets": [ + "set", + "get", + "list", + "delete", + "purge" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').subnets[0].id]" + } + ] + }, + "sku": { + "family": "A", + "name": "standard" + }, + "softDeleteRetentionInDays": 7, + "tenantId": "[subscription().tenantId]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + ] + }, + { + "condition": "[not(parameters('aiServicesExists'))]", + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiServicesName')]", + "location": "[parameters('modelLocation')]", + "sku": { + "name": "S0" + }, + "kind": "[parameters('aisKind')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[toLower(format('{0}', parameters('aiServicesName')))]", + "apiProperties": { + "statisticsEnabled": false + }, + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').subnets[0].id]" + } + ] + }, + "publicNetworkAccess": "Disabled" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + ] + }, + { + "condition": "[not(parameters('aiServicesExists'))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-06-01-preview", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + ] + }, + { + "condition": "[not(parameters('aiSearchExists'))]", + "type": "Microsoft.Search/searchServices", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiSearchName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')))]": {} + } + }, + "properties": { + "disableLocalAuth": false, + "authOptions": { + "aadOrApiKey": { + "aadAuthFailureMode": "http401WithBearerChallenge" + } + }, + "encryptionWithCmk": { + "enforcement": "Unspecified" + }, + "hostingMode": "default", + "partitionCount": 1, + "publicNetworkAccess": "Disabled", + "replicaCount": 1, + "semanticSearch": "disabled" + }, + "sku": { + "name": "standard" + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]" + ] + }, + { + "condition": "[not(parameters('storageExists'))]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[variables('storageNameCleaned')]", + "location": "[parameters('location')]", + "kind": "StorageV2", + "sku": "[parameters('sku')]", + "properties": { + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Disabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[variables('cxSubnetRef')]" + } + ] + }, + "allowSharedKeyAccess": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + ] + }, + { + "condition": "[not(parameters('storageExists'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-storage-rbac', parameters('suffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "suffix": { + "value": "[parameters('suffix')]" + }, + "storageName": { + "value": "[variables('storageNameCleaned')]" + }, + "UAIPrincipalId": { + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "1498445684207187922" + } + }, + "parameters": { + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "UAIPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the managed identity" + } + }, + "suffix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Unique suffix for resource naming" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), parameters('suffix'))]", + "properties": { + "principalId": "[parameters('UAIPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88'), parameters('suffix'))]", + "properties": { + "principalId": "[parameters('UAIPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]" + ] + }, + { + "condition": "[not(parameters('keyvaultExists'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-keyvault-rbac', parameters('suffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "suffix": { + "value": "[parameters('suffix')]" + }, + "keyvaultName": { + "value": "[parameters('keyvaultName')]" + }, + "UAIPrincipalId": { + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName')), '2023-07-31-preview').principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "6274162434845500752" + } + }, + "parameters": { + "keyvaultName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault" + } + }, + "UAIPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the managed identity" + } + }, + "suffix": { + "type": "string", + "metadata": { + "description": "Unique suffix for role assignment naming" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('keyvaultName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395'), parameters('suffix'))]", + "properties": { + "principalId": "[parameters('UAIPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('keyvaultName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, extensionResourceId(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), 'Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7'), parameters('suffix'))]", + "properties": { + "principalId": "[parameters('UAIPrincipalId')]", + "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), 'Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "principalType": "ServicePrincipal" + } + } + ], + "outputs": { + "contributorRoleId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), 'Microsoft.Authorization/roleAssignments', guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395'), parameters('suffix')))]" + }, + "secretsOfficerRoleId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), 'Microsoft.Authorization/roleAssignments', guid(subscription().subscriptionId, resourceGroup().id, extensionResourceId(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), 'Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7'), parameters('suffix')))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]" + ] + } + ], + "outputs": { + "aiServicesName": { + "type": "string", + "value": "[if(parameters('aiServicesExists'), parameters('aiServicesName'), parameters('aiServicesName'))]" + }, + "aiservicesID": { + "type": "string", + "value": "[if(parameters('aiServicesExists'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]" + }, + "aiservicesTarget": { + "type": "string", + "value": "[if(parameters('aiServicesExists'), reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-06-01-preview').endpoint, reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-06-01-preview').endpoint)]" + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "value": "[variables('aiServiceParts')[4]]" + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "value": "[variables('aiServiceParts')[2]]" + }, + "aiSearchName": { + "type": "string", + "value": "[if(parameters('aiSearchExists'), parameters('aiSearchName'), parameters('aiSearchName'))]" + }, + "aisearchID": { + "type": "string", + "value": "[if(parameters('aiSearchExists'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiSearchName')), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]" + }, + "aiSearchServiceResourceGroupName": { + "type": "string", + "value": "[variables('acsParts')[4]]" + }, + "aiSearchServiceSubscriptionId": { + "type": "string", + "value": "[variables('acsParts')[2]]" + }, + "storageAccountName": { + "type": "string", + "value": "[if(parameters('storageExists'), parameters('storageName'), parameters('storageName'))]" + }, + "storageId": { + "type": "string", + "value": "[if(parameters('storageExists'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned')))]" + }, + "storageAccountResourceGroupName": { + "type": "string", + "value": "[variables('storageParts')[4]]" + }, + "storageAccountSubscriptionId": { + "type": "string", + "value": "[variables('storageParts')[2]]" + }, + "virtualNetworkName": { + "type": "string", + "value": "[parameters('vnetName')]" + }, + "virtualNetworkId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "cxSubnetName": { + "type": "string", + "value": "[parameters('cxSubnetName')]" + }, + "agentSubnetName": { + "type": "string", + "value": "[parameters('agentsSubnetName')]" + }, + "cxSubnetId": { + "type": "string", + "value": "[variables('cxSubnetRef')]" + }, + "agentSubnetId": { + "type": "string", + "value": "[variables('agentSubnetRef')]" + }, + "keyvaultId": { + "type": "string", + "value": "[if(parameters('keyvaultExists'), resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiHubName": { + "value": "[format('{0}-{1}', parameters('defaultAiHubName'), parameters('uniqueSuffix'))]" + }, + "aiHubFriendlyName": { + "value": "[parameters('defaultAiHubFriendlyName')]" + }, + "aiHubDescription": { + "value": "[parameters('defaultAiHubDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiSearchName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiSearchId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aisearchID.value]" + }, + "aiSearchServiceResourceGroupName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiSearchServiceResourceGroupName.value]" + }, + "aiSearchServiceSubscriptionId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiSearchServiceSubscriptionId.value]" + }, + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiServicesId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiservicesID.value]" + }, + "aiServicesTarget": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiservicesTarget.value]" + }, + "aiServiceAccountResourceGroupName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountResourceGroupName.value]" + }, + "aiServiceAccountSubscriptionId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountSubscriptionId.value]" + }, + "keyVaultId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.keyvaultId.value]" + }, + "storageAccountId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.storageId.value]" + }, + "uaiName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.uaiName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "14092924036511829135" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiHubName')]", + "metadata": { + "description": "AI hub display name" + } + }, + "aiHubDescription": { + "type": "string", + "metadata": { + "description": "AI hub description" + } + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the key vault for secure secret storage" + } + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account for data persistence" + } + }, + "aiServicesId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services" + } + }, + "aiServicesTarget": { + "type": "string", + "metadata": { + "description": "Endpoint URL of the AI Services" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name of the AI Services resource" + } + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource Group containing AI Services" + } + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "metadata": { + "description": "Subscription ID containing AI Services" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Name of the AI Search service" + } + }, + "aiSearchId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Search service" + } + }, + "aiSearchServiceResourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource Group containing AI Search" + } + }, + "aiSearchServiceSubscriptionId": { + "type": "string", + "metadata": { + "description": "Subscription ID containing AI Search" + } + }, + "uaiName": { + "type": "string", + "metadata": { + "description": "Name of the user-assigned managed identity" + } + }, + "aiHubExists": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Flag indicating if the hub already exists" + } + } + }, + "variables": { + "acsConnectionName": "[format('{0}-connection-AISearch', parameters('aiHubName'))]", + "aoaiConnection": "[format('{0}-connection-AIServices_aoai', parameters('aiHubName'))]", + "userAssignedIdentities": "[json(format('{{''{0}'': {{}}}}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('uaiName'))))]" + }, + "resources": [ + { + "condition": "[not(parameters('aiHubExists'))]", + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-connection-AIServices', parameters('aiHubName')))]", + "properties": { + "category": "AIServices", + "target": "[parameters('aiServicesTarget')]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiServicesId')]", + "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiServiceAccountSubscriptionId'), parameters('aiServiceAccountResourceGroupName')), 'Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2023-05-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "condition": "[not(parameters('aiHubExists'))]", + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), variables('acsConnectionName'))]", + "properties": { + "category": "CognitiveSearch", + "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiSearchId')]", + "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchServiceSubscriptionId'), parameters('aiSearchServiceResourceGroupName')), 'Microsoft.Search/searchServices', parameters('aiSearchName')), '2023-11-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "condition": "[not(parameters('aiHubExists'))]", + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('aiHubName')]", + "location": "[parameters('location')]", + "kind": "hub", + "tags": "[parameters('tags')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": "[variables('userAssignedIdentities')]" + }, + "properties": { + "friendlyName": "[parameters('aiHubFriendlyName')]", + "description": "[parameters('aiHubDescription')]", + "primaryUserAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('uaiName'))]", + "keyVault": "[parameters('keyVaultId')]", + "storageAccount": "[parameters('storageAccountId')]" + } + } + ], + "outputs": { + "aiHubID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + }, + "aiHubName": { + "type": "string", + "value": "[parameters('aiHubName')]" + }, + "aoaiConnectionName": { + "type": "string", + "value": "[variables('aoaiConnection')]" + }, + "acsConnectionName": { + "type": "string", + "value": "[variables('acsConnectionName')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--private-endpoint', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiSearchName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiStorageId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.storageId.value]" + }, + "storageName": { + "value": "[variables('storageNameClean')]" + }, + "vnetName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.virtualNetworkName.value]" + }, + "cxSubnetName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.cxSubnetName.value]" + }, + "suffix": { + "value": "[parameters('uniqueSuffix')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "6718468015645700202" + } + }, + "parameters": { + "aiServicesName": { + "type": "string" + }, + "aiSearchName": { + "type": "string" + }, + "storageName": { + "type": "string" + }, + "vnetName": { + "type": "string" + }, + "cxSubnetName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "aiStorageId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[format('{0}-private-endpoint', parameters('aiServicesName'))]", + "location": "[resourceGroup().location]", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('cxSubnetName'))]" + }, + "privateLinkServiceConnections": [ + { + "name": "[format('{0}-private-link-service-connection', parameters('aiServicesName'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]", + "groupIds": [ + "account" + ] + } + } + ] + } + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[format('{0}-openAi-private-endpoint', parameters('aiServicesName'))]", + "location": "[resourceGroup().location]", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('cxSubnetName'))]" + }, + "privateLinkServiceConnections": [ + { + "name": "[format('{0}-openAi-private-link-service-connection', parameters('aiServicesName'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]", + "groupIds": [ + "account" + ] + } + } + ] + } + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[format('{0}-private-endpoint', parameters('aiSearchName'))]", + "location": "[resourceGroup().location]", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('cxSubnetName'))]" + }, + "privateLinkServiceConnections": [ + { + "name": "[format('{0}-private-link-service-connection', parameters('aiSearchName'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "groupIds": [ + "searchService" + ] + } + } + ] + } + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[format('{0}-private-endpoint', parameters('storageName'))]", + "location": "[resourceGroup().location]", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('cxSubnetName'))]" + }, + "privateLinkServiceConnections": [ + { + "name": "[format('{0}-private-link-service-connection', parameters('storageName'))]", + "properties": { + "privateLinkServiceId": "[parameters('aiStorageId')]", + "groupIds": [ + "blob" + ] + } + } + ] + } + }, + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "privatelink.azureml.ms", + "location": "global" + }, + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "privatelink.openai.azure.com", + "location": "global" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', 'privatelink.azureml.ms', format('aiServices-{0}-link', parameters('suffix')))]", + "location": "global", + "properties": { + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.azureml.ms')]" + ] + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', 'privatelink.openai.azure.com', format('aiServicesOpenAI-{0}-link', parameters('suffix')))]", + "location": "global", + "properties": { + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.openai.azure.com')]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('aiServicesName')), format('{0}-dns-group', parameters('aiServicesName')))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('{0}-dns-config', parameters('aiServicesName'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.azureml.ms')]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.azureml.ms')]", + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiServicesName')))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', format('{0}-openAi-private-endpoint', parameters('aiServicesName')), format('{0}-openAi-dns-group', parameters('aiServicesName')))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('{0}-openAi-dns-config', parameters('aiServicesName'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.openai.azure.com')]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-openAi-private-endpoint', parameters('aiServicesName')))]", + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.openai.azure.com')]" + ] + }, + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "privatelink.search.windows.net", + "location": "global" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', 'privatelink.search.windows.net', format('aiSearch-{0}-link', parameters('suffix')))]", + "location": "global", + "properties": { + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.search.windows.net')]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('aiSearchName')), format('{0}-dns-group', parameters('aiSearchName')))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('{0}-dns-config', parameters('aiSearchName'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.search.windows.net')]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.search.windows.net')]", + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiSearchName')))]" + ] + }, + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "location": "global" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink.blob.{0}', environment().suffixes.storage), format('storage-{0}-link', parameters('suffix')))]", + "location": "global", + "properties": { + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('storageName')), format('{0}-dns-group', parameters('storageName')))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('{0}-dns-config', parameters('storageName'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]", + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('storageName')))]" + ] + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiProjectName": { + "value": "[format('{0}-{1}', parameters('defaultAiProjectName'), parameters('uniqueSuffix'))]" + }, + "aiProjectFriendlyName": { + "value": "[parameters('defaultAiProjectFriendlyName')]" + }, + "aiProjectDescription": { + "value": "[parameters('defaultAiProjectDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiHubId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiHubID.value]" + }, + "uaiName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.uaiName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "9815368297848290681" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI Project name" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiProjectName')]", + "metadata": { + "description": "AI Project display name" + } + }, + "aiProjectDescription": { + "type": "string", + "metadata": { + "description": "AI Project description" + } + }, + "aiHubId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Hub resource" + } + }, + "uaiName": { + "type": "string", + "metadata": { + "description": "Name of the user-assigned managed identity" + } + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "projectConnectionString": "[format('{0}.api.azureml.ms;{1};{2};{3}', parameters('location'), variables('subscriptionId'), variables('resourceGroupName'), parameters('aiProjectName'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('aiProjectName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('ProjectConnectionString', variables('projectConnectionString')))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('uaiName')))]": {} + } + }, + "properties": { + "friendlyName": "[parameters('aiProjectFriendlyName')]", + "description": "[parameters('aiProjectDescription')]", + "primaryUserAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('uaiName'))]", + "hubResourceId": "[parameters('aiHubId')]" + }, + "kind": "project" + } + ], + "outputs": { + "aiProjectName": { + "type": "string", + "value": "[parameters('aiProjectName')]" + }, + "aiProjectResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + }, + "aiProjectWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2023-08-01-preview').workspaceId]" + }, + "projectConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2023-08-01-preview', 'full').tags.ProjectConnectionString]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--private-endpoint', parameters('name'), parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('wait-script-{0}-deployment', parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('wait-script-{0}', parameters('uniqueSuffix'))]" + }, + "location": { + "value": "[parameters('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15002511661880391263" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "retentionTime": { + "type": "string", + "defaultValue": "PT1H" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "AzurePowerShell", + "properties": { + "azPowerShellVersion": "10.0", + "scriptContent": " Write-Output \"Starting wait script...\"\n Start-Sleep -Seconds 120\n Write-Output \"Wait completed. Proceeding with deployment...\"\n ", + "retentionInterval": "[parameters('retentionTime')]", + "cleanupPreference": "Always" + } + } + ], + "outputs": { + "scriptName": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--AiServices-RA', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.uaiPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "12976852655018469345" + } + }, + "parameters": { + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account" + } + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the managed identity" + } + }, + "aiProjectId": { + "type": "string", + "metadata": { + "description": "Unique identifier for role assignments" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('wait-script-{0}-deployment', parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--AiSearch-RA', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiSearchName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.uaiPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "16079856828729639404" + } + }, + "parameters": { + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Name of the AI Search service" + } + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the managed identity" + } + }, + "aiProjectId": { + "type": "string", + "metadata": { + "description": "Unique suffix for resource naming" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(subscription().subscriptionId, resourceGroup().id, resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--identity', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('wait-script-{0}-deployment', parameters('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}--capability-host', parameters('name'), parameters('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "capabilityHostName": { + "value": "[format('{0}-{1}', parameters('uniqueSuffix'), parameters('defaultCapabilityHostName'))]" + }, + "aiHubName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiHubName.value]" + }, + "aiProjectName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aiProjectName.value]" + }, + "acsConnectionName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.acsConnectionName.value]" + }, + "aoaiConnectionName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.aoaiConnectionName.value]" + }, + "customerSubnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.agentSubnetId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "6620421783569115101" + } + }, + "parameters": { + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI project name" + } + }, + "acsConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + }, + "aoaiConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + }, + "capabilityHostName": { + "type": "string", + "metadata": { + "description": "Name for capabilityHost." + } + }, + "customerSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name for customer subnet id" + } + } + }, + "variables": { + "storageConnections": [ + "[format('{0}/workspaceblobstore', parameters('aiProjectName'))]" + ], + "aiSearchConnection": [ + "[format('{0}', parameters('acsConnectionName'))]" + ], + "aiServiceConnections": [ + "[format('{0}', parameters('aoaiConnectionName'))]" + ] + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-{1}', parameters('aiHubName'), parameters('capabilityHostName')))]", + "properties": "[if(empty(parameters('customerSubnetId')), createObject('capabilityHostKind', 'Agents'), createObject('capabilityHostKind', 'Agents', 'customerSubnet', parameters('customerSubnetId')))]" + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiProjectName'), format('{0}-{1}', parameters('aiProjectName'), parameters('capabilityHostName')))]", + "properties": { + "capabilityHostKind": "Agents", + "aiServicesConnections": "[variables('aiServiceConnections')]", + "vectorStoreConnections": "[variables('aiSearchConnection')]", + "storageConnections": "[variables('storageConnections')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces/capabilityHosts', parameters('aiHubName'), format('{0}-{1}', parameters('aiHubName'), parameters('capabilityHostName')))]" + ] + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--dependencies', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--hub', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--AiSearch-RA', parameters('name'), parameters('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}--AiServices-RA', parameters('name'), parameters('uniqueSuffix')))]" + ] + } + ], + "outputs": { + "PROJECT_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}--project', parameters('name'), parameters('uniqueSuffix'))), '2022-09-01').outputs.projectConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/scenarios/Agents/setup/network-secured-agent/azuredeploy.parameters.json b/scenarios/Agents/setup/network-secured-agent/azuredeploy.parameters.json new file mode 100644 index 00000000..0cd8933b --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/azuredeploy.parameters.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "defaultAiHubName": { + "value": "hub-demo" + }, + "defaultAiHubFriendlyName": { + "value": "Agents Hub resource" + }, + "defaultAiHubDescription": { + "value": "This is an example AI resource for use in Azure AI Studio." + }, + "defaultAiProjectName": { + "value": "project-demo" + }, + "defaultAiProjectFriendlyName": { + "value": "Agents Project resource" + }, + "defaultAiProjectDescription": { + "value": "This is an example AI Project resource for use in Azure AI Studio." + }, + "tags": { + "value": {} + }, + "defaultAiSearchName": { + "value": "agent-ai-search" + }, + "defaultCapabilityHostName": { + "value": "caphost1" + }, + "defaultStorageName": { + "value": "agentstorage" + }, + "defaultAiServicesName": { + "value": "agent-ai-services" + }, + "modelName": { + "value": "gpt-4o-mini" + }, + "modelFormat": { + "value": "OpenAI" + }, + "modelVersion": { + "value": "2024-07-18" + }, + "modelSkuName": { + "value": "GlobalStandard" + }, + "modelCapacity": { + "value": 50 + }, + "modelLocation": { + "value": "westus2" + } + } + } + \ No newline at end of file diff --git a/scenarios/Agents/setup/network-secured-agent/main.bicep b/scenarios/Agents/setup/network-secured-agent/main.bicep new file mode 100644 index 00000000..a4cee74b --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/main.bicep @@ -0,0 +1,298 @@ +/* +Network-Secured Agent Architecture Overview +----------------------------------------- +This template deploys an AI agent infrastructure in a network-secured configuration: + +1. Network Security: + - All services are deployed with private endpoints + - Access is restricted through VNet integration + - Private DNS zones manage internal name resolution + +2. Key Network Components: + - Virtual Network: Isolated network environment for all resources + - Subnets: Segregated network spaces for different service types + - Private Endpoints: Secure access points for Azure services + - Private DNS Zones: Internal name resolution for private endpoints + +3. Security Design: + - No public internet exposure for core services + - Network isolation between components + - Managed identity for secure authentication +*/ + +// Existing Resource Overrides - Used when connecting to pre-existing resources +var keyVaultOverride = '' // Override for existing Key Vault +var userAssignedIdentityOverride = '' // Override for existing managed identity + +/* ---------------------------------- Deployment Identifiers ---------------------------------- */ + +param name string = 'network-secured-agent' + +// Create a short, unique suffix, that will be unique to each resource group +param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') +param uniqueSuffix string = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) + +/* ---------------------------------- Default Parameters if Overrides Not Set ---------------------------------- */ + +// Parameters +@minLength(2) +@maxLength(12) +@description('Name for the AI resource and used to derive name of dependent resources.') +param defaultAiHubName string = 'hub-demo' + +@description('Friendly name for your Hub resource') +param defaultAiHubFriendlyName string = 'Agents Hub resource' + +@description('Description of your Azure AI resource displayed in AI studio') +param defaultAiHubDescription string = 'This is an example AI resource for use in Azure AI Studio.' + +@description('Name for the AI project resources.') +param defaultAiProjectName string = 'project-demo' + +@description('Friendly name for your Azure AI resource') +param defaultAiProjectFriendlyName string = 'Agents Project resource' + +@description('Description of your Azure AI resource displayed in AI studio') +param defaultAiProjectDescription string = 'This is an example AI Project resource for use in Azure AI Studio.' + +@description('Azure region used for the deployment of all resources.') +param location string = resourceGroup().location + +@description('Set of tags to apply to all resources.') +param tags object = {} + +@description('Name of the Azure AI Search account') +param defaultAiSearchName string = 'agent-ai-search' + +@description('Name for capabilityHost.') +param defaultCapabilityHostName string = 'caphost1' + +@description('Name of the storage account') +param defaultStorageName string = 'agentstorage' + +@description('Name of the Azure AI Services account') +param defaultAiServicesName string = 'agent-ai-service' + +@description('Model name for deployment') +param modelName string = 'gpt-4o-mini' + +@description('Model format for deployment') +param modelFormat string = 'OpenAI' + +@description('Model version for deployment') +param modelVersion string = '2024-07-18' + +@description('Model deployment SKU name') +param modelSkuName string = 'GlobalStandard' + +@description('Model deployment capacity') +param modelCapacity int = 50 + +@description('Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created.') +param modelLocation string = 'eastus' + +@description('AI service kind, values can be "OpenAI" or "AIService"') +param aisKind string = 'AIServices' + +@description('The AI Service Account name. This is an optional field, and if not provided, the resource will be created. The resource should exist in same resource group') +param aiServiceAccountName string = '' + +@description('The AI Search Service name. This is an optional field, and if not provided, the resource will be created.The resource should exist in same resource group must be Public Network Disabled') +param aiSearchServiceName string = '' + +// @description('The Ai Storage Account name. This is an optional field, and if not provided, the resource will be created.The resource should exist in same resource group') +// param aiStorageAccountName string = '' + +/* ---------------------------------- Create User Assigned Identity ---------------------------------- */ + +@description('The name of User Assigned Identity') +param userAssignedIdentityDefaultName string = 'secured-agents-identity-${uniqueSuffix}' +var uaiName = (userAssignedIdentityOverride == '') ? userAssignedIdentityDefaultName : userAssignedIdentityOverride + +module identity 'modules-network-secured/network-secured-identity.bicep' = { + name: '${name}-${uniqueSuffix}--identity' + params: { + location: location + userAssignedIdentityName: uaiName + uaiExists: userAssignedIdentityOverride != '' + } +} + + + +/* ---------------------------------- Create AI Assistant Dependent Resources ---------------------------------- */ + +// var storageName = empty(aiStorageAccountName) ? '${defaultStorageName}${uniqueSuffix}' : aiStorageAccountName +var keyVaultName = empty(keyVaultOverride) ? 'kv-${defaultAiHubName}-${uniqueSuffix}' : keyVaultOverride +var aiServiceName = empty(aiServiceAccountName) ? '${defaultAiServicesName}${uniqueSuffix}' : aiServiceAccountName +var aiSearchName = empty(aiSearchServiceName) ? '${defaultAiSearchName}${uniqueSuffix}' : aiSearchServiceName + +var storageNameClean = '${defaultStorageName}${uniqueSuffix}' +// Dependent resources for the Azure Machine Learning workspace +module aiDependencies 'modules-network-secured/network-secured-dependent-resources.bicep' = { + name: '${name}-${uniqueSuffix}--dependencies' + params: { + suffix: uniqueSuffix + storageName: storageNameClean + keyvaultName: keyVaultName + aiServicesName: aiServiceName + aiSearchName: aiSearchName + tags: tags + location: location + aisKind: aisKind + + aiServicesExists: !empty(aiServiceAccountName) + aiSearchExists: !empty(aiSearchServiceName) + + // Model deployment parameters + modelName: modelName + modelFormat: modelFormat + modelVersion: modelVersion + modelSkuName: modelSkuName + modelCapacity: modelCapacity + modelLocation: modelLocation + + userAssignedIdentityName: identity.outputs.uaiName + } +} + + + +module aiHub 'modules-network-secured/network-secured-ai-hub.bicep' = { + name: '${name}-${uniqueSuffix}--hub' + params: { + // workspace organization + aiHubName: '${defaultAiHubName}-${uniqueSuffix}' + aiHubFriendlyName: defaultAiHubFriendlyName + aiHubDescription: defaultAiHubDescription + location: location + tags: tags + + aiSearchName: aiDependencies.outputs.aiSearchName + aiSearchId: aiDependencies.outputs.aisearchID + aiSearchServiceResourceGroupName: aiDependencies.outputs.aiSearchServiceResourceGroupName + aiSearchServiceSubscriptionId: aiDependencies.outputs.aiSearchServiceSubscriptionId + + aiServicesName: aiDependencies.outputs.aiServicesName + aiServicesId: aiDependencies.outputs.aiservicesID + aiServicesTarget: aiDependencies.outputs.aiservicesTarget + aiServiceAccountResourceGroupName:aiDependencies.outputs.aiServiceAccountResourceGroupName + aiServiceAccountSubscriptionId:aiDependencies.outputs.aiServiceAccountSubscriptionId + + keyVaultId: aiDependencies.outputs.keyvaultId + storageAccountId: aiDependencies.outputs.storageId + + uaiName: identity.outputs.uaiName + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { + name: aiDependencies.outputs.storageAccountName + scope: resourceGroup() +} + +resource aiServices 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { + name: aiDependencies.outputs.aiServicesName + scope: resourceGroup(aiDependencies.outputs.aiServiceAccountSubscriptionId, aiDependencies.outputs.aiServiceAccountResourceGroupName) +} + +resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = { + name: aiDependencies.outputs.aiSearchName + scope: resourceGroup(aiDependencies.outputs.aiSearchServiceSubscriptionId, aiDependencies.outputs.aiSearchServiceResourceGroupName) +} + +// Private Endpoint and DNS Configuration +// This module sets up private network access for all Azure services: +// 1. Creates private endpoints in the specified subnet +// 2. Sets up private DNS zones for each service: +// - privatelink.search.windows.net for AI Search +// - privatelink.cognitiveservices.azure.com for AI Services +// - privatelink.blob.core.windows.net for Storage +// 3. Links private DNS zones to the VNet for name resolution +// 4. Configures network policies to restrict access to private endpoints only +module privateEndpointAndDNS 'modules-network-secured/private-endpoint-and-dns.bicep' = { + name: '${name}-${uniqueSuffix}--private-endpoint' + params: { + aiServicesName: aiDependencies.outputs.aiServicesName // AI Services to secure + aiSearchName: aiDependencies.outputs.aiSearchName // AI Search to secure + aiStorageId: aiDependencies.outputs.storageId // Storage to secure + storageName: storageNameClean // Clean storage name for DNS + vnetName: aiDependencies.outputs.virtualNetworkName // VNet containing subnets + cxSubnetName: aiDependencies.outputs.cxSubnetName // Subnet for private endpoints + suffix: uniqueSuffix // Unique identifier + } + dependsOn: [ + aiServices // Ensure AI Services exist + aiSearch // Ensure AI Search exists + storage // Ensure Storage exists + ] +} + +module aiProject 'modules-network-secured/network-secured-ai-project.bicep' = { + name: '${name}-${uniqueSuffix}--project' + params: { + // workspace organization + aiProjectName: '${defaultAiProjectName}-${uniqueSuffix}' + aiProjectFriendlyName: defaultAiProjectFriendlyName + aiProjectDescription: defaultAiProjectDescription + location: location + tags: tags + aiHubId: aiHub.outputs.aiHubID + uaiName: identity.outputs.uaiName + } + dependsOn: [ + privateEndpointAndDNS + ] +} +module waitScript 'modules-network-secured/common/wait-script.bicep' = { + name: 'wait-script-${uniqueSuffix}-deployment' + params: { + name: 'wait-script-${uniqueSuffix}' + location: location + } + dependsOn: [ + aiProject + ] +} +module aiServiceRoleAssignments 'modules-network-secured/ai-service-role-assignments.bicep' = { + name: '${name}-${uniqueSuffix}--AiServices-RA' + scope: resourceGroup() + params: { + aiServicesName: aiDependencies.outputs.aiServicesName + aiProjectPrincipalId: identity.outputs.uaiPrincipalId + aiProjectId: aiProject.outputs.aiProjectResourceId + } + dependsOn: [ + waitScript + ] +} + +module aiSearchRoleAssignments 'modules-network-secured/ai-search-role-assignments.bicep' = { + name: '${name}-${uniqueSuffix}--AiSearch-RA' + scope: resourceGroup() + params: { + aiSearchName: aiDependencies.outputs.aiSearchName + aiProjectPrincipalId: identity.outputs.uaiPrincipalId + aiProjectId: aiProject.outputs.aiProjectResourceId + } + dependsOn: [ + waitScript + ] +} + +module addCapabilityHost 'modules-network-secured/network-capability-host.bicep' = { + name: '${name}-${uniqueSuffix}--capability-host' + params: { + capabilityHostName: '${uniqueSuffix}-${defaultCapabilityHostName}' + aiHubName: aiHub.outputs.aiHubName + aiProjectName: aiProject.outputs.aiProjectName + acsConnectionName: aiHub.outputs.acsConnectionName + aoaiConnectionName: aiHub.outputs.aoaiConnectionName + customerSubnetId: aiDependencies.outputs.agentSubnetId + } + dependsOn: [ + aiSearchRoleAssignments, aiServiceRoleAssignments + ] +} + +output PROJECT_CONNECTION_STRING string = aiProject.outputs.projectConnectionString diff --git a/scenarios/Agents/setup/network-secured-agent/metadata.json b/scenarios/Agents/setup/network-secured-agent/metadata.json new file mode 100644 index 00000000..759e952c --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/metadata.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://aka.ms/azure-quickstart-templates-metadata-schema#", + "type": "QuickStart", + "itemDisplayName": "Network Secured Agent with User Managed Identity", + "description": "This set of templates demonstrates how to set up Azure AI Agent Service with virtual network isolation using User Managed Identity authetication for the AI Service/AOAI connection and private network links to connect the agent to your secure data.", + "summary": "This set of templates demonstrates how to set up Azure AI Agent Service with virtual network isolation and authentication using user managed identity", + "githubUsername": "jkrame", + "dateUpdated": "2025-01-08", + "environments": [ + "AzureCloud" + ] +} \ No newline at end of file diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-role-assignments.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-role-assignments.bicep new file mode 100644 index 00000000..911c0ff3 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-role-assignments.bicep @@ -0,0 +1,72 @@ +/* +AI Search Role Assignments Module +------------------------------- +This module configures RBAC permissions for AI Search service access: + +1. Role Assignments: + - Search Index Data Contributor: Read/write access to search indexes + - Search Service Contributor: Manage search service settings + +2. Unique Assignment Names: + - Uses deployment-specific suffix for unique role assignments + - Prevents conflicts in multi-deployment scenarios +*/ + +@description('Name of the AI Search service') +param aiSearchName string + +@description('Principal ID of the managed identity') +param aiProjectPrincipalId string + +@description('Unique suffix for resource naming') +param aiProjectId string + +// Reference existing search service +resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { + name: aiSearchName + scope: resourceGroup() +} + +/* -------------------------------------------- Role Definitions -------------------------------------------- */ + +// Search Index Data Contributor Role +// Provides read/write access to search indexes and their data +resource searchIndexDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' // Built-in role ID + scope: resourceGroup() +} + +// Search Service Contributor Role +// Provides access to manage search service settings +resource searchServiceContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Built-in role ID + scope: resourceGroup() +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +// Assign Search Index Data Contributor role +// Uses subscription ID and timestamp in guid to ensure uniqueness +resource searchIndexDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, searchIndexDataContributorRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: searchIndexDataContributorRole.id + principalType: 'ServicePrincipal' + } +} + +// Assign Search Service Contributor role +// Uses subscription ID and timestamp in guid to ensure uniqueness +resource searchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, searchServiceContributorRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: searchServiceContributorRole.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-service.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-service.bicep new file mode 100644 index 00000000..9bba3d24 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-search-service.bicep @@ -0,0 +1,87 @@ +/* +AI Search Service Module +---------------------- +This module deploys an Azure AI Search service with network security controls: + +1. Security Features: + - User-assigned managed identity for authentication + - Public network access disabled + - AAD-based authentication + - Optional CMK encryption support + +2. Network Security: + - Private endpoint access only + - No public internet exposure + - AAD authentication with bearer challenge + +3. Service Configuration: + - Standard SKU for production workloads + - Configurable partition and replica counts + - Managed identity integration + +4. Authentication: + - AAD-first authentication model + - Bearer token challenge for failed auth + - Local auth optionally available +*/ + +/* -------------------------------------------- Parameters -------------------------------------------- */ + +@description('Name of the user-assigned managed identity') +param userAssignedIdentityName string + +@description('Name of the AI Search service') +param aiSearchName string + +@description('Azure region for the search service') +param searchLocation string + +@description('Tags to add to the resources') +param tags object = {} + +/* -------------------------------------------- Resources -------------------------------------------- */ + +// Reference to user-assigned managed identity +resource uai 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' existing = { + name: userAssignedIdentityName + scope: resourceGroup() +} + +// AI Search Service +// Documentation: https://learn.microsoft.com/en-us/azure/templates/microsoft.search/searchservices +resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = { + name: aiSearchName + location: searchLocation + tags: tags + identity: { + type: 'UserAssigned' // Use managed identity for authentication + userAssignedIdentities: { + '${uai.id}': {} + } + } + properties: { + disableLocalAuth: false // Allow both AAD and API key auth + authOptions: { + aadOrApiKey: { + aadAuthFailureMode: 'http401WithBearerChallenge' // Proper AAD auth challenge + } + } + encryptionWithCmk: { + enforcement: 'Unspecified' // Default encryption mode + } + hostingMode: 'default' // Standard hosting mode + partitionCount: 1 // Number of search partitions + publicNetworkAccess: 'Disabled' // Force private endpoint access + replicaCount: 1 // Number of search replicas + semanticSearch: 'disabled' // Semantic search capability + } + sku: { + name: 'standard' // Production-grade SKU + } +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +output searchServiceName string = aiSearch.name +output searchServiceId string = aiSearch.id +output searchServiceEndpoint string = 'https://${aiSearch.name}.search.windows.net' diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-service-role-assignments.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-service-role-assignments.bicep new file mode 100644 index 00000000..2da75091 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai-service-role-assignments.bicep @@ -0,0 +1,91 @@ +/* +AI Services Role Assignments Module +-------------------------------- +This module configures RBAC permissions for AI Services access: + +1. Role Assignments: + - Cognitive Services Contributor: Full service management + - Cognitive Services OpenAI User: Access to OpenAI features + - Cognitive Services User: Basic service usage + +2. Unique Assignment Names: + - Uses subscription and resource group IDs + - Includes deployment-specific identifiers + - Prevents conflicts in multi-deployment scenarios +*/ + +@description('Name of the AI Services account') +param aiServicesName string + +@description('Principal ID of the managed identity') +param aiProjectPrincipalId string + +@description('Unique identifier for role assignments') +param aiProjectId string + +// Reference existing AI Services account +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = { + name: aiServicesName + scope: resourceGroup() +} + +/* -------------------------------------------- Role Definitions -------------------------------------------- */ + +// Cognitive Services Contributor Role +// Provides full access to manage AI services and their settings +resource cognitiveServicesContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' // Built-in role ID + scope: resourceGroup() +} + +// Cognitive Services OpenAI User Role +// Provides access to use OpenAI features +resource cognitiveServicesOpenAIUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Built-in role ID + scope: resourceGroup() +} + +// Cognitive Services User Role +// Provides basic access to use AI services +resource cognitiveServicesUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'a97b65f3-24c7-4388-baec-2e87135dc908' // Built-in role ID + scope: resourceGroup() +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +// Assign Cognitive Services Contributor role +resource cognitiveServicesContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01'= { + scope: aiServices + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, cognitiveServicesContributorRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesContributorRole.id + principalType: 'ServicePrincipal' + } +} + +// Assign Cognitive Services OpenAI User role +resource cognitiveServicesOpenAIUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, cognitiveServicesOpenAIUserRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesOpenAIUserRole.id + principalType: 'ServicePrincipal' + } +} + +// Assign Cognitive Services User role +resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, cognitiveServicesUserRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesUserRole.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai/ai-services-config.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai/ai-services-config.bicep new file mode 100644 index 00000000..8da8a0c7 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/ai/ai-services-config.bicep @@ -0,0 +1,141 @@ +/* +AI Services Configuration Module +----------------------------- +This module deploys AI Services with network security controls: + +1. Security Features: + - Network ACLs + - Private networking + - Managed identity + - Custom subdomain + +2. Model Deployment: + - Model configuration + - SKU settings + - Capacity management +*/ + +@description('Azure region for the deployment') +param location string + +@description('Tags to apply to resources') +param tags object = {} + +@description('The name of the AI Services account') +param aiServicesName string + +@description('ID of the subnet for network rules') +param subnetId string + +@description('Whether to enable public network access') +param enablePublicNetworkAccess bool = false + +// Model deployment parameters +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('Log Analytics workspace ID for diagnostics') +param logAnalyticsWorkspaceId string = '' + +// AI Services account with network security controls +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = { + name: aiServicesName + location: location + tags: tags + sku: { + name: 'S0' + } + kind: 'AIServices' + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower(aiServicesName) + apiProperties: { + statisticsEnabled: false + } + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + id: subnetId + } + ] + } + publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled' + } +} + +// Model deployment +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-06-01-preview' = { + parent: aiServices + name: modelName + sku: { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model: { + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +// Diagnostic settings +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(logAnalyticsWorkspaceId)) { + name: '${aiServicesName}-diagnostics' + scope: aiServices + properties: { + workspaceId: logAnalyticsWorkspaceId + logs: [ + { + category: 'Audit' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + { + category: 'RequestResponse' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + } +} + +// Output variables +output aiServicesName string = aiServices.name +output aiServicesId string = aiServices.id +output aiServicesEndpoint string = aiServices.properties.endpoint +output modelDeploymentName string = modelDeployment.name +output principalId string = aiServices.identity.principalId diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/cognitive-services-role-assignments.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/cognitive-services-role-assignments.bicep new file mode 100644 index 00000000..88328fd1 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/cognitive-services-role-assignments.bicep @@ -0,0 +1,56 @@ +/* +Cognitive Services Role Assignments Module +--------------------------------------- +This module configures RBAC permissions for Azure AI Services: + +1. Role Configuration: + - Azure AI Administrator Role (b78c5d69-af96-48a3-bf8d-a8b4d589de94) + - Provides full access to manage AI resources and deployments + - Required for AI Studio operations + +2. Permissions Granted: + - Create and manage AI deployments + - Configure model settings + - Monitor resource usage + - Manage security settings + +3. Security Considerations: + - Uses managed identity for authentication + - Scoped to resource group level + - Follows principle of least privilege + +Documentation: +- Azure AI Administrator Role: https://learn.microsoft.com/en-us/azure/ai-studio/concepts/rbac-ai-studio#azure-ai-administrator-role +*/ + +@description('Principal ID of the managed identity') +param UAIPrincipalId string + +@description('Unique suffix for role assignment naming') +param suffix string + +/* -------------------------------------------- Role Definitions -------------------------------------------- */ + +// Azure AI Administrator Role +// Provides full access to manage AI resources and their settings +resource openAIAdmin 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b78c5d69-af96-48a3-bf8d-a8b4d589de94' // Built-in role ID + scope: resourceGroup() +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +// Assign Azure AI Administrator role +resource openAIContributorContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, openAIAdmin.id, suffix) + properties: { + principalId: UAIPrincipalId // Managed identity principal ID + roleDefinitionId: openAIAdmin.id // AI Administrator role + principalType: 'ServicePrincipal' // Identity type + } +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +output roleAssignmentId string = openAIContributorContributorAssignment.id diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/parameters.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/parameters.bicep new file mode 100644 index 00000000..c7530a4b --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/parameters.bicep @@ -0,0 +1,156 @@ +/* +Common Parameters Module +---------------------- +This module defines common parameters and variables used across the infrastructure: + +1. Naming Standards: + - Resource prefixes + - Naming patterns + - Unique suffix generation + +2. Network Configuration: + - Address spaces + - Subnet ranges + - Service endpoints + +3. Tags: + - Environment tags + - Project tags + - Security tags +*/ + +// Resource naming +@description('Prefix for resource names') +param prefix string + +@description('Azure region for the deployment') +param location string = resourceGroup().location + +@description('Environment (e.g., prod, dev, test)') +@allowed([ + 'prod' + 'dev' + 'test' + 'stage' +]) +param environment string = 'prod' + +// Generate unique suffix +var uniqueSuffix = uniqueString(subscription().id, resourceGroup().id, prefix) + +// Common tags +var commonTags = { + Environment: environment + Project: 'NetworkSecuredAgent' + SecurityLevel: 'High' + DataClassification: 'Confidential' + CostCenter: 'AI-Services' + CreatedBy: 'IaC' + ManagedBy: 'DevOps' +} + +// Network configuration +var networkConfig = { + vnetAddressSpace: '172.16.0.0/16' + hubSubnetPrefix: '172.16.0.0/24' + agentsSubnetPrefix: '172.16.101.0/24' + serviceEndpoints: [ + { + service: 'Microsoft.KeyVault' + locations: [ + location + ] + } + { + service: 'Microsoft.Storage' + locations: [ + location + ] + } + { + service: 'Microsoft.CognitiveServices' + locations: [ + location + ] + } + ] + dnsZones: { + keyVault: 'privatelink.vaultcore.azure.net' + storage: 'privatelink.blob.core.windows.net' + aiServices: 'privatelink.cognitiveservices.azure.com' + aiSearch: 'privatelink.search.windows.net' + } +} + +// Resource naming patterns +var namingPatterns = { + vnet: '${prefix}-vnet-${uniqueSuffix}' + keyvault: 'kv-${prefix}-${uniqueSuffix}' + storage: '${prefix}store${uniqueSuffix}' + aiServices: '${prefix}-ai-${uniqueSuffix}' + aiSearch: '${prefix}-search-${uniqueSuffix}' + identity: '${prefix}-identity-${uniqueSuffix}' + logAnalytics: '${prefix}-logs-${uniqueSuffix}' + privateEndpoint: '${prefix}-pe-${uniqueSuffix}' +} + +// Security configuration +var securityConfig = { + enablePublicAccess: false + tlsVersion: 'TLS1_2' + allowSharedKeyAccess: false + enableSoftDelete: true + softDeleteRetentionDays: 7 + enablePurgeProtection: true + diagnosticRetentionDays: 30 + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + } +} + +// Monitoring configuration +var monitoringConfig = { + metrics: { + enabled: true + retentionDays: 30 + } + logs: { + enabled: true + retentionDays: 30 + categories: { + audit: true + requests: true + operations: true + } + } +} + +// SKU configuration +var skuConfig = { + aiServices: { + name: 'S0' + tier: 'Standard' + } + aiSearch: { + name: 'standard' + tier: 'Standard' + } + keyVault: { + name: 'standard' + family: 'A' + } + storage: { + name: 'Standard_ZRS' + tier: 'Standard' + } +} + +// Output variables +output suffix string = uniqueSuffix +output tags object = commonTags +output naming object = namingPatterns +output network object = networkConfig +output security object = securityConfig +output monitoring object = monitoringConfig +output skus object = skuConfig diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/wait-script.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/wait-script.bicep new file mode 100644 index 00000000..39bd9934 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/common/wait-script.bicep @@ -0,0 +1,21 @@ +param name string +param location string +param retentionTime string = 'PT1H' // Retention duration + +resource waitScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { + name: name + location: location + kind: 'AzurePowerShell' + properties: { + azPowerShellVersion: '10.0' + scriptContent: ''' + Write-Output "Starting wait script..." + Start-Sleep -Seconds 120 + Write-Output "Wait completed. Proceeding with deployment..." + ''' + retentionInterval: retentionTime + cleanupPreference: 'Always' + } +} + +output scriptName string = waitScript.name diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/keyvault-role-assignments.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/keyvault-role-assignments.bicep new file mode 100644 index 00000000..3c522dff --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/keyvault-role-assignments.bicep @@ -0,0 +1,96 @@ +/* +Key Vault Role Assignments Module +------------------------------ +This module configures RBAC permissions for Azure Key Vault: + +1. Role Configuration: + - Key Vault Contributor (f25e0fa2-a7c8-4377-a976-54943a77a395) + * Manage Key Vault resources + * Cannot access secret content + - Key Vault Secrets Officer (b86a8fe4-44ce-4948-aee5-eccb2c155cd7) + * Full access to secrets + * Manage secret metadata + +2. Permissions Granted: + Key Vault Contributor: + - Manage vault properties + - Create/delete vaults + - Set access policies + + Key Vault Secrets Officer: + - Create/delete secrets + - Set secret values + - Configure secret attributes + +3. Security Considerations: + - Uses managed identity for authentication + - Follows principle of least privilege + - Separate roles for management vs content access + - Scoped to specific Key Vault instance +*/ + +/* -------------------------------------------- Parameters -------------------------------------------- */ + +@description('Name of the Key Vault') +param keyvaultName string + +@description('Principal ID of the managed identity') +param UAIPrincipalId string + +@description('Unique suffix for role assignment naming') +param suffix string + +/* -------------------------------------------- Resources -------------------------------------------- */ + +// Reference to existing Key Vault +resource keyvault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyvaultName + scope: resourceGroup() +} + +/* -------------------------------------------- Role Definitions -------------------------------------------- */ + +// Key Vault Secrets Officer Role +// Provides full access to manage secrets +resource keyVaultSecretOfficer 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' // Built-in role ID + scope: keyvault +} + +// Key Vault Contributor Role +// Provides access to manage vault but not secrets +resource keyVaultContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'f25e0fa2-a7c8-4377-a976-54943a77a395' // Built-in role ID + scope: resourceGroup() +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +// Assign Key Vault Contributor role +resource keyVaultContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: keyvault + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, keyVaultContributor.id, suffix) + properties: { + principalId: UAIPrincipalId // Managed identity principal ID + roleDefinitionId: keyVaultContributor.id // Contributor role + principalType: 'ServicePrincipal' // Identity type + } +} + +// Assign Key Vault Secrets Officer role +resource keyVaultSecretOfficerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: keyvault + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, keyVaultSecretOfficer.id, suffix) + properties: { + principalId: UAIPrincipalId // Managed identity principal ID + roleDefinitionId: keyVaultSecretOfficer.id // Secrets Officer role + principalType: 'ServicePrincipal' // Identity type + } +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +output contributorRoleId string = keyVaultContributorAssignment.id +output secretsOfficerRoleId string = keyVaultSecretOfficerAssignment.id diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-capability-host.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-capability-host.bicep new file mode 100644 index 00000000..c805cf76 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-capability-host.bicep @@ -0,0 +1,56 @@ +@description('AI hub name') +param aiHubName string + +@description('AI project name') +param aiProjectName string + +@description('Name for ACS connection.') +param acsConnectionName string + +@description('Name for ACS connection.') +param aoaiConnectionName string + +@description('Name for capabilityHost.') +param capabilityHostName string + +@description('Name for customer subnet id') +param customerSubnetId string = '' + +var storageConnections = ['${aiProjectName}/workspaceblobstore'] +var aiSearchConnection = ['${acsConnectionName}'] +var aiServiceConnections = ['${aoaiConnectionName}'] + +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' existing = { + name: aiHubName +} + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' existing = { + name: aiProjectName +} + +#disable-next-line BCP081 +resource hubCapabilityHost 'Microsoft.MachineLearningServices/workspaces/capabilityHosts@2024-10-01-preview' = { + name: '${aiHubName}-${capabilityHostName}' + parent: aiHub + properties: empty(customerSubnetId) ? { + capabilityHostKind: 'Agents' + }: { + capabilityHostKind: 'Agents' + customerSubnet: customerSubnetId + } +} + +#disable-next-line BCP081 +resource projectCapabilityHost 'Microsoft.MachineLearningServices/workspaces/capabilityHosts@2024-10-01-preview' = { + name: '${aiProjectName}-${capabilityHostName}' + parent: aiProject + properties: { + capabilityHostKind: 'Agents' + aiServicesConnections: aiServiceConnections + vectorStoreConnections: aiSearchConnection + storageConnections: storageConnections + } + dependsOn: [ + hubCapabilityHost + ] +} diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-hub.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-hub.bicep new file mode 100644 index 00000000..24aac3d3 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-hub.bicep @@ -0,0 +1,174 @@ +/* +Network-Secured AI Hub Module +--------------------------- +This module deploys an Azure AI Hub with network-secured configuration: + +1. Hub Architecture: + - Central orchestration point for AI services + - Manages connections to dependent services + - Provides network-isolated capability hosting + +2. Network Security: + - VNet integration for capability hosts + - Private endpoints for service access + - No public network exposure + - AAD-based authentication + +3. Service Connections: + - AI Services: For model inference and cognitive capabilities + - AI Search: For vector storage and search + - Key Vault: For secure secret management + - Storage: For data persistence + +4. Security Features: + - User-assigned managed identity + - RBAC-based access control + - Private networking for all connections +*/ + +/* -------------------------------------------- Parameters -------------------------------------------- */ + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI hub name') +param aiHubName string + +@description('AI hub display name') +param aiHubFriendlyName string = aiHubName + +@description('AI hub description') +param aiHubDescription string + +@description('Resource ID of the key vault for secure secret storage') +param keyVaultId string + +@description('Resource ID of the storage account for data persistence') +param storageAccountId string + +// AI Services Configuration +@description('Resource ID of the AI Services') +param aiServicesId string + +@description('Endpoint URL of the AI Services') +param aiServicesTarget string + +@description('Name of the AI Services resource') +param aiServicesName string + +@description('Resource Group containing AI Services') +param aiServiceAccountResourceGroupName string + +@description('Subscription ID containing AI Services') +param aiServiceAccountSubscriptionId string + +// AI Search Configuration +@description('Name of the AI Search service') +param aiSearchName string + +@description('Resource ID of the AI Search service') +param aiSearchId string + +@description('Resource Group containing AI Search') +param aiSearchServiceResourceGroupName string + +@description('Subscription ID containing AI Search') +param aiSearchServiceSubscriptionId string + +@description('Name of the user-assigned managed identity') +param uaiName string + + +@description('Flag indicating if the hub already exists') +param aiHubExists bool = false + +/* -------------------------------------------- Variables -------------------------------------------- */ + +// Connection names for service integration +var acsConnectionName = '${aiHubName}-connection-AISearch' +var aoaiConnection = '${aiHubName}-connection-AIServices_aoai' + +/* -------------------------------------------- Resources -------------------------------------------- */ + +// Reference to managed identity +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = { + name: uaiName +} +var userAssignedIdentities = json('{\'${userAssignedIdentity.id}\': {}}') + +// Reference to existing services +resource aiServices 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { + name: aiServicesName + scope: resourceGroup(aiServiceAccountSubscriptionId, aiServiceAccountResourceGroupName) +} + +resource searchService 'Microsoft.Search/searchServices@2023-11-01' existing = { + name: aiSearchName + scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) +} + +// AI Hub Workspace +// Documentation: https://learn.microsoft.com/en-us/azure/templates/microsoft.machinelearningservices/workspaces +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' = if(!aiHubExists) { + name: aiHubName + location: location + kind: 'hub' + tags: tags + identity: { + type: 'UserAssigned' // Use managed identity for authentication + userAssignedIdentities: userAssignedIdentities + } + properties: { + // Organization metadata + friendlyName: aiHubFriendlyName + description: aiHubDescription + primaryUserAssignedIdentity: userAssignedIdentity.id + + // Core service connections + keyVault: keyVaultId // For secret management + storageAccount: storageAccountId // For data persistence + } + + // AI Services Connection + // Documentation: https://learn.microsoft.com/en-us/azure/templates/microsoft.machinelearningservices/workspaces/connections + resource aiServicesConnection 'connections@2024-07-01-preview' = { + name: '${aiHubName}-connection-AIServices' + properties: { + category: 'AIServices' + target: aiServicesTarget // Service endpoint + authType: 'AAD' // Use Azure AD auth + isSharedToAll: true // Available to all projects + metadata: { + ApiType: 'Azure' + ResourceId: aiServicesId + location: aiServices.location + } + } + } + + // AI Search Connection + resource hub_connection_azureai_search 'connections@2024-07-01-preview' = { + name: acsConnectionName + properties: { + category: 'CognitiveSearch' + target: 'https://${aiSearchName}.search.windows.net' + authType: 'AAD' // Use Azure AD auth + isSharedToAll: true // Available to all projects + metadata: { + ApiType: 'Azure' + ResourceId: aiSearchId + location: searchService.location + } + } + } +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +output aiHubID string = aiHub.id +output aiHubName string = aiHub.name +output aoaiConnectionName string = aoaiConnection +output acsConnectionName string = acsConnectionName diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-project.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-project.bicep new file mode 100644 index 00000000..7b3a63de --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-ai-project.bicep @@ -0,0 +1,98 @@ +/* +Network-Secured AI Project Module +------------------------------- +This module deploys an Azure AI Project workspace with network-secured configuration: + +1. Project Configuration: + - Creates an AI Project workspace with managed identity + - Configures capability host for Agents functionality + - Sets up secure connections to dependent services + +2. Security Features: + - Uses user-assigned managed identity for authentication + - Integrates with network-secured AI Hub + - Configures private connections to storage and services + +3. Service Connections: + - Storage: For data persistence + - AI Search: For vector storage and search capabilities + - AI Services: For model inference and cognitive capabilities + +4. Network Security: + - All connections use private endpoints + - No public internet exposure + - Secure service-to-service communication +*/ + +/* -------------------------------------------- Parameters -------------------------------------------- */ + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string + +@description('Resource ID of the AI Hub resource') +param aiHubId string + + +@description('Name of the user-assigned managed identity') +param uaiName string + +/* -------------------------------------------- Variables -------------------------------------------- */ + +// Connection string components +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name +var projectConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${aiProjectName}' + +/* -------------------------------------------- Resources -------------------------------------------- */ + +// Reference to user-assigned managed identity +resource uai 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' existing = { + name: uaiName + scope: resourceGroup() +} + +// AI Project Workspace +// Documentation: https://learn.microsoft.com/en-us/azure/templates/microsoft.machinelearningservices/workspaces +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' = { + name: aiProjectName + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString // Store connection string in tags for easy access + }) + identity: { + type: 'UserAssigned' // Use managed identity for authentication + userAssignedIdentities: { + '${uai.id}': {} + } + } + properties: { + // Organization metadata + friendlyName: aiProjectFriendlyName + description: aiProjectDescription + primaryUserAssignedIdentity: uai.id + + // Hub integration + hubResourceId: aiHubId // Link to network-secured AI Hub + } + kind: 'project' +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +// Project identifiers +output aiProjectName string = aiProject.name +output aiProjectResourceId string = aiProject.id +output aiProjectWorkspaceId string = aiProject.properties.workspaceId +output projectConnectionString string = aiProject.tags.ProjectConnectionString diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-dependent-resources.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-dependent-resources.bicep new file mode 100644 index 00000000..ab269ead --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-dependent-resources.bicep @@ -0,0 +1,369 @@ +/* +Network-Secured Dependencies Module +--------------------------------- +This module deploys core infrastructure components with network security controls: + +1. Virtual Network Architecture: + - Address space: 172.16.0.0/16 + - Customer Hub subnet: 172.16.0.0/24 (for private endpoints) + - Agents subnet: 172.16.101.0/24 (for container apps) + +2. Network Security Features: + - Service endpoints for secure Azure service access + - Network ACLs to restrict access + - Private endpoints for secure communication + - Disabled public network access + +3. Subnet Configuration: + - Customer Hub subnet: Hosts private endpoints and service endpoints + - Agents subnet: Delegated to container apps with specific CIDR range +*/ + +param storageExists bool = false +param keyvaultExists bool = false +param aiServicesExists bool = false +param aiSearchExists bool = false + +@description('Azure region of the deployment') +param location string = resourceGroup().location + +@description('Tags to add to the resources') +param tags object = {} + +@description('Unique suffix for resource names') +param suffix string + +@description('AI services name') +param aiServicesName string + +@description('The name of the Key Vault') +param keyvaultName string + +@description('The name of the AI Search resource') +param aiSearchName string + +@description('Name of the storage account') +param storageName string + +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('Model/AI Resource deployment location') +param modelLocation string + +@description('The Kind of AI Service, can be "OpenAI" or "AIService"') +param aisKind string + +// Network Resource Names +@description('The name of the virtual network') +param vnetName string = 'agents-vnet-${suffix}' + +@description('The name of Agents Subnet for container apps') +param agentsSubnetName string = 'agents-subnet-${suffix}' + +@description('The name of Customer Hub subnet for private endpoints') +param cxSubnetName string = 'hub-subnet-${suffix}' + +param userAssignedIdentityName string + +// Subnet reference variables for network rules +var cxSubnetRef = resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, cxSubnetName) +var agentSubnetRef = resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, agentsSubnetName) + +// User-assigned managed identity for secure access +resource uai 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { + location: location + name: userAssignedIdentityName +} + +/* -------------------------------------------- Virtual Network Resources -------------------------------------------- */ + +// Virtual Network with segregated subnets and security controls +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { + name: vnetName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '172.16.0.0/16' // Main VNet CIDR + ] + } + subnets: [ + { + name: cxSubnetName + properties: { + addressPrefix: '172.16.0.0/24' // Customer Hub subnet CIDR + serviceEndpoints: [ // Secure service access + { + service: 'Microsoft.KeyVault' + locations: [ + location + ] + } + { + service: 'Microsoft.Storage' + locations: [ + location + ] + } + { + service: 'Microsoft.CognitiveServices' + locations: [ + modelLocation + ] + } + ] + } + } + { + name: agentsSubnetName + properties: { + addressPrefix: '172.16.101.0/24' // Agents subnet CIDR + delegations: [ + { + name: 'Microsoft.app/environments' + properties: { + serviceName: 'Microsoft.app/environments' + } + } + ] + } + } + ] + } + dependsOn: [ + uai + ] +} + +/* -------------------------------------------- Existing Resource References -------------------------------------------- */ + +resource existingStorage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = if(storageExists) { + name: storageName + scope: resourceGroup() +} + +resource existingKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if(keyvaultExists) { + name: keyvaultName + scope: resourceGroup() +} + +resource existingAiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = if(aiServicesExists) { + name: aiServicesName + scope: resourceGroup() +} + +resource existingAiSearch 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = if(aiSearchExists) { + name: aiSearchName + scope: resourceGroup() +} + +/* -------------------------------------------- Network-Secured Resources -------------------------------------------- */ + +// Key Vault with network security controls +resource defaultKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' = if(!keyvaultExists) { + name: keyvaultName + location: location + tags: tags + properties: { + createMode: 'default' + enabledForDeployment: true + enabledForDiskEncryption: false + enabledForTemplateDeployment: true + enableSoftDelete: true + enableRbacAuthorization: true + enablePurgeProtection: true + publicNetworkAccess: 'Disabled' // Block public access + accessPolicies: [ + { + tenantId: subscription().tenantId + objectId: uai.properties.principalId + permissions: { secrets: [ 'set', 'get', 'list', 'delete', 'purge' ] } + } + ] + networkAcls: { + bypass: 'AzureServices' // Allow trusted Azure services + defaultAction: 'Deny' // Deny all other traffic + virtualNetworkRules:[ // Allow access from customer hub subnet + { + id: virtualNetwork.properties.subnets[0].id + } + ] + } + sku: { + family: 'A' + name: 'standard' + } + softDeleteRetentionInDays: 7 + tenantId: subscription().tenantId + } +} + +// AI Services with network security controls +resource defaultAiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' = if(!aiServicesExists) { + name: aiServicesName + location: modelLocation + sku: { + name: 'S0' + } + kind: aisKind + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower('${(aiServicesName)}') + apiProperties: { + statisticsEnabled: false + } + networkAcls: { + bypass: 'AzureServices' // Allow trusted Azure services + defaultAction: 'Deny' // Deny all other traffic + virtualNetworkRules:[ // Allow access from customer hub subnet + { + id: virtualNetwork.properties.subnets[0].id + } + ] + } + publicNetworkAccess: 'Disabled' // Block public access + } +} + +// AI Model deployment +resource defaultModelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-06-01-preview'= if(!aiServicesExists){ + parent: defaultAiServices + name: modelName + sku : { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model:{ + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +// AI Search with network security controls +resource defaultAiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = if(!aiSearchExists) { + name: aiSearchName + location: location + tags: tags + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${uai.id}': {} + } + } + properties: { + disableLocalAuth: false + authOptions: { aadOrApiKey: { aadAuthFailureMode: 'http401WithBearerChallenge' }} + encryptionWithCmk: { + enforcement: 'Unspecified' + } + hostingMode: 'default' + partitionCount: 1 + publicNetworkAccess: 'Disabled' // Block public access + replicaCount: 1 + semanticSearch: 'disabled' + } + sku: { + name: 'standard' + } +} + +// Storage Account with network security controls +param noZRSRegions array = ['southindia', 'westus'] +param sku object = contains(noZRSRegions, location) ? { name: 'Standard_GRS' } : { name: 'Standard_ZRS' } +var storageNameCleaned = storageExists ? existingStorage.name : replace(storageName, '-', '') + +resource defaultStorage 'Microsoft.Storage/storageAccounts@2022-05-01' = if(!storageExists){ + name: storageNameCleaned + location: location + kind: 'StorageV2' + sku: sku + properties: { + minimumTlsVersion: 'TLS1_2' // Enforce TLS 1.2 + allowBlobPublicAccess: false // Prevent public blob access + publicNetworkAccess: 'Disabled' // Block public access + networkAcls: { + bypass: 'AzureServices' // Allow trusted Azure services + defaultAction: 'Deny' // Deny all other traffic + virtualNetworkRules: [ // Allow access from customer hub subnet + { + id: cxSubnetRef + } + ] + } + allowSharedKeyAccess: false // Enforce Azure AD authentication + } + dependsOn: [ + virtualNetwork + ] +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +module storageAccessAssignment './storage-role-assignments.bicep' = if(!storageExists){ + name: 'dependencies-${suffix}-storage-rbac' + params: { + suffix: suffix + storageName: storageNameCleaned + UAIPrincipalId: uai.properties.principalId + } + dependsOn: [ defaultStorage ] +} + +module keyVaultAccessAssignment './keyvault-role-assignments.bicep' = if(!keyvaultExists){ + name: 'dependencies-${suffix}-keyvault-rbac' + params: { + suffix: suffix + keyvaultName: keyvaultName + UAIPrincipalId: uai.properties.principalId + } + dependsOn: [ defaultKeyVault ] +} + +/* -------------------------------------------- Output Variables -------------------------------------------- */ + +var aiServiceParts = aiServicesExists ? split(existingAiServices.id, '/') : split(defaultAiServices.id, '/') +var acsParts = aiSearchExists ? split(existingAiSearch.id, '/') : split(defaultAiSearch.id, '/') +var storageParts = storageExists ? split(existingStorage.id, '/') : split(defaultStorage.id, '/') + +output aiServicesName string = aiServicesExists ? existingAiServices.name : defaultAiServices.name +output aiservicesID string = aiServicesExists ? existingAiServices.id : defaultAiServices.id +output aiservicesTarget string = aiServicesExists ? existingAiServices.properties.endpoint : defaultAiServices.properties.endpoint +output aiServiceAccountResourceGroupName string = aiServiceParts[4] +output aiServiceAccountSubscriptionId string = aiServiceParts[2] + +output aiSearchName string = aiSearchExists ? existingAiSearch.name : defaultAiSearch.name +output aisearchID string = aiSearchExists ? existingAiSearch.id : defaultAiSearch.id +output aiSearchServiceResourceGroupName string = acsParts[4] +output aiSearchServiceSubscriptionId string = acsParts[2] + +output storageAccountName string = storageExists ? existingStorage.name : storageName +output storageId string = storageExists ? existingStorage.id : defaultStorage.id +output storageAccountResourceGroupName string = storageParts[4] +output storageAccountSubscriptionId string = storageParts[2] + +output virtualNetworkName string = virtualNetwork.name +output virtualNetworkId string = virtualNetwork.id +output cxSubnetName string = cxSubnetName +output agentSubnetName string = agentsSubnetName +output cxSubnetId string = cxSubnetRef +output agentSubnetId string = agentSubnetRef + +output keyvaultId string = keyvaultExists ? existingKeyVault.id : defaultKeyVault.id diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-identity.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-identity.bicep new file mode 100644 index 00000000..991204cb --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/network-secured-identity.bicep @@ -0,0 +1,57 @@ +/* +Network-Secured Identity Module +----------------------------- +This module manages the User-Assigned Managed Identity (UAI) for the network-secured deployment: + +1. Purpose: + - Provides a managed identity for secure service-to-service authentication + - Enables zero-trust security model with no credential storage + - Used by AI services to access other Azure resources + +2. Features: + - Supports new identity creation or existing identity reference + - Provides identity properties through outputs + - Enables RBAC-based access control + +3. Security Benefits: + - No password/secret management required + - Platform-managed credential rotation + - Granular access control through RBAC +*/ + +@description('Azure region for resource deployment') +param location string + +@description('Name of the user-assigned managed identity') +param userAssignedIdentityName string + +@description('Flag indicating if the identity already exists') +param uaiExists bool = false + +/* -------------------------------------------- Identity Resources -------------------------------------------- */ + +// Reference existing identity if specified +resource existingUAI 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' existing = if(uaiExists) { + name: userAssignedIdentityName + scope: resourceGroup() +} + +// Create new identity if it doesn't exist +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { + location: location + name: userAssignedIdentityName +} + +/* -------------------------------------------- Outputs -------------------------------------------- */ + +// Identity name for reference by other resources +output uaiName string = uaiExists ? existingUAI.name : userAssignedIdentity.name + +// Full resource ID for RBAC assignments +output uaiId string = uaiExists ? existingUAI.id : userAssignedIdentity.id + +// Principal ID for role assignments +output uaiPrincipalId string = uaiExists ? existingUAI.properties.principalId : userAssignedIdentity.properties.principalId + +// Client ID for service authentication +output uaiClientId string = uaiExists ? existingUAI.properties.clientId : userAssignedIdentity.properties.clientId diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/networking/vnet.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/networking/vnet.bicep new file mode 100644 index 00000000..9ac1b645 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/networking/vnet.bicep @@ -0,0 +1,100 @@ +/* +Virtual Network Module +--------------------- +This module deploys the core network infrastructure with security controls: + +1. Address Space: + - VNet CIDR: 172.16.0.0/16 + - Hub Subnet: 172.16.0.0/24 (private endpoints) + - Agents Subnet: 172.16.101.0/24 (container apps) + +2. Security Features: + - Service endpoints + - Network isolation + - Subnet delegation +*/ + +@description('Azure region for the deployment') +param location string + +@description('Tags to apply to resources') +param tags object = {} + +@description('Unique suffix for resource names') +param suffix string + +@description('The name of the virtual network') +param vnetName string = 'agents-vnet-${suffix}' + +@description('The name of Agents Subnet') +param agentsSubnetName string = 'agents-subnet-${suffix}' + +@description('The name of Hub subnet') +param hubSubnetName string = 'hub-subnet-${suffix}' + +@description('Model/AI Resource deployment location') +param modelLocation string + +// Virtual Network with segregated subnets +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { + name: vnetName + location: location + tags: tags + properties: { + addressSpace: { + addressPrefixes: [ + '172.16.0.0/16' + ] + } + subnets: [ + { + name: hubSubnetName + properties: { + addressPrefix: '172.16.0.0/24' + serviceEndpoints: [ + { + service: 'Microsoft.KeyVault' + locations: [ + location + ] + } + { + service: 'Microsoft.Storage' + locations: [ + location + ] + } + { + service: 'Microsoft.CognitiveServices' + locations: [ + modelLocation + ] + } + ] + } + } + { + name: agentsSubnetName + properties: { + addressPrefix: '172.16.101.0/24' + delegations: [ + { + name: 'Microsoft.app/environments' + properties: { + serviceName: 'Microsoft.app/environments' + } + } + ] + } + } + ] + } +} + +// Output variables +output virtualNetworkName string = virtualNetwork.name +output virtualNetworkId string = virtualNetwork.id +output hubSubnetName string = hubSubnetName +output agentsSubnetName string = agentsSubnetName +output hubSubnetId string = '${virtualNetwork.id}/subnets/${hubSubnetName}' +output agentsSubnetId string = '${virtualNetwork.id}/subnets/${agentsSubnetName}' diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/private-endpoint-and-dns.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/private-endpoint-and-dns.bicep new file mode 100644 index 00000000..b8d9e5c7 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/private-endpoint-and-dns.bicep @@ -0,0 +1,299 @@ +/* +Private Endpoint and DNS Configuration Module +------------------------------------------ +This module configures private network access for Azure services using: + +1. Private Endpoints: + - Creates network interfaces in the specified subnet + - Establishes private connections to Azure services + - Enables secure access without public internet exposure + +2. Private DNS Zones: + - privatelink.azureml.ms for AI Services + - privatelink.search.windows.net for AI Search + - privatelink.blob.core.windows.net for Storage + - Enables custom DNS resolution for private endpoints + +3. DNS Zone Links: + - Links private DNS zones to the VNet + - Enables name resolution for resources in the VNet + - Prevents DNS resolution conflicts + +Security Benefits: +- Eliminates public internet exposure +- Enables secure access from within VNet +- Prevents data exfiltration through network +*/ + +// Resource names and identifiers +param aiServicesName string +param aiSearchName string +param storageName string +param vnetName string +param cxSubnetName string +param suffix string +param aiStorageId string + +// Reference existing services that need private endpoints +resource aiServices 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { + name: aiServicesName + scope: resourceGroup() +} + +resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = { + name: aiSearchName + scope: resourceGroup() +} + +// Reference existing network resources +resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { + name: vnetName + scope: resourceGroup() +} + +resource cxSubnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' existing = { + parent: vnet + name: cxSubnetName +} + +/* -------------------------------------------- AI Services Private Endpoint -------------------------------------------- */ + +// Private endpoint for AI Services +// - Creates network interface in customer hub subnet +// - Establishes private connection to AI Services account +resource aiServicesPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { + name: '${aiServicesName}-private-endpoint' + location: resourceGroup().location + properties: { + subnet: { + id: cxSubnet.id // Deploy in customer hub subnet + } + privateLinkServiceConnections: [ + { + name: '${aiServicesName}-private-link-service-connection' + properties: { + privateLinkServiceId: aiServices.id + groupIds: [ + 'account' // Target AI Services account + ] + } + } + ] + } +} + +resource aiServiceOpenAiPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { + name: '${aiServicesName}-openAi-private-endpoint' + location: resourceGroup().location + properties: { + subnet: { + id: cxSubnet.id // Deploy in customer hub subnet + } + privateLinkServiceConnections: [ + { + name: '${aiServicesName}-openAi-private-link-service-connection' + properties: { + privateLinkServiceId: aiServices.id + groupIds: [ + 'account' // Target AI Services account + ] + } + } + ] + } +} + +/* -------------------------------------------- AI Search Private Endpoint -------------------------------------------- */ + +// Private endpoint for AI Search +// - Creates network interface in customer hub subnet +// - Establishes private connection to AI Search service +resource aiSearchPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { + name: '${aiSearchName}-private-endpoint' + location: resourceGroup().location + properties: { + subnet: { + id: cxSubnet.id // Deploy in customer hub subnet + } + privateLinkServiceConnections: [ + { + name: '${aiSearchName}-private-link-service-connection' + properties: { + privateLinkServiceId: aiSearch.id + groupIds: [ + 'searchService' // Target search service + ] + } + } + ] + } +} + +/* -------------------------------------------- Storage Private Endpoint -------------------------------------------- */ + +// Private endpoint for Storage Account +// - Creates network interface in customer hub subnet +// - Establishes private connection to blob storage +resource storagePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { + name: '${storageName}-private-endpoint' + location: resourceGroup().location + properties: { + subnet: { + id: cxSubnet.id // Deploy in customer hub subnet + } + privateLinkServiceConnections: [ + { + name: '${storageName}-private-link-service-connection' + properties: { + privateLinkServiceId: aiStorageId + groupIds: [ + 'blob' // Target blob storage + ] + } + } + ] + } +} + +/* -------------------------------------------- Private DNS Zones -------------------------------------------- */ + +// Private DNS Zone for AI Services +// - Enables custom DNS resolution for AI Services private endpoint +resource aiServicesPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.azureml.ms' // Standard DNS zone for AI Services + location: 'global' +} + +resource openAiPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.openai.azure.com' + location: 'global' +} + +// Link AI Services DNS Zone to VNet +resource aiServicesLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + parent: aiServicesPrivateDnsZone + location: 'global' + name: 'aiServices-${suffix}-link' + properties: { + virtualNetwork: { + id: vnet.id // Link to specified VNet + } + registrationEnabled: false // Don't auto-register VNet resources + } +} + +resource aiOpenAILink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + parent: openAiPrivateDnsZone + location: 'global' + name: 'aiServicesOpenAI-${suffix}-link' + properties: { + virtualNetwork: { + id: vnet.id // Link to specified VNet + } + registrationEnabled: false // Don't auto-register VNet resources + } +} + +// DNS Zone Group for AI Services +resource aiServicesDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { + parent: aiServicesPrivateEndpoint + name: '${aiServicesName}-dns-group' + properties: { + privateDnsZoneConfigs: [ + { + name: '${aiServicesName}-dns-config' + properties: { + privateDnsZoneId: aiServicesPrivateDnsZone.id + } + } + ] + } +} + +// DNS Zone Group for Azure OpenAI +resource aiOpenAIDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { + parent: aiServiceOpenAiPrivateEndpoint + name: '${aiServicesName}-openAi-dns-group' + properties: { + privateDnsZoneConfigs: [ + { + name: '${aiServicesName}-openAi-dns-config' + properties: { + privateDnsZoneId: openAiPrivateDnsZone.id + } + } + ] + } +} + +// Private DNS Zone for AI Search +// - Enables custom DNS resolution for AI Search private endpoint +resource aiSearchPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.search.windows.net' // Standard DNS zone for AI Search + location: 'global' +} + +// Link AI Search DNS Zone to VNet +resource aiSearchLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + parent: aiSearchPrivateDnsZone + location: 'global' + name: 'aiSearch-${suffix}-link' + properties: { + virtualNetwork: { + id: vnet.id // Link to specified VNet + } + registrationEnabled: false // Don't auto-register VNet resources + } +} + +// DNS Zone Group for AI Search +resource aiSearchDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { + parent: aiSearchPrivateEndpoint + name: '${aiSearchName}-dns-group' + properties: { + privateDnsZoneConfigs: [ + { + name: '${aiSearchName}-dns-config' + properties: { + privateDnsZoneId: aiSearchPrivateDnsZone.id + } + } + ] + } +} + +// Private DNS Zone for Storage +// - Enables custom DNS resolution for blob storage private endpoint +resource storagePrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.blob.${environment().suffixes.storage}' // Dynamic DNS zone for storage + location: 'global' +} + +// Link Storage DNS Zone to VNet +resource storageLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + parent: storagePrivateDnsZone + location: 'global' + name: 'storage-${suffix}-link' + properties: { + virtualNetwork: { + id: vnet.id // Link to specified VNet + } + registrationEnabled: false // Don't auto-register VNet resources + } +} + +// DNS Zone Group for Storage +resource storageDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { + parent: storagePrivateEndpoint + name: '${storageName}-dns-group' + properties: { + privateDnsZoneConfigs: [ + { + name: '${storageName}-dns-config' + properties: { + privateDnsZoneId: storagePrivateDnsZone.id + } + } + ] + } +} diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/security/keyvault-config.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/security/keyvault-config.bicep new file mode 100644 index 00000000..4b701bcc --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/security/keyvault-config.bicep @@ -0,0 +1,122 @@ +/* +Key Vault Configuration Module +---------------------------- +This module deploys a Key Vault with network security controls: + +1. Security Features: + - RBAC authorization + - Network ACLs + - Private networking + - Soft delete enabled + +2. Access Controls: + - Azure AD authentication + - VNet integration + - Service endpoint access +*/ + +@description('Azure region for the deployment') +param location string + +@description('Tags to apply to resources') +param tags object = {} + +@description('The name of the Key Vault') +param keyvaultName string + +@description('Principal ID of the managed identity') +param principalId string + +@description('ID of the subnet for network rules') +param subnetId string + +@description('Whether to enable public network access') +param enablePublicNetworkAccess bool = false + +// Key Vault with network security controls +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyvaultName + location: location + tags: tags + properties: { + createMode: 'default' + enabledForDeployment: true + enabledForDiskEncryption: false + enabledForTemplateDeployment: true + enableSoftDelete: true + enableRbacAuthorization: true + enablePurgeProtection: true + publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled' + accessPolicies: [ + { + tenantId: subscription().tenantId + objectId: principalId + permissions: { + secrets: [ + 'set' + 'get' + 'list' + 'delete' + 'purge' + ] + } + } + ] + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + id: subnetId + } + ] + } + sku: { + family: 'A' + name: 'standard' + } + softDeleteRetentionInDays: 7 + tenantId: subscription().tenantId + } +} + +// Diagnostic settings for Key Vault +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: '${keyvaultName}-diagnostics' + scope: keyVault + properties: { + logs: [ + { + category: 'AuditEvent' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + { + category: 'AzurePolicyEvaluationDetails' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + } +} + +// Output variables +output keyVaultName string = keyVault.name +output keyVaultId string = keyVault.id +output keyVaultUri string = keyVault.properties.vaultUri diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage-role-assignments.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage-role-assignments.bicep new file mode 100644 index 00000000..3b3ae0c9 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage-role-assignments.bicep @@ -0,0 +1,71 @@ +/* +Storage Role Assignments Module +----------------------------- +This module configures RBAC permissions for Storage Account access: + +1. Role Assignments: + - Storage Blob Data Owner: Full access to blob containers and data + - Storage Queue Data Contributor: Read/write access to queues + +2. Unique Assignment Names: + - Uses subscription and resource group IDs + - Includes deployment-specific suffix + - Prevents conflicts in multi-deployment scenarios +*/ + +@description('Name of the storage account') +param storageName string + +@description('Principal ID of the managed identity') +param UAIPrincipalId string + +@description('Unique suffix for resource naming') +param suffix string = '' + +// Reference existing storage account +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { + name: storageName + scope: resourceGroup() +} + +/* -------------------------------------------- Role Definitions -------------------------------------------- */ + +// Storage Blob Data Owner Role +// Provides full access to Azure Storage blob containers and data +resource storageBlobDataOwner 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' // Built-in role ID + scope: resourceGroup() +} + +// Storage Queue Data Contributor Role +// Provides read/write access to Azure Storage queues and messages +resource storageQueueDataContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '974c5e8b-45b9-4653-ba55-5f855dd0fb88' // Built-in role ID + scope: resourceGroup() +} + +/* -------------------------------------------- Role Assignments -------------------------------------------- */ + +// Assign Storage Blob Data Owner role +resource storageBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: storage + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, storageBlobDataOwner.id, suffix) + properties: { + principalId: UAIPrincipalId + roleDefinitionId: storageBlobDataOwner.id + principalType: 'ServicePrincipal' + } +} + +// Assign Storage Queue Data Contributor role +resource storageQueueDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: storage + // Use subscription ID and resource group ID to ensure uniqueness across deployments + name: guid(subscription().subscriptionId, resourceGroup().id, storageQueueDataContributor.id, suffix) + properties: { + principalId: UAIPrincipalId + roleDefinitionId: storageQueueDataContributor.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage/storage-config.bicep b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage/storage-config.bicep new file mode 100644 index 00000000..42ed0b17 --- /dev/null +++ b/scenarios/Agents/setup/network-secured-agent/modules-network-secured/storage/storage-config.bicep @@ -0,0 +1,167 @@ +/* +Storage Configuration Module +-------------------------- +This module deploys a storage account with network security controls: + +1. Security Features: + - Network ACLs + - Private networking + - TLS enforcement + - Azure AD authentication + +2. Storage Configuration: + - ZRS/GRS replication + - Blob service settings + - Diagnostic settings +*/ + +@description('Azure region for the deployment') +param location string + +@description('Tags to apply to resources') +param tags object = {} + +@description('The name of the storage account') +param storageName string + +@description('ID of the subnet for network rules') +param subnetId string + +@description('Whether to enable public network access') +param enablePublicNetworkAccess bool = false + +@description('Log Analytics workspace ID for diagnostics') +param logAnalyticsWorkspaceId string = '' + +// Regions without ZRS support +param noZRSRegions array = [ + 'southindia' + 'westus' +] + +// Determine SKU based on location +var sku = contains(noZRSRegions, location) ? { name: 'Standard_GRS' } : { name: 'Standard_ZRS' } + +// Storage Account with network security controls +resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: replace(storageName, '-', '') // Remove hyphens for storage naming rules + location: location + tags: tags + kind: 'StorageV2' + sku: sku + properties: { + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + id: subnetId + } + ] + } + allowSharedKeyAccess: false + supportsHttpsTrafficOnly: true + encryption: { + services: { + blob: { + enabled: true + } + file: { + enabled: true + } + queue: { + enabled: true + } + table: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + } + } +} + +// Blob service configuration +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2022-05-01' = { + parent: storageAccount + name: 'default' + properties: { + deleteRetentionPolicy: { + enabled: true + days: 7 + } + containerDeleteRetentionPolicy: { + enabled: true + days: 7 + } + isVersioningEnabled: true + changeFeed: { + enabled: true + retentionInDays: 7 + } + } +} + +// Diagnostic settings +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(logAnalyticsWorkspaceId)) { + name: '${storageAccount.name}-diagnostics' + scope: storageAccount + properties: { + workspaceId: logAnalyticsWorkspaceId + metrics: [ + { + category: 'Transaction' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + { + category: 'Capacity' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + logs: [ + { + category: 'StorageRead' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + { + category: 'StorageWrite' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + { + category: 'StorageDelete' + enabled: true + retentionPolicy: { + days: 30 + enabled: true + } + } + ] + } +} + +// Output variables +output storageAccountName string = storageAccount.name +output storageAccountId string = storageAccount.id +output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob +output queueEndpoint string = storageAccount.properties.primaryEndpoints.queue +output tableEndpoint string = storageAccount.properties.primaryEndpoints.table +output fileEndpoint string = storageAccount.properties.primaryEndpoints.file diff --git a/scenarios/Agents/setup/standard-agent/README.md b/scenarios/Agents/setup/standard-agent/README.md new file mode 100644 index 00000000..74ec5890 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/README.md @@ -0,0 +1,51 @@ +--- +description: This set of templates demonstrates how to set up Azure AI Agent Service with the standard setup, meaning with managed identity authentication for project/hub connections and public internet access enabled. Agents use customer-owned, single-tenant search and storage resources. With this setup, you have full control and visibility over these resources, but you will incur costs based on your usage. +page_type: sample +products: +- azure +- azure-resource-manager +urlFragment: standard-agent +languages: +- bicep +- json +--- +# Standard Agent Setup + +![Azure Public Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/PublicLastTestDate.svg) +![Azure Public Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/PublicDeployment.svg) + +![Azure US Gov Last Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/FairfaxLastTestDate.svg) +![Azure US Gov Last Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/FairfaxDeployment.svg) + +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/BestPracticeResult.svg) +![Cred Scan Check](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/CredScanResult.svg) + +![Bicep Version](https://azurequickstartsservice.blob.core.windows.net/badges/quickstarts/microsoft.azure-ai-agent-service/standard-agent/BicepVersion.svg) + +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fdharakumarmsft%2Fazureai-samples%2Fscenario%2FAgents%2Fscenarios%2FAgents%2Fsetup%2Fstandard-agent%2Fazuredeploy.json) + +[![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.svg?sanitize=true)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2Fdharakumarmsft%2Fazureai-samples%2Fscenario%2FAgents%2Fscenarios%2FAgents%2Fsetup%2Fstandard-agent%2Fazuredeploy.json) + +Resources for the hub, project, storage account, key vault, AI Services, and Azure AI Search will be created for you. The AI Services, AI Search, and Azure Blob Storage account will be connected to your project/hub using managed identity for authentication and a gpt-4o-mini model will be deployed in the eastus region. + +Optional use an existing AI Services/AOAI, AI Search, and/or Azure Blob Storage resource by providing the full arm resource id in the parameters file: + +- aiServiceAccountResourceId +- aiSearchServiceResourceId +- aiStorageAccountResourceId + +If you want to use an existing Azure OpenAI resource, you will need to update the `aiServiceAccountResourceId` and the `aiServiceKind` parameter in the parameters file. The `aiServiceKind` parameter should be set to `AzureOpenAI`. + +## Resources + +| Provider and type | Description | +| - | - | +| `Microsoft.Resources/resourceGroups` | The resource group all resources get deployed into | +| `Microsoft.KeyVault/vaults` | An Azure Key Vault instance associated to the Azure Machine Learning workspace | +| `Microsoft.Storage/storageAccounts` | An Azure Storage instance associated to the Azure Machine Learning workspace | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI hub (Azure Machine Learning RP workspace of kind 'hub') | +| `Microsoft.MachineLearningServices/workspaces` | An Azure AI project (Azure Machine Learning RP workspace of kind 'project') | +| `Microsoft.CognitiveServices/accounts` | An Azure AI Services as the model-as-a-service endpoint provider (allowed kinds: 'AIServices' and 'OpenAI') | +| `Microsoft.CognitiveServices/accounts/deployments` | A gpt-4o-mini model is deployed | +| `Microsoft.Search/searchServices` | An Azure AI Search account | +`Tags: ` \ No newline at end of file diff --git a/scenarios/Agents/setup/standard-agent/azuredeploy.json b/scenarios/Agents/setup/standard-agent/azuredeploy.json new file mode 100644 index 00000000..0341b50d --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/azuredeploy.json @@ -0,0 +1,1305 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "1349409650835059152" + } + }, + "parameters": { + "aiHubName": { + "type": "string", + "defaultValue": "hub-demo", + "minLength": 2, + "maxLength": 12, + "metadata": { + "description": "Name for the AI resource and used to derive name of dependent resources." + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "Agents Hub resource", + "metadata": { + "description": "Friendly name for your Hub resource" + } + }, + "aiHubDescription": { + "type": "string", + "defaultValue": "This is an example AI resource for use in Azure AI Studio.", + "metadata": { + "description": "Description of your Azure AI resource displayed in AI studio" + } + }, + "aiProjectName": { + "type": "string", + "defaultValue": "project-demo", + "metadata": { + "description": "Name for the AI project resources." + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "Agents Project resource", + "metadata": { + "description": "Friendly name for your Azure AI resource" + } + }, + "aiProjectDescription": { + "type": "string", + "defaultValue": "This is an example AI Project resource for use in Azure AI Studio.", + "metadata": { + "description": "Description of your Azure AI resource displayed in AI studio" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region used for the deployment of all resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Set of tags to apply to all resources." + } + }, + "aiSearchName": { + "type": "string", + "defaultValue": "agent-ai-search", + "metadata": { + "description": "Name of the Azure AI Search account" + } + }, + "capabilityHostName": { + "type": "string", + "defaultValue": "caphost1", + "metadata": { + "description": "Name for capabilityHost." + } + }, + "storageName": { + "type": "string", + "defaultValue": "agent-storage", + "metadata": { + "description": "Name of the storage account" + } + }, + "aiServicesName": { + "type": "string", + "defaultValue": "agent-ai-services", + "metadata": { + "description": "Name of the Azure AI Services account" + } + }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "2024-07-18", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "defaultValue": "GlobalStandard", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "defaultValue": 50, + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "defaultValue": "eastus", + "metadata": { + "description": "Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created." + } + }, + "aiServiceKind": { + "type": "string", + "defaultValue": "AIServices", + "metadata": { + "description": "AI Service Account kind: either AzureOpenAI or AIServices" + } + }, + "aiServiceAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiSearchServiceResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The Ai Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiStorageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The Ai Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "deploymentTimestamp": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddHHmmss')]" + } + }, + "variables": { + "name": "[toLower(format('{0}', parameters('aiHubName')))]", + "projectName": "[toLower(format('{0}', parameters('aiProjectName')))]", + "uniqueSuffix": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]", + "aiServiceExists": "[not(equals(parameters('aiServiceAccountResourceId'), ''))]", + "acsExists": "[not(equals(parameters('aiSearchServiceResourceId'), ''))]", + "aiServiceParts": "[split(parameters('aiServiceAccountResourceId'), '/')]", + "aiServiceAccountSubscriptionId": "[if(variables('aiServiceExists'), variables('aiServiceParts')[2], subscription().subscriptionId)]", + "aiServiceAccountResourceGroupName": "[if(variables('aiServiceExists'), variables('aiServiceParts')[4], resourceGroup().name)]", + "acsParts": "[split(parameters('aiSearchServiceResourceId'), '/')]", + "aiSearchServiceSubscriptionId": "[if(variables('acsExists'), variables('acsParts')[2], subscription().subscriptionId)]", + "aiSearchServiceResourceGroupName": "[if(variables('acsExists'), variables('acsParts')[4], resourceGroup().name)]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "storageName": { + "value": "[format('{0}{1}', parameters('storageName'), variables('uniqueSuffix'))]" + }, + "keyvaultName": { + "value": "[format('kv-{0}-{1}', variables('name'), variables('uniqueSuffix'))]" + }, + "aiServicesName": { + "value": "[format('{0}{1}', parameters('aiServicesName'), variables('uniqueSuffix'))]" + }, + "aiSearchName": { + "value": "[format('{0}-{1}', parameters('aiSearchName'), variables('uniqueSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" + }, + "modelLocation": { + "value": "[parameters('modelLocation')]" + }, + "aiServiceAccountResourceId": { + "value": "[parameters('aiServiceAccountResourceId')]" + }, + "aiSearchServiceResourceId": { + "value": "[parameters('aiSearchServiceResourceId')]" + }, + "aiStorageAccountResourceId": { + "value": "[parameters('aiStorageAccountResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "17188588850719968439" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "AI services name" + } + }, + "keyvaultName": { + "type": "string", + "metadata": { + "description": "The name of the Key Vault" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "The name of the AI Search resource" + } + }, + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + }, + "aiServiceAccountResourceId": { + "type": "string", + "metadata": { + "description": "The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiSearchServiceResourceId": { + "type": "string", + "metadata": { + "description": "The AI Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiStorageAccountResourceId": { + "type": "string", + "metadata": { + "description": "The AI Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "noZRSRegions": { + "type": "array", + "defaultValue": [ + "southindia", + "westus" + ] + }, + "sku": { + "type": "object", + "defaultValue": "[if(contains(parameters('noZRSRegions'), parameters('location')), createObject('name', 'Standard_GRS'), createObject('name', 'Standard_ZRS'))]" + } + }, + "variables": { + "storageNameCleaned": "[replace(parameters('storageName'), '-', '')]", + "aiServiceExists": "[not(equals(parameters('aiServiceAccountResourceId'), ''))]", + "acsExists": "[not(equals(parameters('aiSearchServiceResourceId'), ''))]", + "aiStorageExists": "[not(equals(parameters('aiStorageAccountResourceId'), ''))]", + "aiServiceParts": "[split(parameters('aiServiceAccountResourceId'), '/')]", + "acsParts": "[split(parameters('aiSearchServiceResourceId'), '/')]", + "aiStorageParts": "[split(parameters('aiStorageAccountResourceId'), '/')]" + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyvaultName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "createMode": "default", + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "enableSoftDelete": true, + "enableRbacAuthorization": true, + "enablePurgeProtection": true, + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny" + }, + "sku": { + "family": "A", + "name": "standard" + }, + "softDeleteRetentionInDays": 7, + "tenantId": "[subscription().tenantId]" + } + }, + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-10-01", + "name": "[parameters('aiServicesName')]", + "location": "[parameters('modelLocation')]", + "sku": { + "name": "S0" + }, + "kind": "AIServices", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[toLower(format('{0}', parameters('aiServicesName')))]", + "apiProperties": { + "statisticsEnabled": false + }, + "publicNetworkAccess": "Enabled" + } + }, + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + ] + }, + { + "condition": "[not(variables('acsExists'))]", + "type": "Microsoft.Search/searchServices", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiSearchName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "disableLocalAuth": false, + "authOptions": { + "aadOrApiKey": { + "aadAuthFailureMode": "http401WithBearerChallenge" + } + }, + "encryptionWithCmk": { + "enforcement": "Unspecified" + }, + "hostingMode": "default", + "partitionCount": 1, + "publicNetworkAccess": "enabled", + "replicaCount": 1, + "semanticSearch": "disabled" + }, + "sku": { + "name": "standard" + } + }, + { + "condition": "[not(variables('aiStorageExists'))]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-05-01", + "name": "[variables('storageNameCleaned')]", + "location": "[parameters('location')]", + "kind": "StorageV2", + "sku": "[parameters('sku')]", + "properties": { + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Allow", + "virtualNetworkRules": [] + }, + "allowSharedKeyAccess": false + } + } + ], + "outputs": { + "aiServicesName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[8], parameters('aiServicesName'))]" + }, + "aiservicesID": { + "type": "string", + "value": "[if(variables('aiServiceExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]" + }, + "aiservicesTarget": { + "type": "string", + "value": "[if(variables('aiServiceExists'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), '2024-10-01').endpoint, reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-10-01').endpoint)]" + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[4], resourceGroup().name)]" + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[2], subscription().subscriptionId)]" + }, + "aiSearchName": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[8], parameters('aiSearchName'))]" + }, + "aisearchID": { + "type": "string", + "value": "[if(variables('acsExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('acsParts')[2], variables('acsParts')[4]), 'Microsoft.Search/searchServices', variables('acsParts')[8]), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]" + }, + "aiSearchServiceResourceGroupName": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[4], resourceGroup().name)]" + }, + "aiSearchServiceSubscriptionId": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[2], subscription().subscriptionId)]" + }, + "storageAccountName": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[8], variables('storageNameCleaned'))]" + }, + "storageId": { + "type": "string", + "value": "[if(variables('aiStorageExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiStorageParts')[2], variables('aiStorageParts')[4]), 'Microsoft.Storage/storageAccounts', variables('aiStorageParts')[8]), resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned')))]" + }, + "storageAccountResourceGroupName": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[4], resourceGroup().name)]" + }, + "storageAccountSubscriptionId": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[2], subscription().subscriptionId)]" + }, + "keyvaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiHubName": { + "value": "[format('{0}-{1}', variables('name'), variables('uniqueSuffix'))]" + }, + "aiHubFriendlyName": { + "value": "[parameters('aiHubFriendlyName')]" + }, + "aiHubDescription": { + "value": "[parameters('aiHubDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiSearchName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiSearchId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aisearchID.value]" + }, + "aiSearchServiceResourceGroupName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiSearchServiceResourceGroupName.value]" + }, + "aiSearchServiceSubscriptionId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiSearchServiceSubscriptionId.value]" + }, + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiServiceKind": { + "value": "[parameters('aiServiceKind')]" + }, + "aiServicesId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesID.value]" + }, + "aiServicesTarget": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiservicesTarget.value]" + }, + "aiServiceAccountResourceGroupName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountResourceGroupName.value]" + }, + "aiServiceAccountSubscriptionId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServiceAccountSubscriptionId.value]" + }, + "keyVaultId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.keyvaultId.value]" + }, + "storageAccountId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.storageId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "16790563755272442555" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiHubName')]", + "metadata": { + "description": "AI hub display name" + } + }, + "aiHubDescription": { + "type": "string", + "metadata": { + "description": "AI hub description" + } + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the key vault resource for storing connection strings" + } + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account resource for storing experimentation outputs" + } + }, + "aiServicesId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services resource" + } + }, + "aiServicesTarget": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services endpoint" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name AI Services resource" + } + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource Group name of the AI Services resource" + } + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "metadata": { + "description": "Subscription ID of the AI Services resource" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Name AI Search resource" + } + }, + "aiSearchId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Search resource" + } + }, + "aiSearchServiceResourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource Group name of the AI Search resource" + } + }, + "aiSearchServiceSubscriptionId": { + "type": "string", + "metadata": { + "description": "Subscription ID of the AI Search resource" + } + }, + "aiServiceKind": { + "type": "string", + "metadata": { + "description": "AI Service Account kind: either OpenAI or AIServices" + } + } + }, + "variables": { + "acsConnectionName": "[format('{0}-connection-AISearch', parameters('aiHubName'))]", + "aoaiConnection": "[format('{0}-connection-AIServices_aoai', parameters('aiHubName'))]", + "kindAIServicesExists": "[equals(parameters('aiServiceKind'), 'AIServices')]", + "aiServiceConnectionName": "[if(variables('kindAIServicesExists'), format('{0}-connection-AIServices', parameters('aiHubName')), variables('aoaiConnection'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), variables('aiServiceConnectionName'))]", + "properties": { + "category": "[parameters('aiServiceKind')]", + "target": "[parameters('aiServicesTarget')]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiServicesId')]", + "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiServiceAccountSubscriptionId'), parameters('aiServiceAccountResourceGroupName')), 'Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-10-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), variables('acsConnectionName'))]", + "properties": { + "category": "CognitiveSearch", + "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiSearchId')]", + "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchServiceSubscriptionId'), parameters('aiSearchServiceResourceGroupName')), 'Microsoft.Search/searchServices', parameters('aiSearchName')), '2024-06-01-preview', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('aiHubName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiHubFriendlyName')]", + "description": "[parameters('aiHubDescription')]", + "keyVault": "[parameters('keyVaultId')]", + "storageAccount": "[parameters('storageAccountId')]", + "systemDatastoresAuthMode": "identity" + }, + "kind": "hub" + } + ], + "outputs": { + "aiHubID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + }, + "aiHubName": { + "type": "string", + "value": "[parameters('aiHubName')]" + }, + "aoaiConnectionName": { + "type": "string", + "value": "[variables('aoaiConnection')]" + }, + "acsConnectionName": { + "type": "string", + "value": "[variables('acsConnectionName')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiProjectName": { + "value": "[format('{0}-{1}', variables('projectName'), variables('uniqueSuffix'))]" + }, + "aiProjectFriendlyName": { + "value": "[parameters('aiProjectFriendlyName')]" + }, + "aiProjectDescription": { + "value": "[parameters('aiProjectDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "aiHubId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiHubID.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "551669852029742156" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI Project name" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiProjectName')]", + "metadata": { + "description": "AI Project display name" + } + }, + "aiProjectDescription": { + "type": "string", + "metadata": { + "description": "AI Project description" + } + }, + "aiHubId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Hub resource" + } + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "projectConnectionString": "[format('{0}.api.azureml.ms;{1};{2};{3}', parameters('location'), variables('subscriptionId'), variables('resourceGroupName'), parameters('aiProjectName'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('aiProjectName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('ProjectConnectionString', variables('projectConnectionString')))]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiProjectFriendlyName')]", + "description": "[parameters('aiProjectDescription')]", + "hubResourceId": "[parameters('aiHubId')]" + }, + "kind": "project" + } + ], + "outputs": { + "aiProjectName": { + "type": "string", + "value": "[parameters('aiProjectName')]" + }, + "aiProjectResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + }, + "aiProjectPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview', 'full').identity.principalId]" + }, + "aiProjectWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview').workspaceId]" + }, + "projectConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2024-10-01-preview', 'full').tags.ProjectConnectionString]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('wait-script-{0}-deployment', variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('wait-script-{0}', variables('uniqueSuffix'))]" + }, + "location": { + "value": "[parameters('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15002511661880391263" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "retentionTime": { + "type": "string", + "defaultValue": "PT1H" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "AzurePowerShell", + "properties": { + "azPowerShellVersion": "10.0", + "scriptContent": " Write-Output \"Starting wait script...\"\n Start-Sleep -Seconds 120\n Write-Output \"Wait completed. Proceeding with deployment...\"\n ", + "retentionInterval": "[parameters('retentionTime')]", + "cleanupPreference": "Always" + } + } + ], + "outputs": { + "scriptName": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-service-role-assignments-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "subscriptionId": "[variables('aiServiceAccountSubscriptionId')]", + "resourceGroup": "[variables('aiServiceAccountResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "8273307596853107787" + } + }, + "parameters": { + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name of the AI Services resource" + } + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the AI project" + } + }, + "aiProjectId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI project" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908'), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('wait-script-{0}-deployment', variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ai-search-role-assignments-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", + "subscriptionId": "[variables('aiSearchServiceSubscriptionId')]", + "resourceGroup": "[variables('aiSearchServiceResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiSearchName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "13082605048606812458" + } + }, + "parameters": { + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Name of the AI Search resource" + } + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the AI project" + } + }, + "aiProjectId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI project" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('wait-script-{0}-deployment', variables('uniqueSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('capabilityHost-configuration--{0}-deployment', variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "capabilityHostName": { + "value": "[format('{0}-{1}', variables('uniqueSuffix'), parameters('capabilityHostName'))]" + }, + "aiHubName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiHubName.value]" + }, + "aiProjectName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.aiProjectName.value]" + }, + "acsConnectionName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.acsConnectionName.value]" + }, + "aoaiConnectionName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix'))), '2022-09-01').outputs.aoaiConnectionName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "17907330302737404035" + } + }, + "parameters": { + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI project name" + } + }, + "acsConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + }, + "aoaiConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + }, + "capabilityHostName": { + "type": "string", + "metadata": { + "description": "Name for capabilityHost." + } + } + }, + "variables": { + "storageConnections": [ + "[format('{0}/workspaceblobstore', parameters('aiProjectName'))]" + ], + "aiSearchConnection": [ + "[format('{0}', parameters('acsConnectionName'))]" + ], + "aiServiceConnections": [ + "[format('{0}', parameters('aoaiConnectionName'))]" + ] + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-{1}', parameters('aiHubName'), parameters('capabilityHostName')))]", + "properties": { + "capabilityHostKind": "Agents" + } + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiProjectName'), format('{0}-{1}', parameters('aiProjectName'), parameters('capabilityHostName')))]", + "properties": { + "capabilityHostKind": "Agents", + "aiServicesConnections": "[variables('aiServiceConnections')]", + "vectorStoreConnections": "[variables('aiSearchConnection')]", + "storageConnections": "[variables('storageConnections')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces/capabilityHosts', parameters('aiHubName'), format('{0}-{1}', parameters('aiHubName'), parameters('capabilityHostName')))]" + ] + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('name'), variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiSearchServiceSubscriptionId'), variables('aiSearchServiceResourceGroupName')), 'Microsoft.Resources/deployments', format('ai-search-role-assignments-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceAccountSubscriptionId'), variables('aiServiceAccountResourceGroupName')), 'Microsoft.Resources/deployments', format('ai-service-role-assignments-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" + ] + } + ], + "outputs": { + "PROJECT_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2022-09-01').outputs.projectConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/scenarios/Agents/setup/standard-agent/azuredeploy.parameters.json b/scenarios/Agents/setup/standard-agent/azuredeploy.parameters.json new file mode 100644 index 00000000..b09458c0 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/azuredeploy.parameters.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "aiHubName": { + "value": "hub-demo" + }, + "aiHubFriendlyName": { + "value": "Agents Hub resource" + }, + "aiHubDescription": { + "value": "This is an example AI resource for use in Azure AI Studio." + }, + "aiProjectName": { + "value": "project-demo" + }, + "aiProjectFriendlyName": { + "value": "Agents Project resource" + }, + "aiProjectDescription": { + "value": "This is an example AI Project resource for use in Azure AI Studio." + }, + "aiSearchName": { + "value": "agent-ai-search" + }, + "capabilityHostName": { + "value": "caphost1" + }, + "storageName": { + "value": "agent-storage" + }, + "aiServicesName": { + "value": "agent-ai-services" + }, + "modelName": { + "value": "gpt-4o-mini" + }, + "modelFormat": { + "value": "OpenAI" + }, + "modelVersion": { + "value": "2024-07-18" + }, + "modelSkuName": { + "value": "GlobalStandard" + }, + "modelCapacity": { + "value": 50 + }, + "modelLocation": { + "value": "eastus" + }, + "aiServiceAccountResourceId": { + "value": "" + }, + "aiSearchServiceResourceId": { + "value": "" + }, + "aiStorageAccountResourceId":{ + "value": "" + }, + "aiServiceKind": { + "value": "AIServices" + } + } + } + \ No newline at end of file diff --git a/scenarios/Agents/setup/standard-agent/main.bicep b/scenarios/Agents/setup/standard-agent/main.bicep new file mode 100644 index 00000000..f5be6491 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/main.bicep @@ -0,0 +1,209 @@ +// Execute this main file to deploy Enterprise Standard Agent setup resources + +// Parameters +@minLength(2) +@maxLength(12) +@description('Name for the AI resource and used to derive name of dependent resources.') +param aiHubName string = 'hub-demo' + +@description('Friendly name for your Hub resource') +param aiHubFriendlyName string = 'Agents Hub resource' + +@description('Description of your Azure AI resource displayed in AI studio') +param aiHubDescription string = 'This is an example AI resource for use in Azure AI Studio.' + +@description('Name for the AI project resources.') +param aiProjectName string = 'project-demo' + +@description('Friendly name for your Azure AI resource') +param aiProjectFriendlyName string = 'Agents Project resource' + +@description('Description of your Azure AI resource displayed in AI studio') +param aiProjectDescription string = 'This is an example AI Project resource for use in Azure AI Studio.' + +@description('Azure region used for the deployment of all resources.') +param location string = resourceGroup().location + +@description('Set of tags to apply to all resources.') +param tags object = {} + +@description('Name of the Azure AI Search account') +param aiSearchName string = 'agent-ai-search' + +@description('Name for capabilityHost.') +param capabilityHostName string = 'caphost1' + +@description('Name of the storage account') +param storageName string = 'agent-storage' + +@description('Name of the Azure AI Services account') +param aiServicesName string = 'agent-ai-services' + +@description('Model name for deployment') +param modelName string = 'gpt-4o-mini' + +@description('Model format for deployment') +param modelFormat string = 'OpenAI' + +@description('Model version for deployment') +param modelVersion string = '2024-07-18' + +@description('Model deployment SKU name') +param modelSkuName string = 'GlobalStandard' + +@description('Model deployment capacity') +param modelCapacity int = 50 + +@description('Model deployment location. If you want to deploy an Azure AI resource/model in different location than the rest of the resources created.') +param modelLocation string = 'eastus' + +@description('AI Service Account kind: either AzureOpenAI or AIServices') +param aiServiceKind string = 'AIServices' + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string = '' + +@description('The Ai Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiSearchServiceResourceId string = '' + +@description('The Ai Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiStorageAccountResourceId string = '' + +// Variables +var name = toLower('${aiHubName}') +var projectName = toLower('${aiProjectName}') + +// Create a short, unique suffix, that will be unique to each resource group +// var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 4) +param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') +var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) + +var aiServiceExists = aiServiceAccountResourceId != '' +var acsExists = aiSearchServiceResourceId != '' + +var aiServiceParts = split(aiServiceAccountResourceId, '/') +var aiServiceAccountSubscriptionId = aiServiceExists ? aiServiceParts[2] : subscription().subscriptionId +var aiServiceAccountResourceGroupName = aiServiceExists ? aiServiceParts[4] : resourceGroup().name + +var acsParts = split(aiSearchServiceResourceId, '/') +var aiSearchServiceSubscriptionId = acsExists ? acsParts[2] : subscription().subscriptionId +var aiSearchServiceResourceGroupName = acsExists ? acsParts[4] : resourceGroup().name + +// Dependent resources for the Azure Machine Learning workspace +module aiDependencies 'modules-standard/standard-dependent-resources.bicep' = { + name: 'dependencies-${name}-${uniqueSuffix}-deployment' + params: { + location: location + storageName: '${storageName}${uniqueSuffix}' + keyvaultName: 'kv-${name}-${uniqueSuffix}' + aiServicesName: '${aiServicesName}${uniqueSuffix}' + aiSearchName: '${aiSearchName}-${uniqueSuffix}' + tags: tags + + // Model deployment parameters + modelName: modelName + modelFormat: modelFormat + modelVersion: modelVersion + modelSkuName: modelSkuName + modelCapacity: modelCapacity + modelLocation: modelLocation + + aiServiceAccountResourceId: aiServiceAccountResourceId + aiSearchServiceResourceId: aiSearchServiceResourceId + aiStorageAccountResourceId: aiStorageAccountResourceId + } +} + +module aiHub 'modules-standard/standard-ai-hub.bicep' = { + name: '${name}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiHubName: '${name}-${uniqueSuffix}' + aiHubFriendlyName: aiHubFriendlyName + aiHubDescription: aiHubDescription + location: location + tags: tags + + aiSearchName: aiDependencies.outputs.aiSearchName + aiSearchId: aiDependencies.outputs.aisearchID + aiSearchServiceResourceGroupName: aiDependencies.outputs.aiSearchServiceResourceGroupName + aiSearchServiceSubscriptionId: aiDependencies.outputs.aiSearchServiceSubscriptionId + + aiServicesName: aiDependencies.outputs.aiServicesName + aiServiceKind: aiServiceKind + aiServicesId: aiDependencies.outputs.aiservicesID + aiServicesTarget: aiDependencies.outputs.aiservicesTarget + aiServiceAccountResourceGroupName:aiDependencies.outputs.aiServiceAccountResourceGroupName + aiServiceAccountSubscriptionId:aiDependencies.outputs.aiServiceAccountSubscriptionId + + keyVaultId: aiDependencies.outputs.keyvaultId + storageAccountId: aiDependencies.outputs.storageId + } +} + + +module aiProject 'modules-standard/standard-ai-project.bicep' = { + name: '${projectName}-${uniqueSuffix}-deployment' + params: { + // workspace organization + aiProjectName: '${projectName}-${uniqueSuffix}' + aiProjectFriendlyName: aiProjectFriendlyName + aiProjectDescription: aiProjectDescription + location: location + tags: tags + aiHubId: aiHub.outputs.aiHubID + } +} + +module waitScript 'modules-standard/wait-script.bicep' = { + name: 'wait-script-${uniqueSuffix}-deployment' + params: { + name: 'wait-script-${uniqueSuffix}' + location: location + } + dependsOn: [ + aiProject + ] +} + +module aiServiceRoleAssignments 'modules-standard/ai-service-role-assignments.bicep' = { + name: 'ai-service-role-assignments-${projectName}-${uniqueSuffix}-deployment' + scope: resourceGroup(aiServiceAccountSubscriptionId, aiServiceAccountResourceGroupName) + params: { + aiServicesName: aiDependencies.outputs.aiServicesName + aiProjectPrincipalId: aiProject.outputs.aiProjectPrincipalId + aiProjectId: aiProject.outputs.aiProjectResourceId + } + dependsOn: [ + waitScript + ] +} + +module aiSearchRoleAssignments 'modules-standard/ai-search-role-assignments.bicep' = { + name: 'ai-search-role-assignments-${projectName}-${uniqueSuffix}-deployment' + scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) + params: { + aiSearchName: aiDependencies.outputs.aiSearchName + aiProjectPrincipalId: aiProject.outputs.aiProjectPrincipalId + aiProjectId: aiProject.outputs.aiProjectResourceId + } + dependsOn: [ + waitScript + ] +} + +module addCapabilityHost 'modules-standard/add-capability-host.bicep' = { + name: 'capabilityHost-configuration--${uniqueSuffix}-deployment' + params: { + capabilityHostName: '${uniqueSuffix}-${capabilityHostName}' + aiHubName: aiHub.outputs.aiHubName + aiProjectName: aiProject.outputs.aiProjectName + acsConnectionName: aiHub.outputs.acsConnectionName + aoaiConnectionName: aiHub.outputs.aoaiConnectionName + } + dependsOn: [ + aiSearchRoleAssignments,aiServiceRoleAssignments + ] +} + +output PROJECT_CONNECTION_STRING string = aiProject.outputs.projectConnectionString diff --git a/scenarios/Agents/setup/standard-agent/metadata.json b/scenarios/Agents/setup/standard-agent/metadata.json new file mode 100644 index 00000000..343266eb --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/metadata.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://aka.ms/azure-quickstart-templates-metadata-schema#", + "type": "QuickStart", + "itemDisplayName": "Standard Agent Setup", + "description": "This set of templates demonstrates how to set up Azure AI Agent Service with the standard setup, meaning with managed identity authentication for project/hub connections and public internet access enabled. Agents use customer-owned, single-tenant search and storage resources. With this setup, you have full control and visibility over these resources, but you will incur costs based on your usage.", + "summary": "This set of templates demonstrates how to use Azure AI Agent Service with the standard setup.", + "githubUsername": "fosteramanda", + "dateUpdated": "2025-02-11", + "environments": [ + "AzureCloud" + ] +} \ No newline at end of file diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/add-capability-host.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/add-capability-host.bicep new file mode 100644 index 00000000..098a4ad1 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/add-capability-host.bicep @@ -0,0 +1,50 @@ +@description('AI hub name') +param aiHubName string + +@description('AI project name') +param aiProjectName string + +@description('Name for ACS connection.') +param acsConnectionName string + +@description('Name for ACS connection.') +param aoaiConnectionName string + +@description('Name for capabilityHost.') +param capabilityHostName string + +var storageConnections = ['${aiProjectName}/workspaceblobstore'] +var aiSearchConnection = ['${acsConnectionName}'] +var aiServiceConnections = ['${aoaiConnectionName}'] + +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' existing = { + name: aiHubName +} + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' existing = { + name: aiProjectName +} + +#disable-next-line BCP081 +resource hubCapabilityHost 'Microsoft.MachineLearningServices/workspaces/capabilityHosts@2024-10-01-preview' = { + name: '${aiHubName}-${capabilityHostName}' + parent: aiHub + properties: { + capabilityHostKind: 'Agents' + } +} + +#disable-next-line BCP081 +resource projectCapabilityHost 'Microsoft.MachineLearningServices/workspaces/capabilityHosts@2024-10-01-preview' = { + name: '${aiProjectName}-${capabilityHostName}' + parent: aiProject + properties: { + capabilityHostKind: 'Agents' + aiServicesConnections: aiServiceConnections + vectorStoreConnections: aiSearchConnection + storageConnections: storageConnections + } + dependsOn: [ + hubCapabilityHost + ] +} diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/ai-search-role-assignments.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/ai-search-role-assignments.bicep new file mode 100644 index 00000000..eb8e8b5e --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/ai-search-role-assignments.bicep @@ -0,0 +1,46 @@ +// Assigns the necessary roles to the AI project + +@description('Name of the AI Search resource') +param aiSearchName string + +@description('Principal ID of the AI project') +param aiProjectPrincipalId string + +@description('Resource ID of the AI project') +param aiProjectId string + +resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { + name: aiSearchName + scope: resourceGroup() +} + +// search roles +resource searchIndexDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' + scope: resourceGroup() +} + +resource searchIndexDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + name: guid(aiProjectId, searchIndexDataContributorRole.id, searchService.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: searchIndexDataContributorRole.id + principalType: 'ServicePrincipal' + } +} + +resource searchServiceContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' + scope: resourceGroup() +} + +resource searchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: searchService + name: guid(aiProjectId, searchServiceContributorRole.id, searchService.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: searchServiceContributorRole.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/ai-service-role-assignments.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/ai-service-role-assignments.bicep new file mode 100644 index 00000000..f9afd3e9 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/ai-service-role-assignments.bicep @@ -0,0 +1,61 @@ +// Assigns the necessary roles to the AI project + +@description('Name of the AI Services resource') +param aiServicesName string + +@description('Principal ID of the AI project') +param aiProjectPrincipalId string + +@description('Resource ID of the AI project') +param aiProjectId string + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = { + name: aiServicesName + scope: resourceGroup() +} + +resource cognitiveServicesContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' + scope: resourceGroup() + +} + +resource cognitiveServicesContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01'= { + scope: aiServices + name: guid(aiServices.id, cognitiveServicesContributorRole.id, aiProjectId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesContributorRole.id + principalType: 'ServicePrincipal' + } +} + + +resource cognitiveServicesOpenAIUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + scope: resourceGroup() +} +resource cognitiveServicesOpenAIUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectId, cognitiveServicesOpenAIUserRole.id, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesOpenAIUserRole.id + principalType: 'ServicePrincipal' + } +} + +resource cognitiveServicesUserRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'a97b65f3-24c7-4388-baec-2e87135dc908' + scope: resourceGroup() +} + +resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectId, cognitiveServicesUserRole.id, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: cognitiveServicesUserRole.id + principalType: 'ServicePrincipal' + } +} diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-hub.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-hub.bicep new file mode 100644 index 00000000..18eb0440 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-hub.bicep @@ -0,0 +1,138 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI hub name') +param aiHubName string + +@description('AI hub display name') +param aiHubFriendlyName string = aiHubName + +@description('AI hub description') +param aiHubDescription string + +@description('Resource ID of the key vault resource for storing connection strings') +param keyVaultId string + +@description('Resource ID of the storage account resource for storing experimentation outputs') +param storageAccountId string + +@description('Resource ID of the AI Services resource') +param aiServicesId string + +@description('Resource ID of the AI Services endpoint') +param aiServicesTarget string + +@description('Name AI Services resource') +param aiServicesName string + +@description('Resource Group name of the AI Services resource') +param aiServiceAccountResourceGroupName string + +@description('Subscription ID of the AI Services resource') +param aiServiceAccountSubscriptionId string + +@description('Name AI Search resource') +param aiSearchName string + +@description('Resource ID of the AI Search resource') +param aiSearchId string + +@description('Resource Group name of the AI Search resource') +param aiSearchServiceResourceGroupName string + +@description('Subscription ID of the AI Search resource') +param aiSearchServiceSubscriptionId string + +/* @description('Name for capabilityHost.') +param capabilityHostName string */ + +@description('AI Service Account kind: either OpenAI or AIServices') +param aiServiceKind string + +var acsConnectionName = '${aiHubName}-connection-AISearch' + +var aoaiConnection = '${aiHubName}-connection-AIServices_aoai' + +var kindAIServicesExists = aiServiceKind == 'AIServices' + +var aiServiceConnectionName = kindAIServicesExists ? '${aiHubName}-connection-AIServices' : aoaiConnection + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = { + name: aiServicesName + scope: resourceGroup(aiServiceAccountSubscriptionId, aiServiceAccountResourceGroupName) +} + +resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { + name: aiSearchName + scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) +} + +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' = { + name: aiHubName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiHubFriendlyName + description: aiHubDescription + + // dependent resources + keyVault: keyVaultId + storageAccount: storageAccountId + systemDatastoresAuthMode: 'identity' + } + kind: 'hub' + + resource aiServicesConnection 'connections@2024-07-01-preview' = { + name: aiServiceConnectionName + properties: { + category: aiServiceKind // either AIServices or AzureOpenAI + target: aiServicesTarget + authType: 'AAD' + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiServicesId + location: aiServices.location + } + } + } + resource hub_connection_azureai_search 'connections@2024-07-01-preview' = { + name: acsConnectionName + properties: { + category: 'CognitiveSearch' + target: 'https://${aiSearchName}.search.windows.net' + authType: 'AAD' + //useWorkspaceManagedIdentity: false + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiSearchId + location: searchService.location + } + } + } + + // Resource definition for the capability host + #disable-next-line BCP081 + /* resource capabilityHost 'capabilityHosts@2024-10-01-preview' = { + name: '${aiHubName}-${capabilityHostName}' + properties: { + capabilityHostKind: 'Agents' + } + } */ + +} + +output aiHubID string = aiHub.id +output aiHubName string = aiHub.name +output aoaiConnectionName string = aoaiConnection +output acsConnectionName string = acsConnectionName diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-project.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-project.bicep new file mode 100644 index 00000000..10dba663 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/standard-ai-project.bicep @@ -0,0 +1,67 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string + +@description('Resource ID of the AI Hub resource') +param aiHubId string + +/* @description('Name for capabilityHost.') +param capabilityHostName string + +@description('Name for ACS connection.') +param acsConnectionName string + +@description('Name for ACS connection.') +param aoaiConnectionName string */ + +//for constructing endpoint +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name + +var projectConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${aiProjectName}' + + +/* var storageConnections = ['${aiProjectName}/workspaceblobstore'] +var aiSearchConnection = ['${acsConnectionName}'] +var aiServiceConnections = ['${aoaiConnectionName}'] */ + + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2024-10-01-preview' = { + name: aiProjectName + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString + }) + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiProjectFriendlyName + description: aiProjectDescription + + // dependent resources + hubResourceId: aiHubId + + } + kind: 'project' +} + +output aiProjectName string = aiProject.name +output aiProjectResourceId string = aiProject.id +output aiProjectPrincipalId string = aiProject.identity.principalId +output aiProjectWorkspaceId string = aiProject.properties.workspaceId +output projectConnectionString string = aiProject.tags.ProjectConnectionString diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/standard-dependent-resources.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/standard-dependent-resources.bicep new file mode 100644 index 00000000..370bde5f --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/standard-dependent-resources.bicep @@ -0,0 +1,196 @@ +// Creates Azure dependent resources for Azure AI Agent Service standard agent setup + +@description('Azure region of the deployment') +param location string = resourceGroup().location + +@description('Tags to add to the resources') +param tags object = {} + +@description('AI services name') +param aiServicesName string + +@description('The name of the Key Vault') +param keyvaultName string + +@description('The name of the AI Search resource') +param aiSearchName string + +@description('Name of the storage account') +param storageName string + +var storageNameCleaned = replace(storageName, '-', '') + +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('Model/AI Resource deployment location') +param modelLocation string + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string + +@description('The AI Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiSearchServiceResourceId string + +@description('The AI Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiStorageAccountResourceId string + +var aiServiceExists = aiServiceAccountResourceId != '' +var acsExists = aiSearchServiceResourceId != '' +var aiStorageExists = aiStorageAccountResourceId != '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyvaultName + location: location + tags: tags + properties: { + createMode: 'default' + enabledForDeployment: false + enabledForDiskEncryption: false + enabledForTemplateDeployment: false + enableSoftDelete: true + enableRbacAuthorization: true + enablePurgeProtection: true + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + } + sku: { + family: 'A' + name: 'standard' + } + softDeleteRetentionInDays: 7 + tenantId: subscription().tenantId + } +} + + +var aiServiceParts = split(aiServiceAccountResourceId, '/') + +resource existingAIServiceAccount 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = if (aiServiceExists) { + name: aiServiceParts[8] + scope: resourceGroup(aiServiceParts[2], aiServiceParts[4]) +} + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-10-01' = if(!aiServiceExists) { + name: aiServicesName + location: modelLocation + sku: { + name: 'S0' + } + kind: 'AIServices' // or 'OpenAI' + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower('${(aiServicesName)}') + apiProperties: { + statisticsEnabled: false + } + publicNetworkAccess: 'Enabled' + } +} +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01'= if(!aiServiceExists){ + parent: aiServices + name: modelName + sku : { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model:{ + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +var acsParts = split(aiSearchServiceResourceId, '/') + +resource existingSearchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = if (acsExists) { + name: acsParts[8] + scope: resourceGroup(acsParts[2], acsParts[4]) +} +resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = if(!acsExists) { + name: aiSearchName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + disableLocalAuth: false + authOptions: { aadOrApiKey: { aadAuthFailureMode: 'http401WithBearerChallenge'}} + encryptionWithCmk: { + enforcement: 'Unspecified' + } + hostingMode: 'default' + partitionCount: 1 + publicNetworkAccess: 'enabled' + replicaCount: 1 + semanticSearch: 'disabled' + } + sku: { + name: 'standard' + } +} + +var aiStorageParts = split(aiStorageAccountResourceId, '/') + +resource existingAIStorageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = if (aiStorageExists) { + name: aiStorageParts[8] + scope: resourceGroup(aiStorageParts[2], aiStorageParts[4]) +} + +// Some regions doesn't support Standard Zone-Redundant storage, need to use Geo-redundant storage +param noZRSRegions array = ['southindia', 'westus'] +param sku object = contains(noZRSRegions, location) ? { name: 'Standard_GRS' } : { name: 'Standard_ZRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = if(!aiStorageExists) { + name: storageNameCleaned + location: location + kind: 'StorageV2' + sku: sku + properties: { + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + publicNetworkAccess: 'Enabled' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + virtualNetworkRules: [] + } + allowSharedKeyAccess: false + } +} + +output aiServicesName string = aiServiceExists ? existingAIServiceAccount.name : aiServicesName +output aiservicesID string = aiServiceExists ? existingAIServiceAccount.id : aiServices.id +output aiservicesTarget string = aiServiceExists ? existingAIServiceAccount.properties.endpoint : aiServices.properties.endpoint +output aiServiceAccountResourceGroupName string = aiServiceExists ? aiServiceParts[4] : resourceGroup().name +output aiServiceAccountSubscriptionId string = aiServiceExists ? aiServiceParts[2] : subscription().subscriptionId + +output aiSearchName string = acsExists ? existingSearchService.name : aiSearch.name +output aisearchID string = acsExists ? existingSearchService.id : aiSearch.id +output aiSearchServiceResourceGroupName string = acsExists ? acsParts[4] : resourceGroup().name +output aiSearchServiceSubscriptionId string = acsExists ? acsParts[2] : subscription().subscriptionId + +output storageAccountName string = aiStorageExists ? existingAIStorageAccount.name : storage.name +output storageId string = aiStorageExists ? existingAIStorageAccount.id : storage.id +output storageAccountResourceGroupName string = aiStorageExists ? aiStorageParts[4] : resourceGroup().name +output storageAccountSubscriptionId string = aiStorageExists ? aiStorageParts[2] : subscription().subscriptionId + +output keyvaultId string = keyVault.id diff --git a/scenarios/Agents/setup/standard-agent/modules-standard/wait-script.bicep b/scenarios/Agents/setup/standard-agent/modules-standard/wait-script.bicep new file mode 100644 index 00000000..39bd9934 --- /dev/null +++ b/scenarios/Agents/setup/standard-agent/modules-standard/wait-script.bicep @@ -0,0 +1,21 @@ +param name string +param location string +param retentionTime string = 'PT1H' // Retention duration + +resource waitScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { + name: name + location: location + kind: 'AzurePowerShell' + properties: { + azPowerShellVersion: '10.0' + scriptContent: ''' + Write-Output "Starting wait script..." + Start-Sleep -Seconds 120 + Write-Output "Wait completed. Proceeding with deployment..." + ''' + retentionInterval: retentionTime + cleanupPreference: 'Always' + } +} + +output scriptName string = waitScript.name