diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/AgentChainingSample.slnx b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/AgentChainingSample.slnx index 675298e..9eec6e2 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/AgentChainingSample.slnx +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/AgentChainingSample.slnx @@ -2,4 +2,4 @@ - + \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Client.csproj b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Client.csproj index f8e179b..d1b3ffc 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Client.csproj +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Client.csproj @@ -1,5 +1,5 @@ - - + + Exe @@ -8,6 +8,7 @@ enable AgentChainingSample.Client AgentChainingSample.Client + 9d1ef95e-1a0b-440c-96b9-87fad3f1091c @@ -19,8 +20,4 @@ - - - - - + \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Dockerfile b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Dockerfile new file mode 100644 index 0000000..d5f90cd --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Dockerfile @@ -0,0 +1,40 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app + +# Copy csproj and restore as distinct layers +COPY ["Client.csproj", "./"] +COPY ["*.config", "./"] +# Create NuGet.config if it doesn't exist in the build context +RUN if [ ! -f "NuGet.config" ] && [ ! -f "nuget.config" ]; then echo '\n\n \n \n \n \n' > NuGet.config; fi + +# Copy any props files and create Directory.Packages.props if needed +COPY ["*.props", "./"] +# Create Directory.Packages.props if it doesn't exist +RUN if [ ! -f "Directory.Packages.props" ]; then echo '\n \n true\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n' > Directory.Packages.props; fi + +RUN dotnet restore "Client.csproj" + +# Copy source code and models +COPY ["Program.cs", "./"] +COPY ["Models/", "Models/"] + +# Build the application +RUN dotnet build -c Release + +# Publish the application +RUN dotnet publish -c Release -o /app/publish + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +WORKDIR /app +COPY --from=build /app/publish . + +# Expose port 5000 explicitly for web API access +EXPOSE 5000 + +# Configure web server to listen on port 5000 +ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_ENVIRONMENT=Production + +# Set the entrypoint +ENTRYPOINT ["dotnet", "AgentChainingSample.Client.dll"] diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Models/ContentModels.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Models/ContentModels.cs similarity index 96% rename from samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Models/ContentModels.cs rename to samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Models/ContentModels.cs index da2ecec..3cd84ea 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Models/ContentModels.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Models/ContentModels.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace AgentChainingSample.Shared.Models; +namespace AgentChainingSample.Client.Models; /// /// Request to initiate the news article generation workflow @@ -64,6 +64,11 @@ public class ContentWorkflowResult /// public string ArticleBlobUrl { get; set; } = string.Empty; + /// + /// The URL endpoint to view the article online + /// + public string ArticleEndpoint { get; set; } = string.Empty; + /// /// Workflow completion timestamp /// diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Program.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Program.cs index 01c9009..ef3a6c4 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Program.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/Program.cs @@ -1,33 +1,18 @@ using Azure.Identity; +using Microsoft.AspNetCore.Mvc; using Microsoft.DurableTask; using Microsoft.DurableTask.Client; using Microsoft.DurableTask.Client.AzureManaged; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using AgentChainingSample.Shared.Models; +using AgentChainingSample.Client.Models; using System.Text.Json; -using System.Threading.Tasks; -using System.Diagnostics; -// Configure configuration -var configuration = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); +var builder = WebApplication.CreateBuilder(args); -// Configure logging -using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => -{ - builder.AddConsole(); - builder.SetMinimumLevel(LogLevel.Information); -}); - -ILogger logger = loggerFactory.CreateLogger(); -logger.LogInformation("Starting Agent Chaining Sample - Content Creation Client"); +// Add services to the container +builder.Services.AddEndpointsApiExplorer(); -// Get connection string from configuration with fallback to default local emulator connection -string connectionString = configuration["DTS_CONNECTION_STRING"] ?? - "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"; +// Get connection string from configuration +string connectionString = BuildConnectionString(builder.Configuration); // Determine if we're connecting to the local emulator bool isLocalEmulator = connectionString.Contains("localhost"); @@ -36,123 +21,302 @@ if (isLocalEmulator) { AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); - logger.LogInformation("Using local emulator"); + builder.Services.AddLogging(logging => logging.AddConsole().SetMinimumLevel(LogLevel.Information)); + builder.Services.AddDurableTaskClient(options => + { + options.UseDurableTaskScheduler(connectionString); + }); } else { - logger.LogInformation("Using Azure endpoint with DefaultAzure authentication"); + builder.Services.AddLogging(logging => logging.AddConsole().SetMinimumLevel(LogLevel.Information)); + builder.Services.AddDurableTaskClient(options => + { + options.UseDurableTaskScheduler(connectionString); + }); } -logger.LogInformation("Connection string: {ConnectionString}", connectionString); +var app = builder.Build(); +var logger = app.Services.GetRequiredService>(); + +logger.LogInformation("Starting Agent Chaining Sample - Content Creation Client"); +// Log the connection string with sensitive info redacted +string managedIdentityClientId = builder.Configuration["AZURE_MANAGED_IDENTITY_CLIENT_ID"] ?? ""; +string logConnectionString = !string.IsNullOrEmpty(managedIdentityClientId) ? + connectionString.Replace(managedIdentityClientId, "[REDACTED]") : + connectionString; +logger.LogInformation("Connection string: {ConnectionString}", logConnectionString); +logger.LogInformation("TaskHub: {TaskHub}", builder.Configuration["TASKHUB"] ?? builder.Configuration["TASKHUB_NAME"] ?? "default"); +logger.LogInformation("Environment Variables:"); +logger.LogInformation(" TASKHUB: {Value}", builder.Configuration["TASKHUB"]); +logger.LogInformation(" TASKHUB_NAME: {Value}", builder.Configuration["TASKHUB_NAME"]); +logger.LogInformation(" ENDPOINT: {Value}", builder.Configuration["ENDPOINT"]); +logger.LogInformation(" DTS_CONNECTION_STRING: {Value}", builder.Configuration["DTS_CONNECTION_STRING"]); +logger.LogInformation(" DTS_URL: {Value}", builder.Configuration["DTS_URL"]); logger.LogInformation("This sample implements a news article generator workflow with multiple specialized agents"); -// Create the client using DI service provider -ServiceCollection services = new ServiceCollection(); -services.AddLogging(builder => builder.AddConsole()); +// Configure the HTTP request pipeline +app.UseHttpsRedirection(); -// Register the client, which can be used to start orchestrations -services.AddDurableTaskClient(options => +// Define routes +app.MapGet("/", () => { - options.UseDurableTaskScheduler(connectionString); + return "Agent Chaining Content Generator API - Use /api/content to create content"; }); -ServiceProvider serviceProvider = services.BuildServiceProvider(); -DurableTaskClient client = serviceProvider.GetRequiredService(); +// Add a health check endpoint +app.MapGet("/health", () => Results.Ok("Healthy")); -try +// Get status of an orchestration +app.MapGet("/api/content/{instanceId}", async (string instanceId, [FromServices] DurableTaskClient client) => { - // Ask for the news topic - Console.WriteLine("\nEnter a news topic to research and generate an article (or type 'exit' to quit):"); - string? topic = Console.ReadLine(); - - while (!string.IsNullOrWhiteSpace(topic) && topic.ToLower() != "exit") + try { - // Create the request - ContentCreationRequest request = new ContentCreationRequest + var metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true); + if (metadata == null) { - Topic = topic, - RequestId = Guid.NewGuid().ToString() - }; + return Results.NotFound($"No orchestration found with ID: {instanceId}"); + } + + // If the orchestration is complete, return the result + if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed && metadata.ReadOutputAs() is ContentWorkflowResult result) + { + return Results.Ok(result); + } + + // Otherwise return status + return Results.Ok(new + { + InstanceId = metadata.InstanceId, + CreatedAt = metadata.CreatedAt, + LastUpdatedAt = metadata.LastUpdatedAt, + Status = metadata.RuntimeStatus.ToString() + }); + } + catch (Exception ex) + { + logger.LogError(ex, "Error getting orchestration status"); + return Results.Problem("Error retrieving orchestration status"); + } +}); + +// Create a new content generation request +app.MapPost("/api/content", async ([FromBody] ContentCreationRequest request, [FromServices] DurableTaskClient client) => +{ + try + { + if (string.IsNullOrEmpty(request.Topic)) + { + return Results.BadRequest("Topic is required"); + } + + // Set request ID if not provided + request.RequestId ??= Guid.NewGuid().ToString(); // Start the orchestration string instanceId = await client.ScheduleNewOrchestrationInstanceAsync( "ContentCreationOrchestration", request); - logger.LogInformation("Started orchestration with ID: {InstanceId}", instanceId); + logger.LogInformation("Started orchestration with ID: {InstanceId} for topic: {Topic}", instanceId, request.Topic); - // Wait for the orchestration to complete with timeout - logger.LogInformation("Waiting for orchestration to complete..."); - using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); - OrchestrationMetadata metadata = await client.WaitForInstanceCompletionAsync( - instanceId, - getInputsAndOutputs: true, - timeoutCts.Token); + return Results.Accepted($"/api/content/{instanceId}", new + { + InstanceId = instanceId, + Topic = request.Topic, + Status = "Accepted", + StatusQueryGetUri = $"/api/content/{instanceId}" + }); + } + catch (Exception ex) + { + logger.LogError(ex, "Error starting orchestration"); + return Results.Problem("Error starting content generation process"); + } +}); + +// Get all active orchestrations +app.MapGet("/api/content", async ([FromServices] DurableTaskClient client) => +{ + try + { + var query = new OrchestrationQuery + { + PageSize = 100, + // Get only running and pending orchestrations using the correct property + // Check latest API documentation for property name + FetchInputsAndOutputs = false + }; + + var resultList = new List(); + await foreach (var instance in client.GetAllInstancesAsync(query)) + { + resultList.Add(new + { + InstanceId = instance.InstanceId, + CreatedAt = instance.CreatedAt, + LastUpdatedAt = instance.LastUpdatedAt, + Status = instance.RuntimeStatus.ToString() + }); + } - if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed) + return Results.Ok(resultList); + } + catch (Exception ex) + { + logger.LogError(ex, "Error listing orchestrations"); + return Results.Problem("Error retrieving orchestrations"); + } +}); + +// Get the final HTML document for viewing +app.MapGet("/api/content/{instanceId}/document", async (string instanceId, [FromServices] DurableTaskClient client) => +{ + try + { + var metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true); + if (metadata == null) { - ContentWorkflowResult? result = metadata.ReadOutputAs(); - - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("\n===== NEWS ARTICLE GENERATION COMPLETED ====="); - Console.ResetColor(); - - Console.WriteLine($"\nTopic: {result?.Topic}"); - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("\n----- RESEARCH SUMMARY -----"); - Console.ResetColor(); - Console.WriteLine(result?.ResearchData.Summary); - - Console.WriteLine("\nSources Found:"); - foreach (var source in result?.ResearchData.Sources ?? new List()) + return Results.NotFound($"No orchestration found with ID: {instanceId}"); + } + + if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed && metadata.ReadOutputAs() is ContentWorkflowResult result) + { + if (!string.IsNullOrEmpty(result.FinalArticle)) { - Console.WriteLine($"- {source.Title}: {source.Url}"); + return Results.Content(result.FinalArticle, "text/html", System.Text.Encoding.UTF8); } - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("\n----- ARTICLE CONTENT -----"); - Console.ResetColor(); - Console.WriteLine(result?.ArticleContent); - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("\n----- GENERATED IMAGES -----"); - Console.ResetColor(); - foreach (var image in result?.GeneratedImages ?? new List()) + else { - Console.WriteLine($"- {image.Description}"); - Console.WriteLine($" Caption: {image.Caption}"); - Console.WriteLine($" DALL-E Prompt: {image.Prompt}"); - Console.WriteLine(); + return Results.NotFound("Final article content not available"); } - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("\n----- COMPLETE ARTICLE WITH IMAGES -----"); - Console.ResetColor(); - - if (!string.IsNullOrEmpty(result?.ArticleBlobUrl)) + } + else + { + return Results.BadRequest($"Orchestration is not completed. Current status: {metadata.RuntimeStatus}"); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error retrieving document"); + return Results.Problem("Error retrieving document"); + } +}); + +// Download the final HTML document as a file +app.MapGet("/api/content/{instanceId}/download", async (string instanceId, [FromServices] DurableTaskClient client) => +{ + try + { + var metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true); + if (metadata == null) + { + return Results.NotFound($"No orchestration found with ID: {instanceId}"); + } + + if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed && metadata.ReadOutputAs() is ContentWorkflowResult result) + { + if (!string.IsNullOrEmpty(result.FinalArticle)) { - Console.WriteLine($"Article is available online at: {result?.ArticleBlobUrl}"); - Console.WriteLine($"Local HTML file saved at: {result?.ArticleFilePath}"); - Console.WriteLine(); + var contentBytes = System.Text.Encoding.UTF8.GetBytes(result.FinalArticle); + var fileName = $"article-{instanceId}-{DateTime.UtcNow:yyyyMMddHHmmss}.html"; + + return Results.File(contentBytes, "text/html", fileName); } - - Console.WriteLine(result?.FinalArticle); - - // Ask for another topic - Console.WriteLine("\nEnter another news topic to research (or type 'exit' to quit):"); - topic = Console.ReadLine(); + else + { + return Results.NotFound("Final article content not available"); + } + } + else + { + return Results.BadRequest($"Orchestration is not completed. Current status: {metadata.RuntimeStatus}"); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error downloading document"); + return Results.Problem("Error downloading document"); + } +}); + +// Wait for a specific result (polling endpoint) +app.MapGet("/api/content/{instanceId}/wait", async (string instanceId, int timeoutSeconds, [FromServices] DurableTaskClient client) => +{ + try + { + timeoutSeconds = Math.Min(timeoutSeconds, 60); // Cap at 60 seconds max + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); + + OrchestrationMetadata metadata = await client.WaitForInstanceCompletionAsync( + instanceId, + getInputsAndOutputs: true, + timeoutCts.Token); + + if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed) + { + ContentWorkflowResult? result = metadata.ReadOutputAs(); + return Results.Ok(result); } else { - Console.WriteLine($"Orchestration ended with status: {metadata.RuntimeStatus}"); - break; + return Results.Ok(new + { + InstanceId = metadata.InstanceId, + Status = metadata.RuntimeStatus.ToString(), + Message = "Orchestration not yet complete" + }); } } + catch (OperationCanceledException) + { + return Results.Accepted($"/api/content/{instanceId}", new { Message = "Operation timed out, but still processing" }); + } + catch (Exception ex) + { + logger.LogError(ex, "Error waiting for orchestration"); + return Results.Problem("Error waiting for content generation to complete"); + } +}); + +// Start the app +try +{ + logger.LogInformation("Starting web host on port 5000"); + app.Run("http://0.0.0.0:5000"); } catch (Exception ex) { - logger.LogError(ex, "Error in client application"); + logger.LogError(ex, "Error starting client application"); } -logger.LogInformation("Client application stopped"); +/// +/// Builds a connection string for the Durable Task Scheduler from configuration values. +/// +/// The configuration object containing connection settings. +/// A properly formatted connection string. +static string BuildConnectionString(IConfiguration configuration) +{ + // Get connection string from configuration with fallback to default local emulator connection + string connectionString = configuration["ENDPOINT"] ?? + configuration["DTS_CONNECTION_STRING"] ?? + "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"; + + // If we have the endpoint but not a full connection string, construct it + if (connectionString.StartsWith("Endpoint=") && !connectionString.Contains("TaskHub=")) + { + string taskHub = configuration["TASKHUB"] ?? configuration["TASKHUB_NAME"] ?? "default"; + string clientId = configuration["AZURE_MANAGED_IDENTITY_CLIENT_ID"] ?? ""; + + if (!string.IsNullOrEmpty(clientId)) + { + connectionString = $"{connectionString};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={taskHub}"; + } + else + { + connectionString = $"{connectionString};TaskHub={taskHub}"; + } + } + + return connectionString; +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/test.http b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/test.http new file mode 100644 index 0000000..7eba04b --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Client/test.http @@ -0,0 +1,57 @@ +# HTTP REST Client test file for Agent Chaining Sample +# Update the baseUrl and instanceId variables below with your actual deployment values + +@baseUrl = https://your-app-name.region.azurecontainerapps.io +@instanceId = your-instance-id-here + +### Health check +GET {{baseUrl}}/health + +### Get root page +GET {{baseUrl}}/ + +### Create a new content generation request +POST {{baseUrl}}/api/content +Content-Type: application/json + +{ + "topic": "The Rise of Artificial Intelligence in Healthcare", + "requestId": "{{$guid}}" +} + +### Get all active orchestrations +GET {{baseUrl}}/api/content + +### Get status of a specific orchestration +# Replace the instanceId with a real ID from a previous request +# The response will now include an "ArticleEndpoint" property with a direct URL to view the article +GET {{baseUrl}}/api/content/{{instanceId}} + +### View the generated document in browser (NEW!) +# Replace the instanceId with a real ID from a completed request +GET {{baseUrl}}/api/content/{{instanceId}}/document + +### Download the generated document as HTML file (NEW!) +# Replace the instanceId with a real ID from a completed request +GET {{baseUrl}}/api/content/{{instanceId}}/download + +### Wait for orchestration to complete (with timeout) +# Replace the instanceId with a real ID from a previous request +GET {{baseUrl}}/api/content/{{instanceId}}/wait?timeoutSeconds=60 + +### Create another content generation request +POST {{baseUrl}}/api/content +Content-Type: application/json + +{ + "topic": "Future of Sustainable Energy", + "requestId": "{{$guid}}" +} + +### Create content about current technology trends +POST {{baseUrl}}/api/content +Content-Type: application/json + +{ + "topic": "Latest Trends in Quantum Computing" +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Directory.Packages.props b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Directory.Packages.props index c23729c..68130f0 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Directory.Packages.props +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Directory.Packages.props @@ -30,5 +30,9 @@ + + + + diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/README.md b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/README.md index 647b9e9..2717be4 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/README.md +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/README.md @@ -38,7 +38,11 @@ The workflow is orchestrated using the Durable Task SDK, which handles the workf ```bash # Required: Azure AI Projects endpoint - export AGENT_CONNECTION_STRING="https://your-ai-project-endpoint.services.ai.azure.com/api/projects/your-project-id" + export AGENT_CONNECTION_STRING="https://{region}.aiprojects.azure.com/api/projects/{resourceGroup}/{projectName}" + + # Note: The AGENT_CONNECTION_STRING must be in URL format as shown above. + # The format should match: https://{region}.aiprojects.azure.com/api/projects/{resourceGroup}/{projectName} + # Example: https://eastus.aiprojects.azure.com/api/projects/my-resource-group/my-ai-project # Optional: OpenAI model name (defaults to "gpt-4") export OPENAI_DEPLOYMENT_NAME="gpt-4-turbo" @@ -66,13 +70,195 @@ The workflow is orchestrated using the Durable Task SDK, which handles the workf 4. **Generate an Article** - Follow the prompts in the client console to enter a news topic. The application will: + **Option 1: Using the HTTP API** + + The client exposes an HTTP API endpoint at http://localhost:5000. You can use tools like curl or any HTTP client to interact with it: + + ```bash + # Make a request to generate an article + curl -X POST http://localhost:5000/api/articles \ + -H "Content-Type: application/json" \ + -d '{"topic": "renewable energy innovations"}' + + # Check the status of an article generation + curl http://localhost:5000/api/articles/{instanceId} + ``` + + There is also a test.http file that can be used with the VS Code REST Client extension. + + **Option 2: Using the Console Interface** + + Alternatively, follow the prompts in the client console to enter a news topic. The application will: - Research the topic - Generate article content - Create supporting images - Save an HTML file with the complete article When finished, the client will show the path to your generated HTML file (typically in a temp directory like `/var/folders/.../T/article-generator/` on macOS). + +## Deploy to Azure + +This sample includes everything needed to deploy to Azure using the Azure Developer CLI (azd). The deployment will automatically provision all required Azure resources and deploy your application. + +### Prerequisites + +Before deploying to Azure, ensure you have: + +- **Azure CLI**: [Download and install](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) +- **Azure Developer CLI (azd)**: [Download and install](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) +- **Azure Subscription**: You'll need an active Azure subscription +- **Docker**: Required for building container images + +### Step-by-Step Deployment + +1. **Login to Azure** + ```bash + # Login with your Azure account + az login + + # Set your subscription (if you have multiple) + az account set --subscription "your-subscription-id" + ``` + +2. **Initialize the Azure Developer CLI** + ```bash + # Initialize azd in the project directory + azd init + + # This will detect the existing azure.yaml configuration + ``` + +3. **Deploy to Azure** + ```bash + # Deploy the entire application with one command + azd up + ``` + + The `azd up` command will: + - **Provision Infrastructure**: Create all required Azure resources + - **Build Images**: Build Docker containers for the client and worker + - **Deploy Services**: Deploy to Azure Container Apps + - **Configure Networking**: Set up ingress and internal communication + - **Set Environment Variables**: Configure all necessary settings + +### What Gets Deployed + +The deployment creates the following Azure resources: + +| Resource | Type | Purpose | +|----------|------|---------| +| **Resource Group** | `Microsoft.Resources/resourceGroups` | Contains all project resources | +| **Container Apps Environment** | `Microsoft.App/managedEnvironments` | Runtime environment for containers | +| **Container Registry** | `Microsoft.ContainerRegistry/registries` | Stores Docker images | +| **Client App** | `Microsoft.App/containerApps` | Web API frontend (publicly accessible) | +| **Worker App** | `Microsoft.App/containerApps` | Background worker (internal only) | +| **Durable Task Scheduler** | `Microsoft.DurableTask/schedulers` | Orchestration engine | +| **AI Project** | `Microsoft.CognitiveServices/accounts` | Azure AI services | +| **OpenAI Deployments** | `Microsoft.CognitiveServices/accounts/deployments` | GPT-4o-mini and DALL-E 3 models | +| **Log Analytics** | `Microsoft.OperationalInsights/workspaces` | Application monitoring and logs | +| **Managed Identity** | `Microsoft.ManagedIdentity/userAssignedIdentities` | Secure authentication | + +### Post-Deployment + +After successful deployment, you'll see output similar to: + +```bash +SUCCESS: Your application was deployed to Azure in 4 minutes. + +You can view the resources created under the resource group rg- in Azure Portal: +https://portal.azure.com/#@/resource/subscriptions/.../resourceGroups/rg-/overview + +Services: + client https://ca--client..azurecontainerapps.io/ + worker (internal only) +``` + +### Using Your Deployed Application + +1. **Test the API** + + Use the provided client URL to interact with your deployed application: + + ```bash + # Replace with your actual deployment URL + curl -X POST /api/content \ + -H "Content-Type: application/json" \ + -d '{"topic": "Latest developments in AI technology"}' + ``` + +2. **View Generated Articles** + + When an orchestration completes, the response will include an `articleEndpoint` property: + + ```json + { + "instanceId": "abc123...", + "articleEndpoint": "https://ca-xyz-client.region.azurecontainerapps.io/api/content/abc123.../document", + "status": "Completed" + } + ``` + + Open the `articleEndpoint` URL in your browser to view the generated HTML article. + +3. **Monitor Your Application** + + - **Azure Portal**: View resources and metrics + - **Container Apps Logs**: Monitor application logs and performance + - **Log Analytics**: Query detailed telemetry and traces + +### Managing Your Deployment + +**Update your application:** +```bash +# Deploy code changes +azd deploy + +# Update infrastructure and deploy +azd up +``` + +**View deployment status:** +```bash +# Show current deployment information +azd show + +# View environment variables +azd env get-values +``` + +**Clean up resources:** +```bash +# Remove all Azure resources (be careful!) +azd down +``` + +### Troubleshooting Deployment + +**Common Issues:** + +1. **Insufficient permissions**: Ensure your Azure account has `Contributor` or `Owner` role +2. **Resource naming conflicts**: Try a different environment name with `azd env set AZURE_ENV_NAME ` +3. **Deployment timeout**: Container builds can take time; wait for completion +4. **Region availability**: Some Azure AI services may not be available in all regions + +**View detailed logs:** +```bash +# View container app logs +az containerapp logs show --name --resource-group + +# View deployment history +azd deploy --debug +``` + +### Cost Considerations + +The deployed resources will incur costs based on usage: +- **Container Apps**: Pay-per-use scaling model +- **Azure OpenAI**: Token-based pricing for GPT and DALL-E +- **Container Registry**: Storage costs for images +- **Log Analytics**: Data ingestion and retention costs + +Consider setting up [Azure Cost Management](https://learn.microsoft.com/en-us/azure/cost-management-billing/) alerts to monitor spending. ## How It Works The application uses a durable orchestration workflow with four key steps: @@ -111,4 +297,4 @@ Monitor your workflow executions in the Durable Task Scheduler dashboard: - [Durable Task SDK for .NET](https://github.com/microsoft/durabletask-dotnet) - [Azure AI Projects Documentation](https://learn.microsoft.com/azure/ai-services/ai-project/overview) -- [Azure OpenAI Service](https://learn.microsoft.com/azure/ai-services/openai/) +- [Azure OpenAI Service](https://learn.microsoft.com/azure/ai-services/openai/) \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Shared.csproj b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Shared.csproj deleted file mode 100644 index 3e57fa1..0000000 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Shared/Shared.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - net8.0 - enable - enable - AgentChainingSample.Shared - AgentChainingSample.Shared - - - - - - - diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Activities/ContentActivities.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Activities/ContentActivities.cs index 0640214..a543a20 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Activities/ContentActivities.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Activities/ContentActivities.cs @@ -1,4 +1,4 @@ -using AgentChainingSample.Shared.Models; +using AgentChainingSample.Worker.Models; using AgentChainingSample.Services; using Microsoft.DurableTask; using Microsoft.Extensions.Configuration; @@ -31,6 +31,11 @@ public class ArticleResult /// The URL to the article in blob storage (kept for compatibility, always empty) /// public string BlobUrl { get; set; } = string.Empty; + + /// + /// The URL endpoint to view the article online + /// + public string ArticleEndpoint { get; set; } = string.Empty; } /// @@ -132,12 +137,12 @@ public override async Task> RunAsync(TaskActivityContext co /// [DurableTask] public class AssembleFinalArticleActivity(ILogger logger, IConfiguration configuration) - : TaskActivity<(string ArticleContent, List Images), ArticleResult> + : TaskActivity<(string ArticleContent, List Images, string InstanceId), ArticleResult> { // Use system temp directory by default, or the configured directory if specified private readonly string _outputDirectory = configuration["OutputDirectory"] ?? Path.GetTempPath(); - public override async Task RunAsync(TaskActivityContext context, (string ArticleContent, List Images) input) + public override async Task RunAsync(TaskActivityContext context, (string ArticleContent, List Images, string InstanceId) input) { logger.LogInformation("Assembling final article with images in HTML format"); @@ -268,6 +273,9 @@ public override async Task RunAsync(TaskActivityContext context, logger.LogInformation("HTML article saved to file: {FilePath}", localFilePath); + // Construct the article endpoint URL + string articleEndpoint = ConstructArticleEndpoint(input.InstanceId); + logger.LogInformation( "Successfully assembled final article in HTML format of {Length} characters with {ImageCount} images", finalHtml.Length, input.Images.Count); @@ -276,7 +284,8 @@ public override async Task RunAsync(TaskActivityContext context, { HtmlContent = finalHtml, FilePath = localFilePath, - BlobUrl = string.Empty // No blob URL since we're not uploading + BlobUrl = string.Empty, // No blob URL since we're not uploading + ArticleEndpoint = articleEndpoint }; } catch (Exception ex) @@ -286,6 +295,23 @@ public override async Task RunAsync(TaskActivityContext context, } } + /// + /// Constructs the article endpoint URL dynamically for both local and container app environments + /// + private string ConstructArticleEndpoint(string instanceId) + { + // Try to get base URL from environment variables (for container apps) + string? baseUrl = Environment.GetEnvironmentVariable("CLIENT_BASE_URL"); + + // Fallback to local development URL + if (string.IsNullOrEmpty(baseUrl)) + { + baseUrl = "http://localhost:5000"; + } + + return $"{baseUrl}/api/content/{instanceId}/document"; + } + /// /// Extracts paragraphs from HTML content /// @@ -333,4 +359,4 @@ private string SanitizeForFileName(string input) return result; } -} +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Dockerfile b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Dockerfile new file mode 100644 index 0000000..b7d4d62 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Dockerfile @@ -0,0 +1,40 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app + +# Copy csproj files and restore as distinct layers +COPY ["Worker.csproj", "./"] +COPY ["*.config", "./"] +# Create NuGet.config if it doesn't exist in the build context +RUN if [ ! -f "NuGet.config" ] && [ ! -f "nuget.config" ]; then echo '\n\n \n \n \n \n' > NuGet.config; fi + +# Copy any props files and create Directory.Packages.props if needed +COPY ["*.props", "./"] +# Create Directory.Packages.props if it doesn't exist +RUN if [ ! -f "Directory.Packages.props" ]; then echo '\n \n true\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n' > Directory.Packages.props; fi + +# Restore packages +RUN dotnet restore "Worker.csproj" + +# Copy source code +COPY ["Program.cs", "./"] +COPY ["Activities/", "Activities/"] +COPY ["Orchestrations/", "Orchestrations/"] +COPY ["Services/", "Services/"] +COPY ["Models/", "Models/"] + +# Build the application +RUN dotnet build "Worker.csproj" -c Release + +# Publish the application +RUN dotnet publish "Worker.csproj" -c Release -o /app/publish + +# Build runtime image +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS final +WORKDIR /app +COPY --from=build /app/publish . + +# Expose port 8080 explicitly +EXPOSE 8080 + +# Set the entrypoint +ENTRYPOINT ["dotnet", "AgentChainingSample.Worker.dll"] diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Models/ContentModels.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Models/ContentModels.cs new file mode 100644 index 0000000..7d2e288 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Models/ContentModels.cs @@ -0,0 +1,202 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AgentChainingSample.Worker.Models; + +/// +/// Request to initiate the news article generation workflow +/// +public class ContentCreationRequest +{ + /// + /// News topic to research and write about + /// + public string Topic { get; set; } = string.Empty; + + /// + /// Optional client request ID for tracking + /// + public string? RequestId { get; set; } + + /// + /// Timestamp when the request was received + /// + public DateTime RequestTimestamp { get; set; } = DateTime.UtcNow; +} + +/// +/// News article workflow result containing all agent outputs +/// +public class ContentWorkflowResult +{ + /// + /// The original topic + /// + public string Topic { get; set; } = string.Empty; + + /// + /// Research data from the Research Agent with Web Search + /// + public ResearchData ResearchData { get; set; } = new(); + + /// + /// Article content from the Content Generation Agent with Knowledge Files + /// + public string ArticleContent { get; set; } = string.Empty; + + /// + /// Image details from the Image Generation Agent with DALL-E + /// + public List GeneratedImages { get; set; } = new(); + + /// + /// Final article HTML content with images and proper formatting + /// + public string FinalArticle { get; set; } = string.Empty; + + /// + /// Local file path where the HTML article is saved + /// + public string ArticleFilePath { get; set; } = string.Empty; + + /// + /// URL to the article in blob storage (kept for compatibility, always empty) + /// + public string ArticleBlobUrl { get; set; } = string.Empty; + + /// + /// The URL endpoint to view the article online + /// + public string ArticleEndpoint { get; set; } = string.Empty; + + /// + /// Workflow completion timestamp + /// + public DateTime CompletedTimestamp { get; set; } = DateTime.UtcNow; +} + +/// +/// Research data from web search +/// +public class ResearchData +{ + /// + /// Key facts discovered during research + /// + [JsonPropertyName("facts")] + public List Facts { get; set; } = new(); + + /// + /// Relevant sources found during research + /// + [JsonPropertyName("sources")] + public List Sources { get; set; } = new(); + + /// + /// Summary of research findings + /// + [JsonPropertyName("summary")] + public string Summary { get; set; } = string.Empty; + + /// + /// Suggested article angles + /// + [JsonPropertyName("articleAngles")] + public List ArticleAngles { get; set; } = new(); + + /// + /// Parses research data from JSON + /// + public static ResearchData FromJson(string json) + { + try + { + var result = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return result ?? new ResearchData(); + } + catch + { + // Return empty research data if parsing fails + return new ResearchData(); + } + } +} + +/// +/// Source information from research +/// +public class ResearchSource +{ + /// + /// URL of the source + /// + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; + + /// + /// Title of the source + /// + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + /// + /// Brief description of the source content + /// + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; +} + +/// +/// Image generated by DALL-E +/// +public class GeneratedImage +{ + /// + /// Description of the image + /// + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// + /// Prompt used to generate the image + /// + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// + /// URL or Base64 representation of the image + /// + [JsonPropertyName("imageUrl")] + public string ImageUrl { get; set; } = string.Empty; + + /// + /// Caption for the image + /// + [JsonPropertyName("caption")] + public string Caption { get; set; } = string.Empty; + + /// + /// Parses generated image from JSON + /// + public static List FromJson(string json) + { + try + { + var result = JsonSerializer.Deserialize>(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return result ?? new List(); + } + catch + { + // Return empty list if parsing fails + return new List(); + } + } +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Orchestrations/ContentCreationOrchestration.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Orchestrations/ContentCreationOrchestration.cs index 6e133c1..f584ead 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Orchestrations/ContentCreationOrchestration.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Orchestrations/ContentCreationOrchestration.cs @@ -1,5 +1,5 @@ using AgentChainingSample.Activities; -using AgentChainingSample.Shared.Models; +using AgentChainingSample.Worker.Models; using Microsoft.DurableTask; using Microsoft.Extensions.Logging; @@ -42,10 +42,11 @@ public override async Task RunAsync(TaskOrchestrationCont // 4. Assemble the final article with content and images and save to file in the project's tmp directory var articleResult = await context.CallActivityAsync( nameof(AssembleFinalArticleActivity), - (articleContent, generatedImages)); + (articleContent, generatedImages, context.InstanceId)); logger.LogInformation("Final article assembled. Length: {Length} characters", articleResult.HtmlContent.Length); logger.LogInformation("Article saved to file: {FilePath}", articleResult.FilePath); + logger.LogInformation("Article endpoint: {Endpoint}", articleResult.ArticleEndpoint); // 5. Return the complete workflow result return new ContentWorkflowResult @@ -57,7 +58,8 @@ public override async Task RunAsync(TaskOrchestrationCont FinalArticle = articleResult.HtmlContent, ArticleFilePath = articleResult.FilePath, ArticleBlobUrl = articleResult.BlobUrl, + ArticleEndpoint = articleResult.ArticleEndpoint, CompletedTimestamp = DateTime.UtcNow }; } -} +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Program.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Program.cs index 787dac5..6f675da 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Program.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Program.cs @@ -9,10 +9,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using AgentChainingSample.Services; +// Force rebuild timestamp: 2025-01-06 10:26 using AgentChainingSample.Activities; using AgentChainingSample.Orchestrations; -using AgentChainingSample.Shared.Models; +using AgentChainingSample.Services; +using AgentChainingSample.Worker.Models; // Configure the host builder HostApplicationBuilder builder = Host.CreateApplicationBuilder(); @@ -43,9 +44,8 @@ // Activities with [DurableTask] attribute are auto-registered via AddAllGeneratedTasks() // No need to manually register them here -// Get connection string from configuration with fallback to default local emulator connection -string connectionString = builder.Configuration["DTS_CONNECTION_STRING"] ?? - "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"; +// Get connection string from configuration +string connectionString = BuildConnectionString(builder.Configuration); // Configure services // Register tasks with DI @@ -64,8 +64,23 @@ // Get a proper logger from the service provider var logger = host.Services.GetRequiredService>(); -logger.LogInformation("Connection string: {ConnectionString}", connectionString); +// Log the constructed connection string with sensitive info redacted +string managedIdentityClientId = builder.Configuration["AZURE_MANAGED_IDENTITY_CLIENT_ID"] ?? ""; +string logConnectionString = !string.IsNullOrEmpty(managedIdentityClientId) ? + connectionString.Replace(managedIdentityClientId, "[REDACTED]") : + connectionString; +logger.LogInformation("Connection string: {ConnectionString}", logConnectionString); +logger.LogInformation("TaskHub: {TaskHub}", builder.Configuration["TASKHUB"] ?? builder.Configuration["TASKHUB_NAME"] ?? "default"); logger.LogInformation("This worker implements a news article generator workflow with multiple specialized agents"); + +// Log OpenAI configuration +logger.LogInformation("Azure OpenAI Endpoint: {Endpoint}", builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? "Not set"); +logger.LogInformation("Azure OpenAI Deployment: {Deployment}", builder.Configuration["OPENAI_DEPLOYMENT_NAME"] ?? "gpt-4 (default)"); +logger.LogInformation("DALL-E Endpoint: {DalleEndpoint}", !string.IsNullOrEmpty(builder.Configuration["DALLE_ENDPOINT"]) ? + "Configured" : "Not set - will use placeholder images"); +logger.LogInformation("Agent Connection String: {AgentConnectionString}", !string.IsNullOrEmpty(builder.Configuration["AGENT_CONNECTION_STRING"]) ? + "Configured" : "Not set - required for agent functionality"); + logger.LogInformation("Starting Agent Chaining Sample Worker"); // Start the host @@ -88,3 +103,34 @@ // Stop the host await host.StopAsync(); + +/// +/// Builds a connection string for the Durable Task Scheduler from configuration values. +/// +/// The configuration object containing connection settings. +/// A properly formatted connection string. +static string BuildConnectionString(IConfiguration configuration) +{ + // Get connection string from configuration with fallback to default local emulator connection + string connectionString = configuration["ENDPOINT"] ?? + configuration["DTS_CONNECTION_STRING"] ?? + "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"; + + // If we have the endpoint but not a full connection string, construct it + if (connectionString.StartsWith("Endpoint=") && !connectionString.Contains("TaskHub=")) + { + string taskHub = configuration["TASKHUB"] ?? configuration["TASKHUB_NAME"] ?? "default"; + string clientId = configuration["AZURE_MANAGED_IDENTITY_CLIENT_ID"] ?? ""; + + if (!string.IsNullOrEmpty(clientId)) + { + connectionString = $"{connectionString};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={taskHub}"; + } + else + { + connectionString = $"{connectionString};TaskHub={taskHub}"; + } + } + + return connectionString; +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/BaseAgentService.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/BaseAgentService.cs index a9a0856..01f9918 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/BaseAgentService.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/BaseAgentService.cs @@ -50,8 +50,22 @@ protected BaseAgentService(string endpointUrl, ILogger logger, DeploymentName = Configuration["OPENAI_DEPLOYMENT_NAME"] ?? "gpt-4"; Logger.LogInformation($"Using OpenAI deployment: {DeploymentName}"); - // Create credential for authentication - Credential = new DefaultAzureCredential(); + // Create credential for authentication with specific client ID if available + var clientId = Configuration["AGENT_CONNECTION_STRING__clientId"] ?? Configuration["AZURE_CLIENT_ID"]; + if (!string.IsNullOrEmpty(clientId)) + { + Logger.LogInformation($"Using managed identity with client ID: {clientId}"); + var defaultCredentialOptions = new DefaultAzureCredentialOptions + { + ManagedIdentityClientId = clientId + }; + Credential = new DefaultAzureCredential(defaultCredentialOptions); + } + else + { + Logger.LogInformation("Using default Azure credential without specific client ID"); + Credential = new DefaultAzureCredential(); + } Logger.LogInformation($"Initializing Azure AI Projects client with endpoint: {Endpoint}"); @@ -464,4 +478,4 @@ private async Task HandleRetry(int retryCount, int retryDelay, string error // Double the delay for the next retry (exponential backoff) return retryDelay * 2; } -} +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ContentGenerationAgentService.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ContentGenerationAgentService.cs index b03f914..6ebf723 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ContentGenerationAgentService.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ContentGenerationAgentService.cs @@ -55,4 +55,4 @@ The article should be approximately 400-600 words in length. Logger.LogInformation($"Requesting article creation for topic: {topic}"); return await GetResponseAsync(prompt); } -} +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ImageGenerationAgentService.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ImageGenerationAgentService.cs index 4d2f41b..80d7fa6 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ImageGenerationAgentService.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ImageGenerationAgentService.cs @@ -1,6 +1,7 @@ + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; -using AgentChainingSample.Shared.Models; +using AgentChainingSample.Worker.Models; using Azure.Identity; using Azure.Core; using System.Collections.Generic; diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ResearchAgentService.cs b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ResearchAgentService.cs index ee57827..c69e05f 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ResearchAgentService.cs +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Services/ResearchAgentService.cs @@ -67,4 +67,4 @@ 5. Consider expert opinions or analyses that might be available Logger.LogInformation($"Requesting research for topic: {topic}"); return await GetResponseAsync(prompt); } -} +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Worker.csproj b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Worker.csproj index 345ca81..34a4832 100644 --- a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Worker.csproj +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/Worker/Worker.csproj @@ -1,4 +1,4 @@ - + @@ -8,13 +8,16 @@ enable AgentChainingSample.Worker AgentChainingSample.Worker + f186b044-82dd-4212-96df-113caf6ef3f9 + + false + agent-chaining-worker - @@ -29,8 +32,4 @@ $(BaseIntermediateOutputPath)Generated - - - - - + \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/azure.yaml b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/azure.yaml new file mode 100644 index 0000000..c0cc762 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/azure.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +# This is an example starter azure.yaml file containing several example services in comments below. +# Make changes as needed to describe your application setup. +# To learn more about the azure.yaml file, visit https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/azd-schema + +# Name of the application. +metadata: + template: agent-prompt-chaining-aca-v2 +name: agent-prompt-chaining-aca +services: + client: + project: ./Client + language: csharp + host: containerapp + apiVersion: 2025-01-01 + docker: + path: ./Dockerfile + bindings: + - port: 5000 + protocol: http + transport: auto + targetPort: 5000 + external: true + worker: + project: ./Worker + language: csharp + host: containerapp + apiVersion: 2025-01-01 + docker: + path: ./Dockerfile + diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/abbreviations.json b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/abbreviations.json new file mode 100644 index 0000000..1f9a112 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/abbreviations.json @@ -0,0 +1,139 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "cognitiveServicesSpeech": "cog-sp-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-", + "dts": "dts-", + "taskhub": "taskhub-" +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/post-capability-host-role-assignments.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/post-capability-host-role-assignments.bicep new file mode 100644 index 0000000..f202255 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/post-capability-host-role-assignments.bicep @@ -0,0 +1,106 @@ +// These must be created post-capability host addition because otherwise +// the containers will not yet exist. + +param aiProjectPrincipalId string +param aiProjectPrincipalType string = 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal +param aiProjectWorkspaceId string + +param aiStorageAccountName string +param cosmosDbAccountName string + +// Assignments for Storage Account containers +// ------------------------------------------------------------------ + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = { + name: aiStorageAccountName +} + +// Assign AI Project Storage Blob Data Owner Role for the dependent resource storage account. +// Limits ownership to containers specific to the Project Workspace. + +var storageBlobDataOwnerRoleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' +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 \'${aiProjectWorkspaceId}\' AND @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringLikeIgnoreCase \'*-azureml-agent\'))' + +resource storageBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: storage + name: guid(storage.id, aiProjectPrincipalId, storageBlobDataOwnerRoleDefinitionId, aiProjectWorkspaceId) + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataOwnerRoleDefinitionId) + principalId: aiProjectPrincipalId + principalType: aiProjectPrincipalType + conditionVersion: '2.0' + condition: conditionStr + } +} + +// Assignments for CosmosDB containers +// ------------------------------------------------------------------ + +var userThreadName = '${aiProjectWorkspaceId}-thread-message-store' +var systemThreadName = '${aiProjectWorkspaceId}-system-thread-message-store' +var entityStoreName = '${aiProjectWorkspaceId}-agent-entity-store' + +resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { + name: cosmosDbAccountName +} + +// Reference existing database +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-12-01-preview' existing = { + parent: cosmosAccount + name: 'enterprise_memory' +} + +resource containerUserMessageStore 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-12-01-preview' existing = { + parent: database + name: userThreadName +} + +resource containerSystemMessageStore 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-12-01-preview' existing = { + parent: database + name: systemThreadName +} + +resource containerEntityStore 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-12-01-preview' existing = { + parent: database + name: entityStoreName +} + +var roleDefinitionId = resourceId( + 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', + cosmosDbAccountName, + '00000000-0000-0000-0000-000000000002' +) + +var scopeSystemContainer = '${cosmosAccount.id}/dbs/enterprise_memory/colls/${systemThreadName}' +var scopeUserContainer = '${cosmosAccount.id}/dbs/enterprise_memory/colls/${userThreadName}' +var scopeEntityContainer = '${cosmosAccount.id}/dbs/enterprise_memory/colls/${entityStoreName}' + +resource containerRoleAssignmentUserContainer 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmosAccount + name: guid(aiProjectWorkspaceId, containerUserMessageStore.id, roleDefinitionId, aiProjectPrincipalId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: roleDefinitionId + scope: scopeUserContainer + } +} + +resource containerRoleAssignmentSystemContainer 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmosAccount + name: guid(aiProjectWorkspaceId, containerSystemMessageStore.id, roleDefinitionId, aiProjectPrincipalId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: roleDefinitionId + scope: scopeSystemContainer + } +} + +resource containerRoleAssignmentEntityContainer 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmosAccount + name: guid(aiProjectWorkspaceId, containerEntityStore.id, roleDefinitionId, aiProjectPrincipalId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: roleDefinitionId + scope: scopeEntityContainer + } +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-hub.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-hub.bicep new file mode 100644 index 0000000..e69de29 diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-capability-host.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-capability-host.bicep new file mode 100644 index 0000000..0bb95b8 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-capability-host.bicep @@ -0,0 +1,43 @@ +param cosmosDbConnection string +param azureStorageConnection string +param aiSearchConnection string +param projectName string +param aiServicesAccountName string +param projectCapHost string +param accountCapHost string + +var threadConnections = ['${cosmosDbConnection}'] +var storageConnections = ['${azureStorageConnection}'] +var vectorStoreConnections = ['${aiSearchConnection}'] + + +resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiServicesAccountName +} + +resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = { + name: projectName + parent: account +} + +resource accountCapabilityHost 'Microsoft.CognitiveServices/accounts/capabilityHosts@2025-04-01-preview' = { + name: accountCapHost + parent: account + properties: { + capabilityHostKind: 'Agents' + } +} + +resource projectCapabilityHost 'Microsoft.CognitiveServices/accounts/projects/capabilityHosts@2025-04-01-preview' = { + name: projectCapHost + parent: project + properties: { + capabilityHostKind: 'Agents' + vectorStoreConnections: vectorStoreConnections + storageConnections: storageConnections + threadStorageConnections: threadConnections + } + dependsOn: [ + accountCapabilityHost + ] +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-role-assignments.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-role-assignments.bicep new file mode 100644 index 0000000..9b558a2 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project-role-assignments.bicep @@ -0,0 +1,269 @@ +param aiProjectPrincipalId string +param aiProjectPrincipalType string = 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal +param userPrincipalId string = '' +param allowUserIdentityPrincipal bool = false // Flag to enable user identity role assignments + +param aiServicesName string +param aiSearchName string +param aiCosmosDbName string +param aiStorageAccountName string + +param integrationStorageAccountName string + +// Parameters for function app managed identity +param functionAppManagedIdentityPrincipalId string = '' +param allowFunctionAppIdentityPrincipal bool = true // Flag to enable function app identity role assignments + +// Parameters for DALL-E deployment +param dalleAiServicesId string = '' + +// Assignments for AI Services +// ------------------------------------------------------------------ + +resource aiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = { + name: aiServicesName +} + +// Assign AI Project the Cognitive Services Contributor Role on the AI Services resource + +var cognitiveServicesContributorRoleDefinitionId = '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' + +resource cognitiveServicesContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01'= { + scope: aiServices + name: guid(aiServices.id, cognitiveServicesContributorRoleDefinitionId, aiProjectPrincipalId) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assign AI Project the Cognitive Services OpenAI User Role on the AI Services resource + +var cognitiveServicesOpenAIUserRoleDefinitionId = '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + +resource cognitiveServicesOpenAIUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectPrincipalId, cognitiveServicesOpenAIUserRoleDefinitionId, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIUserRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assign AI Project the Cognitive Services User Role on the AI Services resource + +var cognitiveServicesUserRoleDefinitionId = 'a97b65f3-24c7-4388-baec-2e87135dc908' + +resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiServices + name: guid(aiProjectPrincipalId, cognitiveServicesUserRoleDefinitionId, aiServices.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesUserRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assign AI Project the Cognitive Services Contributor Role on the User Identity Principal on the AI Services resource + +resource cognitiveServicesContributorAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01'= if (allowUserIdentityPrincipal && !empty(userPrincipalId)) { + scope: aiServices + name: guid(aiServices.id, cognitiveServicesContributorRoleDefinitionId, userPrincipalId) + properties: { + principalId: userPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleDefinitionId) + principalType: 'User' + } +} + +// Assign AI Project the Cognitive Services OpenAI User Role on the User Identity Principal on the AI Services resource + +resource cognitiveServicesOpenAIUserRoleAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowUserIdentityPrincipal && !empty(userPrincipalId)){ + scope: aiServices + name: guid(userPrincipalId, cognitiveServicesOpenAIUserRoleDefinitionId, aiServices.id) + properties: { + principalId: userPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIUserRoleDefinitionId) + principalType: 'User' + } +} + +// Assign AI Project the Cognitive Services User Role on the User Identity Principal on the AI Services resource + +resource cognitiveServicesUserRoleAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowUserIdentityPrincipal && !empty(userPrincipalId)){ + scope: aiServices + name: guid(userPrincipalId, cognitiveServicesUserRoleDefinitionId, aiServices.id) + properties: { + principalId: userPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesUserRoleDefinitionId) + principalType: 'User' + } +} + +// Assignments for AI Search Service +// ------------------------------------------------------------------ + +resource aiSearchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { + name: aiSearchName +} + +// Assign AI Project the Search Index Data Contributor Role on the AI Search Service resource + +var searchIndexDataContributorRoleDefinitionId = '8ebe5a00-799e-43f5-93ac-243d3dce84a7' + +resource searchIndexDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiSearchService + name: guid(aiProjectPrincipalId, searchIndexDataContributorRoleDefinitionId, aiSearchService.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', searchIndexDataContributorRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assign AI Project the Search Index Data Contributor Role on the AI Search Service resource + +var searchServiceContributorRoleDefinitionId = '7ca78c08-252a-4471-8644-bb5ff32d4ba0' + +resource searchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiSearchService + name: guid(aiProjectPrincipalId, searchServiceContributorRoleDefinitionId, aiSearchService.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', searchServiceContributorRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assignments for Storage Account +// ------------------------------------------------------------------ + +resource aiStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = { + name: aiStorageAccountName +} + +// Assign AI Project the Storage Blob Data Contributor Role on the Storage Account resource + +var storageBlobDataContributorRoleDefinitionId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + +resource storageBlobDataContributorRoleAssignmentProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aiStorageAccount + name: guid(aiProjectPrincipalId, storageBlobDataContributorRoleDefinitionId, aiStorageAccount.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assignments for Cosmos DB +// ------------------------------------------------------------------ + +resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { + name: aiCosmosDbName +} + +// Assign AI Project the Cosmos DB Operator Role on the Cosmos DB Account resource + +var cosmosDbOperatorRoleDefinitionId = '230815da-be43-4aae-9cb4-875f7bd000aa' + +resource cosmosDbOperatorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: cosmosDbAccount + name: guid(aiProjectPrincipalId, cosmosDbOperatorRoleDefinitionId, cosmosDbAccount.id) + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cosmosDbOperatorRoleDefinitionId) + principalType: aiProjectPrincipalType + } +} + +// Assignments for Storage Account +// ------------------------------------------------------------------ + +resource integrationStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = { + name: integrationStorageAccountName +} + +// Assign AI Project Storage Queue Data Contributor Role on the integration Storage Account resource +// between the agent and azure function + +var storageQueueDataContributorRoleDefinitionId = '974c5e8b-45b9-4653-ba55-5f855dd0fb88' + +resource storageQueueDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(integrationStorageAccount.id, aiProjectPrincipalId, storageQueueDataContributorRoleDefinitionId) + scope: integrationStorageAccount + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageQueueDataContributorRoleDefinitionId) + principalId: aiProjectPrincipalId + principalType: aiProjectPrincipalType + } +} + +// assignments for User Identity Principal + +resource storageQueueDataContributorRoleAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowUserIdentityPrincipal && !empty(userPrincipalId)) { + name: guid(integrationStorageAccount.id, userPrincipalId, storageQueueDataContributorRoleDefinitionId) + scope: integrationStorageAccount + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageQueueDataContributorRoleDefinitionId) + principalId: userPrincipalId + principalType: 'User' + } +} + +// Assign Function App Managed Identity the Cognitive Services Contributor Role on the AI Services resource + +resource cognitiveServicesContributorAssignmentFunctionApp 'Microsoft.Authorization/roleAssignments@2022-04-01'= if (allowFunctionAppIdentityPrincipal && !empty(functionAppManagedIdentityPrincipalId)) { + scope: aiServices + name: guid(aiServices.id, cognitiveServicesContributorRoleDefinitionId, functionAppManagedIdentityPrincipalId) + properties: { + principalId: functionAppManagedIdentityPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleDefinitionId) + principalType: 'ServicePrincipal' + } +} + +// Assign Function App Managed Identity the Cognitive Services OpenAI User Role on the AI Services resource + +resource cognitiveServicesOpenAIUserRoleAssignmentFunctionApp 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowFunctionAppIdentityPrincipal && !empty(functionAppManagedIdentityPrincipalId)) { + scope: aiServices + name: guid(functionAppManagedIdentityPrincipalId, cognitiveServicesOpenAIUserRoleDefinitionId, aiServices.id) + properties: { + principalId: functionAppManagedIdentityPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIUserRoleDefinitionId) + principalType: 'ServicePrincipal' + } +} + +// Assign Function App Managed Identity the Cognitive Services User Role on the AI Services resource + +resource cognitiveServicesUserRoleAssignmentFunctionApp 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowFunctionAppIdentityPrincipal && !empty(functionAppManagedIdentityPrincipalId)) { + scope: aiServices + name: guid(functionAppManagedIdentityPrincipalId, cognitiveServicesUserRoleDefinitionId, aiServices.id) + properties: { + principalId: functionAppManagedIdentityPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesUserRoleDefinitionId) + principalType: 'ServicePrincipal' + } +} + +// DALL-E Role Assignments +// ------------------------------------------------------------------ + +// Reference to the Image Generation AI Services resource +resource imageGenAiServices 'Microsoft.CognitiveServices/accounts@2024-06-01-preview' existing = { + name: last(split(dalleAiServicesId, '/')) +} + +// Assign Function App Managed Identity the Cognitive Services OpenAI User Role on the Image Generation AI Services resource +resource imageGenOpenAIUserRoleAssignmentFunctionApp 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowFunctionAppIdentityPrincipal && !empty(functionAppManagedIdentityPrincipalId)) { + scope: imageGenAiServices + name: guid(functionAppManagedIdentityPrincipalId, cognitiveServicesOpenAIUserRoleDefinitionId, dalleAiServicesId) + properties: { + principalId: functionAppManagedIdentityPrincipalId + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIUserRoleDefinitionId) + principalType: 'ServicePrincipal' + } +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project.bicep new file mode 100644 index 0000000..c17a3bf --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-ai-project.bicep @@ -0,0 +1,132 @@ +// Creates an Azure AI resource with proxied endpoints for the Azure AI services provider + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object + +@description('AI Services Foundry account under which the project will be created') +param aiServicesAccountName string + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string + +@description('Cosmos DB Account for agent thread storage') +param cosmosDbAccountName string +param cosmosDbAccountSubscriptionId string +param cosmosDbAccountResourceGroupName string + +@description('Storage Account for agent artifacts') +param storageAccountName string +param storageAccountSubscriptionId string +param storageAccountResourceGroupName string + +@description('AI Search Service for vector store and search') +param aiSearchName string +param aiSearchSubscriptionId string +param aiSearchResourceGroupName string + +resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = { + name: cosmosDbAccountName + scope: resourceGroup(cosmosDbAccountSubscriptionId, cosmosDbAccountResourceGroupName) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = { + name: storageAccountName + scope: resourceGroup(storageAccountSubscriptionId, storageAccountResourceGroupName) +} + +resource aiSearchService 'Microsoft.Search/searchServices@2024-06-01-preview' existing = { + name: aiSearchName + scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName) +} + +resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiServicesAccountName +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { + parent: aiServicesAccount + name: aiProjectName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + description: aiProjectDescription + displayName: aiProjectFriendlyName + } + + resource project_connection_cosmosdb_account 'connections@2025-04-01-preview' = { + name: cosmosDbAccountName + 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: storageAccountName + 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: aiSearchService.id + location: aiSearchService.location + } + } + } +} + +// Outputs + +output aiProjectName string = aiProject.name +output aiProjectResourceId string = aiProject.id +output aiProjectPrincipalId string = aiProject.identity.principalId + +output aiSearchConnection string = aiSearchName +output azureStorageConnection string = storageAccountName +output cosmosDbConnection string = cosmosDbAccountName + +// This is used for storage naming conventions and is needed to help +// create the right fine-grained role assignments. The naming +// convention also uses dashes injected into the value, so we're +// handling that here. +// This will likely change or be made available via a different property. +#disable-next-line BCP053 +var internalId = aiProject.properties.internalId +output projectWorkspaceId string = '${substring(internalId, 0, 8)}-${substring(internalId, 8, 4)}-${substring(internalId, 12, 4)}-${substring(internalId, 16, 4)}-${substring(internalId, 20, 12)}' + +// This endpoint is also built by convention at this time but will +// hopefully be available as a different property at some point. +output projectEndpoint string = 'https://${aiServicesAccountName}.services.ai.azure.com/api/projects/${aiProjectName}' diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-dependent-resources.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-dependent-resources.bicep new file mode 100644 index 0000000..bc29153 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/agent/standard-dependent-resources.bicep @@ -0,0 +1,283 @@ +// Creates Azure dependent resources for Azure AI studio + +@description('Azure region of the deployment') +param location string = resourceGroup().location + +@description('Tags to add to the resources') +param tags object = {} + +@description('AI services name') +param aiServicesName string + +@description('The name of the AI Search resource') +param aiSearchName string + +@description('The name of the Cosmos DB account') +param cosmosDbName string + +@description('Name of the storage account') +param storageName string + +@description('Model name for deployment') +param modelName string + +@description('Model format for deployment') +param modelFormat string + +@description('Model version for deployment') +param modelVersion string + +@description('Model deployment SKU name') +param modelSkuName string + +@description('Model deployment capacity') +param modelCapacity int + +@description('DALL-E model name for deployment') +param dalleModelName string = 'dall-e-3' + +@description('DALL-E model version for deployment') +param dalleModelVersion string = '3.0' + +@description('DALL-E model deployment SKU name') +param dalleModelSkuName string = 'Standard' + +@description('DALL-E model deployment capacity') +param dalleModelCapacity int = 1 + +@description('Model/AI Resource deployment location') +param modelLocation string + +// Create a separate Azure OpenAI resource for image generation in East US +// Always create this resource since image models often need specific regions +resource imageGenAiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { + name: '${aiServicesName}-imagegen' + location: 'eastus' // Try East US for DALL-E availability + sku: { + name: 'S0' + } + kind: 'OpenAI' + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: toLower('${aiServicesName}-imagegen') + networkAcls: { + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + } + publicNetworkAccess: 'Enabled' + disableLocalAuth: true + } +} + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string + +@description('The AI Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiSearchServiceResourceId string + +@description('The AI Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiStorageAccountResourceId string + +@description('The AI Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiCosmosDbAccountResourceId string + +var aiServiceExists = aiServiceAccountResourceId != '' +var acsExists = aiSearchServiceResourceId != '' +var aiStorageExists = aiStorageAccountResourceId != '' +var cosmosExists = aiCosmosDbAccountResourceId != '' + +// Create an AI Service account and model deployment if it doesn't already exist + +var aiServiceParts = split(aiServiceAccountResourceId, '/') + +resource existingAIServiceAccount 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = if (aiServiceExists) { + name: aiServiceParts[8] + scope: resourceGroup(aiServiceParts[2], aiServiceParts[4]) +} + +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if(!aiServiceExists) { + name: aiServicesName + location: modelLocation + sku: { + name: 'S0' + } + kind: 'AIServices' + identity: { + type: 'SystemAssigned' + } + properties: { + allowProjectManagement: true + customSubDomainName: toLower('${(aiServicesName)}') + networkAcls: { + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + } + publicNetworkAccess: 'Enabled' + // API-key based auth is not supported for the Agent service + disableLocalAuth: true + } +} +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview'= if(!aiServiceExists){ + parent: aiServices + name: modelName + sku : { + capacity: modelCapacity + name: modelSkuName + } + properties: { + model:{ + name: modelName + format: modelFormat + version: modelVersion + } + } +} + +// DALL-E 3 deployment for image generation in East US +resource dalleDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { + parent: imageGenAiServices + name: dalleModelName + sku: { + capacity: dalleModelCapacity + name: dalleModelSkuName + } + properties: { + model: { + name: dalleModelName + format: 'OpenAI' + version: dalleModelVersion + } + } +} + +// Create an AI Search Service if it doesn't already exist + +var acsParts = split(aiSearchServiceResourceId, '/') + +resource existingSearchService 'Microsoft.Search/searchServices@2023-11-01' existing = if (acsExists) { + name: acsParts[8] + scope: resourceGroup(acsParts[2], acsParts[4]) +} +resource aiSearch 'Microsoft.Search/searchServices@2024-06-01-preview' = if(!acsExists) { + name: aiSearchName + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + properties: { + disableLocalAuth: true + encryptionWithCmk: { + enforcement: 'Unspecified' + } + hostingMode: 'default' + partitionCount: 1 + publicNetworkAccess: 'enabled' + replicaCount: 1 + semanticSearch: 'disabled' + } + sku: { + name: 'standard' + } +} + +// Create a Storage account if it doesn't already exist + +var aiStorageParts = split(aiStorageAccountResourceId, '/') + +resource existingAIStorageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = if (aiStorageExists) { + name: aiStorageParts[8] + scope: resourceGroup(aiStorageParts[2], aiStorageParts[4]) +} + +param sku string = 'Standard_LRS' + +resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = if(!aiStorageExists) { + name: storageName + location: location + kind: 'StorageV2' + sku: { + name: sku + } + properties: { + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + publicNetworkAccess: 'Enabled' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + virtualNetworkRules: [] + } + allowSharedKeyAccess: false + // Do not set requireInfrastructureEncryption as it's a read-only property + } +} + +// Create a Cosmos DB Account if it doesn't already exist + +var cosmosAccountParts = split(aiCosmosDbAccountResourceId, '/') + +resource existingCosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = if (cosmosExists) { + name: cosmosAccountParts[8] + scope: resourceGroup(cosmosAccountParts[2], cosmosAccountParts[4]) +} + +var canaryRegions = ['eastus2euap', 'centraluseuap'] +var cosmosDbRegion = contains(canaryRegions, location) ? 'eastus2' : location +resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = if(!cosmosExists) { + name: cosmosDbName + location: cosmosDbRegion + kind: 'GlobalDocumentDB' + properties: { + consistencyPolicy: { + defaultConsistencyLevel: 'Session' + } + disableLocalAuth: true + enableAutomaticFailover: false + enableMultipleWriteLocations: false + enableFreeTier: false + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + } +} + +// Outputs + +output aiServicesName string = aiServiceExists ? existingAIServiceAccount.name : aiServices.name +output aiservicesID string = aiServiceExists ? existingAIServiceAccount.id : aiServices.id +#disable-next-line BCP318 +output aiservicesTarget string = aiServiceExists ? existingAIServiceAccount.properties.endpoint : aiServices.properties.endpoint +output aiServiceAccountResourceGroupName string = aiServiceExists ? aiServiceParts[4] : resourceGroup().name +output aiServiceAccountSubscriptionId string = aiServiceExists ? aiServiceParts[2] : subscription().subscriptionId + +// DALL-E 3 image generation endpoint +#disable-next-line BCP318 +output dalleEndpoint string = '${imageGenAiServices.properties.endpoint}openai/deployments/${dalleModelName}/images/generations?api-version=2023-12-01-preview' + +// Image generation AI Services resource ID for role assignments +output dalleAiServicesId string = imageGenAiServices.id + +output aiSearchName string = acsExists ? existingSearchService.name : aiSearch.name +output aisearchID string = acsExists ? existingSearchService.id : aiSearch.id +output aiSearchServiceResourceGroupName string = acsExists ? acsParts[4] : resourceGroup().name +output aiSearchServiceSubscriptionId string = acsExists ? acsParts[2] : subscription().subscriptionId + +output storageAccountName string = aiStorageExists ? existingAIStorageAccount.name : storage.name +output storageId string = aiStorageExists ? existingAIStorageAccount.id : storage.id +output storageAccountResourceGroupName string = aiStorageExists ? aiStorageParts[4] : resourceGroup().name +output storageAccountSubscriptionId string = aiStorageExists ? aiStorageParts[2] : subscription().subscriptionId + +output cosmosDbAccountName string = cosmosExists ? existingCosmosDbAccount.name : cosmosDbAccount.name +output cosmosDbAccountId string = cosmosExists ? existingCosmosDbAccount.id : cosmosDbAccount.id +output cosmosDbAccountResourceGroupName string = cosmosExists ? cosmosAccountParts[4] : resourceGroup().name +output cosmosDbAccountSubscriptionId string = cosmosExists ? cosmosAccountParts[2] : subscription().subscriptionId diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/ai-role-assignments.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/ai-role-assignments.bicep new file mode 100644 index 0000000..a82a118 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/ai-role-assignments.bicep @@ -0,0 +1,86 @@ +// ai-role-assignments.bicep +// Assigns the necessary roles for AI resources + +@description('Principal ID to assign roles to') +param principalId string + +@description('Name of the OpenAI resource') +param openAiResourceName string = '' + +@description('Name of the AI Hub') +param aiHubName string = '' + +@description('Name of the AI Project') +param aiProjectName string = '' + +// If OpenAI resource name is provided, assign roles +resource openAi 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = if (!empty(openAiResourceName)) { + name: openAiResourceName +} + +// Assign Cognitive Services OpenAI User role +resource openAiUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(openAiResourceName)) { + name: guid(openAi.id, principalId, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') + scope: openAi + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') // Cognitive Services OpenAI User + principalType: 'ServicePrincipal' + } +} + +// Assign Cognitive Services Contributor role +resource cognitiveServicesContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(openAiResourceName)) { + name: guid(openAi.id, principalId, '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68') + scope: openAi + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68') // Cognitive Services Contributor + principalType: 'ServicePrincipal' + } +} + +// Add AI Hub roles if hub name is provided +resource aiHub 'Microsoft.MachineLearningServices/workspaces@2024-01-01-preview' existing = if (!empty(aiHubName)) { + name: aiHubName +} + +resource aiHubContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiHubName)) { + name: guid(aiHub.id, principalId, 'b24988ac-6180-42a0-ab88-20f7382dd24c') + scope: aiHub + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor + principalType: 'ServicePrincipal' + } +} + +// Add AI Project roles if project name is provided +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' existing = if (!empty(aiProjectName)) { + name: aiProjectName +} + +resource aiProjectContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiProjectName)) { + name: guid(aiProject.id, principalId, 'b24988ac-6180-42a0-ab88-20f7382dd24c') + scope: aiProject + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor + principalType: 'ServicePrincipal' + } +} + +// Add Reader role - required for the managed identity to use AI Project services +resource aiProjectReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiProjectName)) { + name: guid(aiProject.id, principalId, 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + scope: aiProject + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') // Reader role + principalType: 'ServicePrincipal' + } +} + +output openAiUserRoleAssignmentId string = !empty(openAiResourceName) ? openAiUserRoleAssignment.id : '' +output cognitiveServicesContributorRoleAssignmentId string = !empty(openAiResourceName) ? cognitiveServicesContributorRoleAssignment.id : '' +output aiProjectReaderRoleAssignmentId string = !empty(aiProjectName) ? aiProjectReaderRoleAssignment.id : '' diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/app.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/app.bicep new file mode 100644 index 0000000..de86262 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/app.bicep @@ -0,0 +1,181 @@ +param appName string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'aca' +param dtsEndpoint string +param taskHubName string +// Legacy parameters (kept for backward compatibility) +param agentConnectionString string = '' +param openAiEndpoint string = '' +param openAiDeploymentName string = 'gpt-4o-mini' +// New parameters using direct naming convention +param AGENT_CONNECTION_STRING string = '' +param OPENAI_DEPLOYMENT_NAME string = 'gpt-4o-mini' +param AGENT_CONNECTION_STRING__clientId string = '' +param DALLE_ENDPOINT string = '' +param clientBaseUrl string = '' + +type managedIdentity = { + resourceId: string + clientId: string +} + +@description('Unique identifier for user-assigned managed identity.') +param userAssignedManagedIdentity managedIdentity + +// Define different container configurations based on service type +var serviceConfig = serviceName == 'client' ? { + enableIngress: true + external: true + targetPort: 5000 + containerImage: 'mcr.microsoft.com/dotnet/aspnet:8.0' + probes: [ + { + type: 'Startup' + httpGet: { + path: '/health' + port: 5000 + scheme: 'HTTP' + } + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 5 + } + { + type: 'Liveness' + httpGet: { + path: '/health' + port: 5000 + scheme: 'HTTP' + } + initialDelaySeconds: 10 + periodSeconds: 30 + failureThreshold: 3 + timeoutSeconds: 5 + } + ] +} : { + enableIngress: false // Worker doesn't need ingress + external: false + targetPort: 0 // Not needed for worker + containerImage: 'mcr.microsoft.com/dotnet/runtime:8.0' + probes: [] // No probes for worker +} + +module containerAppsApp '../core/host/container-app.bicep' = { + name: 'container-apps-${serviceName}' + params: { + name: appName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + enableIngress: serviceConfig.enableIngress + external: serviceConfig.external + targetPort: serviceConfig.targetPort + containerImage: serviceConfig.containerImage + identityName: identityName + minReplicas: 1 + maxReplicas: 10 + environmentVariables: serviceName == 'worker' ? [ + { + name: 'AZURE_MANAGED_IDENTITY_CLIENT_ID' + secretRef: 'azure-managed-identity-client-id' + } + { + name: 'AZURE_CLIENT_ID' + value: userAssignedManagedIdentity.clientId + } + { + name: 'ENDPOINT' + value: 'Endpoint=${dtsEndpoint};Authentication=ManagedIdentity;ClientID=${userAssignedManagedIdentity.clientId}' + } + { + name: 'TASKHUB' + value: taskHubName + } + { + name: 'AGENT_CONNECTION_STRING' + value: !empty(AGENT_CONNECTION_STRING) ? AGENT_CONNECTION_STRING : !empty(agentConnectionString) ? agentConnectionString : '' + } + { + name: 'OPENAI_DEPLOYMENT_NAME' + value: !empty(OPENAI_DEPLOYMENT_NAME) ? OPENAI_DEPLOYMENT_NAME : !empty(openAiDeploymentName) ? openAiDeploymentName : 'gpt-4o-mini' + } + { + name: 'AGENT_CONNECTION_STRING__clientId' + value: !empty(AGENT_CONNECTION_STRING__clientId) ? AGENT_CONNECTION_STRING__clientId : userAssignedManagedIdentity.clientId + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: !empty(openAiEndpoint) ? openAiEndpoint : '' + } + { + name: 'DALLE_ENDPOINT' + value: DALLE_ENDPOINT + } + { + name: 'CLIENT_BASE_URL' + value: clientBaseUrl + } + { + name: 'CLIENT_BASE_URL' + value: clientBaseUrl + } + ] : [ + { + name: 'AZURE_MANAGED_IDENTITY_CLIENT_ID' + secretRef: 'azure-managed-identity-client-id' + } + { + name: 'AZURE_CLIENT_ID' + value: userAssignedManagedIdentity.clientId + } + { + name: 'ENDPOINT' + value: 'Endpoint=${dtsEndpoint};Authentication=ManagedIdentity;ClientID=${userAssignedManagedIdentity.clientId}' + } + { + name: 'TASKHUB' + value: taskHubName + } + { + name: 'AGENT_CONNECTION_STRING' + value: !empty(AGENT_CONNECTION_STRING) ? AGENT_CONNECTION_STRING : !empty(agentConnectionString) ? agentConnectionString : '' + } + { + name: 'OPENAI_DEPLOYMENT_NAME' + value: !empty(OPENAI_DEPLOYMENT_NAME) ? OPENAI_DEPLOYMENT_NAME : !empty(openAiDeploymentName) ? openAiDeploymentName : 'gpt-4o-mini' + } + { + name: 'AGENT_CONNECTION_STRING__clientId' + value: !empty(AGENT_CONNECTION_STRING__clientId) ? AGENT_CONNECTION_STRING__clientId : userAssignedManagedIdentity.clientId + } + ] + secrets: [ + { + name: 'azure-managed-identity-client-id' + value: userAssignedManagedIdentity.clientId + } + ] + enableCustomScaleRule: false // Disable custom scale rule for initial deployment + scaleRuleName: 'dtsscaler-orchestration' + scaleRuleType: 'azure-durabletask-scheduler' + scaleRuleMetadata: { + endpoint: dtsEndpoint + maxConcurrentWorkItemsCount: '1' + taskhubName: taskHubName + workItemType: 'Orchestration' + } + scaleRuleIdentity: userAssignedManagedIdentity.resourceId + probes: serviceConfig.probes + } +} + +output endpoint string = containerAppsApp.outputs.containerAppFqdn +output envName string = appName diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/dts.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/dts.bicep new file mode 100644 index 0000000..c435832 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/dts.bicep @@ -0,0 +1,29 @@ +param ipAllowlist array +param location string +param tags object = {} +param name string +param taskhubname string +param skuName string +param skuCapacity int + +resource dts 'Microsoft.DurableTask/schedulers@2024-10-01-preview' = { + location: location + tags: tags + name: name + properties: { + ipAllowlist: ipAllowlist + sku: { + name: skuName + capacity: skuCapacity + } + } +} + +resource taskhub 'Microsoft.DurableTask/schedulers/taskhubs@2024-10-01-preview' = { + parent: dts + name: taskhubname +} + +output dts_NAME string = dts.name +output dts_URL string = dts.properties.endpoint +output TASKHUB_NAME string = taskhub.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/openai-access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/openai-access.bicep new file mode 100644 index 0000000..e69de29 diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/registry-access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/registry-access.bicep new file mode 100644 index 0000000..2acd04e --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/registry-access.bicep @@ -0,0 +1,27 @@ +@description('The name of the Container Registry') +param containerRegistryName string + +@description('The Principal ID of the identity that needs access to the registry') +param principalID string + +@description('The type of the principal (User, ServicePrincipal, etc.)') +param principalType string = 'ServicePrincipal' + +// AcrPull role definition ID: 7f951dda-4ed3-4680-a7ca-43fe172d538d +var acrPullRoleDefinitionId = '7f951dda-4ed3-4680-a7ca-43fe172d538d' + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = { + name: containerRegistryName +} + +resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(containerRegistry.id, principalID, acrPullRoleDefinitionId) + scope: containerRegistry + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', acrPullRoleDefinitionId) + principalId: principalID + principalType: principalType + } +} + +output roleAssignmentId string = acrPullRoleAssignment.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/storage-Access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/storage-Access.bicep new file mode 100644 index 0000000..2422be4 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/storage-Access.bicep @@ -0,0 +1,30 @@ +// storage-Access.bicep +// Assigns role to a principal on a storage account + +@description('The name of the storage account') +param storageAccountName string + +@description('The principal ID to assign the role to') +param principalID string + +@description('The role definition ID to assign') +param roleDefinitionID string + +@description('The type of the principal (User, Group, ServicePrincipal)') +param principalType string + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = { + name: storageAccountName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(storageAccount.id, principalID, roleDefinitionID) + scope: storageAccount + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) + principalId: principalID + principalType: principalType + } +} + +output roleAssignmentId string = roleAssignment.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-assigned-identity.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-assigned-identity.bicep new file mode 100644 index 0000000..0583ab8 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-assigned-identity.bicep @@ -0,0 +1,17 @@ +metadata description = 'Creates a Microsoft Entra user-assigned identity.' + +param name string +param location string = resourceGroup().location +param tags object = {} + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: name + location: location + tags: tags +} + +output name string = identity.name +output resourceId string = identity.id +output principalId string = identity.properties.principalId +output clientId string = identity.properties.clientId +output tenantId string = identity.properties.tenantId diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-registry-access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-registry-access.bicep new file mode 100644 index 0000000..1ab4dfd --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/app/user-registry-access.bicep @@ -0,0 +1,26 @@ +@description('The name of the Container Registry') +param containerRegistryName string + +@description('The user principal ID that needs push access to the registry') +param userPrincipalID string + +@description('The type of the principal (usually User)') +param principalType string = 'User' + +// AcrPush role definition ID: 8311e382-0749-4cb8-b61a-304f252e45ec +var acrPushRoleDefinitionId = '8311e382-0749-4cb8-b61a-304f252e45ec' + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = { + name: containerRegistryName +} + +// Only deploy if skip parameter is false +resource acrPushRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(containerRegistry.id, userPrincipalID, acrPushRoleDefinitionId) + scope: containerRegistry + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', acrPushRoleDefinitionId) + principalId: userPrincipalID + principalType: principalType + } +} diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-hub.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-hub.bicep new file mode 100644 index 0000000..e69de29 diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-project.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-project.bicep new file mode 100644 index 0000000..a926943 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-project.bicep @@ -0,0 +1,100 @@ +// This file defines an Azure AI Project resource and capability host for agents + +@description('Name of the AI Project') +param name string + +@description('Azure region for the project') +param location string = resourceGroup().location + +@description('Project SKU') +param sku object = { + name: 'Free' + tier: 'Free' +} + +@description('Tags for the resource') +param tags object = {} + +@description('The Azure OpenAI resource ID to connect to this project') +param openAiResourceId string + +@description('The Azure OpenAI resource name for connections') +param openAiName string + +@description('The Hub resource ID to associate with this project') +param hubResourceId string + +@description('If a Managed Service Identity for this AI Project should be created') +param managedIdentity bool = true + +// For creating proper connection strings +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name +// Original semicolon format - kept for reference +var standardConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${name}' +// URL format connection string that matches the SDK requirements +// Using standard Azure AI Project endpoint format +var projectConnectionString = 'https://${location}.aiprojects.azure.com/api/projects/${resourceGroupName}/${name}' + +var capabilityHostName = 'agent-host' + +// Define the necessary connections for the capability host +var storageConnections = ['${name}/workspaceblobstore'] +var aiServiceConnections = ['${openAiName}-connection'] +var vectorStoreConnections = ['${name}/default-vectorstore'] // Required non-empty value + +// Create the AI Project with the proper hub association and capability host +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' = { + name: name + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString + StandardConnectionString: standardConnectionString + OpenAiResourceId: openAiResourceId + }) + identity: { + type: managedIdentity ? 'SystemAssigned' : 'None' + } + sku: sku + kind: 'project' + properties: { + friendlyName: 'Agent Chaining AI Project' + description: 'AI Project for the agent chaining sample' + hubResourceId: hubResourceId + publicNetworkAccess: 'Enabled' + // Do not reference storage account directly for projects + // Storage is managed by the Hub + } + + // Create the capability host for agents - follows the pattern from Travel Plan Orchestrator example + resource capabilityHost 'capabilityHosts@2024-10-01-preview' = { + name: '${name}-${capabilityHostName}' + properties: { + capabilityHostKind: 'Agents' + aiServicesConnections: aiServiceConnections + vectorStoreConnections: vectorStoreConnections // Using non-empty vector store connection + storageConnections: storageConnections + } + } +} + +// End of aiProject resource + +// Add role assignment for the AI Project to access storage +resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (managedIdentity) { + name: guid(resourceGroup().id, aiProject.id, 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + scope: resourceGroup() + properties: { + principalId: aiProject.identity.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor + principalType: 'ServicePrincipal' + } +} + +// Output the project ID and information +output id string = aiProject.id +output name string = aiProject.name +output projectConnectionString string = projectConnectionString +output principalId string = managedIdentity ? aiProject.identity.principalId : '' +output projectResourceId string = aiProject.id +output capabilityHostName string = '${name}-${capabilityHostName}' diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-role-assignments.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-role-assignments.bicep new file mode 100644 index 0000000..565ecbe --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/ai-role-assignments.bicep @@ -0,0 +1,26 @@ +// ai-role-assignments.bicep - Role assignments for AI services + +@description('The principal ID to assign roles to') +param principalId string + +@description('The ID of the Azure AI Project') +param aiProjectId string + +@description('The type of the principal (ServicePrincipal, User, Group, etc)') +param principalType string = 'ServicePrincipal' + +// AI Project Contributor role definition ID +var aiProjectContributorRoleId = '1affc506-2bb4-4bbd-86a8-804c7c9d7a99' // Azure AI Project Contributor + +// Create role assignment for AI Project Contributor +resource aiProjectContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aiProjectId, principalId, aiProjectContributorRoleId) + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', aiProjectContributorRoleId) + principalId: principalId + principalType: principalType + } +} + +output aiProjectRoleAssignmentId string = aiProjectContributorRoleAssignment.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/hub.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/hub.bicep new file mode 100644 index 0000000..e69de29 diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/standard-ai-project.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/standard-ai-project.bicep new file mode 100644 index 0000000..b2b594b --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/ai/standard-ai-project.bicep @@ -0,0 +1,83 @@ +// standard-ai-project.bicep +// This file defines an Azure AI Project resource and capability host for agents +// Based on the Travel Plan Orchestrator example + +@description('Azure region of the deployment') +param location string + +@description('Tags to add to the resources') +param tags object = {} + +@description('AI Project name') +param aiProjectName string + +@description('AI Project display name') +param aiProjectFriendlyName string = aiProjectName + +@description('AI Project description') +param aiProjectDescription string = 'AI Project for agent chaining functionality' + +@description('Resource ID of the AI Hub resource') +param aiHubId string + +@description('Name for capabilityHost') +param capabilityHostName string = 'agent-host' + +@description('Name for OpenAI connection') +param aoaiConnectionName string = 'openai-connection' + +// For constructing endpoint +var subscriptionId = subscription().subscriptionId +var resourceGroupName = resourceGroup().name +// Original semicolon format - kept for reference/compatibility +var standardConnectionString = '${location}.api.azureml.ms;${subscriptionId};${resourceGroupName};${aiProjectName}' +// URL format connection string that matches the SDK requirements +// Using standard Azure AI Project endpoint format +var projectConnectionString = 'https://${location}.aiprojects.azure.com/api/projects/${resourceGroupName}/${aiProjectName}' + +// Define storage connections exactly as in the example +var storageConnections = ['${aiProjectName}/workspaceblobstore'] + +// Define a dummy vector store connection as required by the API +var vectorStoreConnections = ['dummy-vectorstore'] + +// Define AI services connections - must match the connection name from the hub +// This is critical - must match exactly the connection name defined in the AI Hub +var aiServiceConnections = [aoaiConnectionName] + +resource aiProject 'Microsoft.MachineLearningServices/workspaces@2023-08-01-preview' = { + name: aiProjectName + location: location + tags: union(tags, { + ProjectConnectionString: projectConnectionString + StandardConnectionString: standardConnectionString + }) + identity: { + type: 'SystemAssigned' + } + properties: { + // organization + friendlyName: aiProjectFriendlyName + description: aiProjectDescription + + // dependent resources + hubResourceId: aiHubId + } + kind: 'project' + + // Resource definition for the capability host + resource capabilityHost 'capabilityHosts@2024-10-01-preview' = { + name: '${aiProjectName}-${capabilityHostName}' + properties: { + capabilityHostKind: 'Agents' + aiServicesConnections: aiServiceConnections + vectorStoreConnections: vectorStoreConnections + storageConnections: storageConnections + } + } +} + +output aiProjectName string = aiProject.name +output aiProjectResourceId string = aiProject.id +output aiProjectPrincipalId string = aiProject.identity.principalId +output projectConnectionString string = projectConnectionString diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-appsettings.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-appsettings.bicep new file mode 100644 index 0000000..cfb405c --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-appsettings.bicep @@ -0,0 +1,3 @@ +param appSettings array = [] + +output appSettings array = appSettings diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-plan.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-plan.bicep new file mode 100644 index 0000000..46ea1fb --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/appservice-plan.bicep @@ -0,0 +1,22 @@ +param name string +param location string +param tags object = {} +param sku object = { + name: 'EP1' + tier: 'ElasticPremium' +} + +module appServicePlan 'br/public:avm/res/web/serverfarm:0.4.1' = { + name: 'serverfarmDeployment' + params: { + name: name + location: location + tags: tags + skuName: sku.name + kind: 'linux' + zoneRedundant: false + } +} + +output id string = appServicePlan.outputs.resourceId +output name string = appServicePlan.outputs.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-app.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-app.bicep new file mode 100644 index 0000000..9682994 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-app.bicep @@ -0,0 +1,150 @@ +// container-app.bicep - Creates a Container App resource in a Container Apps Environment + +@description('The name of the container app') +param name string + +@description('The location for the resources') +param location string = resourceGroup().location + +@description('Tags for the resources') +param tags object = {} + +@description('The name of the container apps environment') +param containerAppsEnvironmentName string + +@description('The ID of the container registry') +param containerRegistryName string = '' + +@description('The name of the user-assigned managed identity') +param identityName string = '' + +@description('The container image to deploy') +param containerImage string + +@description('Target port for the container') +param targetPort int = 80 + +@description('Environment variables for the container') +param environmentVariables array = [] + +@description('CPU resources for the container') +param containerCpu string = '0.5' + +@description('Memory resources for the container') +param containerMemory string = '1.0Gi' + +@description('Minimum number of replicas') +param minReplicas int = 1 + +@description('Maximum number of replicas') +param maxReplicas int = 10 + +@description('Enable ingress for the container app') +param enableIngress bool = true + +@description('Additional secrets to be set on the container app') +param secrets array = [] + +@description('Enable custom scale rules') +param enableCustomScaleRule bool = false + +@description('Scale rule name') +param scaleRuleName string = '' + +@description('Scale rule type') +param scaleRuleType string = '' + +@description('Scale rule metadata') +param scaleRuleMetadata object = {} + +@description('Scale rule identity') +param scaleRuleIdentity string = '' + +@description('Make the container app visible externally') +param external bool = true + +@description('Probes to configure for the container app') +param probes array = [] + +var hasIdentity = !empty(identityName) +var hasRegistry = !empty(containerRegistryName) + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-11-02-preview' existing = { + name: containerAppsEnvironmentName +} + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (hasIdentity) { + name: identityName +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = if (hasRegistry) { + name: containerRegistryName +} + +resource containerApp 'Microsoft.App/containerApps@2023-11-02-preview' = { + name: name + location: location + tags: tags + identity: hasIdentity ? { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } : null + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + ingress: enableIngress ? { + external: external + targetPort: targetPort + transport: 'auto' + allowInsecure: false + } : null + registries: hasRegistry ? [ + { + #disable-next-line BCP318 + server: containerRegistry.properties.loginServer + identity: hasIdentity ? identity.id : null + } + ] : [] + secrets: secrets + activeRevisionsMode: 'Single' + } + template: { + containers: [ + { + name: name + image: containerImage + env: environmentVariables + resources: { + cpu: json(containerCpu) + memory: containerMemory + } + probes: !empty(probes) ? probes : [] + } + ] + scale: { + minReplicas: minReplicas + maxReplicas: maxReplicas + rules: enableCustomScaleRule ? [ + { + name: scaleRuleName + custom: { + type: scaleRuleType + metadata: scaleRuleMetadata + auth: !empty(scaleRuleIdentity) ? [ + { + secretRef: 'scale-rule-auth' + triggerParameter: 'userAssignedIdentity' + } + ] : [] + } + } + ] : [] + } + } + } +} + +output containerAppId string = containerApp.id +output containerAppFqdn string = enableIngress && external ? containerApp.properties.configuration.ingress.fqdn : '' diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps-environment.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps-environment.bicep new file mode 100644 index 0000000..2eb8e4e --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps-environment.bicep @@ -0,0 +1,45 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Log Analytics workspace resource ID') +param logAnalyticsWorkspaceId string + +@description('Virtual network resource ID') +param vnetId string = '' + +@description('Subnet name for infrastructure components') +param infrastructureSubnetName string = '' + +@description('Existing subnet ID for infrastructure resources. If not specified, a delegated subnet will be created.') +param infrastructureSubnetId string = '' + +@description('Whether to create the Container Apps Environment in an internal or external network. Default is external.') +param internal bool = false + +// Use existing subnet if provided, otherwise reference it from the VNET +var subnetId = !empty(infrastructureSubnetId) ? infrastructureSubnetId : !empty(vnetId) && !empty(infrastructureSubnetName) ? '${vnetId}/subnets/${infrastructureSubnetName}' : '' + +resource environment 'Microsoft.App/managedEnvironments@2023-05-01' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: reference(logAnalyticsWorkspaceId, '2021-06-01').customerId + sharedKey: listKeys(logAnalyticsWorkspaceId, '2021-06-01').primarySharedKey + } + } + vnetConfiguration: !empty(subnetId) ? { + infrastructureSubnetId: subnetId + internal: internal + } : null + } +} + +output id string = environment.id +output name string = environment.name +output defaultDomain string = environment.properties.defaultDomain +output staticIp string = environment.properties.staticIp diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps.bicep new file mode 100644 index 0000000..e4bbf89 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/container-apps.bicep @@ -0,0 +1,80 @@ +// container-apps.bicep - Create container apps environment and container registry + +@description('The name prefix for resources') +param name string = 'app' + +@description('The name of the container apps environment') +param containerAppsEnvironmentName string + +@description('The name of the container registry') +param containerRegistryName string + +@description('The location of the container apps environment') +param location string = resourceGroup().location + +@description('Tags for the resources') +param tags object = {} + +@description('Whether the container apps environment should be internal') +param internal bool = false + +@description('Resource ID of the virtual network subnet to use for the container apps environment') +param infrastructureSubnetId string = '' + +@description('Virtual network resource ID') +param vnetId string = '' + +@description('Subnet name for infrastructure components') +param infrastructureSubnetName string = '' + +// Create a Log Analytics workspace for the Container Apps environment +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: '${name}-logs' + location: location + tags: tags + properties: { + sku: { + name: 'PerGB2018' + } + retentionInDays: 30 + features: { + enableLogAccessUsingOnlyResourcePermissions: true + } + workspaceCapping: { + dailyQuotaGb: 1 + } + } +} + +// Deploy the Container Apps environment using the module +module containerAppsEnvironment '../host/container-apps-environment.bicep' = { + name: 'container-apps-environment-deploy' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceId: logAnalyticsWorkspace.id + infrastructureSubnetId: infrastructureSubnetId + vnetId: vnetId + infrastructureSubnetName: infrastructureSubnetName + internal: internal + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = { + name: containerRegistryName + location: location + tags: tags + sku: { + name: 'Basic' + } + properties: { + adminUserEnabled: false + } +} + +// Output necessary properties +output environmentName string = containerAppsEnvironment.outputs.name +output environmentDefaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output registryName string = containerRegistry.name +output registryLoginServer string = containerRegistry.properties.loginServer diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/functions-app.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/functions-app.bicep new file mode 100644 index 0000000..3e8a28f --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/host/functions-app.bicep @@ -0,0 +1,75 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param storageAccountName string +param virtualNetworkSubnetId string = '' + +@allowed(['SystemAssigned', 'UserAssigned']) +param identityType string + +@description('User assigned identity name') +param identityId string + +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param appSettings object = {} +param allowedOrigins array = [] + +var linuxFxVersion string = 'DOTNET-ISOLATED|8.0' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = { + name: storageAccountName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +module functions 'br/public:avm/res/web/site:0.16.0' = { + name: 'siteDeployment' + params: { + name: name + kind: kind + location: location + tags: tags + serverFarmResourceId: appServicePlanId + managedIdentities: { + userAssignedResourceIds: [identityId] + } + siteConfig: { + netFrameworkVersion: 'v8.0' + linuxFxVersion: linuxFxVersion + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + alwaysOn: true + use32BitWorkerProcess: false + ftpsState: 'FtpsOnly' + } + virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null + configs: [ + { + name: 'appsettings' + properties: union(appSettings, + { + FUNCTIONS_EXTENSION_VERSION: '~4' + FUNCTIONS_WORKER_RUNTIME: 'dotnet-isolated' + AzureWebJobsStorage__accountName: storageAccount.name + AzureWebJobsStorage__credential : 'managedidentity' + APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString + WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED: '1' + }) + } + ] + } +} + +output name string = functions.outputs.name +output uri string = 'https://${functions.outputs.defaultHostname}' +output identityPrincipalId string = identityType == 'SystemAssigned' ? functions.outputs.systemAssignedMIPrincipalId : '' +output id string = functions.outputs.resourceId diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/identity/user-assigned-identity.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/identity/user-assigned-identity.bicep new file mode 100644 index 0000000..8255649 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/identity/user-assigned-identity.bicep @@ -0,0 +1,14 @@ +param identityName string +param location string +param tags object = {} + +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = { + name: identityName + location: location + tags: tags +} + +output identityId string = userAssignedIdentity.id +output identityName string = userAssignedIdentity.name +output identityPrincipalId string = userAssignedIdentity.properties.principalId +output identityClientId string = userAssignedIdentity.properties.clientId diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/appinsights-access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/appinsights-access.bicep new file mode 100644 index 0000000..1073303 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/appinsights-access.bicep @@ -0,0 +1,20 @@ +param principalID string +param roleDefinitionID string +param appInsightsName string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: appInsightsName +} + +// Allow access from API to app insights using a managed identity and least priv role +resource appInsightsRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(applicationInsights.id, principalID, roleDefinitionID) + scope: applicationInsights + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) + principalId: principalID + principalType: 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal + } +} + +output ROLE_ASSIGNMENT_NAME string = appInsightsRoleAssignment.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/application-insights.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/application-insights.bicep new file mode 100644 index 0000000..f6d9ee5 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/application-insights.bicep @@ -0,0 +1,22 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param logAnalyticsWorkspaceId string +param disableLocalAuth bool = false + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + DisableLocalAuth: disableLocalAuth + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/loganalytics.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/loganalytics.bicep new file mode 100644 index 0000000..770544c --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/loganalytics.bicep @@ -0,0 +1,21 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/monitoring.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/monitoring.bicep new file mode 100644 index 0000000..c6d21bf --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/monitor/monitoring.bicep @@ -0,0 +1,31 @@ +param logAnalyticsName string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} +param disableLocalAuth bool = false + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'application-insights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + logAnalyticsWorkspaceId: logAnalytics.outputs.id + disableLocalAuth: disableLocalAuth + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/networking/vnet.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/networking/vnet.bicep new file mode 100644 index 0000000..c26d44f --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/networking/vnet.bicep @@ -0,0 +1,57 @@ +// vnet.bicep - Create a virtual network with subnets + +@description('Name of the virtual network') +param name string + +@description('Location for the virtual network') +param location string = resourceGroup().location + +@description('Tags for the resources') +param tags object = {} + +@description('Address prefix for the virtual network') +param vnetAddressPrefix string = '10.0.0.0/16' + +@description('Address prefix for the infrastructure subnet') +param infrastructureSubnetPrefix string = '10.0.0.0/23' + +@description('Address prefix for the app subnet') +param appSubnetPrefix string = '10.0.2.0/23' + +var infrastructureSubnetName = 'infrastructure-subnet' +var appSubnetName = 'app-subnet' + +resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = { + name: name + location: location + tags: tags + properties: { + addressSpace: { + addressPrefixes: [ + vnetAddressPrefix + ] + } + subnets: [ + { + name: infrastructureSubnetName + properties: { + addressPrefix: infrastructureSubnetPrefix + // Don't pre-delegate the subnet - Container Apps will handle this + } + } + { + name: appSubnetName + properties: { + addressPrefix: appSubnetPrefix + } + } + ] + } +} + +output vnetId string = vnet.id +output vnetName string = vnet.name +output infrastructureSubnetName string = infrastructureSubnetName +output infrastructureSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', name, infrastructureSubnetName) +output appSubnetName string = appSubnetName +output appSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', name, appSubnetName) diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/registry-access.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/registry-access.bicep new file mode 100644 index 0000000..819681f --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/registry-access.bicep @@ -0,0 +1,25 @@ +// registry-access.bicep - Grant ACR Pull access to a principal + +@description('The name of the container registry') +param containerRegistryName string + +@description('The principal ID to assign ACR Pull role to') +param principalId string + +var acrPullRoleDefinitionId = '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull role + +resource registry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = { + name: containerRegistryName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(registry.id, principalId, acrPullRoleDefinitionId) + scope: registry + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', acrPullRoleDefinitionId) + principalType: 'ServicePrincipal' + } +} + +output roleAssignmentId string = roleAssignment.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/role.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/role.bicep new file mode 100644 index 0000000..e7658a6 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/security/role.bicep @@ -0,0 +1,21 @@ +// role.bicep - Assign a role to a principal on a resource + +@description('The principal ID to assign the role to') +param principalId string + +@description('The role definition ID to assign') +param roleDefinitionId string + +@description('The type of the principal (User, Group, ServicePrincipal)') +param principalType string = 'ServicePrincipal' + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, principalId, roleDefinitionId) + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + principalId: principalId + principalType: principalType + } +} + +output roleAssignmentId string = roleAssignment.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/storage/storage-account.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/storage/storage-account.bicep new file mode 100644 index 0000000..85e651d --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/core/storage/storage-account.bicep @@ -0,0 +1,48 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param allowBlobPublicAccess bool = false +param containers array = [] +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param sku object = { name: 'Standard_LRS' } +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} + +resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + minimumTlsVersion: minimumTlsVersion + allowBlobPublicAccess: allowBlobPublicAccess + allowSharedKeyAccess: false + networkAcls: networkAcls + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: container.?publicAccess ?? 'None' + } + }] + } + + resource queueServices 'queueServices' = { + name: 'default' + resource queue 'queues' = [for queueName in ['input', 'output']: { + name: queueName + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints +output id string = storage.id diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.bicep b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.bicep new file mode 100644 index 0000000..1668509 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.bicep @@ -0,0 +1,429 @@ +targetScope = 'subscription' + +// The main bicep module to provision Azure resources. +// For a more complete walkthrough to understand how this file works with azd, +// see https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/make-azd-compatible?pivots=azd-create + +// For a more complete walkthrough to understand how this file works with azd, +// see https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/make-azd-compatible?pivots=azd-create + +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string + +@description('Id of the user identity to be used for testing and debugging. This is not required in production. Leave empty if not needed.') +param principalId string = deployer().objectId + +@description('Name for the AI project resources.') +param aiProjectName string = 'project-demo' + +@description('Friendly name for your Azure AI resource') +param aiProjectFriendlyName string = 'Agents Project resource' + +@description('Description of your Azure AI resource displayed in AI studio') +param aiProjectDescription string = 'This is an example AI Project resource for use in Azure AI Studio.' + +@description('Name of the Azure AI Search account') +param aiSearchName string = 'agent-ai-search' + +@description('Name for capabilityHost.') +param accountCapabilityHostName string = 'caphostacc' + +@description('Name for capabilityHost.') +param projectCapabilityHostName string = 'caphostproj' + +@description('Name of the Azure AI Services account') +param aiServicesName string = 'agent-ai-services' + +@description('Model name for deployment') +param modelName string = 'gpt-4.1-mini' + +@description('Model format for deployment') +param modelFormat string = 'OpenAI' + +@description('Model version for deployment') +param modelVersion string = '2025-04-14' + +@description('Model deployment SKU name') +param modelSkuName string = 'GlobalStandard' + +@description('Model deployment capacity') +param modelCapacity int = 50 + +@description('Name of the Cosmos DB account for agent thread storage') +param cosmosDbName string = 'agent-ai-cosmos' + +@description('The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiServiceAccountResourceId string = '' + +@description('The Ai Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiSearchServiceResourceId string = '' + +@description('The Ai Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiStorageAccountResourceId string = '' + +@description('The Cosmos DB Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') +param aiCosmosDbAccountResourceId string = '' + +var projectName = toLower('${aiProjectName}') +param vnetEnabled bool + +param containerAppsEnvName string = '' +param containerAppsAppName string = '' +param containerRegistryName string = '' +param dtsLocation string = 'centralus' +param dtsSkuName string = 'Dedicated' +param dtsCapacity int = 1 +param dtsName string = '' +param taskHubName string = '' +var deploymentStorageContainerName = 'app-package-${take(workerServiceName, 32)}-${take(toLower(uniqueString(workerServiceName, resourceToken)), 7)}' +param storageAccountName string = '' +param clientsServiceName string = 'client' +param workerServiceName string = 'worker' +// Create a short, unique suffix, that will be unique to each resource group +var uniqueSuffix = toLower(uniqueString(subscription().id, environmentName, location)) + +// Optional parameters to override the default azd resource naming conventions. +// Add the following to main.parameters.json to provide values: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param resourceGroupName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') + +// tags that should be applied to all resources. +var tags = { + // Tag all resources with the environment name. + 'azd-env-name': environmentName +} + +// Generate a unique token to be used in naming resources. +// Remove linter suppression after using. +#disable-next-line no-unused-vars +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) + +// Organize resources in a resource group +resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' + location: location + tags: tags +} + +// Add resources to be provisioned below. +// A full example that leverages azd bicep modules can be seen in the todo-python-mongo template: +// https://github.com/Azure-Samples/todo-python-mongo/tree/main/infra + +// Create a user assigned identity +module identity './app/user-assigned-identity.bicep' = { + name: 'identity' + scope: rg + params: { + name: 'dts-ca-identity' + } +} + +module identityAssignDTS './core/security/role.bicep' = { + name: 'identityAssignDTS' + scope: rg + params: { + principalId: identity.outputs.principalId + roleDefinitionId: '0ad04412-c4d5-4796-b79c-f76d14c8d402' + principalType: 'ServicePrincipal' + } +} + +module identityAssignDTSDash './core/security/role.bicep' = { + name: 'identityAssignDTSDash' + scope: rg + params: { + principalId: principalId + roleDefinitionId: '0ad04412-c4d5-4796-b79c-f76d14c8d402' + principalType: 'User' + } +} + +// Create virtual network with subnets for Container Apps +module vnet './core/networking/vnet.bicep' = { + name: 'vnet' + scope: rg + params: { + name: '${abbrs.networkVirtualNetworks}${resourceToken}' + location: location + tags: tags + } +} + +// Container apps env and registry +module containerAppsEnv './core/host/container-apps.bicep' = { + name: 'container-apps' + scope: rg + params: { + name: 'app' + containerAppsEnvironmentName: !empty(containerAppsEnvName) ? containerAppsEnvName : '${abbrs.appManagedEnvironments}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + location: location + tags: tags + internal: false + vnetId: vnet.outputs.vnetId + infrastructureSubnetName: 'infrastructure-subnet' + infrastructureSubnetId: vnet.outputs.infrastructureSubnetId + } +} + +module dts './app/dts.bicep' = { + scope: rg + name: 'dtsResource' + params: { + name: !empty(dtsName) ? dtsName : '${abbrs.dts}${resourceToken}' + taskhubname: !empty(taskHubName) ? taskHubName : '${abbrs.taskhub}${resourceToken}' + location: dtsLocation + tags: tags + ipAllowlist: [ + '0.0.0.0/0' + ] + skuName: dtsSkuName + skuCapacity: dtsCapacity + } +} + + +// Client registry access must be deployed before client container app +module clientRegistryAccess 'app/registry-access.bicep' = { + name: 'client-registry-access' + scope: rg + params: { + containerRegistryName: containerAppsEnv.outputs.registryName + principalID: identity.outputs.principalId + principalType: 'ServicePrincipal' + } +} + +// Container app +module client 'app/app.bicep' = { + name: clientsServiceName + scope: rg + params: { + appName: !empty(containerAppsAppName) ? '${containerAppsAppName}-client' : '${abbrs.appContainerApps}${resourceToken}-client' + containerAppsEnvironmentName: containerAppsEnv.outputs.environmentName + containerRegistryName: containerAppsEnv.outputs.registryName + userAssignedManagedIdentity: { + resourceId: identity.outputs.resourceId + clientId: identity.outputs.clientId + } + location: location + tags: tags + serviceName: 'client' + identityName: identity.outputs.name + dtsEndpoint: dts.outputs.dts_URL + taskHubName: dts.outputs.TASKHUB_NAME + AGENT_CONNECTION_STRING: aiProject.outputs.projectEndpoint + OPENAI_DEPLOYMENT_NAME: modelName + AGENT_CONNECTION_STRING__clientId: identity.outputs.clientId + clientBaseUrl: '' // Not used by client + } + dependsOn: [ + clientRegistryAccess // Make sure registry access is set up before deploying the app + ] +} + +// Give the worker access to ACR +module workerRegistryAccess 'app/registry-access.bicep' = { + name: 'worker-registry-access' + scope: rg + params: { + containerRegistryName: containerAppsEnv.outputs.registryName + principalID: identity.outputs.principalId + principalType: 'ServicePrincipal' + } +} + + // Container app +module worker 'app/app.bicep' = { + name: workerServiceName + scope: rg + params: { + appName: !empty(containerAppsAppName) ? '${containerAppsAppName}-worker' : '${abbrs.appContainerApps}${resourceToken}-worker' + containerAppsEnvironmentName: containerAppsEnv.outputs.environmentName + containerRegistryName: containerAppsEnv.outputs.registryName + userAssignedManagedIdentity: { + resourceId: identity.outputs.resourceId + clientId: identity.outputs.clientId + } + location: location + tags: tags + serviceName: 'worker' + identityName: identity.outputs.name + dtsEndpoint: dts.outputs.dts_URL + taskHubName: dts.outputs.TASKHUB_NAME + // Use the connection string in URL format for direct client usage + AGENT_CONNECTION_STRING: aiProject.outputs.projectEndpoint + OPENAI_DEPLOYMENT_NAME: modelName + AGENT_CONNECTION_STRING__clientId: identity.outputs.clientId + DALLE_ENDPOINT: aiDependencies.outputs.dalleEndpoint + clientBaseUrl: 'https://${client.outputs.endpoint}' + } +} + +// Dependent resources for the Azure Machine Learning workspace +module aiDependencies './agent/standard-dependent-resources.bicep' = { + name: 'dependencies${projectName}${uniqueSuffix}deployment' + scope: rg + params: { + location: location + storageName: 'stai${uniqueSuffix}' + aiServicesName: '${aiServicesName}${uniqueSuffix}' + aiSearchName: '${aiSearchName}${uniqueSuffix}' + cosmosDbName: '${cosmosDbName}${uniqueSuffix}' + tags: tags + + // Model deployment parameters + modelName: modelName + modelFormat: modelFormat + modelVersion: modelVersion + modelSkuName: modelSkuName + modelCapacity: modelCapacity + modelLocation: location + + aiServiceAccountResourceId: aiServiceAccountResourceId + aiSearchServiceResourceId: aiSearchServiceResourceId + aiStorageAccountResourceId: aiStorageAccountResourceId + aiCosmosDbAccountResourceId: aiCosmosDbAccountResourceId + } +} + +module aiProject './agent/standard-ai-project.bicep' = { + name: '${projectName}${uniqueSuffix}deployment' + scope: rg + params: { + // workspace organization + aiServicesAccountName: aiDependencies.outputs.aiServicesName + aiProjectName: '${projectName}${uniqueSuffix}' + aiProjectFriendlyName: aiProjectFriendlyName + aiProjectDescription: aiProjectDescription + location: location + tags: tags + + // dependent resources + aiSearchName: aiDependencies.outputs.aiSearchName + aiSearchSubscriptionId: aiDependencies.outputs.aiSearchServiceSubscriptionId + aiSearchResourceGroupName: aiDependencies.outputs.aiSearchServiceResourceGroupName + storageAccountName: aiDependencies.outputs.storageAccountName + storageAccountSubscriptionId: aiDependencies.outputs.storageAccountSubscriptionId + storageAccountResourceGroupName: aiDependencies.outputs.storageAccountResourceGroupName + cosmosDbAccountName: aiDependencies.outputs.cosmosDbAccountName + cosmosDbAccountSubscriptionId: aiDependencies.outputs.cosmosDbAccountSubscriptionId + cosmosDbAccountResourceGroupName: aiDependencies.outputs.cosmosDbAccountResourceGroupName + } +} + +module projectRoleAssignments './agent/standard-ai-project-role-assignments.bicep' = { + name: 'aiprojectroleassignments${projectName}${uniqueSuffix}deployment' + scope: rg + params: { + aiProjectPrincipalId: aiProject.outputs.aiProjectPrincipalId + userPrincipalId: principalId + allowUserIdentityPrincipal: true // Enable user identity role assignments + aiServicesName: aiDependencies.outputs.aiServicesName + aiSearchName: aiDependencies.outputs.aiSearchName + aiCosmosDbName: aiDependencies.outputs.cosmosDbAccountName + integrationStorageAccountName: storage.outputs.name + aiStorageAccountName: aiDependencies.outputs.storageAccountName + functionAppManagedIdentityPrincipalId: identity.outputs.principalId + allowFunctionAppIdentityPrincipal: true // Enable function app identity role assignments + dalleAiServicesId: aiDependencies.outputs.dalleAiServicesId + } +} + +module aiProjectCapabilityHost './agent/standard-ai-project-capability-host.bicep' = { + name: 'capabilityhost${projectName}${uniqueSuffix}deployment' + scope: rg + params: { + aiServicesAccountName: aiDependencies.outputs.aiServicesName + projectName: aiProject.outputs.aiProjectName + aiSearchConnection: aiProject.outputs.aiSearchConnection + azureStorageConnection: aiProject.outputs.azureStorageConnection + cosmosDbConnection: aiProject.outputs.cosmosDbConnection + + accountCapHost: '${accountCapabilityHostName}${uniqueSuffix}' + projectCapHost: '${projectCapabilityHostName}${uniqueSuffix}' + } + dependsOn: [ projectRoleAssignments ] +} + +module postCapabilityHostCreationRoleAssignments './agent/post-capability-host-role-assignments.bicep' = { + name: 'postcaphostra${projectName}${uniqueSuffix}deployment' + scope: rg + params: { + aiProjectPrincipalId: aiProject.outputs.aiProjectPrincipalId + aiProjectWorkspaceId: aiProject.outputs.projectWorkspaceId + aiStorageAccountName: aiDependencies.outputs.storageAccountName + cosmosDbAccountName: aiDependencies.outputs.cosmosDbAccountName + } + dependsOn: [ aiProjectCapabilityHost ] +} + +// Backing storage for Azure functions backend API +module storage 'br/public:avm/res/storage/storage-account:0.8.3' = { + name: 'storage' + scope: rg + params: { + name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}' + allowBlobPublicAccess: false + allowSharedKeyAccess: false // Disable local authentication methods as per policy + dnsEndpointType: 'Standard' + publicNetworkAccess: vnetEnabled ? 'Disabled' : 'Enabled' + // Explicitly disable the property that can't be updated + requireInfrastructureEncryption: false + // When vNet is enabled, restrict access but allow Azure services and specifically grant access to the AI Agent service + networkAcls: vnetEnabled ? { + defaultAction: 'Deny' + bypass: 'AzureServices' // Allow Azure services including AI Agent service + resourceAccessRules: [ + { + tenantId: tenant().tenantId + resourceId: aiDependencies.outputs.aiservicesID // Grant explicit access to AI Agent service + } + ] + } : { + defaultAction: 'Allow' + bypass: 'AzureServices' + } + blobServices: { + containers: [{name: deploymentStorageContainerName}] + } + queueServices: { + queues: [ + { name: 'input' } + { name: 'output' } + ] + } + minimumTlsVersion: 'TLS1_2' // Enforcing TLS 1.2 for better security + location: location + tags: tags + } +} + + +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +// Container outputs +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerAppsEnv.outputs.registryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = containerAppsEnv.outputs.registryName + +// Application outputs +output AZURE_CONTAINER_APP_ENDPOINT string = client.outputs.endpoint +output AZURE_CONTAINER_ENVIRONMENT_NAME string = client.outputs.envName +output DTS_URL string = dts.outputs.dts_URL +output TASKHUB_NAME string = dts.outputs.TASKHUB_NAME + +// AI Project outputs +output AI_PROJECT_NAME string = aiProject.outputs.aiProjectName + +// Identity outputs +output AZURE_USER_ASSIGNED_IDENTITY_NAME string = identity.outputs.name diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.json b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.json new file mode 100644 index 0000000..7a21100 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.json @@ -0,0 +1,3949 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5767556487397985243" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "containerAppsEnvName": { + "type": "string", + "defaultValue": "" + }, + "containerAppsAppName": { + "type": "string", + "defaultValue": "" + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "" + }, + "dtsLocation": { + "type": "string", + "defaultValue": "centralus" + }, + "dtsSkuName": { + "type": "string", + "defaultValue": "Dedicated" + }, + "dtsCapacity": { + "type": "int", + "defaultValue": 1 + }, + "dtsName": { + "type": "string", + "defaultValue": "" + }, + "taskHubName": { + "type": "string", + "defaultValue": "" + }, + "clientsServiceName": { + "type": "string", + "defaultValue": "client" + }, + "workerServiceName": { + "type": "string", + "defaultValue": "worker" + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "" + }, + "name": { + "type": "string", + "defaultValue": "aihub", + "metadata": { + "description": "Base name for AI resources" + } + }, + "projectName": { + "type": "string", + "defaultValue": "aiproj", + "metadata": { + "description": "Name for AI project" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "Agent Chaining Hub", + "metadata": { + "description": "AI Hub friendly name" + } + }, + "aiHubDescription": { + "type": "string", + "defaultValue": "Hub for AI agent capabilities", + "metadata": { + "description": "AI Hub description" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "Agent Chaining Project", + "metadata": { + "description": "AI Project friendly name" + } + }, + "aiProjectDescription": { + "type": "string", + "defaultValue": "Project for AI agent capabilities", + "metadata": { + "description": "AI Project description" + } + }, + "capabilityHostName": { + "type": "string", + "defaultValue": "agent-host", + "metadata": { + "description": "Capability host name" + } + }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4o-mini" + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI" + }, + "modelVersion": { + "type": "string", + "defaultValue": "2024-07-18" + }, + "modelSkuName": { + "type": "string", + "defaultValue": "Standard" + }, + "modelCapacity": { + "type": "int", + "defaultValue": 20 + }, + "modelLocation": { + "type": "string", + "defaultValue": "[parameters('location')]" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "cognitiveServicesSpeech": "cog-sp-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-", + "dts": "dts-", + "taskhub": "taskhub-" + }, + "abbrs": "[variables('$fxv#0')]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + }, + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "uniqueSuffix": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "storageBlobDataContributorRole": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "storageQueueDataContributorRoleDefinitionId": "974c5e8b-45b9-4653-ba55-5f855dd0fb88", + "storageTableDataContributorRoleDefinitionId": "0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3" + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2021-04-01", + "name": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "location": "[parameters('location')]", + "tags": "[variables('tags')]" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "identity", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "dts-ca-identity" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5039160413996154793" + }, + "description": "Creates a Microsoft Entra user-assigned identity." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]" + }, + "clientId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').clientId]" + }, + "tenantId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').tenantId]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "identityAssignDTS", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.principalId.value]" + }, + "roleDefinitionId": { + "value": "0ad04412-c4d5-4796-b79c-f76d14c8d402" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "4856410466654776593" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceGroup().id, parameters('principalId'), parameters('roleDefinitionId'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceGroup().id, parameters('principalId'), parameters('roleDefinitionId')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "identityAssignDTSDash", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('principalId')]" + }, + "roleDefinitionId": { + "value": "0ad04412-c4d5-4796-b79c-f76d14c8d402" + }, + "principalType": { + "value": "User" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "4856410466654776593" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceGroup().id, parameters('principalId'), parameters('roleDefinitionId'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[resourceId('Microsoft.Authorization/roleAssignments', guid(resourceGroup().id, parameters('principalId'), parameters('roleDefinitionId')))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "vnet", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}{1}', variables('abbrs').networkVirtualNetworks, variables('resourceToken'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "13244175646240278370" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the virtual network" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for the virtual network" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the resources" + } + }, + "vnetAddressPrefix": { + "type": "string", + "defaultValue": "10.0.0.0/16", + "metadata": { + "description": "Address prefix for the virtual network" + } + }, + "infrastructureSubnetPrefix": { + "type": "string", + "defaultValue": "10.0.0.0/23", + "metadata": { + "description": "Address prefix for the infrastructure subnet" + } + }, + "appSubnetPrefix": { + "type": "string", + "defaultValue": "10.0.2.0/23", + "metadata": { + "description": "Address prefix for the app subnet" + } + } + }, + "variables": { + "infrastructureSubnetName": "infrastructure-subnet", + "appSubnetName": "app-subnet" + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('vnetAddressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('infrastructureSubnetName')]", + "properties": { + "addressPrefix": "[parameters('infrastructureSubnetPrefix')]" + } + }, + { + "name": "[variables('appSubnetName')]", + "properties": { + "addressPrefix": "[parameters('appSubnetPrefix')]" + } + } + ] + } + } + ], + "outputs": { + "vnetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "vnetName": { + "type": "string", + "value": "[parameters('name')]" + }, + "infrastructureSubnetName": { + "type": "string", + "value": "[variables('infrastructureSubnetName')]" + }, + "infrastructureSubnetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), variables('infrastructureSubnetName'))]" + }, + "appSubnetName": { + "type": "string", + "value": "[variables('appSubnetName')]" + }, + "appSubnetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), variables('appSubnetName'))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "app" + }, + "containerAppsEnvironmentName": "[if(not(empty(parameters('containerAppsEnvName'))), createObject('value', parameters('containerAppsEnvName')), createObject('value', format('{0}{1}', variables('abbrs').appManagedEnvironments, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "internal": { + "value": false + }, + "vnetId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'vnet'), '2022-09-01').outputs.vnetId.value]" + }, + "infrastructureSubnetName": { + "value": "infrastructure-subnet" + }, + "infrastructureSubnetId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'vnet'), '2022-09-01').outputs.infrastructureSubnetId.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.34.44.8038", + "templateHash": "5726479277483441007" + } + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "app", + "metadata": { + "description": "The name prefix for resources" + } + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "The name of the container apps environment" + } + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name of the container registry" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the container apps environment" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the resources" + } + }, + "internal": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether the container apps environment should be internal" + } + }, + "infrastructureSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the virtual network subnet to use for the container apps environment" + } + }, + "vnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Virtual network resource ID" + } + }, + "infrastructureSubnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet name for infrastructure components" + } + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2022-10-01", + "name": "[format('{0}-logs', parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "sku": { + "name": "PerGB2018" + }, + "retentionInDays": 30, + "features": { + "enableLogAccessUsingOnlyResourcePermissions": true + }, + "workspaceCapping": { + "dailyQuotaGb": 1 + } + } + }, + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-11-01-preview", + "name": "[parameters('containerRegistryName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "Basic" + }, + "properties": { + "adminUserEnabled": false + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps-environment-deploy", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', format('{0}-logs', parameters('name')))]" + }, + "infrastructureSubnetId": { + "value": "[parameters('infrastructureSubnetId')]" + }, + "vnetId": { + "value": "[parameters('vnetId')]" + }, + "infrastructureSubnetName": { + "value": "[parameters('infrastructureSubnetName')]" + }, + "internal": { + "value": "[parameters('internal')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "9322202461622995362" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Log Analytics workspace resource ID" + } + }, + "vnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Virtual network resource ID" + } + }, + "infrastructureSubnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet name for infrastructure components" + } + }, + "infrastructureSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Existing subnet ID for infrastructure resources. If not specified, a delegated subnet will be created." + } + }, + "internal": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to create the Container Apps Environment in an internal or external network. Default is external." + } + } + }, + "variables": { + "subnetId": "[if(not(empty(parameters('infrastructureSubnetId'))), parameters('infrastructureSubnetId'), if(and(not(empty(parameters('vnetId'))), not(empty(parameters('infrastructureSubnetName')))), format('{0}/subnets/{1}', parameters('vnetId'), parameters('infrastructureSubnetName')), ''))]" + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(parameters('logAnalyticsWorkspaceId'), '2021-06-01').customerId]", + "sharedKey": "[listKeys(parameters('logAnalyticsWorkspaceId'), '2021-06-01').primarySharedKey]" + } + }, + "vnetConfiguration": "[if(not(empty(variables('subnetId'))), createObject('infrastructureSubnetId', variables('subnetId'), 'internal', parameters('internal')), null())]" + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-05-01').defaultDomain]" + }, + "staticIp": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-05-01').staticIp]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', format('{0}-logs', parameters('name')))]" + ] + } + ], + "outputs": { + "environmentName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps-environment-deploy'), '2022-09-01').outputs.name.value]" + }, + "environmentDefaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps-environment-deploy'), '2022-09-01').outputs.defaultDomain.value]" + }, + "registryName": { + "type": "string", + "value": "[parameters('containerRegistryName')]" + }, + "registryLoginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), '2023-11-01-preview').loginServer]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'vnet')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dtsResource", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('dtsName'))), createObject('value', parameters('dtsName')), createObject('value', format('{0}{1}', variables('abbrs').dts, variables('resourceToken'))))]", + "taskhubname": "[if(not(empty(parameters('taskHubName'))), createObject('value', parameters('taskHubName')), createObject('value', format('{0}{1}', variables('abbrs').taskhub, variables('resourceToken'))))]", + "location": { + "value": "[parameters('dtsLocation')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "ipAllowlist": { + "value": [ + "0.0.0.0/0" + ] + }, + "skuName": { + "value": "[parameters('dtsSkuName')]" + }, + "skuCapacity": { + "value": "[parameters('dtsCapacity')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "13524563139135649995" + } + }, + "parameters": { + "ipAllowlist": { + "type": "array" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "name": { + "type": "string" + }, + "taskhubname": { + "type": "string" + }, + "skuName": { + "type": "string" + }, + "skuCapacity": { + "type": "int" + } + }, + "resources": [ + { + "type": "Microsoft.DurableTask/schedulers", + "apiVersion": "2024-10-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "ipAllowlist": "[parameters('ipAllowlist')]", + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]" + } + } + }, + { + "type": "Microsoft.DurableTask/schedulers/taskHubs", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('name'), parameters('taskhubname'))]", + "dependsOn": [ + "[resourceId('Microsoft.DurableTask/schedulers', parameters('name'))]" + ] + } + ], + "outputs": { + "dts_NAME": { + "type": "string", + "value": "[parameters('name')]" + }, + "dts_URL": { + "type": "string", + "value": "[reference(resourceId('Microsoft.DurableTask/schedulers', parameters('name')), '2024-10-01-preview').endpoint]" + }, + "TASKHUB_NAME": { + "type": "string", + "value": "[parameters('taskhubname')]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[parameters('clientsServiceName')]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "appName": "[if(not(empty(parameters('containerAppsAppName'))), createObject('value', format('{0}-client', parameters('containerAppsAppName'))), createObject('value', format('{0}{1}-client', variables('abbrs').appContainerApps, variables('resourceToken'))))]", + "containerAppsEnvironmentName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "containerRegistryName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "userAssignedManagedIdentity": { + "value": { + "resourceId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.resourceId.value]", + "clientId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.clientId.value]" + } + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "serviceName": { + "value": "client" + }, + "exists": { + "value": false + }, + "identityName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.name.value]" + }, + "dtsEndpoint": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.dts_URL.value]" + }, + "taskHubName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.TASKHUB_NAME.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "6029474384670576685" + } + }, + "definitions": { + "managedIdentity": { + "type": "object", + "properties": { + "resourceId": { + "type": "string" + }, + "clientId": { + "type": "string" + } + } + } + }, + "parameters": { + "appName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "identityName": { + "type": "string" + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "containerRegistryName": { + "type": "string" + }, + "serviceName": { + "type": "string", + "defaultValue": "aca" + }, + "exists": { + "type": "bool" + }, + "dtsEndpoint": { + "type": "string" + }, + "taskHubName": { + "type": "string" + }, + "agentConnectionString": { + "type": "string", + "defaultValue": "" + }, + "userAssignedManagedIdentity": { + "$ref": "#/definitions/managedIdentity", + "metadata": { + "description": "Unique identifier for user-assigned managed identity." + } + } + }, + "resources": { + "containerAppsApp": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('container-apps-{0}', parameters('serviceName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('appName')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(parameters('tags'), createObject('azd-service-name', parameters('serviceName')))]" + }, + "enableIngress": { + "value": true + }, + "external": { + "value": true + }, + "containerImage": { + "value": "mcr.microsoft.com/dotnet/aspnet:8.0" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "minReplicas": { + "value": 1 + }, + "maxReplicas": { + "value": 10 + }, + "environmentVariables": { + "value": [ + { + "name": "AZURE_MANAGED_IDENTITY_CLIENT_ID", + "secretRef": "azure-managed-identity-client-id" + }, + { + "name": "ENDPOINT", + "value": "[format('Endpoint={0};Authentication=ManagedIdentity;ClientID={1}', parameters('dtsEndpoint'), parameters('userAssignedManagedIdentity').clientId)]" + }, + { + "name": "TASKHUB", + "value": "[parameters('taskHubName')]" + }, + { + "name": "AGENT_CONNECTION_STRING", + "value": "[if(not(empty(parameters('agentConnectionString'))), parameters('agentConnectionString'), '')]" + } + ] + }, + "secrets": { + "value": [ + { + "name": "azure-managed-identity-client-id", + "value": "[parameters('userAssignedManagedIdentity').clientId]" + } + ] + }, + "enableCustomScaleRule": { + "value": false + }, + "scaleRuleName": { + "value": "dtsscaler-orchestration" + }, + "scaleRuleType": { + "value": "azure-durabletask-scheduler" + }, + "scaleRuleMetadata": { + "value": { + "endpoint": "[parameters('dtsEndpoint')]", + "maxConcurrentWorkItemsCount": "1", + "taskhubName": "[parameters('taskHubName')]", + "workItemType": "Orchestration" + } + }, + "scaleRuleIdentity": { + "value": "[parameters('userAssignedManagedIdentity').resourceId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "17598589017450875416" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location for the resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the resources" + } + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "The name of the container apps environment" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The ID of the container registry" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned managed identity" + } + }, + "containerImage": { + "type": "string", + "metadata": { + "description": "The container image to deploy" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for the container" + } + }, + "environmentVariables": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Environment variables for the container" + } + }, + "containerCpu": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "CPU resources for the container" + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "Memory resources for the container" + } + }, + "minReplicas": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Minimum number of replicas" + } + }, + "maxReplicas": { + "type": "int", + "defaultValue": 10, + "metadata": { + "description": "Maximum number of replicas" + } + }, + "enableIngress": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable ingress for the container app" + } + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Additional secrets to be set on the container app" + } + }, + "enableCustomScaleRule": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable custom scale rules" + } + }, + "scaleRuleName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule name" + } + }, + "scaleRuleType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule type" + } + }, + "scaleRuleMetadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Scale rule metadata" + } + }, + "scaleRuleIdentity": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule identity" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Make the container app visible externally" + } + } + }, + "variables": { + "hasIdentity": "[not(empty(parameters('identityName')))]", + "hasRegistry": "[not(empty(parameters('containerRegistryName')))]" + }, + "resources": [ + { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-11-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[if(variables('hasIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))), createObject())), null())]", + "properties": { + "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName'))]", + "configuration": { + "ingress": "[if(parameters('enableIngress'), createObject('external', parameters('external'), 'targetPort', parameters('targetPort'), 'transport', 'auto', 'allowInsecure', false()), null())]", + "registries": "[if(variables('hasRegistry'), createArray(createObject('server', reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), '2023-11-01-preview').loginServer, 'identity', if(variables('hasIdentity'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), null()))), createArray())]", + "secrets": "[parameters('secrets')]" + }, + "template": { + "containers": [ + { + "name": "[parameters('name')]", + "image": "[parameters('containerImage')]", + "env": "[parameters('environmentVariables')]", + "resources": { + "cpu": "[json(parameters('containerCpu'))]", + "memory": "[parameters('containerMemory')]" + } + } + ], + "scale": { + "minReplicas": "[parameters('minReplicas')]", + "maxReplicas": "[parameters('maxReplicas')]", + "rules": "[if(parameters('enableCustomScaleRule'), createArray(createObject('name', parameters('scaleRuleName'), 'custom', createObject('type', parameters('scaleRuleType'), 'metadata', parameters('scaleRuleMetadata'), 'auth', if(not(empty(parameters('scaleRuleIdentity'))), createArray(createObject('secretRef', 'scale-rule-auth', 'triggerParameter', 'userAssignedIdentity')), createArray())))), createArray())]" + } + } + } + } + ], + "outputs": { + "containerAppId": { + "type": "string", + "value": "[resourceId('Microsoft.App/containerApps', parameters('name'))]" + }, + "containerAppFqdn": { + "type": "string", + "value": "[if(and(parameters('enableIngress'), parameters('external')), reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-11-02-preview').configuration.ingress.fqdn, '')]" + } + } + } + } + } + }, + "outputs": { + "endpoint": { + "type": "string", + "value": "[reference('containerAppsApp').outputs.containerAppFqdn.value]" + }, + "envName": { + "type": "string", + "value": "[parameters('appName')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "client-registry-access", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "principalID": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.principalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "15121309236660803763" + } + }, + "parameters": { + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name of the Container Registry" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The Principal ID of the identity that needs access to the registry" + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "metadata": { + "description": "The type of the principal (User, ServicePrincipal, etc.)" + } + } + }, + "variables": { + "acrPullRoleDefinitionId": "7f951dda-4ed3-4680-a7ca-43fe172d538d" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), parameters('principalID'), variables('acrPullRoleDefinitionId'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('acrPullRoleDefinitionId'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), parameters('principalID'), variables('acrPullRoleDefinitionId')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[parameters('workerServiceName')]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "appName": "[if(not(empty(parameters('containerAppsAppName'))), createObject('value', format('{0}-worker', parameters('containerAppsAppName'))), createObject('value', format('{0}{1}-worker', variables('abbrs').appContainerApps, variables('resourceToken'))))]", + "containerAppsEnvironmentName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "containerRegistryName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "userAssignedManagedIdentity": { + "value": { + "resourceId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.resourceId.value]", + "clientId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.clientId.value]" + } + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "serviceName": { + "value": "worker" + }, + "exists": { + "value": false + }, + "identityName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.name.value]" + }, + "dtsEndpoint": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.dts_URL.value]" + }, + "taskHubName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.TASKHUB_NAME.value]" + }, + "agentConnectionString": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.projectConnectionString.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "6029474384670576685" + } + }, + "definitions": { + "managedIdentity": { + "type": "object", + "properties": { + "resourceId": { + "type": "string" + }, + "clientId": { + "type": "string" + } + } + } + }, + "parameters": { + "appName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "identityName": { + "type": "string" + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "containerRegistryName": { + "type": "string" + }, + "serviceName": { + "type": "string", + "defaultValue": "aca" + }, + "exists": { + "type": "bool" + }, + "dtsEndpoint": { + "type": "string" + }, + "taskHubName": { + "type": "string" + }, + "agentConnectionString": { + "type": "string", + "defaultValue": "" + }, + "userAssignedManagedIdentity": { + "$ref": "#/definitions/managedIdentity", + "metadata": { + "description": "Unique identifier for user-assigned managed identity." + } + } + }, + "resources": { + "containerAppsApp": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('container-apps-{0}', parameters('serviceName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('appName')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(parameters('tags'), createObject('azd-service-name', parameters('serviceName')))]" + }, + "enableIngress": { + "value": true + }, + "external": { + "value": true + }, + "containerImage": { + "value": "mcr.microsoft.com/dotnet/aspnet:8.0" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "minReplicas": { + "value": 1 + }, + "maxReplicas": { + "value": 10 + }, + "environmentVariables": { + "value": [ + { + "name": "AZURE_MANAGED_IDENTITY_CLIENT_ID", + "secretRef": "azure-managed-identity-client-id" + }, + { + "name": "ENDPOINT", + "value": "[format('Endpoint={0};Authentication=ManagedIdentity;ClientID={1}', parameters('dtsEndpoint'), parameters('userAssignedManagedIdentity').clientId)]" + }, + { + "name": "TASKHUB", + "value": "[parameters('taskHubName')]" + }, + { + "name": "AGENT_CONNECTION_STRING", + "value": "[if(not(empty(parameters('agentConnectionString'))), parameters('agentConnectionString'), '')]" + } + ] + }, + "secrets": { + "value": [ + { + "name": "azure-managed-identity-client-id", + "value": "[parameters('userAssignedManagedIdentity').clientId]" + } + ] + }, + "enableCustomScaleRule": { + "value": false + }, + "scaleRuleName": { + "value": "dtsscaler-orchestration" + }, + "scaleRuleType": { + "value": "azure-durabletask-scheduler" + }, + "scaleRuleMetadata": { + "value": { + "endpoint": "[parameters('dtsEndpoint')]", + "maxConcurrentWorkItemsCount": "1", + "taskhubName": "[parameters('taskHubName')]", + "workItemType": "Orchestration" + } + }, + "scaleRuleIdentity": { + "value": "[parameters('userAssignedManagedIdentity').resourceId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "17598589017450875416" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location for the resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the resources" + } + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "The name of the container apps environment" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The ID of the container registry" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned managed identity" + } + }, + "containerImage": { + "type": "string", + "metadata": { + "description": "The container image to deploy" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for the container" + } + }, + "environmentVariables": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Environment variables for the container" + } + }, + "containerCpu": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "CPU resources for the container" + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "Memory resources for the container" + } + }, + "minReplicas": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Minimum number of replicas" + } + }, + "maxReplicas": { + "type": "int", + "defaultValue": 10, + "metadata": { + "description": "Maximum number of replicas" + } + }, + "enableIngress": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable ingress for the container app" + } + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Additional secrets to be set on the container app" + } + }, + "enableCustomScaleRule": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable custom scale rules" + } + }, + "scaleRuleName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule name" + } + }, + "scaleRuleType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule type" + } + }, + "scaleRuleMetadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Scale rule metadata" + } + }, + "scaleRuleIdentity": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Scale rule identity" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Make the container app visible externally" + } + } + }, + "variables": { + "hasIdentity": "[not(empty(parameters('identityName')))]", + "hasRegistry": "[not(empty(parameters('containerRegistryName')))]" + }, + "resources": [ + { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-11-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[if(variables('hasIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))), createObject())), null())]", + "properties": { + "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName'))]", + "configuration": { + "ingress": "[if(parameters('enableIngress'), createObject('external', parameters('external'), 'targetPort', parameters('targetPort'), 'transport', 'auto', 'allowInsecure', false()), null())]", + "registries": "[if(variables('hasRegistry'), createArray(createObject('server', reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('containerRegistryName')), '2023-11-01-preview').loginServer, 'identity', if(variables('hasIdentity'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), null()))), createArray())]", + "secrets": "[parameters('secrets')]" + }, + "template": { + "containers": [ + { + "name": "[parameters('name')]", + "image": "[parameters('containerImage')]", + "env": "[parameters('environmentVariables')]", + "resources": { + "cpu": "[json(parameters('containerCpu'))]", + "memory": "[parameters('containerMemory')]" + } + } + ], + "scale": { + "minReplicas": "[parameters('minReplicas')]", + "maxReplicas": "[parameters('maxReplicas')]", + "rules": "[if(parameters('enableCustomScaleRule'), createArray(createObject('name', parameters('scaleRuleName'), 'custom', createObject('type', parameters('scaleRuleType'), 'metadata', parameters('scaleRuleMetadata'), 'auth', if(not(empty(parameters('scaleRuleIdentity'))), createArray(createObject('secretRef', 'scale-rule-auth', 'triggerParameter', 'userAssignedIdentity')), createArray())))), createArray())]" + } + } + } + } + ], + "outputs": { + "containerAppId": { + "type": "string", + "value": "[resourceId('Microsoft.App/containerApps', parameters('name'))]" + }, + "containerAppFqdn": { + "type": "string", + "value": "[if(and(parameters('enableIngress'), parameters('external')), reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-11-02-preview').configuration.ingress.fqdn, '')]" + } + } + } + } + } + }, + "outputs": { + "endpoint": { + "type": "string", + "value": "[reference('containerAppsApp').outputs.containerAppFqdn.value]" + }, + "envName": { + "type": "string", + "value": "[parameters('appName')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "worker-ai-roles", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.principalId.value]" + }, + "openAiResourceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiHubName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiHubName.value]" + }, + "aiProjectName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectName.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.34.44.8038", + "templateHash": "6757808878387663145" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID to assign roles to" + } + }, + "openAiResourceName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the OpenAI resource" + } + }, + "aiHubName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the AI Hub" + } + }, + "aiProjectName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the AI Project" + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('openAiResourceName')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAiResourceName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), parameters('principalId'), '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(empty(parameters('openAiResourceName')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAiResourceName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), parameters('principalId'), '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(empty(parameters('aiHubName')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.MachineLearningServices/workspaces/{0}', parameters('aiHubName'))]", + "name": "[guid(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName')), parameters('principalId'), 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(empty(parameters('aiProjectName')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.MachineLearningServices/workspaces/{0}', parameters('aiProjectName'))]", + "name": "[guid(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), parameters('principalId'), 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(empty(parameters('aiProjectName')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.MachineLearningServices/workspaces/{0}', parameters('aiProjectName'))]", + "name": "[guid(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), parameters('principalId'), 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "principalType": "ServicePrincipal" + } + } + ], + "outputs": { + "openAiUserRoleAssignmentId": { + "type": "string", + "value": "[if(not(empty(parameters('openAiResourceName'))), extensionResourceId(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), parameters('principalId'), '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')), '')]" + }, + "cognitiveServicesContributorRoleAssignmentId": { + "type": "string", + "value": "[if(not(empty(parameters('openAiResourceName'))), extensionResourceId(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiResourceName')), parameters('principalId'), '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')), '')]" + }, + "aiProjectReaderRoleAssignmentId": { + "type": "string", + "value": "[if(not(empty(parameters('aiProjectName'))), extensionResourceId(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), parameters('principalId'), 'acdd72a7-3385-48ef-bd42-f606fba81ae7')), '')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aiStorage", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('st{0}', variables('resourceToken'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "12086832046631103271" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false + }, + "containers": { + "type": "array", + "defaultValue": [] + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2" + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "TLS1_2" + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Standard_LRS" + } + }, + "networkAcls": { + "type": "object", + "defaultValue": { + "bypass": "AzureServices", + "defaultAction": "Allow" + } + } + }, + "resources": [ + { + "copy": { + "name": "storage::blobServices::container", + "count": "[length(parameters('containers'))]" + }, + "condition": "[not(empty(parameters('containers')))]", + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', parameters('containers')[copyIndex()].name)]", + "properties": { + "publicAccess": "[coalesce(tryGet(parameters('containers')[copyIndex()], 'publicAccess'), 'None')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('name'), 'default')]" + ] + }, + { + "copy": { + "name": "storage::queueServices::queue", + "count": "[length(createArray('input', 'output'))]" + }, + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', createArray('input', 'output')[copyIndex()])]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('name'), 'default')]" + ] + }, + { + "condition": "[not(empty(parameters('containers')))]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "sku": "[parameters('sku')]", + "properties": { + "minimumTlsVersion": "[parameters('minimumTlsVersion')]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "allowSharedKeyAccess": false, + "networkAcls": "[parameters('networkAcls')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "primaryEndpoints": { + "type": "object", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2023-05-01').primaryEndpoints]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "api-identity", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('api-identity-{0}', variables('resourceToken'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5039160413996154793" + }, + "description": "Creates a Microsoft Entra user-assigned identity." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]" + }, + "clientId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').clientId]" + }, + "tenantId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').tenantId]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "storageName": { + "value": "[format('st{0}', variables('resourceToken'))]" + }, + "keyvaultName": { + "value": "[format('kv-{0}{1}', parameters('name'), variables('resourceToken'))]" + }, + "aiServicesName": { + "value": "[format('ai{0}', variables('resourceToken'))]" + }, + "aiSearchName": { + "value": "[format('search{0}', variables('resourceToken'))]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" + }, + "modelLocation": { + "value": "[parameters('modelLocation')]" + }, + "aiStorageAccountResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.id.value]" + }, + "aiServiceAccountResourceId": { + "value": "" + }, + "aiSearchServiceResourceId": { + "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.34.44.8038", + "templateHash": "13415245204975770646" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "AI services name" + } + }, + "keyvaultName": { + "type": "string", + "metadata": { + "description": "The name of the Key Vault" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "The name of the AI Search resource" + } + }, + "storageName": { + "type": "string", + "metadata": { + "description": "Name of the storage account" + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Model name for deployment" + } + }, + "modelFormat": { + "type": "string", + "metadata": { + "description": "Model format for deployment" + } + }, + "modelVersion": { + "type": "string", + "metadata": { + "description": "Model version for deployment" + } + }, + "modelSkuName": { + "type": "string", + "metadata": { + "description": "Model deployment SKU name" + } + }, + "modelCapacity": { + "type": "int", + "metadata": { + "description": "Model deployment capacity" + } + }, + "modelLocation": { + "type": "string", + "metadata": { + "description": "Model/AI Resource deployment location" + } + }, + "aiServiceAccountResourceId": { + "type": "string", + "metadata": { + "description": "The AI Service Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiSearchServiceResourceId": { + "type": "string", + "metadata": { + "description": "The AI Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "aiStorageAccountResourceId": { + "type": "string", + "metadata": { + "description": "The AI Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created." + } + }, + "sku": { + "type": "string", + "defaultValue": "Standard_LRS" + } + }, + "variables": { + "aiServiceExists": "[not(equals(parameters('aiServiceAccountResourceId'), ''))]", + "acsExists": "[not(equals(parameters('aiSearchServiceResourceId'), ''))]", + "aiStorageExists": "[not(equals(parameters('aiStorageAccountResourceId'), ''))]", + "aiServiceParts": "[split(parameters('aiServiceAccountResourceId'), '/')]", + "acsParts": "[split(parameters('aiSearchServiceResourceId'), '/')]", + "aiStorageParts": "[split(parameters('aiStorageAccountResourceId'), '/')]" + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-12-01-preview", + "name": "[parameters('keyvaultName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "createMode": "default", + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "enableSoftDelete": true, + "enableRbacAuthorization": true, + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny" + }, + "sku": { + "family": "A", + "name": "standard" + }, + "softDeleteRetentionInDays": 7, + "tenantId": "[subscription().tenantId]" + } + }, + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiServicesName')]", + "location": "[parameters('modelLocation')]", + "sku": { + "name": "S0" + }, + "kind": "AIServices", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[toLower(format('{0}', parameters('aiServicesName')))]", + "publicNetworkAccess": "Enabled" + } + }, + { + "condition": "[not(variables('aiServiceExists'))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-06-01-preview", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName'))]" + ] + }, + { + "condition": "[not(variables('acsExists'))]", + "type": "Microsoft.Search/searchServices", + "apiVersion": "2024-06-01-preview", + "name": "[parameters('aiSearchName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "disableLocalAuth": false, + "authOptions": { + "aadOrApiKey": { + "aadAuthFailureMode": "http401WithBearerChallenge" + } + }, + "encryptionWithCmk": { + "enforcement": "Unspecified" + }, + "hostingMode": "default", + "partitionCount": 1, + "publicNetworkAccess": "enabled", + "replicaCount": 1, + "semanticSearch": "disabled" + }, + "sku": { + "name": "standard" + } + }, + { + "condition": "[not(variables('aiStorageExists'))]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[parameters('storageName')]", + "location": "[parameters('location')]", + "kind": "StorageV2", + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Allow", + "virtualNetworkRules": [] + }, + "allowSharedKeyAccess": false + } + } + ], + "outputs": { + "aiServicesName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[8], parameters('aiServicesName'))]" + }, + "aiservicesID": { + "type": "string", + "value": "[if(variables('aiServiceExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]" + }, + "aiservicesTarget": { + "type": "string", + "value": "[if(variables('aiServiceExists'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceParts')[2], variables('aiServiceParts')[4]), 'Microsoft.CognitiveServices/accounts', variables('aiServiceParts')[8]), '2023-05-01').endpoint, reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2024-06-01-preview').endpoint)]" + }, + "aiServiceAccountResourceGroupName": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[4], resourceGroup().name)]" + }, + "aiServiceAccountSubscriptionId": { + "type": "string", + "value": "[if(variables('aiServiceExists'), variables('aiServiceParts')[2], subscription().subscriptionId)]" + }, + "aiSearchName": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[8], parameters('aiSearchName'))]" + }, + "aisearchID": { + "type": "string", + "value": "[if(variables('acsExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('acsParts')[2], variables('acsParts')[4]), 'Microsoft.Search/searchServices', variables('acsParts')[8]), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]" + }, + "aiSearchServiceResourceGroupName": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[4], resourceGroup().name)]" + }, + "aiSearchServiceSubscriptionId": { + "type": "string", + "value": "[if(variables('acsExists'), variables('acsParts')[2], subscription().subscriptionId)]" + }, + "storageAccountName": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[8], parameters('storageName'))]" + }, + "storageId": { + "type": "string", + "value": "[if(variables('aiStorageExists'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiStorageParts')[2], variables('aiStorageParts')[4]), 'Microsoft.Storage/storageAccounts', variables('aiStorageParts')[8]), resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')))]" + }, + "storageAccountResourceGroupName": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[4], resourceGroup().name)]" + }, + "storageAccountSubscriptionId": { + "type": "string", + "value": "[if(variables('aiStorageExists'), variables('aiStorageParts')[2], subscription().subscriptionId)]" + }, + "keyvaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]" + }, + "keyvaultUri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName')), '2024-12-01-preview').vaultUri]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}', parameters('name'), variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiHubName": { + "value": "[format('{0}{1}', parameters('name'), variables('uniqueSuffix'))]" + }, + "aiHubFriendlyName": { + "value": "[parameters('aiHubFriendlyName')]" + }, + "aiHubDescription": { + "value": "[parameters('aiHubDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "capabilityHostName": { + "value": "[format('{0}{1}{2}', parameters('name'), variables('uniqueSuffix'), parameters('capabilityHostName'))]" + }, + "aiSearchName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiSearchId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aisearchID.value]" + }, + "aiServicesName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiServicesId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiservicesID.value]" + }, + "aiServicesTarget": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiservicesTarget.value]" + }, + "keyVaultId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.keyvaultId.value]" + }, + "storageAccountId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.storageId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "7733613507821473412" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiHubName": { + "type": "string", + "metadata": { + "description": "AI hub name" + } + }, + "aiHubFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiHubName')]", + "metadata": { + "description": "AI hub display name" + } + }, + "aiHubDescription": { + "type": "string", + "metadata": { + "description": "AI hub description" + } + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the key vault resource for storing connection strings" + } + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account resource for storing experimentation outputs" + } + }, + "aiServicesId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services resource" + } + }, + "aiServicesTarget": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services endpoint" + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Name AI Services resource" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Name AI Search resource" + } + }, + "aiSearchId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Search resource" + } + }, + "capabilityHostName": { + "type": "string", + "defaultValue": "caphost1", + "metadata": { + "description": "Name for capabilityHost." + } + } + }, + "variables": { + "acsConnectionName": "[format('{0}-connection-AISearch', parameters('aiHubName'))]", + "aoaiConnection": "[format('{0}-connection-AIServices_aoai', parameters('aiHubName'))]" + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-connection-AIServices', parameters('aiHubName')))]", + "properties": { + "category": "AIServices", + "target": "[parameters('aiServicesTarget')]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiServicesId')]", + "location": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2023-05-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/connections", + "apiVersion": "2024-07-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), variables('acsConnectionName'))]", + "properties": { + "category": "CognitiveSearch", + "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiSearchId')]", + "location": "[reference(resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')), '2023-11-01', 'full').location]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiHubName'), format('{0}-{1}', parameters('aiHubName'), parameters('capabilityHostName')))]", + "properties": { + "capabilityHostKind": "Agents" + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2024-07-01-preview", + "name": "[parameters('aiHubName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiHubFriendlyName')]", + "description": "[parameters('aiHubDescription')]", + "keyVault": "[parameters('keyVaultId')]", + "storageAccount": "[parameters('storageAccountId')]" + }, + "kind": "hub" + } + ], + "outputs": { + "aiHubID": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiHubName'))]" + }, + "aiHubName": { + "type": "string", + "value": "[parameters('aiHubName')]" + }, + "aoaiConnectionName": { + "type": "string", + "value": "[variables('aoaiConnection')]" + }, + "acsConnectionName": { + "type": "string", + "value": "[variables('acsConnectionName')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiProjectName": { + "value": "[format('{0}{1}', parameters('projectName'), variables('uniqueSuffix'))]" + }, + "aiProjectFriendlyName": { + "value": "[parameters('aiProjectFriendlyName')]" + }, + "aiProjectDescription": { + "value": "[parameters('aiProjectDescription')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "capabilityHostName": { + "value": "[format('{0}{1}{2}', parameters('projectName'), variables('uniqueSuffix'), parameters('capabilityHostName'))]" + }, + "aiHubId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiHubID.value]" + }, + "acsConnectionName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.acsConnectionName.value]" + }, + "aoaiConnectionName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aoaiConnectionName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "13093580484595830078" + } + }, + "parameters": { + "location": { + "type": "string", + "metadata": { + "description": "Azure region of the deployment" + } + }, + "tags": { + "type": "object", + "metadata": { + "description": "Tags to add to the resources" + } + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "AI Project name" + } + }, + "aiProjectFriendlyName": { + "type": "string", + "defaultValue": "[parameters('aiProjectName')]", + "metadata": { + "description": "AI Project display name" + } + }, + "aiProjectDescription": { + "type": "string", + "metadata": { + "description": "AI Project description" + } + }, + "aiHubId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Hub resource" + } + }, + "capabilityHostName": { + "type": "string", + "defaultValue": "caphost1", + "metadata": { + "description": "Name for capabilityHost." + } + }, + "acsConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + }, + "aoaiConnectionName": { + "type": "string", + "metadata": { + "description": "Name for ACS connection." + } + } + }, + "variables": { + "subscriptionId": "[subscription().subscriptionId]", + "resourceGroupName": "[resourceGroup().name]", + "standardConnectionString": "[format('{0}.api.azureml.ms;{1};{2};{3}', parameters('location'), variables('subscriptionId'), variables('resourceGroupName'), parameters('aiProjectName'))]", + "projectConnectionString": "[format('https://{0}.aiprojects.azure.com/api/projects/{1}/{2}', parameters('location'), variables('resourceGroupName'), parameters('aiProjectName'))]", + "storageConnections": [ + "[format('{0}/workspaceblobstore', parameters('aiProjectName'))]" + ], + "aiSearchConnection": [ + "[format('{0}', parameters('acsConnectionName'))]" + ], + "aiServiceConnections": [ + "[format('{0}', parameters('aoaiConnectionName'))]" + ] + }, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/capabilityHosts", + "apiVersion": "2024-10-01-preview", + "name": "[format('{0}/{1}', parameters('aiProjectName'), format('{0}-{1}', parameters('aiProjectName'), parameters('capabilityHostName')))]", + "properties": { + "capabilityHostKind": "Agents", + "aiServicesConnections": "[variables('aiServiceConnections')]", + "vectorStoreConnections": "[variables('aiSearchConnection')]", + "storageConnections": "[variables('storageConnections')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + ] + }, + { + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('aiProjectName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('ProjectConnectionString', variables('projectConnectionString'), 'StandardConnectionString', variables('standardConnectionString')))]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "friendlyName": "[parameters('aiProjectFriendlyName')]", + "description": "[parameters('aiProjectDescription')]", + "hubResourceId": "[parameters('aiHubId')]" + }, + "kind": "project" + } + ], + "outputs": { + "aiProjectName": { + "type": "string", + "value": "[parameters('aiProjectName')]" + }, + "aiProjectResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName'))]" + }, + "aiProjectPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2023-08-01-preview', 'full').identity.principalId]" + }, + "aiProjectWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', parameters('aiProjectName')), '2023-08-01-preview').workspaceId]" + }, + "projectConnectionString": { + "type": "string", + "value": "[variables('projectConnectionString')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('aiserviceroleassignments{0}-{1}', parameters('projectName'), variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiServicesName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "3327108401608568038" + } + }, + "parameters": { + "aiServicesName": { + "type": "string" + }, + "aiProjectPrincipalId": { + "type": "string" + }, + "aiProjectId": { + "type": "string" + } + }, + "variables": { + "cognitiveServicesOpenAIUserRoleId": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", + "cognitiveServiceServicesUserRoleId": "a97b65f3-24c7-4388-baec-2e87135dc908" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'), parameters('aiProjectId'))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesOpenAIUserRoleId')), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesOpenAIUserRoleId'))]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiServicesName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServiceServicesUserRoleId')), resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServiceServicesUserRoleId'))]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('aisearchroleassignments{0}-{1}', parameters('projectName'), variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiSearchName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiSearchName.value]" + }, + "aiProjectPrincipalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "aiProjectId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "6371455345142990163" + } + }, + "parameters": { + "aiSearchName": { + "type": "string" + }, + "aiProjectPrincipalId": { + "type": "string" + }, + "aiProjectId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]", + "principalType": "ServicePrincipal" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('aiSearchName'))]", + "name": "[guid(parameters('aiProjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), resourceId('Microsoft.Search/searchServices', parameters('aiSearchName')))]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('dependencies-{0}-{1}', parameters('name'), variables('resourceToken')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageRoleAssignmentApi-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageBlobDataContributorRole')]" + }, + "principalID": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity'), '2022-09-01').outputs.principalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageRoleAssignmentUser-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageBlobDataContributorRole')]" + }, + "principalID": { + "value": "[parameters('principalId')]" + }, + "principalType": { + "value": "User" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageQueueDataContributorRoleAssignmentprocessor-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageQueueDataContributorRoleDefinitionId')]" + }, + "principalID": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity'), '2022-09-01').outputs.principalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageQueueDataContributorRoleAssignmentAIProject-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageQueueDataContributorRoleDefinitionId')]" + }, + "principalID": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectPrincipalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageQueueDataContributorRole-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageQueueDataContributorRoleDefinitionId')]" + }, + "principalID": { + "value": "[parameters('principalId')]" + }, + "principalType": { + "value": "User" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageTableDataContributorRole-{0}', variables('resourceToken'))]", + "resourceGroup": "[if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage'), '2022-09-01').outputs.name.value]" + }, + "roleDefinitionID": { + "value": "[variables('storageTableDataContributorRoleDefinitionId')]" + }, + "principalID": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity'), '2022-09-01').outputs.principalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "5825364076825170648" + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account" + } + }, + "principalID": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionID": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "principalType": { + "type": "string", + "metadata": { + "description": "The type of the principal (User, Group, ServicePrincipal)" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionID'))]", + "principalId": "[parameters('principalID')]", + "principalType": "[parameters('principalType')]" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('principalID'), parameters('roleDefinitionID')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'api-identity')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName'))))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'aiStorage')]" + ] + } + ], + "outputs": { + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "AZURE_CONTAINER_APP_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', parameters('clientsServiceName')), '2022-09-01').outputs.endpoint.value]" + }, + "AZURE_CONTAINER_ENVIRONMENT_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', parameters('clientsServiceName')), '2022-09-01').outputs.envName.value]" + }, + "DTS_URL": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.dts_URL.value]" + }, + "TASKHUB_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'dtsResource'), '2022-09-01').outputs.TASKHUB_NAME.value]" + }, + "AGENT_CONNECTION_STRING": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.projectConnectionString.value]" + }, + "AI_PROJECT_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}-deployment', parameters('projectName'), variables('resourceToken'))), '2022-09-01').outputs.aiProjectName.value]" + }, + "AI_HUB_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', format('{0}-{1}', parameters('name'), variables('resourceToken'))), '2022-09-01').outputs.aiHubName.value]" + }, + "AZURE_USER_ASSIGNED_IDENTITY_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, if(not(empty(parameters('resourceGroupName'))), parameters('resourceGroupName'), format('{0}{1}', variables('abbrs').resourcesResourceGroups, parameters('environmentName')))), 'Microsoft.Resources/deployments', 'identity'), '2022-09-01').outputs.name.value]" + } + } +} \ No newline at end of file diff --git a/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.parameters.json b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.parameters.json new file mode 100644 index 0000000..c8d3453 --- /dev/null +++ b/samples/durable-task-sdks/dotnet/Agents/PromptChaining/infra/main.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +}