diff --git a/infra/abbreviations.json b/infra/abbreviations.json index 93b9565..9d232cc 100644 --- a/infra/abbreviations.json +++ b/infra/abbreviations.json @@ -2,6 +2,8 @@ "ai": { "aiSearch": "srch-", "aiServices": "aisa-", + "aiFoundry": "aif-", + "aiFoundryProject": "aifp-", "aiVideoIndexer": "avi-", "machineLearningWorkspace": "mlw-", "openAIService": "oai-", diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 604966b..740df33 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -11,7 +11,9 @@ param managedIdentityObjectId string param aiServicesEndpoint string param aiServicesKey string param aiServicesId string +param aureaiFoundryEndpoint string +param aiFoundryName string param existingLogAnalyticsWorkspaceId string = '' var useExisting = !empty(existingLogAnalyticsWorkspaceId) @@ -24,15 +26,10 @@ var abbrs = loadJsonContent('./abbreviations.json') var storageName = '${abbrs.storage.storageAccount}${solutionName}' var storageSkuName = 'Standard_LRS' -var aiServicesName = '${abbrs.ai.aiServices}${solutionName}' var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}' var keyvaultName = '${abbrs.security.keyVault}${solutionName}' var location = solutionLocation -var azureAiHubName = '${abbrs.ai.aiHub}${solutionName}' -var aiHubFriendlyName = azureAiHubName -var aiHubDescription = 'AI Hub for KM template' -var aiProjectName = '${abbrs.ai.aiHubProject}${solutionName}' -var aiProjectFriendlyName = aiProjectName + var aiSearchName = '${solutionName}-search' var applicationInsightsName = '${solutionName}-appi' @@ -73,125 +70,6 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { var storageNameCleaned = replace(replace(replace(replace('${storageName}cast', '-', ''), '_', ''), '.', ''),'/', '') - - - -resource storage 'Microsoft.Storage/storageAccounts@2022-09-01' = { - name: storageNameCleaned - location: location - sku: { - name: storageSkuName - } - kind: 'StorageV2' - identity: { - type: 'SystemAssigned' - } - properties: { - accessTier: 'Hot' - allowBlobPublicAccess: false - allowCrossTenantReplication: false - allowSharedKeyAccess: false - encryption: { - keySource: 'Microsoft.Storage' - requireInfrastructureEncryption: false - services: { - blob: { - enabled: true - keyType: 'Account' - } - file: { - enabled: true - keyType: 'Account' - } - queue: { - enabled: true - keyType: 'Service' - } - table: { - enabled: true - keyType: 'Service' - } - } - } - isHnsEnabled: false - isNfsV3Enabled: false - keyPolicy: { - keyExpirationPeriodInDays: 7 - } - largeFileSharesState: 'Disabled' - minimumTlsVersion: 'TLS1_2' - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Allow' - } - supportsHttpsTrafficOnly: true - } -} - -@description('This is the built-in Storage Blob Data Contributor.') -resource blobDataContributor 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { - scope: subscription() - name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' -} - -resource storageroleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(resourceGroup().id, managedIdentityObjectId, blobDataContributor.id) - scope: storage - properties: { - principalId: managedIdentityObjectId - roleDefinitionId: blobDataContributor.id - principalType: 'ServicePrincipal' - } -} - -resource aiHub 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' = { - name: azureAiHubName - location: location - identity: { - type: 'SystemAssigned' - } - properties: { - // organization - friendlyName: aiHubFriendlyName - description: aiHubDescription - - // dependent resources - keyVault: keyVault.id - storageAccount: storage.id - } - kind: 'hub' - - resource aiServicesConnection 'connections@2024-07-01-preview' = { - name: '${azureAiHubName}-connection-AzureOpenAI' - properties: { - category: 'AIServices' - target: aiServicesEndpoint - authType: 'ApiKey' - isSharedToAll: true - credentials: { - key: aiServicesKey - } - metadata: { - ApiType: 'Azure' - ResourceId: aiServicesId - } - } - } -} - -resource aiHubProject 'Microsoft.MachineLearningServices/workspaces@2024-01-01-preview' = { - name: aiProjectName - location: location - kind: 'Project' - identity: { - type: 'SystemAssigned' - } - properties: { - friendlyName: aiProjectFriendlyName - hubResourceId: aiHub.id - } -} - resource tenantIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { parent: keyVault name: 'TENANT-ID' @@ -248,11 +126,11 @@ resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01- } } -resource azureAIProjectConnectionStringEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { +resource azureAIProjectEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { parent: keyVault - name: 'AZURE-AI-PROJECT-CONN-STRING' + name: 'AI-PROJECT-ENDPOINT' properties: { - value: '${split(aiHubProject.properties.discoveryUrl, '/')[2]};${subscription().subscriptionId};${resourceGroup().name};${aiHubProject.name}' + value: aureaiFoundryEndpoint } } @@ -292,7 +170,7 @@ resource cogServiceNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-previ parent: keyVault name: 'COG-SERVICES-NAME' properties: { - value: aiServicesName + value: aiFoundryName } } @@ -323,14 +201,9 @@ resource azureLocatioEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview output keyvaultName string = keyvaultName output keyvaultId string = keyVault.id -output aiServicesName string = aiServicesName output aiSearchName string = aiSearchName -output aiProjectName string = aiHubProject.name output storageAccountName string = storageNameCleaned output logAnalyticsId string = useExisting ? existingLogAnalyticsWorkspace.id : logAnalytics.id -output storageAccountId string = storage.id output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString - -output projectConnectionString string = '${split(aiHubProject.properties.discoveryUrl, '/')[2]};${subscription().subscriptionId};${resourceGroup().name};${aiHubProject.name}' diff --git a/infra/main.bicep b/infra/main.bicep index ec585c5..79933cb 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -57,9 +57,11 @@ var cosmosdbLogContainer = 'cmsalog' var containerName = 'appstorage' var storageSkuName = 'Standard_LRS' var storageContainerName = replace(replace(replace(replace('${ResourcePrefix}cast', '-', ''), '_', ''), '.', ''),'/', '') -var azureAiServicesName = '${abbrs.ai.aiServices}${ResourcePrefix}' - +var aiFoundryName = '${abbrs.ai.aiFoundry}${ResourcePrefix}' +var aiProjectDescription = 'AI foundary project for CPS template' +var aiProjectName = '${abbrs.ai.aiFoundryProject}${ResourcePrefix}' +var aiProjectFriendlyName = aiProjectName var aiModelDeployments = [ { @@ -74,15 +76,39 @@ var aiModelDeployments = [ } ] -resource azureAiServices 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' = { - name: azureAiServicesName +resource azureAiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { + name: aiFoundryName location: AzureAiServiceLocation sku: { name: 'S0' } kind: 'AIServices' + identity: { + type: 'SystemAssigned' + } + properties: { + allowProjectManagement: true + customSubDomainName: aiFoundryName + networkAcls: { + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + } + publicNetworkAccess: 'Enabled' + disableLocalAuth: false + } +} + +resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { + parent: azureAiServices + name: aiProjectName + location: AzureAiServiceLocation + identity: { + type: 'SystemAssigned' + } properties: { - customSubDomainName: azureAiServicesName + description: aiProjectDescription + displayName: aiProjectFriendlyName } } @@ -137,6 +163,7 @@ module azureAifoundry 'deploy_ai_foundry.bicep' = { params: { solutionName: ResourcePrefix solutionLocation: AzureAiServiceLocation + aiFoundryName: aiFoundryName keyVaultName: kvault.outputs.keyvaultName gptModelName: llmModel gptModelVersion: gptModelVersion @@ -145,6 +172,7 @@ module azureAifoundry 'deploy_ai_foundry.bicep' = { aiServicesKey: azureAiServices.listKeys().key1 aiServicesId: azureAiServices.id existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId + aureaiFoundryEndpoint: aiFoundryProject.properties.endpoints['AI Foundry API'] } scope: resourceGroup(resourceGroup().name) } @@ -323,7 +351,7 @@ resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = { } { name: 'AZURE_OPENAI_ENDPOINT' - value: 'https://${azureAifoundry.outputs.aiServicesName}.openai.azure.com/' + value: 'https://${aiFoundryName}.openai.azure.com/' } { name: 'MIGRATOR_AGENT_MODEL_DEPLOY' @@ -359,7 +387,7 @@ resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = { } { name: 'AZURE_AI_AGENT_PROJECT_NAME' - value: azureAifoundry.outputs.aiProjectName + value: aiProjectName } { name: 'AZURE_AI_AGENT_RESOURCE_GROUP_NAME' @@ -370,8 +398,8 @@ resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = { value: subscription().subscriptionId } { - name: 'AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' - value: azureAifoundry.outputs.projectConnectionString + name: 'AI_PROJECT_ENDPOINT' + value: aiFoundryProject.properties.endpoints['AI Foundry API'] } ] resources: { @@ -440,6 +468,7 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { properties: { roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor principalId: containerAppBackend.identity.principalId + principalType: 'ServicePrincipal' } } var openAiContributorRoleId = 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Fixed Role ID for OpenAI Contributor @@ -450,6 +479,7 @@ resource openAiRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-0 properties: { roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', openAiContributorRoleId) // OpenAI Service Contributor principalId: containerAppBackend.identity.principalId + principalType: 'ServicePrincipal' } } @@ -466,20 +496,42 @@ resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2 dependsOn: [azureAifoundry] }] -resource aiHubProject 'Microsoft.MachineLearningServices/workspaces@2024-01-01-preview' existing = { - name: '${abbrs.ai.aiHubProject}${ResourcePrefix}' // aiProjectName must be calculated - available at main start. -} - resource aiDeveloper 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { name: '64702f94-c441-49e6-a78b-ef80e0188fee' } resource aiDeveloperAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(containerAppBackend.name, aiHubProject.id, aiDeveloper.id) - scope: aiHubProject + name: guid(containerAppBackend.name, aiDeveloper.id) + scope: resourceGroup() properties: { roleDefinitionId: aiDeveloper.id principalId: containerAppBackend.identity.principalId + principalType: 'ServicePrincipal' + } +} + + +resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' +} + +resource aiUserAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(containerAppBackend.name, aiUser.id) + scope: resourceGroup() + properties: { + roleDefinitionId: aiUser.id + principalId: containerAppBackend.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource aiUserAccessFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(containerAppBackend.name, aiFoundryProject.id) + scope: resourceGroup() + properties: { + roleDefinitionId: aiUser.id + principalId: containerAppBackend.identity.principalId + principalType: 'ServicePrincipal' } } @@ -508,3 +560,7 @@ module deploymentScriptCLI 'br/public:avm/res/resources/deployment-script:0.5.1' } output AZURE_AIFOUNDRY_NAME string = azureAiServices.name + +output aiFoundryName string = aiFoundryName +output aiProjectName string = aiFoundryProject.name +output projectEndpointString string = aiFoundryProject.properties.endpoints['AI Foundry API'] diff --git a/src/backend/common/config/config.py b/src/backend/common/config/config.py index 24eb2fe..945cc8b 100644 --- a/src/backend/common/config/config.py +++ b/src/backend/common/config/config.py @@ -38,6 +38,7 @@ def __init__(self): self.azure_queue_name = os.getenv("AZURE_QUEUE_NAME") self.azure_openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") + self.ai_project_endpoint = os.getenv("AI_PROJECT_ENDPOINT") self.migrator_agent_model_deploy = os.getenv("MIGRATOR_AGENT_MODEL_DEPLOY") self.picker_agent_model_deploy = os.getenv("PICKER_AGENT_MODEL_DEPLOY") self.fixer_agent_model_deploy = os.getenv("FIXER_AGENT_MODEL_DEPLOY") @@ -62,3 +63,4 @@ def get_azure_credentials(self): app_config = Config() +print(f"[DEBUG] AI_PROJECT_ENDPOINT: '{os.getenv('AI_PROJECT_ENDPOINT')}'") diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 3e01933..61aaae2 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -35,8 +35,8 @@ structlog typing-extensions python-jose[cryptography] passlib[bcrypt] -semantic-kernel[azure]==1.27.2 -openai +semantic-kernel[azure]==1.32.2 +openai==1.84.0 sqlparse sqlglot unittest2 @@ -51,4 +51,4 @@ opentelemetry-api==1.31.1 opentelemetry-semantic-conventions==0.52b1 opentelemetry-instrumentation==0.52b1 azure-monitor-opentelemetry==1.6.8 -azure-ai-projects==1.0.0b9 +azure-ai-projects==1.0.0b11 diff --git a/src/backend/sql_agents/agents/agent_base.py b/src/backend/sql_agents/agents/agent_base.py index 34bb9e8..aec5793 100644 --- a/src/backend/sql_agents/agents/agent_base.py +++ b/src/backend/sql_agents/agents/agent_base.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from typing import Any, Generic, List, Optional, TypeVar, Union -from azure.ai.projects.models import ( +from azure.ai.agents.models import ( ResponseFormatJsonSchema, ResponseFormatJsonSchemaType, ) diff --git a/src/backend/sql_agents/process_batch.py b/src/backend/sql_agents/process_batch.py index b7b8401..cb22efe 100644 --- a/src/backend/sql_agents/process_batch.py +++ b/src/backend/sql_agents/process_batch.py @@ -11,6 +11,7 @@ from azure.identity.aio import DefaultAzureCredential +from common.config.config import app_config from common.models.api import ( FileProcessUpdate, FileRecord, @@ -60,7 +61,7 @@ async def process_batch_async( # Add client and auto cleanup async with ( DefaultAzureCredential() as creds, - AzureAIAgent.create_client(credential=creds) as client, + AzureAIAgent.create_client(credential=creds, endpoint=app_config.ai_project_endpoint) as client, ): # setup all agent settings and agents per batch