mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Adding azure functions support
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
<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>ConditionalEdgesFunctionApp</AssemblyName>
|
||||
<RootNamespace>ConditionalEdgesFunctionApp</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.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" />
|
||||
</ItemGroup>
|
||||
-->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace ConditionalEdgesFunctionApp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an order with customer and payment details.
|
||||
/// </summary>
|
||||
internal sealed class Order
|
||||
{
|
||||
public Order(string id, decimal amount)
|
||||
{
|
||||
this.Id = id;
|
||||
this.Amount = amount;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public decimal Amount { get; }
|
||||
public Customer? Customer { get; set; }
|
||||
public string? PaymentReferenceNumber { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a customer associated with an order.
|
||||
/// </summary>
|
||||
public sealed record Customer(int Id, string Name, bool IsBlocked);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the order ID and retrieves order details.
|
||||
/// </summary>
|
||||
internal sealed class OrderIdParser() : Executor<string, Order>("OrderIdParser")
|
||||
{
|
||||
public override ValueTask<Order> HandleAsync(
|
||||
string message,
|
||||
IWorkflowContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Console.WriteLine($"[OrderIdParser] Parsing order ID: {message}");
|
||||
Order order = new(message, 100.0m);
|
||||
return ValueTask.FromResult(order);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enriches the order with customer information.
|
||||
/// Orders with IDs containing 'B' are associated with blocked customers.
|
||||
/// </summary>
|
||||
internal sealed class OrderEnrich() : Executor<Order, Order>("EnrichOrder")
|
||||
{
|
||||
public override ValueTask<Order> HandleAsync(
|
||||
Order message,
|
||||
IWorkflowContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
message.Customer = GetCustomerForOrder(message.Id);
|
||||
Console.WriteLine($"[EnrichOrder] Customer: {message.Customer.Name}, IsBlocked: {message.Customer.IsBlocked}");
|
||||
return ValueTask.FromResult(message);
|
||||
}
|
||||
|
||||
private static Customer GetCustomerForOrder(string orderId)
|
||||
{
|
||||
if (orderId.Contains('B'))
|
||||
{
|
||||
return new Customer(101, "George", true);
|
||||
}
|
||||
|
||||
return new Customer(201, "Jerry", false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes payment for valid (non-blocked) orders.
|
||||
/// </summary>
|
||||
internal sealed class PaymentProcessor() : Executor<Order, Order>("PaymentProcessor")
|
||||
{
|
||||
public override ValueTask<Order> HandleAsync(
|
||||
Order message,
|
||||
IWorkflowContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
message.PaymentReferenceNumber = Guid.NewGuid().ToString()[..4];
|
||||
Console.WriteLine($"[PaymentProcessor] Payment processed for order {message.Id}. Reference: {message.PaymentReferenceNumber}");
|
||||
return ValueTask.FromResult(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the fraud team when a blocked customer places an order.
|
||||
/// </summary>
|
||||
internal sealed class NotifyFraud() : Executor<Order, string>("NotifyFraud")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(
|
||||
Order message,
|
||||
IWorkflowContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = $"Order {message.Id} flagged as fraudulent for customer {message.Customer?.Name}.";
|
||||
Console.WriteLine($"[NotifyFraud] {result}");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines condition functions for routing orders based on customer status.
|
||||
/// </summary>
|
||||
internal static class OrderRouteConditions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a condition that evaluates to true when the customer is blocked.
|
||||
/// </summary>
|
||||
internal static Func<Order?, bool> WhenBlocked() => order => order?.Customer?.IsBlocked == true;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a condition that evaluates to true when the customer is not blocked.
|
||||
/// </summary>
|
||||
internal static Func<Order?, bool> WhenNotBlocked() => order => order?.Customer?.IsBlocked == false;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates conditional edges in a workflow hosted as an Azure Function.
|
||||
// Orders are routed to different executors based on customer status:
|
||||
// - Blocked customers → NotifyFraud
|
||||
// - Valid customers → PaymentProcessor
|
||||
|
||||
using ConditionalEdgesFunctionApp;
|
||||
using Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
// Create executor instances
|
||||
OrderIdParser orderParser = new();
|
||||
OrderEnrich orderEnrich = new();
|
||||
PaymentProcessor paymentProcessor = new();
|
||||
NotifyFraud notifyFraud = new();
|
||||
|
||||
// Build workflow with conditional edges.
|
||||
// The condition functions evaluate the Order output from OrderEnrich
|
||||
// to determine whether to route to NotifyFraud or PaymentProcessor.
|
||||
Workflow auditOrder = new WorkflowBuilder(orderParser)
|
||||
.WithName("AuditOrder")
|
||||
.WithDescription("Audits an order and routes based on customer status")
|
||||
.AddEdge(orderParser, orderEnrich)
|
||||
.AddEdge(orderEnrich, notifyFraud, condition: OrderRouteConditions.WhenBlocked())
|
||||
.AddEdge(orderEnrich, paymentProcessor, condition: OrderRouteConditions.WhenNotBlocked())
|
||||
.Build();
|
||||
|
||||
// Configure the function app to host the workflow.
|
||||
// This will automatically generate HTTP API endpoints for the workflow.
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
.ConfigureFunctionsWebApplication()
|
||||
.ConfigureDurableOptions(durableOption =>
|
||||
{
|
||||
// Add the workflow.
|
||||
durableOption.Workflows.AddWorkflow(auditOrder);
|
||||
})
|
||||
.Build();
|
||||
app.Run();
|
||||
@@ -0,0 +1,84 @@
|
||||
# Conditional Edges Workflow - Azure Functions Sample
|
||||
|
||||
This sample demonstrates how to build a workflow with **conditional edges** hosted as an Azure Function. Orders are routed to different executors based on customer status.
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
- Building workflows with **conditional edges** using `AddEdge` with a `condition` parameter
|
||||
- Hosting a conditional workflow as an Azure Function using `ConfigureDurableOptions`
|
||||
- Defining reusable condition functions for routing logic
|
||||
- Branching workflow execution based on data-driven decisions
|
||||
|
||||
## Overview
|
||||
|
||||
The workflow implements an order audit that routes orders differently based on whether the customer is blocked (flagged for fraud):
|
||||
|
||||
```
|
||||
OrderIdParser --> OrderEnrich --[IsBlocked]--> NotifyFraud
|
||||
|
|
||||
+--[NotBlocked]--> PaymentProcessor
|
||||
```
|
||||
|
||||
| Executor | Description |
|
||||
|----------|-------------|
|
||||
| OrderIdParser | Parses the order ID and retrieves order details |
|
||||
| OrderEnrich | Enriches the order with customer information |
|
||||
| PaymentProcessor | Processes payment for valid orders |
|
||||
| NotifyFraud | Notifies the fraud team for blocked customers |
|
||||
|
||||
## How Conditional Edges Work
|
||||
|
||||
Conditional edges allow you to specify a condition function that determines whether the edge should be traversed:
|
||||
|
||||
```csharp
|
||||
builder
|
||||
.AddEdge(orderParser, orderEnrich)
|
||||
.AddEdge(orderEnrich, notifyFraud, condition: OrderRouteConditions.WhenBlocked())
|
||||
.AddEdge(orderEnrich, paymentProcessor, condition: OrderRouteConditions.WhenNotBlocked());
|
||||
```
|
||||
|
||||
The condition functions receive the output of the source executor and return a boolean:
|
||||
|
||||
```csharp
|
||||
internal static class OrderRouteConditions
|
||||
{
|
||||
internal static Func<Order?, bool> WhenBlocked() =>
|
||||
order => order?.Customer?.IsBlocked == true;
|
||||
|
||||
internal static Func<Order?, bool> WhenNotBlocked() =>
|
||||
order => order?.Customer?.IsBlocked == false;
|
||||
}
|
||||
```
|
||||
|
||||
### Routing Logic
|
||||
|
||||
- Order IDs containing the letter **'B'** are associated with blocked customers → routed to `NotifyFraud`
|
||||
- All other order IDs are associated with valid customers → routed to `PaymentProcessor`
|
||||
|
||||
## Environment Setup
|
||||
|
||||
This sample requires:
|
||||
|
||||
- [.NET 10 SDK](https://dotnet.microsoft.com/download)
|
||||
- [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)
|
||||
- [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-azure-managed-storage) running locally (default: `http://localhost:8080`)
|
||||
- [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite) for local Azure Storage emulation
|
||||
|
||||
## Running the Sample
|
||||
|
||||
```bash
|
||||
cd dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges
|
||||
func start
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
**Valid order (routes to PaymentProcessor):**
|
||||
```bash
|
||||
curl -X POST http://localhost:7071/api/workflows/AuditOrder/run -H "Content-Type: text/plain" -d "12345"
|
||||
```
|
||||
|
||||
**Blocked order (routes to NotifyFraud):**
|
||||
```bash
|
||||
curl -X POST http://localhost:7071/api/workflows/AuditOrder/run -H "Content-Type: text/plain" -d "12345B"
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
# Default endpoint address for local testing
|
||||
@authority=http://localhost:7071
|
||||
|
||||
### Valid order (routes to PaymentProcessor)
|
||||
POST {{authority}}/api/workflows/AuditOrder/run
|
||||
Content-Type: text/plain
|
||||
|
||||
12345
|
||||
|
||||
### Blocked order (routes to NotifyFraud) - Order IDs containing 'B' are flagged
|
||||
POST {{authority}}/api/workflows/AuditOrder/run
|
||||
Content-Type: text/plain
|
||||
|
||||
12345B
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"logLevel": {
|
||||
"Microsoft.Agents.AI.DurableTask": "Information",
|
||||
"Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
|
||||
"DurableTask": "Information",
|
||||
"Microsoft.DurableTask": "Information"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"durableTask": {
|
||||
"hubName": "default",
|
||||
"storageProvider": {
|
||||
"type": "AzureManaged",
|
||||
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user