mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
WIP
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
<Project Path="samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
|
||||
<Project Path="samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
|
||||
<Project Path="samples/AzureFunctions/08_ReliableStreaming/08_ReliableStreaming.csproj" />
|
||||
<Project Path="samples/AzureFunctions/09_Workflow/09_Workflow.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/DurableAgents/">
|
||||
<File Path="samples/DurableAgents/ConsoleApps/README.md" />
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<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>
|
||||
<AssemblyName>Workflow</AssemblyName>
|
||||
<RootNamespace>Workflow</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@authority=http://localhost:7071
|
||||
|
||||
### Prompt the agent
|
||||
POST {{authority}}/api/agents/Joker/run
|
||||
POST {{authority}}/api/workflows/MyTestWorkflow/run
|
||||
Content-Type: text/plain
|
||||
|
||||
Tell me a joke about a pirate.
|
||||
Hello world
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<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" />
|
||||
<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" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SingleAgent;
|
||||
|
||||
public static class OrchFunction
|
||||
{
|
||||
[Function(nameof(OrchFunction))]
|
||||
public static async Task<List<string>> RunOrchestratorAsync(
|
||||
[OrchestrationTrigger] TaskOrchestrationContext context)
|
||||
{
|
||||
ILogger logger = context.CreateReplaySafeLogger(nameof(OrchFunction));
|
||||
logger.LogInformation("Saying hello.");
|
||||
var outputs = new List<string>();
|
||||
|
||||
outputs.Add("Tokyo");
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
[Function("OrchFunction_HttpStart")]
|
||||
public static async Task<HttpResponseData> HttpStartAsync(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
|
||||
[DurableClient] DurableTaskClient client,
|
||||
FunctionContext executionContext)
|
||||
{
|
||||
// Function input comes from the request content.
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
|
||||
nameof(OrchFunction));
|
||||
|
||||
// Returns an HTTP 202 response with an instance management payload.
|
||||
// See https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-http-api#start-orchestration
|
||||
return await client.CreateCheckStatusResponseAsync(req, instanceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Azure.Functions.Worker.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// Get the Azure OpenAI endpoint and deployment name from environment variables.
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
|
||||
|
||||
// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
|
||||
string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
|
||||
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
|
||||
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
Func<string, string> uppercaseFunc = s => s.ToUpperInvariant();
|
||||
var uppercase = uppercaseFunc.BindAsExecutor("UppercaseExecutor");
|
||||
|
||||
Func<string, string> reverseTextFunc = s => s.ToUpperInvariant();
|
||||
var reverse = reverseTextFunc.BindAsExecutor("ReverseTextExecutor");
|
||||
|
||||
WorkflowBuilder builder = new(uppercase);
|
||||
builder.AddEdge(uppercase, reverse).WithOutputFrom(reverse);
|
||||
var workflow = builder.WithName("MyTestWorkflow").Build();
|
||||
|
||||
// Configure the function app to host the AI agent.
|
||||
// This will automatically generate HTTP API endpoints for the agent.
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
.ConfigureFunctionsWebApplication()
|
||||
.ConfigureDurableAgents(options => options.AddAIAgent(agent, timeToLive: TimeSpan.FromHours(1)))
|
||||
.AddDurableWorkflows(options =>
|
||||
{
|
||||
// Configure durable workflow options here if needed.
|
||||
options.AddWorkflow(workflow);
|
||||
})
|
||||
.Build();
|
||||
app.Run();
|
||||
@@ -0,0 +1,89 @@
|
||||
# Single Agent Sample
|
||||
|
||||
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
|
||||
- Registering agents with the Function app and running them using HTTP.
|
||||
- Conversation management (via session IDs) for isolated interactions.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
|
||||
|
||||
## Running the Sample
|
||||
|
||||
With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
|
||||
|
||||
You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
|
||||
|
||||
Bash (Linux/macOS/WSL):
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:7071/api/agents/Joker/run \
|
||||
-H "Content-Type: text/plain" \
|
||||
-d "Tell me a joke about a pirate."
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod -Method Post `
|
||||
-Uri http://localhost:7071/api/agents/Joker/run `
|
||||
-ContentType text/plain `
|
||||
-Body "Tell me a joke about a pirate."
|
||||
```
|
||||
|
||||
You can also send JSON requests:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:7071/api/agents/Joker/run \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-d '{"message": "Tell me a joke about a pirate."}'
|
||||
```
|
||||
|
||||
To continue a conversation, include the `thread_id` in the query string or JSON body:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-d '{"message": "Tell me another one."}'
|
||||
```
|
||||
|
||||
The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
|
||||
|
||||
```text
|
||||
Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
|
||||
```
|
||||
|
||||
The expected `application/json` output will look something like:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 200,
|
||||
"thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
|
||||
"response": {
|
||||
"Messages": [
|
||||
{
|
||||
"AuthorName": "Joker",
|
||||
"CreatedAt": "2025-11-11T12:00:00.0000000Z",
|
||||
"Role": "assistant",
|
||||
"Contents": [
|
||||
{
|
||||
"Type": "text",
|
||||
"Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Usage": {
|
||||
"InputTokenCount": 78,
|
||||
"OutputTokenCount": 36,
|
||||
"TotalTokenCount": 114
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# Default endpoint address for local testing
|
||||
@authority=http://localhost:7071
|
||||
|
||||
### Prompt the agent
|
||||
POST {{authority}}/api/agents/Joker/run
|
||||
Content-Type: text/plain
|
||||
|
||||
Tell me a joke about a pirate.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask;
|
||||
|
||||
/// <summary>
|
||||
/// Provides configuration options for managing durable workflows within an application.
|
||||
/// </summary>
|
||||
public sealed class DurableWorkflowOptions
|
||||
{
|
||||
private readonly Dictionary<string, Workflow> _workflows = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a workflow to the collection for processing or execution.
|
||||
/// </summary>
|
||||
/// <param name="workflow">The workflow instance to add. Cannot be null.</param>
|
||||
public void AddWorkflow(Workflow workflow)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(workflow);
|
||||
|
||||
if (string.IsNullOrEmpty(workflow.Name))
|
||||
{
|
||||
throw new ArgumentException("Workflow must have a valid Name property.", nameof(workflow));
|
||||
}
|
||||
|
||||
this._workflows[workflow.Name] = workflow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of workflows available in the current context, keyed by their unique names.
|
||||
/// </summary>
|
||||
/// <remarks>The returned dictionary is read-only and reflects the current set of registered workflows.
|
||||
/// Changes to the underlying workflow collection are immediately visible through this property. Accessing a
|
||||
/// workflow by name that does not exist will result in a KeyNotFoundException.</remarks>
|
||||
public IReadOnlyDictionary<string, Workflow> Workflows => this._workflows;
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Azure.Functions.Worker.Context.Features;
|
||||
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
using Microsoft.Azure.Functions.Worker.Invocation;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
@@ -25,6 +26,20 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
IFunctionInputBindingFeature? functionInputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>() ??
|
||||
throw new InvalidOperationException("Function input binding feature is not available on the current context.");
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint)
|
||||
{
|
||||
var triggerBinding = context.FunctionDefinition.InputBindings.Values.FirstOrDefault(b => b.Type == "orchestrationTrigger");
|
||||
var taskOrechstrationContextBinding = context.BindInputAsync<TaskOrchestrationContext>(triggerBinding!);
|
||||
|
||||
if (taskOrechstrationContextBinding.IsCompletedSuccessfully)
|
||||
{
|
||||
var t = taskOrechstrationContextBinding.Result.Value;
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrchestratorAsync(t!);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FunctionInputBindingResult? inputBindingResults = await functionInputBindingFeature.BindFunctionInputAsync(context);
|
||||
if (inputBindingResults is not { Values: { } values })
|
||||
{
|
||||
@@ -35,6 +50,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
string? encodedEntityRequest = null;
|
||||
DurableTaskClient? durableTaskClient = null;
|
||||
ToolInvocationContext? mcpToolInvocationContext = null;
|
||||
//string? encodedTaskOrchestrationContext = null;
|
||||
|
||||
foreach (var binding in values)
|
||||
{
|
||||
@@ -52,10 +68,13 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
case ToolInvocationContext toolContext:
|
||||
mcpToolInvocationContext = toolContext;
|
||||
break;
|
||||
//case string orchestrationContext:
|
||||
// encodedTaskOrchestrationContext = orchestrationContext;
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
if (durableTaskClient is null)
|
||||
if (durableTaskClient is null && context.FunctionDefinition.EntryPoint != BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint)
|
||||
{
|
||||
// This is not expected to happen since all built-in functions are
|
||||
// expected to have a Durable Task client binding.
|
||||
@@ -71,7 +90,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.RunAgentHttpAsync(
|
||||
httpRequestData,
|
||||
durableTaskClient,
|
||||
durableTaskClient!,
|
||||
context);
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +103,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
}
|
||||
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.InvokeAgentAsync(
|
||||
durableTaskClient,
|
||||
durableTaskClient!,
|
||||
encodedEntityRequest,
|
||||
context);
|
||||
return;
|
||||
@@ -98,7 +117,40 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
|
||||
}
|
||||
|
||||
context.GetInvocationResult().Value =
|
||||
await BuiltInFunctions.RunMcpToolAsync(mcpToolInvocationContext, durableTaskClient, context);
|
||||
await BuiltInFunctions.RunMcpToolAsync(mcpToolInvocationContext, durableTaskClient!, context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint)
|
||||
{
|
||||
//if (httpRequestData == null)
|
||||
//{
|
||||
// throw new InvalidOperationException($"HTTP request data binding is missing for the invocation {context.InvocationId}.");
|
||||
//}
|
||||
|
||||
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.RunWorkflowOrechstrtationFunctionEntryPoint)
|
||||
{
|
||||
var triggerBinding = context.FunctionDefinition.InputBindings.Values.FirstOrDefault(b => b.Type == "orchestrationTrigger");
|
||||
var taskOrechstrationContextBinding = context.BindInputAsync<TaskOrchestrationContext>(triggerBinding!);
|
||||
|
||||
if (taskOrechstrationContextBinding.IsCompletedSuccessfully)
|
||||
{
|
||||
var t = taskOrechstrationContextBinding.Result.Value;
|
||||
context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrchestratorAsync(t!);
|
||||
}
|
||||
|
||||
//context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrchestratorAsync(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Agents.AI.DurableTask;
|
||||
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;
|
||||
@@ -20,8 +21,47 @@ internal static class BuiltInFunctions
|
||||
|
||||
internal static readonly string RunAgentHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunAgentHttpAsync)}";
|
||||
internal static readonly string RunAgentEntityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeAgentAsync)}";
|
||||
internal static readonly string RunWorkflowOrechstrtationHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrechstrtationHttpTriggerAsync)}";
|
||||
internal static readonly string RunWorkflowOrechstrtationFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrchestratorAsync)}";
|
||||
internal static readonly string InvokeWorkflowActivityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeWorkflowActivityAsync)}";
|
||||
internal static readonly string RunAgentMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunMcpToolAsync)}";
|
||||
|
||||
#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
|
||||
|
||||
// Exposed as an activity trigger for workflow executors
|
||||
public static Task<string> InvokeWorkflowActivityAsync(
|
||||
[ActivityTrigger] string input,
|
||||
FunctionContext functionContext)
|
||||
{
|
||||
return Task.FromResult($"Hello from activity with input: {input}");
|
||||
}
|
||||
|
||||
//[Function("my-Orchestration")]
|
||||
//public static async Task<List<string>> RunOrchestrator1Async(
|
||||
//[OrchestrationTrigger] TaskOrchestrationContext context)
|
||||
//{
|
||||
// ILogger logger = context.CreateReplaySafeLogger(nameof(Function));
|
||||
// logger.LogInformation("Saying hello.");
|
||||
|
||||
// // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
||||
// return new List<string>();
|
||||
//}
|
||||
|
||||
//[Function("dafx-Orchestration")]
|
||||
public static async Task<List<string>> RunWorkflowOrchestratorAsync(TaskOrchestrationContext taskOrchestrationContext)
|
||||
{
|
||||
//ILogger logger = context.CreateReplaySafeLogger(nameof(Function));
|
||||
//logger.LogInformation("Invoking RunWorkflowOrchestrator");
|
||||
var outputs = new List<string>();
|
||||
|
||||
await Task.Delay(1);
|
||||
outputs.Add("to do - call get executor result");
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
// Exposed as an entity trigger via AgentFunctionsProvider
|
||||
public static Task<string> InvokeAgentAsync(
|
||||
[DurableClient] DurableTaskClient client,
|
||||
@@ -43,6 +83,25 @@ internal static class BuiltInFunctions
|
||||
return GrpcEntityRunner.LoadAndRunAsync(encodedEntityRequest, entity, combinedServiceProvider);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// to do: Retrieve the workflow and execute it.
|
||||
var workflowName = context.FunctionDefinition.Name.Replace("http", "dafx");
|
||||
|
||||
//string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("dafx-MyTestWorkflow");
|
||||
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("OrchFunction"); // dafx-MyTestWorkflow");
|
||||
|
||||
HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
|
||||
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}.{instanceId}");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseData> RunAgentHttpAsync(
|
||||
[HttpTrigger] HttpRequestData req,
|
||||
[DurableClient] DurableTaskClient client,
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.DurableTask;
|
||||
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
|
||||
internal sealed class DurableWorkflowFunctionMetadataTransformer : IFunctionMetadataTransformer
|
||||
{
|
||||
private readonly ILogger<DurableWorkflowFunctionMetadataTransformer> _logger;
|
||||
private readonly DurableWorkflowOptions _options;
|
||||
|
||||
public DurableWorkflowFunctionMetadataTransformer(ILogger<DurableWorkflowFunctionMetadataTransformer> logger, DurableWorkflowOptions durableWorkflowOptions)
|
||||
{
|
||||
this._logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this._options = durableWorkflowOptions ?? throw new ArgumentNullException(nameof(durableWorkflowOptions));
|
||||
}
|
||||
|
||||
public string Name => nameof(DurableWorkflowFunctionMetadataTransformer);
|
||||
|
||||
public void Transform(IList<IFunctionMetadata> original)
|
||||
{
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Transforming function metadata to add durable workflow functions. Initial function count: {FunctionCount}", original.Count);
|
||||
}
|
||||
|
||||
foreach (var workflow in this._options.Workflows)
|
||||
{
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Adding durable workflow function for workflow: {WorkflowName}", workflow.Key);
|
||||
}
|
||||
|
||||
original.Add(CreateOrchestrationTrigger(workflow.Key));
|
||||
// We also want to create an HTTP trigge for this orchestration so users can start it via HTTP.
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Adding HTTP trigger function for workflow: {WorkflowName}", workflow.Key);
|
||||
var httpTriggerMetadata = CreateHttpTrigger(workflow.Key, $"workflows/{workflow.Key}/run");
|
||||
original.Add(httpTriggerMetadata);
|
||||
}
|
||||
|
||||
// Create activity functions for each executor in the workflow
|
||||
// Extract executor IDs from edges and start executor (since ExecutorBindings is internal)
|
||||
var executorIds = new HashSet<string> { 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var executorId in executorIds)
|
||||
{
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation(
|
||||
"Adding activity function for executor: {ExecutorId} in workflow: {WorkflowName}",
|
||||
executorId,
|
||||
workflow.Key);
|
||||
}
|
||||
|
||||
original.Add(CreateActivityTrigger(workflow.Key, executorId));
|
||||
}
|
||||
}
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Transform finished. Updated function count: {FunctionCount}", 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 CreateActivityTrigger(string workflowName, string executorId)
|
||||
{
|
||||
string functionName = $"{AgentSessionId.ToEntityName(workflowName)}_{executorId}";
|
||||
|
||||
return new DefaultFunctionMetadata()
|
||||
{
|
||||
Name = functionName,
|
||||
Language = "dotnet-isolated",
|
||||
RawBindings =
|
||||
[
|
||||
"""{"name":"input","type":"activityTrigger","direction":"In","dataType":"String"}""",
|
||||
],
|
||||
EntryPoint = BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint,
|
||||
ScriptFile = BuiltInFunctions.ScriptFile,
|
||||
};
|
||||
}
|
||||
}
|
||||
+18
@@ -14,6 +14,22 @@ namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
|
||||
/// </summary>
|
||||
public static class FunctionsApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds support for durable workflows to the specified Functions application builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The Functions application builder to configure with durable workflow capabilities.</param>
|
||||
/// <param name="configure"></param>
|
||||
/// <returns>The same instance of <see cref="FunctionsApplicationBuilder"/> to allow for method chaining.</returns>
|
||||
public static FunctionsApplicationBuilder AddDurableWorkflows(this FunctionsApplicationBuilder builder, Action<DurableWorkflowOptions> configure)
|
||||
{
|
||||
var options = new DurableWorkflowOptions();
|
||||
configure(options);
|
||||
builder.Services.AddSingleton(options);
|
||||
builder.Services.AddSingleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use durable agents with a builder pattern.
|
||||
/// </summary>
|
||||
@@ -38,6 +54,8 @@ 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.RunWorkflowOrechstrtationFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal) ||
|
||||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal));
|
||||
builder.Services.AddSingleton<BuiltInFunctionExecutor>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user