-
Notifications
You must be signed in to change notification settings - Fork 1.7k
.NET: Add durable workflow support #4436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
e8d0bd9
.NET: [Feature Branch] Add basic durable workflow support (#3648)
kshyju b62b1f2
.NET: [Feature Branch] Add Azure Functions hosting support for durabl…
kshyju 3256baa
.NET: [Feature Branch] Adding support for events & shared state in du…
kshyju 2988568
.NET: [Feature Branch] Add nested sub-workflow support for durable w…
kshyju ad51aee
.NET: [Feature Branch] Add Human In the Loop support for durable work…
kshyju bb96bf6
Add changelog entries for durable workflow support (#4436)
kshyju 154c44a
Merge main into feat/durable_task and resolve conflicts
kshyju f8eebfe
Bump Microsoft.DurableTask.Worker to 1.19.1 to fix version downgrade
kshyju cd3929c
Fix broken markdown links in durable workflow sample READMEs
kshyju 123cd74
Fix build errors from main merge: Throw conflict, ExecuteAsync rename…
kshyju cd8344d
Move durable workflow samples to 04-hosting/DurableWorkflows
kshyju 3c586b2
Fix build errors: remove duplicate base class members, update renamed…
kshyju 9213645
Merge branch 'main' into feat/durable_task
kshyju ae6b23c
Fix dotnet format issues: add UTF-8 BOM and remove unused using
kshyju 41d5c6e
Fix typo PaymentProcesser -> PaymentProcessor and garbled arrows in R…
kshyju f59eba4
Fix GetExecutorName to handle agent names with underscores
kshyju 931ec6b
Align DurableTask.Client.AzureManaged to 1.19.1
kshyju e248763
Merge branch 'main' into feat/durable_task
kshyju 22989ab
Bump DurableTask and Azure Functions extension package versions
kshyju d03d59b
Bump DurableTask SDK packages to 1.22.0
kshyju 710c8e2
Merge branch 'main' into feat/durable_task
kshyju 4ed1e5f
Update Microsoft.Azure.Functions.Worker.Extensions.DurableTask to "1.…
kshyju 6caed99
Add the local.settings.json files to the sample which were previously…
kshyju d0eb1db
Merge branch 'main' into feat/durable_task
kshyju dad2969
Merge branch 'main' into feat/durable_task
kshyju 34a7cb4
Merge branch 'main' into feat/durable_task
kshyju 7779803
Merge branch 'main' into feat/durable_task
kshyju 1174724
Merge branch 'main' into feat/durable_task
kshyju d8dadbe
Increase timeout for tests as CI has them failing transiently.
kshyju c756bd6
increaset timeout value for azure functions integration tests.
kshyju 81705e2
Merge branch 'main' into feat/durable_task
kshyju 5beb041
Add YieldsOutput(string) to workflow shared state sample executors
kshyju ec18c02
Merge branch 'main' into feat/durable_task
kshyju 83f5200
Merge branch 'main' into feat/durable_task
kshyju 3135875
Merge branch 'main' into feat/durable_task
kshyju 8ad46b1
Merge branch 'main' into feat/durable_task
kshyju 102fe1a
Merge branch 'main' into feat/durable_task
kshyju d013a45
Downgrade the durable packages to 1.18.0
kshyju a96f9ef
Downgrading Worker.Extensions.DurableTask to 1.12.1
kshyju 0344afc
Merge branch 'main' into feat/durable_task
kshyju acc3b95
Merge branch 'main' into feat/durable_task
kshyju File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...osting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFrameworks>net10.0</TargetFrameworks> | ||
| <AzureFunctionsVersion>v4</AzureFunctionsVersion> | ||
| <OutputType>Exe</OutputType> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <!-- The Functions build tools don't like namespaces that start with a number --> | ||
| <AssemblyName>SingleAgent</AssemblyName> | ||
| <RootNamespace>SingleAgent</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
| </ItemGroup> | ||
|
|
||
| <!-- Azure Functions packages --> | ||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.AI.OpenAI" /> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| </ItemGroup> | ||
|
|
||
| <!-- Local projects that should be switched to package references when using the sample outside of this MAF repo --> | ||
| <!-- | ||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" /> | ||
| <PackageReference Include="Microsoft.Agents.AI.OpenAI" /> | ||
| </ItemGroup> | ||
| --> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" /> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" /> | ||
| </ItemGroup> | ||
| </Project> |
215 changes: 215 additions & 0 deletions
215
.../04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/OrderCancelExecutors.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using Microsoft.Agents.AI.Workflows; | ||
|
|
||
| namespace SequentialWorkflow; | ||
|
|
||
| /// <summary> | ||
| /// Looks up an order by its ID and return an Order object. | ||
| /// </summary> | ||
| internal sealed class OrderLookup() : Executor<string, Order>("OrderLookup") | ||
| { | ||
| public override async ValueTask<Order> HandleAsync( | ||
| string message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Magenta; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine($"│ [Activity] OrderLookup: Starting lookup for order '{message}'"); | ||
| Console.ResetColor(); | ||
|
|
||
| // Simulate database lookup with delay | ||
| await Task.Delay(TimeSpan.FromMicroseconds(100), cancellationToken); | ||
|
|
||
| Order order = new( | ||
| Id: message, | ||
| OrderDate: DateTime.UtcNow.AddDays(-1), | ||
| IsCancelled: false, | ||
| Customer: new Customer(Name: "Jerry", Email: "jerry@example.com")); | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Magenta; | ||
| Console.WriteLine($"│ [Activity] OrderLookup: Found order '{message}' for customer '{order.Customer.Name}'"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return order; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Cancels an order. | ||
| /// </summary> | ||
| internal sealed class OrderCancel() : Executor<Order, Order>("OrderCancel") | ||
| { | ||
| public override async ValueTask<Order> HandleAsync( | ||
| Order message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine($"│ [Activity] OrderCancel: Starting cancellation for order '{message.Id}'"); | ||
| Console.ResetColor(); | ||
|
|
||
| // Simulate a slow cancellation process (e.g., calling external payment system) | ||
| for (int i = 1; i <= 3; i++) | ||
| { | ||
| await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); | ||
| Console.ForegroundColor = ConsoleColor.DarkYellow; | ||
| Console.WriteLine("│ [Activity] OrderCancel: Processing..."); | ||
| Console.ResetColor(); | ||
| } | ||
|
|
||
| Order cancelledOrder = message with { IsCancelled = true }; | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine($"│ [Activity] OrderCancel: ✓ Order '{cancelledOrder.Id}' has been cancelled"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return cancelledOrder; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sends a cancellation confirmation email to the customer. | ||
| /// </summary> | ||
| internal sealed class SendEmail() : Executor<Order, string>("SendEmail") | ||
| { | ||
| public override ValueTask<string> HandleAsync( | ||
| Order message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Cyan; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine($"│ [Activity] SendEmail: Sending email to '{message.Customer.Email}'..."); | ||
| Console.ResetColor(); | ||
|
|
||
| string result = $"Cancellation email sent for order {message.Id} to {message.Customer.Email}."; | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Cyan; | ||
| Console.WriteLine("│ [Activity] SendEmail: ✓ Email sent successfully!"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return ValueTask.FromResult(result); | ||
| } | ||
| } | ||
|
|
||
| internal sealed record Order(string Id, DateTime OrderDate, bool IsCancelled, Customer Customer); | ||
|
|
||
| internal sealed record Customer(string Name, string Email); | ||
|
|
||
| /// <summary> | ||
| /// Represents a batch cancellation request with multiple order IDs and a reason. | ||
| /// This demonstrates using a complex typed object as workflow input. | ||
| /// </summary> | ||
| #pragma warning disable CA1812 // Instantiated via JSON deserialization at runtime | ||
| internal sealed record BatchCancelRequest(string[] OrderIds, string Reason, bool NotifyCustomers); | ||
| #pragma warning restore CA1812 | ||
|
|
||
| /// <summary> | ||
| /// Represents the result of processing a batch cancellation. | ||
| /// </summary> | ||
| internal sealed record BatchCancelResult(int TotalOrders, int CancelledCount, string Reason); | ||
|
|
||
| /// <summary> | ||
| /// Generates a status report for an order. | ||
| /// </summary> | ||
| internal sealed class StatusReport() : Executor<Order, string>("StatusReport") | ||
| { | ||
| public override ValueTask<string> HandleAsync( | ||
| Order message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Green; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine($"│ [Activity] StatusReport: Generating report for order '{message.Id}'"); | ||
| Console.ResetColor(); | ||
|
|
||
| string status = message.IsCancelled ? "Cancelled" : "Active"; | ||
| string result = $"Order {message.Id} for {message.Customer.Name}: Status={status}, Date={message.OrderDate:yyyy-MM-dd}"; | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Green; | ||
| Console.WriteLine($"│ [Activity] StatusReport: ✓ {result}"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return ValueTask.FromResult(result); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Processes a batch cancellation request. Accepts a complex <see cref="BatchCancelRequest"/> object | ||
| /// as input, demonstrating how workflows can receive structured JSON input. | ||
| /// </summary> | ||
| internal sealed class BatchCancelProcessor() : Executor<BatchCancelRequest, BatchCancelResult>("BatchCancelProcessor") | ||
| { | ||
| public override async ValueTask<BatchCancelResult> HandleAsync( | ||
| BatchCancelRequest message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine($"│ [Activity] BatchCancelProcessor: Processing {message.OrderIds.Length} orders"); | ||
| Console.WriteLine($"│ [Activity] BatchCancelProcessor: Reason: {message.Reason}"); | ||
| Console.WriteLine($"│ [Activity] BatchCancelProcessor: Notify customers: {message.NotifyCustomers}"); | ||
| Console.ResetColor(); | ||
|
|
||
| // Simulate processing each order | ||
| int cancelledCount = 0; | ||
| foreach (string orderId in message.OrderIds) | ||
| { | ||
| await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); | ||
| cancelledCount++; | ||
| Console.ForegroundColor = ConsoleColor.DarkYellow; | ||
| Console.WriteLine($"│ [Activity] BatchCancelProcessor: ✓ Cancelled order '{orderId}'"); | ||
| Console.ResetColor(); | ||
| } | ||
|
|
||
| BatchCancelResult result = new(message.OrderIds.Length, cancelledCount, message.Reason); | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine($"│ [Activity] BatchCancelProcessor: ✓ Batch complete: {cancelledCount}/{message.OrderIds.Length} cancelled"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Generates a summary of the batch cancellation. | ||
| /// </summary> | ||
| internal sealed class BatchCancelSummary() : Executor<BatchCancelResult, string>("BatchCancelSummary") | ||
| { | ||
| public override ValueTask<string> HandleAsync( | ||
| BatchCancelResult message, | ||
| IWorkflowContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.ForegroundColor = ConsoleColor.Cyan; | ||
| Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐"); | ||
| Console.WriteLine("│ [Activity] BatchCancelSummary: Generating summary"); | ||
| Console.ResetColor(); | ||
|
|
||
| string result = $"Batch cancellation complete: {message.CancelledCount}/{message.TotalOrders} orders cancelled. Reason: {message.Reason}"; | ||
|
|
||
| Console.ForegroundColor = ConsoleColor.Cyan; | ||
| Console.WriteLine($"│ [Activity] BatchCancelSummary: ✓ {result}"); | ||
| Console.WriteLine("└─────────────────────────────────────────────────────────────────┘"); | ||
| Console.ResetColor(); | ||
|
|
||
| return ValueTask.FromResult(result); | ||
| } | ||
| } |
52 changes: 52 additions & 0 deletions
52
dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| // This sample demonstrates three workflows that share executors. | ||
| // The CancelOrder workflow cancels an order and notifies the customer. | ||
| // The OrderStatus workflow looks up an order and generates a status report. | ||
| // The BatchCancelOrders workflow accepts a complex JSON input to cancel multiple orders. | ||
| // Both CancelOrder and OrderStatus reuse the same OrderLookup executor, demonstrating executor sharing. | ||
|
|
||
| using Microsoft.Agents.AI.Hosting.AzureFunctions; | ||
| using Microsoft.Agents.AI.Workflows; | ||
| using Microsoft.Azure.Functions.Worker.Builder; | ||
| using Microsoft.Extensions.Hosting; | ||
| using SequentialWorkflow; | ||
|
|
||
| // Define executors for all workflows | ||
| OrderLookup orderLookup = new(); | ||
| OrderCancel orderCancel = new(); | ||
| SendEmail sendEmail = new(); | ||
| StatusReport statusReport = new(); | ||
| BatchCancelProcessor batchCancelProcessor = new(); | ||
| BatchCancelSummary batchCancelSummary = new(); | ||
|
|
||
| // Build the CancelOrder workflow: OrderLookup -> OrderCancel -> SendEmail | ||
| Workflow cancelOrder = new WorkflowBuilder(orderLookup) | ||
| .WithName("CancelOrder") | ||
| .WithDescription("Cancel an order and notify the customer") | ||
| .AddEdge(orderLookup, orderCancel) | ||
| .AddEdge(orderCancel, sendEmail) | ||
| .Build(); | ||
|
|
||
| // Build the OrderStatus workflow: OrderLookup -> StatusReport | ||
| // This workflow shares the OrderLookup executor with the CancelOrder workflow. | ||
| Workflow orderStatus = new WorkflowBuilder(orderLookup) | ||
| .WithName("OrderStatus") | ||
| .WithDescription("Look up an order and generate a status report") | ||
| .AddEdge(orderLookup, statusReport) | ||
| .Build(); | ||
|
|
||
| // Build the BatchCancelOrders workflow: BatchCancelProcessor -> BatchCancelSummary | ||
| // This workflow demonstrates using a complex JSON object as the workflow input. | ||
| Workflow batchCancelOrders = new WorkflowBuilder(batchCancelProcessor) | ||
| .WithName("BatchCancelOrders") | ||
| .WithDescription("Cancel multiple orders in a batch using a complex JSON input") | ||
| .AddEdge(batchCancelProcessor, batchCancelSummary) | ||
| .Build(); | ||
|
|
||
| using IHost app = FunctionsApplication | ||
| .CreateBuilder(args) | ||
| .ConfigureFunctionsWebApplication() | ||
| .ConfigureDurableWorkflows(workflows => workflows.AddWorkflows(cancelOrder, orderStatus, batchCancelOrders)) | ||
| .Build(); | ||
| app.Run(); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.