diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2911289af..d52654241 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -26,17 +26,19 @@ /samples/REST/quickstart/quickstart-responses.sh @microsoft-foundry/AI-Platform-Docs /samples/csharp/enterprise-agent-tutorial/1-idea-to-prototype/Evaluate/Program.cs @microsoft-foundry/AI-Platform-Docs /samples/csharp/enterprise-agent-tutorial/1-idea-to-prototype/ModernWorkplaceAssistant/Program.cs @microsoft-foundry/AI-Platform-Docs -/samples/csharp/quickstart/quickstart-chat-with-agent.cs @microsoft-foundry/AI-Platform-Docs -/samples/csharp/quickstart/quickstart-create-agent.cs @microsoft-foundry/AI-Platform-Docs -/samples/csharp/quickstart/quickstart-responses.cs @microsoft-foundry/AI-Platform-Docs -/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/ChatWithAgent.java @microsoft-foundry/AI-Platform-Docs -/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateAgent.java @microsoft-foundry/AI-Platform-Docs -/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateResponses.java @microsoft-foundry/AI-Platform-Docs +/samples/csharp/quickstart/agent-service/AgentService.cs @microsoft-foundry/AI-Platform-Docs +/samples/csharp/quickstart/chat-with-agent/quickstart-chat-with-agent.cs @microsoft-foundry/AI-Platform-Docs +/samples/csharp/quickstart/create-agent/quickstart-create-agent.cs @microsoft-foundry/AI-Platform-Docs +/samples/csharp/quickstart/responses/quickstart-responses.cs @microsoft-foundry/AI-Platform-Docs +/samples/java/quickstart/chat-with-agent/src/main/java/com/microsoft/foundry/samples/ChatWithAgent.java @microsoft-foundry/AI-Platform-Docs +/samples/java/quickstart/create-agent/src/main/java/com/microsoft/foundry/samples/CreateAgent.java @microsoft-foundry/AI-Platform-Docs +/samples/java/quickstart/responses/src/main/java/com/microsoft/foundry/samples/CreateResponses.java @microsoft-foundry/AI-Platform-Docs /samples/python/enterprise-agent-tutorial/1-idea-to-prototype/evaluate.py @microsoft-foundry/AI-Platform-Docs /samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py @microsoft-foundry/AI-Platform-Docs /samples/python/quickstart/chat-with-agent/quickstart-chat-with-agent.py @microsoft-foundry/AI-Platform-Docs /samples/python/quickstart/create-agent/quickstart-create-agent.py @microsoft-foundry/AI-Platform-Docs /samples/python/quickstart/responses/quickstart-responses.py @microsoft-foundry/AI-Platform-Docs -/samples/typescript/quickstart/src/quickstart-chat-with-agent.ts @microsoft-foundry/AI-Platform-Docs -/samples/typescript/quickstart/src/quickstart-create-agent.ts @microsoft-foundry/AI-Platform-Docs -/samples/typescript/quickstart/src/quickstart-responses.ts @microsoft-foundry/AI-Platform-Docs +/samples/typescript/quickstart/agent-service/src/quickstart.ts @microsoft-foundry/AI-Platform-Docs +/samples/typescript/quickstart/chat-with-agent/src/quickstart-chat-with-agent.ts @microsoft-foundry/AI-Platform-Docs +/samples/typescript/quickstart/create-agent/src/quickstart-create-agent.ts @microsoft-foundry/AI-Platform-Docs +/samples/typescript/quickstart/responses/src/quickstart-responses.ts @microsoft-foundry/AI-Platform-Docs 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/18-managed-virtual-network-preview/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json index 613e9c073..57c847169 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/azuredeploy.json @@ -2093,8 +2093,7 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/projects/connections', parameters('accountName'), parameters('projectName'), parameters('cosmosDBName'))]" + "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" ] }, { @@ -2112,8 +2111,7 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/projects/connections', parameters('accountName'), parameters('projectName'), parameters('azureStorageName'))]" + "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('accountName'), parameters('projectName'))]" ] }, { diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/ai-project-identity.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/ai-project-identity.bicep index 619f8cc09..90aebfbd3 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/ai-project-identity.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network-preview/modules-network-secured/ai-project-identity.bicep @@ -60,7 +60,6 @@ resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-previ } } - // dependsOn to serialize connection creation and avoid 409 conflicts resource project_connection_azure_storage 'connections@2025-04-01-preview' = { name: azureStorageName properties: { @@ -73,12 +72,8 @@ resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-previ location: storageAccount.location } } - dependsOn: [ - project_connection_cosmosdb_account - ] } - // dependsOn to serialize connection creation and avoid 409 conflicts resource project_connection_azureai_search 'connections@2025-04-01-preview' = { name: aiSearchName properties: { @@ -91,9 +86,6 @@ resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-previ location: searchService.location } } - dependsOn: [ - project_connection_azure_storage - ] } } 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/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/README.md b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/README.md deleted file mode 100644 index fe0943235..000000000 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/README.md +++ /dev/null @@ -1,399 +0,0 @@ -# AI Foundry with Managed VNet - Terraform - -This Terraform configuration deploys an Azure AI Foundry environment with complete private networking and Entra ID authentication, including: - -- Virtual Network with 3 subnets (private endpoints, VMs, Azure Bastion) -- **Azure Bastion Standard** with tunneling, file copy, and IP connect features -- **Windows Server 2025 VM with Entra ID authentication** (no local password required) -- 7 Private DNS Zones for complete private connectivity -- **Azure AI Foundry with Project-level Capability Host** for Agents workloads -- Storage Account, Cosmos DB, and AI Search with private endpoints -- **Managed Virtual Network V2 with outbound rules** for secure agent connectivity -- Role assignments with RBAC and ABAC conditions for secure access - -> **Note:** All resources (including the resource group) are created by Terraform and controlled via feature flags. Networking, storage, Cosmos DB, AI Search, DNS, and VM/Bastion can each be independently enabled or disabled. - -## Prerequisites - -- Azure subscription -- Terraform >= 1.0 -- AzureRM provider ~> 4.0 and AzAPI provider ~> 2.0 -- Azure CLI installed and authenticated (`az login`) -- Appropriate Azure RBAC permissions to create resources - -## Configuration - -1. Copy the example variables file: - ```bash - cp terraform.tfvars.example terraform.tfvars - ``` - -2. Edit `terraform.tfvars` with your values: - - Subscription ID - - Resource group name prefix and location - - Feature flags to enable/disable optional resources - - Network configuration (if networking is enabled) - - VM credentials (if VM is enabled) - -3. Review the variables in `variables.tf` for additional customization options. - -## Deployment - -### Initial Deployment - -1. **Initialize Terraform:** - ```bash - terraform init - ``` - -2. **Review the planned changes:** - ```bash - terraform plan -out=tfplan - ``` - -3. **Apply the configuration:** - ```bash - terraform apply tfplan - ``` - - The deployment will create: - - Virtual Network and subnets - - Private DNS zones - - Storage Account, Cosmos DB, AI Search - - AI Foundry account with managed network - - Private endpoints for all services - - Windows VM with Entra ID authentication - - Azure Bastion Standard - - **Project Capability Host** with proper role assignments - -### Updating Existing Infrastructure - -If you already have infrastructure deployed and want to add the new features: - -1. **Review changes:** - ```bash - terraform plan -out=tfplan - ``` - -2. **Apply updates:** - ```bash - terraform apply tfplan - ``` - - **Expected Changes:** - - **VM Update**: Adds system-assigned identity and Entra ID extension (no VM replacement) - - **Bastion Upgrade**: Changes from Basic to Standard SKU (~6 minutes) - - **New Resources**: - - Project Capability Host - - Cosmos DB Operator role assignment - - Storage Blob Data Owner role (with ABAC condition) - - Cosmos DB Built-in Data Contributor role - - Virtual Machine Administrator Login role - - AADLoginForWindows VM extension - -3. **Important Notes:** - - ⚠️ **VM Size Changes**: If changing VM size, ensure it's compatible (same resource disk configuration) - - ⚠️ **Bastion Upgrade**: Upgrading Bastion from Basic to Standard takes ~6 minutes - - ✅ **No Downtime**: VM remains running during identity and extension installation - - ✅ **Role Propagation**: Role assignments may take 5-10 minutes to take effect - -## Post-Deployment Configuration - -### Verify Capability Host Configuration - -The deployment automatically configures the AI Foundry Project Capability Host with: -- **Storage Connections**: Links to Azure Storage Account -- **Thread Storage Connections**: Links to Cosmos DB for conversation history -- **Vector Store Connections**: Links to AI Search for vector storage - -Verify in Azure Portal: -1. Navigate to AI Foundry resource -2. Go to **Projects** > **firstProject** -3. Check **Capability Hosts** section -4. Verify connections are configured - -### Role Assignments Summary - -The following roles are automatically assigned: - -**Before Capability Host Creation:** -- ✅ Storage Blob Data Contributor (Project → Storage) -- ✅ Search Index Data Contributor (Project → AI Search) -- ✅ Search Service Contributor (Project → AI Search) -- ✅ Cosmos DB Account Reader Role (Project → Cosmos DB) -- ✅ **Cosmos DB Operator** (Project → Cosmos DB) - *Required for capability host* - -**After Capability Host Creation:** -- ✅ **Storage Blob Data Owner** (Project → Storage) - *With ABAC condition for agent containers* -- ✅ **Cosmos DB Built-in Data Contributor** (Project → Cosmos DB) - *For thread storage* - -**VM Access:** -- ✅ **Virtual Machine Administrator Login** (Current User → VM) - -### Additional Outbound Rules (Optional) - -If you need additional managed VNet outbound rules (e.g., for Cosmos DB or AI Search), configure them using Azure CLI: - -**For Cosmos DB:** -```bash -COSMOS_ID=$(terraform output -raw cosmos_account_id) -AI_FOUNDRY_NAME=$(terraform output -raw ai_foundry_name) -RG_NAME=$(terraform output -raw resource_group_name) -SUBSCRIPTION_ID=$(az account show --query id -o tsv) - -az rest --method PUT \ - --uri "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RG_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_FOUNDRY_NAME}/managedNetworks/default/outboundRules/cosmos-pe-rule?api-version=2025-10-01-preview" \ - --body '{ - "properties": { - "type": "PrivateEndpoint", - "destination": { - "serviceResourceId": "'${COSMOS_ID}'", - "subresourceTarget": "Sql" - }, - "category": "UserDefined" - } - }' -``` - -**For AI Search:** -```bash -SEARCH_ID=$(terraform output -raw aisearch_id) - -az rest --method PUT \ - --uri "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RG_NAME}/providers/Microsoft.CognitiveServices/accounts/${AI_FOUNDRY_NAME}/managedNetworks/default/outboundRules/search-pe-rule?api-version=2025-10-01-preview" \ - --body '{ - "properties": { - "type": "PrivateEndpoint", - "destination": { - "serviceResourceId": "'${SEARCH_ID}'", - "subresourceTarget": "searchService" - }, - "category": "UserDefined" - } - }' -``` - -**Note:** The storage outbound rule is already created by Terraform. Additional rules are only needed for specific scenarios. - -## Connecting to the VM - -### Option 1: Azure Portal with Entra ID (Recommended) - -1. Navigate to the Azure Portal -2. Go to the Windows VM resource -3. Click **Connect** > **Bastion** -4. **Authentication Type**: Select **"Entra ID"** (previously Azure AD) -5. Click **Connect** -6. You'll be authenticated using your current Entra ID credentials -7. No username/password required! - -### Option 2: Native RDP Client with Bastion Tunneling - -```powershell -# Get VM resource ID -$vmId = $(terraform output -raw windows_vm_id) -$bastionName = $(terraform output -raw bastion_id | Split-Path -Leaf) -$rgName = $(terraform output -raw resource_group_name) - -# Create tunnel -az network bastion tunnel ` - --name $bastionName ` - --resource-group $rgName ` - --target-resource-id $vmId ` - --resource-port 3389 ` - --port 3389 - -# In another terminal, connect with your Entra ID credentials -mstsc /v:localhost:3389 -``` - -### Option 3: Traditional Username/Password (Fallback) - -If you need to use the local admin account: -1. Click **Connect** > **Bastion** -## Architecture - -``` -┌─────────────────────────────────────────────────────────┐ -│ Virtual Network │ -│ 10.0.0.0/16 │ -│ │ -│ ┌────────────────────────────────────────────┐ │ -│ │ Private Endpoints Subnet (10.0.1.0/24) │ │ -│ │ - AI Foundry Private Endpoint │ │ -│ │ - Storage Blob Private Endpoint │ │ -│ │ - Storage File Private Endpoint │ │ -│ │ - Storage Table Private Endpoint │ │ -│ │ - Storage Queue Private Endpoint │ │ -│ │ - Cosmos DB Private Endpoint │ │ -│ │ - AI Search Private Endpoint │ │ -│ └────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────┐ │ -│ │ VM Subnet (10.0.2.0/24) │ │ -│ │ - Windows Server 2025 VM │ │ -│ │ + System Managed Identity │ │ -│ │ + AADLoginForWindows Extension │ │ -│ │ + No Public IP │ │ -│ └────────────────────────────────────────────┘ │ -│ │ -│ ┌────────────────────────────────────────────┐ │ -│ │ Azure Bastion Subnet (10.0.3.0/26) │ │ -│ │ - Azure Bastion Standard │ │ -│ │ + Tunneling Enabled │ │ -│ │ + File Copy Enabled │ │ -│ │ + IP Connect Enabled │ │ -│ └────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────┘ - │ - │ Private Endpoints - ▼ - ┌───────────────────────────────┐ - │ Azure AI Foundry │ - │ + System Managed Identity │ - │ + Managed Network V2 │ - │ + Project: firstProject │ - │ - Capability Host │ - │ - Storage Connection │ - │ - Cosmos DB Connection │ - │ - AI Search Connection │ - └───────────────────────────────┘ - │ - │ Managed Outbound (PE) - ▼ - ┌───────────────────────────────┐ - │ Storage Account │ - │ (Private Access Only) │ - │ + Blob, File, Table, Queue │ - └───────────────────────────────┘ - - ┌───────────────────────────────┐ - │ Cosmos DB for NoSQL │ - │ (Thread Storage) │ - └───────────────────────────────┘ - - ┌───────────────────────────────┐ - │ AI Search │ - │ (Vector Store) │ - └───────────────────────────────┘ -``` - -## Key Features Comparison - -| Feature | Basic Setup | This Configuration | -|---------|-------------|-------------------| -| VM Authentication | Local Password | ✅ **Entra ID** | -| Bastion SKU | Basic | ✅ **Standard** | -| File Copy via Bastion | ❌ | ✅ | -| Native RDP Tunnel | ❌ | ✅ | -| Capability Host Level | Account | ✅ **Project** | -| Connection References | Generic | ✅ **Explicit** | -| Cosmos DB RBAC | Account Reader | ✅ **+ Operator + Built-in Contributor** | -| Storage RBAC | Blob Contributor | ✅ **+ Blob Owner (ABAC)** | -| Agent Workloads | Not Ready | ✅ **Ready** | - - -## Files - -- `providers.tf` - Provider configuration (AzureRM ~> 4.0, AzAPI ~> 2.0, Random ~> 3.0) -- `variables.tf` - Input variables and feature flags -- `main.tf` - Random suffix, data sources, locals, and resource group -- `network.tf` - VNet, subnets, Bastion, VM, and Entra ID configuration -- `dns.tf` - Private DNS zones and VNet links -- `storage.tf` - Storage account and private endpoints (blob, file, table, queue) -- `cosmos.tf` - Cosmos DB account and private endpoint -- `aisearch.tf` - AI Search service and private endpoint -- `ai-foundry.tf` - AI Foundry account, project, managed network, capability host, connections, and RBAC -- `outputs.tf` - Output values for resource IDs, names, and endpoints -- `terraform.tfvars.example` - Example variable values - -## Notes - -- **Capability Host Architecture**: Uses project-level capability host matching Bicep template -- **Workspace ID Formatting**: Automatically extracts and formats project workspace ID for ABAC conditions -- **Entra ID Authentication**: VM configured with AADLoginForWindows extension -- **Role Assignment Timing**: - - Cosmos DB Operator must be assigned **before** capability host creation - - Storage Blob Data Owner and Cosmos Built-in Contributor assigned **after** -- **ABAC Conditions**: Storage role includes condition for agent-specific containers -- **Bastion Features**: Standard SKU required for tunneling and file copy -- **VM Size Compatibility**: Changing VM sizes requires same resource disk configuration -- **Role Propagation**: RBAC assignments may take 5-10 minutes to fully propagate -- **Managed Network**: Uses Managed Virtual Network V2 with AllowInternetOutbound isolation mode -- **Resource Naming**: All resource names include a random hex suffix (via `random_id`) for uniqueness - -## Known Issues - -### Destroy operation fails with Managed Virtual Network - -There is a known issue when running `terraform destroy` on deployments that use the Managed Virtual Network V2 configuration. The AI Foundry managed network creates internal `serviceAssociationLinks` on agent subnets that are not automatically cleaned up during destroy, which can cause errors such as: - -- `InUseSubnetCannotBeDeleted` — the agent subnet delegation cannot be removed because the managed network still holds a service association link -- Dependent resources may time out waiting for the AI Foundry account deletion to fully propagate - -**Workaround:** After a failed destroy, wait 10-15 minutes for Azure to release the service association links, then purge the soft-deleted cognitive account manually before retrying: - -```bash -SUBSCRIPTION_ID=$(az account show --query id -o tsv) -LOCATION="" -RG_NAME="" -FOUNDRY_NAME="" - -az rest --method DELETE \ - --uri "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/providers/Microsoft.CognitiveServices/locations/${LOCATION}/resourceGroups/${RG_NAME}/deletedAccounts/${FOUNDRY_NAME}?api-version=2021-04-30" -``` - -Then re-run `terraform destroy`. - -## Troubleshooting - -### VM Size Change Error -**Error**: "Unable to resize the VM since changing from resource disk to non-resource disk VM size and vice-versa is not allowed" - -**Solution**: Choose a VM size in the same family: -- From `Standard_B2s` → Use `Standard_B*s` series -- From `Standard_D*s_v5` → Use other D-series v5 SKUs -- Or deallocate, delete, and recreate the VM - -### Entra ID Login Not Working -1. Verify AADLoginForWindows extension is installed: - ```bash - az vm extension list --resource-group --vm-name - ``` -2. Check role assignment exists: - ```bash - az role assignment list --assignee --scope - ``` -3. Wait 5-10 minutes for role propagation - -### Capability Host Connection Issues -1. Verify connections exist in Azure Portal: AI Foundry → Projects → Connections -2. Check role assignments are complete: - ```bash - terraform output - az role assignment list --scope - ``` -3. Ensure Cosmos DB Operator role was assigned before capability host creation - -## Cleanup - -To destroy all resources: - -```bash -terraform destroy -``` - -Confirm by typing `yes` when prompted. - -> ⚠️ **See [Known Issues](#known-issues) above** — the destroy operation may fail due to managed network service association links. Follow the workaround if you encounter `InUseSubnetCannotBeDeleted` errors. - -## Security Considerations - -- All PaaS services have public network access disabled -- VM has no public IP address - access only via Bastion -- Storage account is only accessible via private endpoints -- AI Foundry uses managed network with controlled outbound rules -- All DNS resolution happens via private DNS zones -- Managed identity with RBAC for service-to-service authentication -- Local authentication disabled on the AI Foundry account (`disableLocalAuth = true`) diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/ai-foundry.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/ai-foundry.tf index 9333dc221..89cf99c7e 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/ai-foundry.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/ai-foundry.tf @@ -7,11 +7,6 @@ resource "azapi_resource" "cognitive_account" { schema_validation_enabled = false - response_export_values = [ - "properties.endpoint", - "properties.endpoints" - ] - identity { type = "SystemAssigned" } @@ -23,6 +18,7 @@ resource "azapi_resource" "cognitive_account" { kind = "AIServices" properties = merge( { + allowProjectManagement = true apiProperties = {} customSubDomainName = local.foundry_name disableLocalAuth = true @@ -31,9 +27,6 @@ resource "azapi_resource" "cognitive_account" { virtualNetworkRules = [] ipRules = [] } - allowProjectManagement = true - defaultProject = "firstProject" - associatedProjects = ["firstProject"] networkInjections = [ { scenario = "agent" @@ -67,13 +60,17 @@ resource "azapi_resource" "cognitive_account" { ) } - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) lifecycle { ignore_changes = [ - body["properties"]["restore"] + body["properties"]["restore"], + output ] } @@ -130,6 +127,8 @@ resource "azurerm_private_endpoint" "cognitive_services" { private_dns_zone_ids = [ azurerm_private_dns_zone.cognitive_services[0].id, azurerm_private_dns_zone.openai[0].id, + azurerm_private_dns_zone.aifoundry_api[0].id, + azurerm_private_dns_zone.aifoundry_notebooks[0].id, azurerm_private_dns_zone.aifoundry_services[0].id ] } @@ -154,42 +153,42 @@ resource "azapi_resource" "managed_network" { } } -# Note: Outbound rule for Storage is auto-created by Azure when the connection is established -# The rule will be named: Connection_{storageAccountName}_blob - -# Wait for managed network, project, and AI Search to be fully provisioned before creating connection -resource "time_sleep" "wait_for_aisearch_connection" { - count = var.enable_aisearch ? 1 : 0 - create_duration = "500s" +# Wait for Storage Account to be fully created before creating outbound rule +resource "time_sleep" "wait_storage" { + count = var.enable_storage ? 1 : 0 + create_duration = "10m" depends_on = [ - azapi_resource.managed_network, - azapi_resource.ai_foundry_project, - azurerm_search_service.main + azurerm_storage_account.main, + azurerm_private_endpoint.storage_blob ] } -# Wait for managed network, project, and Cosmos DB to be fully provisioned before creating connection -resource "time_sleep" "wait_for_cosmosdb_connection" { - count = var.enable_cosmos ? 1 : 0 - create_duration = "500s" +# Managed Network Outbound Rule for Storage Account +resource "azapi_resource" "storage_outbound_rule" { + count = var.enable_storage ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview" + name = "storage-blob-rule" + parent_id = azapi_resource.managed_network.id - depends_on = [ - azapi_resource.managed_network, - azapi_resource.ai_foundry_project, - azurerm_cosmosdb_account.main - ] -} + schema_validation_enabled = false -# Wait for managed network, project, and Storage to be fully provisioned before creating connection -resource "time_sleep" "wait_for_storage_connection" { - count = var.enable_storage ? 1 : 0 - create_duration = "500s" + body = { + properties = { + type = "PrivateEndpoint" + destination = { + serviceResourceId = azurerm_storage_account.main[0].id + subresourceTarget = "blob" + } + category = "UserDefined" + } + } depends_on = [ - azapi_resource.managed_network, - azapi_resource.ai_foundry_project, - azurerm_storage_account.main + time_sleep.wait_storage, + azurerm_role_assignment.foundry_network_connection_approver, + azurerm_role_assignment.foundry_storage_blob, + azurerm_role_assignment.foundry_storage_contributor ] } @@ -281,6 +280,19 @@ resource "time_sleep" "wait_project_rbac" { ] } +# Wait for managed network outbound rules to fully provision +# Outbound rules need additional time beyond creation to be in Succeeded state +# Azure managed network provisioning can take several minutes +resource "time_sleep" "wait_outbound_rules" { + create_duration = "600s" + + depends_on = [ + azapi_resource.storage_outbound_rule, + azapi_resource.cosmos_outbound_rule, + azapi_resource.aisearch_outbound_rule + ] +} + # AI Foundry Project Capability Host (matches Bicep implementation) # This configures the capability host at the project level with connection references resource "azapi_resource" "project_capability_host" { @@ -314,11 +326,17 @@ resource "azapi_resource" "project_capability_host" { azurerm_role_assignment.project_search_index, azurerm_role_assignment.project_search_contributor, # Wait for RBAC propagation - time_sleep.wait_project_rbac + time_sleep.wait_project_rbac, + # CRITICAL: All outbound rules must be created AND provisioned before capability host + # The capability host validates that outbound rules exist and are in Succeeded state + azapi_resource.storage_outbound_rule, + azapi_resource.cosmos_outbound_rule, + azapi_resource.aisearch_outbound_rule, + time_sleep.wait_outbound_rules ] } -# Connection: AI Search (created first) +# Connection: AI Search resource "azapi_resource" "conn_aisearch" { count = var.enable_aisearch ? 1 : 0 type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" @@ -339,21 +357,9 @@ resource "azapi_resource" "conn_aisearch" { } } } - - depends_on = [ - time_sleep.wait_for_aisearch_connection - ] -} - -# Wait 60s after AI Search connection before creating the next connection -resource "time_sleep" "wait_after_aisearch_conn" { - count = var.enable_aisearch ? 1 : 0 - create_duration = "60s" - - depends_on = [azapi_resource.conn_aisearch] } -# Connection: Cosmos DB (created second, after AI Search completes + 60s) +# Connection: Cosmos DB resource "azapi_resource" "conn_cosmosdb" { count = var.enable_cosmos ? 1 : 0 type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" @@ -374,22 +380,9 @@ resource "azapi_resource" "conn_cosmosdb" { } } } - - depends_on = [ - time_sleep.wait_for_cosmosdb_connection, - time_sleep.wait_after_aisearch_conn - ] -} - -# Wait 60s after Cosmos DB connection before creating the next connection -resource "time_sleep" "wait_after_cosmosdb_conn" { - count = var.enable_cosmos ? 1 : 0 - create_duration = "60s" - - depends_on = [azapi_resource.conn_cosmosdb] } -# Connection: Storage Account (created third, after Cosmos DB completes + 60s) +# Connection: Storage Account resource "azapi_resource" "conn_storage" { count = var.enable_storage ? 1 : 0 type = "Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview" @@ -410,12 +403,6 @@ resource "azapi_resource" "conn_storage" { } } } - - depends_on = [ - time_sleep.wait_for_storage_connection, - time_sleep.wait_after_aisearch_conn, - time_sleep.wait_after_cosmosdb_conn - ] } # Local variable to format project workspace ID as GUID diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/aisearch.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/aisearch.tf index 2df8eb22a..ebc78870a 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/aisearch.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/aisearch.tf @@ -14,9 +14,12 @@ resource "azurerm_search_service" "main" { type = "SystemAssigned" } - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # Private Endpoint for AI Search @@ -39,13 +42,54 @@ resource "azurerm_private_endpoint" "aisearch" { private_dns_zone_ids = [azurerm_private_dns_zone.aisearch[0].id] } - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) +} + +# Wait for AI Search to be fully created before creating outbound rule + +# Wait for AI Search to be fully created before creating outbound rule +resource "time_sleep" "wait_aisearch" { + count = var.enable_aisearch ? 1 : 0 + create_duration = "10m" + + depends_on = [ + azurerm_search_service.main, + azurerm_private_endpoint.aisearch + ] } -# Note: Outbound rule for AI Search is auto-created by Azure when the connection is established -# The rule will be named: Connection_{searchServiceName}_searchService +# Managed Network Outbound Rule for AI Search Service +resource "azapi_resource" "aisearch_outbound_rule" { + count = var.enable_aisearch ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview" + name = "aisearch-rule" + parent_id = azapi_resource.managed_network.id + + schema_validation_enabled = false + + body = { + properties = { + type = "PrivateEndpoint" + destination = { + serviceResourceId = azurerm_search_service.main[0].id + subresourceTarget = "searchService" + } + category = "UserDefined" + } + } + + depends_on = [ + time_sleep.wait_aisearch, + azurerm_role_assignment.foundry_network_connection_approver, + azurerm_role_assignment.project_search_index, + azurerm_role_assignment.project_search_contributor + ] +} # Role Assignment: Current user needs Search Service Contributor resource "azurerm_role_assignment" "current_user_search_contributor" { diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/cosmos.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/cosmos.tf index ce94abcd2..2f00bb0c0 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/cosmos.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/cosmos.tf @@ -22,9 +22,12 @@ resource "azurerm_cosmosdb_account" "main" { network_acl_bypass_for_azure_services = false local_authentication_disabled = true - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # Private Endpoint for Cosmos DB @@ -47,9 +50,12 @@ resource "azurerm_private_endpoint" "cosmos" { private_dns_zone_ids = [azurerm_private_dns_zone.cosmos[0].id] } - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # Role Assignment: AI Foundry Account Identity - Contributor on Cosmos DB @@ -60,8 +66,45 @@ resource "azurerm_role_assignment" "foundry_cosmos_contributor" { principal_id = azapi_resource.cognitive_account.identity[0].principal_id } -# Note: Outbound rule for Cosmos DB is auto-created by Azure when the connection is established -# The rule will be named: Connection_{cosmosDBName}_sql +# Wait for Cosmos DB to be fully created before creating outbound rule +resource "time_sleep" "wait_cosmos" { + count = var.enable_cosmos ? 1 : 0 + create_duration = "10m" + + depends_on = [ + azurerm_cosmosdb_account.main, + azurerm_private_endpoint.cosmos + ] +} + +# Managed Network Outbound Rule for Cosmos DB Account +resource "azapi_resource" "cosmos_outbound_rule" { + count = var.enable_cosmos ? 1 : 0 + type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview" + name = "cosmos-sql-rule" + parent_id = azapi_resource.managed_network.id + + schema_validation_enabled = false + + body = { + properties = { + type = "PrivateEndpoint" + destination = { + serviceResourceId = azurerm_cosmosdb_account.main[0].id + subresourceTarget = "Sql" + } + category = "UserDefined" + } + } + + depends_on = [ + time_sleep.wait_cosmos, + azurerm_role_assignment.foundry_network_connection_approver, + azurerm_role_assignment.foundry_cosmos_contributor, + azurerm_role_assignment.project_cosmos_reader, + azurerm_role_assignment.project_cosmos_operator + ] +} # Role Assignment: Current user needs Cosmos DB Built-in Data Contributor resource "azurerm_cosmosdb_sql_role_assignment" "current_user" { diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/dns.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/dns.tf index d8f0ba96f..dbc0661c0 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/dns.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/dns.tf @@ -11,18 +11,54 @@ resource "azurerm_private_dns_zone" "storage_blob" { resource_group_name = azurerm_resource_group.main.name } +resource "azurerm_private_dns_zone" "storage_file" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.file.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_table" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.table.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "storage_queue" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.queue.core.windows.net" + resource_group_name = azurerm_resource_group.main.name +} + resource "azurerm_private_dns_zone" "key_vault" { count = var.enable_dns ? 1 : 0 name = "privatelink.vaultcore.azure.net" resource_group_name = azurerm_resource_group.main.name } +resource "azurerm_private_dns_zone" "container_registry" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.azurecr.io" + resource_group_name = azurerm_resource_group.main.name +} + resource "azurerm_private_dns_zone" "openai" { count = var.enable_dns ? 1 : 0 name = "privatelink.openai.azure.com" resource_group_name = azurerm_resource_group.main.name } +resource "azurerm_private_dns_zone" "aifoundry_api" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.api.azureml.ms" + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_private_dns_zone" "aifoundry_notebooks" { + count = var.enable_dns ? 1 : 0 + name = "privatelink.notebooks.azure.net" + resource_group_name = azurerm_resource_group.main.name +} + resource "azurerm_private_dns_zone" "aifoundry_services" { count = var.enable_dns ? 1 : 0 name = "privatelink.services.ai.azure.com" @@ -58,6 +94,30 @@ resource "azurerm_private_dns_zone_virtual_network_link" "storage_blob" { virtual_network_id = azurerm_virtual_network.main[0].id } +resource "azurerm_private_dns_zone_virtual_network_link" "storage_file" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-file-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_file[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_table" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-table-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_table[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "storage_queue" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-queue-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.storage_queue[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + resource "azurerm_private_dns_zone_virtual_network_link" "key_vault" { count = var.enable_dns && var.enable_networking ? 1 : 0 name = "${var.vnet_name}-keyvault-link" @@ -66,6 +126,14 @@ resource "azurerm_private_dns_zone_virtual_network_link" "key_vault" { virtual_network_id = azurerm_virtual_network.main[0].id } +resource "azurerm_private_dns_zone_virtual_network_link" "container_registry" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-acr-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.container_registry[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + resource "azurerm_private_dns_zone_virtual_network_link" "openai" { count = var.enable_dns && var.enable_networking ? 1 : 0 name = "${var.vnet_name}-openai-link" @@ -74,6 +142,22 @@ resource "azurerm_private_dns_zone_virtual_network_link" "openai" { virtual_network_id = azurerm_virtual_network.main[0].id } +resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_api" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aifoundryapi-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aifoundry_api[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + +resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_notebooks" { + count = var.enable_dns && var.enable_networking ? 1 : 0 + name = "${var.vnet_name}-aifoundrynb-link" + resource_group_name = azurerm_resource_group.main.name + private_dns_zone_name = azurerm_private_dns_zone.aifoundry_notebooks[0].name + virtual_network_id = azurerm_virtual_network.main[0].id +} + resource "azurerm_private_dns_zone_virtual_network_link" "aifoundry_services" { count = var.enable_dns && var.enable_networking ? 1 : 0 name = "${var.vnet_name}-aifoundrysvc-link" diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/keyvault.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/keyvault.tf new file mode 100644 index 000000000..164ca781c --- /dev/null +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/keyvault.tf @@ -0,0 +1,135 @@ +# Generate a random password for VM admin +resource "random_password" "vm_admin" { + count = var.enable_vm ? 1 : 0 + length = 24 + special = true + override_special = "!@#$%^&*()-_=+[]{}:?" + min_upper = 2 + min_lower = 2 + min_numeric = 2 + min_special = 2 +} + +# Key Vault +resource "azurerm_key_vault" "main" { + count = var.enable_vm ? 1 : 0 + name = local.keyvault_name + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + soft_delete_retention_days = 7 + purge_protection_enabled = false + + # Enable public network access with network rules to allow specific IPs + public_network_access_enabled = length(var.allowed_public_ips) > 0 ? true : false + + # Network ACLs to restrict access + network_acls { + bypass = "AzureServices" + default_action = length(var.allowed_public_ips) > 0 ? "Deny" : "Allow" + ip_rules = var.allowed_public_ips + } + + # Enable RBAC for access control + rbac_authorization_enabled = true + + tags = { + environment = "lab" + } +} + +# Private Endpoint for Key Vault +resource "azurerm_private_endpoint" "keyvault" { + count = var.enable_vm && var.enable_networking ? 1 : 0 + name = "${azurerm_key_vault.main[0].name}-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_key_vault.main[0].name}-psc" + private_connection_resource_id = azurerm_key_vault.main[0].id + is_manual_connection = false + subresource_names = ["vault"] + } + + private_dns_zone_group { + name = "keyvault-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.key_vault[0].id] + } + + tags = merge( + var.tags, + { + environment = "lab" + } + ) +} + +# Role Assignment: Key Vault Administrator for current user +resource "azurerm_role_assignment" "current_user_keyvault_admin" { + count = var.enable_vm ? 1 : 0 + scope = azurerm_key_vault.main[0].id + role_definition_name = "Key Vault Administrator" + principal_id = data.azurerm_client_config.current.object_id +} + +# Role Assignment: Key Vault Secrets User for VM managed identity +# This allows the VM to read secrets (useful for future automation scenarios) +resource "azurerm_role_assignment" "vm_keyvault_secrets_user" { + count = var.enable_vm ? 1 : 0 + scope = azurerm_key_vault.main[0].id + role_definition_name = "Key Vault Secrets User" + principal_id = azurerm_windows_virtual_machine.main[0].identity[0].principal_id +} + +# Wait for RBAC propagation for Key Vault +resource "time_sleep" "wait_keyvault_rbac" { + count = var.enable_vm ? 1 : 0 + create_duration = "30s" + + depends_on = [ + azurerm_role_assignment.current_user_keyvault_admin + ] +} + +# Store VM admin password in Key Vault +resource "azurerm_key_vault_secret" "vm_admin_password" { + count = var.enable_vm ? 1 : 0 + name = "vm-admin-password" + value = random_password.vm_admin[0].result + key_vault_id = azurerm_key_vault.main[0].id + + depends_on = [ + time_sleep.wait_keyvault_rbac + ] + + tags = merge( + var.tags, + { + environment = "lab" + purpose = "VM administrator password" + } + ) +} + +# Store VM admin username in Key Vault for reference +resource "azurerm_key_vault_secret" "vm_admin_username" { + count = var.enable_vm ? 1 : 0 + name = "vm-admin-username" + value = var.vm_admin_username + key_vault_id = azurerm_key_vault.main[0].id + + depends_on = [ + time_sleep.wait_keyvault_rbac + ] + + tags = merge( + var.tags, + { + environment = "lab" + purpose = "VM administrator username" + } + ) +} diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/main.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/main.tf index b2cda082d..ffa77dde4 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/main.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/main.tf @@ -11,13 +11,17 @@ locals { storage_name = "st${local.resource_suffix}" aisearch_name = "srch-${local.resource_suffix}" cosmos_name = "cosmos-${local.resource_suffix}" + keyvault_name = "kv-${local.resource_suffix}" } resource "azurerm_resource_group" "main" { name = local.rg_name location = var.location - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/network.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/network.tf index 511a97268..45f9c492f 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/network.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/network.tf @@ -66,9 +66,12 @@ resource "azurerm_bastion_host" "main" { copy_paste_enabled = true file_copy_enabled = true - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # Network Interface for VM @@ -93,7 +96,7 @@ resource "azurerm_windows_virtual_machine" "main" { resource_group_name = azurerm_resource_group.main.name size = "Standard_B2s" admin_username = var.vm_admin_username - admin_password = var.vm_admin_password + admin_password = random_password.vm_admin[0].result network_interface_ids = [ azurerm_network_interface.vm[0].id @@ -124,9 +127,12 @@ resource "azurerm_windows_virtual_machine" "main" { patch_mode = "AutomaticByPlatform" patch_assessment_mode = "AutomaticByPlatform" - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # AAD Login Extension for Windows diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/outputs.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/outputs.tf index 50e2bc931..118b8bb3a 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/outputs.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/outputs.tf @@ -54,6 +54,27 @@ output "windows_vm_computer_name" { value = var.enable_vm ? azurerm_windows_virtual_machine.main[0].computer_name : null } +output "key_vault_id" { + description = "The ID of the Key Vault" + value = var.enable_vm ? azurerm_key_vault.main[0].id : null +} + +output "key_vault_name" { + description = "The name of the Key Vault" + value = var.enable_vm ? azurerm_key_vault.main[0].name : null +} + +output "key_vault_uri" { + description = "The URI of the Key Vault" + value = var.enable_vm ? azurerm_key_vault.main[0].vault_uri : null +} + +output "vm_admin_password_secret_id" { + description = "The Key Vault secret ID containing the VM admin password" + value = var.enable_vm ? azurerm_key_vault_secret.vm_admin_password[0].id : null + sensitive = true +} + output "storage_account_id" { description = "The ID of the storage account" value = var.enable_storage ? azurerm_storage_account.main[0].id : null @@ -106,7 +127,12 @@ output "ai_foundry_name" { output "ai_foundry_endpoint" { description = "The endpoint of the AI Foundry / Cognitive Services account" - value = azapi_resource.cognitive_account.output.properties.endpoint + value = "https://${azapi_resource.cognitive_account.name}.cognitiveservices.azure.com/" +} + +output "ai_foundry_custom_subdomain" { + description = "The custom subdomain name of the AI Foundry account" + value = try(jsondecode(azapi_resource.cognitive_account.output).properties.customSubDomainName, azapi_resource.cognitive_account.name) } output "private_dns_zone_ids" { @@ -114,8 +140,14 @@ output "private_dns_zone_ids" { value = var.enable_dns ? { cognitive_services = azurerm_private_dns_zone.cognitive_services[0].id storage_blob = azurerm_private_dns_zone.storage_blob[0].id + storage_file = azurerm_private_dns_zone.storage_file[0].id + storage_table = azurerm_private_dns_zone.storage_table[0].id + storage_queue = azurerm_private_dns_zone.storage_queue[0].id key_vault = azurerm_private_dns_zone.key_vault[0].id + container_registry = azurerm_private_dns_zone.container_registry[0].id openai = azurerm_private_dns_zone.openai[0].id + aifoundry_api = azurerm_private_dns_zone.aifoundry_api[0].id + aifoundry_notebooks = azurerm_private_dns_zone.aifoundry_notebooks[0].id aifoundry_services = azurerm_private_dns_zone.aifoundry_services[0].id cosmos = azurerm_private_dns_zone.cosmos[0].id aisearch = azurerm_private_dns_zone.aisearch[0].id diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/storage.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/storage.tf index 3e217bb34..8e70eae32 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/storage.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/storage.tf @@ -22,9 +22,12 @@ resource "azurerm_storage_account" "main" { ] } - tags = { - environment = "lab" - } + tags = merge( + var.tags, + { + environment = "lab" + } + ) } # Private Endpoint for Blob @@ -48,6 +51,69 @@ resource "azurerm_private_endpoint" "storage_blob" { } } +# Private Endpoint for File +resource "azurerm_private_endpoint" "storage_file" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-file-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-file-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["file"] + } + + private_dns_zone_group { + name = "file-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_file[0].id] + } +} + +# Private Endpoint for Table +resource "azurerm_private_endpoint" "storage_table" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-table-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-table-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["table"] + } + + private_dns_zone_group { + name = "table-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_table[0].id] + } +} + +# Private Endpoint for Queue +resource "azurerm_private_endpoint" "storage_queue" { + count = var.enable_storage && var.enable_networking ? 1 : 0 + name = "${azurerm_storage_account.main[0].name}-queue-pe" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + subnet_id = azurerm_subnet.private_endpoints[0].id + + private_service_connection { + name = "${azurerm_storage_account.main[0].name}-queue-psc" + private_connection_resource_id = azurerm_storage_account.main[0].id + is_manual_connection = false + subresource_names = ["queue"] + } + + private_dns_zone_group { + name = "queue-dns-zone-group" + private_dns_zone_ids = [azurerm_private_dns_zone.storage_queue[0].id] + } +} + # Role Assignment: Current user needs Storage Blob Data Contributor for Terraform to manage storage resource "azurerm_role_assignment" "current_user_storage_blob" { count = var.enable_storage ? 1 : 0 diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/terraform.tfvars.example b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/terraform.tfvars.example index 4c44047e0..75a56c12d 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/terraform.tfvars.example +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/terraform.tfvars.example @@ -1,9 +1,16 @@ # Example Terraform Variables File # Copy this file to terraform.tfvars and update with your values -subscription_id = "%subscription-id%" +subscription_id = "%subscription_id%" resource_group_name = "foundry-mvnet-rg" location = "eastus2" -foundry_identifier = "foundry-v5" # Change to recreate +foundry_identifier = "foundry-v1" # Avoid issues with soft delete, helps make the resource unique + +# Tags to apply to all resources +tags = { + Environment = "lab" + Project = "AI Foundry" + ManagedBy = "Terraform" +} # Feature Flags - Enable/disable resources enable_networking = true @@ -29,3 +36,7 @@ bastion_name = "foundry-bastion" vm_name = "foundry-vm" vm_admin_username = "azureadmin" +# Key Vault Network Configuration +allowed_public_ips = ["0.0.0.0/32"] # Replace with your public IP in CIDR format (e.g., "1.2.3.4/32") + +# Note: VM admin password is automatically generated and stored in Azure Key Vault diff --git a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/variables.tf b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/variables.tf index ada49ee2c..cd39bb89a 100644 --- a/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/variables.tf +++ b/infrastructure/infrastructure-setup-terraform/18-managed-virtual-network-preview/variables.tf @@ -20,6 +20,18 @@ variable "foundry_identifier" { default = "foundry" } +variable "tags" { + description = "A map of tags to apply to all resources" + type = map(string) + default = {} +} + +variable "allowed_public_ips" { + description = "List of public IP addresses (CIDR format) allowed to access Key Vault" + type = list(string) + default = [] +} + # Feature flags for optional resources variable "enable_networking" { description = "Enable VNet, subnets, and network infrastructure" @@ -116,9 +128,3 @@ variable "vm_admin_username" { type = string sensitive = true } - -variable "vm_admin_password" { - description = "Admin password for the VM" - type = string - sensitive = true -} diff --git a/samples-mistral/python/0000950170-25-100226.pdf b/samples-mistral/python/0000950170-25-100226.pdf new file mode 100644 index 000000000..09918b716 Binary files /dev/null and b/samples-mistral/python/0000950170-25-100226.pdf differ diff --git a/samples-mistral/python/mistral-docai-basics.ipynb b/samples-mistral/python/mistral-docai-basics.ipynb index a80915a5a..5183dd9cc 100644 --- a/samples-mistral/python/mistral-docai-basics.ipynb +++ b/samples-mistral/python/mistral-docai-basics.ipynb @@ -10,7 +10,7 @@ "import base64\n", "import json\n", "import requests\n", - "from IPython.display import display, HTML, Markdown" + "from IPython.display import Markdown, display" ] }, { @@ -28,7 +28,7 @@ "source": [ "Mistral Document AI offers enterprise-level document processing, combining cutting-edge OCR technology with advanced structured data extraction. This notebook showcases a few examples of basic OCR extraction for text and images.\n", "\n", - "We will be using the `mistral-document-ai-2512` model with a few documents and images to show the capabilities of the model.\n", + "We will be using the `mistral-document-ai-2505` model with a few documents and images to show the capabilities of the model.\n", "\n", "> **Note**: The Document AI endpoint on Azure Foundry cannot process sources from external URLSs, instead we show you how to encode documents and images and call the API." ] @@ -158,17 +158,7 @@ "id": "28f46097", "metadata": {}, "source": [ - "Next, we construct the JSON for the request.\n", - "\n", - " **Table formatting** can be toggled between `null`, `markdown`, and `html` via the `table_format` parameter.\n", - "\n", - "- `null` Tables are returned inline as markdown within the extracted page.\n", - "\n", - "- `markdown` Tables are returned as markdown tables separately.\n", - "\n", - "- `html` Tables are returned as HTML tables separately.\n", - "\n", - " Option to extract headers and footers via the extract_header and the extract_footer parameter, when used, the headers and footers content will be provided in the header and footer fields. By default, headers and footers are considered as part of the main content output." + "Next we construct the JSON for the request." ] }, { @@ -184,9 +174,6 @@ " \"type\": \"document_url\",\n", " \"document_url\": f\"data:application/pdf;base64,{encodedDocument}\",\n", " },\n", - " \"table_format\": \"html\",\n", - " \"extract_header\": True, # default is False\n", - " \"extract_footer\": True, # default is False\n", "}" ] }, @@ -258,24 +245,6 @@ "If you are interested in just the text, this works fine. If you need the images along with the text we will need to return the images in the response, and then combine with the markdown text." ] }, - { - "cell_type": "markdown", - "id": "ec3ee177", - "metadata": {}, - "source": [ - "Here's how to extract header in page 6." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5861528f", - "metadata": {}, - "outputs": [], - "source": [ - "display(HTML(documentResponse.json()[\"pages\"][6][\"header\"]))" - ] - }, { "cell_type": "markdown", "id": "f4e65b6f", @@ -306,9 +275,6 @@ " \"document_url\": f\"data:application/pdf;base64,{encodedDocument}\",\n", " },\n", " \"include_image_base64\": \"true\",\n", - " \"table_format\": \"html\",\n", - " \"extract_header\": True, # default is False\n", - " \"extract_footer\": True, # default is False\n", "}" ] }, @@ -383,9 +349,6 @@ " \"type\": \"document_url\",\n", " \"document_url\": f\"data:application/pdf;base64,{ms8kDocument}\",\n", " },\n", - " \"table_format\": \"html\",\n", - " \"extract_header\": True, # default is False\n", - " \"extract_footer\": True, # default is False\n", "}" ] }, @@ -429,24 +392,6 @@ "As observed, the extracted text in the table is accurate and true to the original tabular representation." ] }, - { - "cell_type": "markdown", - "id": "38bf2b39", - "metadata": {}, - "source": [ - "Selecting the header on a page." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a601d9d2", - "metadata": {}, - "outputs": [], - "source": [ - "display(ms8kResponse.json()[\"pages\"][0][\"header\"])" - ] - }, { "cell_type": "markdown", "id": "876805a1", @@ -478,9 +423,6 @@ " \"type\": \"document_url\",\n", " \"document_url\": f\"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{wordDocument}\",\n", " },\n", - " \"table_format\": \"markdown\",\n", - " \"extract_header\": True, # default is False\n", - " \"extract_footer\": True, # default is False\n", "}" ] }, @@ -552,9 +494,6 @@ " },\n", " \"include_image_base64\": \"false\",\n", " \"image_limit\": 0,\n", - " \"table_format\": \"html\",\n", - " \"extract_header\": True, # default is False\n", - " \"extract_footer\": True, # default is False\n", "}" ] }, @@ -601,7 +540,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv (3.13.5)", "language": "python", "name": "python3" }, @@ -614,8 +553,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/samples-mistral/python/samples/0000950170-25-100226.pdf b/samples-mistral/python/samples/0000950170-25-100226.pdf deleted file mode 100644 index 1df5fc7a0..000000000 Binary files a/samples-mistral/python/samples/0000950170-25-100226.pdf and /dev/null differ diff --git a/samples-mistral/python/samples/TranscriptFY25q4.docx b/samples-mistral/python/samples/TranscriptFY25q4.docx deleted file mode 100644 index 81dd18bf1..000000000 Binary files a/samples-mistral/python/samples/TranscriptFY25q4.docx and /dev/null differ diff --git a/samples/cli/finetuning/README.md b/samples/cli/finetuning/README.md index 705485efe..1f0e3549a 100644 --- a/samples/cli/finetuning/README.md +++ b/samples/cli/finetuning/README.md @@ -33,3 +33,4 @@ The `reinforcement` folder contains multiple sample configurations for different - [python-grader/sample_finetuning_rft.yaml](reinforcement/python-grader/sample_finetuning_rft.yaml) — Uses a custom Python script as the grader. - [string-check-grader/sample_finetuning_rft.yaml](reinforcement/string-check-grader/sample_finetuning_rft.yaml) — Uses string equality for grading. - [text-similarity-grader/sample_finetuning_rft.yaml](reinforcement/text-similarity-grader/sample_finetuning_rft.yaml) — Uses text similarity metrics (e.g., fuzzy match, BLEU, ROUGE). +- [multi-grader/sample_finetuning_rft.yaml](reinforcement/multi-grader/sample_finetuning_rft.yaml) — Combines multiple graders with weighted scoring. diff --git a/samples/cli/finetuning/reinforcement/multi-grader/sample_finetuning_rft.yaml b/samples/cli/finetuning/reinforcement/multi-grader/sample_finetuning_rft.yaml new file mode 100644 index 000000000..55edc788a --- /dev/null +++ b/samples/cli/finetuning/reinforcement/multi-grader/sample_finetuning_rft.yaml @@ -0,0 +1,34 @@ +name: rft-multi-grader-cli-demo +description: Template to demonstrate reinforcement fine-tuning with multi grader via CLI +model: o4-mini-2025-04-16 +method: + type: reinforcement + reinforcement: + hyperparameters: + epochs: 3 + batch_size: 8 + learning_rate_multiplier: 1.0 + beta: 0.5 + compute_multiplier: 1.0 + reasoning_effort: high + grader: + type: multi + name: strict_partial_credit + graders: + exact: + type: string_check + name: exact + operation: eq + input: "{{sample.output_text}}" + reference: "{{item.reference_answer}}" + fuzzy: + type: text_similarity + name: fuzzy + evaluation_metric: fuzzy_match + input: "{{sample.output_text}}" + reference: "{{item.reference_answer}}" + calculate_output: "0.9 * exact + 0.1 * fuzzy" +suffix: "rft-multi-trained" +seed: 42 +training_file: local:../data/medmcq_training.jsonl +validation_file: local:../data/medmcq_validation.jsonl diff --git a/samples/typescript/quickstart/.env-template b/samples/csharp/quickstart/agent-service/.env-template similarity index 100% rename from samples/typescript/quickstart/.env-template rename to samples/csharp/quickstart/agent-service/.env-template diff --git a/samples/csharp/quickstart/AgentService.cs b/samples/csharp/quickstart/agent-service/AgentService.cs similarity index 100% rename from samples/csharp/quickstart/AgentService.cs rename to samples/csharp/quickstart/agent-service/AgentService.cs diff --git a/samples/csharp/quickstart/agent-service/agent-service.csproj b/samples/csharp/quickstart/agent-service/agent-service.csproj new file mode 100644 index 000000000..763925e41 --- /dev/null +++ b/samples/csharp/quickstart/agent-service/agent-service.csproj @@ -0,0 +1,14 @@ + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/samples/csharp/quickstart/agent-service/nuget.config b/samples/csharp/quickstart/agent-service/nuget.config new file mode 100644 index 000000000..90a2bf18c --- /dev/null +++ b/samples/csharp/quickstart/agent-service/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/csharp/quickstart/agent-service/sample.yaml b/samples/csharp/quickstart/agent-service/sample.yaml new file mode 100644 index 000000000..93daabf06 --- /dev/null +++ b/samples/csharp/quickstart/agent-service/sample.yaml @@ -0,0 +1,2 @@ +name: Quickstart Agent Service +description: Basic quickstart sample demonstrating Microsoft Foundry combined agent service. diff --git a/samples/csharp/quickstart/chat-with-agent/.env-template b/samples/csharp/quickstart/chat-with-agent/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/csharp/quickstart/chat-with-agent/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/csharp/quickstart/chat-with-agent/chat-with-agent.csproj b/samples/csharp/quickstart/chat-with-agent/chat-with-agent.csproj new file mode 100644 index 000000000..763925e41 --- /dev/null +++ b/samples/csharp/quickstart/chat-with-agent/chat-with-agent.csproj @@ -0,0 +1,14 @@ + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/samples/csharp/quickstart/chat-with-agent/nuget.config b/samples/csharp/quickstart/chat-with-agent/nuget.config new file mode 100644 index 000000000..90a2bf18c --- /dev/null +++ b/samples/csharp/quickstart/chat-with-agent/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/csharp/quickstart/quickstart-chat-with-agent.cs b/samples/csharp/quickstart/chat-with-agent/quickstart-chat-with-agent.cs similarity index 100% rename from samples/csharp/quickstart/quickstart-chat-with-agent.cs rename to samples/csharp/quickstart/chat-with-agent/quickstart-chat-with-agent.cs diff --git a/samples/csharp/quickstart/chat-with-agent/sample.yaml b/samples/csharp/quickstart/chat-with-agent/sample.yaml new file mode 100644 index 000000000..211f6af23 --- /dev/null +++ b/samples/csharp/quickstart/chat-with-agent/sample.yaml @@ -0,0 +1,2 @@ +name: Quickstart Chat with Agent +description: Basic quickstart sample demonstrating Microsoft Foundry chat with Agent. diff --git a/samples/csharp/quickstart/create-agent/.env-template b/samples/csharp/quickstart/create-agent/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/csharp/quickstart/create-agent/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/csharp/quickstart/create-agent/create-agent.csproj b/samples/csharp/quickstart/create-agent/create-agent.csproj new file mode 100644 index 000000000..ef72450c2 --- /dev/null +++ b/samples/csharp/quickstart/create-agent/create-agent.csproj @@ -0,0 +1,13 @@ + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/samples/csharp/quickstart/create-agent/nuget.config b/samples/csharp/quickstart/create-agent/nuget.config new file mode 100644 index 000000000..90a2bf18c --- /dev/null +++ b/samples/csharp/quickstart/create-agent/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/csharp/quickstart/quickstart-create-agent.cs b/samples/csharp/quickstart/create-agent/quickstart-create-agent.cs similarity index 100% rename from samples/csharp/quickstart/quickstart-create-agent.cs rename to samples/csharp/quickstart/create-agent/quickstart-create-agent.cs diff --git a/samples/csharp/quickstart/create-agent/sample.yaml b/samples/csharp/quickstart/create-agent/sample.yaml new file mode 100644 index 000000000..60b881e2d --- /dev/null +++ b/samples/csharp/quickstart/create-agent/sample.yaml @@ -0,0 +1,2 @@ +name: Quickstart Create Agent +description: Basic quickstart sample demonstrating Microsoft Foundry agent creation. diff --git a/samples/csharp/quickstart/responses/.env-template b/samples/csharp/quickstart/responses/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/csharp/quickstart/responses/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/csharp/quickstart/responses/nuget.config b/samples/csharp/quickstart/responses/nuget.config new file mode 100644 index 000000000..90a2bf18c --- /dev/null +++ b/samples/csharp/quickstart/responses/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/csharp/quickstart/quickstart-responses.cs b/samples/csharp/quickstart/responses/quickstart-responses.cs similarity index 100% rename from samples/csharp/quickstart/quickstart-responses.cs rename to samples/csharp/quickstart/responses/quickstart-responses.cs diff --git a/samples/csharp/quickstart/responses/responses.csproj b/samples/csharp/quickstart/responses/responses.csproj new file mode 100644 index 000000000..763925e41 --- /dev/null +++ b/samples/csharp/quickstart/responses/responses.csproj @@ -0,0 +1,14 @@ + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/samples/csharp/quickstart/responses/sample.yaml b/samples/csharp/quickstart/responses/sample.yaml new file mode 100644 index 000000000..cae951663 --- /dev/null +++ b/samples/csharp/quickstart/responses/sample.yaml @@ -0,0 +1,2 @@ +name: Quickstart Responses +description: Basic quickstart sample demonstrating Microsoft Foundry responses. diff --git a/samples/java/enterprise-agent-tutorial/1-idea-to-prototype/JAVA_MIGRATION_NOTES.md b/samples/java/enterprise-agent-tutorial/1-idea-to-prototype/JAVA_MIGRATION_NOTES.md index 98a9eab10..019465d08 100644 --- a/samples/java/enterprise-agent-tutorial/1-idea-to-prototype/JAVA_MIGRATION_NOTES.md +++ b/samples/java/enterprise-agent-tutorial/1-idea-to-prototype/JAVA_MIGRATION_NOTES.md @@ -140,7 +140,7 @@ conversation = openai_client.conversations.create( response = openai_client.responses.create( conversation=conversation.id, - extra_body={"agent": AgentReference(name=agent_name).as_dict()} + extra_body={"agent_reference": {"name": agent_name, "type": "agent_reference"}}, ) ``` diff --git a/samples/java/quickstart/chat-with-agent/pom.xml b/samples/java/quickstart/chat-with-agent/pom.xml new file mode 100644 index 000000000..e8bdf65bf --- /dev/null +++ b/samples/java/quickstart/chat-with-agent/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.azure.ai.foundry.samples + quickstart-chat-with-agent + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + azure-sdk-for-java + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + true + true + + + + + + com.azure + azure-ai-agents + 1.0.0-beta.1 + + + com.azure + azure-identity + 1.18.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/samples/java/quickstart/chat-with-agent/sample.yaml b/samples/java/quickstart/chat-with-agent/sample.yaml new file mode 100644 index 000000000..a92890d08 --- /dev/null +++ b/samples/java/quickstart/chat-with-agent/sample.yaml @@ -0,0 +1,7 @@ +name: Quickstart Chat with Agent +description: Basic quickstart sample demonstrating Microsoft Foundry chat with Agent. + +# SKIP: Source imports from unreleased SDKs (openai-java conversations API, +# azure-identity AuthenticationUtil). Re-enable when dependencies are available. +build: "echo 'SKIP: requires unreleased openai-java conversations API'" +validate: "echo 'SKIP: requires unreleased openai-java conversations API'" diff --git a/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/ChatWithAgent.java b/samples/java/quickstart/chat-with-agent/src/main/java/com/azure/ai/agents/ChatWithAgent.java similarity index 100% rename from samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/ChatWithAgent.java rename to samples/java/quickstart/chat-with-agent/src/main/java/com/azure/ai/agents/ChatWithAgent.java diff --git a/samples/java/quickstart/create-agent/pom.xml b/samples/java/quickstart/create-agent/pom.xml new file mode 100644 index 000000000..f8bcb3430 --- /dev/null +++ b/samples/java/quickstart/create-agent/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.azure.ai.foundry.samples + quickstart-create-agent + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + azure-sdk-for-java + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + true + true + + + + + + com.azure + azure-ai-agents + 1.0.0-beta.1 + + + com.azure + azure-core + 1.57.0 + + + com.azure + azure-identity + 1.18.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/samples/java/quickstart/create-agent/sample.yaml b/samples/java/quickstart/create-agent/sample.yaml new file mode 100644 index 000000000..a44016409 --- /dev/null +++ b/samples/java/quickstart/create-agent/sample.yaml @@ -0,0 +1,5 @@ +name: Quickstart Create Agent +description: Basic quickstart sample demonstrating Microsoft Foundry agent creation. + +build: "mvn compile" +validate: "mvn compile" diff --git a/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateAgent.java b/samples/java/quickstart/create-agent/src/main/java/com/azure/ai/agents/CreateAgent.java similarity index 100% rename from samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateAgent.java rename to samples/java/quickstart/create-agent/src/main/java/com/azure/ai/agents/CreateAgent.java diff --git a/samples/java/quickstart/responses/pom.xml b/samples/java/quickstart/responses/pom.xml new file mode 100644 index 000000000..2caf1119c --- /dev/null +++ b/samples/java/quickstart/responses/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.azure.ai.foundry.samples + quickstart-responses + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + azure-sdk-for-java + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + true + true + + + + + + com.azure + azure-ai-agents + 1.0.0-beta.1 + + + com.azure + azure-core + 1.57.0 + + + com.azure + azure-identity + 1.18.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/samples/java/quickstart/responses/sample.yaml b/samples/java/quickstart/responses/sample.yaml new file mode 100644 index 000000000..c37f8c468 --- /dev/null +++ b/samples/java/quickstart/responses/sample.yaml @@ -0,0 +1,5 @@ +name: Quickstart Responses +description: Basic quickstart sample demonstrating Microsoft Foundry responses. + +build: "mvn compile" +validate: "mvn compile" diff --git a/samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateResponses.java b/samples/java/quickstart/responses/src/main/java/com/azure/ai/agents/CreateResponse.java similarity index 100% rename from samples/java/quickstart/src/main/java/com/microsoft/foundry/samples/CreateResponses.java rename to samples/java/quickstart/responses/src/main/java/com/azure/ai/agents/CreateResponse.java diff --git a/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py b/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py index 0f74cb0c7..99aa5e9d9 100644 --- a/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py +++ b/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py @@ -281,7 +281,7 @@ def create_agent_response(agent, message, openai_client): response = openai_client.responses.create( input=message, extra_body={ - "agent": {"name": agent.name, "type": "agent_reference"} + "agent_reference": {"name": agent.name, "type": "agent_reference"} }, ) @@ -302,7 +302,7 @@ def create_agent_response(agent, message, openai_client): input=approval_list, previous_response_id=response.id, extra_body={ - "agent": {"name": agent.name, "type": "agent_reference"} + "agent_reference": {"name": agent.name, "type": "agent_reference"} }, ) diff --git a/samples/python/hosted-agents/agent-framework/human-in-the-loop/agent-with-thread-and-hitl/README.md b/samples/python/hosted-agents/agent-framework/human-in-the-loop/agent-with-thread-and-hitl/README.md index 37784a8ed..667a282dc 100644 --- a/samples/python/hosted-agents/agent-framework/human-in-the-loop/agent-with-thread-and-hitl/README.md +++ b/samples/python/hosted-agents/agent-framework/human-in-the-loop/agent-with-thread-and-hitl/README.md @@ -66,8 +66,7 @@ The hosted agent can be seamlessly deployed to Microsoft Foundry using the Azure ## Validate the deployed Agent ```python # Before running the sample: -# pip install --pre azure-ai-projects>=2.0.0b1 -# pip install azure-identity +# pip install --pre azure-ai-projects>=2.0.0b4 from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient @@ -94,7 +93,7 @@ conversation = openai_client.conversations.create() response = openai_client.responses.create( input="Add a dentist appointment on March 15th", conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) call_id = "" @@ -116,7 +115,7 @@ else: "output": human_response }], conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) print(f"Human response: {human_response}") print(f"Agent response: {response.output_text}") diff --git a/samples/python/hosted-agents/agent-framework/human-in-the-loop/workflow-agent-with-checkpoint-and-hitl/README.md b/samples/python/hosted-agents/agent-framework/human-in-the-loop/workflow-agent-with-checkpoint-and-hitl/README.md index 1df66000a..0450c1dc0 100644 --- a/samples/python/hosted-agents/agent-framework/human-in-the-loop/workflow-agent-with-checkpoint-and-hitl/README.md +++ b/samples/python/hosted-agents/agent-framework/human-in-the-loop/workflow-agent-with-checkpoint-and-hitl/README.md @@ -61,8 +61,7 @@ The same container image can be deployed to Microsoft Foundry with the Azure Dev ## Validate the deployed Agent ```python # Before running the sample: -# pip install --pre azure-ai-projects>=2.0.0b1 -# pip install azure-identity +# pip install --pre azure-ai-projects>=2.0.0b4 from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient @@ -89,7 +88,7 @@ conversation = openai_client.conversations.create() response = openai_client.responses.create( input="Draft a launch plan for a sustainable backpack brand", conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) call_id = "" @@ -120,7 +119,7 @@ else: "output": json.dumps(human_response) }], conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) print(f"Human response: {human_response['feedback']}") print(f"Agent response: {response.output_text}") diff --git a/samples/python/hosted-agents/code-interpreter-custom/main.py b/samples/python/hosted-agents/code-interpreter-custom/main.py index 847f4203f..02559cd5e 100644 --- a/samples/python/hosted-agents/code-interpreter-custom/main.py +++ b/samples/python/hosted-agents/code-interpreter-custom/main.py @@ -50,7 +50,7 @@ # Use the agent to analyze a CSV file and produce a histogram response = openai_client.responses.create( input=f"Please analyze the CSV file at {EXAMPLE_DATA_FILE_URL}. Could you please create bar chart in the TRANSPORTATION sector for the operating profit and provide a file to me?", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) print(f"[Response {response.id}]: {response.output_text}") diff --git a/samples/python/hosted-agents/langgraph/human-in-the-loop/README.md b/samples/python/hosted-agents/langgraph/human-in-the-loop/README.md index 0830043b8..5b3d105f9 100644 --- a/samples/python/hosted-agents/langgraph/human-in-the-loop/README.md +++ b/samples/python/hosted-agents/langgraph/human-in-the-loop/README.md @@ -35,8 +35,7 @@ The extension builds a container image into Azure Container Registry (ACR), and ## Validate the deployed Agent ```python # Before running the sample: -# pip install --pre azure-ai-projects>=2.0.0b1 -# pip install azure-identity +# pip install --pre azure-ai-projects>=2.0.0b4 from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient @@ -63,7 +62,7 @@ conversation = openai_client.conversations.create() response = openai_client.responses.create( input=[{"role": "user", "content": "Ask the user where they are, then look up the weather there."}], conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) call_id = "" @@ -84,7 +83,7 @@ else: "output": json.dumps(human_response) }], conversation=conversation.id, - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) print(f"Human response: {human_response['resume']}") print(f"Agent response: {response.output_text}") diff --git a/samples/python/prompt-agents/code-interpreter-custom/main.py b/samples/python/prompt-agents/code-interpreter-custom/main.py index 847f4203f..02559cd5e 100644 --- a/samples/python/prompt-agents/code-interpreter-custom/main.py +++ b/samples/python/prompt-agents/code-interpreter-custom/main.py @@ -50,7 +50,7 @@ # Use the agent to analyze a CSV file and produce a histogram response = openai_client.responses.create( input=f"Please analyze the CSV file at {EXAMPLE_DATA_FILE_URL}. Could you please create bar chart in the TRANSPORTATION sector for the operating profit and provide a file to me?", - extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, ) print(f"[Response {response.id}]: {response.output_text}") diff --git a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/README.md b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/README.md index 91b3b8764..2fef3fbef 100644 --- a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/README.md +++ b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/README.md @@ -143,8 +143,8 @@ The evaluation script validates: │ ▼ ┌─────────────────────────────────────────────────────────┐ -│ Azure AI Agents SDK v2 │ -│ (@azure/ai-agents-ii) │ +│ Azure AI Projects SDK │ +│ (@azure/ai-projects) │ └─────────────┬───────────────────┬───────────────────────┘ │ │ ▼ ▼ @@ -211,7 +211,7 @@ This tutorial is part of a series: ## Resources - [Azure AI Foundry Documentation](https://learn.microsoft.com/azure/ai-studio/) -- [Azure AI Agents SDK v2](https://www.npmjs.com/package/@azure/ai-agents-ii) +- [Azure AI Projects SDK](https://www.npmjs.com/package/@azure/ai-projects) - [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) ## License diff --git a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/package.json b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/package.json index d76c9c62a..2b77d39dd 100644 --- a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/package.json +++ b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/package.json @@ -5,9 +5,9 @@ "main": "dist/main.js", "scripts": { "build": "tsc", - "start": "node dist/main.js", + "start": "node dist/main-simple.js", "evaluate": "node dist/evaluate.js", - "dev": "tsc && node dist/main.js", + "dev": "tsc && node dist/main-simple.js", "clean": "rimraf dist/" }, "keywords": [ @@ -19,11 +19,9 @@ "author": "Microsoft Corporation", "license": "MIT", "dependencies": { - "@azure/ai-agents-ii": "file:f:/git/agentsv2-preview/javascript/package/ai-agents-ii-2.0.0-alpha.20251020.12.tgz", - "@azure/ai-projects": "^1.0.0-beta.1", - "@azure/identity": "^4.11.1", - "dotenv": "^16.4.5", - "openai": "^6.2.0" + "@azure/ai-projects": "beta", + "@azure/identity": "^4.13.0", + "dotenv": "^16.4.5" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/evaluate.ts b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/evaluate.ts index 202d268d5..5f141fdac 100644 --- a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/evaluate.ts +++ b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/evaluate.ts @@ -10,7 +10,7 @@ import * as fs from "fs"; import * as path from "path"; import { chatWithAssistant } from "./main"; -import { AgentsClient } from "@azure/ai-agents-ii"; +import { AIProjectClient } from "@azure/ai-projects"; import { DefaultAzureCredential } from "@azure/identity"; import { config } from "dotenv"; // @@ -18,7 +18,7 @@ import { config } from "dotenv"; config(); const credential = new DefaultAzureCredential(); -const agentsClient = new AgentsClient( +const project = new AIProjectClient( process.env.PROJECT_ENDPOINT || "", credential ); @@ -135,12 +135,12 @@ function validateResponse( // // -async function runEvaluation(agentId: string): Promise { +async function runEvaluation(agentName: string): Promise { /** - * Run evaluation with test questions using Agent SDK v2. + * Run evaluation with test questions. * * Args: - * agentId: The ID of the agent to evaluate + * agentName: The name of the agent to evaluate * * Returns: * Array of evaluation results for each question @@ -167,7 +167,7 @@ async function runEvaluation(agentId: string): Promise { console.log(`\n📝 Question ${i + 1}/${questions.length} [${testType.toUpperCase()}]`); console.log(` ${q.question.substring(0, 80)}...`); - const { response, status } = await chatWithAssistant(agentId, q.question); + const { response, status } = await chatWithAssistant(agentName, q.question); // Validate response using source-specific checks const { passed, details } = validateResponse( @@ -260,10 +260,11 @@ async function createWorkplaceAssistant() { const instructions = `You are a Modern Workplace Assistant specializing in Azure and Microsoft 365 guidance. Provide comprehensive technical guidance with step-by-step implementation instructions.`; - const agent = await agentsClient.createAgent( - process.env.MODEL_DEPLOYMENT_NAME || "gpt-4o", + const agent = await project.agents.createVersion( + "modern-workplace-assistant-eval", { - name: "Modern Workplace Assistant", + kind: "prompt", + model: process.env.MODEL_DEPLOYMENT_NAME || "gpt-4o", instructions: instructions, } ); @@ -289,7 +290,7 @@ async function main(): Promise { console.log("=".repeat(70)); // Run evaluation - const results = await runEvaluation(agent.id); + const results = await runEvaluation(agent.name!); // Calculate and save results calculateAndSaveResults(results); diff --git a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main-simple.ts b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main-simple.ts index 64fbc35ce..916911d1c 100644 --- a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main-simple.ts +++ b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main-simple.ts @@ -4,9 +4,8 @@ * Simplified version for demonstration purposes */ -import { AgentsClient } from "@azure/ai-agents-ii"; -import { getBearerTokenProvider, DefaultAzureCredential } from "@azure/identity"; -import OpenAI from "openai"; +import { AIProjectClient } from "@azure/ai-projects"; +import { DefaultAzureCredential } from "@azure/identity"; import { config } from "dotenv"; config(); @@ -21,16 +20,15 @@ async function main(): Promise { try { const credential = new DefaultAzureCredential(); - // Create Agents client - const agentsClient = new AgentsClient(projectEndpoint, credential, { - apiVersion: "2025-05-15-preview", - }); + // Create AI Project client + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); console.log("✅ Connected to Azure AI Foundry"); console.log(`🛠️ Creating agent with model: ${modelDeploymentName}`); // Create agent - const agent = await agentsClient.createVersion("workplace-assistant", { + const agent = await project.agents.createVersion("workplace-assistant", { kind: "prompt", model: modelDeploymentName, instructions: "You are a helpful assistant specializing in Azure and Microsoft 365 guidance.", @@ -38,16 +36,6 @@ async function main(): Promise { console.log(`✅ Agent created: ${agent.name} (version ${agent.version})`); - // Create OpenAI client for conversations - const scope = "https://ai.azure.com/.default"; - const azureADTokenProvider = await getBearerTokenProvider(credential, scope); - - const openAIClient = new OpenAI({ - apiKey: azureADTokenProvider, - baseURL: `${projectEndpoint}/openai`, - defaultQuery: { "api-version": "2025-05-15-preview" }, - }); - // Create conversation console.log("\n📝 Creating conversation..."); const conversation = await openAIClient.conversations.create({ @@ -89,7 +77,7 @@ async function main(): Promise { // Cleanup console.log("\n🧹 Cleaning up..."); await openAIClient.conversations.delete(conversation.id); - await agentsClient.deleteVersion(agent.name!, agent.version!); + await project.agents.deleteVersion(agent.name!, agent.version!); console.log("✅ Demo completed successfully!"); } catch (error: any) { diff --git a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main.ts b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main.ts index 92ffe2751..e010dbe3c 100644 --- a/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main.ts +++ b/samples/typescript/enterprise-agent-tutorial/1-idea-to-prototype/src/main.ts @@ -4,7 +4,7 @@ * * This sample demonstrates a complete business scenario using Azure AI Agents SDK v2: * - Agent creation with the new SDK - * - Thread and message management + * - Conversation and response management * - Robust error handling and graceful degradation * * Educational Focus: @@ -21,25 +21,10 @@ */ // -import { AgentsClient } from "@azure/ai-agents-ii"; -import { getBearerTokenProvider, DefaultAzureCredential } from "@azure/identity"; -import OpenAI from "openai"; +import { AIProjectClient } from "@azure/ai-projects"; +import { DefaultAzureCredential } from "@azure/identity"; import { config } from "dotenv"; import * as readline from "readline"; - -// Import for connection resolution -let AIProjectClient: any; -let ConnectionType: any; -let HAS_PROJECT_CLIENT = false; - -try { - const projectModule = require("@azure/ai-projects"); - AIProjectClient = projectModule.AIProjectClient; - ConnectionType = projectModule.ConnectionType; - HAS_PROJECT_CLIENT = true; -} catch (error) { - HAS_PROJECT_CLIENT = false; -} // config(); @@ -51,27 +36,12 @@ config(); // Support default Azure credentials const credential = new DefaultAzureCredential(); -const agentsClient = new AgentsClient( +const project = new AIProjectClient( process.env.PROJECT_ENDPOINT || "", credential, - { - apiVersion: "2025-05-15-preview", - } ); -// Create OpenAI client for conversations -let openAIClient: OpenAI; - -async function initializeOpenAIClient() { - const scope = "https://ai.azure.com/.default"; - const azureADTokenProvider = await getBearerTokenProvider(credential, scope); - - openAIClient = new OpenAI({ - apiKey: azureADTokenProvider, - baseURL: `${process.env.PROJECT_ENDPOINT}/openai`, - defaultQuery: { "api-version": "2025-05-15-preview" }, - }); -} +const openAIClient = project.getOpenAIClient(); console.log(`✅ Connected to Azure AI Foundry: ${process.env.PROJECT_ENDPOINT}`); // @@ -120,15 +90,8 @@ async function createWorkplaceAssistant(): Promise { try { console.log(" 🔍 Resolving connection name to ARM resource ID..."); - if (HAS_PROJECT_CLIENT) { // Use AIProjectClient to list and find the connection - const projectClient = new AIProjectClient( - process.env.PROJECT_ENDPOINT || "", - credential - ); - - // List all connections and find the one we need - const connections = await projectClient.connections.list(); + const connections = await project.connections.list(); let connectionId: string | null = null; for await (const conn of connections) { @@ -151,15 +114,7 @@ async function createWorkplaceAssistant(): Promise { connectionId: connectionId, }; console.log("✅ SharePoint tool configured successfully"); - } else { - throw new Error("azure-ai-projects not installed"); - } } catch (error: any) { - if (error.message === "azure-ai-projects not installed") { - console.log("⚠️ Connection resolution requires @azure/ai-projects package"); - console.log(" Install with: npm install @azure/ai-projects"); - console.log(" Agent will operate without SharePoint access"); - } else { console.log(`⚠️ SharePoint connection unavailable: ${error.message}`); console.log(" Possible causes:"); console.log( @@ -168,7 +123,6 @@ async function createWorkplaceAssistant(): Promise { console.log(" - Insufficient permissions to access the connection"); console.log(" - Connection configuration is incomplete"); console.log(" Agent will operate without SharePoint access"); - } sharepointTool = null; } } else { @@ -287,10 +241,11 @@ RESPONSE STRATEGY: console.log(` Total tools: ${tools.length}`); // Create agent with or without tools - const agent = await agentsClient.createAgent( - process.env.MODEL_DEPLOYMENT_NAME || "gpt-4o", + const agent = await project.agents.createVersion( + "modern-workplace-assistant", { - name: "Modern Workplace Assistant", + kind: "prompt", + model: process.env.MODEL_DEPLOYMENT_NAME || "gpt-4o", instructions: instructions, tools: tools.length > 0 ? tools : undefined, } @@ -301,110 +256,44 @@ RESPONSE STRATEGY: // } -// -class MCPApprovalHandler { - /** - * Handler to automatically approve MCP tool calls. - * - * This demonstrates the MCP approval pattern in Azure AI Agents SDK v2. - * - * Educational Value: - * - Shows proper MCP integration with Agent SDK v2 - * - Demonstrates handler pattern for tool approval - * - Provides foundation for custom approval logic (RBAC, logging, etc.) - */ - - async submitMcpToolApproval(run: any, toolCall: any): Promise { - /** - * Auto-approve MCP tool calls. - * - * In production, you might implement custom approval logic here: - * - RBAC checks (is user authorized for this tool?) - * - Cost controls (has budget limit been reached?) - * - Logging and auditing - * - Interactive approval prompts - */ - return { - toolCallId: toolCall.id, - approve: true, - }; - } -} -// - export async function chatWithAssistant( - agentId: string, + agentName: string, message: string ): Promise { /** - * Execute a conversation with the workplace assistant using Agent SDK v2. + * Execute a conversation with the workplace assistant using conversations/responses API. * * Educational Value: - * - Shows proper conversation management with Agent SDK v2 - * - Demonstrates thread creation and message handling - * - Illustrates MCP approval with handler - * - Includes timeout and error management patterns + * - Shows proper conversation management with the v2 SDK + * - Demonstrates agent reference pattern for responses + * - Includes error management patterns */ try { - // Create a thread for the conversation - const thread = await agentsClient.threads.create(); - - // Create a message in the thread - await agentsClient.messages.create(thread.id, { - role: "user", - content: message, + // Create conversation with user message + const conversation = await openAIClient.conversations.create({ + items: [ + { type: "message", role: "user", content: message }, + ], }); - // - // Use createAndProcess with handler to automatically handle MCP approvals - const handler = new MCPApprovalHandler(); - const run = await agentsClient.runs.createAndProcess( - thread.id, + // Generate response using the agent + const response = await openAIClient.responses.create( { - agentId: agentId, + conversation: conversation.id, }, { - runHandler: handler, - } + body: { agent: { name: agentName, type: "agent_reference" } }, + }, ); - // - // Retrieve messages - if (run.status === "completed") { - const messages = await agentsClient.messages.list(thread.id, { - order: "asc", - }); + // Clean up conversation + await openAIClient.conversations.delete(conversation.id); - // Get the assistant's response (last message from assistant) - const messageList = []; - for await (const msg of messages) { - messageList.push(msg); - } - - for (let i = messageList.length - 1; i >= 0; i--) { - const msg = messageList[i]; - if (msg.role === "assistant" && msg.content && msg.content.length > 0) { - const textContent = msg.content.find((c: any) => c.type === "text"); - if (textContent && textContent.text) { - return { - response: textContent.text.value, - status: "completed", - }; - } - } - } - - return { - response: "No response from assistant", - status: "completed", - }; - } else { - return { - response: `Run ended with status: ${run.status}`, - status: run.status, - }; - } + return { + response: response.output_text, + status: "completed", + }; } catch (error: any) { return { response: `Error in conversation: ${error.message}`, @@ -419,8 +308,8 @@ async function demonstrateBusinessScenarios(agent: Agent): Promise { * * Educational Value: * - Shows real business problems that AI agents can solve - * - Demonstrates proper thread and message management - * - Illustrates Agent SDK v2 conversation patterns + * - Demonstrates conversation and response patterns + * - Illustrates agent reference usage for responses */ const scenarios = [ @@ -467,7 +356,7 @@ async function demonstrateBusinessScenarios(agent: Agent): Promise { // console.log("🤖 ASSISTANT RESPONSE:"); - const { response, status } = await chatWithAssistant(agent.id, scenario.question); + const { response, status } = await chatWithAssistant(agent.name, scenario.question); // if (status === "completed" && response && response.trim().length > 10) { @@ -490,7 +379,7 @@ async function demonstrateBusinessScenarios(agent: Agent): Promise { console.log("\n✅ DEMONSTRATION COMPLETED!"); console.log("🎓 Key Learning Outcomes:"); console.log(" • Agent SDK v2 usage for enterprise AI"); - console.log(" • Proper thread and message management"); + console.log(" • Proper conversation and response management"); console.log(" • Real business value through AI assistance"); console.log( " • Foundation for governance and monitoring (Tutorials 2-3)" @@ -547,7 +436,7 @@ async function interactiveMode(agent: Agent): Promise { } process.stdout.write("\n🤖 Workplace Assistant: "); - const { response, status } = await chatWithAssistant(agent.id, question); + const { response, status } = await chatWithAssistant(agent.name, question); console.log(response); if (status !== "completed") { diff --git a/samples/typescript/quickstart/agent-service/.env-template b/samples/typescript/quickstart/agent-service/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/typescript/quickstart/agent-service/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/typescript/quickstart/agent-service/package.json b/samples/typescript/quickstart/agent-service/package.json new file mode 100644 index 000000000..7ef2b25f5 --- /dev/null +++ b/samples/typescript/quickstart/agent-service/package.json @@ -0,0 +1,22 @@ +{ + "name": "foundry-samples-quickstart-agent-service", + "version": "1.0.0", + "description": "TypeScript AI Foundry Quickstart - Agent Service", + "license": "MIT", + "type": "module", + "main": "dist/quickstart.js", + "scripts": { + "start": "tsx --no-deprecation src/quickstart.ts", + "build": "npx tsc" + }, + "dependencies": { + "@azure/ai-projects": "1.0.0", + "@azure/identity": "^4.11.1", + "dotenv": "^17.2.1" + }, + "devDependencies": { + "@types/node": "^24.3.0", + "tsx": "^4.20.4", + "typescript": "^5.9.2" + } +} diff --git a/samples/typescript/quickstart/agent-service/sample.yaml b/samples/typescript/quickstart/agent-service/sample.yaml new file mode 100644 index 000000000..bb23830ab --- /dev/null +++ b/samples/typescript/quickstart/agent-service/sample.yaml @@ -0,0 +1,8 @@ +name: Quickstart Agent Service +description: Basic quickstart sample demonstrating Microsoft Foundry agent service. + +# SKIP: Source uses unreleased @azure/ai-projects APIs (getOpenAIClient renamed +# to getAzureOpenAIClient, createVersion/deleteVersion not yet on AgentsClient). +# Re-enable when @azure/ai-projects is updated with these APIs. +build: "echo 'SKIP: requires unreleased @azure/ai-projects API'" +validate: "echo 'SKIP: requires unreleased @azure/ai-projects API'" diff --git a/samples/typescript/quickstart/src/quickstart.ts b/samples/typescript/quickstart/agent-service/src/quickstart.ts similarity index 96% rename from samples/typescript/quickstart/src/quickstart.ts rename to samples/typescript/quickstart/agent-service/src/quickstart.ts index 6e10ec247..dfe2b5692 100644 --- a/samples/typescript/quickstart/src/quickstart.ts +++ b/samples/typescript/quickstart/agent-service/src/quickstart.ts @@ -9,7 +9,7 @@ const agentName: string = process.env["AGENT_NAME"] || ""; async function main(): Promise { // Create AI Project client const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); - const openAIClient = await project.getOpenAIClient(); + const openAIClient = project.getOpenAIClient(); // Create agent console.log("Creating agent..."); diff --git a/samples/typescript/quickstart/agent-service/tsconfig.json b/samples/typescript/quickstart/agent-service/tsconfig.json new file mode 100644 index 000000000..077f52cdb --- /dev/null +++ b/samples/typescript/quickstart/agent-service/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "sourceMap": true, + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/samples/typescript/quickstart/chat-with-agent/.env-template b/samples/typescript/quickstart/chat-with-agent/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/typescript/quickstart/chat-with-agent/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/typescript/quickstart/chat-with-agent/package.json b/samples/typescript/quickstart/chat-with-agent/package.json new file mode 100644 index 000000000..1095b8ad3 --- /dev/null +++ b/samples/typescript/quickstart/chat-with-agent/package.json @@ -0,0 +1,22 @@ +{ + "name": "foundry-samples-quickstart-chat-with-agent", + "version": "1.0.0", + "description": "TypeScript AI Foundry Quickstart - Chat with Agent", + "license": "MIT", + "type": "module", + "main": "dist/quickstart-chat-with-agent.js", + "scripts": { + "start": "tsx --no-deprecation src/quickstart-chat-with-agent.ts", + "build": "npx tsc" + }, + "dependencies": { + "@azure/ai-projects": "1.0.0", + "@azure/identity": "^4.11.1", + "dotenv": "^17.2.1" + }, + "devDependencies": { + "@types/node": "^24.3.0", + "tsx": "^4.20.4", + "typescript": "^5.9.2" + } +} diff --git a/samples/typescript/quickstart/chat-with-agent/sample.yaml b/samples/typescript/quickstart/chat-with-agent/sample.yaml new file mode 100644 index 000000000..0057f977d --- /dev/null +++ b/samples/typescript/quickstart/chat-with-agent/sample.yaml @@ -0,0 +1,8 @@ +name: Quickstart Chat with Agent +description: Basic quickstart sample demonstrating Microsoft Foundry chat with Agent. + +# SKIP: Source uses unreleased @azure/ai-projects APIs (getOpenAIClient renamed +# to getAzureOpenAIClient, createVersion/deleteVersion not yet on AgentsClient). +# Re-enable when @azure/ai-projects is updated with these APIs. +build: "echo 'SKIP: requires unreleased @azure/ai-projects API'" +validate: "echo 'SKIP: requires unreleased @azure/ai-projects API'" diff --git a/samples/typescript/quickstart/src/quickstart-chat-with-agent.ts b/samples/typescript/quickstart/chat-with-agent/src/quickstart-chat-with-agent.ts similarity index 97% rename from samples/typescript/quickstart/src/quickstart-chat-with-agent.ts rename to samples/typescript/quickstart/chat-with-agent/src/quickstart-chat-with-agent.ts index b991c4523..f6818fc3d 100644 --- a/samples/typescript/quickstart/src/quickstart-chat-with-agent.ts +++ b/samples/typescript/quickstart/chat-with-agent/src/quickstart-chat-with-agent.ts @@ -7,7 +7,7 @@ const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || " { const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); - const openAIClient = await project.getOpenAIClient(); + const openAIClient = project.getOpenAIClient(); // Create agent console.log("Creating agent..."); diff --git a/samples/typescript/quickstart/chat-with-agent/tsconfig.json b/samples/typescript/quickstart/chat-with-agent/tsconfig.json new file mode 100644 index 000000000..077f52cdb --- /dev/null +++ b/samples/typescript/quickstart/chat-with-agent/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "sourceMap": true, + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/samples/typescript/quickstart/create-agent/.env-template b/samples/typescript/quickstart/create-agent/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/typescript/quickstart/create-agent/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/typescript/quickstart/create-agent/package.json b/samples/typescript/quickstart/create-agent/package.json new file mode 100644 index 000000000..c077cbd15 --- /dev/null +++ b/samples/typescript/quickstart/create-agent/package.json @@ -0,0 +1,22 @@ +{ + "name": "foundry-samples-quickstart-create-agent", + "version": "1.0.0", + "description": "TypeScript AI Foundry Quickstart - Create Agent", + "license": "MIT", + "type": "module", + "main": "dist/quickstart-create-agent.js", + "scripts": { + "start": "tsx --no-deprecation src/quickstart-create-agent.ts", + "build": "npx tsc" + }, + "dependencies": { + "@azure/ai-projects": "1.0.0", + "@azure/identity": "^4.11.1", + "dotenv": "^17.2.1" + }, + "devDependencies": { + "@types/node": "^24.3.0", + "tsx": "^4.20.4", + "typescript": "^5.9.2" + } +} diff --git a/samples/typescript/quickstart/create-agent/sample.yaml b/samples/typescript/quickstart/create-agent/sample.yaml new file mode 100644 index 000000000..f857fc9fd --- /dev/null +++ b/samples/typescript/quickstart/create-agent/sample.yaml @@ -0,0 +1,8 @@ +name: Quickstart Create Agent +description: Basic quickstart sample demonstrating Microsoft Foundry agent creation. + +# SKIP: Source uses unreleased @azure/ai-projects APIs (getOpenAIClient renamed +# to getAzureOpenAIClient, createVersion/deleteVersion not yet on AgentsClient). +# Re-enable when @azure/ai-projects is updated with these APIs. +build: "echo 'SKIP: requires unreleased @azure/ai-projects API'" +validate: "echo 'SKIP: requires unreleased @azure/ai-projects API'" diff --git a/samples/typescript/quickstart/src/quickstart-create-agent.ts b/samples/typescript/quickstart/create-agent/src/quickstart-create-agent.ts similarity index 100% rename from samples/typescript/quickstart/src/quickstart-create-agent.ts rename to samples/typescript/quickstart/create-agent/src/quickstart-create-agent.ts diff --git a/samples/typescript/quickstart/create-agent/tsconfig.json b/samples/typescript/quickstart/create-agent/tsconfig.json new file mode 100644 index 000000000..077f52cdb --- /dev/null +++ b/samples/typescript/quickstart/create-agent/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "sourceMap": true, + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/samples/typescript/quickstart/responses/.env-template b/samples/typescript/quickstart/responses/.env-template new file mode 100644 index 000000000..0ec5c4639 --- /dev/null +++ b/samples/typescript/quickstart/responses/.env-template @@ -0,0 +1,3 @@ +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_FOUNDRY_AGENT_NAME=MyAgent +AZURE_AI_FOUNDRY_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini \ No newline at end of file diff --git a/samples/typescript/quickstart/responses/package.json b/samples/typescript/quickstart/responses/package.json new file mode 100644 index 000000000..9c2e421c1 --- /dev/null +++ b/samples/typescript/quickstart/responses/package.json @@ -0,0 +1,22 @@ +{ + "name": "foundry-samples-quickstart-responses", + "version": "1.0.0", + "description": "TypeScript AI Foundry Quickstart - Responses", + "license": "MIT", + "type": "module", + "main": "dist/quickstart-responses.js", + "scripts": { + "start": "tsx --no-deprecation src/quickstart-responses.ts", + "build": "npx tsc" + }, + "dependencies": { + "@azure/ai-projects": "1.0.0", + "@azure/identity": "^4.11.1", + "dotenv": "^17.2.1" + }, + "devDependencies": { + "@types/node": "^24.3.0", + "tsx": "^4.20.4", + "typescript": "^5.9.2" + } +} diff --git a/samples/typescript/quickstart/responses/sample.yaml b/samples/typescript/quickstart/responses/sample.yaml new file mode 100644 index 000000000..5d60c6d94 --- /dev/null +++ b/samples/typescript/quickstart/responses/sample.yaml @@ -0,0 +1,8 @@ +name: Quickstart Responses +description: Basic quickstart sample demonstrating Microsoft Foundry responses. + +# SKIP: Source uses unreleased @azure/ai-projects APIs (getOpenAIClient renamed +# to getAzureOpenAIClient, createVersion/deleteVersion not yet on AgentsClient). +# Re-enable when @azure/ai-projects is updated with these APIs. +build: "echo 'SKIP: requires unreleased @azure/ai-projects API'" +validate: "echo 'SKIP: requires unreleased @azure/ai-projects API'" diff --git a/samples/typescript/quickstart/src/quickstart-responses.ts b/samples/typescript/quickstart/responses/src/quickstart-responses.ts similarity index 92% rename from samples/typescript/quickstart/src/quickstart-responses.ts rename to samples/typescript/quickstart/responses/src/quickstart-responses.ts index 694f4baf5..ad68301a1 100644 --- a/samples/typescript/quickstart/src/quickstart-responses.ts +++ b/samples/typescript/quickstart/responses/src/quickstart-responses.ts @@ -7,7 +7,7 @@ const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || " { const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); - const openAIClient = await project.getOpenAIClient(); + const openAIClient = project.getOpenAIClient(); const response = await openAIClient.responses.create({ model: deploymentName, input: "What is the size of France in square miles?", diff --git a/samples/typescript/quickstart/responses/tsconfig.json b/samples/typescript/quickstart/responses/tsconfig.json new file mode 100644 index 000000000..077f52cdb --- /dev/null +++ b/samples/typescript/quickstart/responses/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "sourceMap": true, + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}