Skip to main content

Overview

The Microsoft Agent Framework integrates seamlessly with Azure Functions to host agents as serverless HTTP services. The AgentFunctionApp (Python) and ConfigureDurableAgents (C#) provide automatic endpoint generation, session management, and multi-agent hosting capabilities.

Key Features

  • Auto-generated HTTP endpoints per agent (/api/agents/{name}/run)
  • Session management for multi-turn conversations
  • Multi-agent hosting in a single Functions app
  • Health checks for monitoring
  • Durable orchestrations for complex workflows
  • Reliable streaming with response callbacks

Quick Start

Python Setup

1

Install Azure Functions Core Tools

Install Azure Functions Core Tools v4.x:
npm install -g azure-functions-core-tools@4 --unsafe-perm true
2

Install Package

pip install agent-framework-azurefunctions
3

Create Function App

Create function_app.py:
from agent_framework.azure import AgentFunctionApp, AzureOpenAIChatClient
from azure.identity import AzureCliCredential

# Create agent
client = AzureOpenAIChatClient(credential=AzureCliCredential())
agent = client.as_agent(
    name="Assistant",
    instructions="You are a helpful assistant."
)

# Register with Functions app
app = AgentFunctionApp(agents=[agent], enable_health_check=True)
4

Configure Settings

Create local.settings.json:
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
    "TASKHUB_NAME": "default",
    "AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com/",
    "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4o-mini"
  }
}
5

Create host.json

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "extensions": {
    "durableTask": {
      "hubName": "%TASKHUB_NAME%"
    }
  }
}
6

Start Function Host

func start

C# Setup

1

Create Function Project

func init MyAgentApp --worker-runtime dotnet-isolated
cd MyAgentApp
2

Add Package Reference

dotnet add package Microsoft.Agents.AI.Hosting.AzureFunctions
3

Create Program.cs

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;

string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
    ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
    ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");

var client = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
AIAgent agent = client.GetChatClient(deploymentName)
    .AsAIAgent("You are a helpful assistant.", "Assistant");

using IHost app = FunctionsApplication
    .CreateBuilder(args)
    .ConfigureFunctionsWebApplication()
    .ConfigureDurableAgents(options => options.AddAIAgent(agent))
    .Build();

app.Run();
4

Configure Settings

Create local.settings.json:
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
    "AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com/",
    "AZURE_OPENAI_DEPLOYMENT_NAME": "gpt-4o-mini"
  }
}
5

Start Function Host

func start

Single Agent Hosting

Host a single agent with automatic HTTP endpoint generation:
from agent_framework.azure import AgentFunctionApp, AzureOpenAIChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv

load_dotenv()

# Create agent with specific instructions
def _create_agent():
    return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
        name="Joker",
        instructions="You are good at telling jokes.",
    )

# Register agent with Functions app
app = AgentFunctionApp(
    agents=[_create_agent()],
    enable_health_check=True,
    max_poll_retries=50
)

Testing the Agent

# Plain text request
curl -X POST http://localhost:7071/api/agents/Joker/run \
  -H "Content-Type: text/plain" \
  -d "Tell me a joke about cloud computing."

# JSON request with session
curl -X POST http://localhost:7071/api/agents/Joker/run \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Tell me a joke",
    "thread_id": "user-123"
  }'

Response Format

Synchronous (default):
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
x-ms-thread-id: 4f205157170244bfbd80209df383757e

Why did the cloud break up with the server?
Because it found someone more "uplifting"!
Asynchronous (with wait_for_response: false):
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "status": "accepted",
  "response": "Agent request accepted",
  "message": "Tell me a joke about cloud computing.",
  "thread_id": "<guid>",
  "correlation_id": "<guid>"
}

Multi-Agent Hosting

Host multiple specialized agents in a single Functions app:
from agent_framework import tool
from agent_framework.azure import AgentFunctionApp, AzureOpenAIChatClient
from azure.identity import AzureCliCredential

# Define tools
@tool(approval_mode="never_require")
def get_weather(location: str) -> dict:
    """Get current weather for a location."""
    return {
        "location": location,
        "temperature": 72,
        "conditions": "Sunny"
    }

@tool(approval_mode="never_require")
def calculate_tip(bill_amount: float, tip_percentage: float = 15.0) -> dict:
    """Calculate tip amount and total bill."""
    tip = bill_amount * (tip_percentage / 100)
    return {
        "bill_amount": bill_amount,
        "tip_amount": round(tip, 2),
        "total": round(bill_amount + tip, 2)
    }

# Create specialized agents
client = AzureOpenAIChatClient(credential=AzureCliCredential())

weather_agent = client.as_agent(
    name="WeatherAgent",
    instructions="You are a helpful weather assistant.",
    tools=[get_weather]
)

math_agent = client.as_agent(
    name="MathAgent",
    instructions="You are a helpful math assistant.",
    tools=[calculate_tip]
)

# Register all agents
app = AgentFunctionApp(
    agents=[weather_agent, math_agent],
    enable_health_check=True
)

Testing Multiple Agents

# Weather agent
curl -X POST http://localhost:7071/api/agents/WeatherAgent/run \
  -d "What's the weather in Seattle?"

# Math agent
curl -X POST http://localhost:7071/api/agents/MathAgent/run \
  -d "Calculate a 20% tip on a $50 bill"

Agent Orchestrations

Create complex workflows by orchestrating multiple agent calls:
import azure.functions as func
from azure.durable_functions import DurableOrchestrationContext, DurableOrchestrationClient
from agent_framework.azure import AgentFunctionApp, AzureOpenAIChatClient
from azure.identity import AzureCliCredential

# Create agent
WRITER_AGENT_NAME = "WriterAgent"

def _create_writer_agent():
    return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
        name=WRITER_AGENT_NAME,
        instructions="""You refine short pieces of text. When given an initial 
        sentence you enhance it; when given an improved sentence you polish it further."""
    )

app = AgentFunctionApp(agents=[_create_writer_agent()], enable_health_check=True)

# Define orchestration
@app.orchestration_trigger(context_name="context")
def writer_orchestration(context: DurableOrchestrationContext):
    """Sequential agent calls with shared session."""
    writer = app.get_agent(context, WRITER_AGENT_NAME)
    session = writer.create_session()
    
    # First pass: initial content
    initial = yield writer.run(
        messages="Write a concise inspirational sentence about learning.",
        session=session
    )
    
    # Second pass: refinement
    improved_prompt = f"Improve this further while keeping it under 25 words: {initial.text}"
    refined = yield writer.run(
        messages=improved_prompt,
        session=session
    )
    
    return refined.text

# HTTP trigger to start orchestration
@app.route(route="writer/run", methods=["POST"])
@app.durable_client_input(client_name="client")
async def start_writer_orchestration(
    req: func.HttpRequest,
    client: DurableOrchestrationClient
) -> func.HttpResponse:
    """Start the orchestration."""
    instance_id = await client.start_new(
        orchestration_function_name="writer_orchestration"
    )
    
    return func.HttpResponse(
        body=f'{{"instanceId": "{instance_id}"}}',
        status_code=202,
        mimetype="application/json"
    )

Testing Orchestrations

# Start orchestration
curl -X POST http://localhost:7071/api/writer/run

# Response with status URLs
{
  "id": "abc123",
  "statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/abc123",
  "sendEventPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/abc123/raiseEvent/{eventName}"
}

# Check status
curl http://localhost:7071/runtime/webhooks/durabletask/instances/abc123

Configuration

host.json

Configure the Functions runtime:
{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "extensions": {
    "durableTask": {
      "hubName": "%TASKHUB_NAME%",
      "storageProvider": {
        "connectionStringName": "AzureWebJobsStorage"
      }
    }
  },
  "logging": {
    "logLevel": {
      "default": "Information",
      "Microsoft.Agents": "Debug"
    }
  }
}

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
    "TASKHUB_NAME": "default",
    "AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com/",
    "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4o-mini",
    "AZURE_OPENAI_API_KEY": "<optional-api-key>"
  }
}

Advanced Features

Session Management

Maintain conversation context across requests:
# Client provides session ID
curl -X POST http://localhost:7071/api/agents/Assistant/run \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Remember my favorite color is blue",
    "thread_id": "user-123"
  }'

# Subsequent request with same session
curl -X POST http://localhost:7071/api/agents/Assistant/run \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What is my favorite color?",
    "thread_id": "user-123"
  }'

Health Checks

Monitor agent availability:
curl http://localhost:7071/api/health

# Response
{
  "status": "healthy",
  "agents": [
    {"name": "Assistant", "status": "ready"},
    {"name": "WeatherAgent", "status": "ready"}
  ]
}

Time-to-Live (TTL)

Configure agent session expiration:
app = AgentFunctionApp(
    agents=[agent],
    session_ttl=timedelta(hours=2)
)

Reliable Streaming

Implement resumable streaming with Redis:
from agent_framework_azurefunctions import RedisStreamResponseHandler
import redis.asyncio as redis

redis_client = redis.from_url(os.environ["REDIS_CONNECTION_STRING"])

app = AgentFunctionApp(
    agents=[agent],
    response_handler=RedisStreamResponseHandler(redis_client)
)

Deployment to Azure

1

Create Function App

az functionapp create \
  --resource-group myResourceGroup \
  --consumption-plan-location westus \
  --runtime python \
  --runtime-version 3.11 \
  --functions-version 4 \
  --name myAgentFunction \
  --storage-account mystorageaccount
2

Configure App Settings

az functionapp config appsettings set \
  --name myAgentFunction \
  --resource-group myResourceGroup \
  --settings \
    AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" \
    AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" \
    DURABLE_TASK_SCHEDULER_CONNECTION_STRING="<dts-connection>"
3

Deploy Code

func azure functionapp publish myAgentFunction
4

Test Production Endpoint

curl -X POST https://myagentfunction.azurewebsites.net/api/agents/Assistant/run \
  -d "Hello from production!"

Best Practices

Avoid API keys in production. Configure managed identity for Azure OpenAI access:
az functionapp identity assign \
  --name myAgentFunction \
  --resource-group myResourceGroup

az role assignment create \
  --assignee <function-app-principal-id> \
  --role "Cognitive Services OpenAI User" \
  --scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<openai-resource>
Monitor performance and errors:
{
  "Values": {
    "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=..."
  }
}
Set appropriate session expiration based on use case:
  • Short-lived (30 min - 1 hour): Customer support chats
  • Medium-lived (2-4 hours): Development assistants
  • Long-lived (24+ hours): Personal assistants
For production, use Azure Storage instead of emulator:
{
  "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=..."
}
Protect against abuse with Azure API Management or custom middleware.

Troubleshooting

Error: Cannot connect to Durable Task SchedulerSolution: Ensure DTS emulator is running:
docker ps | grep dts-emulator
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
Error: Azure OpenAI authentication failedSolution: Verify authentication:
# Check Azure CLI login
az account show

# Test OpenAI access
az rest --method GET --url "https://your-resource.openai.azure.com/openai/deployments?api-version=2023-05-15"
Error: Agent name not found in registered agentsSolution: Verify agent registration and exact name match:
app = AgentFunctionApp(agents=[agent])
# Ensure agent.name matches URL: /api/agents/{agent.name}/run

Next Steps

DurableTask Integration

Build long-running orchestrations

A2A Protocol

Connect distributed agents

Build docs developers (and LLMs) love