Skip to main content

Command Palette

Search for a command to run...

Azure Durable Agents in Microsoft Agent Framework with Docker Durable Task Scheduler (DTS) Emulator

Updated
6 min read
Azure Durable Agents in Microsoft Agent Framework with Docker Durable Task Scheduler (DTS) Emulator
S
From Synapse Analytics, Power BI, Spark, Microsoft Fabric,ASP.NET Core and recently Agentic AI on .NET I try to explore, learn and share all aspects of Microsoft Data Stack in this blog.

Azure Durable Functions are an extension of Azure Functions that makes it easy to build long-running, stateful workflows in serverless environments.

Instead of managing state, retries, checkpoints and recovery custom built logic, Azure Durable Functions automatically handles them behind the scenes.

You can think of it as a workflow coordinator that orchestrates multiple tasks, waits for external events, schedules timers, and resumes execution even after application restarts or failures. Such implementation make it ideal for business processes that requires Human-in-the-loop (HITL) that may run for minutes, hours, or even days.

Azure Durable Functions uses an event-sourcing model where every action is recorded as an event. When the workflow needs to resume, the orchestrator rebuilds its state by replaying these events without requiring developers to manage state and infrastructure concerns.

In this article I will demonstrate a very basic setup of an Azure Durable Agent in MAF through Docker Emulator for Durable Task Scheduler (DTS) that can run durable and stateful agents.

SetUp

Create a new Azure Function project and add the following references.

dotnet add package Azure;
dotnet add package Azure.AI.OpenAI;
dotnet add package Microsoft.Agents.AI;
dotnet add package Microsoft.Extensions.AI;
dotnet add package Microsoft.Extensions.Configuration;
dotnet add package Microsoft.Extensions.DependencyInjection;
dotnet add package Microsoft.Extensions.Hosting;
dotnet add package Microsoft.Agents.AI.Hosting.AzureFunctions--prerelase;
dotnet add package Microsoft.Azure.Functions.Worker.Builder;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
dotnet add package  Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged;

Of the above ,ensure that you don't miss to reference the following two libraries in the project.

Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged;

Otherwise you will face issue that is highlighted in the following GitHub post.

https://github.com/microsoft/agent-framework/issues/5927

Not referencing the above two libraries, will result in you having to declare a dummy orchestrator.

public static class MyDummyOrchestrator
{
    [Function(nameof(MyDummyOrchestrator))]
    public static Task RunOrchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        return Task.CompletedTask;
    }
}

This is because the function worker fails to find an entry point of execution.

These are the major packages and version numbers that I have referenced in the project

Microsoft Agent Framework , Azure Durable Function

Docker DTS Emulator >>

Install Docker Desktop if you don't have on your system and then pull the Docker image containing the DTS emulator

docker pull mcr.microsoft.com/dts/dts-emulator:latest

Now run the emulator in the Docker desktop terminal

docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
Microsoft Agent Framework , Azure Durable Function, Docker Emulator

You can change the port numbers if you want to.

Ensure that the Emulator is up and running.

Microsoft Agent Framework , Azure Durable Function, Docker Emulator

Navigate to http://localhost:8082/ and you should see the DTS endpoints running on port number 8082.

Microsoft Agent Framework , Azure Durable Function, Docker Emulator

Add appsetting.json to the project

"AppSettings": 
{ 
    "Chat_DeploymentName": "Deployment Name",
    "EndPoint": "Azure OpenAI endpoint",
    "ApiKey": "Azure OpenAI API key"
}

local.settings.json

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
        "AZURE_OPENAI_ENDPOINT": "Azure OpenAI endpoint",
        "AZURE_OPENAI_DEPLOYMENT_NAME": "Deployment Name"
    }
}

In DURABLE_TASK_SCHEDULER_CONNECTION_STRING settings above, the Endpoint value should match the Endpoint that was set while configuring the Docker DTS Emulator.

launchSettings.json

{
  "profiles": {
    "Azure_Durable_SingleAgent": {
      "commandName": "Project",
      "commandLineArgs": "--port 7001",
      "launchBrowser": false
    }
  }
}

--port 7001 value will be used to send HttpRequest to the running Agent.

host.json

{
    "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"
            }
        }
    }
}

"DURABLE_TASK_SCHEDULER_CONNECTION_STRING" is the keyword above.

If you are using Azure storage, then there is no need for connectionStringName and type settings but in case you are using Durable Task Scheduler (DTS) which is the recommended approach , then setting type and connectionStringName property is required.

serviceDependencies.json

{
  "dependencies": {
    "appInsights1": {
      "type": "appInsights"
    },
    "storage1": {
      "type": "storage",
      "connectionId": "AzureWebJobsStorage"
    }
  }
}

Code

Implementation is pretty straightforward.

After the above artifacts in place, add the following code to read the settings from appsettings.json in Program.cs of the project.

var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();

Read the credentials and register a Chatclient

var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);

builder.Services.AddKeyedChatClient(
    "ChatClient",
    (
        sp =>
            new AzureOpenAIClient(new Uri(configuration["AppSettings:EndPoint"]), credential)
                .GetChatClient(configuration["AppSettings:Chat_DeploymentName"])
                .AsIChatClient()
    )
);

In the next step, register the AIAgent in the hosted DI container.

servicecollection.AddSingleton<ChatClientAgent>(sp =>

{
    Func<ChatClientAgentOptions> func = () =>
   {
       return new ChatClientAgentOptions
       {
           ChatOptions = new ChatOptions
           {
               Instructions = "You are good at explaining topics on history",
           },
           Name = "History",
           Id = "1"

       };
   };

    return new ChatClientAgent(sp.GetKeyedService<IChatClient>("ChatClient"), options: func());

});

Build the service and get the agent from the ServiceProvider as ChatClientAgent.


ServiceProvider serviceProvider = servicecollection.BuildServiceProvider();

var agent = serviceProvider.GetServices<ChatClientAgent>();

List<ChatClientAgent> chatclientagent = new(agent);

Then add this agent as DurableAgent to the Azure Function worker.

 using IHost app = FunctionsApplication
 .CreateBuilder(args)
 .ConfigureFunctionsWebApplication()
 .ConfigureDurableAgents(options => options.AddAIAgent(chatclientagent[0], timeToLive: TimeSpan.FromHours(1)))
 .Build();

 app.Run();

The time to live of agent is configured to to an hour.

The agent name History set during defining the agent will act as the Durable Agent endpoint.

In our example the HttpRequest endpoint and the call will be as follows

Invoke-RestMethod -Method Post `
    -Uri  http://localhost:7001/api/agents/History/run `
    -ContentType text/plain `
    -Body "Tell me more about World war 2"

Port number 7001 comes from the set value in launchSettings.json.

That's all. Go ahead and invoke the call.

I invoked it through PowerShell

Microsoft Agent Framework , Azure Durable Function, Docker Emulator

You should see the Agent chat history and other details in the DTS Emulator Dashboard.

Microsoft Agent Framework , Azure Durable Function, Docker Emulator Microsoft Agent Framework , Azure Durable Function, Docker Emulator

Execution >>

Microsoft Agent Framework , Azure Durable Function, Docker Emulator

Conclusion

Leveraging Azure Durable Function for state management of Agents is the most optimal approach and effective approach to build reliable long running workflows that can recover through restarts and process failures without the over head of implementation and maintaining custom processes.

Though the above set up is an example for a very simple use case, in a few upcoming articles I will touch base on more advance use cases.

Till then stay tuned !!!