diff --git a/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json index fbb96f353..5edf30433 100644 --- a/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15247532385970128893" + "version": "0.39.26.7824", + "templateHash": "4687470877814835157" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json index 3c7ec4096..672e78928 100644 --- a/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14038434470119297573" + "version": "0.39.26.7824", + "templateHash": "17555450976668837106" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json index 396a1b9aa..d2cee29a4 100644 --- a/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "6518302005159872058" + "version": "0.39.26.7824", + "templateHash": "6087024663439753870" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json index 7ea345c11..840868a3e 100644 --- a/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "6904353034679125432" + "version": "0.39.26.7824", + "templateHash": "258335635459710717" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/16-private-network-standard-agent-apim-setup-preview/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/16-private-network-standard-agent-apim-setup-preview/azuredeploy.json index c2a5c5198..3e10f19fa 100644 --- a/infrastructure/infrastructure-setup-bicep/16-private-network-standard-agent-apim-setup-preview/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/16-private-network-standard-agent-apim-setup-preview/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1883763996282384972" + "version": "0.39.26.7824", + "templateHash": "13929636312670904506" } }, "parameters": { @@ -297,8 +297,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "9522150535342725348" + "version": "0.39.26.7824", + "templateHash": "8505298823279202405" } }, "parameters": { @@ -411,8 +411,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1725519900292599397" + "version": "0.39.26.7824", + "templateHash": "4954184648131521061" } }, "parameters": { @@ -584,8 +584,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12834877200138369468" + "version": "0.39.26.7824", + "templateHash": "3152324712046183852" } }, "parameters": { @@ -674,8 +674,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16684585228229443328" + "version": "0.39.26.7824", + "templateHash": "17043822047386586435" } }, "parameters": { @@ -757,8 +757,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16684585228229443328" + "version": "0.39.26.7824", + "templateHash": "17043822047386586435" } }, "parameters": { @@ -929,8 +929,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "481468620289904255" + "version": "0.39.26.7824", + "templateHash": "854097619778148359" } }, "parameters": { @@ -1069,8 +1069,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "923626409245918017" + "version": "0.39.26.7824", + "templateHash": "15994578372975581196" } }, "parameters": { @@ -1248,8 +1248,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1228922674432785221" + "version": "0.39.26.7824", + "templateHash": "2754228344238136934" } }, "parameters": { @@ -1537,8 +1537,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7465170083747306470" + "version": "0.39.26.7824", + "templateHash": "16093594075579008850" } }, "parameters": { @@ -2159,8 +2159,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1007872875847158534" + "version": "0.39.26.7824", + "templateHash": "5095087340309076800" } }, "parameters": { @@ -2334,8 +2334,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12606978205101260380" + "version": "0.39.26.7824", + "templateHash": "6910483561575524105" } }, "parameters": { @@ -2389,8 +2389,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15991712531324316353" + "version": "0.39.26.7824", + "templateHash": "14683840003859985069" } }, "parameters": { @@ -2405,7 +2405,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2447,8 +2447,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11201886913940098363" + "version": "0.39.26.7824", + "templateHash": "2161753938341361575" } }, "parameters": { @@ -2469,7 +2469,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2511,8 +2511,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15867124606695536257" + "version": "0.39.26.7824", + "templateHash": "7968115481508840" } }, "parameters": { @@ -2533,7 +2533,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2544,7 +2544,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2596,8 +2596,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "687161311666023487" + "version": "0.39.26.7824", + "templateHash": "17458377866351620215" } }, "parameters": { @@ -2690,8 +2690,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "3625425571119261380" + "version": "0.39.26.7824", + "templateHash": "13874725855824693255" } }, "parameters": { @@ -2721,7 +2721,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", "name": "[guid(resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]", "properties": { "principalId": "[parameters('aiProjectPrincipalId')]", @@ -2769,8 +2769,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14722474518981746838" + "version": "0.39.26.7824", + "templateHash": "17187611271934567223" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/17-private-network-standard-user-assigned-identity-agent-setup/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/17-private-network-standard-user-assigned-identity-agent-setup/azuredeploy.json index d77f1b20f..96525e295 100644 --- a/infrastructure/infrastructure-setup-bicep/17-private-network-standard-user-assigned-identity-agent-setup/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/17-private-network-standard-user-assigned-identity-agent-setup/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "5825478414824080876" + "version": "0.39.26.7824", + "templateHash": "9271946558272429054" } }, "parameters": { @@ -283,8 +283,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16080922426874297734" + "version": "0.39.26.7824", + "templateHash": "12473591672685297473" } }, "parameters": { @@ -384,8 +384,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "9522150535342725348" + "version": "0.39.26.7824", + "templateHash": "8505298823279202405" } }, "parameters": { @@ -498,8 +498,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1725519900292599397" + "version": "0.39.26.7824", + "templateHash": "4954184648131521061" } }, "parameters": { @@ -671,8 +671,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12834877200138369468" + "version": "0.39.26.7824", + "templateHash": "3152324712046183852" } }, "parameters": { @@ -761,8 +761,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16684585228229443328" + "version": "0.39.26.7824", + "templateHash": "17043822047386586435" } }, "parameters": { @@ -844,8 +844,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16684585228229443328" + "version": "0.39.26.7824", + "templateHash": "17043822047386586435" } }, "parameters": { @@ -1019,8 +1019,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7102706229714471787" + "version": "0.39.26.7824", + "templateHash": "12620781326236378852" } }, "parameters": { @@ -1159,8 +1159,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16869070767065140538" + "version": "0.39.26.7824", + "templateHash": "7641310640078958122" } }, "parameters": { @@ -1311,8 +1311,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1228922674432785221" + "version": "0.39.26.7824", + "templateHash": "2754228344238136934" } }, "parameters": { @@ -1591,8 +1591,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "8505208925695592232" + "version": "0.39.26.7824", + "templateHash": "8094529554453089222" } }, "parameters": { @@ -2123,8 +2123,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "3709848476025682641" + "version": "0.39.26.7824", + "templateHash": "3622791801420135420" } }, "parameters": { @@ -2301,8 +2301,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12606978205101260380" + "version": "0.39.26.7824", + "templateHash": "6910483561575524105" } }, "parameters": { @@ -2356,8 +2356,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15991712531324316353" + "version": "0.39.26.7824", + "templateHash": "14683840003859985069" } }, "parameters": { @@ -2372,7 +2372,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2414,8 +2414,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7256419427588810161" + "version": "0.39.26.7824", + "templateHash": "25128059954858801" } }, "parameters": { @@ -2436,7 +2436,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2478,8 +2478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15867124606695536257" + "version": "0.39.26.7824", + "templateHash": "7968115481508840" } }, "parameters": { @@ -2500,7 +2500,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2511,7 +2511,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -2563,8 +2563,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "687161311666023487" + "version": "0.39.26.7824", + "templateHash": "17458377866351620215" } }, "parameters": { @@ -2657,8 +2657,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "3625425571119261380" + "version": "0.39.26.7824", + "templateHash": "13874725855824693255" } }, "parameters": { @@ -2688,7 +2688,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", "name": "[guid(resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]", "properties": { "principalId": "[parameters('aiProjectPrincipalId')]", @@ -2736,8 +2736,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14722474518981746838" + "version": "0.39.26.7824", + "templateHash": "17187611271934567223" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/README.md b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/README.md deleted file mode 100644 index 03014500c..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# Hybrid Private Resources Agent Setup - -This template deploys an Azure AI Foundry account with backend resources (AI Search, Cosmos DB, Storage) on **private endpoints**. By default, the Foundry resource itself also has **public network access disabled**, but this can be switched to public access if needed (see [Switching Between Private and Public Access](#switching-between-private-and-public-access)). - -## Architecture (Default — Private Foundry) - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ Secure Access (VPN Gateway / ExpressRoute / Azure Bastion) │ -└──────────────────────────────────┬──────────────────────────────────┘ - │ - ┌──────────────▼──────────────┐ - │ AI Services Account │ - │ (publicNetworkAccess: │ - │ DISABLED) │ ◄── Private by default - │ │ - │ ┌────────────────────────┐ │ - │ │ Data Proxy / Agent │ │ - │ │ ToolServer │ │ - │ └───────────┬────────────┘ │ - └──────────────┼──────────────┘ - │ networkInjections - ┌──────────────▼──────────────┐ - │ Private VNet │ - │ │ - │ ┌─────────┐ ┌─────────┐ │ - │ │AI Search│ │Cosmos DB│ │ ◄── Private endpoints - │ └─────────┘ └─────────┘ │ (no public access) - │ │ - │ ┌─────────┐ ┌─────────┐ │ - │ │ Storage │ │ MCP │ │ - │ └─────────┘ │ Servers │ │ - │ └─────────┘ │ - └─────────────────────────────┘ -``` - -## Key Features - -| Feature | This Template (19) — Private (default) | This Template (19) — Public | Fully Private (15) | -|---------|----------------------------------------|-----------------------------|-----------------------| -| AI Services public access | ❌ Disabled | ✅ Enabled | ❌ Disabled | -| Portal access | Via VPN/ExpressRoute/Bastion | ✅ Works directly | Via VPN/ExpressRoute/Bastion | -| Backend resources | 🔒 Private | 🔒 Private | 🔒 Private | -| Data Proxy | ✅ Configured | ✅ Configured | ✅ Configured | -| Secure connection required | ✅ Yes | ❌ No | ✅ Yes | - -## Switching Between Private and Public Access - -The Foundry resource has **public network access disabled by default**. You can switch between the two modes by modifying the Bicep template. - -### To enable public access - -In [modules-network-secured/ai-account-identity.bicep](modules-network-secured/ai-account-identity.bicep), change: - -```bicep -// Change from: -publicNetworkAccess: 'Disabled' -// To: -publicNetworkAccess: 'Enabled' - -// Also change: -defaultAction: 'Deny' -// To: -defaultAction: 'Allow' -``` - -This makes the Foundry resource accessible from the internet (e.g., for portal-based development without VPN). - -### To disable public access (default) - -Revert the changes above, setting `publicNetworkAccess: 'Disabled'` and `defaultAction: 'Deny'`. - -## Connecting to a Private Foundry Resource - -When public network access is disabled (the default), you need a secure connection to reach the Foundry resource. Azure provides three methods: - -1. **Azure VPN Gateway** — Connect from your local network to the Azure VNet over an encrypted tunnel. -2. **Azure ExpressRoute** — Use a private, dedicated connection from your on-premises infrastructure to Azure. -3. **Azure Bastion** — Use a jump box VM on the VNet, accessed securely through the Azure portal. - -For detailed setup instructions, see: [Securely connect to Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/configure-private-link?view=foundry#securely-connect-to-foundry). - -## When to Use This Template - -Use this template when you want: -- **Private backend resources** — Keep AI Search, Cosmos DB, and Storage behind private endpoints -- **MCP server integration** — Deploy MCP servers on the VNet that agents can access via Data Proxy -- **Private Foundry (default)** — Full network isolation with secure access via VPN/ExpressRoute/Bastion -- **Optional public Foundry access** — Switch to public for portal-based development if allowed by your security policy - -## When NOT to Use This Template - -Use [template 15](../15-private-network-standard-agent-setup/) instead when you need: -- **Fully managed private networking** — Including managed VNet with Microsoft-managed private endpoints -- **Compliance requirements** — Regulations that require a different private networking topology - -## Deployment - -### Prerequisites - -1. Azure CLI installed and authenticated -2. Owner or Contributor role on the subscription -3. Sufficient quota for model deployment (gpt-4o-mini) - -### Deploy - -```bash -# Create resource group -az group create --name "rg-hybrid-agent-test" --location "westus2" - -# Deploy the template -az deployment group create \ - --resource-group "rg-hybrid-agent-test" \ - --template-file main.bicep \ - --parameters location="westus2" -``` - -### Verify Deployment - -```bash -# Check deployment status -az deployment group show \ - --resource-group "rg-hybrid-agent-test" \ - --name "main" \ - --query "properties.provisioningState" - -# List private endpoints (should see AI Search, Storage, Cosmos DB) -az network private-endpoint list \ - --resource-group "rg-hybrid-agent-test" \ - --output table -``` - -## Testing Agents with Private Resources - -### Option 1: Portal Testing - -If the Foundry resource has **public network access enabled**, you can test directly in the portal: - -1. Navigate to [Azure AI Foundry portal](https://ai.azure.com) -2. Select your project -3. Create an agent with AI Search tool -4. Test that the agent can query the private AI Search index - -If the Foundry resource has **public network access disabled** (default), you need to connect via VPN Gateway, ExpressRoute, or Azure Bastion before accessing the portal. See [Connecting to a Private Foundry Resource](#connecting-to-a-private-foundry-resource). - -### Option 2: SDK Testing - -See [tests/TESTING-GUIDE.md](tests/TESTING-GUIDE.md) for detailed SDK testing instructions. - -## MCP Server Deployment - -To deploy MCP servers on the private VNet: - -```bash -# Create Container Apps environment on mcp-subnet -az containerapp env create \ - --resource-group "rg-hybrid-agent-test" \ - --name "mcp-env" \ - --location "westus2" \ - --infrastructure-subnet-resource-id "" \ - --internal-only true - -# Deploy MCP server -az containerapp create \ - --resource-group "rg-hybrid-agent-test" \ - --name "my-mcp-server" \ - --environment "mcp-env" \ - --image "" \ - --target-port 8080 \ - --ingress external \ - --min-replicas 1 -``` - -Then configure private DNS zone for Container Apps (see TESTING-GUIDE.md Step 6.3). - -## Parameters - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `location` | Azure region | `eastus2` | -| `aiServices` | Base name for AI Services | `aiservices` | -| `modelName` | Model to deploy | `gpt-4o-mini` | -| `modelCapacity` | TPM capacity | `30` | -| `vnetName` | VNet name | `agent-vnet-test` | -| `agentSubnetName` | Subnet for AI Foundry (reserved) | `agent-subnet` | -| `peSubnetName` | Subnet for private endpoints | `pe-subnet` | -| `mcpSubnetName` | Subnet for MCP servers | `mcp-subnet` | - -## Cleanup - -```bash -# Delete all resources -az group delete --name "rg-hybrid-agent-test" --yes --no-wait -``` - -## Related Templates - -- [15-private-network-standard-agent-setup](../15-private-network-standard-agent-setup/) - Fully private setup (no public access) -- [40-basic-agent-setup](../40-basic-agent-setup/) - Basic agent setup without private networking -- [41-standard-agent-setup](../41-standard-agent-setup/) - Standard agent setup without private networking diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicep deleted file mode 100644 index 6a6d570df..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicep +++ /dev/null @@ -1,197 +0,0 @@ -@description('Location for the project resources.') -param location string = 'westus' - -@description('Name of the existing AI Services account') -param existingAccountName string - -@description('Resource group containing the AI Services account') -param accountResourceGroupName string = resourceGroup().name - -@description('Subscription ID containing the AI Services account') -param accountSubscriptionId string = subscription().subscriptionId - -@description('Name for the new project') -param projectName string - -@description('Description for the new project') -param projectDescription string = 'Additional AI Foundry project with network secured deployed Agent' - -@description('Display name for the new project') -param displayName string - -@description('Name for the project capability host') -param projectCapHost string = 'caphostproj' - -// Existing shared resources (from your original deployment) -@description('Name of the existing AI Search service') -param existingAiSearchName string - -@description('Resource group containing the AI Search service') -param aiSearchResourceGroupName string - -@description('Subscription ID containing the AI Search service') -param aiSearchSubscriptionId string - -@description('Name of the existing Storage Account') -param existingStorageName string - -@description('Resource group containing the Storage Account') -param storageResourceGroupName string - -@description('Subscription ID containing the Storage Account') -param storageSubscriptionId string - -@description('Name of the existing Cosmos DB account') -param existingCosmosDBName string - -@description('Resource group containing the Cosmos DB account') -param cosmosDBResourceGroupName string - -@description('Subscription ID containing the Cosmos DB account') -param cosmosDBSubscriptionId string - -// Create a short, unique suffix for this project -param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') -var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) -var finalProjectName = toLower('${projectName}${uniqueSuffix}') - -// Reference existing AI Services account -resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: existingAccountName - scope: resourceGroup(accountSubscriptionId, accountResourceGroupName) -} - -// Reference existing shared resources -resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = { - name: existingAiSearchName - scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName) -} - -resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { - name: existingStorageName - scope: resourceGroup(storageSubscriptionId, storageResourceGroupName) -} - -resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = { - name: existingCosmosDBName - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) -} - -// Create the new project using the unique connection module -module aiProject 'modules-network-secured/ai-project-identity-unique.bicep' = { - name: 'ai-${finalProjectName}-${uniqueSuffix}-deployment' - params: { - projectName: finalProjectName - projectDescription: projectDescription - displayName: displayName - location: location - - aiSearchName: existingAiSearchName - aiSearchServiceResourceGroupName: aiSearchResourceGroupName - aiSearchServiceSubscriptionId: aiSearchSubscriptionId - - cosmosDBName: existingCosmosDBName - cosmosDBSubscriptionId: cosmosDBSubscriptionId - cosmosDBResourceGroupName: cosmosDBResourceGroupName - - azureStorageName: existingStorageName - azureStorageSubscriptionId: storageSubscriptionId - azureStorageResourceGroupName: storageResourceGroupName - - accountName: existingAccountName - - // Pass unique suffix for connection names - uniqueConnectionSuffix: '-${finalProjectName}' - } -} - -module formatProjectWorkspaceId 'modules-network-secured/format-project-workspace-id.bicep' = { - name: 'format-project-workspace-id-${uniqueSuffix}-deployment' - params: { - projectWorkspaceId: aiProject.outputs.projectWorkspaceId - } -} - -// Assign storage account role -module storageAccountRoleAssignment 'modules-network-secured/azure-storage-account-role-assignment.bicep' = { - name: 'storage-${existingStorageName}-${uniqueSuffix}-deployment' - scope: resourceGroup(storageSubscriptionId, storageResourceGroupName) - params: { - azureStorageName: existingStorageName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } -} - -// Assign Cosmos DB account role -module cosmosAccountRoleAssignments 'modules-network-secured/cosmosdb-account-role-assignment.bicep' = { - name: 'cosmos-account-ra-${finalProjectName}-${uniqueSuffix}-deployment' - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) - params: { - cosmosDBName: existingCosmosDBName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } -} - -// Assign AI Search role -module aiSearchRoleAssignments 'modules-network-secured/ai-search-role-assignments.bicep' = { - name: 'ai-search-ra-${finalProjectName}-${uniqueSuffix}-deployment' - scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName) - params: { - aiSearchName: existingAiSearchName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } -} - -// Create capability host for the new project -module addProjectCapabilityHost 'modules-network-secured/add-project-capability-host.bicep' = { - name: 'capabilityHost-configuration-${uniqueSuffix}-deployment' - params: { - accountName: existingAccountName - projectName: aiProject.outputs.projectName - cosmosDBConnection: aiProject.outputs.cosmosDBConnection - azureStorageConnection: aiProject.outputs.azureStorageConnection - aiSearchConnection: aiProject.outputs.aiSearchConnection - projectCapHost: projectCapHost - } - dependsOn: [ - cosmosAccountRoleAssignments - storageAccountRoleAssignment - aiSearchRoleAssignments - ] -} - -// Assign storage container roles after capability host creation -module storageContainersRoleAssignment 'modules-network-secured/blob-storage-container-role-assignments-unique.bicep' = { - name: 'storage-containers-${uniqueSuffix}-deployment' - scope: resourceGroup(storageSubscriptionId, storageResourceGroupName) - params: { - aiProjectPrincipalId: aiProject.outputs.projectPrincipalId - storageName: existingStorageName - workspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid - uniqueSuffix: uniqueSuffix // Add this line - } - dependsOn: [ - addProjectCapabilityHost - ] -} - -// Assign Cosmos container roles after capability host creation -module cosmosContainerRoleAssignments 'modules-network-secured/cosmos-container-role-assignments.bicep' = { - name: 'cosmos-ra-${uniqueSuffix}-deployment' - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) - params: { - cosmosAccountName: existingCosmosDBName - projectWorkspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid - projectPrincipalId: aiProject.outputs.projectPrincipalId - } - dependsOn: [ - addProjectCapabilityHost - storageContainersRoleAssignment - ] -} - -// Outputs -output projectName string = aiProject.outputs.projectName -output projectPrincipalId string = aiProject.outputs.projectPrincipalId -output projectWorkspaceId string = aiProject.outputs.projectWorkspaceId -output capabilityHostName string = addProjectCapabilityHost.outputs.projectCapHost diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicepparam b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicepparam deleted file mode 100644 index 127907979..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/add-project.bicepparam +++ /dev/null @@ -1,29 +0,0 @@ -using './add-project.bicep' - -param location = 'westus' - -// New project details -param projectName = 'secondproject' -param projectDescription = 'Second AI Foundry project with network secured deployed Agent' -param displayName = 'Second Project' -param projectCapHost = 'caphostsecond' - -// Existing AI Services account details (from your original deployment) -// You'll need to get these from your existing deployment -param existingAccountName = '' // Replace with your actual account name -param accountResourceGroupName = '' // Your resource group -param accountSubscriptionId = '' - -// Existing shared resources (from your original deployment) -// You'll need to get these from your existing deployment outputs -param existingAiSearchName = '' // Replace with your actual search service name -param aiSearchResourceGroupName = '' // Your resource group -param aiSearchSubscriptionId = '' - -param existingStorageName = '' // Replace with your actual storage account name -param storageResourceGroupName = '' // Your resource group -param storageSubscriptionId = '' - -param existingCosmosDBName = '' // Replace with your actual Cosmos DB name -param cosmosDBResourceGroupName = '' // Your resource group -param cosmosDBSubscriptionId = '' diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.json deleted file mode 100644 index 460001bba..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.json +++ /dev/null @@ -1,2679 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3024624923779287280" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "eastus2", - "allowedValues": [ - "westus", - "eastus", - "eastus2", - "japaneast", - "francecentral", - "spaincentral", - "uaenorth", - "southcentralus", - "italynorth", - "germanywestcentral", - "brazilsouth", - "southafricanorth", - "australiaeast", - "swedencentral", - "canadaeast", - "westeurope", - "westus3", - "uksouth", - "southindia", - "koreacentral", - "polandcentral", - "switzerlandnorth", - "norwayeast" - ], - "metadata": { - "description": "Location for all resources." - } - }, - "aiServices": { - "type": "string", - "defaultValue": "aiservices", - "metadata": { - "description": "Name for your AI Services resource." - } - }, - "modelName": { - "type": "string", - "defaultValue": "gpt-4o", - "metadata": { - "description": "The name of the model you want to deploy" - } - }, - "modelFormat": { - "type": "string", - "defaultValue": "OpenAI", - "metadata": { - "description": "The provider of your model" - } - }, - "modelVersion": { - "type": "string", - "defaultValue": "2024-11-20", - "metadata": { - "description": "The version of your model" - } - }, - "modelSkuName": { - "type": "string", - "defaultValue": "GlobalStandard", - "metadata": { - "description": "The sku of your model deployment" - } - }, - "modelCapacity": { - "type": "int", - "defaultValue": 30, - "metadata": { - "description": "The tokens per minute (TPM) of your model deployment" - } - }, - "deploymentTimestamp": { - "type": "string", - "defaultValue": "[utcNow('yyyyMMddHHmmss')]" - }, - "firstProjectName": { - "type": "string", - "defaultValue": "project", - "metadata": { - "description": "Name for your project resource." - } - }, - "projectDescription": { - "type": "string", - "defaultValue": "A project for the AI Foundry account with network secured deployed Agent", - "metadata": { - "description": "This project will be a sub-resource of your account" - } - }, - "displayName": { - "type": "string", - "defaultValue": "network secured agent project", - "metadata": { - "description": "The display name of the project" - } - }, - "vnetName": { - "type": "string", - "defaultValue": "agent-vnet-test", - "metadata": { - "description": "Virtual Network name for the Agent to create new or existing virtual network" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet to create new or existing subnet for agents" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet to create new or existing subnet for private endpoints" - } - }, - "existingVnetResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Existing Virtual Network name Resource ID" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet (only used for new VNet)" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet. The default value is 192.168.0.0/24 but you can choose any size /26 or any class like 10.0.0.0 or 172.168.0.0" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - }, - "aiSearchResourceId": { - "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." - } - }, - "azureStorageAccountResourceId": { - "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." - } - }, - "azureCosmosDBAccountResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." - } - }, - "existingDnsZones": { - "type": "object", - "defaultValue": { - "privatelink.services.ai.azure.com": "", - "privatelink.openai.azure.com": "", - "privatelink.cognitiveservices.azure.com": "", - "privatelink.search.windows.net": "", - "privatelink.blob.core.windows.net": "", - "privatelink.documents.azure.com": "" - }, - "metadata": { - "description": "Object mapping DNS zone names to their resource group, or empty string to indicate creation" - } - }, - "dnsZoneNames": { - "type": "array", - "defaultValue": [ - "privatelink.services.ai.azure.com", - "privatelink.openai.azure.com", - "privatelink.cognitiveservices.azure.com", - "privatelink.search.windows.net", - "privatelink.blob.core.windows.net", - "privatelink.documents.azure.com" - ], - "metadata": { - "description": "Zone Names for Validation of existing Private Dns Zones" - } - }, - "projectCapHost": { - "type": "string", - "defaultValue": "caphostproj", - "metadata": { - "description": "The name of the project capability host to be created" - } - } - }, - "variables": { - "uniqueSuffix": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]", - "accountName": "[toLower(format('{0}{1}', parameters('aiServices'), variables('uniqueSuffix')))]", - "projectName": "[toLower(format('{0}{1}', parameters('firstProjectName'), variables('uniqueSuffix')))]", - "cosmosDBName": "[toLower(format('{0}{1}cosmosdb', parameters('aiServices'), variables('uniqueSuffix')))]", - "aiSearchName": "[toLower(format('{0}{1}search', parameters('aiServices'), variables('uniqueSuffix')))]", - "azureStorageName": "[toLower(format('{0}{1}storage', parameters('aiServices'), variables('uniqueSuffix')))]", - "storagePassedIn": "[not(equals(parameters('azureStorageAccountResourceId'), ''))]", - "searchPassedIn": "[not(equals(parameters('aiSearchResourceId'), ''))]", - "cosmosPassedIn": "[not(equals(parameters('azureCosmosDBAccountResourceId'), ''))]", - "existingVnetPassedIn": "[not(equals(parameters('existingVnetResourceId'), ''))]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "aiSearchServiceSubscriptionId": "[if(variables('searchPassedIn'), variables('acsParts')[2], subscription().subscriptionId)]", - "aiSearchServiceResourceGroupName": "[if(variables('searchPassedIn'), variables('acsParts')[4], resourceGroup().name)]", - "cosmosParts": "[split(parameters('azureCosmosDBAccountResourceId'), '/')]", - "cosmosDBSubscriptionId": "[if(variables('cosmosPassedIn'), variables('cosmosParts')[2], subscription().subscriptionId)]", - "cosmosDBResourceGroupName": "[if(variables('cosmosPassedIn'), variables('cosmosParts')[4], resourceGroup().name)]", - "storageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]", - "azureStorageSubscriptionId": "[if(variables('storagePassedIn'), variables('storageParts')[2], subscription().subscriptionId)]", - "azureStorageResourceGroupName": "[if(variables('storagePassedIn'), variables('storageParts')[4], resourceGroup().name)]", - "vnetParts": "[split(parameters('existingVnetResourceId'), '/')]", - "vnetSubscriptionId": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[2], subscription().subscriptionId)]", - "vnetResourceGroupName": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[4], resourceGroup().name)]", - "existingVnetName": "[if(variables('existingVnetPassedIn'), last(variables('vnetParts')), parameters('vnetName'))]", - "trimVnetName": "[trim(variables('existingVnetName'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "vnetName": { - "value": "[variables('trimVnetName')]" - }, - "useExistingVnet": { - "value": "[variables('existingVnetPassedIn')]" - }, - "existingVnetResourceGroupName": { - "value": "[variables('vnetResourceGroupName')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "vnetAddressPrefix": { - "value": "[parameters('vnetAddressPrefix')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - }, - "existingVnetSubscriptionId": { - "value": "[variables('vnetSubscriptionId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8505298823279202405" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region for the deployment" - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "The name of the virtual network" - } - }, - "useExistingVnet": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Indicates if an existing VNet should be used" - } - }, - "existingVnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID of the existing VNet (if different from current subscription)" - } - }, - "existingVnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name of the existing VNet (if different from current resource group)" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet (only used for new VNet)" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - } - }, - "resources": [ - { - "condition": "[not(parameters('useExistingVnet'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "vnet-deployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "vnetAddressPrefix": { - "value": "[parameters('vnetAddressPrefix')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4954184648131521061" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region for the deployment" - } - }, - "vnetName": { - "type": "string", - "defaultValue": "agents-vnet-test", - "metadata": { - "description": "The name of the virtual network" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Hub subnet" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - } - }, - "variables": { - "defaultVnetAddressPrefix": "192.168.0.0/16", - "vnetAddress": "[if(empty(parameters('vnetAddressPrefix')), variables('defaultVnetAddressPrefix'), parameters('vnetAddressPrefix'))]", - "agentSubnet": "[if(empty(parameters('agentSubnetPrefix')), cidrSubnet(variables('vnetAddress'), 24, 0), parameters('agentSubnetPrefix'))]", - "peSubnet": "[if(empty(parameters('peSubnetPrefix')), cidrSubnet(variables('vnetAddress'), 24, 1), parameters('peSubnetPrefix'))]" - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-05-01", - "name": "[parameters('vnetName')]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('vnetAddress')]" - ] - }, - "subnets": [ - { - "name": "[parameters('agentSubnetName')]", - "properties": { - "addressPrefix": "[variables('agentSubnet')]", - "delegations": [ - { - "name": "Microsoft.app/environments", - "properties": { - "serviceName": "Microsoft.App/environments" - } - } - ] - } - }, - { - "name": "[parameters('peSubnetName')]", - "properties": { - "addressPrefix": "[variables('peSubnet')]" - } - } - ] - } - } - ], - "outputs": { - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('agentSubnetName'))]" - }, - "peSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('peSubnetName'))]" - }, - "virtualNetworkName": { - "type": "string", - "value": "[parameters('vnetName')]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[resourceGroup().name]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[subscription().subscriptionId]" - } - } - } - } - }, - { - "condition": "[parameters('useExistingVnet')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "existing-vnet-deployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "vnetResourceGroupName": { - "value": "[parameters('existingVnetResourceGroupName')]" - }, - "vnetSubscriptionId": { - "value": "[parameters('existingVnetSubscriptionId')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3152324712046183852" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "The name of the existing virtual network" - } - }, - "vnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID of virtual network (if different from current subscription)" - } - }, - "vnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name of the existing VNet (if different from current resource group)" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet (only needed if creating new subnet)" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet (only needed if creating new subnet)" - } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('agent-subnet-{0}', uniqueString(deployment().name, parameters('agentSubnetName')))]", - "resourceGroup": "[parameters('vnetResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "subnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "addressPrefix": "[if(empty(parameters('agentSubnetPrefix')), createObject('value', cidrSubnet(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').addressSpace.addressPrefixes[0], 24, 0)), createObject('value', parameters('agentSubnetPrefix')))]", - "delegations": { - "value": [ - { - "name": "Microsoft.App/environments", - "properties": { - "serviceName": "Microsoft.App/environments" - } - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17043822047386586435" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the virtual network" - } - }, - "subnetName": { - "type": "string", - "metadata": { - "description": "Name of the subnet" - } - }, - "addressPrefix": { - "type": "string", - "metadata": { - "description": "Address prefix for the subnet" - } - }, - "delegations": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Array of subnet delegations" - } - } - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', parameters('vnetName'), parameters('subnetName'))]", - "properties": { - "addressPrefix": "[parameters('addressPrefix')]", - "delegations": "[parameters('delegations')]" - } - } - ], - "outputs": { - "subnetId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[0], split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[1])]" - }, - "subnetName": { - "type": "string", - "value": "[parameters('subnetName')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('pe-subnet-{0}', uniqueString(deployment().name, parameters('peSubnetName')))]", - "resourceGroup": "[parameters('vnetResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "subnetName": { - "value": "[parameters('peSubnetName')]" - }, - "addressPrefix": "[if(empty(parameters('peSubnetPrefix')), createObject('value', cidrSubnet(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').addressSpace.addressPrefixes[0], 24, 1)), createObject('value', parameters('peSubnetPrefix')))]", - "delegations": { - "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.39.26.7824", - "templateHash": "17043822047386586435" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the virtual network" - } - }, - "subnetName": { - "type": "string", - "metadata": { - "description": "Name of the subnet" - } - }, - "addressPrefix": { - "type": "string", - "metadata": { - "description": "Address prefix for the subnet" - } - }, - "delegations": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Array of subnet delegations" - } - } - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', parameters('vnetName'), parameters('subnetName'))]", - "properties": { - "addressPrefix": "[parameters('addressPrefix')]", - "delegations": "[parameters('delegations')]" - } - } - ], - "outputs": { - "subnetId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[0], split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[1])]" - }, - "subnetName": { - "type": "string", - "value": "[parameters('subnetName')]" - } - } - } - } - } - ], - "outputs": { - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('agentSubnetName'))]" - }, - "peSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('peSubnetName'))]" - }, - "virtualNetworkName": { - "type": "string", - "value": "[parameters('vnetName')]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[parameters('vnetResourceGroupName')]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[parameters('vnetSubscriptionId')]" - } - } - } - } - } - ], - "outputs": { - "virtualNetworkName": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkName.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkName.value)]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkId.value)]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkSubscriptionId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkSubscriptionId.value)]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkResourceGroup.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkResourceGroup.value)]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.agentSubnetId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.agentSubnetId.value)]" - }, - "peSubnetId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.peSubnetId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.peSubnetId.value)]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "accountName": { - "value": "[variables('accountName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "modelName": { - "value": "[parameters('modelName')]" - }, - "modelFormat": { - "value": "[parameters('modelFormat')]" - }, - "modelVersion": { - "value": "[parameters('modelVersion')]" - }, - "modelSkuName": { - "value": "[parameters('modelSkuName')]" - }, - "modelCapacity": { - "value": "[parameters('modelCapacity')]" - }, - "agentSubnetId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-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.39.26.7824", - "templateHash": "854097619778148359" - } - }, - "parameters": { - "accountName": { - "type": "string" - }, - "location": { - "type": "string" - }, - "modelName": { - "type": "string" - }, - "modelFormat": { - "type": "string" - }, - "modelVersion": { - "type": "string" - }, - "modelSkuName": { - "type": "string" - }, - "modelCapacity": { - "type": "int" - }, - "agentSubnetId": { - "type": "string" - }, - "networkInjection": { - "type": "string", - "defaultValue": "true" - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2025-04-01-preview", - "name": "[parameters('accountName')]", - "location": "[parameters('location')]", - "sku": { - "name": "S0" - }, - "kind": "AIServices", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "allowProjectManagement": true, - "customSubDomainName": "[parameters('accountName')]", - "networkAcls": { - "defaultAction": "Deny", - "virtualNetworkRules": [], - "ipRules": [], - "bypass": "AzureServices" - }, - "publicNetworkAccess": "Disabled", - "networkInjections": "[if(equals(parameters('networkInjection'), 'true'), createArray(createObject('scenario', 'agent', 'subnetArmId', parameters('agentSubnetId'), 'useMicrosoftManagedNetwork', false())), null())]", - "disableLocalAuth": false - } - }, - { - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('accountName'), 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('accountName'))]" - ] - } - ], - "outputs": { - "accountName": { - "type": "string", - "value": "[parameters('accountName')]" - }, - "accountID": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName'))]" - }, - "accountTarget": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName')), '2025-04-01-preview').endpoint]" - }, - "accountPrincipalId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName')), '2025-04-01-preview', 'full').identity.principalId]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiSearchResourceId": { - "value": "[parameters('aiSearchResourceId')]" - }, - "azureStorageAccountResourceId": { - "value": "[parameters('azureStorageAccountResourceId')]" - }, - "azureCosmosDBAccountResourceId": { - "value": "[parameters('azureCosmosDBAccountResourceId')]" - }, - "existingDnsZones": { - "value": "[parameters('existingDnsZones')]" - }, - "dnsZoneNames": { - "value": "[parameters('dnsZoneNames')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7641310640078958122" - } - }, - "parameters": { - "aiSearchResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the AI Search Service." - } - }, - "azureStorageAccountResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Azure Storage Account." - } - }, - "azureCosmosDBAccountResourceId": { - "type": "string", - "metadata": { - "description": "ResourceId of Cosmos DB Account" - } - }, - "existingDnsZones": { - "type": "object", - "metadata": { - "description": "Object mapping DNS zone names to their resource group, or empty string to indicate creation" - } - }, - "dnsZoneNames": { - "type": "array", - "metadata": { - "description": "List of private DNS zone names to validate" - } - } - }, - "variables": { - "storagePassedIn": "[not(equals(parameters('azureStorageAccountResourceId'), ''))]", - "searchPassedIn": "[not(equals(parameters('aiSearchResourceId'), ''))]", - "cosmosPassedIn": "[not(equals(parameters('azureCosmosDBAccountResourceId'), ''))]", - "storageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]", - "azureStorageSubscriptionId": "[if(and(variables('storagePassedIn'), greater(length(variables('storageParts')), 2)), variables('storageParts')[2], subscription().subscriptionId)]", - "azureStorageResourceGroupName": "[if(and(variables('storagePassedIn'), greater(length(variables('storageParts')), 4)), variables('storageParts')[4], resourceGroup().name)]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "aiSearchServiceSubscriptionId": "[if(and(variables('searchPassedIn'), greater(length(variables('acsParts')), 2)), variables('acsParts')[2], subscription().subscriptionId)]", - "aiSearchServiceResourceGroupName": "[if(and(variables('searchPassedIn'), greater(length(variables('acsParts')), 4)), variables('acsParts')[4], resourceGroup().name)]", - "cosmosParts": "[split(parameters('azureCosmosDBAccountResourceId'), '/')]", - "cosmosDBSubscriptionId": "[if(and(variables('cosmosPassedIn'), greater(length(variables('cosmosParts')), 2)), variables('cosmosParts')[2], subscription().subscriptionId)]", - "cosmosDBResourceGroupName": "[if(and(variables('cosmosPassedIn'), greater(length(variables('cosmosParts')), 4)), variables('cosmosParts')[4], resourceGroup().name)]", - "dnsZoneTypes": [ - "Microsoft.Network/privateDnsZones" - ] - }, - "resources": [], - "outputs": { - "aiSearchExists": { - "type": "bool", - "value": "[and(variables('searchPassedIn'), equals(last(split(parameters('aiSearchResourceId'), '/')), variables('acsParts')[8]))]" - }, - "cosmosDBExists": { - "type": "bool", - "value": "[and(variables('cosmosPassedIn'), equals(last(split(parameters('azureCosmosDBAccountResourceId'), '/')), variables('cosmosParts')[8]))]" - }, - "azureStorageExists": { - "type": "bool", - "value": "[and(variables('storagePassedIn'), equals(last(split(parameters('azureStorageAccountResourceId'), '/')), variables('storageParts')[8]))]" - }, - "aiSearchServiceSubscriptionId": { - "type": "string", - "value": "[variables('aiSearchServiceSubscriptionId')]" - }, - "aiSearchServiceResourceGroupName": { - "type": "string", - "value": "[variables('aiSearchServiceResourceGroupName')]" - }, - "cosmosDBSubscriptionId": { - "type": "string", - "value": "[variables('cosmosDBSubscriptionId')]" - }, - "cosmosDBResourceGroupName": { - "type": "string", - "value": "[variables('cosmosDBResourceGroupName')]" - }, - "azureStorageSubscriptionId": { - "type": "string", - "value": "[variables('azureStorageSubscriptionId')]" - }, - "azureStorageResourceGroupName": { - "type": "string", - "value": "[variables('azureStorageResourceGroupName')]" - }, - "dnsZoneExists": { - "type": "array", - "copy": { - "count": "[length(parameters('dnsZoneNames'))]", - "input": { - "name": "[parameters('dnsZoneNames')[copyIndex()]]", - "exists": "[not(empty(parameters('existingDnsZones')[parameters('dnsZoneNames')[copyIndex()]]))]" - } - } - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('dependencies-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "azureStorageName": { - "value": "[variables('azureStorageName')]" - }, - "aiSearchName": { - "value": "[variables('aiSearchName')]" - }, - "cosmosDBName": { - "value": "[variables('cosmosDBName')]" - }, - "aiSearchResourceId": { - "value": "[parameters('aiSearchResourceId')]" - }, - "aiSearchExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchExists.value]" - }, - "azureStorageAccountResourceId": { - "value": "[parameters('azureStorageAccountResourceId')]" - }, - "azureStorageExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageExists.value]" - }, - "cosmosDBResourceId": { - "value": "[parameters('azureCosmosDBAccountResourceId')]" - }, - "cosmosDBExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBExists.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.39.26.7824", - "templateHash": "2754228344238136934" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region of the deployment" - } - }, - "aiSearchName": { - "type": "string", - "metadata": { - "description": "The name of the AI Search resource" - } - }, - "azureStorageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the new Cosmos DB account" - } - }, - "aiSearchResourceId": { - "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." - } - }, - "azureStorageAccountResourceId": { - "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." - } - }, - "cosmosDBResourceId": { - "type": "string", - "metadata": { - "description": "The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." - } - }, - "aiSearchExists": { - "type": "bool" - }, - "azureStorageExists": { - "type": "bool" - }, - "cosmosDBExists": { - "type": "bool" - }, - "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": { - "cosmosParts": "[split(parameters('cosmosDBResourceId'), '/')]", - "canaryRegions": [ - "eastus2euap", - "centraluseuap" - ], - "cosmosDbRegion": "[if(contains(variables('canaryRegions'), parameters('location')), 'westus', parameters('location'))]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "azureStorageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]" - }, - "resources": [ - { - "condition": "[not(parameters('cosmosDBExists'))]", - "type": "Microsoft.DocumentDB/databaseAccounts", - "apiVersion": "2024-11-15", - "name": "[parameters('cosmosDBName')]", - "location": "[variables('cosmosDbRegion')]", - "kind": "GlobalDocumentDB", - "properties": { - "consistencyPolicy": { - "defaultConsistencyLevel": "Session" - }, - "disableLocalAuth": true, - "enableAutomaticFailover": false, - "enableMultipleWriteLocations": false, - "publicNetworkAccess": "Disabled", - "enableFreeTier": false, - "locations": [ - { - "locationName": "[parameters('location')]", - "failoverPriority": 0, - "isZoneRedundant": false - } - ], - "databaseAccountOfferType": "Standard" - } - }, - { - "condition": "[not(parameters('aiSearchExists'))]", - "type": "Microsoft.Search/searchServices", - "apiVersion": "2024-06-01-preview", - "name": "[parameters('aiSearchName')]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "disableLocalAuth": false, - "authOptions": { - "aadOrApiKey": { - "aadAuthFailureMode": "http401WithBearerChallenge" - } - }, - "encryptionWithCmk": { - "enforcement": "Unspecified" - }, - "hostingMode": "default", - "partitionCount": 1, - "publicNetworkAccess": "disabled", - "replicaCount": 1, - "semanticSearch": "disabled", - "networkRuleSet": { - "bypass": "None", - "ipRules": [] - } - }, - "sku": { - "name": "standard" - } - }, - { - "condition": "[not(parameters('azureStorageExists'))]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2023-05-01", - "name": "[parameters('azureStorageName')]", - "location": "[parameters('location')]", - "kind": "StorageV2", - "sku": "[parameters('sku')]", - "properties": { - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Disabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [] - }, - "allowSharedKeyAccess": false - } - } - ], - "outputs": { - "aiSearchName": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), variables('acsParts')[8], parameters('aiSearchName'))]" - }, - "aiSearchID": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), 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(parameters('aiSearchExists'), variables('acsParts')[4], resourceGroup().name)]" - }, - "aiSearchServiceSubscriptionId": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), variables('acsParts')[2], subscription().subscriptionId)]" - }, - "azureStorageName": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[8], parameters('azureStorageName'))]" - }, - "azureStorageId": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageParts')[2], variables('azureStorageParts')[4]), 'Microsoft.Storage/storageAccounts', variables('azureStorageParts')[8]), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]" - }, - "azureStorageResourceGroupName": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[4], resourceGroup().name)]" - }, - "azureStorageSubscriptionId": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[2], subscription().subscriptionId)]" - }, - "cosmosDBName": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[8], parameters('cosmosDBName'))]" - }, - "cosmosDBId": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('cosmosParts')[2], variables('cosmosParts')[4]), 'Microsoft.DocumentDB/databaseAccounts', variables('cosmosParts')[8]), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]" - }, - "cosmosDBResourceGroupName": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[4], resourceGroup().name)]" - }, - "cosmosDBSubscriptionId": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[2], subscription().subscriptionId)]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-private-endpoint', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiAccountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.value]" - }, - "aiSearchName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "storageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "vnetName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkName.value]" - }, - "peSubnetName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.peSubnetName.value]" - }, - "suffix": { - "value": "[variables('uniqueSuffix')]" - }, - "vnetResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkResourceGroup.value]" - }, - "vnetSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkSubscriptionId.value]" - }, - "cosmosDBSubscriptionId": { - "value": "[variables('cosmosDBSubscriptionId')]" - }, - "cosmosDBResourceGroupName": { - "value": "[variables('cosmosDBResourceGroupName')]" - }, - "aiSearchSubscriptionId": { - "value": "[variables('aiSearchServiceSubscriptionId')]" - }, - "aiSearchResourceGroupName": { - "value": "[variables('aiSearchServiceResourceGroupName')]" - }, - "storageAccountResourceGroupName": { - "value": "[variables('azureStorageResourceGroupName')]" - }, - "storageAccountSubscriptionId": { - "value": "[variables('azureStorageSubscriptionId')]" - }, - "existingDnsZones": { - "value": "[parameters('existingDnsZones')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8094529554453089222" - } - }, - "parameters": { - "aiAccountName": { - "type": "string", - "metadata": { - "description": "Name of the AI Foundry account" - } - }, - "aiSearchName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search service" - } - }, - "storageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the Cosmos DB account" - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the Vnet" - } - }, - "peSubnetName": { - "type": "string", - "metadata": { - "description": "Name of the Customer subnet" - } - }, - "suffix": { - "type": "string", - "metadata": { - "description": "Suffix for unique resource names" - } - }, - "vnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for existing Virtual Network (if different from current resource group)" - } - }, - "vnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Virtual Network" - } - }, - "storageAccountResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for Storage Account" - } - }, - "storageAccountSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Storage account" - } - }, - "aiSearchSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for AI Search service" - } - }, - "aiSearchResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for AI Search service" - } - }, - "cosmosDBSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Cosmos DB account" - } - }, - "cosmosDBResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource group name for Cosmos DB account" - } - }, - "existingDnsZones": { - "type": "object", - "defaultValue": { - "privatelink.services.ai.azure.com": "", - "privatelink.openai.azure.com": "", - "privatelink.cognitiveservices.azure.com": "", - "privatelink.search.windows.net": "", - "[format('privatelink.blob.{0}', environment().suffixes.storage)]": "", - "privatelink.documents.azure.com": "" - }, - "metadata": { - "description": "Map of DNS zone FQDNs to resource group names. If provided, reference existing DNS zones in this resource group instead of creating them." - } - } - }, - "variables": { - "aiServicesDnsZoneName": "privatelink.services.ai.azure.com", - "openAiDnsZoneName": "privatelink.openai.azure.com", - "cognitiveServicesDnsZoneName": "privatelink.cognitiveservices.azure.com", - "aiSearchDnsZoneName": "privatelink.search.windows.net", - "storageDnsZoneName": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "cosmosDBDnsZoneName": "privatelink.documents.azure.com", - "aiServicesDnsZoneRG": "[parameters('existingDnsZones')[variables('aiServicesDnsZoneName')]]", - "openAiDnsZoneRG": "[parameters('existingDnsZones')[variables('openAiDnsZoneName')]]", - "cognitiveServicesDnsZoneRG": "[parameters('existingDnsZones')[variables('cognitiveServicesDnsZoneName')]]", - "aiSearchDnsZoneRG": "[parameters('existingDnsZones')[variables('aiSearchDnsZoneName')]]", - "storageDnsZoneRG": "[parameters('existingDnsZones')[variables('storageDnsZoneName')]]", - "cosmosDBDnsZoneRG": "[parameters('existingDnsZones')[variables('cosmosDBDnsZoneName')]]", - "aiServicesDnsZoneId": "[if(empty(variables('aiServicesDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('aiServicesDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName')))]", - "openAiDnsZoneId": "[if(empty(variables('openAiDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('openAiDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName')))]", - "cognitiveServicesDnsZoneId": "[if(empty(variables('cognitiveServicesDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('cognitiveServicesDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName')))]", - "aiSearchDnsZoneId": "[if(empty(variables('aiSearchDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('aiSearchDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName')))]", - "storageDnsZoneId": "[if(empty(variables('storageDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('storageDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('storageDnsZoneName')))]", - "cosmosDBDnsZoneId": "[if(empty(variables('cosmosDBDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('cosmosDBDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName')))]" - }, - "resources": [ - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('aiAccountName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('aiAccountName'))]", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiAccountName'))]", - "groupIds": [ - "account" - ] - } - } - ] - } - }, - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('aiSearchName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('aiSearchName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchSubscriptionId'), parameters('aiSearchResourceGroupName')), '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": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('storageName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('storageAccountSubscriptionId'), parameters('storageAccountResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('storageName'))]", - "groupIds": [ - "blob" - ] - } - } - ] - } - }, - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('cosmosDBName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('cosmosDBName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", - "groupIds": [ - "Sql" - ] - } - } - ] - } - }, - { - "condition": "[empty(variables('aiServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('aiServicesDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('openAiDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('openAiDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('cognitiveServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('cognitiveServicesDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('aiSearchDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('aiSearchDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('storageDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('storageDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('cosmosDBDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('cosmosDBDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('aiServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('aiServicesDnsZoneName'), format('aiServices-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('openAiDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('openAiDnsZoneName'), format('aiServicesOpenAI-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('cognitiveServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('cognitiveServicesDnsZoneName'), format('aiServicesCognitiveServices-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('aiSearchDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('aiSearchDnsZoneName'), format('aiSearch-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('storageDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('storageDnsZoneName'), format('storage-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('cosmosDBDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('cosmosDBDnsZoneName'), format('cosmosDB-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName'))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('aiAccountName')), format('{0}-dns-group', parameters('aiAccountName')))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('{0}-dns-aiserv-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('aiServicesDnsZoneId')]" - } - }, - { - "name": "[format('{0}-dns-openai-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('openAiDnsZoneId')]" - } - }, - { - "name": "[format('{0}-dns-cogserv-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('cognitiveServicesDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiAccountName')))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('aiServicesDnsZoneName'), format('aiServices-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('cognitiveServicesDnsZoneName'), format('aiServicesCognitiveServices-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('openAiDnsZoneName'), format('aiServicesOpenAI-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName'))]" - ] - }, - { - "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": "[variables('aiSearchDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('aiSearchDnsZoneName'), format('aiSearch-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiSearchName')))]" - ] - }, - { - "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": "[variables('storageDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('storageDnsZoneName'), format('storage-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('storageName')))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('cosmosDBName')), format('{0}-dns-group', parameters('cosmosDBName')))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('{0}-dns-config', parameters('cosmosDBName'))]", - "properties": { - "privateDnsZoneId": "[variables('cosmosDBDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('cosmosDBDnsZoneName'), format('cosmosDB-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('cosmosDBName')))]" - ] - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "projectName": { - "value": "[variables('projectName')]" - }, - "projectDescription": { - "value": "[parameters('projectDescription')]" - }, - "displayName": { - "value": "[parameters('displayName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "aiSearchName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "aiSearchServiceResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchServiceResourceGroupName.value]" - }, - "aiSearchServiceSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchServiceSubscriptionId.value]" - }, - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "cosmosDBSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBSubscriptionId.value]" - }, - "cosmosDBResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBResourceGroupName.value]" - }, - "azureStorageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "azureStorageSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageSubscriptionId.value]" - }, - "azureStorageResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageResourceGroupName.value]" - }, - "accountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.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.39.26.7824", - "templateHash": "5095087340309076800" - } - }, - "parameters": { - "accountName": { - "type": "string" - }, - "location": { - "type": "string" - }, - "projectName": { - "type": "string" - }, - "projectDescription": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "aiSearchName": { - "type": "string" - }, - "aiSearchServiceResourceGroupName": { - "type": "string" - }, - "aiSearchServiceSubscriptionId": { - "type": "string" - }, - "cosmosDBName": { - "type": "string" - }, - "cosmosDBSubscriptionId": { - "type": "string" - }, - "cosmosDBResourceGroupName": { - "type": "string" - }, - "azureStorageName": { - "type": "string" - }, - "azureStorageSubscriptionId": { - "type": "string" - }, - "azureStorageResourceGroupName": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('cosmosDBName'))]", - "properties": { - "category": "CosmosDB", - "target": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), '2024-12-01-preview').documentEndpoint]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", - "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), '2024-12-01-preview', 'full').location]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('azureStorageName'))]", - "properties": { - "category": "AzureStorageAccount", - "target": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName')), '2023-05-01').primaryEndpoints.blob]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", - "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName')), '2023-05-01', 'full').location]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('aiSearchName'))]", - "properties": { - "category": "CognitiveSearch", - "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchServiceSubscriptionId'), parameters('aiSearchServiceResourceGroupName')), 'Microsoft.Search/searchServices', parameters('aiSearchName'))]", - "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.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('accountName'), parameters('projectName'))]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "description": "[parameters('projectDescription')]", - "displayName": "[parameters('displayName')]" - } - } - ], - "outputs": { - "projectName": { - "type": "string", - "value": "[parameters('projectName')]" - }, - "projectId": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - }, - "projectPrincipalId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName')), '2025-04-01-preview', 'full').identity.principalId]" - }, - "projectWorkspaceId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName')), '2025-04-01-preview').internalId]" - }, - "cosmosDBConnection": { - "type": "string", - "value": "[parameters('cosmosDBName')]" - }, - "azureStorageConnection": { - "type": "string", - "value": "[parameters('azureStorageName')]" - }, - "aiSearchConnection": { - "type": "string", - "value": "[parameters('aiSearchName')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "projectWorkspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceId.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.39.26.7824", - "templateHash": "6910483561575524105" - } - }, - "parameters": { - "projectWorkspaceId": { - "type": "string" - } - }, - "variables": { - "part1": "[substring(parameters('projectWorkspaceId'), 0, 8)]", - "part2": "[substring(parameters('projectWorkspaceId'), 8, 4)]", - "part3": "[substring(parameters('projectWorkspaceId'), 12, 4)]", - "part4": "[substring(parameters('projectWorkspaceId'), 16, 4)]", - "part5": "[substring(parameters('projectWorkspaceId'), 20, 12)]", - "formattedGuid": "[format('{0}-{1}-{2}-{3}-{4}', variables('part1'), variables('part2'), variables('part3'), variables('part4'), variables('part5'))]" - }, - "resources": [], - "outputs": { - "projectWorkspaceIdGuid": { - "type": "string", - "value": "[variables('formattedGuid')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('storage-{0}-{1}-deployment', variables('azureStorageName'), variables('uniqueSuffix'))]", - "subscriptionId": "[variables('azureStorageSubscriptionId')]", - "resourceGroup": "[variables('azureStorageResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "azureStorageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "14683840003859985069" - } - }, - "parameters": { - "azureStorageName": { - "type": "string" - }, - "projectPrincipalId": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", - "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('cosmos-account-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('cosmosDBSubscriptionId')]", - "resourceGroup": "[variables('cosmosDBResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "25128059954858801" - } - }, - "parameters": { - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the Cosmos DB resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the AI project" - } - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", - "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('ai-search-ra-{0}-deployment', 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}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "7968115481508840" - } - }, - "parameters": { - "aiSearchName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal 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('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "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('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "accountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.value]" - }, - "projectName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectName.value]" - }, - "cosmosDBConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBConnection.value]" - }, - "azureStorageConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageConnection.value]" - }, - "aiSearchConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchConnection.value]" - }, - "projectCapHost": { - "value": "[parameters('projectCapHost')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17458377866351620215" - } - }, - "parameters": { - "cosmosDBConnection": { - "type": "string" - }, - "azureStorageConnection": { - "type": "string" - }, - "aiSearchConnection": { - "type": "string" - }, - "projectName": { - "type": "string" - }, - "accountName": { - "type": "string" - }, - "projectCapHost": { - "type": "string" - } - }, - "variables": { - "threadConnections": [ - "[format('{0}', parameters('cosmosDBConnection'))]" - ], - "storageConnections": [ - "[format('{0}', parameters('azureStorageConnection'))]" - ], - "vectorStoreConnections": [ - "[format('{0}', parameters('aiSearchConnection'))]" - ] - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects/capabilityHosts", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('projectCapHost'))]", - "properties": { - "capabilityHostKind": "Agents", - "vectorStoreConnections": "[variables('vectorStoreConnections')]", - "storageConnections": "[variables('storageConnections')]", - "threadStorageConnections": "[variables('threadConnections')]" - } - } - ], - "outputs": { - "projectCapHost": { - "type": "string", - "value": "[parameters('projectCapHost')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', 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-ra-{0}-deployment', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('cosmosDBSubscriptionId'), variables('cosmosDBResourceGroupName')), 'Microsoft.Resources/deployments', format('cosmos-account-ra-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageSubscriptionId'), variables('azureStorageResourceGroupName')), 'Microsoft.Resources/deployments', format('storage-{0}-{1}-deployment', variables('azureStorageName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('storage-containers-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('azureStorageSubscriptionId')]", - "resourceGroup": "[variables('azureStorageResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiProjectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.value]" - }, - "storageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "workspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceIdGuid.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.39.26.7824", - "templateHash": "13874725855824693255" - } - }, - "parameters": { - "storageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "aiProjectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the AI Project" - } - }, - "workspaceId": { - "type": "string", - "metadata": { - "description": "Workspace Id of the AI Project" - } - } - }, - "variables": { - "conditionStr": "[format('((!(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read''}}) AND !(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action''}}) AND !(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write''}}) ) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase ''{0}'' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase ''*-azureml-agent''))', parameters('workspaceId'))]" - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", - "name": "[guid(resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]", - "properties": { - "principalId": "[parameters('aiProjectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", - "principalType": "ServicePrincipal", - "conditionVersion": "2.0", - "condition": "[variables('conditionStr')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('cosmos-containers-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('cosmosDBSubscriptionId')]", - "resourceGroup": "[variables('cosmosDBResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "cosmosAccountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "projectWorkspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceIdGuid.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "17187611271934567223" - } - }, - "parameters": { - "cosmosAccountName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Project name" - } - }, - "projectWorkspaceId": { - "type": "string" - } - }, - "variables": { - "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosAccountName'), '00000000-0000-0000-0000-000000000002')]", - "accountScope": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DocumentDB/databaseAccounts/{2}/dbs/enterprise_memory', subscription().subscriptionId, resourceGroup().name, parameters('cosmosAccountName'))]" - }, - "resources": [ - { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", - "apiVersion": "2022-05-15", - "name": "[format('{0}/{1}', parameters('cosmosAccountName'), guid(parameters('projectWorkspaceId'), parameters('cosmosAccountName'), variables('roleDefinitionId'), parameters('projectPrincipalId')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[variables('roleDefinitionId')]", - "scope": "[variables('accountScope')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageSubscriptionId'), variables('azureStorageResourceGroupName')), 'Microsoft.Resources/deployments', format('storage-containers-ra-{0}-deployment', variables('uniqueSuffix')))]" - ] - } - ] -} \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.parameters.json b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.parameters.json deleted file mode 100644 index f3621743e..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/azuredeploy.parameters.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "location": { - "value": "eastus2" - }, - "aiServices": { - "value": "" - }, - "modelName": { - "value": "" - }, - "modelFormat": { - "value": "" - }, - "modelVersion": { - "value": "" - }, - "modelSkuName": { - "value": "" - }, - "modelCapacity": { - "value": 0 - }, - "deploymentTimestamp": { - "value": "" - }, - "firstProjectName": { - "value": "" - }, - "projectDescription": { - "value": "" - }, - "displayName": { - "value": "" - }, - "vnetName": { - "value": "" - }, - "agentSubnetName": { - "value": "" - }, - "peSubnetName": { - "value": "" - }, - "existingVnetResourceId": { - "value": "" - }, - "vnetAddressPrefix": { - "value": "" - }, - "agentSubnetPrefix": { - "value": "" - }, - "peSubnetPrefix": { - "value": "" - }, - "aiSearchResourceId": { - "value": "" - }, - "azureStorageAccountResourceId": { - "value": "" - }, - "azureCosmosDBAccountResourceId": { - "value": "" - }, - "projectCapHost": { - "value": "" - }, - "existingDnsZones": { - "value": { - "privatelink.services.ai.azure.com": "", - "privatelink.openai.azure.com": "", - "privatelink.cognitiveservices.azure.com": "", - "privatelink.search.windows.net": "", - "privatelink.blob.core.windows.net": "", - "privatelink.documents.azure.com": "" - } - } - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/createCapHost.sh b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/createCapHost.sh deleted file mode 100644 index 00de21300..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/createCapHost.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash - -# Script to create the account capability host - -# Prompt for required information -read -p "Enter Subscription ID: " subscription_id -read -p "Enter Resource Group name: " resource_group -read -p "Enter Foundry Account or Project name: " account_name -read -p "Enter CapabilityHost name: " caphost_name -read -p "Enter Customer full ARM subnet ResourceId: " subnet_resource_id - -# Get Azure access token -echo "Getting Azure access token..." -access_token=$(az account get-access-token --query accessToken -o tsv) - -if [ -z "$access_token" ]; then - echo "Error: Failed to get access token. Please make sure you're logged in with 'az login'" - exit 1 -fi - -# Construct the API URL -api_url="https://management.azure.com/subscriptions/${subscription_id}/resourceGroups/${resource_group}/providers/Microsoft.CognitiveServices/accounts/${account_name}/capabilityHosts/${caphost_name}?api-version=2025-04-01-preview" - -echo "Creating capability host: ${caphost_name}" -echo "API URL: ${api_url}" - -# Send PUT request and capture headers -echo "Sending PUT request..." -response_headers=$(mktemp) - -read -r -d '' BODY < Linked to Agent Subnet for secure runtime execution', ha='center', va='center', - fontsize=9, color=COLORS['text_dark']) - ax.text(7, 2.6, '(From Layer 1: VNet Agent Subnet)', ha='center', va='center', - fontsize=9, color=COLORS['primary'], style='italic') - - plt.tight_layout() - plt.savefig('layer3_ai_services.png', dpi=150, bbox_inches='tight', - facecolor='white', edgecolor='none') - plt.close() - print("Generated layer3_ai_services.png") - - -# ============================================================================= -# LAYER 4: Project + Connections -# ============================================================================= -def generate_layer4(): - fig, ax = setup_figure('Layer 4: Project & Service Connections', figsize=(14, 10)) - - # Parent account container (dashed) - parent = FancyBboxPatch( - (0.5, 1), 13, 6.3, - boxstyle="round,pad=0.02,rounding_size=0.15", - facecolor=COLORS['bg_light'], - edgecolor=COLORS['border_light'], - linewidth=2, - linestyle='--' - ) - ax.add_patch(parent) - ax.text(7, 7.1, 'AI Services Account (Parent)', ha='center', va='center', - fontsize=10, color=COLORS['primary'], style='italic') - - # Project box - draw_box(ax, 1, 1.5, 12, 5.3, COLORS['primary'], COLORS['primary_dark'], 3) - ax.text(7, 6.5, 'FOUNDRY PROJECT', ha='center', va='center', - fontsize=14, fontweight='bold', color=COLORS['text_light']) - ax.text(7, 6.1, 'Microsoft.CognitiveServices/accounts/projects', ha='center', va='center', - fontsize=9, color=COLORS['bg_light'], family='monospace') - - # Identity box - draw_box(ax, 1.5, 5, 3, 0.9, COLORS['white'], COLORS['border_light'], 1.5) - ax.text(3, 5.6, 'Identity (MSI)', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - ax.text(3, 5.25, 'System-Assigned', ha='center', va='center', - fontsize=8, color=COLORS['text_dark']) - - # Connections header - ax.text(7, 4.6, '-- Service Connections (AAD Auth) --', ha='center', va='center', - fontsize=11, fontweight='bold', color=COLORS['text_light']) - - # Connection boxes - conn_y = 3.0 - conn_h = 1.2 - - # Cosmos Connection - draw_box(ax, 1.5, conn_y, 3.5, conn_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(3.25, conn_y + 0.85, 'CosmosDB Connection', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - ax.text(3.25, conn_y + 0.4, 'category: CosmosDB', ha='center', va='center', - fontsize=8, color=COLORS['text_dark'], family='monospace') - - # Storage Connection - draw_box(ax, 5.25, conn_y, 3.5, conn_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(7, conn_y + 0.85, 'Storage Connection', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - ax.text(7, conn_y + 0.4, 'category: AzureStorage', ha='center', va='center', - fontsize=8, color=COLORS['text_dark'], family='monospace') - - # AI Search Connection - draw_box(ax, 9, conn_y, 3.5, conn_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(10.75, conn_y + 0.85, 'AI Search Connection', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - ax.text(10.75, conn_y + 0.4, 'category: CognitiveSearch', ha='center', va='center', - fontsize=8, color=COLORS['text_dark'], family='monospace') - - # Key insight box - draw_box(ax, 1.5, 1.7, 11, 0.9, COLORS['white'], COLORS['highlight'], 2) - ax.text(7, 2.15, 'KEY INSIGHT: Connections store target endpoints + auth method.', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - - plt.tight_layout() - plt.savefig('layer4_project_connections.png', dpi=150, bbox_inches='tight', - facecolor='white', edgecolor='none') - plt.close() - print("Generated layer4_project_connections.png") - - -# ============================================================================= -# LAYER 5: Capability Host -# ============================================================================= -def generate_layer5(): - fig, ax = setup_figure('Layer 5: Capability Host - The Activator', figsize=(14, 10)) - - # Main capability host box - draw_box(ax, 0.5, 2.8, 13, 4.5, COLORS['primary'], COLORS['primary_dark'], 3) - ax.text(7, 7, 'PROJECT CAPABILITY HOST', ha='center', va='center', - fontsize=16, fontweight='bold', color=COLORS['text_light']) - ax.text(7, 6.55, 'Microsoft.CognitiveServices/accounts/projects/capabilityHosts', ha='center', va='center', - fontsize=9, color=COLORS['bg_light'], family='monospace') - - # capabilityHostKind box - draw_box(ax, 1, 5.5, 3.5, 1, COLORS['white'], COLORS['border_light'], 2) - ax.text(2.75, 6.15, 'capabilityHostKind', ha='center', va='center', - fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - ax.text(2.75, 5.75, '"Agents"', ha='center', va='center', - fontsize=11, fontweight='bold', color=COLORS['primary'], family='monospace') - - # Connection bindings header - ax.text(7, 5.1, '-- Connection Bindings --', ha='center', va='center', - fontsize=11, fontweight='bold', color=COLORS['text_light']) - - bind_y = 3.9 - bind_h = 0.9 - - # Vector Store Connections - draw_box(ax, 1, bind_y, 3.8, bind_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(2.9, bind_y + 0.6, 'vectorStoreConnections', ha='center', va='center', - fontsize=8, fontweight='bold', color=COLORS['primary_dark'], family='monospace') - ax.text(2.9, bind_y + 0.25, '-> AI Search', ha='center', va='center', - fontsize=9, color=COLORS['text_dark']) - - # Storage Connections - draw_box(ax, 5.1, bind_y, 3.8, bind_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(7, bind_y + 0.6, 'storageConnections', ha='center', va='center', - fontsize=8, fontweight='bold', color=COLORS['primary_dark'], family='monospace') - ax.text(7, bind_y + 0.25, '-> Azure Storage', ha='center', va='center', - fontsize=9, color=COLORS['text_dark']) - - # Thread Storage Connections - draw_box(ax, 9.2, bind_y, 3.8, bind_h, COLORS['bg_medium'], COLORS['text_light'], 1.5) - ax.text(11.1, bind_y + 0.6, 'threadStorageConnections', ha='center', va='center', - fontsize=8, fontweight='bold', color=COLORS['primary_dark'], family='monospace') - ax.text(11.1, bind_y + 0.25, '-> Cosmos DB', ha='center', va='center', - fontsize=9, color=COLORS['text_dark']) - - # Runtime info box - draw_box(ax, 1, 3, 12, 0.7, COLORS['bg_light'], COLORS['text_light'], 1.5) - ax.text(7, 3.35, 'RUNTIME: Creates Container App environment in Agent Subnet | Provisions infrastructure', - ha='center', va='center', fontsize=9, fontweight='bold', color=COLORS['primary_dark']) - - # Header injection explanation box - draw_box(ax, 0.5, 0.5, 13, 2.1, COLORS['bg_light'], COLORS['primary'], 2) - ax.text(7, 2.3, 'HOW CAPABILITY HOST ENABLES ADDITIONAL HEADERS', ha='center', va='center', - fontsize=11, fontweight='bold', color=COLORS['primary_dark']) - - flow_text = [ - '1. Agent makes API call -> 2. Capability Host intercepts -> 3. Looks up connection config', - '4. Injects headers: Authorization (Bearer token from MSI), x-ms-documentdb-partitionkey, etc.', - '5. Routes through Private Endpoint -> 6. Resource receives authenticated request' - ] - for i, line in enumerate(flow_text): - ax.text(7, 1.85 - i*0.4, line, ha='center', va='center', - fontsize=9, color=COLORS['text_dark'], family='monospace') - - plt.tight_layout() - plt.savefig('layer5_capability_host.png', dpi=150, bbox_inches='tight', - facecolor='white', edgecolor='none') - plt.close() - print("Generated layer5_capability_host.png") - - -# ============================================================================= -# DEPLOYMENT FLOW - All Phases (Two Column Layout) -# ============================================================================= -def generate_deployment_flow(): - fig, ax = plt.subplots(1, 1, figsize=(32, 18)) - ax.set_xlim(0, 28) - ax.set_ylim(0, 13) - ax.set_aspect('equal') - ax.axis('off') - fig.patch.set_facecolor(COLORS['white']) - ax.set_facecolor(COLORS['white']) - - # Left column phases (1-4) - left_phases = [ - { - 'num': '1', - 'title': 'Network Infrastructure', - 'y': 10.0, - 'items': ['VNet + Agent Subnet + PE Subnet + MCP Subnet'] - }, - { - 'num': '2', - 'title': 'AI Services Account + Model', - 'y': 7.2, - 'items': ['AI Services (Kind: AIServices)', 'Model Deployment (GPT-4o)'] - }, - { - 'num': '3', - 'title': 'BYO Data Resources', - 'y': 4.4, - 'items': ['Cosmos DB (threads)', 'Storage (files)', 'AI Search (vector store)'] - }, - { - 'num': '4', - 'title': 'Private Network Security', - 'y': 1.6, - 'items': ['Private Endpoints for all services', 'Private DNS Zones'] - } - ] - - # Right column phases (5-8) - right_phases = [ - { - 'num': '5', - 'title': 'Project + Connections', - 'y': 10.0, - 'items': ['Foundry Project', 'CosmosDB / Storage / AI Search', 'connections (AAD Auth)'] - }, - { - 'num': '6', - 'title': 'RBAC (Pre-Capability Host)', - 'y': 7.2, - 'items': ['Storage Blob Data Contributor', 'Cosmos DB Operator', 'Search Index Data Contributor'] - }, - { - 'num': '7', - 'title': 'Capability Host', - 'y': 4.4, - 'items': ['vectorStoreConnections', 'storageConnections', 'threadStorageConnections'] - }, - { - 'num': '8', - 'title': 'RBAC (Post-Capability Host)', - 'y': 1.6, - 'items': ['Storage Blob Data Owner', 'Cosmos Built-In Data Contributor', '(on containers created by caphost)'] - } - ] - - box_width = 12.5 - box_height = 2.4 - left_x = 0.8 - right_x = 14.5 - - def draw_phase(phase, box_x, y): - # Main phase box - box = FancyBboxPatch( - (box_x, y), box_width, box_height, - boxstyle="round,pad=0.02,rounding_size=0.15", - facecolor=COLORS['bg_medium'], - edgecolor=COLORS['border'], - linewidth=4 - ) - ax.add_patch(box) - - # Phase number circle - circle = FancyBboxPatch( - (box_x + 0.3, y + box_height/2 - 0.5), 1.0, 1.0, - boxstyle="round,pad=0.02,rounding_size=0.5", - facecolor=COLORS['primary'], - edgecolor=COLORS['primary_dark'], - linewidth=3 - ) - ax.add_patch(circle) - ax.text(box_x + 0.8, y + box_height/2, phase['num'], ha='center', va='center', - fontsize=36, fontweight='bold', color=COLORS['text_light']) - - # Phase title - ax.text(box_x + 1.6, y + box_height - 0.45, f"Phase {phase['num']}", ha='left', va='center', - fontsize=26, color=COLORS['accent'], fontweight='bold') - ax.text(box_x + 4.0, y + box_height - 0.45, f"({phase['title']})", ha='left', va='center', - fontsize=26, fontweight='bold', color=COLORS['primary_dark']) - - # Items - each on its own line - for j, item in enumerate(phase['items']): - ax.text(box_x + 1.6, y + box_height - 1.0 - j*0.5, f"• {item}", ha='left', va='center', - fontsize=22, color=COLORS['text_dark']) - - # Draw left column - for i, phase in enumerate(left_phases): - draw_phase(phase, left_x, phase['y']) - # Arrow to next phase (except last in column) - if i < len(left_phases) - 1: - arrow_y = phase['y'] - 0.2 - ax.annotate('', xy=(left_x + box_width/2, arrow_y - 0.35), - xytext=(left_x + box_width/2, arrow_y + 0.05), - arrowprops=dict(arrowstyle='->', color=COLORS['primary'], lw=4)) - - # Draw right column - for i, phase in enumerate(right_phases): - draw_phase(phase, right_x, phase['y']) - # Arrow to next phase (except last in column) - if i < len(right_phases) - 1: - arrow_y = phase['y'] - 0.2 - ax.annotate('', xy=(right_x + box_width/2, arrow_y - 0.35), - xytext=(right_x + box_width/2, arrow_y + 0.05), - arrowprops=dict(arrowstyle='->', color=COLORS['primary'], lw=4)) - - plt.tight_layout() - plt.savefig('deployment_flow.png', dpi=400, bbox_inches='tight', - facecolor='white', edgecolor='none') - plt.close() - print("Generated deployment_flow.png") - - -# ============================================================================= -# Main -# ============================================================================= -if __name__ == '__main__': - print("Generating architecture diagrams...") - print("-" * 40) - generate_layer1() - generate_layer2() - generate_layer3() - generate_layer4() - generate_layer5() - generate_deployment_flow() - print("-" * 40) - print("All diagrams generated successfully!") diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer1_network_foundation.png b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer1_network_foundation.png deleted file mode 100644 index 3006a0ae3..000000000 Binary files a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer1_network_foundation.png and /dev/null differ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer2_data_resources.png b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer2_data_resources.png deleted file mode 100644 index 2f8a80cac..000000000 Binary files a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer2_data_resources.png and /dev/null differ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer3_ai_services.png b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer3_ai_services.png deleted file mode 100644 index 498c159b2..000000000 Binary files a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer3_ai_services.png and /dev/null differ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer4_project_connections.png b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer4_project_connections.png deleted file mode 100644 index 82e57f9a1..000000000 Binary files a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer4_project_connections.png and /dev/null differ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer5_capability_host.png b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer5_capability_host.png deleted file mode 100644 index b8c040870..000000000 Binary files a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/diagrams/layer5_capability_host.png and /dev/null differ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/get-existing-resources.ps1 b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/get-existing-resources.ps1 deleted file mode 100644 index 76cca81a9..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/get-existing-resources.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -# PowerShell script to help you get the names of your existing resources -# Run this after your initial deployment to get the resource names for the add-project parameters - -param( - [Parameter(Mandatory=$true)] - [string]$ResourceGroupName, - - [Parameter(Mandatory=$false)] - [string]$SubscriptionId -) - -if ($SubscriptionId) { - az account set --subscription $SubscriptionId -} - -Write-Host "Getting existing AI Foundry resources from Resource Group: $ResourceGroupName" -ForegroundColor Green - -# Get AI Services account -Write-Host "`n=== AI Services Account ===" -ForegroundColor Yellow -$aiAccount = az cognitiveservices account list --resource-group $ResourceGroupName --query "[?kind=='AIServices'].[name]" -o tsv -if ($aiAccount) { - Write-Host "AI Services Account Name: $aiAccount" -} else { - Write-Host "No AI Services account found" -ForegroundColor Red -} - -# Get Storage Account -Write-Host "`n=== Storage Account ===" -ForegroundColor Yellow -$storageAccount = az storage account list --resource-group $ResourceGroupName --query "[].name" -o tsv -if ($storageAccount) { - Write-Host "Storage Account Name: $storageAccount" -} else { - Write-Host "No Storage account found" -ForegroundColor Red -} - -# Get AI Search Service -Write-Host "`n=== AI Search Service ===" -ForegroundColor Yellow -$searchService = az search service list --resource-group $ResourceGroupName --query "[].name" -o tsv -if ($searchService) { - Write-Host "AI Search Service Name: $searchService" -} else { - Write-Host "No AI Search service found" -ForegroundColor Red -} - -# Get Cosmos DB Account -Write-Host "`n=== Cosmos DB Account ===" -ForegroundColor Yellow -$cosmosAccount = az cosmosdb list --resource-group $ResourceGroupName --query "[].name" -o tsv -if ($cosmosAccount) { - Write-Host "Cosmos DB Account Name: $cosmosAccount" -} else { - Write-Host "No Cosmos DB account found" -ForegroundColor Red -} - -Write-Host "`n=== Summary for add-project.bicepparam ===" -ForegroundColor Green -Write-Host "param existingAccountName = '$aiAccount'" -Write-Host "param existingAiSearchName = '$searchService'" -Write-Host "param existingStorageName = '$storageAccount'" -Write-Host "param existingCosmosDBName = '$cosmosAccount'" -Write-Host "param accountResourceGroupName = '$ResourceGroupName'" -Write-Host "param aiSearchResourceGroupName = '$ResourceGroupName'" -Write-Host "param storageResourceGroupName = '$ResourceGroupName'" -Write-Host "param cosmosDBResourceGroupName = '$ResourceGroupName'" diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicep deleted file mode 100644 index 11d4c2a06..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicep +++ /dev/null @@ -1,438 +0,0 @@ -/* -Hybrid Private Resources Setup for Azure AI Foundry Agents ------------------------------------------------------------ -This template creates an Azure AI Foundry account with public network access DISABLED, -while keeping backend resources (AI Search, Cosmos DB, Storage) on private endpoints. - -Key differences from template 15 (fully private): -- AI Services: publicNetworkAccess = Disabled (default) -- Backend resources: Still private (AI Search, Cosmos DB, Storage) -- Data Proxy: networkInjections configured to route to private VNet - -This enables: -✓ Agents can use AI Search tool (routed via Data Proxy to private endpoint) -✓ Agents can use MCP servers running on the VNet - -Architecture: - Private VNet → AI Services (private) → Data Proxy → Private VNet → Backend Resources -*/ -@description('Location for all resources.') -@allowed([ - 'westus' - 'westus2' - 'eastus' - 'eastus2' - 'japaneast' - 'francecentral' - 'spaincentral' - 'uaenorth' - 'southcentralus' - 'italynorth' - 'germanywestcentral' - 'brazilsouth' - 'southafricanorth' - 'australiaeast' - 'swedencentral' - 'canadaeast' - 'westeurope' - 'westus3' - 'uksouth' - 'southindia' - - //only class B and C - 'koreacentral' - 'polandcentral' - 'switzerlandnorth' - 'norwayeast' -]) -param location string = 'eastus2' - -@description('Name for your AI Services resource.') -param aiServices string = 'aiservices' - -// Model deployment parameters -@description('The name of the model you want to deploy') -param modelName string = 'gpt-4o-mini' -@description('The provider of your model') -param modelFormat string = 'OpenAI' -@description('The version of your model') -param modelVersion string = '2024-07-18' -@description('The sku of your model deployment') -param modelSkuName string = 'GlobalStandard' -@description('The tokens per minute (TPM) of your model deployment') -param modelCapacity int = 30 - -// Create a short, unique suffix, that will be unique to each resource group -param deploymentTimestamp string = utcNow('yyyyMMddHHmmss') -var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4) -var accountName = toLower('${aiServices}${uniqueSuffix}') - -@description('Name for your project resource.') -param firstProjectName string = 'project' - -@description('This project will be a sub-resource of your account') -param projectDescription string = 'A project for the AI Foundry account with network secured deployed Agent' - -@description('The display name of the project') -param displayName string = 'network secured agent project' - -// Existing Virtual Network parameters -@description('Virtual Network name for the Agent to create new or existing virtual network') -param vnetName string = 'agent-vnet-test' - -@description('The name of Agents Subnet to create new or existing subnet for agents') -param agentSubnetName string = 'agent-subnet' - -@description('The name of Private Endpoint subnet to create new or existing subnet for private endpoints') -param peSubnetName string = 'pe-subnet' - -@description('The name of MCP subnet for user-deployed Container Apps (e.g., MCP servers)') -param mcpSubnetName string = 'mcp-subnet' - -//Existing standard Agent required resources -@description('Existing Virtual Network name Resource ID') -param existingVnetResourceId string = '' - -@description('Address space for the VNet (only used for new VNet)') -param vnetAddressPrefix string = '' - -@description('Address prefix for the agent subnet. The default value is 192.168.0.0/24 but you can choose any size /26 or any class like 10.0.0.0 or 172.168.0.0') -param agentSubnetPrefix string = '' - -@description('Address prefix for the private endpoint subnet') -param peSubnetPrefix string = '' - -@description('Address prefix for the MCP subnet. The default value is 192.168.2.0/24.') -param mcpSubnetPrefix 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 aiSearchResourceId 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 azureStorageAccountResourceId string = '' -@description('The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') -param azureCosmosDBAccountResourceId string = '' - -@description('The Microsoft Fabric Workspace full ARM Resource ID. This is an optional field for Fabric private link connectivity.') -param fabricWorkspaceResourceId string = '' - -//New Param for resource group of Private DNS zones -//@description('Optional: Resource group containing existing private DNS zones. If specified, DNS zones will not be created.') -//param existingDnsZonesResourceGroup string = '' - -@description('Object mapping DNS zone names to their resource group, or empty string to indicate creation') -param existingDnsZones object = { - 'privatelink.services.ai.azure.com': '' - 'privatelink.openai.azure.com': '' - 'privatelink.cognitiveservices.azure.com': '' - 'privatelink.search.windows.net': '' - 'privatelink.blob.core.windows.net': '' - 'privatelink.documents.azure.com': '' - 'privatelink.analysis.windows.net': '' -} - -@description('Zone Names for Validation of existing Private Dns Zones') -param dnsZoneNames array = [ - 'privatelink.services.ai.azure.com' - 'privatelink.openai.azure.com' - 'privatelink.cognitiveservices.azure.com' - 'privatelink.search.windows.net' - 'privatelink.blob.core.windows.net' - 'privatelink.documents.azure.com' - 'privatelink.analysis.windows.net' -] - -var projectName = toLower('${firstProjectName}${uniqueSuffix}') -var cosmosDBName = toLower('${aiServices}${uniqueSuffix}cosmosdb') -var aiSearchName = toLower('${aiServices}${uniqueSuffix}search') -var azureStorageName = toLower('${aiServices}${uniqueSuffix}storage') - -// Check if existing resources have been passed in -var storagePassedIn = azureStorageAccountResourceId != '' -var searchPassedIn = aiSearchResourceId != '' -var cosmosPassedIn = azureCosmosDBAccountResourceId != '' -var existingVnetPassedIn = existingVnetResourceId != '' - -var acsParts = split(aiSearchResourceId, '/') -var aiSearchServiceSubscriptionId = searchPassedIn ? acsParts[2] : subscription().subscriptionId -var aiSearchServiceResourceGroupName = searchPassedIn ? acsParts[4] : resourceGroup().name - -var cosmosParts = split(azureCosmosDBAccountResourceId, '/') -var cosmosDBSubscriptionId = cosmosPassedIn ? cosmosParts[2] : subscription().subscriptionId -var cosmosDBResourceGroupName = cosmosPassedIn ? cosmosParts[4] : resourceGroup().name - -var storageParts = split(azureStorageAccountResourceId, '/') -var azureStorageSubscriptionId = storagePassedIn ? storageParts[2] : subscription().subscriptionId -var azureStorageResourceGroupName = storagePassedIn ? storageParts[4] : resourceGroup().name - -var vnetParts = split(existingVnetResourceId, '/') -var vnetSubscriptionId = existingVnetPassedIn ? vnetParts[2] : subscription().subscriptionId -var vnetResourceGroupName = existingVnetPassedIn ? vnetParts[4] : resourceGroup().name -var existingVnetName = existingVnetPassedIn ? last(vnetParts) : vnetName -var trimVnetName = trim(existingVnetName) - -@description('The name of the project capability host to be created') -param projectCapHost string = 'caphostproj' - -// Create Virtual Network and Subnets -module vnet 'modules-network-secured/network-agent-vnet.bicep' = { - name: 'vnet-${trimVnetName}-${uniqueSuffix}-deployment' - params: { - location: location - vnetName: trimVnetName - useExistingVnet: existingVnetPassedIn - existingVnetResourceGroupName: vnetResourceGroupName - agentSubnetName: agentSubnetName - peSubnetName: peSubnetName - mcpSubnetName: mcpSubnetName - vnetAddressPrefix: vnetAddressPrefix - agentSubnetPrefix: agentSubnetPrefix - peSubnetPrefix: peSubnetPrefix - mcpSubnetPrefix: mcpSubnetPrefix - existingVnetSubscriptionId: vnetSubscriptionId - } -} - -/* - Create the AI Services account and gpt-4o model deployment -*/ -module aiAccount 'modules-network-secured/ai-account-identity.bicep' = { - name: '${accountName}-${uniqueSuffix}-deployment' - params: { - // workspace organization - accountName: accountName - location: location - modelName: modelName - modelFormat: modelFormat - modelVersion: modelVersion - modelSkuName: modelSkuName - modelCapacity: modelCapacity - agentSubnetId: vnet.outputs.agentSubnetId - } -} -/* - Validate existing resources - This module will check if the AI Search Service, Storage Account, and Cosmos DB Account already exist. - If they do, it will set the corresponding output to true. If they do not exist, it will set the output to false. -*/ -module validateExistingResources 'modules-network-secured/validate-existing-resources.bicep' = { - name: 'validate-existing-resources-${uniqueSuffix}-deployment' - params: { - aiSearchResourceId: aiSearchResourceId - azureStorageAccountResourceId: azureStorageAccountResourceId - azureCosmosDBAccountResourceId: azureCosmosDBAccountResourceId - existingDnsZones: existingDnsZones - dnsZoneNames: dnsZoneNames - } -} - -// This module will create new agent dependent resources -// A Cosmos DB account, an AI Search Service, and a Storage Account are created if they do not already exist -module aiDependencies 'modules-network-secured/standard-dependent-resources.bicep' = { - name: 'dependencies-${uniqueSuffix}-deployment' - params: { - location: location - azureStorageName: azureStorageName - aiSearchName: aiSearchName - cosmosDBName: cosmosDBName - - // AI Search Service parameters - aiSearchResourceId: aiSearchResourceId - aiSearchExists: validateExistingResources.outputs.aiSearchExists - - // Storage Account - azureStorageAccountResourceId: azureStorageAccountResourceId - azureStorageExists: validateExistingResources.outputs.azureStorageExists - - // Cosmos DB Account - cosmosDBResourceId: azureCosmosDBAccountResourceId - cosmosDBExists: validateExistingResources.outputs.cosmosDBExists - } -} - -resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { - name: aiDependencies.outputs.azureStorageName - scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) -} - -resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = { - name: aiDependencies.outputs.aiSearchName - scope: resourceGroup( - aiDependencies.outputs.aiSearchServiceSubscriptionId, - aiDependencies.outputs.aiSearchServiceResourceGroupName - ) -} - -resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = { - name: aiDependencies.outputs.cosmosDBName - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) -} - -// 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 -// 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: '${uniqueSuffix}-private-endpoint' - params: { - aiAccountName: aiAccount.outputs.accountName // AI Services to secure - aiSearchName: aiDependencies.outputs.aiSearchName // AI Search to secure - storageName: aiDependencies.outputs.azureStorageName // Storage to secure - cosmosDBName: aiDependencies.outputs.cosmosDBName - fabricWorkspaceResourceId: fabricWorkspaceResourceId // Microsoft Fabric workspace (optional) - vnetName: vnet.outputs.virtualNetworkName // VNet containing subnets - peSubnetName: vnet.outputs.peSubnetName // Subnet for private endpoints - suffix: uniqueSuffix // Unique identifier - vnetResourceGroupName: vnet.outputs.virtualNetworkResourceGroup - vnetSubscriptionId: vnet.outputs.virtualNetworkSubscriptionId // Subscription ID for the VNet - cosmosDBSubscriptionId: cosmosDBSubscriptionId // Subscription ID for Cosmos DB - cosmosDBResourceGroupName: cosmosDBResourceGroupName // Resource Group for Cosmos DB - aiSearchSubscriptionId: aiSearchServiceSubscriptionId // Subscription ID for AI Search Service - aiSearchResourceGroupName: aiSearchServiceResourceGroupName // Resource Group for AI Search Service - storageAccountResourceGroupName: azureStorageResourceGroupName // Resource Group for Storage Account - storageAccountSubscriptionId: azureStorageSubscriptionId // Subscription ID for Storage Account - existingDnsZones: existingDnsZones - } - dependsOn: [ - aiSearch // Ensure AI Search exists - storage // Ensure Storage exists - cosmosDB // Ensure Cosmos DB exists - ] -} - -/* - Creates a new project (sub-resource of the AI Services account) -*/ -module aiProject 'modules-network-secured/ai-project-identity.bicep' = { - name: '${projectName}-${uniqueSuffix}-deployment' - params: { - // workspace organization - projectName: projectName - projectDescription: projectDescription - displayName: displayName - location: location - - aiSearchName: aiDependencies.outputs.aiSearchName - aiSearchServiceResourceGroupName: aiDependencies.outputs.aiSearchServiceResourceGroupName - aiSearchServiceSubscriptionId: aiDependencies.outputs.aiSearchServiceSubscriptionId - - cosmosDBName: aiDependencies.outputs.cosmosDBName - cosmosDBSubscriptionId: aiDependencies.outputs.cosmosDBSubscriptionId - cosmosDBResourceGroupName: aiDependencies.outputs.cosmosDBResourceGroupName - - azureStorageName: aiDependencies.outputs.azureStorageName - azureStorageSubscriptionId: aiDependencies.outputs.azureStorageSubscriptionId - azureStorageResourceGroupName: aiDependencies.outputs.azureStorageResourceGroupName - // dependent resources - accountName: aiAccount.outputs.accountName - } - dependsOn: [ - privateEndpointAndDNS - cosmosDB - aiSearch - storage - ] -} - -module formatProjectWorkspaceId 'modules-network-secured/format-project-workspace-id.bicep' = { - name: 'format-project-workspace-id-${uniqueSuffix}-deployment' - params: { - projectWorkspaceId: aiProject.outputs.projectWorkspaceId - } -} - -/* - Assigns the project SMI the storage blob data contributor role on the storage account -*/ -module storageAccountRoleAssignment 'modules-network-secured/azure-storage-account-role-assignment.bicep' = { - name: 'storage-${azureStorageName}-${uniqueSuffix}-deployment' - scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) - params: { - azureStorageName: aiDependencies.outputs.azureStorageName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } - dependsOn: [ - storage - privateEndpointAndDNS - ] -} - -// The Comos DB Operator role must be assigned before the caphost is created -module cosmosAccountRoleAssignments 'modules-network-secured/cosmosdb-account-role-assignment.bicep' = { - name: 'cosmos-account-ra-${uniqueSuffix}-deployment' - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) - params: { - cosmosDBName: aiDependencies.outputs.cosmosDBName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } - dependsOn: [ - cosmosDB - privateEndpointAndDNS - ] -} - -// This role can be assigned before or after the caphost is created -module aiSearchRoleAssignments 'modules-network-secured/ai-search-role-assignments.bicep' = { - name: 'ai-search-ra-${uniqueSuffix}-deployment' - scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) - params: { - aiSearchName: aiDependencies.outputs.aiSearchName - projectPrincipalId: aiProject.outputs.projectPrincipalId - } - dependsOn: [ - aiSearch - privateEndpointAndDNS - ] -} - -// This module creates the capability host for the project and account -module addProjectCapabilityHost 'modules-network-secured/add-project-capability-host.bicep' = { - name: 'capabilityHost-configuration-${uniqueSuffix}-deployment' - params: { - accountName: aiAccount.outputs.accountName - projectName: aiProject.outputs.projectName - cosmosDBConnection: aiProject.outputs.cosmosDBConnection - azureStorageConnection: aiProject.outputs.azureStorageConnection - aiSearchConnection: aiProject.outputs.aiSearchConnection - projectCapHost: projectCapHost - } - dependsOn: [ - aiSearch // Ensure AI Search exists - storage // Ensure Storage exists - cosmosDB - privateEndpointAndDNS - cosmosAccountRoleAssignments - storageAccountRoleAssignment - aiSearchRoleAssignments - ] -} - -// The Storage Blob Data Owner role must be assigned after the caphost is created -module storageContainersRoleAssignment 'modules-network-secured/blob-storage-container-role-assignments.bicep' = { - name: 'storage-containers-ra-${uniqueSuffix}-deployment' - scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) - params: { - aiProjectPrincipalId: aiProject.outputs.projectPrincipalId - storageName: aiDependencies.outputs.azureStorageName - workspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid - } - dependsOn: [ - addProjectCapabilityHost - ] -} - -// The Cosmos Built-In Data Contributor role must be assigned after the caphost is created -module cosmosContainerRoleAssignments 'modules-network-secured/cosmos-container-role-assignments.bicep' = { - name: 'cosmos-containers-ra-${uniqueSuffix}-deployment' - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) - params: { - cosmosAccountName: aiDependencies.outputs.cosmosDBName - projectWorkspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid - projectPrincipalId: aiProject.outputs.projectPrincipalId - } - dependsOn: [ - addProjectCapabilityHost - storageContainersRoleAssignment - ] -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicepparam b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicepparam deleted file mode 100644 index a5125398b..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.bicepparam +++ /dev/null @@ -1,66 +0,0 @@ -using './main.bicep' - -param location = 'norwayeast' -param aiServices = 'djetchev' -param modelName = 'gpt-4o-mini' -param modelFormat = 'OpenAI' -param modelVersion = '2024-07-18' -param modelSkuName = 'GlobalStandard' -param modelCapacity = 1 -param firstProjectName = 'project' -param projectDescription = 'A project for the AI Foundry account with network secured deployed Agent' -param displayName = 'project' -param peSubnetName = 'pe-subnet' - -// Resource IDs for existing resources -// If you provide these, the deployment will use the existing resources instead of creating new ones -param existingVnetResourceId = '' -param vnetName = 'agent-vnet-test' -param agentSubnetName = 'agent-subnet' -param aiSearchResourceId = '' -param azureStorageAccountResourceId = '' -param azureCosmosDBAccountResourceId = '' -// Pass the DNS zone map here -// Leave empty to create new DNS zone, add the resource group of existing DNS zone to use it -param existingDnsZones = { - 'privatelink.services.ai.azure.com': '' - 'privatelink.openai.azure.com': '' - 'privatelink.cognitiveservices.azure.com': '' - 'privatelink.search.windows.net': '' - 'privatelink.blob.core.windows.net': '' - 'privatelink.documents.azure.com': '' -} - -//DNSZones names for validating if they exist -param dnsZoneNames = [ - 'privatelink.services.ai.azure.com' - 'privatelink.openai.azure.com' - 'privatelink.cognitiveservices.azure.com' - 'privatelink.search.windows.net' - 'privatelink.blob.core.windows.net' - 'privatelink.documents.azure.com' -] - -// Network configuration (behavior depends on `existingVnetResourceId`) -// -// - NEW VNet (existingVnetResourceId is empty): -// The values below are used to CREATE the VNet and the two subnets. -// Provide explicit, non-overlapping CIDR ranges when creating a new VNet. -// -// - EXISTING VNet (existingVnetResourceId is provided): -// The module will reference the existing VNet. Subnet handling depends on the -// values you provide: -// * If `agentSubnetPrefix` or `peSubnetPrefix` are empty, the module may -// auto-derive subnet CIDRs from the existing VNet's address space -// (using cidrSubnet). This can produce /24 (or configured) subnets -// starting at index 0, 1, etc. -// * If you provide explicit subnet prefixes, the module will attempt to -// create or update subnets with those prefixes in the existing VNet. -// -// Important operational notes and risks (when existingVnetResourceId is provided): -// - Avoid CIDR overlaps with any existing subnets in the target VNet. Overlap -// leads to `NetcfgSubnetRangesOverlap` and failed deployments. -// - For highest safety when using an existing VNet, supply the existing `agentSubnetPrefix` and `peSubnetPrefix`. -param vnetAddressPrefix = '' -param agentSubnetPrefix = '' -param peSubnetPrefix = '' diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.json b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.json deleted file mode 100644 index d7dd22475..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/main.json +++ /dev/null @@ -1,2772 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "789904159633670276" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "eastus2", - "allowedValues": [ - "westus", - "eastus", - "eastus2", - "japaneast", - "francecentral", - "spaincentral", - "uaenorth", - "southcentralus", - "italynorth", - "germanywestcentral", - "brazilsouth", - "southafricanorth", - "australiaeast", - "swedencentral", - "canadaeast", - "westeurope", - "westus3", - "uksouth", - "southindia", - "koreacentral", - "polandcentral", - "switzerlandnorth", - "norwayeast" - ], - "metadata": { - "description": "Location for all resources." - } - }, - "aiServices": { - "type": "string", - "defaultValue": "aiservices", - "metadata": { - "description": "Name for your AI Services resource." - } - }, - "modelName": { - "type": "string", - "defaultValue": "gpt-4o", - "metadata": { - "description": "The name of the model you want to deploy" - } - }, - "modelFormat": { - "type": "string", - "defaultValue": "OpenAI", - "metadata": { - "description": "The provider of your model" - } - }, - "modelVersion": { - "type": "string", - "defaultValue": "2024-11-20", - "metadata": { - "description": "The version of your model" - } - }, - "modelSkuName": { - "type": "string", - "defaultValue": "GlobalStandard", - "metadata": { - "description": "The sku of your model deployment" - } - }, - "modelCapacity": { - "type": "int", - "defaultValue": 30, - "metadata": { - "description": "The tokens per minute (TPM) of your model deployment" - } - }, - "deploymentTimestamp": { - "type": "string", - "defaultValue": "[utcNow('yyyyMMddHHmmss')]" - }, - "firstProjectName": { - "type": "string", - "defaultValue": "project", - "metadata": { - "description": "Name for your project resource." - } - }, - "projectDescription": { - "type": "string", - "defaultValue": "A project for the AI Foundry account with network secured deployed Agent", - "metadata": { - "description": "This project will be a sub-resource of your account" - } - }, - "displayName": { - "type": "string", - "defaultValue": "network secured agent project", - "metadata": { - "description": "The display name of the project" - } - }, - "vnetName": { - "type": "string", - "defaultValue": "agent-vnet-test", - "metadata": { - "description": "Virtual Network name for the Agent to create new or existing virtual network" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet to create new or existing subnet for agents" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet to create new or existing subnet for private endpoints" - } - }, - "existingVnetResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Existing Virtual Network name Resource ID" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet (only used for new VNet)" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet. The default value is 192.168.0.0/24 but you can choose any size /26 or any class like 10.0.0.0 or 172.168.0.0" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - }, - "aiSearchResourceId": { - "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." - } - }, - "azureStorageAccountResourceId": { - "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." - } - }, - "azureCosmosDBAccountResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." - } - }, - "fabricWorkspaceResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The Microsoft Fabric Workspace full ARM Resource ID. This is an optional field for Fabric private link connectivity." - } - }, - "existingDnsZones": { - "type": "object", - "defaultValue": { - "privatelink.services.ai.azure.com": "", - "privatelink.openai.azure.com": "", - "privatelink.cognitiveservices.azure.com": "", - "privatelink.search.windows.net": "", - "privatelink.blob.core.windows.net": "", - "privatelink.documents.azure.com": "", - "privatelink.analysis.windows.net": "" - }, - "metadata": { - "description": "Object mapping DNS zone names to their resource group, or empty string to indicate creation" - } - }, - "dnsZoneNames": { - "type": "array", - "defaultValue": [ - "privatelink.services.ai.azure.com", - "privatelink.openai.azure.com", - "privatelink.cognitiveservices.azure.com", - "privatelink.search.windows.net", - "privatelink.blob.core.windows.net", - "privatelink.documents.azure.com", - "privatelink.analysis.windows.net" - ], - "metadata": { - "description": "Zone Names for Validation of existing Private Dns Zones" - } - }, - "projectCapHost": { - "type": "string", - "defaultValue": "caphostproj", - "metadata": { - "description": "The name of the project capability host to be created" - } - } - }, - "variables": { - "uniqueSuffix": "[substring(uniqueString(format('{0}-{1}', resourceGroup().id, parameters('deploymentTimestamp'))), 0, 4)]", - "accountName": "[toLower(format('{0}{1}', parameters('aiServices'), variables('uniqueSuffix')))]", - "projectName": "[toLower(format('{0}{1}', parameters('firstProjectName'), variables('uniqueSuffix')))]", - "cosmosDBName": "[toLower(format('{0}{1}cosmosdb', parameters('aiServices'), variables('uniqueSuffix')))]", - "aiSearchName": "[toLower(format('{0}{1}search', parameters('aiServices'), variables('uniqueSuffix')))]", - "azureStorageName": "[toLower(format('{0}{1}storage', parameters('aiServices'), variables('uniqueSuffix')))]", - "storagePassedIn": "[not(equals(parameters('azureStorageAccountResourceId'), ''))]", - "searchPassedIn": "[not(equals(parameters('aiSearchResourceId'), ''))]", - "cosmosPassedIn": "[not(equals(parameters('azureCosmosDBAccountResourceId'), ''))]", - "existingVnetPassedIn": "[not(equals(parameters('existingVnetResourceId'), ''))]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "aiSearchServiceSubscriptionId": "[if(variables('searchPassedIn'), variables('acsParts')[2], subscription().subscriptionId)]", - "aiSearchServiceResourceGroupName": "[if(variables('searchPassedIn'), variables('acsParts')[4], resourceGroup().name)]", - "cosmosParts": "[split(parameters('azureCosmosDBAccountResourceId'), '/')]", - "cosmosDBSubscriptionId": "[if(variables('cosmosPassedIn'), variables('cosmosParts')[2], subscription().subscriptionId)]", - "cosmosDBResourceGroupName": "[if(variables('cosmosPassedIn'), variables('cosmosParts')[4], resourceGroup().name)]", - "storageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]", - "azureStorageSubscriptionId": "[if(variables('storagePassedIn'), variables('storageParts')[2], subscription().subscriptionId)]", - "azureStorageResourceGroupName": "[if(variables('storagePassedIn'), variables('storageParts')[4], resourceGroup().name)]", - "vnetParts": "[split(parameters('existingVnetResourceId'), '/')]", - "vnetSubscriptionId": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[2], subscription().subscriptionId)]", - "vnetResourceGroupName": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[4], resourceGroup().name)]", - "existingVnetName": "[if(variables('existingVnetPassedIn'), last(variables('vnetParts')), parameters('vnetName'))]", - "trimVnetName": "[trim(variables('existingVnetName'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "vnetName": { - "value": "[variables('trimVnetName')]" - }, - "useExistingVnet": { - "value": "[variables('existingVnetPassedIn')]" - }, - "existingVnetResourceGroupName": { - "value": "[variables('vnetResourceGroupName')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "vnetAddressPrefix": { - "value": "[parameters('vnetAddressPrefix')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - }, - "existingVnetSubscriptionId": { - "value": "[variables('vnetSubscriptionId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8505298823279202405" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region for the deployment" - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "The name of the virtual network" - } - }, - "useExistingVnet": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Indicates if an existing VNet should be used" - } - }, - "existingVnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID of the existing VNet (if different from current subscription)" - } - }, - "existingVnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name of the existing VNet (if different from current resource group)" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet (only used for new VNet)" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - } - }, - "resources": [ - { - "condition": "[not(parameters('useExistingVnet'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "vnet-deployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "vnetAddressPrefix": { - "value": "[parameters('vnetAddressPrefix')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4954184648131521061" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region for the deployment" - } - }, - "vnetName": { - "type": "string", - "defaultValue": "agents-vnet-test", - "metadata": { - "description": "The name of the virtual network" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Hub subnet" - } - }, - "vnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address space for the VNet" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet" - } - } - }, - "variables": { - "defaultVnetAddressPrefix": "192.168.0.0/16", - "vnetAddress": "[if(empty(parameters('vnetAddressPrefix')), variables('defaultVnetAddressPrefix'), parameters('vnetAddressPrefix'))]", - "agentSubnet": "[if(empty(parameters('agentSubnetPrefix')), cidrSubnet(variables('vnetAddress'), 24, 0), parameters('agentSubnetPrefix'))]", - "peSubnet": "[if(empty(parameters('peSubnetPrefix')), cidrSubnet(variables('vnetAddress'), 24, 1), parameters('peSubnetPrefix'))]" - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-05-01", - "name": "[parameters('vnetName')]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('vnetAddress')]" - ] - }, - "subnets": [ - { - "name": "[parameters('agentSubnetName')]", - "properties": { - "addressPrefix": "[variables('agentSubnet')]", - "delegations": [ - { - "name": "Microsoft.app/environments", - "properties": { - "serviceName": "Microsoft.App/environments" - } - } - ] - } - }, - { - "name": "[parameters('peSubnetName')]", - "properties": { - "addressPrefix": "[variables('peSubnet')]" - } - } - ] - } - } - ], - "outputs": { - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('agentSubnetName'))]" - }, - "peSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('peSubnetName'))]" - }, - "virtualNetworkName": { - "type": "string", - "value": "[parameters('vnetName')]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[resourceGroup().name]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[subscription().subscriptionId]" - } - } - } - } - }, - { - "condition": "[parameters('useExistingVnet')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "existing-vnet-deployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "vnetResourceGroupName": { - "value": "[parameters('existingVnetResourceGroupName')]" - }, - "vnetSubscriptionId": { - "value": "[parameters('existingVnetSubscriptionId')]" - }, - "agentSubnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetPrefix": { - "value": "[parameters('agentSubnetPrefix')]" - }, - "peSubnetPrefix": { - "value": "[parameters('peSubnetPrefix')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3152324712046183852" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "The name of the existing virtual network" - } - }, - "vnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID of virtual network (if different from current subscription)" - } - }, - "vnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name of the existing VNet (if different from current resource group)" - } - }, - "agentSubnetName": { - "type": "string", - "defaultValue": "agent-subnet", - "metadata": { - "description": "The name of Agents Subnet" - } - }, - "peSubnetName": { - "type": "string", - "defaultValue": "pe-subnet", - "metadata": { - "description": "The name of Private Endpoint subnet" - } - }, - "agentSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the agent subnet (only needed if creating new subnet)" - } - }, - "peSubnetPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Address prefix for the private endpoint subnet (only needed if creating new subnet)" - } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('agent-subnet-{0}', uniqueString(deployment().name, parameters('agentSubnetName')))]", - "resourceGroup": "[parameters('vnetResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "subnetName": { - "value": "[parameters('agentSubnetName')]" - }, - "addressPrefix": "[if(empty(parameters('agentSubnetPrefix')), createObject('value', cidrSubnet(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').addressSpace.addressPrefixes[0], 24, 0)), createObject('value', parameters('agentSubnetPrefix')))]", - "delegations": { - "value": [ - { - "name": "Microsoft.App/environments", - "properties": { - "serviceName": "Microsoft.App/environments" - } - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17043822047386586435" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the virtual network" - } - }, - "subnetName": { - "type": "string", - "metadata": { - "description": "Name of the subnet" - } - }, - "addressPrefix": { - "type": "string", - "metadata": { - "description": "Address prefix for the subnet" - } - }, - "delegations": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Array of subnet delegations" - } - } - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', parameters('vnetName'), parameters('subnetName'))]", - "properties": { - "addressPrefix": "[parameters('addressPrefix')]", - "delegations": "[parameters('delegations')]" - } - } - ], - "outputs": { - "subnetId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[0], split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[1])]" - }, - "subnetName": { - "type": "string", - "value": "[parameters('subnetName')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('pe-subnet-{0}', uniqueString(deployment().name, parameters('peSubnetName')))]", - "resourceGroup": "[parameters('vnetResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vnetName": { - "value": "[parameters('vnetName')]" - }, - "subnetName": { - "value": "[parameters('peSubnetName')]" - }, - "addressPrefix": "[if(empty(parameters('peSubnetPrefix')), createObject('value', cidrSubnet(reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), '2024-05-01').addressSpace.addressPrefixes[0], 24, 1)), createObject('value', parameters('peSubnetPrefix')))]", - "delegations": { - "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.39.26.7824", - "templateHash": "17043822047386586435" - } - }, - "parameters": { - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the virtual network" - } - }, - "subnetName": { - "type": "string", - "metadata": { - "description": "Name of the subnet" - } - }, - "addressPrefix": { - "type": "string", - "metadata": { - "description": "Address prefix for the subnet" - } - }, - "delegations": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Array of subnet delegations" - } - } - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', parameters('vnetName'), parameters('subnetName'))]", - "properties": { - "addressPrefix": "[parameters('addressPrefix')]", - "delegations": "[parameters('delegations')]" - } - } - ], - "outputs": { - "subnetId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[0], split(format('{0}/{1}', parameters('vnetName'), parameters('subnetName')), '/')[1])]" - }, - "subnetName": { - "type": "string", - "value": "[parameters('subnetName')]" - } - } - } - } - } - ], - "outputs": { - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('agentSubnetName'))]" - }, - "peSubnetId": { - "type": "string", - "value": "[format('{0}/subnets/{1}', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName')), parameters('peSubnetName'))]" - }, - "virtualNetworkName": { - "type": "string", - "value": "[parameters('vnetName')]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[parameters('vnetResourceGroupName')]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[parameters('vnetSubscriptionId')]" - } - } - } - } - } - ], - "outputs": { - "virtualNetworkName": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkName.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkName.value)]" - }, - "virtualNetworkId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkId.value)]" - }, - "virtualNetworkSubscriptionId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkSubscriptionId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkSubscriptionId.value)]" - }, - "virtualNetworkResourceGroup": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.virtualNetworkResourceGroup.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.virtualNetworkResourceGroup.value)]" - }, - "agentSubnetName": { - "type": "string", - "value": "[parameters('agentSubnetName')]" - }, - "peSubnetName": { - "type": "string", - "value": "[parameters('peSubnetName')]" - }, - "agentSubnetId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.agentSubnetId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.agentSubnetId.value)]" - }, - "peSubnetId": { - "type": "string", - "value": "[if(parameters('useExistingVnet'), reference(resourceId('Microsoft.Resources/deployments', 'existing-vnet-deployment'), '2025-04-01').outputs.peSubnetId.value, reference(resourceId('Microsoft.Resources/deployments', 'vnet-deployment'), '2025-04-01').outputs.peSubnetId.value)]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "accountName": { - "value": "[variables('accountName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "modelName": { - "value": "[parameters('modelName')]" - }, - "modelFormat": { - "value": "[parameters('modelFormat')]" - }, - "modelVersion": { - "value": "[parameters('modelVersion')]" - }, - "modelSkuName": { - "value": "[parameters('modelSkuName')]" - }, - "modelCapacity": { - "value": "[parameters('modelCapacity')]" - }, - "agentSubnetId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-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.39.26.7824", - "templateHash": "854097619778148359" - } - }, - "parameters": { - "accountName": { - "type": "string" - }, - "location": { - "type": "string" - }, - "modelName": { - "type": "string" - }, - "modelFormat": { - "type": "string" - }, - "modelVersion": { - "type": "string" - }, - "modelSkuName": { - "type": "string" - }, - "modelCapacity": { - "type": "int" - }, - "agentSubnetId": { - "type": "string" - }, - "networkInjection": { - "type": "string", - "defaultValue": "true" - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2025-04-01-preview", - "name": "[parameters('accountName')]", - "location": "[parameters('location')]", - "sku": { - "name": "S0" - }, - "kind": "AIServices", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "allowProjectManagement": true, - "customSubDomainName": "[parameters('accountName')]", - "networkAcls": { - "defaultAction": "Deny", - "virtualNetworkRules": [], - "ipRules": [], - "bypass": "AzureServices" - }, - "publicNetworkAccess": "Disabled", - "networkInjections": "[if(equals(parameters('networkInjection'), 'true'), createArray(createObject('scenario', 'agent', 'subnetArmId', parameters('agentSubnetId'), 'useMicrosoftManagedNetwork', false())), null())]", - "disableLocalAuth": false - } - }, - { - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('accountName'), 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('accountName'))]" - ] - } - ], - "outputs": { - "accountName": { - "type": "string", - "value": "[parameters('accountName')]" - }, - "accountID": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName'))]" - }, - "accountTarget": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName')), '2025-04-01-preview').endpoint]" - }, - "accountPrincipalId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName')), '2025-04-01-preview', 'full').identity.principalId]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiSearchResourceId": { - "value": "[parameters('aiSearchResourceId')]" - }, - "azureStorageAccountResourceId": { - "value": "[parameters('azureStorageAccountResourceId')]" - }, - "azureCosmosDBAccountResourceId": { - "value": "[parameters('azureCosmosDBAccountResourceId')]" - }, - "existingDnsZones": { - "value": "[parameters('existingDnsZones')]" - }, - "dnsZoneNames": { - "value": "[parameters('dnsZoneNames')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7641310640078958122" - } - }, - "parameters": { - "aiSearchResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the AI Search Service." - } - }, - "azureStorageAccountResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Azure Storage Account." - } - }, - "azureCosmosDBAccountResourceId": { - "type": "string", - "metadata": { - "description": "ResourceId of Cosmos DB Account" - } - }, - "existingDnsZones": { - "type": "object", - "metadata": { - "description": "Object mapping DNS zone names to their resource group, or empty string to indicate creation" - } - }, - "dnsZoneNames": { - "type": "array", - "metadata": { - "description": "List of private DNS zone names to validate" - } - } - }, - "variables": { - "storagePassedIn": "[not(equals(parameters('azureStorageAccountResourceId'), ''))]", - "searchPassedIn": "[not(equals(parameters('aiSearchResourceId'), ''))]", - "cosmosPassedIn": "[not(equals(parameters('azureCosmosDBAccountResourceId'), ''))]", - "storageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]", - "azureStorageSubscriptionId": "[if(and(variables('storagePassedIn'), greater(length(variables('storageParts')), 2)), variables('storageParts')[2], subscription().subscriptionId)]", - "azureStorageResourceGroupName": "[if(and(variables('storagePassedIn'), greater(length(variables('storageParts')), 4)), variables('storageParts')[4], resourceGroup().name)]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "aiSearchServiceSubscriptionId": "[if(and(variables('searchPassedIn'), greater(length(variables('acsParts')), 2)), variables('acsParts')[2], subscription().subscriptionId)]", - "aiSearchServiceResourceGroupName": "[if(and(variables('searchPassedIn'), greater(length(variables('acsParts')), 4)), variables('acsParts')[4], resourceGroup().name)]", - "cosmosParts": "[split(parameters('azureCosmosDBAccountResourceId'), '/')]", - "cosmosDBSubscriptionId": "[if(and(variables('cosmosPassedIn'), greater(length(variables('cosmosParts')), 2)), variables('cosmosParts')[2], subscription().subscriptionId)]", - "cosmosDBResourceGroupName": "[if(and(variables('cosmosPassedIn'), greater(length(variables('cosmosParts')), 4)), variables('cosmosParts')[4], resourceGroup().name)]", - "dnsZoneTypes": [ - "Microsoft.Network/privateDnsZones" - ] - }, - "resources": [], - "outputs": { - "aiSearchExists": { - "type": "bool", - "value": "[and(variables('searchPassedIn'), equals(last(split(parameters('aiSearchResourceId'), '/')), variables('acsParts')[8]))]" - }, - "cosmosDBExists": { - "type": "bool", - "value": "[and(variables('cosmosPassedIn'), equals(last(split(parameters('azureCosmosDBAccountResourceId'), '/')), variables('cosmosParts')[8]))]" - }, - "azureStorageExists": { - "type": "bool", - "value": "[and(variables('storagePassedIn'), equals(last(split(parameters('azureStorageAccountResourceId'), '/')), variables('storageParts')[8]))]" - }, - "aiSearchServiceSubscriptionId": { - "type": "string", - "value": "[variables('aiSearchServiceSubscriptionId')]" - }, - "aiSearchServiceResourceGroupName": { - "type": "string", - "value": "[variables('aiSearchServiceResourceGroupName')]" - }, - "cosmosDBSubscriptionId": { - "type": "string", - "value": "[variables('cosmosDBSubscriptionId')]" - }, - "cosmosDBResourceGroupName": { - "type": "string", - "value": "[variables('cosmosDBResourceGroupName')]" - }, - "azureStorageSubscriptionId": { - "type": "string", - "value": "[variables('azureStorageSubscriptionId')]" - }, - "azureStorageResourceGroupName": { - "type": "string", - "value": "[variables('azureStorageResourceGroupName')]" - }, - "dnsZoneExists": { - "type": "array", - "copy": { - "count": "[length(parameters('dnsZoneNames'))]", - "input": { - "name": "[parameters('dnsZoneNames')[copyIndex()]]", - "exists": "[not(empty(parameters('existingDnsZones')[parameters('dnsZoneNames')[copyIndex()]]))]" - } - } - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('dependencies-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - }, - "azureStorageName": { - "value": "[variables('azureStorageName')]" - }, - "aiSearchName": { - "value": "[variables('aiSearchName')]" - }, - "cosmosDBName": { - "value": "[variables('cosmosDBName')]" - }, - "aiSearchResourceId": { - "value": "[parameters('aiSearchResourceId')]" - }, - "aiSearchExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchExists.value]" - }, - "azureStorageAccountResourceId": { - "value": "[parameters('azureStorageAccountResourceId')]" - }, - "azureStorageExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageExists.value]" - }, - "cosmosDBResourceId": { - "value": "[parameters('azureCosmosDBAccountResourceId')]" - }, - "cosmosDBExists": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBExists.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.39.26.7824", - "templateHash": "2754228344238136934" - } - }, - "parameters": { - "location": { - "type": "string", - "metadata": { - "description": "Azure region of the deployment" - } - }, - "aiSearchName": { - "type": "string", - "metadata": { - "description": "The name of the AI Search resource" - } - }, - "azureStorageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the new Cosmos DB account" - } - }, - "aiSearchResourceId": { - "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." - } - }, - "azureStorageAccountResourceId": { - "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." - } - }, - "cosmosDBResourceId": { - "type": "string", - "metadata": { - "description": "The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." - } - }, - "aiSearchExists": { - "type": "bool" - }, - "azureStorageExists": { - "type": "bool" - }, - "cosmosDBExists": { - "type": "bool" - }, - "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": { - "cosmosParts": "[split(parameters('cosmosDBResourceId'), '/')]", - "canaryRegions": [ - "eastus2euap", - "centraluseuap" - ], - "cosmosDbRegion": "[if(contains(variables('canaryRegions'), parameters('location')), 'westus', parameters('location'))]", - "acsParts": "[split(parameters('aiSearchResourceId'), '/')]", - "azureStorageParts": "[split(parameters('azureStorageAccountResourceId'), '/')]" - }, - "resources": [ - { - "condition": "[not(parameters('cosmosDBExists'))]", - "type": "Microsoft.DocumentDB/databaseAccounts", - "apiVersion": "2024-11-15", - "name": "[parameters('cosmosDBName')]", - "location": "[variables('cosmosDbRegion')]", - "kind": "GlobalDocumentDB", - "properties": { - "consistencyPolicy": { - "defaultConsistencyLevel": "Session" - }, - "disableLocalAuth": true, - "enableAutomaticFailover": false, - "enableMultipleWriteLocations": false, - "publicNetworkAccess": "Disabled", - "enableFreeTier": false, - "locations": [ - { - "locationName": "[parameters('location')]", - "failoverPriority": 0, - "isZoneRedundant": false - } - ], - "databaseAccountOfferType": "Standard" - } - }, - { - "condition": "[not(parameters('aiSearchExists'))]", - "type": "Microsoft.Search/searchServices", - "apiVersion": "2024-06-01-preview", - "name": "[parameters('aiSearchName')]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "disableLocalAuth": false, - "authOptions": { - "aadOrApiKey": { - "aadAuthFailureMode": "http401WithBearerChallenge" - } - }, - "encryptionWithCmk": { - "enforcement": "Unspecified" - }, - "hostingMode": "default", - "partitionCount": 1, - "publicNetworkAccess": "disabled", - "replicaCount": 1, - "semanticSearch": "disabled", - "networkRuleSet": { - "bypass": "None", - "ipRules": [] - } - }, - "sku": { - "name": "standard" - } - }, - { - "condition": "[not(parameters('azureStorageExists'))]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2023-05-01", - "name": "[parameters('azureStorageName')]", - "location": "[parameters('location')]", - "kind": "StorageV2", - "sku": "[parameters('sku')]", - "properties": { - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Disabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [] - }, - "allowSharedKeyAccess": false - } - } - ], - "outputs": { - "aiSearchName": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), variables('acsParts')[8], parameters('aiSearchName'))]" - }, - "aiSearchID": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), 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(parameters('aiSearchExists'), variables('acsParts')[4], resourceGroup().name)]" - }, - "aiSearchServiceSubscriptionId": { - "type": "string", - "value": "[if(parameters('aiSearchExists'), variables('acsParts')[2], subscription().subscriptionId)]" - }, - "azureStorageName": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[8], parameters('azureStorageName'))]" - }, - "azureStorageId": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageParts')[2], variables('azureStorageParts')[4]), 'Microsoft.Storage/storageAccounts', variables('azureStorageParts')[8]), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]" - }, - "azureStorageResourceGroupName": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[4], resourceGroup().name)]" - }, - "azureStorageSubscriptionId": { - "type": "string", - "value": "[if(parameters('azureStorageExists'), variables('azureStorageParts')[2], subscription().subscriptionId)]" - }, - "cosmosDBName": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[8], parameters('cosmosDBName'))]" - }, - "cosmosDBId": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('cosmosParts')[2], variables('cosmosParts')[4]), 'Microsoft.DocumentDB/databaseAccounts', variables('cosmosParts')[8]), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]" - }, - "cosmosDBResourceGroupName": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[4], resourceGroup().name)]" - }, - "cosmosDBSubscriptionId": { - "type": "string", - "value": "[if(parameters('cosmosDBExists'), variables('cosmosParts')[2], subscription().subscriptionId)]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('validate-existing-resources-{0}-deployment', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-private-endpoint', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiAccountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.value]" - }, - "aiSearchName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "storageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "fabricWorkspaceResourceId": { - "value": "[parameters('fabricWorkspaceResourceId')]" - }, - "vnetName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkName.value]" - }, - "peSubnetName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.peSubnetName.value]" - }, - "suffix": { - "value": "[variables('uniqueSuffix')]" - }, - "vnetResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkResourceGroup.value]" - }, - "vnetSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkSubscriptionId.value]" - }, - "cosmosDBSubscriptionId": { - "value": "[variables('cosmosDBSubscriptionId')]" - }, - "cosmosDBResourceGroupName": { - "value": "[variables('cosmosDBResourceGroupName')]" - }, - "aiSearchSubscriptionId": { - "value": "[variables('aiSearchServiceSubscriptionId')]" - }, - "aiSearchResourceGroupName": { - "value": "[variables('aiSearchServiceResourceGroupName')]" - }, - "storageAccountResourceGroupName": { - "value": "[variables('azureStorageResourceGroupName')]" - }, - "storageAccountSubscriptionId": { - "value": "[variables('azureStorageSubscriptionId')]" - }, - "existingDnsZones": { - "value": "[parameters('existingDnsZones')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "10536644141407027053" - } - }, - "parameters": { - "aiAccountName": { - "type": "string", - "metadata": { - "description": "Name of the AI Foundry account" - } - }, - "aiSearchName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search service" - } - }, - "storageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the Cosmos DB account" - } - }, - "fabricWorkspaceResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The Microsoft Fabric Workspace full ARM Resource ID. Optional - leave empty to skip Fabric private endpoint." - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the Vnet" - } - }, - "peSubnetName": { - "type": "string", - "metadata": { - "description": "Name of the Customer subnet" - } - }, - "suffix": { - "type": "string", - "metadata": { - "description": "Suffix for unique resource names" - } - }, - "vnetResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for existing Virtual Network (if different from current resource group)" - } - }, - "vnetSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Virtual Network" - } - }, - "storageAccountResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for Storage Account" - } - }, - "storageAccountSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Storage account" - } - }, - "aiSearchSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for AI Search service" - } - }, - "aiSearchResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource Group name for AI Search service" - } - }, - "cosmosDBSubscriptionId": { - "type": "string", - "defaultValue": "[subscription().subscriptionId]", - "metadata": { - "description": "Subscription ID for Cosmos DB account" - } - }, - "cosmosDBResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Resource group name for Cosmos DB account" - } - }, - "existingDnsZones": { - "type": "object", - "defaultValue": { - "privatelink.services.ai.azure.com": "", - "privatelink.openai.azure.com": "", - "privatelink.cognitiveservices.azure.com": "", - "privatelink.search.windows.net": "", - "[format('privatelink.blob.{0}', environment().suffixes.storage)]": "", - "privatelink.documents.azure.com": "", - "privatelink.fabric.microsoft.com": "" - }, - "metadata": { - "description": "Map of DNS zone FQDNs to resource group names. If provided, reference existing DNS zones in this resource group instead of creating them." - } - } - }, - "variables": { - "fabricPassedIn": "[not(equals(parameters('fabricWorkspaceResourceId'), ''))]", - "fabricParts": "[split(parameters('fabricWorkspaceResourceId'), '/')]", - "fabricWorkspaceName": "[if(variables('fabricPassedIn'), last(variables('fabricParts')), '')]", - "aiServicesDnsZoneName": "privatelink.services.ai.azure.com", - "openAiDnsZoneName": "privatelink.openai.azure.com", - "cognitiveServicesDnsZoneName": "privatelink.cognitiveservices.azure.com", - "aiSearchDnsZoneName": "privatelink.search.windows.net", - "storageDnsZoneName": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "cosmosDBDnsZoneName": "privatelink.documents.azure.com", - "fabricDnsZoneName": "privatelink.fabric.microsoft.com", - "aiServicesDnsZoneRG": "[parameters('existingDnsZones')[variables('aiServicesDnsZoneName')]]", - "openAiDnsZoneRG": "[parameters('existingDnsZones')[variables('openAiDnsZoneName')]]", - "cognitiveServicesDnsZoneRG": "[parameters('existingDnsZones')[variables('cognitiveServicesDnsZoneName')]]", - "aiSearchDnsZoneRG": "[parameters('existingDnsZones')[variables('aiSearchDnsZoneName')]]", - "storageDnsZoneRG": "[parameters('existingDnsZones')[variables('storageDnsZoneName')]]", - "cosmosDBDnsZoneRG": "[parameters('existingDnsZones')[variables('cosmosDBDnsZoneName')]]", - "fabricDnsZoneRG": "[coalesce(tryGet(parameters('existingDnsZones'), 'fabricDnsZoneName'), '')]", - "aiServicesDnsZoneId": "[if(empty(variables('aiServicesDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('aiServicesDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName')))]", - "openAiDnsZoneId": "[if(empty(variables('openAiDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('openAiDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName')))]", - "cognitiveServicesDnsZoneId": "[if(empty(variables('cognitiveServicesDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('cognitiveServicesDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName')))]", - "aiSearchDnsZoneId": "[if(empty(variables('aiSearchDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('aiSearchDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName')))]", - "storageDnsZoneId": "[if(empty(variables('storageDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('storageDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('storageDnsZoneName')))]", - "cosmosDBDnsZoneId": "[if(empty(variables('cosmosDBDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('cosmosDBDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName')))]", - "fabricDnsZoneId": "[if(variables('fabricPassedIn'), if(empty(variables('fabricDnsZoneRG')), resourceId('Microsoft.Network/privateDnsZones', variables('fabricDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('fabricDnsZoneRG')), 'Microsoft.Network/privateDnsZones', variables('fabricDnsZoneName'))), '')]" - }, - "resources": [ - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('aiAccountName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('aiAccountName'))]", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiAccountName'))]", - "groupIds": [ - "account" - ] - } - } - ] - } - }, - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('aiSearchName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('aiSearchName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchSubscriptionId'), parameters('aiSearchResourceGroupName')), '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": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('storageName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('storageAccountSubscriptionId'), parameters('storageAccountResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('storageName'))]", - "groupIds": [ - "blob" - ] - } - } - ] - } - }, - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-private-endpoint', parameters('cosmosDBName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', parameters('cosmosDBName'))]", - "properties": { - "privateLinkServiceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", - "groupIds": [ - "Sql" - ] - } - } - ] - } - }, - { - "condition": "[variables('fabricPassedIn')]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", - "name": "[format('{0}-fabric-private-endpoint', variables('fabricWorkspaceName'))]", - "location": "[resourceGroup().location]", - "properties": { - "subnet": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('peSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "[format('{0}-private-link-service-connection', variables('fabricWorkspaceName'))]", - "properties": { - "privateLinkServiceId": "[parameters('fabricWorkspaceResourceId')]", - "groupIds": [ - "Fabric" - ] - } - } - ] - } - }, - { - "condition": "[empty(variables('aiServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('aiServicesDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('openAiDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('openAiDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('cognitiveServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('cognitiveServicesDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('aiSearchDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('aiSearchDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('storageDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('storageDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('cosmosDBDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('cosmosDBDnsZoneName')]", - "location": "global" - }, - { - "condition": "[and(variables('fabricPassedIn'), empty(variables('fabricDnsZoneRG')))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('fabricDnsZoneName')]", - "location": "global" - }, - { - "condition": "[empty(variables('aiServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('aiServicesDnsZoneName'), format('aiServices-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('openAiDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('openAiDnsZoneName'), format('aiServicesOpenAI-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('cognitiveServicesDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('cognitiveServicesDnsZoneName'), format('aiServicesCognitiveServices-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('aiSearchDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('aiSearchDnsZoneName'), format('aiSearch-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('storageDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('storageDnsZoneName'), format('storage-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName'))]" - ] - }, - { - "condition": "[empty(variables('cosmosDBDnsZoneRG'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('cosmosDBDnsZoneName'), format('cosmosDB-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName'))]" - ] - }, - { - "condition": "[and(variables('fabricPassedIn'), empty(variables('fabricDnsZoneRG')))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('fabricDnsZoneName'), format('fabric-{0}-link', parameters('suffix')))]", - "location": "global", - "properties": { - "virtualNetwork": { - "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('vnetSubscriptionId'), parameters('vnetResourceGroupName')), 'Microsoft.Network/virtualNetworks', parameters('vnetName'))]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('fabricDnsZoneName'))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('aiAccountName')), format('{0}-dns-group', parameters('aiAccountName')))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('{0}-dns-aiserv-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('aiServicesDnsZoneId')]" - } - }, - { - "name": "[format('{0}-dns-openai-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('openAiDnsZoneId')]" - } - }, - { - "name": "[format('{0}-dns-cogserv-config', parameters('aiAccountName'))]", - "properties": { - "privateDnsZoneId": "[variables('cognitiveServicesDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiAccountName')))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('aiServicesDnsZoneName'), format('aiServices-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiServicesDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('cognitiveServicesDnsZoneName'), format('aiServicesCognitiveServices-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('cognitiveServicesDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('openAiDnsZoneName'), format('aiServicesOpenAI-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('openAiDnsZoneName'))]" - ] - }, - { - "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": "[variables('aiSearchDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('aiSearchDnsZoneName'), format('aiSearch-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('aiSearchDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('aiSearchName')))]" - ] - }, - { - "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": "[variables('storageDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('storageDnsZoneName'), format('storage-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('storageDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('storageName')))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('cosmosDBName')), format('{0}-dns-group', parameters('cosmosDBName')))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('{0}-dns-config', parameters('cosmosDBName'))]", - "properties": { - "privateDnsZoneId": "[variables('cosmosDBDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('cosmosDBDnsZoneName'), format('cosmosDB-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('cosmosDBDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('cosmosDBName')))]" - ] - }, - { - "condition": "[variables('fabricPassedIn')]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", - "name": "[format('{0}/{1}', format('{0}-fabric-private-endpoint', variables('fabricWorkspaceName')), format('{0}-dns-group', variables('fabricWorkspaceName')))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('{0}-dns-config', variables('fabricWorkspaceName'))]", - "properties": { - "privateDnsZoneId": "[variables('fabricDnsZoneId')]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('fabricDnsZoneName'), format('fabric-{0}-link', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('fabricDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-fabric-private-endpoint', variables('fabricWorkspaceName')))]" - ] - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "projectName": { - "value": "[variables('projectName')]" - }, - "projectDescription": { - "value": "[parameters('projectDescription')]" - }, - "displayName": { - "value": "[parameters('displayName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "aiSearchName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "aiSearchServiceResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchServiceResourceGroupName.value]" - }, - "aiSearchServiceSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchServiceSubscriptionId.value]" - }, - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "cosmosDBSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBSubscriptionId.value]" - }, - "cosmosDBResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBResourceGroupName.value]" - }, - "azureStorageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "azureStorageSubscriptionId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageSubscriptionId.value]" - }, - "azureStorageResourceGroupName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageResourceGroupName.value]" - }, - "accountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.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.39.26.7824", - "templateHash": "5095087340309076800" - } - }, - "parameters": { - "accountName": { - "type": "string" - }, - "location": { - "type": "string" - }, - "projectName": { - "type": "string" - }, - "projectDescription": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "aiSearchName": { - "type": "string" - }, - "aiSearchServiceResourceGroupName": { - "type": "string" - }, - "aiSearchServiceSubscriptionId": { - "type": "string" - }, - "cosmosDBName": { - "type": "string" - }, - "cosmosDBSubscriptionId": { - "type": "string" - }, - "cosmosDBResourceGroupName": { - "type": "string" - }, - "azureStorageName": { - "type": "string" - }, - "azureStorageSubscriptionId": { - "type": "string" - }, - "azureStorageResourceGroupName": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('cosmosDBName'))]", - "properties": { - "category": "CosmosDB", - "target": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), '2024-12-01-preview').documentEndpoint]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", - "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('cosmosDBSubscriptionId'), parameters('cosmosDBResourceGroupName')), 'Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), '2024-12-01-preview', 'full').location]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('azureStorageName'))]", - "properties": { - "category": "AzureStorageAccount", - "target": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName')), '2023-05-01').primaryEndpoints.blob]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", - "location": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('azureStorageSubscriptionId'), parameters('azureStorageResourceGroupName')), 'Microsoft.Storage/storageAccounts', parameters('azureStorageName')), '2023-05-01', 'full').location]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('aiSearchName'))]", - "properties": { - "category": "CognitiveSearch", - "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", - "authType": "AAD", - "metadata": { - "ApiType": "Azure", - "ResourceId": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('aiSearchServiceSubscriptionId'), parameters('aiSearchServiceResourceGroupName')), 'Microsoft.Search/searchServices', parameters('aiSearchName'))]", - "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.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - ] - }, - { - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('accountName'), parameters('projectName'))]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "description": "[parameters('projectDescription')]", - "displayName": "[parameters('displayName')]" - } - } - ], - "outputs": { - "projectName": { - "type": "string", - "value": "[parameters('projectName')]" - }, - "projectId": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" - }, - "projectPrincipalId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName')), '2025-04-01-preview', 'full').identity.principalId]" - }, - "projectWorkspaceId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName')), '2025-04-01-preview').internalId]" - }, - "cosmosDBConnection": { - "type": "string", - "value": "[parameters('cosmosDBName')]" - }, - "azureStorageConnection": { - "type": "string", - "value": "[parameters('azureStorageName')]" - }, - "aiSearchConnection": { - "type": "string", - "value": "[parameters('aiSearchName')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "projectWorkspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceId.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.39.26.7824", - "templateHash": "6910483561575524105" - } - }, - "parameters": { - "projectWorkspaceId": { - "type": "string" - } - }, - "variables": { - "part1": "[substring(parameters('projectWorkspaceId'), 0, 8)]", - "part2": "[substring(parameters('projectWorkspaceId'), 8, 4)]", - "part3": "[substring(parameters('projectWorkspaceId'), 12, 4)]", - "part4": "[substring(parameters('projectWorkspaceId'), 16, 4)]", - "part5": "[substring(parameters('projectWorkspaceId'), 20, 12)]", - "formattedGuid": "[format('{0}-{1}-{2}-{3}-{4}', variables('part1'), variables('part2'), variables('part3'), variables('part4'), variables('part5'))]" - }, - "resources": [], - "outputs": { - "projectWorkspaceIdGuid": { - "type": "string", - "value": "[variables('formattedGuid')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('storage-{0}-{1}-deployment', variables('azureStorageName'), variables('uniqueSuffix'))]", - "subscriptionId": "[variables('azureStorageSubscriptionId')]", - "resourceGroup": "[variables('azureStorageResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "azureStorageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "14683840003859985069" - } - }, - "parameters": { - "azureStorageName": { - "type": "string" - }, - "projectPrincipalId": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", - "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('cosmos-account-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('cosmosDBSubscriptionId')]", - "resourceGroup": "[variables('cosmosDBResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "cosmosDBName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "25128059954858801" - } - }, - "parameters": { - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Name of the Cosmos DB resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the AI project" - } - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", - "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('ai-search-ra-{0}-deployment', 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}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchName.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "7968115481508840" - } - }, - "parameters": { - "aiSearchName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal 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('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "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('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalType": "ServicePrincipal" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "accountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountName.value]" - }, - "projectName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectName.value]" - }, - "cosmosDBConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBConnection.value]" - }, - "azureStorageConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageConnection.value]" - }, - "aiSearchConnection": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.aiSearchConnection.value]" - }, - "projectCapHost": { - "value": "[parameters('projectCapHost')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17458377866351620215" - } - }, - "parameters": { - "cosmosDBConnection": { - "type": "string" - }, - "azureStorageConnection": { - "type": "string" - }, - "aiSearchConnection": { - "type": "string" - }, - "projectName": { - "type": "string" - }, - "accountName": { - "type": "string" - }, - "projectCapHost": { - "type": "string" - } - }, - "variables": { - "threadConnections": [ - "[format('{0}', parameters('cosmosDBConnection'))]" - ], - "storageConnections": [ - "[format('{0}', parameters('azureStorageConnection'))]" - ], - "vectorStoreConnections": [ - "[format('{0}', parameters('aiSearchConnection'))]" - ] - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects/capabilityHosts", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('accountName'), parameters('projectName'), parameters('projectCapHost'))]", - "properties": { - "capabilityHostKind": "Agents", - "vectorStoreConnections": "[variables('vectorStoreConnections')]", - "storageConnections": "[variables('storageConnections')]", - "threadStorageConnections": "[variables('threadConnections')]" - } - } - ], - "outputs": { - "projectCapHost": { - "type": "string", - "value": "[parameters('projectCapHost')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', 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-ra-{0}-deployment', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('cosmosDBSubscriptionId'), variables('cosmosDBResourceGroupName')), 'Microsoft.Resources/deployments', format('cosmos-account-ra-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-private-endpoint', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageSubscriptionId'), variables('azureStorageResourceGroupName')), 'Microsoft.Resources/deployments', format('storage-{0}-{1}-deployment', variables('azureStorageName'), variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('storage-containers-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('azureStorageSubscriptionId')]", - "resourceGroup": "[variables('azureStorageResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiProjectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.value]" - }, - "storageName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.azureStorageName.value]" - }, - "workspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceIdGuid.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.39.26.7824", - "templateHash": "13874725855824693255" - } - }, - "parameters": { - "storageName": { - "type": "string", - "metadata": { - "description": "Name of the storage account" - } - }, - "aiProjectPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the AI Project" - } - }, - "workspaceId": { - "type": "string", - "metadata": { - "description": "Workspace Id of the AI Project" - } - } - }, - "variables": { - "conditionStr": "[format('((!(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read''}}) AND !(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action''}}) AND !(ActionMatches{{''Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write''}}) ) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase ''{0}'' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase ''*-azureml-agent''))', parameters('workspaceId'))]" - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", - "name": "[guid(resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]", - "properties": { - "principalId": "[parameters('aiProjectPrincipalId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", - "principalType": "ServicePrincipal", - "conditionVersion": "2.0", - "condition": "[variables('conditionStr')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('cosmos-containers-ra-{0}-deployment', variables('uniqueSuffix'))]", - "subscriptionId": "[variables('cosmosDBSubscriptionId')]", - "resourceGroup": "[variables('cosmosDBResourceGroupName')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "cosmosAccountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.cosmosDBName.value]" - }, - "projectWorkspaceId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.projectWorkspaceIdGuid.value]" - }, - "projectPrincipalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.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.39.26.7824", - "templateHash": "16291470712974205281" - } - }, - "parameters": { - "cosmosAccountName": { - "type": "string", - "metadata": { - "description": "Name of the AI Search resource" - } - }, - "projectPrincipalId": { - "type": "string", - "metadata": { - "description": "Project name" - } - }, - "projectWorkspaceId": { - "type": "string" - } - }, - "variables": { - "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosAccountName'), '00000000-0000-0000-0000-000000000002')]", - "accountScope": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DocumentDB/databaseAccounts/{2}', subscription().subscriptionId, resourceGroup().name, parameters('cosmosAccountName'))]" - }, - "resources": [ - { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", - "apiVersion": "2022-05-15", - "name": "[format('{0}/{1}', parameters('cosmosAccountName'), guid(parameters('projectWorkspaceId'), parameters('cosmosAccountName'), variables('roleDefinitionId'), parameters('projectPrincipalId')))]", - "properties": { - "principalId": "[parameters('projectPrincipalId')]", - "roleDefinitionId": "[variables('roleDefinitionId')]", - "scope": "[variables('accountScope')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', format('capabilityHost-configuration-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]", - "[resourceId('Microsoft.Resources/deployments', format('format-project-workspace-id-{0}-deployment', variables('uniqueSuffix')))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('azureStorageSubscriptionId'), variables('azureStorageResourceGroupName')), 'Microsoft.Resources/deployments', format('storage-containers-ra-{0}-deployment', variables('uniqueSuffix')))]" - ] - } - ] -} \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/mcp-http-server/Dockerfile b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/mcp-http-server/Dockerfile deleted file mode 100644 index 784c302f4..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/mcp-http-server/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Multi-Auth MCP Server Image -# -# This Dockerfile simply references the pre-built multi-auth MCP image. -# The source image is maintained at: retrievaltestacr.azurecr.io/multi-auth-mcp/api-multi-auth-mcp-env -# -# To use this image: -# 1. Import to your ACR: -# az acr import --name --source retrievaltestacr.azurecr.io/multi-auth-mcp/api-multi-auth-mcp-env:latest --image multi-auth-mcp:latest -# -# 2. Or build locally using this Dockerfile: -# docker build -t multi-auth-mcp:latest . -# -# The MCP server exposes the following endpoints: -# - /noauth/mcp - No authentication required (for testing) -# - /mcp - Requires authentication -# - /healthz - Health check endpoint -# -# Environment variables (optional): -# - PORT: Server port (default: 8080) -# - TENANT_ID: Azure AD tenant ID -# - MCP_APP_ID: MCP application ID -# - API_KEYS: Comma-separated API keys for authentication - -FROM retrievaltestacr.azurecr.io/multi-auth-mcp/api-multi-auth-mcp-env:latest - -# The base image already configures everything, just expose the port -EXPOSE 8080 diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/metadata.json b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/metadata.json deleted file mode 100644 index a91a5830a..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/metadata.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "Hybrid Private Resources Agent Setup", - "description": "Azure AI Foundry with public API access and private backend resources (AI Search, Cosmos DB, Storage). Enables portal-based agent testing with MCP servers and AI Search tools on private endpoints.", - "version": "1.0.0", - "keywords": [ - "hybrid", - "private-endpoints", - "data-proxy", - "mcp", - "ai-search", - "portal-access" - ], - "architecture": { - "ai_services_access": "public", - "backend_resources": "private", - "data_proxy": "enabled", - "portal_compatible": true - }, - "prerequisites": [ - "Azure subscription with Owner or Contributor role", - "Azure AI Account Owner role for creating AI Services", - "Sufficient quota for gpt-4o-mini model deployment" - ], - "resources_created": [ - "Microsoft.CognitiveServices/accounts (AI Services with public access)", - "Microsoft.CognitiveServices/accounts/projects", - "Microsoft.Search/searchServices (private endpoint)", - "Microsoft.DocumentDB/databaseAccounts (private endpoint)", - "Microsoft.Storage/storageAccounts (private endpoint)", - "Microsoft.Network/virtualNetworks", - "Microsoft.Network/privateEndpoints", - "Microsoft.Network/privateDnsZones" - ] -} \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/add-project-capability-host.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/add-project-capability-host.bicep deleted file mode 100644 index dd2ac3297..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/add-project-capability-host.bicep +++ /dev/null @@ -1,34 +0,0 @@ -param cosmosDBConnection string -param azureStorageConnection string -param aiSearchConnection string -param projectName string -param accountName string -param projectCapHost string - -var threadConnections = ['${cosmosDBConnection}'] -var storageConnections = ['${azureStorageConnection}'] -var vectorStoreConnections = ['${aiSearchConnection}'] - - -resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: accountName -} - -resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = { - name: projectName - parent: account -} - -resource projectCapabilityHost 'Microsoft.CognitiveServices/accounts/projects/capabilityHosts@2025-04-01-preview' = { - name: projectCapHost - parent: project - properties: { - capabilityHostKind: 'Agents' - vectorStoreConnections: vectorStoreConnections - storageConnections: storageConnections - threadStorageConnections: threadConnections - } - -} - -output projectCapHost string = projectCapabilityHost.name diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-account-identity.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-account-identity.bicep deleted file mode 100644 index 1689c4214..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-account-identity.bicep +++ /dev/null @@ -1,68 +0,0 @@ -param accountName string -param location string -param modelName string -param modelFormat string -param modelVersion string -param modelSkuName string -param modelCapacity int -param agentSubnetId string -param networkInjection string = 'true' - -// Hybrid setup: Public network access disabled by default for the Foundry resource -// The Data Proxy (networkInjections) routes tool calls to private resources - -#disable-next-line BCP036 -resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { - name: accountName - location: location - sku: { - name: 'S0' - } - kind: 'AIServices' - identity: { - type: 'SystemAssigned' - } - properties: { - allowProjectManagement: true - customSubDomainName: accountName - networkAcls: { - defaultAction: 'Deny' - virtualNetworkRules: [] - ipRules: [] - bypass: 'AzureServices' - } - publicNetworkAccess: 'Disabled' - networkInjections: ((networkInjection == 'true') - ? [ - { - scenario: 'agent' - subnetArmId: agentSubnetId - useMicrosoftManagedNetwork: false - } - ] - : null) - disableLocalAuth: false - } -} - -#disable-next-line BCP081 -resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { - parent: account - name: modelName - sku: { - capacity: modelCapacity - name: modelSkuName - } - properties: { - model: { - name: modelName - format: modelFormat - version: modelVersion - } - } -} - -output accountName string = account.name -output accountID string = account.id -output accountTarget string = account.properties.endpoint -output accountPrincipalId string = account.identity.principalId diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity-unique.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity-unique.bicep deleted file mode 100644 index 471e1fb98..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity-unique.bicep +++ /dev/null @@ -1,106 +0,0 @@ -param accountName string -param location string -param projectName string -param projectDescription string -param displayName string - -param aiSearchName string -param aiSearchServiceResourceGroupName string -param aiSearchServiceSubscriptionId string - -param cosmosDBName string -param cosmosDBSubscriptionId string -param cosmosDBResourceGroupName string - -param azureStorageName string -param azureStorageSubscriptionId string -param azureStorageResourceGroupName string - -// Add unique connection name parameter -param uniqueConnectionSuffix string = '' - -resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { - name: aiSearchName - scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) -} -resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { - name: cosmosDBName - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) -} -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { - name: azureStorageName - scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) -} - -resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: accountName - scope: resourceGroup() -} - -resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { - parent: account - name: projectName - location: location - identity: { - type: 'SystemAssigned' - } - properties: { - description: projectDescription - displayName: displayName - } - - // Use unique connection names by appending the suffix - resource project_connection_cosmosdb_account 'connections@2025-04-01-preview' = { - name: '${cosmosDBName}${uniqueConnectionSuffix}' - properties: { - category: 'CosmosDB' - target: cosmosDBAccount.properties.documentEndpoint - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: cosmosDBAccount.id - location: cosmosDBAccount.location - } - } - } - - resource project_connection_azure_storage 'connections@2025-04-01-preview' = { - name: '${azureStorageName}${uniqueConnectionSuffix}' - properties: { - category: 'AzureStorageAccount' - target: storageAccount.properties.primaryEndpoints.blob - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: storageAccount.id - location: storageAccount.location - } - } - } - - resource project_connection_azureai_search 'connections@2025-04-01-preview' = { - name: '${aiSearchName}${uniqueConnectionSuffix}' - properties: { - category: 'CognitiveSearch' - target: 'https://${aiSearchName}.search.windows.net' - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: searchService.id - location: searchService.location - } - } - } -} - -output projectName string = project.name -output projectId string = project.id -output projectPrincipalId string = project.identity.principalId - -#disable-next-line BCP053 -output projectWorkspaceId string = project.properties.internalId - -// Return the unique connection names -output cosmosDBConnection string = '${cosmosDBName}${uniqueConnectionSuffix}' -output azureStorageConnection string = '${azureStorageName}${uniqueConnectionSuffix}' -output aiSearchConnection string = '${aiSearchName}${uniqueConnectionSuffix}' diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity.bicep deleted file mode 100644 index 90aebfbd3..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-project-identity.bicep +++ /dev/null @@ -1,103 +0,0 @@ -param accountName string -param location string -param projectName string -param projectDescription string -param displayName string - -param aiSearchName string -param aiSearchServiceResourceGroupName string -param aiSearchServiceSubscriptionId string - -param cosmosDBName string -param cosmosDBSubscriptionId string -param cosmosDBResourceGroupName string - -param azureStorageName string -param azureStorageSubscriptionId string -param azureStorageResourceGroupName string - -resource searchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { - name: aiSearchName - scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) -} -resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { - name: cosmosDBName - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) -} -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { - name: azureStorageName - scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) -} - -resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: accountName - scope: resourceGroup() -} - -resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { - parent: account - name: projectName - location: location - identity: { - type: 'SystemAssigned' - } - properties: { - description: projectDescription - displayName: displayName - } - - resource project_connection_cosmosdb_account 'connections@2025-04-01-preview' = { - name: cosmosDBName - properties: { - category: 'CosmosDB' - target: cosmosDBAccount.properties.documentEndpoint - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: cosmosDBAccount.id - location: cosmosDBAccount.location - } - } - } - - resource project_connection_azure_storage 'connections@2025-04-01-preview' = { - name: azureStorageName - properties: { - category: 'AzureStorageAccount' - target: storageAccount.properties.primaryEndpoints.blob - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: storageAccount.id - location: storageAccount.location - } - } - } - - resource project_connection_azureai_search 'connections@2025-04-01-preview' = { - name: aiSearchName - properties: { - category: 'CognitiveSearch' - target: 'https://${aiSearchName}.search.windows.net' - authType: 'AAD' - metadata: { - ApiType: 'Azure' - ResourceId: searchService.id - location: searchService.location - } - } - } - -} - -output projectName string = project.name -output projectId string = project.id -output projectPrincipalId string = project.identity.principalId - -#disable-next-line BCP053 -output projectWorkspaceId string = project.properties.internalId - -// return the BYO connection names -output cosmosDBConnection string = cosmosDBName -output azureStorageConnection string = azureStorageName -output aiSearchConnection string = aiSearchName diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-search-role-assignments.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-search-role-assignments.bicep deleted file mode 100644 index 715663a6c..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/ai-search-role-assignments.bicep +++ /dev/null @@ -1,43 +0,0 @@ -// 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 projectPrincipalId 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(projectPrincipalId, searchIndexDataContributorRole.id, searchService.id) - properties: { - principalId: projectPrincipalId - 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(projectPrincipalId, searchServiceContributorRole.id, searchService.id) - properties: { - principalId: projectPrincipalId - roleDefinitionId: searchServiceContributorRole.id - principalType: 'ServicePrincipal' - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/azure-storage-account-role-assignment.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/azure-storage-account-role-assignment.bicep deleted file mode 100644 index afc355a48..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/azure-storage-account-role-assignment.bicep +++ /dev/null @@ -1,24 +0,0 @@ -param azureStorageName string -param projectPrincipalId string - -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { - name: azureStorageName - scope: resourceGroup() -} - -// Blob Storage Owner: b7e6dc6d-f1e8-4753-8033-0f276bb0955b -// Blob Storage Contributor: ba92f5b4-2d11-453d-a403-e96b0029c9fe -resource storageBlobDataContributor 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' existing = { - name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' - scope: resourceGroup() -} - -resource storageBlobDataContributorRoleAssignmentProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - scope: storageAccount - name: guid(projectPrincipalId, storageBlobDataContributor.id, storageAccount.id) - properties: { - principalId: projectPrincipalId - roleDefinitionId: storageBlobDataContributor.id - principalType: 'ServicePrincipal' - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments-unique.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments-unique.bicep deleted file mode 100644 index 2535a42c9..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments-unique.bicep +++ /dev/null @@ -1,38 +0,0 @@ -@description('Name of the storage account') -param storageName string - -@description('Principal ID of the AI Project') -param aiProjectPrincipalId string - -@description('Workspace Id of the AI Project') -param workspaceId string - -@description('Unique suffix to make role assignment unique') -param uniqueSuffix string - -// Reference existing storage account -resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { - name: storageName - scope: resourceGroup() -} - -// Storage Blob Data Owner Role -resource storageBlobDataOwner 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' // Built-in role ID - scope: resourceGroup() -} - -var conditionStr= '((!(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write\'}) ) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase \'${workspaceId}\' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase \'*-azureml-agent\'))' - -// Assign Storage Blob Data Owner role with unique name -resource storageBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - scope: storage - name: guid(storageBlobDataOwner.id, storage.id, aiProjectPrincipalId, uniqueSuffix) - properties: { - principalId: aiProjectPrincipalId - roleDefinitionId: storageBlobDataOwner.id - principalType: 'ServicePrincipal' - conditionVersion: '2.0' - condition: conditionStr - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments.bicep deleted file mode 100644 index 71abc97d6..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/blob-storage-container-role-assignments.bicep +++ /dev/null @@ -1,36 +0,0 @@ -@description('Name of the storage account') -param storageName string - -@description('Principal ID of the AI Project') -param aiProjectPrincipalId string - -@description('Workspace Id of the AI Project') -param workspaceId string - - -// Reference existing storage account -resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { - name: storageName - scope: resourceGroup() -} - -// Storage Blob Data Owner Role -resource storageBlobDataOwner 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' // Built-in role ID - scope: resourceGroup() -} - -var conditionStr= '((!(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write\'}) ) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase \'${workspaceId}\' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase \'*-azureml-agent\'))' - -// Assign Storage Blob Data Owner role -resource storageBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - scope: storage - name: guid(storageBlobDataOwner.id, storage.id) - properties: { - principalId: aiProjectPrincipalId - roleDefinitionId: storageBlobDataOwner.id - principalType: 'ServicePrincipal' - conditionVersion: '2.0' - condition: conditionStr - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmos-container-role-assignments.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmos-container-role-assignments.bicep deleted file mode 100644 index a196cf80e..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmos-container-role-assignments.bicep +++ /dev/null @@ -1,32 +0,0 @@ -// Assigns the necessary roles to the AI project - -@description('Name of the AI Search resource') -param cosmosAccountName string - -@description('Project name') -param projectPrincipalId string - -param projectWorkspaceId string - -resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { - name: cosmosAccountName - scope: resourceGroup() -} - -var roleDefinitionId = resourceId( - 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', - cosmosAccountName, - '00000000-0000-0000-0000-000000000002' -) - -var accountScope = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosAccountName}' - -resource containerRoleAssignmentUserContainer 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { - parent: cosmosAccount - name: guid(projectWorkspaceId, cosmosAccountName, roleDefinitionId, projectPrincipalId) - properties: { - principalId: projectPrincipalId - roleDefinitionId: roleDefinitionId - scope: accountScope - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmosdb-account-role-assignment.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmosdb-account-role-assignment.bicep deleted file mode 100644 index d5d083486..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/cosmosdb-account-role-assignment.bicep +++ /dev/null @@ -1,27 +0,0 @@ -// Assigns Role Cosmos DB Operator to the Project Principal ID -@description('Name of the Cosmos DB resource') -param cosmosDBName string - -@description('Principal ID of the AI project') -param projectPrincipalId string - - -resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { - name: cosmosDBName - scope: resourceGroup() -} - -resource cosmosDBOperatorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: '230815da-be43-4aae-9cb4-875f7bd000aa' - scope: resourceGroup() -} - -resource cosmosDBOperatorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - scope: cosmosDBAccount - name: guid(projectPrincipalId, cosmosDBOperatorRole.id, cosmosDBAccount.id) - properties: { - principalId: projectPrincipalId - roleDefinitionId: cosmosDBOperatorRole.id - principalType: 'ServicePrincipal' - } -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/existing-vnet.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/existing-vnet.bicep deleted file mode 100644 index b464dedba..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/existing-vnet.bicep +++ /dev/null @@ -1,116 +0,0 @@ -/* -Virtual Network Module -This module works with existing virtual networks and required subnets. - -1. Flexibility: - - Works with any existing VNet address space - - Can use existing subnets or create new ones - - Cross-resource group support - -2. Security Features: - - Network isolation - - Subnet delegation for containerized workloads - - Private endpoint subnet for secure connectivity -*/ - -@description('The name of the existing virtual network') -param vnetName string - -@description('Subscription ID of virtual network (if different from current subscription)') -param vnetSubscriptionId string = subscription().subscriptionId - -@description('Resource Group name of the existing VNet (if different from current resource group)') -param vnetResourceGroupName string = resourceGroup().name - -@description('The name of Agents Subnet') -param agentSubnetName string = 'agent-subnet' - -@description('The name of Private Endpoint subnet') -param peSubnetName string = 'pe-subnet' - -@description('The name of MCP subnet for user-deployed Container Apps') -param mcpSubnetName string = 'mcp-subnet' - -@description('Address prefix for the agent subnet (only needed if creating new subnet)') -param agentSubnetPrefix string = '' - -@description('Address prefix for the private endpoint subnet (only needed if creating new subnet)') -param peSubnetPrefix string = '' - -@description('Address prefix for the MCP subnet (only needed if creating new subnet)') -param mcpSubnetPrefix string = '' - -// Get the address space (array of CIDR strings) -var vnetAddressSpace = existingVNet.properties.addressSpace.addressPrefixes[0] - -var agentSubnetSpaces = empty(agentSubnetPrefix) ? cidrSubnet(vnetAddressSpace, 24, 0) : agentSubnetPrefix -var peSubnetSpaces = empty(peSubnetPrefix) ? cidrSubnet(vnetAddressSpace, 24, 1) : peSubnetPrefix -var mcpSubnetSpaces = empty(mcpSubnetPrefix) ? cidrSubnet(vnetAddressSpace, 24, 2) : mcpSubnetPrefix - -// Reference the existing virtual network -resource existingVNet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { - name: vnetName - scope: resourceGroup(vnetResourceGroupName) -} - -// Create the agent subnet if requested -module agentSubnet 'subnet.bicep' = { - name: 'agent-subnet-${uniqueString(deployment().name, agentSubnetName)}' - scope: resourceGroup(vnetResourceGroupName) - params: { - vnetName: vnetName - subnetName: agentSubnetName - addressPrefix: agentSubnetSpaces - delegations: [ - { - name: 'Microsoft.App/environments' - properties: { - serviceName: 'Microsoft.App/environments' - } - } - ] - } -} - -// Create the private endpoint subnet if requested -module peSubnet 'subnet.bicep' = { - name: 'pe-subnet-${uniqueString(deployment().name, peSubnetName)}' - scope: resourceGroup(vnetResourceGroupName) - params: { - vnetName: vnetName - subnetName: peSubnetName - addressPrefix: peSubnetSpaces - delegations: [] - } -} - -// Create the MCP subnet for user-deployed Container Apps -module mcpSubnet 'subnet.bicep' = { - name: 'mcp-subnet-${uniqueString(deployment().name, mcpSubnetName)}' - scope: resourceGroup(vnetResourceGroupName) - params: { - vnetName: vnetName - subnetName: mcpSubnetName - addressPrefix: mcpSubnetSpaces - delegations: [ - { - name: 'Microsoft.App/environments' - properties: { - serviceName: 'Microsoft.App/environments' - } - } - ] - } -} - -// Output variables -output peSubnetName string = peSubnetName -output agentSubnetName string = agentSubnetName -output mcpSubnetName string = mcpSubnetName -output agentSubnetId string = '${existingVNet.id}/subnets/${agentSubnetName}' -output peSubnetId string = '${existingVNet.id}/subnets/${peSubnetName}' -output mcpSubnetId string = '${existingVNet.id}/subnets/${mcpSubnetName}' -output virtualNetworkName string = existingVNet.name -output virtualNetworkId string = existingVNet.id -output virtualNetworkResourceGroup string = vnetResourceGroupName -output virtualNetworkSubscriptionId string = vnetSubscriptionId diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/format-project-workspace-id.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/format-project-workspace-id.bicep deleted file mode 100644 index ac7d0c3f2..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/format-project-workspace-id.bicep +++ /dev/null @@ -1,12 +0,0 @@ - -param projectWorkspaceId string - -var part1 = substring(projectWorkspaceId, 0, 8) // First 8 characters -var part2 = substring(projectWorkspaceId, 8, 4) // Next 4 characters -var part3 = substring(projectWorkspaceId, 12, 4) // Next 4 characters -var part4 = substring(projectWorkspaceId, 16, 4) // Next 4 characters -var part5 = substring(projectWorkspaceId, 20, 12) // Remaining 12 characters - -var formattedGuid = '${part1}-${part2}-${part3}-${part4}-${part5}' - -output projectWorkspaceIdGuid string = formattedGuid diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/network-agent-vnet.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/network-agent-vnet.bicep deleted file mode 100644 index 7be3fa966..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/network-agent-vnet.bicep +++ /dev/null @@ -1,87 +0,0 @@ -@description('Azure region for the deployment') -param location string - -@description('The name of the virtual network') -param vnetName string - -@description('Indicates if an existing VNet should be used') -param useExistingVnet bool = false - -@description('Subscription ID of the existing VNet (if different from current subscription)') -param existingVnetSubscriptionId string = subscription().subscriptionId - -@description('Resource Group name of the existing VNet (if different from current resource group)') -param existingVnetResourceGroupName string = resourceGroup().name - -@description('The name of Agents Subnet') -param agentSubnetName string = 'agent-subnet' - -@description('The name of Private Endpoint subnet') -param peSubnetName string = 'pe-subnet' - -@description('The name of MCP subnet for user-deployed Container Apps') -param mcpSubnetName string = 'mcp-subnet' - -@description('Address space for the VNet (only used for new VNet)') -param vnetAddressPrefix string = '' - -@description('Address prefix for the agent subnet') -param agentSubnetPrefix string = '' - -@description('Address prefix for the private endpoint subnet') -param peSubnetPrefix string = '' - -@description('Address prefix for the MCP subnet') -param mcpSubnetPrefix string = '' - -// Create new VNet if needed -module newVNet 'vnet.bicep' = if (!useExistingVnet) { - name: 'vnet-deployment' - params: { - location: location - vnetName: vnetName - agentSubnetName: agentSubnetName - peSubnetName: peSubnetName - mcpSubnetName: mcpSubnetName - vnetAddressPrefix: vnetAddressPrefix - agentSubnetPrefix: agentSubnetPrefix - peSubnetPrefix: peSubnetPrefix - mcpSubnetPrefix: mcpSubnetPrefix - } -} - -// Use existing VNet if requested -module existingVNet 'existing-vnet.bicep' = if (useExistingVnet) { - name: 'existing-vnet-deployment' - params: { - vnetName: vnetName - vnetResourceGroupName: existingVnetResourceGroupName - vnetSubscriptionId: existingVnetSubscriptionId - agentSubnetName: agentSubnetName - peSubnetName: peSubnetName - mcpSubnetName: mcpSubnetName - agentSubnetPrefix: agentSubnetPrefix - peSubnetPrefix: peSubnetPrefix - mcpSubnetPrefix: mcpSubnetPrefix - } -} - -// Provide unified outputs regardless of which module was used -output virtualNetworkName string = useExistingVnet - ? existingVNet.outputs.virtualNetworkName - : newVNet.outputs.virtualNetworkName -output virtualNetworkId string = useExistingVnet - ? existingVNet.outputs.virtualNetworkId - : newVNet.outputs.virtualNetworkId -output virtualNetworkSubscriptionId string = useExistingVnet - ? existingVNet.outputs.virtualNetworkSubscriptionId - : newVNet.outputs.virtualNetworkSubscriptionId -output virtualNetworkResourceGroup string = useExistingVnet - ? existingVNet.outputs.virtualNetworkResourceGroup - : newVNet.outputs.virtualNetworkResourceGroup -output agentSubnetName string = agentSubnetName -output peSubnetName string = peSubnetName -output mcpSubnetName string = mcpSubnetName -output agentSubnetId string = useExistingVnet ? existingVNet.outputs.agentSubnetId : newVNet.outputs.agentSubnetId -output peSubnetId string = useExistingVnet ? existingVNet.outputs.peSubnetId : newVNet.outputs.peSubnetId -output mcpSubnetId string = useExistingVnet ? existingVNet.outputs.mcpSubnetId : newVNet.outputs.mcpSubnetId diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/private-endpoint-and-dns.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/private-endpoint-and-dns.bicep deleted file mode 100644 index 96387c417..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/private-endpoint-and-dns.bicep +++ /dev/null @@ -1,478 +0,0 @@ -/* -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: - - 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 -@description('Name of the AI Foundry account') -param aiAccountName string -@description('Name of the AI Search service') -param aiSearchName string -@description('Name of the storage account') -param storageName string -@description('Name of the Cosmos DB account') -param cosmosDBName string -@description('The Microsoft Fabric Workspace full ARM Resource ID. Optional - leave empty to skip Fabric private endpoint.') -param fabricWorkspaceResourceId string = '' -@description('Name of the Vnet') -param vnetName string -@description('Name of the Customer subnet') -param peSubnetName string -@description('Suffix for unique resource names') -param suffix string - -@description('Resource Group name for existing Virtual Network (if different from current resource group)') -param vnetResourceGroupName string = resourceGroup().name - -@description('Subscription ID for Virtual Network') -param vnetSubscriptionId string = subscription().subscriptionId - -@description('Resource Group name for Storage Account') -param storageAccountResourceGroupName string = resourceGroup().name - -@description('Subscription ID for Storage account') -param storageAccountSubscriptionId string = subscription().subscriptionId - -@description('Subscription ID for AI Search service') -param aiSearchSubscriptionId string = subscription().subscriptionId - -@description('Resource Group name for AI Search service') -param aiSearchResourceGroupName string = resourceGroup().name - -@description('Subscription ID for Cosmos DB account') -param cosmosDBSubscriptionId string = subscription().subscriptionId - -@description('Resource group name for Cosmos DB account') -param cosmosDBResourceGroupName string = resourceGroup().name - -@description('Map of DNS zone FQDNs to resource group names. If provided, reference existing DNS zones in this resource group instead of creating them.') -param existingDnsZones object = { - 'privatelink.services.ai.azure.com': '' - 'privatelink.openai.azure.com': '' - 'privatelink.cognitiveservices.azure.com': '' - 'privatelink.search.windows.net': '' - 'privatelink.blob.${environment().suffixes.storage}': '' - 'privatelink.documents.azure.com': '' - 'privatelink.fabric.microsoft.com': '' -} - -// ---- Resource references ---- -resource aiAccount 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { - name: aiAccountName - scope: resourceGroup() -} - -resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = { - name: aiSearchName - scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName) -} - -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { - name: storageName - scope: resourceGroup(storageAccountSubscriptionId, storageAccountResourceGroupName) -} - -resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = { - name: cosmosDBName - scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) -} - -// ---- Fabric resource reference (conditional) ---- -var fabricPassedIn = fabricWorkspaceResourceId != '' -var fabricParts = split(fabricWorkspaceResourceId, '/') -var fabricWorkspaceName = fabricPassedIn ? last(fabricParts) : '' - -// Reference existing network resources -resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { - name: vnetName - scope: resourceGroup(vnetSubscriptionId, vnetResourceGroupName) -} -resource peSubnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' existing = { - parent: vnet - name: peSubnetName -} - -/* -------------------------------------------- AI Foundry Account Private Endpoint -------------------------------------------- */ - -// Private endpoint for AI Services account -// - Creates network interface in customer hub subnet -// - Establishes private connection to AI Services account -resource aiAccountPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { - name: '${aiAccountName}-private-endpoint' - location: resourceGroup().location - properties: { - subnet: { id: peSubnet.id } // Deploy in customer hub subnet - privateLinkServiceConnections: [ - { - name: '${aiAccountName}-private-link-service-connection' - properties: { - privateLinkServiceId: aiAccount.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: peSubnet.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: peSubnet.id } // Deploy in customer hub subnet - privateLinkServiceConnections: [ - { - name: '${storageName}-private-link-service-connection' - properties: { - privateLinkServiceId: storageAccount.id // Target blob storage - groupIds: ['blob'] - } - } - ] - } -} - -/*--------------------------------------------- Cosmos DB Private Endpoint -------------------------------------*/ - -resource cosmosDBPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { - name: '${cosmosDBName}-private-endpoint' - location: resourceGroup().location - properties: { - subnet: { id: peSubnet.id } // Deploy in customer hub subnet - privateLinkServiceConnections: [ - { - name: '${cosmosDBName}-private-link-service-connection' - properties: { - privateLinkServiceId: cosmosDBAccount.id // Target Cosmos DB account - groupIds: ['Sql'] - } - } - ] - } -} - -/*--------------------------------------------- Microsoft Fabric Private Endpoint -------------------------------------*/ - -// Private endpoint for Microsoft Fabric Workspace -// - Creates network interface in customer private endpoint subnet -// - Establishes private connection to Fabric workspace -// - Only created if fabricWorkspaceResourceId is provided -resource fabricPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = if (fabricPassedIn) { - name: '${fabricWorkspaceName}-fabric-private-endpoint' - location: resourceGroup().location - properties: { - subnet: { id: peSubnet.id } // Deploy in customer private endpoint subnet - privateLinkServiceConnections: [ - { - name: '${fabricWorkspaceName}-private-link-service-connection' - properties: { - privateLinkServiceId: fabricWorkspaceResourceId // Target Fabric workspace - groupIds: ['Fabric'] // Fabric private link group - } - } - ] - } -} - -/* -------------------------------------------- Private DNS Zones -------------------------------------------- */ - -// Format: 1) Private DNS Zone -// 2) Link Private DNS Zone to VNet -// 3) Create DNS Zone Group for Private Endpoint - -// Private DNS Zone for AI Services (Account) -// 1) Enables custom DNS resolution for AI Services private endpoint - -var aiServicesDnsZoneName = 'privatelink.services.ai.azure.com' -var openAiDnsZoneName = 'privatelink.openai.azure.com' -var cognitiveServicesDnsZoneName = 'privatelink.cognitiveservices.azure.com' -var aiSearchDnsZoneName = 'privatelink.search.windows.net' -var storageDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}' -var cosmosDBDnsZoneName = 'privatelink.documents.azure.com' -var fabricDnsZoneName = 'privatelink.fabric.microsoft.com' - -// ---- DNS Zone Resource Group lookups ---- -var aiServicesDnsZoneRG = existingDnsZones[aiServicesDnsZoneName] -var openAiDnsZoneRG = existingDnsZones[openAiDnsZoneName] -var cognitiveServicesDnsZoneRG = existingDnsZones[cognitiveServicesDnsZoneName] -var aiSearchDnsZoneRG = existingDnsZones[aiSearchDnsZoneName] -var storageDnsZoneRG = existingDnsZones[storageDnsZoneName] -var cosmosDBDnsZoneRG = existingDnsZones[cosmosDBDnsZoneName] -var fabricDnsZoneRG = existingDnsZones.?fabricDnsZoneName ?? '' - -// ---- DNS Zone Resources and References ---- -resource aiServicesPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(aiServicesDnsZoneRG)) { - name: aiServicesDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingAiServicesPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(aiServicesDnsZoneRG)) { - name: aiServicesDnsZoneName - scope: resourceGroup(aiServicesDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var aiServicesDnsZoneId = empty(aiServicesDnsZoneRG) ? aiServicesPrivateDnsZone.id : existingAiServicesPrivateDnsZone.id - -resource openAiPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(openAiDnsZoneRG)) { - name: openAiDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingOpenAiPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(openAiDnsZoneRG)) { - name: openAiDnsZoneName - scope: resourceGroup(openAiDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var openAiDnsZoneId = empty(openAiDnsZoneRG) ? openAiPrivateDnsZone.id : existingOpenAiPrivateDnsZone.id - -resource cognitiveServicesPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(cognitiveServicesDnsZoneRG)) { - name: cognitiveServicesDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingCognitiveServicesPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(cognitiveServicesDnsZoneRG)) { - name: cognitiveServicesDnsZoneName - scope: resourceGroup(cognitiveServicesDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var cognitiveServicesDnsZoneId = empty(cognitiveServicesDnsZoneRG) - ? cognitiveServicesPrivateDnsZone.id - : existingCognitiveServicesPrivateDnsZone.id - -resource aiSearchPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(aiSearchDnsZoneRG)) { - name: aiSearchDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingAiSearchPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(aiSearchDnsZoneRG)) { - name: aiSearchDnsZoneName - scope: resourceGroup(aiSearchDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var aiSearchDnsZoneId = empty(aiSearchDnsZoneRG) ? aiSearchPrivateDnsZone.id : existingAiSearchPrivateDnsZone.id - -resource storagePrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(storageDnsZoneRG)) { - name: storageDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingStoragePrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(storageDnsZoneRG)) { - name: storageDnsZoneName - scope: resourceGroup(storageDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var storageDnsZoneId = empty(storageDnsZoneRG) ? storagePrivateDnsZone.id : existingStoragePrivateDnsZone.id - -resource cosmosDBPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(cosmosDBDnsZoneRG)) { - name: cosmosDBDnsZoneName - location: 'global' -} - -// Reference existing private DNS zone if provided -resource existingCosmosDBPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(cosmosDBDnsZoneRG)) { - name: cosmosDBDnsZoneName - scope: resourceGroup(cosmosDBDnsZoneRG) -} -//creating condition if user pass existing dns zones or not -var cosmosDBDnsZoneId = empty(cosmosDBDnsZoneRG) ? cosmosDBPrivateDnsZone.id : existingCosmosDBPrivateDnsZone.id - -// Microsoft Fabric Private DNS Zone - only created if Fabric workspace is provided -resource fabricPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (fabricPassedIn && empty(fabricDnsZoneRG)) { - name: fabricDnsZoneName - location: 'global' -} - -// Reference existing Fabric private DNS zone if provided -resource existingFabricPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (fabricPassedIn && !empty(fabricDnsZoneRG)) { - name: fabricDnsZoneName - scope: resourceGroup(fabricDnsZoneRG) -} -// Fabric DNS Zone ID - conditional based on whether Fabric is configured -var fabricDnsZoneId = fabricPassedIn - ? (empty(fabricDnsZoneRG) ? fabricPrivateDnsZone.id : existingFabricPrivateDnsZone.id) - : '' - -// ---- DNS VNet Links ---- -resource aiServicesLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(aiServicesDnsZoneRG)) { - parent: aiServicesPrivateDnsZone - location: 'global' - name: 'aiServices-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} -resource openAiLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(openAiDnsZoneRG)) { - parent: openAiPrivateDnsZone - location: 'global' - name: 'aiServicesOpenAI-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} -resource cognitiveServicesLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(cognitiveServicesDnsZoneRG)) { - parent: cognitiveServicesPrivateDnsZone - location: 'global' - name: 'aiServicesCognitiveServices-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} -resource aiSearchLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(aiSearchDnsZoneRG)) { - parent: aiSearchPrivateDnsZone - location: 'global' - name: 'aiSearch-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} -resource storageLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(storageDnsZoneRG)) { - parent: storagePrivateDnsZone - location: 'global' - name: 'storage-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} -resource cosmosDBLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(cosmosDBDnsZoneRG)) { - parent: cosmosDBPrivateDnsZone - location: 'global' - name: 'cosmosDB-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} - -// Fabric VNet Link - only created if Fabric workspace is provided -resource fabricLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (fabricPassedIn && empty(fabricDnsZoneRG)) { - parent: fabricPrivateDnsZone - location: 'global' - name: 'fabric-${suffix}-link' - properties: { - virtualNetwork: { id: vnet.id } - registrationEnabled: false - } -} - -// ---- DNS Zone Groups ---- -resource aiServicesDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { - parent: aiAccountPrivateEndpoint - name: '${aiAccountName}-dns-group' - properties: { - privateDnsZoneConfigs: [ - { name: '${aiAccountName}-dns-aiserv-config', properties: { privateDnsZoneId: aiServicesDnsZoneId } } - { name: '${aiAccountName}-dns-openai-config', properties: { privateDnsZoneId: openAiDnsZoneId } } - { name: '${aiAccountName}-dns-cogserv-config', properties: { privateDnsZoneId: cognitiveServicesDnsZoneId } } - ] - } - dependsOn: [ - empty(aiServicesDnsZoneRG) ? aiServicesLink : null - empty(openAiDnsZoneRG) ? openAiLink : null - empty(cognitiveServicesDnsZoneRG) ? cognitiveServicesLink : null - ] -} -resource aiSearchDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { - parent: aiSearchPrivateEndpoint - name: '${aiSearchName}-dns-group' - properties: { - privateDnsZoneConfigs: [ - { name: '${aiSearchName}-dns-config', properties: { privateDnsZoneId: aiSearchDnsZoneId } } - ] - } - dependsOn: [ - empty(aiSearchDnsZoneRG) ? aiSearchLink : null - ] -} -resource storageDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { - parent: storagePrivateEndpoint - name: '${storageName}-dns-group' - properties: { - privateDnsZoneConfigs: [ - { name: '${storageName}-dns-config', properties: { privateDnsZoneId: storageDnsZoneId } } - ] - } - dependsOn: [ - empty(storageDnsZoneRG) ? storageLink : null - ] -} -resource cosmosDBDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { - parent: cosmosDBPrivateEndpoint - name: '${cosmosDBName}-dns-group' - properties: { - privateDnsZoneConfigs: [ - { name: '${cosmosDBName}-dns-config', properties: { privateDnsZoneId: cosmosDBDnsZoneId } } - ] - } - dependsOn: [ - empty(cosmosDBDnsZoneRG) ? cosmosDBLink : null - ] -} - -// Fabric DNS Zone Group - only created if Fabric workspace is provided -resource fabricDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = if (fabricPassedIn) { - parent: fabricPrivateEndpoint - name: '${fabricWorkspaceName}-dns-group' - properties: { - privateDnsZoneConfigs: [ - { name: '${fabricWorkspaceName}-dns-config', properties: { privateDnsZoneId: fabricDnsZoneId } } - ] - } - dependsOn: [ - (fabricPassedIn && empty(fabricDnsZoneRG)) ? fabricLink : null - ] -} diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/standard-dependent-resources.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/standard-dependent-resources.bicep deleted file mode 100644 index c4c9fb657..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/standard-dependent-resources.bicep +++ /dev/null @@ -1,148 +0,0 @@ -// Creates Azure dependent resources for Azure AI Agent Service standard agent setup - -@description('Azure region of the deployment') -param location 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 azureStorageName string - -@description('Name of the new Cosmos DB account') -param cosmosDBName 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 aiSearchResourceId 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 azureStorageAccountResourceId string - -@description('The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') -param cosmosDBResourceId string - -// param aiServiceExists bool -param aiSearchExists bool -param azureStorageExists bool -param cosmosDBExists bool - -var cosmosParts = split(cosmosDBResourceId, '/') - -resource existingCosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = if (cosmosDBExists) { - name: cosmosParts[8] - scope: resourceGroup(cosmosParts[2], cosmosParts[4]) -} - -// CosmosDB creation - -var canaryRegions = ['eastus2euap', 'centraluseuap'] -var cosmosDbRegion = contains(canaryRegions, location) ? 'westus' : location -resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = if(!cosmosDBExists) { - name: cosmosDBName - location: cosmosDbRegion - kind: 'GlobalDocumentDB' - properties: { - consistencyPolicy: { - defaultConsistencyLevel: 'Session' - } - disableLocalAuth: true - enableAutomaticFailover: false - enableMultipleWriteLocations: false - publicNetworkAccess: 'Disabled' - enableFreeTier: false - locations: [ - { - locationName: location - failoverPriority: 0 - isZoneRedundant: false - } - ] - databaseAccountOfferType: 'Standard' - } -} - -var acsParts = split(aiSearchResourceId, '/') - -resource existingSearchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = if (aiSearchExists) { - name: acsParts[8] - scope: resourceGroup(acsParts[2], acsParts[4]) -} - -// AI Search creation - -resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = if(!aiSearchExists) { - name: aiSearchName - location: location - identity: { - type: 'SystemAssigned' - } - properties: { - disableLocalAuth: false - authOptions: { aadOrApiKey: { aadAuthFailureMode: 'http401WithBearerChallenge'}} - encryptionWithCmk: { - enforcement: 'Unspecified' - } - hostingMode: 'default' - partitionCount: 1 - publicNetworkAccess: 'disabled' - replicaCount: 1 - semanticSearch: 'disabled' - networkRuleSet: { - bypass: 'None' - ipRules: [] - } - } - sku: { - name: 'standard' - } -} - -var azureStorageParts = split(azureStorageAccountResourceId, '/') - -resource existingAzureStorageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = if (azureStorageExists) { - name: azureStorageParts[8] - scope: resourceGroup(azureStorageParts[2], azureStorageParts[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' } - -// Storage creation - -resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = if(!azureStorageExists) { - name: azureStorageName - location: location - kind: 'StorageV2' - sku: sku - properties: { - minimumTlsVersion: 'TLS1_2' - allowBlobPublicAccess: false - publicNetworkAccess: 'Disabled' - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - virtualNetworkRules: [] - } - allowSharedKeyAccess: false - } -} - -output aiSearchName string = aiSearchExists ? existingSearchService.name : aiSearch.name -output aiSearchID string = aiSearchExists ? existingSearchService.id : aiSearch.id -output aiSearchServiceResourceGroupName string = aiSearchExists ? acsParts[4] : resourceGroup().name -output aiSearchServiceSubscriptionId string = aiSearchExists ? acsParts[2] : subscription().subscriptionId - -output azureStorageName string = azureStorageExists ? existingAzureStorageAccount.name : storage.name -output azureStorageId string = azureStorageExists ? existingAzureStorageAccount.id : storage.id -output azureStorageResourceGroupName string = azureStorageExists ? azureStorageParts[4] : resourceGroup().name -output azureStorageSubscriptionId string = azureStorageExists ? azureStorageParts[2] : subscription().subscriptionId - -output cosmosDBName string = cosmosDBExists ? existingCosmosDB.name : cosmosDB.name -output cosmosDBId string = cosmosDBExists ? existingCosmosDB.id : cosmosDB.id -output cosmosDBResourceGroupName string = cosmosDBExists ? cosmosParts[4] : resourceGroup().name -output cosmosDBSubscriptionId string = cosmosDBExists ? cosmosParts[2] : subscription().subscriptionId -// output keyvaultId string = keyVault.id diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/subnet.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/subnet.bicep deleted file mode 100644 index bf81553d8..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/subnet.bicep +++ /dev/null @@ -1,22 +0,0 @@ -@description('Name of the virtual network') -param vnetName string - -@description('Name of the subnet') -param subnetName string - -@description('Address prefix for the subnet') -param addressPrefix string - -@description('Array of subnet delegations') -param delegations array = [] - -resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' = { - name: '${vnetName}/${subnetName}' - properties: { - addressPrefix: addressPrefix - delegations: delegations - } -} - -output subnetId string = subnet.id -output subnetName string = subnetName diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/validate-existing-resources.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/validate-existing-resources.bicep deleted file mode 100644 index f798e5e27..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/validate-existing-resources.bicep +++ /dev/null @@ -1,91 +0,0 @@ -// @description('Resource ID of the AI Service Account. ') -// param aiServiceAccountResourceId string - -@description('Resource ID of the AI Search Service.') -param aiSearchResourceId string - -@description('Resource ID of the Azure Storage Account.') -param azureStorageAccountResourceId string - -@description('ResourceId of Cosmos DB Account') -param azureCosmosDBAccountResourceId string - -// Check if existing resources have been passed in -var storagePassedIn = azureStorageAccountResourceId != '' -var searchPassedIn = aiSearchResourceId != '' -var cosmosPassedIn = azureCosmosDBAccountResourceId != '' - -var storageParts = split(azureStorageAccountResourceId, '/') -var azureStorageSubscriptionId = storagePassedIn && length(storageParts) > 2 ? storageParts[2] : subscription().subscriptionId -var azureStorageResourceGroupName = storagePassedIn && length(storageParts) > 4 ? storageParts[4] : resourceGroup().name - -var acsParts = split(aiSearchResourceId, '/') -var aiSearchServiceSubscriptionId = searchPassedIn && length(acsParts) > 2 ? acsParts[2] : subscription().subscriptionId -var aiSearchServiceResourceGroupName = searchPassedIn && length(acsParts) > 4 ? acsParts[4] : resourceGroup().name - -var cosmosParts = split(azureCosmosDBAccountResourceId, '/') -var cosmosDBSubscriptionId = cosmosPassedIn && length(cosmosParts) > 2 ? cosmosParts[2] : subscription().subscriptionId -var cosmosDBResourceGroupName = cosmosPassedIn && length(cosmosParts) > 4 ? cosmosParts[4] : resourceGroup().name - -// Validate AI Search -resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' existing = if (searchPassedIn) { - name: last(split(aiSearchResourceId, '/')) - scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) -} - -// Validate Cosmos DB Account -resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = if (cosmosPassedIn) { - name: last(split(azureCosmosDBAccountResourceId, '/')) - scope: resourceGroup(cosmosDBSubscriptionId,cosmosDBResourceGroupName) -} - -// Validate Storage Account -resource azureStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = if (storagePassedIn) { - name: last(split(azureStorageAccountResourceId, '/')) - scope: resourceGroup(azureStorageSubscriptionId,azureStorageResourceGroupName) -} - -// output aiServiceExists bool = aiServicesPassedIn && (aiServiceAccount.name == aiServiceParts[8]) -output aiSearchExists bool = searchPassedIn && (aiSearch.name == acsParts[8]) -output cosmosDBExists bool = cosmosPassedIn && (cosmosDBAccount.name == cosmosParts[8]) -output azureStorageExists bool = storagePassedIn && (azureStorageAccount.name == storageParts[8]) - -output aiSearchServiceSubscriptionId string = aiSearchServiceSubscriptionId -output aiSearchServiceResourceGroupName string = aiSearchServiceResourceGroupName - -output cosmosDBSubscriptionId string = cosmosDBSubscriptionId -output cosmosDBResourceGroupName string = cosmosDBResourceGroupName - -output azureStorageSubscriptionId string = azureStorageSubscriptionId -output azureStorageResourceGroupName string = azureStorageResourceGroupName - -// Adding DNS Zone Check - -@description('Object mapping DNS zone names to their resource group, or empty string to indicate creation') -param existingDnsZones object - -@description('List of private DNS zone names to validate') -param dnsZoneNames array - -var dnsZoneTypes = [ - 'Microsoft.Network/privateDnsZones' -] - -// Output whether each DNS zone exists -output dnsZoneExists array = [ - for zoneName in dnsZoneNames: { - name: zoneName - exists: !empty(existingDnsZones[zoneName]) - } -] - -/* -// Helper function to check existence -function resourceExists(resourceType: string, name: string, rg: string): bool { - // Use the existing resource reference to check - var res = existing resource dnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { - name: name - scope: resourceGroup(rg) - } - return !empty(res.id) -}*/ diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/vnet.bicep b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/vnet.bicep deleted file mode 100644 index 8d013eb3c..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/modules-network-secured/vnet.bicep +++ /dev/null @@ -1,107 +0,0 @@ -/* -Virtual Network Module -This module deploys the core network infrastructure with security controls: - -1. Address Space: - - VNet CIDR: 172.16.0.0/16 OR 192.168.0.0/16 - - Agents Subnet: 172.16.0.0/24 OR 192.168.0.0/24 (reserved for Azure AI Foundry) - - Private Endpoint Subnet: 172.16.1.0/24 OR 192.168.1.0/24 - - MCP Subnet: 172.16.2.0/24 OR 192.168.2.0/24 (for user Container Apps) - -2. Security Features: - - Network isolation - - Subnet delegation - - Private endpoint subnet -*/ - -@description('Azure region for the deployment') -param location string - -@description('The name of the virtual network') -param vnetName string = 'agents-vnet-test' - -@description('The name of Agents Subnet') -param agentSubnetName string = 'agent-subnet' - -@description('The name of Hub subnet') -param peSubnetName string = 'pe-subnet' - -@description('The name of MCP subnet for user-deployed Container Apps') -param mcpSubnetName string = 'mcp-subnet' - -@description('Address space for the VNet') -param vnetAddressPrefix string = '' - -@description('Address prefix for the agent subnet') -param agentSubnetPrefix string = '' - -@description('Address prefix for the private endpoint subnet') -param peSubnetPrefix string = '' - -@description('Address prefix for the MCP subnet') -param mcpSubnetPrefix string = '' - -var defaultVnetAddressPrefix = '192.168.0.0/16' -var vnetAddress = empty(vnetAddressPrefix) ? defaultVnetAddressPrefix : vnetAddressPrefix -var agentSubnet = empty(agentSubnetPrefix) ? cidrSubnet(vnetAddress, 24, 0) : agentSubnetPrefix -var peSubnet = empty(peSubnetPrefix) ? cidrSubnet(vnetAddress, 24, 1) : peSubnetPrefix -var mcpSubnet = empty(mcpSubnetPrefix) ? cidrSubnet(vnetAddress, 24, 2) : mcpSubnetPrefix - -resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { - name: vnetName - location: location - properties: { - addressSpace: { - addressPrefixes: [ - vnetAddress - ] - } - subnets: [ - { - name: agentSubnetName - properties: { - addressPrefix: agentSubnet - delegations: [ - { - name: 'Microsoft.app/environments' - properties: { - serviceName: 'Microsoft.App/environments' - } - } - ] - } - } - { - name: peSubnetName - properties: { - addressPrefix: peSubnet - } - } - { - name: mcpSubnetName - properties: { - addressPrefix: mcpSubnet - delegations: [ - { - name: 'Microsoft.App/environments' - properties: { - serviceName: 'Microsoft.App/environments' - } - } - ] - } - } - ] - } -} -// Output variables -output peSubnetName string = peSubnetName -output agentSubnetName string = agentSubnetName -output mcpSubnetName string = mcpSubnetName -output agentSubnetId string = '${virtualNetwork.id}/subnets/${agentSubnetName}' -output peSubnetId string = '${virtualNetwork.id}/subnets/${peSubnetName}' -output mcpSubnetId string = '${virtualNetwork.id}/subnets/${mcpSubnetName}' -output virtualNetworkName string = virtualNetwork.name -output virtualNetworkId string = virtualNetwork.id -output virtualNetworkResourceGroup string = resourceGroup().name -output virtualNetworkSubscriptionId string = subscription().subscriptionId diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/TESTING-GUIDE.md b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/TESTING-GUIDE.md deleted file mode 100644 index 1209075c6..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/TESTING-GUIDE.md +++ /dev/null @@ -1,462 +0,0 @@ -# Hybrid Private Resources - Testing Guide - -This guide covers testing Azure AI Foundry agents with tools that access private resources (AI Search, MCP servers). By default, the Foundry (AI Services) resource has **public network access disabled**. You can optionally [switch to public access](#switching-the-foundry-resource-to-public-access) for easier development. - -> **Private Foundry (default):** You need a secure connection (VPN Gateway, ExpressRoute, or Azure Bastion) to reach the Foundry resource and run SDK tests. See [Connecting to a Private Foundry Resource](#connecting-to-a-private-foundry-resource). - ---- - -## Table of Contents - -1. [Prerequisites](#prerequisites) -2. [Connecting to a Private Foundry Resource](#connecting-to-a-private-foundry-resource) -3. [Switching the Foundry Resource to Public Access](#switching-the-foundry-resource-to-public-access) -4. [Step 1: Deploy the Template](#step-1-deploy-the-template) -5. [Step 2: Verify Private Endpoints](#step-2-verify-private-endpoints) -6. [Step 3: Create Test Data in AI Search](#step-3-create-test-data-in-ai-search) -7. [Step 4: Deploy MCP Server](#step-4-deploy-mcp-server) -8. [Step 5: Test via SDK](#step-5-test-via-sdk) -9. [Troubleshooting](#troubleshooting) -10. [Test Results Summary](#test-results-summary) - ---- - -## Prerequisites - -- Azure CLI installed and authenticated -- Owner or Contributor role on the subscription -- Python 3.10+ (for SDK testing) - ---- - -## Connecting to a Private Foundry Resource - -When the Foundry resource has public network access **disabled** (the default), you must connect to the Azure VNet before you can reach the Foundry endpoint for SDK testing or portal access. - -Azure provides three methods: - -| Method | Use Case | -|--------|----------| -| **Azure VPN Gateway** | Connect from your local machine/network over an encrypted tunnel | -| **Azure ExpressRoute** | Private, dedicated connection from on-premises infrastructure | -| **Azure Bastion** | Access a jump box VM on the VNet securely through the Azure portal | - -For step-by-step setup instructions, see: [Securely connect to Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/configure-private-link?view=foundry#securely-connect-to-foundry). - -Once connected to the VNet, all SDK commands and portal interactions in this guide will work as documented. - ---- - -## Switching the Foundry Resource to Public Access - -If your security policy permits, you can enable public network access on the Foundry resource so that SDK tests and portal access work directly from the internet without VPN/ExpressRoute/Bastion. - -In `modules-network-secured/ai-account-identity.bicep`, change: - -```bicep -// Change from: -publicNetworkAccess: 'Disabled' -// To: -publicNetworkAccess: 'Enabled' - -// Also change: -defaultAction: 'Deny' -// To: -defaultAction: 'Allow' -``` - -Then redeploy the template. Backend resources (AI Search, Cosmos DB, Storage) remain on private endpoints regardless of this setting. - -To revert to private, set `publicNetworkAccess: 'Disabled'` and `defaultAction: 'Deny'`, then redeploy. - ---- - -## Step 1: Deploy the Template - -```bash -# Set variables -RESOURCE_GROUP="rg-hybrid-agent-test" -LOCATION="westus2" - -# Create resource group -az group create --name $RESOURCE_GROUP --location $LOCATION - -# Deploy the template -az deployment group create \ - --resource-group $RESOURCE_GROUP \ - --template-file main.bicep \ - --parameters location=$LOCATION - -# Get the deployment outputs -AI_SERVICES_NAME=$(az cognitiveservices account list -g $RESOURCE_GROUP --query "[0].name" -o tsv) -echo "AI Services: $AI_SERVICES_NAME" -``` - ---- - -## Step 2: Verify Private Endpoints - -Confirm that backend resources have private endpoints: - -```bash -# List private endpoints -az network private-endpoint list -g $RESOURCE_GROUP -o table - -# Expected: Private endpoints for: -# - AI Search (*search-private-endpoint) -# - Cosmos DB (*cosmosdb-private-endpoint) -# - Storage (*storage-private-endpoint) -# - AI Services (*-private-endpoint) - -# If public access is ENABLED, verify AI Services is publicly accessible: -AI_ENDPOINT=$(az cognitiveservices account show -g $RESOURCE_GROUP -n $AI_SERVICES_NAME --query "properties.endpoint" -o tsv) -curl -I $AI_ENDPOINT -# Should return HTTP 200 (accessible from internet) - -# If public access is DISABLED (default), the curl above will fail. -# You must connect via VPN/ExpressRoute/Bastion to reach the endpoint. -# See: Connecting to a Private Foundry Resource -``` - ---- - -## Step 3: Create Test Data in AI Search - -Since AI Search has a private endpoint, you need to access it from within the VNet or temporarily allow public access. - -### Option A: Temporarily Enable Public Access on AI Search - -```bash -AI_SEARCH_NAME=$(az search service list -g $RESOURCE_GROUP --query "[0].name" -o tsv) - -# Temporarily enable public access -az search service update -g $RESOURCE_GROUP -n $AI_SEARCH_NAME \ - --public-network-access enabled - -# Get admin key -ADMIN_KEY=$(az search admin-key show -g $RESOURCE_GROUP --service-name $AI_SEARCH_NAME --query "primaryKey" -o tsv) - -# Create test index -curl -X POST "https://${AI_SEARCH_NAME}.search.windows.net/indexes?api-version=2023-11-01" \ - -H "Content-Type: application/json" \ - -H "api-key: ${ADMIN_KEY}" \ - -d '{ - "name": "test-index", - "fields": [ - {"name": "id", "type": "Edm.String", "key": true}, - {"name": "content", "type": "Edm.String", "searchable": true} - ] - }' - -# Add a test document -curl -X POST "https://${AI_SEARCH_NAME}.search.windows.net/indexes/test-index/docs/index?api-version=2023-11-01" \ - -H "Content-Type: application/json" \ - -H "api-key: ${ADMIN_KEY}" \ - -d '{ - "value": [ - {"@search.action": "upload", "id": "1", "content": "This is a test document for validating AI Search integration with Azure AI Foundry agents."} - ] - }' - -# Disable public access again -az search service update -g $RESOURCE_GROUP -n $AI_SEARCH_NAME \ - --public-network-access disabled -``` - ---- - -## Step 4: Deploy MCP Server - -Deploy an HTTP-based MCP server using the pre-built multi-auth MCP image. - -> **Important**: Azure AI Agents require MCP servers that implement the **Streamable HTTP transport** (JSON-RPC over HTTP with session management). The multi-auth MCP server provides this with a `/noauth/mcp` endpoint for testing. - -### 4.1 Import the Multi-Auth MCP Image - -```bash -# Create ACR if needed -ACR_NAME="mcpacr$(date +%s | tail -c 5)" -az acr create --name $ACR_NAME --resource-group $RESOURCE_GROUP --sku Basic --location $LOCATION - -# Import the pre-built multi-auth MCP image -az acr import \ - --name $ACR_NAME \ - --source retrievaltestacr.azurecr.io/multi-auth-mcp/api-multi-auth-mcp-env:latest \ - --image multi-auth-mcp:latest - -# Create user-assigned identity with AcrPull role -az identity create --name mcp-identity --resource-group $RESOURCE_GROUP --location $LOCATION -IDENTITY_ID=$(az identity show --name mcp-identity -g $RESOURCE_GROUP --query "id" -o tsv) -IDENTITY_PRINCIPAL=$(az identity show --name mcp-identity -g $RESOURCE_GROUP --query "principalId" -o tsv) -ACR_ID=$(az acr show --name $ACR_NAME --query "id" -o tsv) -az role assignment create --assignee $IDENTITY_PRINCIPAL --role AcrPull --scope $ACR_ID - -# Wait for role assignment to propagate -sleep 30 -``` - -### 4.2 Create Container Apps Environment - -```bash -VNET_NAME=$(az network vnet list -g $RESOURCE_GROUP --query "[0].name" -o tsv) -MCP_SUBNET_ID=$(az network vnet subnet show -g $RESOURCE_GROUP --vnet-name $VNET_NAME -n "mcp-subnet" --query "id" -o tsv) - -# Create internal Container Apps environment -az containerapp env create \ - --resource-group $RESOURCE_GROUP \ - --name "mcp-env" \ - --location $LOCATION \ - --infrastructure-subnet-resource-id $MCP_SUBNET_ID \ - --internal-only true -``` - -### 4.3 Deploy the MCP Server - -```bash -# Deploy container app with multi-auth MCP image -# Note: The image runs on port 8080 -az containerapp create \ - --resource-group $RESOURCE_GROUP \ - --name "mcp-http-server" \ - --environment "mcp-env" \ - --image "${ACR_NAME}.azurecr.io/multi-auth-mcp:latest" \ - --target-port 8080 \ - --ingress external \ - --min-replicas 1 \ - --user-assigned $IDENTITY_ID \ - --registry-server "${ACR_NAME}.azurecr.io" \ - --registry-identity $IDENTITY_ID - -# Get the MCP server URL -MCP_FQDN=$(az containerapp show -g $RESOURCE_GROUP -n "mcp-http-server" --query "properties.configuration.ingress.fqdn" -o tsv) -echo "MCP Server URL: https://${MCP_FQDN}/noauth/mcp" -``` - -### 4.4 Configure Private DNS - -```bash -MCP_STATIC_IP=$(az containerapp env show -g $RESOURCE_GROUP -n "mcp-env" --query "properties.staticIp" -o tsv) -DEFAULT_DOMAIN=$(az containerapp env show -g $RESOURCE_GROUP -n "mcp-env" --query "properties.defaultDomain" -o tsv) - -# Create private DNS zone -az network private-dns zone create -g $RESOURCE_GROUP -n $DEFAULT_DOMAIN - -# Link to VNet -VNET_ID=$(az network vnet show -g $RESOURCE_GROUP -n $VNET_NAME --query "id" -o tsv) -az network private-dns link vnet create \ - -g $RESOURCE_GROUP \ - -z $DEFAULT_DOMAIN \ - -n "containerapp-link" \ - -v $VNET_ID \ - --registration-enabled false - -# Add wildcard A record -az network private-dns record-set a add-record -g $RESOURCE_GROUP -z $DEFAULT_DOMAIN -n "*" -a $MCP_STATIC_IP -``` - -### 4.5 (Optional) Deploy Public MCP Server for Testing - -For easier testing without VNet constraints, you can also deploy a public MCP server: - -```bash -# Create public Container Apps environment -az containerapp env create \ - --resource-group $RESOURCE_GROUP \ - --name "mcp-env-public" \ - --location $LOCATION - -# Deploy public MCP server -az containerapp create \ - --resource-group $RESOURCE_GROUP \ - --name "mcp-http-server-public" \ - --environment "mcp-env-public" \ - --image "${ACR_NAME}.azurecr.io/multi-auth-mcp:latest" \ - --target-port 8080 \ - --ingress external \ - --min-replicas 1 \ - --user-assigned $IDENTITY_ID \ - --registry-server "${ACR_NAME}.azurecr.io" \ - --registry-identity $IDENTITY_ID - -# Get public MCP URL -PUBLIC_MCP_FQDN=$(az containerapp show -g $RESOURCE_GROUP -n "mcp-http-server-public" --query "properties.configuration.ingress.fqdn" -o tsv) -echo "Public MCP Server URL: https://${PUBLIC_MCP_FQDN}/noauth/mcp" -``` - ---- - -## Step 5: Test via SDK - -Two test scripts are provided: - -| Script | Description | -|--------|-------------| -| `test_agents_v2.py` | Full test suite: basic agent, AI Search, MCP tools | -| `test_mcp_tools_agents_v2.py` | Focused MCP testing: connectivity + public/private agent tests | - -### 5.1 Install Dependencies - -```bash -pip install azure-ai-projects azure-identity openai -``` - -### 5.2 Configure Environment - -```bash -# Set the project endpoint (get from Azure Portal -> AI Services -> Projects -> Properties) -export PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" - -# Optional: Override MCP server URLs -export MCP_SERVER_PUBLIC="https:///noauth/mcp" -export MCP_SERVER_PRIVATE="https:///noauth/mcp" -``` - -### 5.3 Run Full Test Suite - -```bash -# Run all tests (basic agent, AI Search, MCP) -python test_agents_v2.py - -# Run specific test -python test_agents_v2.py --test basic_agent -python test_agents_v2.py --test ai_search -python test_agents_v2.py --test mcp_tool -``` - -### 5.4 Run MCP-Focused Tests - -```bash -# Run all MCP tests (connectivity + public + private) -python test_mcp_tools_agents_v2.py - -# Test only public MCP server -python test_mcp_tools_agents_v2.py --test public - -# Test only private MCP server -python test_mcp_tools_agents_v2.py --test private - -# With retries (useful for transient Hyena cluster routing issues) -python test_mcp_tools_agents_v2.py --test public --retry 3 -``` - -### 5.5 Understanding Test Results - -**MCP Connectivity Test**: Direct HTTP test to verify the MCP server responds correctly: -- Sends `initialize` request and captures `mcp-session-id` header -- Sends `tools/list` to enumerate available tools -- Sends `tools/call` to execute the `add` tool - -**MCP Tool via Agent Test**: Tests the full agent workflow: -- Creates an agent with MCP tool configuration -- Sends a request that triggers the MCP tool -- Validates the agent can call MCP tools through the Data Proxy - -> **Known Issue**: Agent tests may fail ~50% of the time with `TaskCanceledException` due to Hyena cluster routing. The Data Proxy is only deployed on one of two scale units, and the load balancer routes in round-robin fashion. Use `--retry` to mitigate. - ---- - -## Troubleshooting - -### Agent Can't Access AI Search - -1. **Verify private endpoint exists**: - ```bash - az network private-endpoint list -g $RESOURCE_GROUP --query "[?contains(name,'search')]" - ``` - -2. **Check Data Proxy configuration**: - ```bash - az cognitiveservices account show -g $RESOURCE_GROUP -n $AI_SERVICES_NAME \ - --query "properties.networkInjections" - ``` - -3. **Verify AI Search connection in project**: - - Go to the portal → Project → Settings → Connections - - Confirm AI Search connection exists - -### MCP Tool Fails with TaskCanceledException - -This is a **known issue** with the Hyena cluster infrastructure: -- The Data Proxy is deployed on only **one of two scale units** -- The load balancer routes requests in **round-robin** fashion -- ~50% of requests hit the wrong scale unit and get `TaskCanceledException` - -**Workaround**: Use `--retry` flag when running tests: -```bash -python test_mcp_tools_agents_v2.py --test public --retry 3 -``` - -### MCP Tool Fails with 400 Bad Request - -Check the error message for details: -- **404 Not Found**: Verify the MCP server URL includes the correct path (`/noauth/mcp`) -- **DNS resolution**: Ensure private DNS zone is configured correctly for Container Apps - -### MCP Server Not Responding - -1. **Check container app health**: - ```bash - az containerapp show -g $RESOURCE_GROUP -n "mcp-http-server" --query "properties.runningStatus" - ``` - -2. **Check container logs**: - ```bash - az containerapp logs show -g $RESOURCE_GROUP -n "mcp-http-server" --tail 50 - ``` - -3. **Verify ingress port is 8080** (not 80): - ```bash - az containerapp ingress show -g $RESOURCE_GROUP -n "mcp-http-server" --query "targetPort" - ``` - -### Portal Shows "New Foundry Not Supported" - -This is expected when network injection is configured. Use SDK testing instead - it works perfectly with network injection. - ---- - -## Test Results Summary - -### Test Scripts - -| Script | Purpose | -|--------|---------| -| `test_agents_v2.py` | Full test suite: OpenAI API, basic agent, AI Search, MCP | -| `test_mcp_tools_agents_v2.py` | Focused MCP testing with retry support | - -### Validated ✅ - -| Test | Status | Notes | -|------|--------|-------| -| OpenAI Responses API (direct) | ✅ Pass | Works from anywhere | -| Basic Agent (no tools) | ✅ Pass | Works from anywhere | -| AI Search Tool | ✅ Pass | Data Proxy routes to private endpoint | -| MCP Connectivity (direct HTTP) | ✅ Pass | Server responds correctly | -| MCP Tool via Agent (public server) | ✅ Pass* | *~50% fail rate due to Hyena routing | - -### Known Limitations ⚠️ - -| Issue | Cause | Workaround | -|-------|-------|------------| -| ~50% TaskCanceledException | Hyena cluster has 2 scale units, Data Proxy only on 1 | Use `--retry` flag | -| Portal "New Foundry" blocked | Network injection not supported in portal | Use SDK testing | -| Private MCP via Data Proxy | DNS resolution issues for Container Apps | Use public MCP server | - -### Architecture Notes - -1. **AI Search Tool works** because it uses Azure Private Endpoints with built-in DNS integration (`privatelink.search.windows.net`). - -2. **MCP uses Streamable HTTP transport** - The multi-auth MCP server implements proper session management with `mcp-session-id` headers required by Azure's MCP client. - -3. **Container Apps require port 8080** - The multi-auth MCP image runs on port 8080, not 80. - -4. **Use `/noauth/mcp` endpoint** for testing without authentication. Production deployments should use `/mcp` with proper auth configuration. - ---- - -## Cleanup - -```bash -# Delete all resources -az group delete --name $RESOURCE_GROUP --yes --no-wait -``` diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_agents_v2.py b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_agents_v2.py deleted file mode 100644 index 711246355..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_agents_v2.py +++ /dev/null @@ -1,646 +0,0 @@ -#!/usr/bin/env python3 -""" -Hybrid Private Resources - Agents v2 Test Script - -This script tests that agents can use tools that connect to private resources -via the Data Proxy when AI Services has PUBLIC access enabled. - -Template 19: AI Services (public) → Data Proxy → Private Resources (VNet) - -Key tests: -1. Basic agent - validates public API access works using Responses API -2. AI Search tool - validates Data Proxy routes to private AI Search -3. MCP tool - validates Data Proxy routes to private MCP server - -This script can be run from ANYWHERE (no jump box required for API access). -However, MCP connectivity test requires access to the private VNet. - -Uses the new Agents v2 SDK pattern: -- AIProjectClient with context manager -- project_client.get_openai_client() for OpenAI-compatible API -- openai_client.responses.create() for the Responses API -- project_client.agents.create_version() with PromptAgentDefinition -- openai_client.conversations.create() for conversation threads -""" - -import os -import sys -import logging - -# ============================================================================ -# LOGGING CONFIGURATION - Enable HTTP request/response logging for debugging -# ============================================================================ -# Set to logging.DEBUG for full request/response bodies, INFO for headers only -LOG_LEVEL = logging.INFO - -# Configure basic logging format -logging.basicConfig( - level=LOG_LEVEL, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) - -# Azure SDK HTTP logging (captures request IDs, headers, URLs) -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(LOG_LEVEL) - -# OpenAI client uses httpx for HTTP requests -logging.getLogger("httpx").setLevel(LOG_LEVEL) - -# Optional: urllib3 for lower-level HTTP debugging -logging.getLogger("urllib3").setLevel(logging.WARNING) # Set to DEBUG for full details - -# Reduce noise from other loggers -logging.getLogger("azure.identity").setLevel(logging.WARNING) - -# ============================================================================ - -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import ( - MCPTool, - PromptAgentDefinition, - AzureAISearchAgentTool, - AzureAISearchToolResource, - AISearchIndexResource, - AzureAISearchQueryType, -) -from azure.identity import DefaultAzureCredential -from openai.types.responses import ResponseInputParam -from openai.types.responses.response_input_param import McpApprovalResponse - -# ============================================================================ -# CONFIGURATION - Update these values for your deployment -# ============================================================================ -# NOTE: Use the project-scoped endpoint from Azure Portal: -# AI Services resource -> Projects -> -> Properties -> "AI Foundry API" endpoint -PROJECT_ENDPOINT = os.environ.get( - "PROJECT_ENDPOINT", - "https://aiservicesaxy3.services.ai.azure.com/api/projects/projectaxy3" -) -MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini") - -# AI Search configuration -# AI_SEARCH_CONNECTION_NAME = os.environ.get("AI_SEARCH_CONNECTION_NAME", "") -AI_SEARCH_CONNECTION_NAME = "aiservicesaxy3search" -AI_SEARCH_INDEX_NAME = os.environ.get("AI_SEARCH_INDEX_NAME", "test-index") - -# MCP Server configuration - Using multi-auth MCP image deployed to your Container Apps -# Private (internal to VNet): mcp-http-server.jollydune-20a0f709.westus2.azurecontainerapps.io -# Public (external): mcp-http-server-public.victoriousfield-89c08f4e.westus2.azurecontainerapps.io -MCP_SERVER_URL = os.environ.get( - "MCP_SERVER_URL", - "https://mcp-http-server-public.victoriousfield-89c08f4e.westus2.azurecontainerapps.io/noauth/mcp" -) - -# ============================================================================ - - -def log_response_info(response, label="Response"): - """Extract and log useful debugging info from OpenAI response objects.""" - logger = logging.getLogger(__name__) - try: - # Try to get request ID from response - if hasattr(response, '_request_id'): - logger.info(f"{label} - Request ID: {response._request_id}") - if hasattr(response, 'id'): - logger.info(f"{label} - Response ID: {response.id}") - # For openai responses, the request_id is often in headers - if hasattr(response, '_response') and hasattr(response._response, 'headers'): - headers = response._response.headers - if 'x-request-id' in headers: - logger.info(f"{label} - x-request-id: {headers['x-request-id']}") - if 'x-ms-request-id' in headers: - logger.info(f"{label} - x-ms-request-id: {headers['x-ms-request-id']}") - except Exception as e: - logger.debug(f"Could not extract response info: {e}") - - -def log_exception_info(exception, label="Exception"): - """Extract and log request info from OpenAI exceptions for debugging failed requests.""" - logger = logging.getLogger(__name__) - try: - # OpenAI exceptions have a response attribute with the HTTP response - if hasattr(exception, 'response') and exception.response is not None: - resp = exception.response - headers = resp.headers if hasattr(resp, 'headers') else {} - - # Log common request identifiers - request_id = headers.get('x-request-id', 'N/A') - ms_request_id = headers.get('x-ms-request-id', 'N/A') - - logger.error(f"{label} - x-request-id: {request_id}") - logger.error(f"{label} - x-ms-request-id: {ms_request_id}") - - # Also print to console for visibility - print(f" 📋 Request ID (x-request-id): {request_id}") - print(f" 📋 MS Request ID (x-ms-request-id): {ms_request_id}") - - # Log status code - if hasattr(resp, 'status_code'): - logger.error(f"{label} - HTTP Status: {resp.status_code}") - - # Also try to get request_id attribute directly - if hasattr(exception, 'request_id'): - logger.error(f"{label} - request_id attribute: {exception.request_id}") - print(f" 📋 Request ID: {exception.request_id}") - - except Exception as e: - logger.debug(f"Could not extract exception info: {e}") - - -def test_mcp_server_connectivity(): - """Test MCP server with full session workflow: initialize → list tools → call tool.""" - print("\n" + "=" * 60) - print("TEST 1: MCP Server Connectivity (Full Session Flow)") - print("=" * 60) - - import urllib.request - import ssl - import json - - try: - # Create SSL context - ctx = ssl.create_default_context() - - print(f" Target MCP Server: {MCP_SERVER_URL}") - - # ===================================================================== - # Step 1: Initialize - Get mcp-session-id - # ===================================================================== - print("\n--- Step 1: Initialize (get mcp-session-id) ---") - - init_data = json.dumps({ - "method": "initialize", - "params": { - "protocolVersion": "2025-11-25", - "capabilities": { - "sampling": {}, - "elicitation": {}, - "roots": { - "listChanged": True - } - }, - "clientInfo": { - "name": "test-mcp-client", - "version": "1.0.0" - } - }, - "jsonrpc": "2.0", - "id": 0 - }).encode('utf-8') - - init_req = urllib.request.Request( - MCP_SERVER_URL, - data=init_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream" - }, - method="POST" - ) - - with urllib.request.urlopen(init_req, timeout=10, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - mcp_session_id = response.getheader('mcp-session-id') - - print(f" ✓ HTTP Status: {status}") - print(f" ✓ Response: {body[:300]}...") - - if mcp_session_id: - print(f" ✓ MCP Session ID: {mcp_session_id}") - else: - print(" ✗ No mcp-session-id header in response!") - print("\n✗ TEST FAILED: MCP server did not return session ID") - return False - - # ===================================================================== - # Step 2: List Tools - Using mcp-session-id - # ===================================================================== - print("\n--- Step 2: List Tools (using session ID) ---") - - list_data = json.dumps({ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/list", - "params": {} - }).encode('utf-8') - - list_req = urllib.request.Request( - MCP_SERVER_URL, - data=list_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", - "mcp-session-id": mcp_session_id - }, - method="POST" - ) - - with urllib.request.urlopen(list_req, timeout=10, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - result = json.loads(body) - - print(f" ✓ HTTP Status: {status}") - - if "result" in result and "tools" in result["result"]: - tools = result["result"]["tools"] - print(f" ✓ Found {len(tools)} tools:") - for tool in tools: - print(f" - {tool.get('name', 'unknown')}: {tool.get('description', '')[:50]}") - else: - print(f" ✓ Response: {body[:300]}...") - - # ===================================================================== - # Step 3: Call Tool - Using mcp-session-id - # ===================================================================== - print("\n--- Step 3: Call Tool 'add' (using session ID) ---") - - call_data = json.dumps({ - "jsonrpc": "2.0", - "id": 2, - "method": "tools/call", - "params": { - "name": "add", - "arguments": { - "a": 2, - "b": 4 - } - } - }).encode('utf-8') - - call_req = urllib.request.Request( - MCP_SERVER_URL, - data=call_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", - "mcp-session-id": mcp_session_id - }, - method="POST" - ) - - with urllib.request.urlopen(call_req, timeout=10, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - result = json.loads(body) - - print(f" ✓ HTTP Status: {status}") - print(f" ✓ Response: {body}") - - # Check if we got the expected result (2 + 4 = 6) - if "result" in result: - print(f" ✓ Tool call successful!") - else: - print(f" ⚠ Unexpected response format") - - print("\n" + "=" * 60) - print("✓ TEST PASSED: MCP server session flow working correctly") - print("=" * 60) - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: MCP server error: {str(e)}") - import traceback - traceback.print_exc() - print(" Note: This test requires network access to the MCP server.") - return False - - -def test_basic_agent(): - """Test basic agent creation and execution using Responses API.""" - print("\n" + "=" * 60) - print("TEST 2: Basic Agent Creation and Execution (Responses API)") - print("=" * 60) - - agent = None - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Create a simple agent without tools - agent = project_client.agents.create_version( - agent_name="basic-test-agent", - definition=PromptAgentDefinition( - model=MODEL_NAME, - instructions="You are a helpful assistant. Answer briefly and concisely.", - ), - ) - print(f"✓ Created agent (id: {agent.id}, name: {agent.name}, version: {agent.version})") - - # Create a conversation thread - conversation = openai_client.conversations.create() - print(f"✓ Created conversation: {conversation.id}") - - # Send a request using the Responses API - response = openai_client.responses.create( - conversation=conversation.id, - input="Say hello and confirm you are working. Keep it brief.", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - log_response_info(response, "Basic Agent Response") - - print(f"\n✓ Agent response: {response.output_text}") - print("\n✓ TEST PASSED: Basic agent works with Responses API") - - # Cleanup - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - print(f" Cleaned up agent: {agent.name}") - - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - log_exception_info(e, "Basic Agent Error") - import traceback - traceback.print_exc() - return False - - -def test_ai_search_tool(): - """Test that an agent can use AI Search tool to query private AI Search.""" - print("\n" + "=" * 60) - print("TEST 3: AI Search Tool → Private AI Search") - print("=" * 60) - - if not AI_SEARCH_CONNECTION_NAME: - print(" ⚠ AI_SEARCH_CONNECTION_NAME not set, skipping this test") - print(" Set it with: export AI_SEARCH_CONNECTION_NAME=") - return None - - agent = None - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Create AI Search tool with SIMPLE query type (our index doesn't have vector fields) - search_tool = AzureAISearchAgentTool( - azure_ai_search=AzureAISearchToolResource(indexes=[ - AISearchIndexResource( - project_connection_id=AI_SEARCH_CONNECTION_NAME, - index_name=AI_SEARCH_INDEX_NAME, - query_type=AzureAISearchQueryType.SIMPLE, # Use simple text search - ) - ]) - ) - - # Create an agent with AI Search tool - agent = project_client.agents.create_version( - agent_name="search-test-agent", - definition=PromptAgentDefinition( - model=MODEL_NAME, - instructions="""You are a helpful assistant that searches for information. - When asked a question, use the search tool to find relevant information.""", - tools=[search_tool], - ), - ) - print(f"✓ Created agent with AI Search tool (id: {agent.id})") - - # Create a conversation thread - conversation = openai_client.conversations.create() - print(f"✓ Created conversation: {conversation.id}") - - # Send a request that should trigger the search tool - response = openai_client.responses.create( - conversation=conversation.id, - input="Search for any documents in the index and tell me what you find.", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - log_response_info(response, "AI Search Response") - - print(f"\n✓ Agent response: {response.output_text[:500]}...") - print("\n✓ TEST PASSED: AI Search tool successfully queried private AI Search") - - # Cleanup - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - print(f" Cleaned up agent: {agent.name}") - - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - log_exception_info(e, "AI Search Error") - import traceback - traceback.print_exc() - return False - - -def test_mcp_tool_with_agent(): - """Test that an agent can use MCP tool to call the private MCP server.""" - print("\n" + "=" * 60) - print("TEST 4: MCP Tool → Private MCP Server") - print("=" * 60) - - agent = None - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Create MCP tool pointing to our private MCP server - mcp_tool = MCPTool( - server_label="hello-mcp", - server_url=MCP_SERVER_URL, - require_approval="never", # Auto-approve for testing - ) - - # Create an agent with MCP tool - agent = project_client.agents.create_version( - agent_name="mcp-test-agent", - definition=PromptAgentDefinition( - model=MODEL_NAME, - instructions="""You are a helpful agent that can use MCP tools. - Use the available MCP tools to answer questions and perform tasks. - When asked to greet someone, use the hello tool from the MCP server.""", - tools=[mcp_tool], - ), - ) - print(f"✓ Created agent with MCP tool (id: {agent.id})") - print(f" MCP Server URL: {MCP_SERVER_URL}") - - # Create a conversation thread - conversation = openai_client.conversations.create() - print(f"✓ Created conversation: {conversation.id}") - - # Send a request that should trigger the MCP tool - print(" Sending request to use MCP hello tool...") - response = openai_client.responses.create( - conversation=conversation.id, - input="Please, calculate 1 + 2 using the MCP tool and print the response.", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - log_response_info(response, "MCP Tool Response") - - # Check if we got an MCP approval request (if require_approval was set) - for item in response.output: - if hasattr(item, 'type') and item.type == "mcp_approval_request": - print(f" MCP approval requested for: {item.server_label}") - - # Auto-approve - input_list: ResponseInputParam = [ - McpApprovalResponse( - type="mcp_approval_response", - approve=True, - approval_request_id=item.id, - ) - ] - - response = openai_client.responses.create( - input=input_list, - previous_response_id=response.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - - print(f"\n✓ Agent response: {response.output_text}") - - # Check if the response mentions the MCP server or greeting - if "hello" in response.output_text.lower() or "greet" in response.output_text.lower(): - print("\n✓ TEST PASSED: MCP tool connected to private MCP server") - result = True - else: - print("\n⚠ TEST UNCERTAIN: Got response but unclear if MCP tool was used") - result = True # Still consider it a pass if we got a response - - # Cleanup - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - print(f" Cleaned up agent: {agent.name}") - - return result - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - log_exception_info(e, "MCP Tool Error") - import traceback - traceback.print_exc() - - # Check for specific error patterns - error_str = str(e) - if "424" in error_str or "Failed Dependency" in error_str: - print("\n ⚠ This is the known DNS resolution issue:") - print(" The Data Proxy cannot resolve the private Container Apps DNS.") - print(" The MCP server IS reachable from VNet VMs (Test 1), but not via Data Proxy.") - - return False - - -def test_openai_responses_api(): - """Test direct usage of OpenAI Responses API without an agent.""" - print("\n" + "=" * 60) - print("TEST 5: OpenAI Responses API (Direct)") - print("=" * 60) - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Use the Responses API directly without an agent - response = openai_client.responses.create( - model=MODEL_NAME, - input="What is 2 + 2? Answer with just the number.", - ) - log_response_info(response, "Direct OpenAI Response") - - print(f"\n✓ Response: {response.output_text}") - print("\n✓ TEST PASSED: OpenAI Responses API works directly") - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - log_exception_info(e, "OpenAI API Error") - import traceback - traceback.print_exc() - return False - - -def main(): - print("=" * 60) - print("AGENTS V2 END-TO-END TEST") - print("Using new Responses API and Agent Versioning") - print("=" * 60) - print(f"\nConfiguration:") - print(f" Project Endpoint: {PROJECT_ENDPOINT}") - print(f" Model: {MODEL_NAME}") - print(f" AI Search Index: {AI_SEARCH_INDEX_NAME}") - print(f" AI Search Connection: {AI_SEARCH_CONNECTION_NAME or '(not set)'}") - print(f" MCP Server: {MCP_SERVER_URL}") - - results = {} - - # Test 1: MCP Server Connectivity (direct HTTP) - results['mcp_connectivity'] = test_mcp_server_connectivity() - - # Test 2: OpenAI Responses API (direct) - results['responses_api'] = test_openai_responses_api() - - # Test 3: Basic Agent with Responses API - results['basic_agent'] = test_basic_agent() - - # Test 4: AI Search Tool (optional) - ai_search_result = test_ai_search_tool() - if ai_search_result is not None: - results['ai_search'] = ai_search_result - - # Test 5: MCP Tool with Agent - mcp_tool_result = test_mcp_tool_with_agent() - if mcp_tool_result is not None: - results['mcp_tool'] = mcp_tool_result - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - for test_name, passed in results.items(): - status = "✓ PASSED" if passed else "✗ FAILED" - print(f" {test_name}: {status}") - - all_passed = all(results.values()) - print("\n" + ("=" * 60)) - if all_passed: - print("ALL TESTS PASSED - Agents v2 API is working!") - else: - print("SOME TESTS FAILED - Check the output above for details") - print("=" * 60) - - return 0 if all_passed else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_ai_search_tool_agents_v2.py b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_ai_search_tool_agents_v2.py deleted file mode 100644 index ab2223892..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_ai_search_tool_agents_v2.py +++ /dev/null @@ -1,388 +0,0 @@ -#!/usr/bin/env python3 -""" -AI Search Tool Test Script - -This script focuses on testing Azure AI Search tool integration -with Azure AI Foundry Agents v2. - -Tests: -1. AI Search Connectivity - Direct REST API test to AI Search service -2. AI Search Tool via Agent - Test AI Search tool via agent (uses Data Proxy) - -The agent test validates that: -- The Data Proxy can resolve private endpoint DNS -- The AI Search connection is properly configured -- The agent can query documents from the private AI Search index -""" - -import os -import sys -import logging -import argparse -import json -import urllib.request -import urllib.error -import ssl - -# ============================================================================ -# LOGGING CONFIGURATION -# ============================================================================ -LOG_LEVEL = logging.INFO - -logging.basicConfig( - level=LOG_LEVEL, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) - -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(LOG_LEVEL) -logging.getLogger("httpx").setLevel(LOG_LEVEL) -logging.getLogger("urllib3").setLevel(logging.WARNING) -logging.getLogger("azure.identity").setLevel(logging.WARNING) - -# ============================================================================ - -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import ( - AzureAISearchAgentTool, - AzureAISearchToolResource, - AISearchIndexResource, - AzureAISearchQueryType, - PromptAgentDefinition, -) -from azure.identity import DefaultAzureCredential - -# ============================================================================ -# CONFIGURATION -# ============================================================================ -PROJECT_ENDPOINT = os.environ.get( - "PROJECT_ENDPOINT", - "https://aiservicesaxy3.services.ai.azure.com/api/projects/projectaxy3" -) -MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini") - -# AI Search Configuration -AI_SEARCH_CONNECTION_NAME = os.environ.get( - "AI_SEARCH_CONNECTION_NAME", - "aiservicesaxy3search" # Default connection name from template deployment -) -AI_SEARCH_INDEX_NAME = os.environ.get("AI_SEARCH_INDEX_NAME", "test-index") - -# AI Search endpoint for direct connectivity test (optional) -# This is only used for the connectivity test, not the agent test -AI_SEARCH_ENDPOINT = os.environ.get( - "AI_SEARCH_ENDPOINT", - "" # e.g., "https://aiservicesaxy3search.search.windows.net" -) - -# ============================================================================ - - -def log_response_info(response, label="Response"): - """Extract and log useful debugging info from OpenAI response objects.""" - logger = logging.getLogger(__name__) - try: - if hasattr(response, '_request_id'): - logger.info(f"{label} - Request ID: {response._request_id}") - if hasattr(response, 'id'): - logger.info(f"{label} - Response ID: {response.id}") - if hasattr(response, '_response') and hasattr(response._response, 'headers'): - headers = response._response.headers - if 'x-request-id' in headers: - logger.info(f"{label} - x-request-id: {headers['x-request-id']}") - if 'x-ms-request-id' in headers: - logger.info(f"{label} - x-ms-request-id: {headers['x-ms-request-id']}") - except Exception as e: - logger.debug(f"Could not extract response info: {e}") - - -def log_exception_info(exception, label="Exception"): - """Extract and log request info from OpenAI exceptions.""" - logger = logging.getLogger(__name__) - try: - if hasattr(exception, 'response') and exception.response is not None: - resp = exception.response - headers = resp.headers if hasattr(resp, 'headers') else {} - - request_id = headers.get('x-request-id', 'N/A') - ms_request_id = headers.get('x-ms-request-id', 'N/A') - - logger.error(f"{label} - x-request-id: {request_id}") - logger.error(f"{label} - x-ms-request-id: {ms_request_id}") - logger.error(f"{label} - Status: {resp.status_code if hasattr(resp, 'status_code') else 'N/A'}") - - if hasattr(resp, 'text'): - logger.error(f"{label} - Body: {resp.text[:500]}") - except Exception as e: - logger.debug(f"Could not extract exception info: {e}") - - -def test_ai_search_connectivity(): - """ - Test direct connectivity to AI Search service. - - Note: This test requires AI_SEARCH_ENDPOINT to be set and will only work - from within the VNet (e.g., jump box) for private AI Search endpoints. - """ - print("\n" + "=" * 60) - print("TEST: AI Search Connectivity (Direct REST API)") - print("=" * 60) - - if not AI_SEARCH_ENDPOINT: - print(" ⚠ AI_SEARCH_ENDPOINT not set, skipping connectivity test") - print(" Set it with: export AI_SEARCH_ENDPOINT=https://.search.windows.net") - print(" Note: This test only works from within the VNet for private endpoints") - return None - - print(f" Target: {AI_SEARCH_ENDPOINT}") - print(f" Index: {AI_SEARCH_INDEX_NAME}") - - try: - # Get Azure AD token for AI Search - credential = DefaultAzureCredential() - token = credential.get_token("https://search.azure.com/.default") - - # Query the index - url = f"{AI_SEARCH_ENDPOINT}/indexes/{AI_SEARCH_INDEX_NAME}/docs?api-version=2024-07-01&search=*&$top=1" - - ctx = ssl.create_default_context() - headers = { - "Authorization": f"Bearer {token.token}", - "Content-Type": "application/json" - } - - req = urllib.request.Request(url, headers=headers, method="GET") - - print("\n--- Querying AI Search Index ---") - with urllib.request.urlopen(req, timeout=15, context=ctx) as response: - status = response.status - body = response.read().decode('utf-8') - result = json.loads(body) - - print(f" ✓ HTTP Status: {status}") - doc_count = len(result.get('value', [])) - print(f" ✓ Documents found: {doc_count}") - - if doc_count > 0: - print(f" ✓ Sample document keys: {list(result['value'][0].keys())[:5]}") - - print("\n" + "=" * 60) - print("✓ TEST PASSED: AI Search connectivity working") - print("=" * 60) - return True - - except urllib.error.URLError as e: - print(f"\n✗ TEST FAILED: {e}") - if "Name or service not known" in str(e): - print(" Note: This is expected if running from outside the VNet") - print(" The AI Search endpoint is only accessible via private endpoint") - return False - except Exception as e: - print(f"\n✗ TEST FAILED: {e}") - import traceback - traceback.print_exc() - return False - - -def test_ai_search_tool_via_agent(): - """ - Test AI Search tool via Azure AI Agent. - - This test validates that: - - The Data Proxy can reach the private AI Search endpoint - - The AI Search connection is properly configured - - The agent can query and retrieve documents - """ - print("\n" + "=" * 60) - print("TEST: AI Search Tool via Agent") - print("=" * 60) - - if not AI_SEARCH_CONNECTION_NAME: - print(" ⚠ AI_SEARCH_CONNECTION_NAME not set, skipping this test") - print(" Set it with: export AI_SEARCH_CONNECTION_NAME=") - return None - - print(f" Connection: {AI_SEARCH_CONNECTION_NAME}") - print(f" Index: {AI_SEARCH_INDEX_NAME}") - - agent = None - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Create AI Search tool with SIMPLE query type - search_tool = AzureAISearchAgentTool( - azure_ai_search=AzureAISearchToolResource(indexes=[ - AISearchIndexResource( - project_connection_id=AI_SEARCH_CONNECTION_NAME, - index_name=AI_SEARCH_INDEX_NAME, - query_type=AzureAISearchQueryType.SIMPLE, - ) - ]) - ) - - # Create an agent with AI Search tool - agent = project_client.agents.create_version( - agent_name="search-tool-test", - definition=PromptAgentDefinition( - model=MODEL_NAME, - instructions="""You are a helpful assistant that searches for information. - When asked a question, use the search tool to find relevant information. - Always summarize what you found from the search results.""", - tools=[search_tool], - ), - ) - print(f"✓ Created agent with AI Search tool (id: {agent.id})") - print(f" Connection: {AI_SEARCH_CONNECTION_NAME}") - print(f" Index: {AI_SEARCH_INDEX_NAME}") - - # Create a conversation - conversation = openai_client.conversations.create() - print(f"✓ Created conversation: {conversation.id}") - - # Send a request that should trigger the search tool - print(" Sending search request to agent...") - response = openai_client.responses.create( - conversation=conversation.id, - input="Search for any documents in the index and tell me what you find. List any document titles or content you discover.", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - log_response_info(response, "AI Search Response") - - # Display response (truncate if too long) - output_text = response.output_text - if len(output_text) > 500: - print(f"\n✓ Agent response: {output_text[:500]}...") - else: - print(f"\n✓ Agent response: {output_text}") - - # Cleanup - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - print(f" Cleaned up agent: {agent.name}") - - print("\n" + "=" * 60) - print("✓ TEST PASSED: AI Search tool via agent") - print("=" * 60) - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - log_exception_info(e, "AI Search Error") - import traceback - traceback.print_exc() - - # Cleanup on failure - if agent: - try: - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - except: - pass - - return False - - -def main(): - parser = argparse.ArgumentParser( - description="Test AI Search tool integration with Azure AI Foundry Agents v2", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - python test_ai_search_tool_agents_v2.py # Run all tests - python test_ai_search_tool_agents_v2.py --test connectivity # Only connectivity test - python test_ai_search_tool_agents_v2.py --test agent # Only agent test - python test_ai_search_tool_agents_v2.py --retry 3 # Retry failed tests up to 3 times - -Environment variables: - PROJECT_ENDPOINT - Azure AI project endpoint - MODEL_NAME - Model to use (default: gpt-4o-mini) - AI_SEARCH_CONNECTION_NAME - AI Search connection name in the project - AI_SEARCH_INDEX_NAME - AI Search index name (default: test-index) - AI_SEARCH_ENDPOINT - AI Search endpoint URL (for connectivity test only) -""" - ) - parser.add_argument( - "--test", - choices=["connectivity", "agent", "all"], - default="all", - help="Which test to run (default: all)" - ) - parser.add_argument( - "--retry", - type=int, - default=0, - help="Number of times to retry failed tests (default: 0)" - ) - - args = parser.parse_args() - - print("=" * 60) - print("AI SEARCH TOOL TEST") - print("=" * 60) - print() - print("Configuration:") - print(f" Project Endpoint: {PROJECT_ENDPOINT}") - print(f" Model: {MODEL_NAME}") - print(f" AI Search Connection: {AI_SEARCH_CONNECTION_NAME or '(not set)'}") - print(f" AI Search Index: {AI_SEARCH_INDEX_NAME}") - print(f" AI Search Endpoint: {AI_SEARCH_ENDPOINT or '(not set - connectivity test skipped)'}") - - results = {} - - # Run connectivity test - if args.test in ["connectivity", "all"]: - result = test_ai_search_connectivity() - if result is not None: - results['connectivity'] = result - - # Run agent test - if args.test in ["agent", "all"]: - for attempt in range(args.retry + 1): - if attempt > 0: - print(f"\n--- Retry attempt {attempt}/{args.retry} ---") - - result = test_ai_search_tool_via_agent() - if result is not None: - results['agent'] = result - if result: - break # Success, no need to retry - else: - break # Skipped, no need to retry - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - - for test_name, passed in results.items(): - status = "✓ PASSED" if passed else "✗ FAILED" - print(f" {test_name}: {status}") - - # Exit with appropriate code - all_passed = all(results.values()) if results else True - if all_passed: - print("\n" + "=" * 60) - print("ALL TESTS PASSED!") - print("=" * 60) - sys.exit(0) - else: - print("\n" + "=" * 60) - print("SOME TESTS FAILED") - print("=" * 60) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_mcp_tools_agents_v2.py b/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_mcp_tools_agents_v2.py deleted file mode 100644 index 32698baf1..000000000 --- a/infrastructure/infrastructure-setup-bicep/19-hybrid-private-resources-agent-setup/tests/test_mcp_tools_agents_v2.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python3 -""" -MCP Tools Test Script - -This script focuses on testing MCP (Model Context Protocol) tool integration -with Azure AI Foundry Agents v2. - -Tests: -1. MCP Connectivity (Direct HTTP) - Direct session flow test to MCP server -2. MCP Tool via Agent (Public) - Test MCP tool via public Container App -3. MCP Tool via Agent (Private) - Test MCP tool via private Container App (VNet) - -Note: Tests 2 and 3 may intermittently fail due to known Hyena cluster routing -issue where ~50% of requests hit a scale unit without Data Proxy deployed. -""" - -import os -import sys -import logging -import argparse - -# ============================================================================ -# LOGGING CONFIGURATION -# ============================================================================ -LOG_LEVEL = logging.INFO - -logging.basicConfig( - level=LOG_LEVEL, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) - -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(LOG_LEVEL) -logging.getLogger("httpx").setLevel(LOG_LEVEL) -logging.getLogger("urllib3").setLevel(logging.WARNING) -logging.getLogger("azure.identity").setLevel(logging.WARNING) - -# ============================================================================ - -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import ( - MCPTool, - PromptAgentDefinition, -) -from azure.identity import DefaultAzureCredential -from openai.types.responses import ResponseInputParam -from openai.types.responses.response_input_param import McpApprovalResponse - -# ============================================================================ -# CONFIGURATION -# ============================================================================ -PROJECT_ENDPOINT = os.environ.get( - "PROJECT_ENDPOINT", - "https://aiservicesaxy3.services.ai.azure.com/api/projects/projectaxy3" -) -MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini") - -# MCP Server URLs -# Public MCP server (external, accessible from anywhere) -MCP_SERVER_PUBLIC = os.environ.get( - "MCP_SERVER_PUBLIC", - "https://multi-auth-mcp.victorioussmoke-7859ae09.uksouth.azurecontainerapps.io/noauth/mcp" -) - -# Private MCP server (internal, only accessible from VNet via Data Proxy) -MCP_SERVER_PRIVATE = os.environ.get( - "MCP_SERVER_PRIVATE", - "https://mcp-http-server.jollydune-20a0f709.westus2.azurecontainerapps.io/noauth/mcp" -) - -# ============================================================================ - - -def log_response_info(response, label="Response"): - """Extract and log useful debugging info from OpenAI response objects.""" - logger = logging.getLogger(__name__) - try: - if hasattr(response, '_request_id'): - logger.info(f"{label} - Request ID: {response._request_id}") - if hasattr(response, 'id'): - logger.info(f"{label} - Response ID: {response.id}") - if hasattr(response, '_response') and hasattr(response._response, 'headers'): - headers = response._response.headers - if 'x-request-id' in headers: - logger.info(f"{label} - x-request-id: {headers['x-request-id']}") - if 'x-ms-request-id' in headers: - logger.info(f"{label} - x-ms-request-id: {headers['x-ms-request-id']}") - except Exception as e: - logger.debug(f"Could not extract response info: {e}") - - -def log_exception_info(exception, label="Exception"): - """Extract and log request info from OpenAI exceptions.""" - logger = logging.getLogger(__name__) - try: - if hasattr(exception, 'response') and exception.response is not None: - resp = exception.response - headers = resp.headers if hasattr(resp, 'headers') else {} - - request_id = headers.get('x-request-id', 'N/A') - ms_request_id = headers.get('x-ms-request-id', 'N/A') - - logger.error(f"{label} - x-request-id: {request_id}") - logger.error(f"{label} - x-ms-request-id: {ms_request_id}") - - print(f" 📋 Request ID (x-request-id): {request_id}") - print(f" 📋 MS Request ID (x-ms-request-id): {ms_request_id}") - - if hasattr(resp, 'status_code'): - logger.error(f"{label} - HTTP Status: {resp.status_code}") - - if hasattr(exception, 'request_id'): - logger.error(f"{label} - request_id attribute: {exception.request_id}") - print(f" 📋 Request ID: {exception.request_id}") - - except Exception as e: - logger.debug(f"Could not extract exception info: {e}") - - -def test_mcp_connectivity(mcp_url: str, label: str = "MCP Server"): - """Test MCP server with full session workflow: initialize → list tools → call tool.""" - print("\n" + "=" * 60) - print(f"TEST: MCP Connectivity - {label}") - print("=" * 60) - - import urllib.request - import ssl - import json - - try: - ctx = ssl.create_default_context() - - print(f" Target MCP Server: {mcp_url}") - - # Step 1: Initialize - Get mcp-session-id - print("\n--- Step 1: Initialize (get mcp-session-id) ---") - - init_data = json.dumps({ - "method": "initialize", - "params": { - "protocolVersion": "2025-11-25", - "capabilities": { - "sampling": {}, - "elicitation": {}, - "roots": {"listChanged": True} - }, - "clientInfo": { - "name": "test-mcp-client", - "version": "1.0.0" - } - }, - "jsonrpc": "2.0", - "id": 0 - }).encode('utf-8') - - init_req = urllib.request.Request( - mcp_url, - data=init_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream" - }, - method="POST" - ) - - with urllib.request.urlopen(init_req, timeout=15, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - mcp_session_id = response.getheader('mcp-session-id') - - print(f" ✓ HTTP Status: {status}") - print(f" ✓ Response: {body[:300]}...") - - if mcp_session_id: - print(f" ✓ MCP Session ID: {mcp_session_id}") - else: - print(" ✗ No mcp-session-id header in response!") - return False - - # Step 2: List Tools - print("\n--- Step 2: List Tools (using session ID) ---") - - list_data = json.dumps({ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/list", - "params": {} - }).encode('utf-8') - - list_req = urllib.request.Request( - mcp_url, - data=list_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", - "mcp-session-id": mcp_session_id - }, - method="POST" - ) - - with urllib.request.urlopen(list_req, timeout=10, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - result = json.loads(body) - - print(f" ✓ HTTP Status: {status}") - - if "result" in result and "tools" in result["result"]: - tools = result["result"]["tools"] - print(f" ✓ Found {len(tools)} tools:") - for tool in tools: - print(f" - {tool.get('name', 'unknown')}: {tool.get('description', '')[:50]}") - else: - print(f" ✓ Response: {body[:300]}...") - - # Step 3: Call Tool 'add' - print("\n--- Step 3: Call Tool 'add' (using session ID) ---") - - call_data = json.dumps({ - "jsonrpc": "2.0", - "id": 2, - "method": "tools/call", - "params": { - "name": "add", - "arguments": {"a": 2, "b": 4} - } - }).encode('utf-8') - - call_req = urllib.request.Request( - mcp_url, - data=call_data, - headers={ - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", - "mcp-session-id": mcp_session_id - }, - method="POST" - ) - - with urllib.request.urlopen(call_req, timeout=10, context=ctx) as response: - status = response.getcode() - body = response.read().decode('utf-8') - result = json.loads(body) - - print(f" ✓ HTTP Status: {status}") - print(f" ✓ Response: {body}") - - if "result" in result: - print(f" ✓ Tool call successful!") - else: - print(f" ⚠ Unexpected response format") - - print("\n" + "=" * 60) - print(f"✓ TEST PASSED: {label} session flow working correctly") - print("=" * 60) - return True - - except Exception as e: - print(f"\n✗ TEST FAILED: {str(e)}") - import traceback - traceback.print_exc() - return False - - -def test_mcp_tool_via_agent(mcp_url: str, label: str = "MCP Server"): - """Test that an agent can use MCP tool via the Data Proxy.""" - print("\n" + "=" * 60) - print(f"TEST: MCP Tool via Agent - {label}") - print("=" * 60) - - agent = None - - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient( - credential=credential, - endpoint=PROJECT_ENDPOINT - ) as project_client, - project_client.get_openai_client() as openai_client, - ): - print(f"✓ Connected to AI Project at {PROJECT_ENDPOINT}") - - # Create MCP tool - mcp_tool = MCPTool( - server_label="test-mcp", - server_url=mcp_url, - require_approval="never", - ) - - # Create agent with MCP tool - agent = project_client.agents.create_version( - agent_name="mcp-tool-test", - definition=PromptAgentDefinition( - model=MODEL_NAME, - instructions="""You are a helpful agent that can use MCP tools. - When asked to calculate, use the 'add' tool from the MCP server.""", - tools=[mcp_tool], - ), - ) - print(f"✓ Created agent with MCP tool (id: {agent.id})") - print(f" MCP Server URL: {mcp_url}") - - # Create conversation - conversation = openai_client.conversations.create() - print(f"✓ Created conversation: {conversation.id}") - - # Send request - print(" Sending request to use MCP add tool...") - response = openai_client.responses.create( - conversation=conversation.id, - input="Please calculate 1 + 2 using the MCP add tool and tell me the result.", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - log_response_info(response, "MCP Tool Response") - - # Handle MCP approval if needed - for item in response.output: - if hasattr(item, 'type') and item.type == "mcp_approval_request": - print(f" MCP approval requested for: {item.server_label}") - input_list: ResponseInputParam = [ - McpApprovalResponse( - type="mcp_approval_response", - approve=True, - approval_request_id=item.id, - ) - ] - response = openai_client.responses.create( - input=input_list, - previous_response_id=response.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, - ) - - print(f"\n✓ Agent response: {response.output_text}") - - # Cleanup - project_client.agents.delete_version( - agent_name=agent.name, - agent_version=agent.version - ) - print(f" Cleaned up agent: {agent.name}") - - print(f"\n✓ TEST PASSED: MCP tool via {label}") - return True - - except Exception as e: - error_str = str(e) - print(f"\n✗ TEST FAILED: {error_str}") - log_exception_info(e, "MCP Tool Error") - - # Provide context for known issues - if "TaskCanceledException" in error_str: - print("\n ⚠ Known Issue: TaskCanceledException") - print(" This occurs when request hits the wrong Hyena scale unit") - print(" (Data Proxy is only deployed on one of two scale units)") - print(" Re-running the test may succeed on the next attempt.") - elif "424" in error_str or "Failed Dependency" in error_str: - print("\n ⚠ Known Issue: DNS Resolution") - print(" Data Proxy cannot resolve private Container Apps DNS.") - - import traceback - traceback.print_exc() - - # Cleanup agent if created - if agent is not None: - try: - with ( - DefaultAzureCredential() as credential, - AIProjectClient(credential=credential, endpoint=PROJECT_ENDPOINT) as project_client, - ): - project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) - print(f" Cleaned up agent: {agent.name}") - except: - pass - - return False - - -def main(): - parser = argparse.ArgumentParser(description="MCP Tools Test Script") - parser.add_argument( - "--test", - choices=["public", "private", "all"], - default="all", - help="Which MCP server to test via agent: public, private, or all (default: all)" - ) - parser.add_argument( - "--retry", - type=int, - default=1, - help="Number of retries for agent tests (default: 1)" - ) - args = parser.parse_args() - - print("=" * 60) - print("MCP TOOLS TEST") - print("=" * 60) - print(f"\nConfiguration:") - print(f" Project Endpoint: {PROJECT_ENDPOINT}") - print(f" Model: {MODEL_NAME}") - print(f" Public MCP Server: {MCP_SERVER_PUBLIC}") - print(f" Private MCP Server: {MCP_SERVER_PRIVATE}") - - results = {} - - # Always run connectivity test first (opens the session) - mcp_url = MCP_SERVER_PUBLIC if args.test in ["public", "all"] else MCP_SERVER_PRIVATE - results['connectivity'] = test_mcp_connectivity(mcp_url, "Public MCP Server" if mcp_url == MCP_SERVER_PUBLIC else "Private MCP Server") - - # Test: MCP Tool via Agent (Public) - if args.test in ["public", "all"]: - for attempt in range(args.retry): - if attempt > 0: - print(f"\n Retry attempt {attempt + 1}/{args.retry}...") - result = test_mcp_tool_via_agent(MCP_SERVER_PUBLIC, "Public MCP Server") - if result: - results['agent_public'] = True - break - else: - results['agent_public'] = False - - # Test: MCP Tool via Agent (Private) - if args.test in ["private", "all"]: - for attempt in range(args.retry): - if attempt > 0: - print(f"\n Retry attempt {attempt + 1}/{args.retry}...") - result = test_mcp_tool_via_agent(MCP_SERVER_PRIVATE, "Private MCP Server") - if result: - results['agent_private'] = True - break - else: - results['agent_private'] = False - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - for test_name, passed in results.items(): - status = "✓ PASSED" if passed else "✗ FAILED" - print(f" {test_name}: {status}") - - all_passed = all(results.values()) - print("\n" + "=" * 60) - if all_passed: - print("ALL TESTS PASSED!") - else: - print("SOME TESTS FAILED") - print("Note: Agent tests may fail due to Hyena cluster routing (~50% chance)") - print(" Use --retry N to retry failed tests") - print("=" * 60) - - return 0 if all_passed else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json index a87e40609..06c33519b 100644 --- a/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11385179130648578926" + "version": "0.39.26.7824", + "templateHash": "3240349148352159726" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json index 6cb7392cd..64192cbd4 100644 --- a/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16822506733457783114" + "version": "0.39.26.7824", + "templateHash": "16694821934772097765" } }, "parameters": { @@ -142,8 +142,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "812724488663360404" + "version": "0.39.26.7824", + "templateHash": "10984074837058238866" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/31-customer-managed-keys-standard-agent/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/31-customer-managed-keys-standard-agent/azuredeploy.json index 2df23eb63..80fece3fc 100644 --- a/infrastructure/infrastructure-setup-bicep/31-customer-managed-keys-standard-agent/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/31-customer-managed-keys-standard-agent/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "18062982936332919565" + "version": "0.39.26.7824", + "templateHash": "6166861745321957187" } }, "parameters": { @@ -152,8 +152,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "4091539512693342510" + "version": "0.39.26.7824", + "templateHash": "10572111282973522005" } }, "parameters": { @@ -288,8 +288,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7266831742569425479" + "version": "0.39.26.7824", + "templateHash": "4777074434873577497" } }, "parameters": { @@ -546,8 +546,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "10231344616002774243" + "version": "0.39.26.7824", + "templateHash": "17728000413021798670" } }, "parameters": { @@ -710,8 +710,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12192296260645390578" + "version": "0.39.26.7824", + "templateHash": "8294688920685850261" } }, "parameters": { @@ -886,8 +886,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15991712531324316353" + "version": "0.39.26.7824", + "templateHash": "14683840003859985069" } }, "parameters": { @@ -902,7 +902,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -943,8 +943,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11201886913940098363" + "version": "0.39.26.7824", + "templateHash": "2161753938341361575" } }, "parameters": { @@ -965,7 +965,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1007,8 +1007,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15867124606695536257" + "version": "0.39.26.7824", + "templateHash": "7968115481508840" } }, "parameters": { @@ -1029,7 +1029,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1040,7 +1040,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1096,8 +1096,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11180853619796693081" + "version": "0.39.26.7824", + "templateHash": "16815875843770432836" } }, "parameters": { @@ -1193,8 +1193,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "9077220512228030688" + "version": "0.39.26.7824", + "templateHash": "8346749807649424278" } }, "parameters": { @@ -1263,8 +1263,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "3625425571119261380" + "version": "0.39.26.7824", + "templateHash": "13874725855824693255" } }, "parameters": { @@ -1294,7 +1294,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", "name": "[guid(resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]", "properties": { "principalId": "[parameters('aiProjectPrincipalId')]", diff --git a/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json deleted file mode 100644 index c9dbbfd7f..000000000 --- a/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json +++ /dev/null @@ -1,428 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15203946428285185650" - } - }, - "parameters": { - "aiFoundryName": { - "type": "string", - "defaultValue": "ai-foundry-complete-cmk", - "metadata": { - "description": "That name is the name of our application. It has to be unique." - } - }, - "aiProjectName": { - "type": "string", - "defaultValue": "[format('{0}-proj', parameters('aiFoundryName'))]", - "metadata": { - "description": "Name of the AI Foundry project" - } - }, - "location": { - "type": "string", - "defaultValue": "eastus2", - "metadata": { - "description": "Location for all resources." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Azure Key Vault target" - } - }, - "keyName": { - "type": "string", - "metadata": { - "description": "Name of the Azure Key Vault key" - } - }, - "keyVersion": { - "type": "string", - "metadata": { - "description": "Version of the Azure Key Vault key" - } - }, - "userAssignedIdentityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user-assigned managed identity to use for CMK encryption" - } - }, - "userAssignedIdentityClientId": { - "type": "string", - "metadata": { - "description": "Client ID of the user-assigned managed identity" - } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "foundryAccount", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiFoundryName": { - "value": "[parameters('aiFoundryName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "userAssignedIdentityId": { - "value": "[parameters('userAssignedIdentityId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11667222887400574489" - } - }, - "parameters": { - "aiFoundryName": { - "type": "string", - "metadata": { - "description": "Name of the AI Foundry account" - } - }, - "location": { - "type": "string", - "metadata": { - "description": "Location for the resource" - } - }, - "userAssignedIdentityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user-assigned managed identity" - } - } - }, - "variables": { - "identityConfig": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', parameters('userAssignedIdentityId'))]": {} - } - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2025-04-01-preview", - "name": "[parameters('aiFoundryName')]", - "location": "[parameters('location')]", - "identity": "[variables('identityConfig')]", - "kind": "AIServices", - "sku": { - "name": "S0" - }, - "properties": { - "allowProjectManagement": true, - "publicNetworkAccess": "Enabled", - "customSubDomainName": "[parameters('aiFoundryName')]", - "disableLocalAuth": true - } - } - ], - "outputs": { - "accountId": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]" - }, - "accountName": { - "type": "string", - "value": "[parameters('aiFoundryName')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "cmkEncryption", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiFoundryName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" - }, - "location": { - "value": "[parameters('location')]" - }, - "keyVaultName": { - "value": "[parameters('keyVaultName')]" - }, - "keyName": { - "value": "[parameters('keyName')]" - }, - "keyVersion": { - "value": "[parameters('keyVersion')]" - }, - "userAssignedIdentityId": { - "value": "[parameters('userAssignedIdentityId')]" - }, - "userAssignedIdentityClientId": { - "value": "[parameters('userAssignedIdentityClientId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14977133416255499203" - } - }, - "parameters": { - "aiFoundryName": { - "type": "string", - "metadata": { - "description": "Name of the AI Foundry account" - } - }, - "location": { - "type": "string", - "metadata": { - "description": "Location for the resource" - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Azure Key Vault" - } - }, - "keyName": { - "type": "string", - "metadata": { - "description": "Name of the Azure Key Vault key" - } - }, - "keyVersion": { - "type": "string", - "metadata": { - "description": "Version of the Azure Key Vault key" - } - }, - "userAssignedIdentityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user-assigned managed identity" - } - }, - "userAssignedIdentityClientId": { - "type": "string", - "metadata": { - "description": "Client ID of the user-assigned managed identity" - } - } - }, - "variables": { - "keyVaultUri": "[format('https://{0}{1}/', parameters('keyVaultName'), environment().suffixes.keyvaultDns)]" - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2025-04-01-preview", - "name": "[parameters('aiFoundryName')]", - "location": "[parameters('location')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', parameters('userAssignedIdentityId'))]": {} - } - }, - "kind": "AIServices", - "sku": { - "name": "S0" - }, - "properties": { - "encryption": { - "keySource": "Microsoft.KeyVault", - "keyVaultProperties": { - "keyVaultUri": "[variables('keyVaultUri')]", - "keyName": "[parameters('keyName')]", - "keyVersion": "[parameters('keyVersion')]", - "identityClientId": "[parameters('userAssignedIdentityClientId')]" - } - }, - "allowProjectManagement": true, - "publicNetworkAccess": "Enabled", - "customSubDomainName": "[parameters('aiFoundryName')]", - "disableLocalAuth": true - } - } - ], - "outputs": { - "encryptionStatus": { - "type": "string", - "value": "CMK encryption enabled" - }, - "keyVaultUri": { - "type": "string", - "value": "[variables('keyVaultUri')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'foundryAccount')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "foundryProject", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "aiFoundryName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" - }, - "projectName": { - "value": "[parameters('aiProjectName')]" - }, - "projectDisplayName": { - "value": "[parameters('aiProjectName')]" - }, - "projectDescription": { - "value": "AI Foundry project with customer-managed keys" - }, - "location": { - "value": "[parameters('location')]" - }, - "userAssignedIdentityId": { - "value": "[parameters('userAssignedIdentityId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1706744676486014899" - } - }, - "parameters": { - "aiFoundryName": { - "type": "string", - "metadata": { - "description": "Name of the AI Foundry account (parent)" - } - }, - "location": { - "type": "string", - "metadata": { - "description": "Location for the resource" - } - }, - "projectName": { - "type": "string", - "metadata": { - "description": "Name of the project" - } - }, - "projectDisplayName": { - "type": "string", - "metadata": { - "description": "Display name for the project" - } - }, - "projectDescription": { - "type": "string", - "metadata": { - "description": "Description for the project" - } - }, - "userAssignedIdentityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user-assigned managed identity" - } - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('projectName'))]", - "location": "[parameters('location')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', parameters('userAssignedIdentityId'))]": {} - } - }, - "properties": { - "displayName": "[parameters('projectDisplayName')]", - "description": "[parameters('projectDescription')]" - } - } - ], - "outputs": { - "projectId": { - "type": "string", - "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('projectName'))]" - }, - "projectName": { - "type": "string", - "value": "[parameters('projectName')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'foundryAccount')]", - "[resourceId('Microsoft.Resources/deployments', 'cmkEncryption')]" - ] - } - ], - "outputs": { - "accountId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountId.value]" - }, - "accountName": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" - }, - "projectId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryProject'), '2025-04-01').outputs.projectId.value]" - }, - "projectName": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryProject'), '2025-04-01').outputs.projectName.value]" - }, - "keyVaultUri": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cmkEncryption'), '2025-04-01').outputs.keyVaultUri.value]" - } - } -} \ No newline at end of file diff --git a/infrastructure/infrastructure-setup-bicep/40-basic-agent-setup/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/40-basic-agent-setup/azuredeploy.json index aaaa380f0..8d7b06f37 100644 --- a/infrastructure/infrastructure-setup-bicep/40-basic-agent-setup/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/40-basic-agent-setup/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16994587194208622939" + "version": "0.39.26.7824", + "templateHash": "17632891943305566295" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/41-standard-agent-setup/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/41-standard-agent-setup/azuredeploy.json index 76c4fb72a..3449d1659 100644 --- a/infrastructure/infrastructure-setup-bicep/41-standard-agent-setup/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/41-standard-agent-setup/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "6214563059109390518" + "version": "0.39.26.7824", + "templateHash": "17836798406056740836" } }, "parameters": { @@ -187,8 +187,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "4091539512693342510" + "version": "0.39.26.7824", + "templateHash": "10572111282973522005" } }, "parameters": { @@ -314,8 +314,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "949742597496737540" + "version": "0.39.26.7824", + "templateHash": "6621069906435874124" } }, "parameters": { @@ -562,8 +562,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "619228671809139369" + "version": "0.39.26.7824", + "templateHash": "12622173730539075929" } }, "parameters": { @@ -718,8 +718,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1007872875847158534" + "version": "0.39.26.7824", + "templateHash": "5095087340309076800" } }, "parameters": { @@ -892,8 +892,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12606978205101260380" + "version": "0.39.26.7824", + "templateHash": "6910483561575524105" } }, "parameters": { @@ -947,8 +947,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15991712531324316353" + "version": "0.39.26.7824", + "templateHash": "14683840003859985069" } }, "parameters": { @@ -963,7 +963,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1004,8 +1004,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11201886913940098363" + "version": "0.39.26.7824", + "templateHash": "2161753938341361575" } }, "parameters": { @@ -1026,7 +1026,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1068,8 +1068,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15867124606695536257" + "version": "0.39.26.7824", + "templateHash": "7968115481508840" } }, "parameters": { @@ -1090,7 +1090,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1101,7 +1101,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1157,8 +1157,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11180853619796693081" + "version": "0.39.26.7824", + "templateHash": "16815875843770432836" } }, "parameters": { @@ -1257,8 +1257,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "890248754515065430" + "version": "0.39.26.7824", + "templateHash": "16535347639253167655" } }, "parameters": { @@ -1288,7 +1288,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), parameters('aiProjectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), parameters('workspaceId'))]", "properties": { "principalId": "[parameters('aiProjectPrincipalId')]", @@ -1336,8 +1336,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14722474518981746838" + "version": "0.39.26.7824", + "templateHash": "17187611271934567223" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/42-basic-agent-setup-with-customization/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/42-basic-agent-setup-with-customization/azuredeploy.json index 3abb8f3d8..3d2f2bd45 100644 --- a/infrastructure/infrastructure-setup-bicep/42-basic-agent-setup-with-customization/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/42-basic-agent-setup-with-customization/azuredeploy.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12681773111736129347" + "version": "0.39.26.7824", + "templateHash": "14667354563602935622" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/43-standard-agent-setup-with-customization/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/43-standard-agent-setup-with-customization/azuredeploy.json index 888db2440..4ab72e331 100644 --- a/infrastructure/infrastructure-setup-bicep/43-standard-agent-setup-with-customization/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/43-standard-agent-setup-with-customization/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "17570657245063969848" + "version": "0.39.26.7824", + "templateHash": "12453864151003563685" } }, "parameters": { @@ -167,8 +167,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7565081239538254993" + "version": "0.39.26.7824", + "templateHash": "18106522243622185101" } }, "parameters": { @@ -317,8 +317,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "949742597496737540" + "version": "0.39.26.7824", + "templateHash": "6621069906435874124" } }, "parameters": { @@ -550,8 +550,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "4544502123013939487" + "version": "0.39.26.7824", + "templateHash": "9372436613092246432" } }, "parameters": { @@ -684,8 +684,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "7242163695078660841" + "version": "0.39.26.7824", + "templateHash": "17727575287719616015" } }, "parameters": { @@ -893,8 +893,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "12606978205101260380" + "version": "0.39.26.7824", + "templateHash": "6910483561575524105" } }, "parameters": { @@ -948,8 +948,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15991712531324316353" + "version": "0.39.26.7824", + "templateHash": "14683840003859985069" } }, "parameters": { @@ -964,7 +964,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('azureStorageName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'), resourceId('Microsoft.Storage/storageAccounts', parameters('azureStorageName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1005,8 +1005,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "11201886913940098363" + "version": "0.39.26.7824", + "templateHash": "2161753938341361575" } }, "parameters": { @@ -1027,7 +1027,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1069,8 +1069,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "15867124606695536257" + "version": "0.39.26.7824", + "templateHash": "7968115481508840" } }, "parameters": { @@ -1091,7 +1091,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1102,7 +1102,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Search/searchServices', parameters('aiSearchName'))]", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", "name": "[guid(parameters('projectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", "properties": { "principalId": "[parameters('projectPrincipalId')]", @@ -1164,8 +1164,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "16625483348629189251" + "version": "0.39.26.7824", + "templateHash": "8489333536894699406" } }, "parameters": { @@ -1290,8 +1290,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "890248754515065430" + "version": "0.39.26.7824", + "templateHash": "16535347639253167655" } }, "parameters": { @@ -1321,7 +1321,7 @@ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageName'))]", "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), parameters('aiProjectPrincipalId'), resourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'), parameters('workspaceId'))]", "properties": { "principalId": "[parameters('aiProjectPrincipalId')]", @@ -1369,8 +1369,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "14722474518981746838" + "version": "0.39.26.7824", + "templateHash": "17187611271934567223" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/45-basic-agent-bing/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/45-basic-agent-bing/azuredeploy.json index 2fc4cd91f..9c5a0bde1 100644 --- a/infrastructure/infrastructure-setup-bicep/45-basic-agent-bing/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/45-basic-agent-bing/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "8968481198143661871" + "version": "0.39.26.7824", + "templateHash": "7778551102092775636" } }, "parameters": { @@ -97,8 +97,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "9662938821485102145" + "version": "0.39.26.7824", + "templateHash": "18028369509636259517" } }, "parameters": { @@ -218,8 +218,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "1170667204161260016" + "version": "0.39.26.7824", + "templateHash": "9956949102179771191" } }, "parameters": { @@ -294,8 +294,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.40.2.10011", - "templateHash": "2322306944964107716" + "version": "0.39.26.7824", + "templateHash": "1420647491421234827" } }, "parameters": { diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/AgentThreadAndHITL.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/AgentThreadAndHITL.csproj index dce28b582..c438e5e08 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/AgentThreadAndHITL.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/AgentThreadAndHITL.csproj @@ -12,10 +12,10 @@ - - + + - - + + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/Program.cs b/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/Program.cs index 43a4c2967..d356d491e 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/Program.cs +++ b/samples/csharp/hosted-agents/AgentFramework/AgentThreadAndHITL/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using OpenAI; +using OpenAI.Chat; namespace AgengThreadAndHITL; @@ -31,7 +32,7 @@ static string GetWeather([Description("The location to get the weather for.")] s new Uri(endpoint), new AzureCliCredential()) .GetChatClient(deploymentName) - .CreateAIAgent( + .AsAIAgent( instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))] ); diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/AgentWithHostedMCP.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/AgentWithHostedMCP.csproj index ba80d2c1b..dfba6f51b 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/AgentWithHostedMCP.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/AgentWithHostedMCP.csproj @@ -11,11 +11,11 @@ - - + + - - + + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/Program.cs b/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/Program.cs index 9cbea8b73..3a8908876 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/Program.cs +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithHostedMCP/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using OpenAI; +using OpenAI.Responses; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; @@ -25,8 +26,8 @@ AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) - .GetOpenAIResponseClient(deploymentName) - .CreateAIAgent( + .GetResponsesClient(deploymentName) + .AsAIAgent( instructions: "You answer questions by searching the Microsoft Learn content only.", name: "MicrosoftLearnAgent", tools: [mcpTool]); diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithLocalTools/AgentWithLocalTools.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentWithLocalTools/AgentWithLocalTools.csproj index 8a17c304d..8600536a6 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithLocalTools/AgentWithLocalTools.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithLocalTools/AgentWithLocalTools.csproj @@ -12,6 +12,6 @@ - + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj index 40c9a2544..ff2c1ca0c 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/Program.cs b/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/Program.cs index 94552a801..c3389988f 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/Program.cs +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithTextSearchRag/Program.cs @@ -8,9 +8,8 @@ using Azure.AI.OpenAI; using Azure.Identity; using Microsoft.Agents.AI; -using Microsoft.Agents.AI.Data; using Microsoft.Extensions.AI; -using OpenAI; +using OpenAI.Chat; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; @@ -26,10 +25,13 @@ new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) - .CreateAIAgent(new ChatClientAgentOptions + .AsAIAgent(new ChatClientAgentOptions { - Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.", - AIContextProviderFactory = ctx => new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions) + ChatOptions = new ChatOptions + { + Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.", + }, + AIContextProviders = [new TextSearchProvider(MockSearchAsync, textSearchOptions)] }); await agent.RunAIAgentAsync(); diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentWithTools/AgentWithTools.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentWithTools/AgentWithTools.csproj index 85c5105fd..048213625 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentWithTools/AgentWithTools.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentWithTools/AgentWithTools.csproj @@ -8,10 +8,10 @@ - - + + - + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/AgentsInWorkflows.csproj b/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/AgentsInWorkflows.csproj index a25b0b6b0..f6184dee0 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/AgentsInWorkflows.csproj +++ b/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/AgentsInWorkflows.csproj @@ -9,11 +9,11 @@ - + - - + + diff --git a/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/Program.cs b/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/Program.cs index 279d1de21..ede01fa33 100644 --- a/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/Program.cs +++ b/samples/csharp/hosted-agents/AgentFramework/AgentsInWorkflows/Program.cs @@ -32,7 +32,7 @@ .AddEdge(frenchAgent, spanishAgent) .AddEdge(spanishAgent, englishAgent) .Build() - .AsAgent(); + .AsAIAgent(); return Task.FromResult(agent); }; diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/.dockerignore b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/.dockerignore new file mode 100644 index 000000000..6bfa65a8f --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/.dockerignore @@ -0,0 +1,57 @@ +# Build outputs +bin/ +obj/ +out/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +*.sln.docstates +.foundry/ + +# Git +.git/ +.gitignore + +# Documentation and samples (not needed in container) +*.md +*.http + +# Ignore files +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Environment files with secrets +.env +.env.* +*.local +appsettings.*.json +!appsettings.json + +.venv/ +__pycache__/ diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/AzureAIAgentsInWorkflow.csproj b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/AzureAIAgentsInWorkflow.csproj new file mode 100644 index 000000000..dd6ab3c5f --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/AzureAIAgentsInWorkflow.csproj @@ -0,0 +1,19 @@ + + + Exe + net10.0 + enable + enable + true + + + + + + + + + + + + diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Dockerfile b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Dockerfile new file mode 100644 index 000000000..102e36e08 --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Dockerfile @@ -0,0 +1,20 @@ +# Build the application +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /src + +# Copy files from the current directory on the host to the working directory in the container +COPY . . + +RUN dotnet restore +RUN dotnet build -c Release --no-restore +RUN dotnet publish -c Release --no-build -o /app -f net10.0 + +# Run the application +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final +WORKDIR /app + +# Copy everything needed to run the app from the "build" stage. +COPY --from=build /app . + +EXPOSE 8088 +ENTRYPOINT ["dotnet", "AzureAIAgentsInWorkflow.dll"] diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Program.cs b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Program.cs new file mode 100644 index 000000000..c80b373e7 --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/Program.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample demonstrates a multi-agent workflow with Writer and Reviewer agents +// using Azure AI Foundry AIProjectClient and the Agent Framework WorkflowBuilder. + +using Azure.AI.AgentServer.AgentFramework.Extensions; +using Azure.AI.Projects; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Workflows; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") + ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + +Console.WriteLine($"Using Azure AI endpoint: {endpoint}"); +Console.WriteLine($"Using model deployment: {deploymentName}"); + +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); + +// Create Foundry agents +AIAgent writerAgent = await aiProjectClient.CreateAIAgentAsync( + name: "Writer", + model: deploymentName, + instructions: "You are an excellent content writer. You create new content and edit contents based on the feedback."); + +AIAgent reviewerAgent = await aiProjectClient.CreateAIAgentAsync( + name: "Reviewer", + model: deploymentName, + instructions: "You are an excellent content reviewer. Provide actionable feedback to the writer about the provided content. Provide the feedback in the most concise manner possible."); + +try +{ + var workflow = new WorkflowBuilder(writerAgent) + .AddEdge(writerAgent, reviewerAgent) + .Build(); + + Console.WriteLine("Starting Writer-Reviewer Workflow Agent Server on http://localhost:8088"); + await workflow.AsAgent().RunAIAgentAsync(); +} +finally +{ + // Cleanup server-side agents + await aiProjectClient.Agents.DeleteAgentAsync(writerAgent.Name); + await aiProjectClient.Agents.DeleteAgentAsync(reviewerAgent.Name); +} \ No newline at end of file diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/README.md b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/README.md new file mode 100644 index 000000000..7032f0d36 --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/README.md @@ -0,0 +1,144 @@ +**IMPORTANT!** All samples and other resources made available in this GitHub repository ("samples") are designed to assist in accelerating development of agents, solutions, and agent workflows for various scenarios. Review all provided resources and carefully test output behavior in the context of your use case. AI responses may be inaccurate and AI actions should be monitored with human oversight. Learn more in the transparency documents for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note) and [Agent Framework](https://github.com/microsoft/agent-framework/blob/main/TRANSPARENCY_FAQ.md). + +Agents, solutions, or other output you create may be subject to legal and regulatory requirements, may require licenses, or may not be suitable for all industries, scenarios, or use cases. By using any sample, you are acknowledging that any output created using those samples are solely your responsibility, and that you will comply with all applicable laws, regulations, and relevant safety standards, terms of service, and codes of conduct. + +Third-party samples contained in this folder are subject to their own designated terms, and they have not been tested or verified by Microsoft or its affiliates. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Multi-agent workflows** - Orchestrate multiple agents working together + +Code-based agents can execute **any C# code** you write. This sample includes a Writer-Reviewer workflow where two agents collaborate: a Writer creates content and a Reviewer provides feedback. + +The agent is hosted using the [Azure AI AgentServer SDK](https://www.nuget.org/packages/Azure.AI.AgentServer.AgentFramework/) and +deploy it to Microsoft Foundry using the Azure Developer CLI [ai agent](https://aka.ms/azdaiagent/docs) extension. + +## How It Works + +### Multi-Agent Workflow + +This sample creates two agents: + +- **Writer** - An agent that creates and edits content based on feedback +- **Reviewer** - An agent that provides actionable feedback on the content + +The `WorkflowBuilder` connects these agents in a sequential flow: + +1. The Writer receives the initial request and generates content +2. The Reviewer evaluates the content and provides feedback +3. Both agent responses are output to the user + +### Agent Hosting + +The agent is hosted using the [Azure AI AgentServer SDK](https://www.nuget.org/packages/Azure.AI.AgentServer.AgentFramework/), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. This allows interaction with the agent workflow using OpenAI Responses compatible clients. + +### Agent Deployment + +The hosted agent workflow can be seamlessly deployed to Microsoft Foundry using the Azure Developer CLI [ai agent](https://aka.ms/azdaiagent/docs) extension. +The extension builds a container image for the agent, deploys it to Azure Container Instances (ACI), and creates a hosted agent version and deployment on Foundry Agent Service. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Azure AI Foundry Project** + - Project created. + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`) + - Note your project endpoint URL and model deployment name + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **.NET 10.0 SDK or later** + - Verify your version: `dotnet --version` + - Download from [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download) + +### Environment Variables + +**PowerShell:** + +```powershell +# Replace with your actual values +$env:AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +**Bash:** + +```bash +export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Running the Sample + +To run the agent, execute the following command in your terminal: + +```bash +dotnet restore +dotnet build +dotnet run +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +### Interacting with the Agent + +**Run-Requests:** + +You can interact with the agent workflow using: + +- The `run-requests.http` file in this directory to test and prompt the agent +- Any OpenAI Responses compatible client by sending requests to `http://localhost:8088/`. + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "Create a slogan for a new electric SUV that is affordable and fun to drive" + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive","stream":false}' +``` + +You can also use the `run-requests.http` file in this directory with the VS Code REST Client extension. + +The Writer agent will generate content based on your prompt, and the Reviewer agent will provide feedback on the output. + +### Deploying the Agent to Microsoft Foundry + +To deploy your agent to Microsoft Foundry, follow the comprehensive deployment guide at https://aka.ms/azdaiagent/docs + +## Troubleshooting + +### Images built on Apple Silicon or other ARM64 machines do not work on our service + +We **recommend using `azd` cloud build**, which always builds images with the correct architecture. + +If you choose to **build locally**, and your machine is **not `linux/amd64`** (for example, an Apple Silicon Mac), the image will **not be compatible with our service**, causing runtime failures. + +**Fix for local builds** + +Add this line at the top of your `Dockerfile`: + +```dockerfile +FROM --platform=linux/amd64 python:3.12-slim +``` + +This forces the image to be built for the required `amd64` architecture. diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/agent.yaml b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/agent.yaml new file mode 100644 index 000000000..283c1a194 --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/agent.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: AzureAIAgentsInWorkflow +description: > + A multi-agent workflow featuring a Writer and Reviewer that collaborate + to create and refine content. +metadata: + authors: + - Microsoft + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Multi-Agent Workflow + - Writer-Reviewer + - Content Creation +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: AZURE_AI_PROJECT_ENDPOINT + value: ${AZURE_OPENAI_ENDPOINT} + - name: MODEL_DEPLOYMENT_NAME + value: "{{chat}}" +resources: + - name: chat + kind: model + id: gpt-4o-mini diff --git a/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/run-requests.http b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/run-requests.http new file mode 100644 index 000000000..2fcdb2499 --- /dev/null +++ b/samples/csharp/hosted-agents/AgentFramework/AzureAIAgentsInWorkfllow/run-requests.http @@ -0,0 +1,34 @@ +@host = http://localhost:8088 +@endpoint = {{host}}/responses + +### Health Check +GET {{host}}/readiness + +### Simple string input - Content creation request +POST {{endpoint}} +Content-Type: application/json + +{ + "input": "Create a slogan for a new electric SUV that is affordable and fun to drive", + "stream": false +} + +### Explicit input format +POST {{endpoint}} +Content-Type: application/json + +{ + "input": [ + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "Write a short product description for a smart water bottle that tracks hydration" + } + ] + } + ], + "stream": false +} diff --git a/samples/csharp/hosted-agents/AgentWithCustomFramework/SystemUtilityAgent/SystemUtilityAgent.csproj b/samples/csharp/hosted-agents/AgentWithCustomFramework/SystemUtilityAgent/SystemUtilityAgent.csproj index 4d8f573c6..f46080b1d 100644 --- a/samples/csharp/hosted-agents/AgentWithCustomFramework/SystemUtilityAgent/SystemUtilityAgent.csproj +++ b/samples/csharp/hosted-agents/AgentWithCustomFramework/SystemUtilityAgent/SystemUtilityAgent.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.dockerignore b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.dockerignore new file mode 100644 index 000000000..79cc80773 --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.dockerignore @@ -0,0 +1,51 @@ +# Build artifacts +bin/ +obj/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +.foundry/ + +# Source control +.git/ + +# Documentation +README.md + +# Ignore files +.gitignore +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Local development config +appsettings.Development.json +.env + +.venv/ +__pycache__/ diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.env.sample b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.env.sample new file mode 100644 index 000000000..7a7d4d5ec --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/.env.sample @@ -0,0 +1,3 @@ +# IMPORTANT: Never commit .env to version control - add it to .gitignore +PROJECT_ENDPOINT= +MODEL_DEPLOYMENT_NAME= \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/Dockerfile b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/Dockerfile new file mode 100644 index 000000000..0cc939d9b --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY . user_agent/ +WORKDIR /app/user_agent + +RUN if [ -f requirements.txt ]; then \ + pip install -r requirements.txt; \ + else \ + echo "No requirements.txt found"; \ + fi + +EXPOSE 8088 + +CMD ["python", "main.py"] diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/README.md b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/README.md new file mode 100644 index 000000000..beb70ec98 --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/README.md @@ -0,0 +1,159 @@ +**IMPORTANT!** All samples and other resources made available in this GitHub repository ("samples") are designed to assist in accelerating development of agents, solutions, and agent workflows for various scenarios. Review all provided resources and carefully test output behavior in the context of your use case. AI responses may be inaccurate and AI actions should be monitored with human oversight. Learn more in the transparency documents for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note) and [Agent Framework](https://github.com/microsoft/agent-framework/blob/main/TRANSPARENCY_FAQ.md). + +Agents, solutions, or other output you create may be subject to legal and regulatory requirements, may require licenses, or may not be suitable for all industries, scenarios, or use cases. By using any sample, you are acknowledging that any output created using those samples are solely your responsibility, and that you will comply with all applicable laws, regulations, and relevant safety standards, terms of service, and codes of conduct. + +Third-party samples contained in this folder are subject to their own designated terms, and they have not been tested or verified by Microsoft or its affiliates. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Agents in Workflows** - Use AI agents as executors within a workflow pipeline + +Code-based agents can execute **any Python code** you write. This sample includes a multi-agent workflow where Writer and Reviewer agents collaborate to draft content and provide review feedback. + +The agent is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/) and can be deployed to Microsoft Foundry using the Azure Developer CLI. + +## How It Works + +### Agents in Workflows + +This sample demonstrates the integration of AI agents within a workflow pipeline. The workflow operates as follows: + +1. **Writer Agent** - Drafts content +2. **Reviewer Agent** - Reviews the draft and provides concise, actionable feedback + +### Agent Hosting + +The agent workflow is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +### Agent Deployment + +The hosted agent workflow can be deployed to Microsoft Foundry using the Azure Developer CLI [ai agent](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/concepts/hosted-agents?view=foundry&tabs=cli#create-a-hosted-agent) extension. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Azure AI Foundry Project** + - Project created in [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/what-is-foundry?view=foundry#microsoft-foundry-portals) + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`) + - Note your project endpoint URL and model deployment name + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **Python 3.10 or higher** + - Verify your version: `python --version` + - If you have Python 3.9 or older, install a newer version: + - Windows: `winget install Python.Python.3.12` + - macOS: `brew install python@3.12` + - Linux: Use your package manager + +### Environment Variables + +Set the following environment variables (matching `agent.yaml`): + +- `PROJECT_ENDPOINT` - Your Azure AI Foundry project endpoint URL (required) +- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4.1-mini`) + +This sample loads environment variables from a local `.env` file if present. + +Create a `.env` file in this directory with the following content: + +``` +PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +MODEL_DEPLOYMENT_NAME=gpt-4.1-mini +``` + +Or set them via PowerShell: + +```powershell +# Replace with your actual values +$env:PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Setting Up a Virtual Environment + +It's recommended to use a virtual environment to isolate project dependencies: + +**macOS/Linux:** + +```bash +python -m venv .venv +source .venv/bin/activate +``` + +**Windows (PowerShell):** + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +``` + +### Installing Dependencies + +Install the required Python dependencies using pip: + +```bash +pip install -r requirements.txt +``` + +### Running the Sample + +To run the agent, execute the following command in your terminal: + +```powershell +python main.py +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +### Interacting with the Agent + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "Create a slogan for a new electric SUV that is affordable and fun to drive." + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive.","stream":false}' +``` + +### Deploying the Agent to Microsoft Foundry + +To deploy your agent to Microsoft Foundry, follow the comprehensive deployment guide at https://learn.microsoft.com/en-us/azure/ai-foundry/agents/concepts/hosted-agents?view=foundry&tabs=cli + +## Troubleshooting + +### Images built on Apple Silicon or other ARM64 machines do not work on our service + +We **recommend using `azd` cloud build**, which always builds images with the correct architecture. + +If you choose to **build locally**, and your machine is **not `linux/amd64`** (for example, an Apple Silicon Mac), the image will **not be compatible with our service**, causing runtime failures. + +**Fix for local builds** + +Use this command to build the image locally: + +```shell +docker build --platform=linux/amd64 -t image . +``` + +This forces the image to be built for the required `amd64` architecture. diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/agent.yaml b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/agent.yaml new file mode 100644 index 000000000..5734170d8 --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/agent.yaml @@ -0,0 +1,31 @@ +# Unique identifier/name for this agent +name: azure-ai-agents-in-workflow +# Brief description of what this agent does +description: > + A multi-agent workflow featuring a Writer and Reviewer that collaborate + to create and refine content. +metadata: + # Categorization tags for organizing and discovering agents + authors: + - Microsoft Agent Framework Team + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Multi-Agent Workflow + - Writer-Reviewer + - Content Creation +template: + name: azure-ai-agents-in-workflow + # The type of agent - "hosted" for HOBO, "container" for COBO + kind: hosted + protocols: + - protocol: responses + environment_variables: + - name: PROJECT_ENDPOINT + value: ${PROJECT_ENDPOINT} + - name: MODEL_DEPLOYMENT_NAME + value: "{{chat}}" +resources: + - kind: model + id: gpt-4.1-mini + name: chat diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/main.py b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/main.py new file mode 100644 index 000000000..98d604d45 --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/main.py @@ -0,0 +1,85 @@ +import asyncio +import os +from contextlib import asynccontextmanager + +from agent_framework import WorkflowBuilder +from agent_framework.azure import AzureAIAgentClient +from azure.ai.agentserver.agentframework import from_agent_framework +from azure.identity.aio import DefaultAzureCredential, ManagedIdentityCredential +from dotenv import load_dotenv + +load_dotenv(override=True) + +# Configure these for your Foundry project +# Read the explicit variables present in the .env file +PROJECT_ENDPOINT = os.getenv( + "PROJECT_ENDPOINT" +) # e.g., "https://.services.ai.azure.com/api/projects/" +MODEL_DEPLOYMENT_NAME = os.getenv( + "MODEL_DEPLOYMENT_NAME", "gpt-4.1-mini" +) # Your model deployment name e.g., "gpt-4.1-mini" + + +def get_credential(): + """Will use Managed Identity when running in Azure, otherwise falls back to DefaultAzureCredential.""" + return ( + ManagedIdentityCredential() + if os.getenv("MSI_ENDPOINT") + else DefaultAzureCredential() + ) + + +@asynccontextmanager +async def create_agents(): + async with ( + get_credential() as credential, + AzureAIAgentClient( + project_endpoint=PROJECT_ENDPOINT, + model_deployment_name=MODEL_DEPLOYMENT_NAME, + credential=credential, + ) as writer_client, + AzureAIAgentClient( + project_endpoint=PROJECT_ENDPOINT, + model_deployment_name=MODEL_DEPLOYMENT_NAME, + credential=credential, + ) as reviewer_client, + ): + writer = writer_client.create_agent( + name="Writer", + instructions="You are an excellent content writer. You create new content and edit contents based on the feedback.", + ) + reviewer = reviewer_client.create_agent( + name="Reviewer", + instructions="You are an excellent content reviewer. Provide actionable feedback to the writer about the provided content in the most concise manner possible.", + ) + yield writer, reviewer + + +def create_workflow(writer, reviewer): + workflow = ( + WorkflowBuilder(name="Writer-Reviewer") + .register_agent(lambda: writer, name="Writer", output_response=True) + .register_agent(lambda: reviewer, name="Reviewer", output_response=True) + .set_start_executor("Writer") + .add_edge("Writer", "Reviewer") + .build() + ) + return workflow.as_agent() + + +async def main() -> None: + """ + The writer and reviewer multi-agent workflow. + + Environment variables required: + - PROJECT_ENDPOINT: Your Microsoft Foundry project endpoint + - MODEL_DEPLOYMENT_NAME: Your Microsoft Foundry model deployment name + """ + + async with create_agents() as (writer, reviewer): + agent = create_workflow(writer, reviewer) + await from_agent_framework(agent).run_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/requirements.txt b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/requirements.txt new file mode 100644 index 000000000..3d190f704 --- /dev/null +++ b/samples/python/hosted-agents/agent-framework/azure-ai-agents-in-workflow/requirements.txt @@ -0,0 +1 @@ +azure-ai-agentserver-agentframework==1.0.0b15 \ No newline at end of file