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:
@@ -197,24 +197,33 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// Configure Durable Task Worker - capture sharedOptions reference in closure.
|
||||
// The options object is populated by all Configure* calls before the worker starts.
|
||||
services.AddDurableTaskWorker(builder =>
|
||||
|
||||
if (workerBuilder is not null)
|
||||
{
|
||||
workerBuilder?.Invoke(builder);
|
||||
|
||||
builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
|
||||
});
|
||||
services.AddDurableTaskWorker(builder =>
|
||||
{
|
||||
workerBuilder?.Invoke(builder);
|
||||
|
||||
builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
|
||||
});
|
||||
}
|
||||
// Configure Durable Task Client
|
||||
// Only register a client if explicitly configured. For Azure Functions,
|
||||
// the Functions extension provides the DurableTaskClient automatically via bindings.
|
||||
if (clientBuilder is not null)
|
||||
{
|
||||
services.AddDurableTaskClient(clientBuilder);
|
||||
|
||||
// These services depend on DurableTaskClient from DI, so only register them
|
||||
// when we're registering a client. For Azure Functions, the client comes from
|
||||
// bindings, not DI, so these won't work there.
|
||||
services.TryAddSingleton<DurableWorkflowClient>();
|
||||
services.TryAddSingleton<IWorkflowClient>(sp => sp.GetRequiredService<DurableWorkflowClient>());
|
||||
services.TryAddSingleton<IDurableAgentClient, DefaultDurableAgentClient>();
|
||||
}
|
||||
|
||||
// Register workflow and agent services
|
||||
services.TryAddSingleton<DurableWorkflowClient>();
|
||||
services.TryAddSingleton<IWorkflowClient>(sp => sp.GetRequiredService<DurableWorkflowClient>());
|
||||
// Register workflow and agent services that don't depend on DurableTaskClient
|
||||
services.TryAddSingleton<DataConverter, DurableDataConverter>();
|
||||
services.TryAddSingleton<IDurableAgentClient, DefaultDurableAgentClient>();
|
||||
|
||||
// Register agent factories resolver - returns factories from the shared options
|
||||
services.TryAddSingleton(
|
||||
@@ -257,7 +266,11 @@ public static class ServiceCollectionExtensions
|
||||
ExecutorBinding binding = activity.Binding;
|
||||
registry.AddActivityFunc<string, string>(
|
||||
activity.ActivityName,
|
||||
(context, input) => DurableActivityExecutor.ExecuteAsync(binding, input));
|
||||
(context, input) =>
|
||||
{
|
||||
// to do:a
|
||||
return DurableActivityExecutor.ExecuteAsync(binding, input);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,19 +14,20 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches workflow executors to either activities or AI agents.
|
||||
/// Dispatches workflow executors to activities, AI agents, or sub-workflow orchestrations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called during the dispatch phase of each superstep by
|
||||
/// <c>DurableWorkflowRunner.DispatchExecutorsInParallelAsync</c>. For each executor that has
|
||||
/// pending input, this dispatcher determines whether the executor is an AI agent (stateful,
|
||||
/// backed by Durable Entities) or a regular activity, and invokes the appropriate Durable Task API.
|
||||
/// backed by Durable Entities), a sub-workflow (child orchestration), or a regular activity,
|
||||
/// and invokes the appropriate Durable Task API.
|
||||
/// The serialised string result is returned to the runner for the routing phase.
|
||||
/// </remarks>
|
||||
internal static class DurableExecutorDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatches an executor based on its type (activity or AI agent).
|
||||
/// Dispatches an executor based on its type (activity, AI agent, or sub-workflow).
|
||||
/// </summary>
|
||||
/// <param name="context">The task orchestration context.</param>
|
||||
/// <param name="executorInfo">Information about the executor to dispatch.</param>
|
||||
@@ -46,6 +47,11 @@ internal static class DurableExecutorDispatcher
|
||||
return await ExecuteAgentAsync(context, executorInfo, logger, envelope.Message).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
if (executorInfo.IsSubworkflowExecutor)
|
||||
{
|
||||
return await ExecuteSubWorkflowAsync(context, executorInfo, envelope.Message).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
return await ExecuteActivityAsync(context, executorInfo, envelope.Message, envelope.InputTypeName).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
@@ -96,4 +102,29 @@ internal static class DurableExecutorDispatcher
|
||||
|
||||
return response.Text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a sub-workflow as a child orchestration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The input is passed as a raw JSON object (not a string) to avoid double-encoding.
|
||||
/// </remarks>
|
||||
private static async Task<string> ExecuteSubWorkflowAsync(
|
||||
TaskOrchestrationContext context,
|
||||
WorkflowExecutorInfo executorInfo,
|
||||
string input)
|
||||
{
|
||||
string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorInfo.SubWorkflow!.Name!);
|
||||
|
||||
// Parse the input JSON to pass as an object, preventing double-encoding.
|
||||
// The sub-workflow orchestrator receives DurableWorkflowInput<object> where Input
|
||||
// should be the deserialized object (not a JSON string).
|
||||
using JsonDocument doc = JsonDocument.Parse(input);
|
||||
object inputObj = doc.RootElement.Clone();
|
||||
DurableWorkflowInput<object> workflowInput = new() { Input = inputObj };
|
||||
|
||||
return await context.CallSubOrchestratorAsync<string>(
|
||||
orchestrationName,
|
||||
workflowInput).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
/// <summary>
|
||||
/// Runs workflow orchestrations using message-driven superstep execution with Durable Task.
|
||||
/// </summary>
|
||||
internal sealed class DurableWorkflowRunner
|
||||
public class DurableWorkflowRunner
|
||||
{
|
||||
private const int MaxSupersteps = 100;
|
||||
|
||||
@@ -75,7 +75,7 @@ internal sealed class DurableWorkflowRunner
|
||||
/// Initializes a new instance of the <see cref="DurableWorkflowRunner"/> class.
|
||||
/// </summary>
|
||||
/// <param name="durableOptions">The durable options containing workflow configurations.</param>
|
||||
internal DurableWorkflowRunner(DurableOptions durableOptions)
|
||||
public DurableWorkflowRunner(DurableOptions durableOptions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(durableOptions);
|
||||
|
||||
|
||||
@@ -102,6 +102,43 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint)
|
||||
{
|
||||
if (httpRequestData == null)
|
||||
{
|
||||
throw new InvalidOperationException($"HTTP request data binding is missing for the invocation {context.InvocationId}.");
|
||||
}
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrechstrtationHttpTriggerAsync(
|
||||
httpRequestData,
|
||||
durableTaskClient,
|
||||
context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.InvokeWorkflowOrchestrationFunctionEntryPoint)
|
||||
{
|
||||
// The orchestration trigger binding provides an encoded orchestration request string.
|
||||
// We use the same string binding that entities use (encodedEntityRequest is reused for orchestrations).
|
||||
if (encodedEntityRequest is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Orchestration trigger binding is missing for the invocation {context.InvocationId}.");
|
||||
}
|
||||
|
||||
// Execute the orchestration using the static method in BuiltInFunctions
|
||||
context.GetInvocationResult().Value = BuiltInFunctions.InvokeWorkflowOrchestration(
|
||||
encodedEntityRequest,
|
||||
context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint)
|
||||
{
|
||||
// to do
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.InvokeWorkflowActivityAsync(encodedEntityRequest!, durableTaskClient, context);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported function entry point '{context.FunctionDefinition.EntryPoint}' for invocation {context.InvocationId}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.DurableTask.Worker.Grpc;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
|
||||
@@ -22,6 +25,120 @@ internal static class BuiltInFunctions
|
||||
internal static readonly string RunAgentEntityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeAgentAsync)}";
|
||||
internal static readonly string RunAgentMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunMcpToolAsync)}";
|
||||
|
||||
internal static readonly string RunWorkflowOrechstrtationHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrechstrtationHttpTriggerAsync)}";
|
||||
internal static readonly string InvokeWorkflowOrchestrationFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeWorkflowOrchestration)}";
|
||||
|
||||
internal static readonly string InvokeWorkflowActivityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeWorkflowActivityAsync)}";
|
||||
internal static readonly string RunWorkflowMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowMcpToolAsync)}";
|
||||
|
||||
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file - Azure Functions does not use single-file publishing
|
||||
internal static readonly string ScriptFile = Path.GetFileName(typeof(BuiltInFunctions).Assembly.Location);
|
||||
#pragma warning restore IL3000
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a workflow orchestration using the encoded orchestration request.
|
||||
/// </summary>
|
||||
/// <param name="encodedOrchestratorRequest">The base64-encoded protobuf payload for the orchestration.</param>
|
||||
/// <param name="functionContext">The function context.</param>
|
||||
/// <returns>The encoded orchestration response.</returns>
|
||||
internal static string InvokeWorkflowOrchestration(
|
||||
string encodedOrchestratorRequest,
|
||||
FunctionContext functionContext)
|
||||
{
|
||||
DurableOptions durableOptions = functionContext.InstanceServices.GetRequiredService<DurableOptions>();
|
||||
|
||||
return GrpcOrchestrationRunner.LoadAndRun<DurableWorkflowInput<object>, string>(
|
||||
encodedOrchestratorRequest,
|
||||
(orchestrationContext, input) => RunWorkflowOrchestrationCoreAsync(orchestrationContext, input, durableOptions)!,
|
||||
functionContext.InstanceServices);
|
||||
}
|
||||
|
||||
private static async Task<string> RunWorkflowOrchestrationCoreAsync(
|
||||
TaskOrchestrationContext orchestrationContext,
|
||||
DurableWorkflowInput<object>? input,
|
||||
DurableOptions durableOptions)
|
||||
{
|
||||
ILogger logger = orchestrationContext.CreateReplaySafeLogger("DurableWorkflow");
|
||||
DurableWorkflowRunner runner = new(durableOptions);
|
||||
|
||||
// ConfigureAwait(true) is required in orchestration code for deterministic replay.
|
||||
return await runner.RunWorkflowOrchestrationAsync(
|
||||
orchestrationContext,
|
||||
input ?? new DurableWorkflowInput<object> { Input = string.Empty },
|
||||
logger).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a workflow orchestration in response to an HTTP request.
|
||||
/// </summary>
|
||||
public static async Task<HttpResponseData> RunWorkflowOrechstrtationHttpTriggerAsync(
|
||||
[HttpTrigger] HttpRequestData req,
|
||||
[DurableClient] DurableTaskClient client,
|
||||
FunctionContext context)
|
||||
{
|
||||
var workflowName = context.FunctionDefinition.Name.Replace(HttpPrefix, string.Empty);
|
||||
var orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
|
||||
var inputMessage = await req.ReadAsStringAsync();
|
||||
|
||||
DurableWorkflowInput<string> orchestrtionInput = new() { Input = inputMessage! };
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, orchestrtionInput);
|
||||
|
||||
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
|
||||
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}. Orchestration instanceId: {instanceId}");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static Task<string> InvokeWorkflowActivityAsync(
|
||||
[ActivityTrigger] string input,
|
||||
[DurableClient] DurableTaskClient durableTaskClient,
|
||||
FunctionContext functionContext)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(input);
|
||||
ArgumentNullException.ThrowIfNull(durableTaskClient);
|
||||
ArgumentNullException.ThrowIfNull(functionContext);
|
||||
|
||||
string activityFunctionName = functionContext.FunctionDefinition.Name;
|
||||
string executorName = WorkflowNamingHelper.ToWorkflowName(activityFunctionName);
|
||||
|
||||
DurableOptions durableOptions = functionContext.InstanceServices.GetRequiredService<DurableOptions>();
|
||||
if (!durableOptions.Workflows.Executors.TryGetExecutor(executorName, out ExecutorRegistration? registration))
|
||||
{
|
||||
throw new InvalidOperationException($"Executor '{executorName}' not found in workflow options.");
|
||||
}
|
||||
|
||||
return DurableActivityExecutor.ExecuteAsync(registration.Binding, input, functionContext.CancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<string?> RunWorkflowMcpToolAsync(
|
||||
[McpToolTrigger("BuiltInWorkflowMcpTool")] ToolInvocationContext context,
|
||||
[DurableClient] DurableTaskClient client,
|
||||
FunctionContext functionContext)
|
||||
{
|
||||
if (context.Arguments is null)
|
||||
{
|
||||
throw new ArgumentException("MCP Tool invocation is missing required arguments.");
|
||||
}
|
||||
|
||||
if (!context.Arguments.TryGetValue("input", out object? inputObj) || inputObj is not string input)
|
||||
{
|
||||
throw new ArgumentException("MCP Tool invocation is missing required 'input' argument of type string.");
|
||||
}
|
||||
|
||||
// Extract workflow name from the MCP tool name (format: mcptool-workflow-{workflowName})
|
||||
string workflowName = context.Name;
|
||||
string orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
|
||||
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, input);
|
||||
|
||||
// Wait for the orchestration to complete and return the result
|
||||
OrchestrationMetadata? metadata = await client.WaitForInstanceCompletionAsync(
|
||||
instanceId,
|
||||
getInputsAndOutputs: true,
|
||||
cancellation: functionContext.CancellationToken);
|
||||
|
||||
return metadata?.ReadOutputAs<string>();
|
||||
}
|
||||
|
||||
// Exposed as an entity trigger via AgentFunctionsProvider
|
||||
public static Task<string> InvokeAgentAsync(
|
||||
[DurableClient] DurableTaskClient client,
|
||||
|
||||
+161
-1
@@ -1,11 +1,18 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
using Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Builder;
|
||||
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Worker;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
|
||||
@@ -14,6 +21,153 @@ namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
/// </summary>
|
||||
public static class FunctionsApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures durable agents and workflows in a unified way.
|
||||
/// </summary>
|
||||
/// <param name="builder">The Functions application builder.</param>
|
||||
/// <param name="configure">A delegate to configure the durable options.</param>
|
||||
/// <returns>The Functions application builder for method chaining.</returns>
|
||||
/// <remarks>
|
||||
/// This method provides a unified configuration point for both durable agents and workflows.
|
||||
/// It automatically generates HTTP API endpoints for agents and workflows, and configures
|
||||
/// the necessary middleware and services for durable execution.
|
||||
/// </remarks>
|
||||
public static FunctionsApplicationBuilder ConfigureDurableOptions(
|
||||
this FunctionsApplicationBuilder builder,
|
||||
Action<DurableOptions> configure)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
DurableOptions options = new();
|
||||
configure(options);
|
||||
|
||||
builder.Services.AddSingleton(options);
|
||||
|
||||
if (options.Workflows.Workflows.Count > 0)
|
||||
{
|
||||
ConfigureWorkflowOrchestrations(builder, options.Workflows);
|
||||
// Do things to enable workflow as orchestrator functions.
|
||||
// Register the Workflow metadata transformer.
|
||||
builder.ConfigureDurableWorkflows(durableWorkflwoOptions =>
|
||||
{
|
||||
// what
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>();
|
||||
|
||||
builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowOrchestrationFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal)
|
||||
);
|
||||
builder.Services.AddSingleton<BuiltInFunctionExecutor>();
|
||||
|
||||
//builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
|
||||
// string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
// || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowOrchestrationFunctionEntryPoint, StringComparison.Ordinal)
|
||||
// || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal)
|
||||
// || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
// );
|
||||
//builder.Services.AddSingleton<BuiltInFunctionExecutor>();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void ConfigureWorkflowOrchestrations(FunctionsApplicationBuilder builder, DurableWorkflowOptions workflows)
|
||||
{
|
||||
// Discover sub-workflows recursively and add them to the workflows dictionary
|
||||
// so they are registered as separate orchestrations alongside the main workflows.
|
||||
DiscoverSubWorkflows(workflows);
|
||||
|
||||
builder.ConfigureDurableWorker().AddTasks(tasks =>
|
||||
{
|
||||
foreach (string workflowName in workflows.Workflows.Select(kp => kp.Key))
|
||||
{
|
||||
string orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
|
||||
|
||||
tasks.AddOrchestratorFunc<DurableWorkflowInput<object>, string>(
|
||||
orchestrationFunctionName,
|
||||
async (orchestrationContext, orchInput) =>
|
||||
{
|
||||
FunctionContext functionContext = orchestrationContext.GetFunctionContext()
|
||||
?? throw new InvalidOperationException("FunctionContext is not available in the orchestration context.");
|
||||
|
||||
DurableWorkflowRunner runner = functionContext.InstanceServices.GetRequiredService<DurableWorkflowRunner>();
|
||||
ILogger logger = orchestrationContext.CreateReplaySafeLogger(orchestrationFunctionName);
|
||||
DurableWorkflowInput<object> workflowInput = orchInput;
|
||||
|
||||
return await runner.RunWorkflowOrchestrationAsync(orchestrationContext, workflowInput, logger).ConfigureAwait(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void DiscoverSubWorkflows(DurableWorkflowOptions workflows)
|
||||
{
|
||||
HashSet<string> visited = new(workflows.Workflows.Keys);
|
||||
Queue<Workflow> queue = new(workflows.Workflows.Values);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Workflow workflow = queue.Dequeue();
|
||||
|
||||
foreach (ExecutorBinding binding in workflow.ReflectExecutors().Values)
|
||||
{
|
||||
if (binding is SubworkflowBinding subworkflowBinding)
|
||||
{
|
||||
Workflow subWorkflow = subworkflowBinding.WorkflowInstance;
|
||||
if (subWorkflow.Name is not null && visited.Add(subWorkflow.Name))
|
||||
{
|
||||
workflows.AddWorkflow(subWorkflow);
|
||||
queue.Enqueue(subWorkflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static FunctionsApplicationBuilder RegisterWorkflowServices(this FunctionsApplicationBuilder builder)
|
||||
{
|
||||
// Register FunctionsWorkflowRunner as a singleton
|
||||
// builder.Services.TryAddSingleton<FunctionsWorkflowRunner>();
|
||||
|
||||
// Also register it as DurableWorkflowRunner so orchestrations can resolve it by base type
|
||||
//builder.Services.TryAddSingleton<DurableWorkflowRunner>(sp => sp.GetRequiredService<FunctionsWorkflowRunner>());
|
||||
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>());
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures durable workflow services for the application and allows customization of durable workflow options.
|
||||
/// </summary>
|
||||
/// <remarks>This method registers the services required for durable workflows using
|
||||
/// Microsoft.DurableTask.Workflows. Call this method during application startup to enable durable workflows in your
|
||||
/// Azure Functions app.</remarks>
|
||||
/// <param name="builder">The application builder used to configure services and middleware for the Azure Functions app.</param>
|
||||
/// <param name="configure">A delegate that is used to configure the durable workflow options. Cannot be null.</param>
|
||||
/// <returns>The same <see cref="FunctionsApplicationBuilder"/> instance that this method was called on, to support method
|
||||
/// chaining.</returns>
|
||||
public static FunctionsApplicationBuilder ConfigureDurableWorkflows(this FunctionsApplicationBuilder builder, Action<DurableWorkflowOptions> configure)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
//RegisterWorkflowServices(builder);
|
||||
//builder.Services.AddSingleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>();
|
||||
|
||||
// The main durable workflows services registration is done in Microsoft.DurableTask.Workflows.
|
||||
builder.Services.ConfigureDurableWorkflows(configure);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use durable agents with a builder pattern.
|
||||
/// </summary>
|
||||
@@ -38,7 +192,13 @@ public static class FunctionsApplicationBuilderExtensions
|
||||
builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal));
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowOrchestrationFunctionEntryPoint, StringComparison.Ordinal)
|
||||
|| string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal)
|
||||
);
|
||||
builder.Services.AddSingleton<BuiltInFunctionExecutor>();
|
||||
|
||||
return builder;
|
||||
|
||||
+4
-1
@@ -4,7 +4,10 @@
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- CA2007: This rule should generally be suppressed in Durable Task libraries. Also, this is not library code. -->
|
||||
<NoWarn>$(NoWarn);CA2007</NoWarn>
|
||||
<!-- AD0001: Suppress analyzer crashes from DurableTask analyzers (known bug with certain code patterns) -->
|
||||
<NoWarn>$(NoWarn);CA2007;AD0001</NoWarn>
|
||||
<!-- Ensure analyzer exceptions don't fail the build -->
|
||||
<ReportAnalyzerExceptionsAsErrors>false</ReportAnalyzerExceptionsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
|
||||
internal sealed class DurableWorkflowFunctionMetadataTransformer : IFunctionMetadataTransformer
|
||||
{
|
||||
private static readonly HashSet<string> _registeredFunctionNames = new();
|
||||
private readonly ILogger<DurableWorkflowFunctionMetadataTransformer> _logger;
|
||||
private readonly DurableWorkflowOptions _options;
|
||||
|
||||
public DurableWorkflowFunctionMetadataTransformer(ILogger<DurableWorkflowFunctionMetadataTransformer> logger, DurableOptions durableOptions)
|
||||
{
|
||||
this._logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
ArgumentNullException.ThrowIfNull(durableOptions);
|
||||
this._options = durableOptions.Workflows;
|
||||
}
|
||||
|
||||
public string Name => nameof(DurableWorkflowFunctionMetadataTransformer);
|
||||
|
||||
public void Transform(IList<IFunctionMetadata> original)
|
||||
{
|
||||
this._logger.LogTransformStart(original.Count);
|
||||
|
||||
foreach (var workflow in this._options.Workflows)
|
||||
{
|
||||
this._logger.LogAddingWorkflowFunction(workflow.Key);
|
||||
|
||||
// Currently due to how durable executor is registered, we are not able to bind TaskOrechestrationContext parameter properly
|
||||
// because the InputBinding for TOC happens inside the DurableExecutor (rathen than in an input converter).
|
||||
// So for now, we are going to use single orchestration function for all workflows.
|
||||
//original.Add(CreateOrchestrationTrigger(workflow.Key));
|
||||
|
||||
// We also want to create an HTTP trigger for this orchestration so users can start it via HTTP.
|
||||
this._logger.LogAddingHttpTrigger(workflow.Key);
|
||||
original.Add(CreateHttpTrigger(workflow.Key, $"workflows/{workflow.Key}/run"));
|
||||
|
||||
// Check if MCP tool trigger is enabled for this workflow
|
||||
if (DurableWorkflowOptionsExtensions.TryGetWorkflowOptions(workflow.Key, out FunctionsWorkflowOptions? workflowOptions) &&
|
||||
workflowOptions?.McpToolTrigger.IsEnabled == true)
|
||||
{
|
||||
this._logger.LogAddingMcpToolTrigger(workflow.Key);
|
||||
original.Add(CreateMcpToolTrigger(workflow.Key, workflow.Value.Description));
|
||||
}
|
||||
|
||||
// Create activity/entity functions for each executor in the workflow based on their type
|
||||
// Extract executor IDs from edges and start executor
|
||||
HashSet<string> executorIds = new() { workflow.Value.StartExecutorId };
|
||||
|
||||
var reflectedEdges = workflow.Value.ReflectEdges();
|
||||
foreach (var (sourceId, edgeSet) in reflectedEdges)
|
||||
{
|
||||
executorIds.Add(sourceId);
|
||||
foreach (var edge in edgeSet)
|
||||
{
|
||||
foreach (var sinkId in edge.Connection.SinkIds)
|
||||
{
|
||||
executorIds.Add(sinkId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, ExecutorBinding> executorBindings = workflow.Value.ReflectExecutors();
|
||||
|
||||
foreach (string executorId in executorIds)
|
||||
{
|
||||
if (executorBindings.TryGetValue(executorId, out ExecutorBinding? executorBinding))
|
||||
{
|
||||
// Sub-workflow bindings are registered as separate orchestrations, not as activities
|
||||
if (executorBinding is SubworkflowBinding)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string executorName = WorkflowNamingHelper.GetExecutorName(executorId);
|
||||
string functionName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorName);
|
||||
|
||||
// Skip if this function has already been registered by another workflow
|
||||
if (!_registeredFunctionNames.Add(functionName))
|
||||
{
|
||||
this._logger.LogSkippingDuplicateFunction(functionName, workflow.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the executor type is an agent-related type
|
||||
if (executorBinding is AIAgentBinding)
|
||||
{
|
||||
this._logger.LogAddingAgentEntityFunction(executorId, executorBinding.ExecutorType.FullName ?? executorBinding.ExecutorType.Name, workflow.Key);
|
||||
original.Add(CreateEntityTrigger(executorName));
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogAddingActivityFunction(executorId, executorBinding.ExecutorType.FullName ?? executorBinding.ExecutorType.Name, workflow.Key);
|
||||
original.Add(CreateActivityTrigger(functionName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._logger.LogTransformFinished(original.Count);
|
||||
}
|
||||
|
||||
private static DefaultFunctionMetadata CreateHttpTrigger(string name, string route)
|
||||
{
|
||||
return new DefaultFunctionMetadata()
|
||||
{
|
||||
Name = $"{BuiltInFunctions.HttpPrefix}{name}",
|
||||
Language = "dotnet-isolated",
|
||||
RawBindings =
|
||||
[
|
||||
$"{{\"name\":\"req\",\"type\":\"httpTrigger\",\"direction\":\"In\",\"authLevel\":\"function\",\"methods\": [\"post\"],\"route\":\"{route}\"}}",
|
||||
"{\"name\":\"$return\",\"type\":\"http\",\"direction\":\"Out\"}",
|
||||
"{\"name\":\"client\",\"type\":\"durableClient\",\"direction\":\"In\"}"
|
||||
],
|
||||
EntryPoint = BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint,
|
||||
ScriptFile = BuiltInFunctions.ScriptFile
|
||||
};
|
||||
}
|
||||
|
||||
//private static DefaultFunctionMetadata CreateOrchestrationTrigger(string name)
|
||||
//{
|
||||
// return new DefaultFunctionMetadata()
|
||||
// {
|
||||
// Name = AgentSessionId.ToEntityName(name),
|
||||
// Language = "dotnet-isolated",
|
||||
// RawBindings =
|
||||
// [
|
||||
// // """{"name":"context","type":"orchestrationTrigger","direction":"In"}""",
|
||||
// """{"name":"taskOrchestrationContext","type":"orchestrationTrigger","direction":"In"}""",
|
||||
|
||||
// ],
|
||||
// EntryPoint = BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint,
|
||||
// ScriptFile = BuiltInFunctions.ScriptFile,
|
||||
// };
|
||||
//}
|
||||
|
||||
//private static DefaultFunctionMetadata CreateOrchestrationFunction(string functionName)
|
||||
//{
|
||||
// throw new NotImplementedException();
|
||||
//}
|
||||
|
||||
private static DefaultFunctionMetadata CreateActivityTrigger(string functionName)
|
||||
{
|
||||
return new DefaultFunctionMetadata()
|
||||
{
|
||||
Name = functionName,
|
||||
Language = "dotnet-isolated",
|
||||
RawBindings =
|
||||
[
|
||||
"""{"name":"input","type":"activityTrigger","direction":"In","dataType":"String"}""",
|
||||
"""{"name":"durableTaskClient","type":"durableClient","direction":"In"}"""
|
||||
],
|
||||
EntryPoint = BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint,
|
||||
ScriptFile = BuiltInFunctions.ScriptFile,
|
||||
};
|
||||
}
|
||||
|
||||
private static DefaultFunctionMetadata CreateEntityTrigger(string functionName)
|
||||
{
|
||||
return new DefaultFunctionMetadata()
|
||||
{
|
||||
Name = AgentSessionId.ToEntityName(functionName),
|
||||
Language = "dotnet-isolated",
|
||||
RawBindings =
|
||||
[
|
||||
"""{"name":"encodedEntityRequest","type":"entityTrigger","direction":"In"}""",
|
||||
"""{"name":"client","type":"durableClient","direction":"In"}"""
|
||||
],
|
||||
EntryPoint = BuiltInFunctions.RunAgentEntityFunctionEntryPoint,
|
||||
ScriptFile = BuiltInFunctions.ScriptFile,
|
||||
};
|
||||
}
|
||||
|
||||
private static DefaultFunctionMetadata CreateMcpToolTrigger(string workflowName, string? description)
|
||||
{
|
||||
return new DefaultFunctionMetadata
|
||||
{
|
||||
Name = $"{BuiltInFunctions.McpToolPrefix}{workflowName}",
|
||||
Language = "dotnet-isolated",
|
||||
RawBindings =
|
||||
[
|
||||
$$"""{"name":"context","type":"mcpToolTrigger","direction":"In","toolName":"{{workflowName}}","description":"{{description ?? $"Run the {workflowName} workflow"}}","toolProperties":"[{\"propertyName\":\"input\",\"propertyType\":\"string\",\"description\":\"The input to the workflow.\",\"isRequired\":true,\"isArray\":false}]"}""",
|
||||
"""{"name":"input","type":"mcpToolProperty","direction":"In","propertyName":"input","description":"The input to the workflow","isRequired":true,"dataType":"String","propertyType":"string"}""",
|
||||
"""{"name":"client","type":"durableClient","direction":"In"}"""
|
||||
],
|
||||
EntryPoint = BuiltInFunctions.RunWorkflowMcpToolFunctionEntryPoint,
|
||||
ScriptFile = BuiltInFunctions.ScriptFile,
|
||||
};
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Logging messages for <see cref="DurableWorkflowFunctionMetadataTransformer"/>.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal static partial class DurableWorkflowFunctionMetadataTransformerLogs
|
||||
{
|
||||
[LoggerMessage(
|
||||
EventId = 200,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Transforming function metadata to add durable workflow functions. Initial function count: {FunctionCount}")]
|
||||
public static partial void LogTransformStart(this ILogger logger, int functionCount);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 201,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Adding durable workflow function for workflow: {WorkflowName}")]
|
||||
public static partial void LogAddingWorkflowFunction(this ILogger logger, string workflowName);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 202,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Adding HTTP trigger function for workflow: {WorkflowName}")]
|
||||
public static partial void LogAddingHttpTrigger(this ILogger logger, string workflowName);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 203,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Adding activity function for executor: {ExecutorId} (Type: {ExecutorType}) in workflow: {WorkflowName}")]
|
||||
public static partial void LogAddingActivityFunction(this ILogger logger, string executorId, string executorType, string workflowName);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 204,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Adding agent entity function for executor: {ExecutorId} (Type: {ExecutorType}) in workflow: {WorkflowName}")]
|
||||
public static partial void LogAddingAgentEntityFunction(this ILogger logger, string executorId, string executorType, string workflowName);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 205,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Adding MCP tool trigger function for workflow: {WorkflowName}")]
|
||||
public static partial void LogAddingMcpToolTrigger(this ILogger logger, string workflowName);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 206,
|
||||
Level = LogLevel.Information,
|
||||
Message = "Transform finished. Updated function count: {FunctionCount}")]
|
||||
public static partial void LogTransformFinished(this ILogger logger, int functionCount);
|
||||
|
||||
[LoggerMessage(
|
||||
EventId = 207,
|
||||
Level = LogLevel.Debug,
|
||||
Message = "Skipping duplicate function registration: {FunctionName} (already registered by another workflow) in workflow: {WorkflowName}")]
|
||||
public static partial void LogSkippingDuplicateFunction(this ILogger logger, string functionName, string workflowName);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering and configuring workflows in the context of the Azure Functions hosting environment.
|
||||
/// </summary>
|
||||
public static class DurableWorkflowOptionsExtensions
|
||||
{
|
||||
// Registry of workflow options.
|
||||
private static readonly Dictionary<string, FunctionsWorkflowOptions> s_workflowOptions = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a workflow to the specified <see cref="DurableWorkflowOptions"/> instance and optionally configures
|
||||
/// workflow-specific options.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DurableWorkflowOptions"/> instance to which the workflow will be added.</param>
|
||||
/// <param name="workflow">The workflow to add. The workflow's Name property must not be null or empty.</param>
|
||||
/// <param name="configure">An optional delegate to configure workflow-specific options. If null, default options are used.</param>
|
||||
/// <returns>The updated <see cref="DurableWorkflowOptions"/> instance containing the added workflow.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> or <paramref name="workflow"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
|
||||
public static DurableWorkflowOptions AddWorkflow(
|
||||
this DurableWorkflowOptions options,
|
||||
Workflow workflow,
|
||||
Action<FunctionsWorkflowOptions>? configure)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(workflow);
|
||||
|
||||
if (string.IsNullOrEmpty(workflow.Name))
|
||||
{
|
||||
throw new ArgumentException("Workflow must have a valid Name property.", nameof(workflow));
|
||||
}
|
||||
|
||||
// Initialize with default behavior (MCP trigger disabled)
|
||||
FunctionsWorkflowOptions workflowOptions = new();
|
||||
configure?.Invoke(workflowOptions);
|
||||
|
||||
options.AddWorkflow(workflow);
|
||||
s_workflowOptions[workflow.Name] = workflowOptions;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a workflow to the specified <see cref="DurableWorkflowOptions"/> instance and configures
|
||||
/// trigger support for MCP tool invocations.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DurableWorkflowOptions"/> instance to which the workflow will be added.</param>
|
||||
/// <param name="workflow">The workflow to add. The workflow's Name property must not be null or empty.</param>
|
||||
/// <param name="enableMcpToolTrigger">true to enable an MCP tool trigger for the workflow; otherwise, false.</param>
|
||||
/// <returns>The updated <see cref="DurableWorkflowOptions"/> instance with the specified workflow and trigger configuration applied.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> or <paramref name="workflow"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
|
||||
public static DurableWorkflowOptions AddWorkflow(
|
||||
this DurableWorkflowOptions options,
|
||||
Workflow workflow,
|
||||
bool enableMcpToolTrigger)
|
||||
{
|
||||
return AddWorkflow(options, workflow, workflowOptions => workflowOptions.McpToolTrigger.IsEnabled = enableMcpToolTrigger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the <see cref="FunctionsWorkflowOptions"/> for a workflow by name.
|
||||
/// </summary>
|
||||
/// <param name="workflowName">The name of the workflow.</param>
|
||||
/// <param name="workflowOptions">When this method returns, contains the workflow options if found; otherwise, null.</param>
|
||||
/// <returns><c>true</c> if the workflow options were found; otherwise, <c>false</c>.</returns>
|
||||
internal static bool TryGetWorkflowOptions(string workflowName, out FunctionsWorkflowOptions? workflowOptions)
|
||||
{
|
||||
return s_workflowOptions.TryGetValue(workflowName, out workflowOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the workflow options used for dependency injection (read-only copy).
|
||||
/// </summary>
|
||||
internal static IReadOnlyDictionary<string, FunctionsWorkflowOptions> GetWorkflowOptionsSnapshot()
|
||||
{
|
||||
return new Dictionary<string, FunctionsWorkflowOptions>(s_workflowOptions, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Provides configuration options for enabling and customizing function triggers for a workflow.
|
||||
/// </summary>
|
||||
public sealed class FunctionsWorkflowOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the options used to configure the MCP tool trigger behavior.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, MCP tool trigger is disabled for workflows.
|
||||
/// </remarks>
|
||||
public McpToolTriggerOptions McpToolTrigger { get; set; } = new(false);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Net;
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Agents.AI.DurableTask.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
using Microsoft.DurableTask.Client;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to invoke and manage workflow orchestrations in response to HTTP requests within an Azure
|
||||
/// Functions environment.
|
||||
/// </summary>
|
||||
public sealed class FunctionsWorkflowRunner : DurableWorkflowRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the FunctionsWorkflowRunner class using the specified DurableOptions.
|
||||
/// </summary>
|
||||
/// <param name="durableOptions">The DurableOptions that configure the behavior of the workflow runner. This parameter cannot be null.</param>
|
||||
public FunctionsWorkflowRunner(DurableOptions durableOptions) : base(durableOptions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a workflow orchestration in response to an HTTP request.
|
||||
/// </summary>
|
||||
public static async Task<HttpResponseData> RunWorkflowOrechstrtationHttpTriggerAsync(
|
||||
[HttpTrigger] HttpRequestData req,
|
||||
[DurableClient] DurableTaskClient client,
|
||||
FunctionContext context)
|
||||
{
|
||||
var functionName = context.FunctionDefinition.Name;
|
||||
var workflowName = functionName.Replace("-http", string.Empty);
|
||||
var orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
|
||||
var inputMessage = await req.ReadAsStringAsync();
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, inputMessage);
|
||||
|
||||
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
|
||||
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}. Orchestration instanceId: {instanceId}");
|
||||
return response;
|
||||
}
|
||||
|
||||
internal async Task<string> ExecuteActivityAsync(string activityFunctionName, string input, DurableTaskClient durableTaskClient, FunctionContext functionContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user