mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
198 lines
9.2 KiB
C#
198 lines
9.2 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
// This sample demonstrates how to use sub-workflows within a durable orchestration.
|
|
// Sub-workflows allow you to compose complex workflows from simpler, reusable components.
|
|
//
|
|
// The sample implements an order processing workflow with three sub-workflows:
|
|
// 1. PaymentProcessing - Validates and processes payment
|
|
// - Contains a nested FraudCheck sub-workflow (Level 2 nesting)
|
|
// 2. InventoryManagement - Checks and reserves inventory
|
|
// 3. ShippingArrangement - Arranges shipping and generates tracking
|
|
//
|
|
// The FraudCheck sub-workflow demonstrates Level 2 nesting (sub-workflow within sub-workflow):
|
|
// - AnalyzePatterns - Analyzes transaction patterns
|
|
// - CalculateRiskScore - Calculates fraud risk score
|
|
//
|
|
// Each sub-workflow runs as a separate orchestration instance, visible in the DTS dashboard.
|
|
// This provides:
|
|
// - Modular, reusable workflow components
|
|
// - Independent checkpointing and replay
|
|
// - Hierarchical visualization in the dashboard (including nested sub-workflows)
|
|
// - Failure isolation between parent and child workflows
|
|
|
|
using Microsoft.Agents.AI.DurableTask;
|
|
using Microsoft.Agents.AI.Workflows;
|
|
using Microsoft.DurableTask.Client.AzureManaged;
|
|
using Microsoft.DurableTask.Worker.AzureManaged;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using SubWorkflows;
|
|
|
|
// Get DTS connection string from environment variable
|
|
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
|
|
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
|
|
|
|
// ============================================
|
|
// Step 1: Build the Fraud Check sub-sub-workflow (Level 2 nesting)
|
|
// ============================================
|
|
AnalyzePatterns analyzePatterns = new();
|
|
CalculateRiskScore calculateRiskScore = new();
|
|
|
|
Workflow fraudCheckWorkflow = new WorkflowBuilder(analyzePatterns)
|
|
.WithName("SubFraudCheck")
|
|
.WithDescription("Analyzes transaction patterns and calculates risk score")
|
|
.AddEdge(analyzePatterns, calculateRiskScore)
|
|
.Build();
|
|
|
|
// ============================================
|
|
// Step 2: Build the Payment Processing sub-workflow (now with nested sub-workflow)
|
|
// ============================================
|
|
ValidatePayment validatePayment = new();
|
|
ExecutorBinding fraudCheckExecutor = fraudCheckWorkflow.BindAsExecutor("FraudCheck");
|
|
ChargePayment chargePayment = new();
|
|
|
|
Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
|
|
.WithName("SubPaymentProcessing")
|
|
.WithDescription("Validates and processes payment for an order")
|
|
.AddEdge(validatePayment, fraudCheckExecutor)
|
|
.AddEdge(fraudCheckExecutor, chargePayment)
|
|
.Build();
|
|
|
|
// ============================================
|
|
// Step 3: Build the Inventory Management sub-workflow
|
|
// ============================================
|
|
CheckInventory checkInventory = new();
|
|
ReserveInventory reserveInventory = new();
|
|
|
|
Workflow inventoryWorkflow = new WorkflowBuilder(checkInventory)
|
|
.WithName("SubInventoryManagement")
|
|
.WithDescription("Checks availability and reserves inventory")
|
|
.AddEdge(checkInventory, reserveInventory)
|
|
.Build();
|
|
|
|
// ============================================
|
|
// Step 4: Build the Shipping Arrangement sub-workflow
|
|
// ============================================
|
|
SelectCarrier selectCarrier = new();
|
|
CreateShipment createShipment = new();
|
|
|
|
Workflow shippingWorkflow = new WorkflowBuilder(selectCarrier)
|
|
.WithName("SubShippingArrangement")
|
|
.WithDescription("Selects carrier and creates shipment")
|
|
.AddEdge(selectCarrier, createShipment)
|
|
.Build();
|
|
|
|
// ============================================
|
|
// Step 5: Build the Main Order Processing workflow using sub-workflows
|
|
// ============================================
|
|
// Bind sub-workflows as executors for use in the main workflow
|
|
ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
|
|
ExecutorBinding inventoryExecutor = inventoryWorkflow.BindAsExecutor("Inventory");
|
|
ExecutorBinding shippingExecutor = shippingWorkflow.BindAsExecutor("Shipping");
|
|
|
|
// Create entry and exit executors for the main workflow
|
|
OrderReceived orderReceived = new();
|
|
OrderCompleted orderCompleted = new();
|
|
|
|
// Build the main workflow: OrderReceived -> Payment -> Inventory -> Shipping -> OrderCompleted
|
|
Workflow orderProcessingWorkflow = new WorkflowBuilder(orderReceived)
|
|
.WithName("OrderProcessing")
|
|
.WithDescription("Processes an order through payment, inventory, and shipping")
|
|
.AddEdge(orderReceived, paymentExecutor)
|
|
.AddEdge(paymentExecutor, inventoryExecutor)
|
|
.AddEdge(inventoryExecutor, shippingExecutor)
|
|
.AddEdge(shippingExecutor, orderCompleted)
|
|
.Build();
|
|
|
|
// ============================================
|
|
// Step 6: Configure and start the host
|
|
// ============================================
|
|
IHost host = Host.CreateDefaultBuilder(args)
|
|
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
|
|
.ConfigureServices(services =>
|
|
{
|
|
// Register only the main workflow - sub-workflows are discovered automatically!
|
|
services.ConfigureDurableWorkflows(
|
|
options => options.Workflows.AddWorkflow(orderProcessingWorkflow),
|
|
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
|
|
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
|
|
})
|
|
.Build();
|
|
|
|
await host.StartAsync();
|
|
|
|
// Get the IWorkflowClient from DI
|
|
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
|
|
|
|
Console.WriteLine("╔══════════════════════════════════════════════════════════════════╗");
|
|
Console.WriteLine("║ Durable Sub-Workflows Sample ║");
|
|
Console.WriteLine("╠══════════════════════════════════════════════════════════════════╣");
|
|
Console.WriteLine("║ Main Workflow: OrderProcessing ║");
|
|
Console.WriteLine("║ ├── Payment (sub-workflow) ║");
|
|
Console.WriteLine("║ │ ├── ValidatePayment (1s) ║");
|
|
Console.WriteLine("║ │ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting! ║");
|
|
Console.WriteLine("║ │ │ ├── AnalyzePatterns (1s) ║");
|
|
Console.WriteLine("║ │ │ └── CalculateRiskScore (1s) ║");
|
|
Console.WriteLine("║ │ └── ChargePayment (2s) ║");
|
|
Console.WriteLine("║ ├── Inventory (sub-workflow) ║");
|
|
Console.WriteLine("║ │ ├── CheckInventory (1s) ║");
|
|
Console.WriteLine("║ │ └── ReserveInventory (2s) ║");
|
|
Console.WriteLine("║ └── Shipping (sub-workflow) ║");
|
|
Console.WriteLine("║ ├── SelectCarrier (1s) ║");
|
|
Console.WriteLine("║ └── CreateShipment (2s) ║");
|
|
Console.WriteLine("╚══════════════════════════════════════════════════════════════════╝");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Open the DTS dashboard at http://localhost:8080 to see the");
|
|
Console.WriteLine("parent-child orchestration hierarchy in the Timeline view!");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Enter an order ID (or 'exit'):");
|
|
|
|
while (true)
|
|
{
|
|
Console.Write("> ");
|
|
string? input = Console.ReadLine();
|
|
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
await StartNewWorkflowAsync(input, orderProcessingWorkflow, workflowClient);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error: {ex.Message}");
|
|
}
|
|
|
|
Console.WriteLine();
|
|
}
|
|
|
|
await host.StopAsync();
|
|
|
|
// Start a new workflow using IWorkflowClient
|
|
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client)
|
|
{
|
|
Console.WriteLine($"\nStarting order processing for '{orderId}'...");
|
|
|
|
await using DurableRun run = (DurableRun)await client.RunAsync(workflow, orderId);
|
|
Console.WriteLine($"Instance ID: {run.InstanceId}");
|
|
Console.WriteLine("Check the DTS dashboard Timeline tab to see sub-orchestrations!");
|
|
Console.WriteLine();
|
|
|
|
try
|
|
{
|
|
string? result = await run.WaitForCompletionAsync();
|
|
Console.ForegroundColor = ConsoleColor.Green;
|
|
Console.WriteLine($"✓ Order completed: {result}");
|
|
Console.ResetColor();
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.WriteLine($"✗ Failed: {ex.Message}");
|
|
Console.ResetColor();
|
|
}
|
|
}
|