{"id":4035,"date":"2026-10-06T08:00:10","date_gmt":"2026-10-06T15:00:10","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=4035"},"modified":"2026-10-05T13:46:29","modified_gmt":"2026-10-05T20:46:29","slug":"azure-functions-agent-binding","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/azure-functions-agent-binding\/","title":{"rendered":"Bring agentic reasoning to Python function apps with Azure Functions Agent bindings (preview)"},"content":{"rendered":"<p>Azure Functions applications already have a strong model for event-driven work: a trigger starts the function, bindings connect it to other services, and application code validates input and controls the result. AI reasoning doesn&#8217;t need to replace that model.<\/p>\n<p>Agent bindings for Python function apps add reasoning as a bounded step inside it. The binding constructs a Microsoft Agent Framework <code>Agent<\/code> from Markdown instructions and injects it into a Python handler. The handler still decides what data the Agent sees, when to invoke it, and what to do with the response.<\/p>\n<p>The same model also works in Durable Functions. An orchestrator can schedule replay-safe Agent calls alongside regular activities, allowing a workflow to persist progress and continue after its original request ends.<\/p>\n<blockquote><p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>Agent bindings are in preview. Microsoft Agent Framework is currently the only supported provider, and Python 3.13 or later is required.<\/div><\/p><\/blockquote>\n<h2>Why use an Agent binding?<\/h2>\n<p>Most production AI workflows contain two different classes of work.<\/p>\n<p>Deterministic code should own operations with a precise contract: parsing a request, validating a schema, enforcing authorization, calculating values, selecting fields, branching on known state, and writing a response. Agent reasoning is useful when the application needs to interpret context, classify content, summarize evidence, or make a bounded recommendation.<\/p>\n<p>Consider an order-processing API. The function can validate the order and reduce it to the fields relevant to fulfillment. An Agent can then assess operational risk and identify missing context. The function owns the HTTP contract and decides how that assessment affects later processing.<\/p>\n<p>This separation has practical benefits:<\/p>\n<ul>\n<li>Existing HTTP, queue, timer, Event Grid, and Service Bus triggers continue to work as they do in any Python v2 function app.<\/li>\n<li>Deterministic code controls the trust boundary and limits the data sent to the model.<\/li>\n<li>Agent instructions remain separate from runtime and model configuration.<\/li>\n<li>The handler can invoke the Agent conditionally, inspect its response, combine it with other results, or skip it entirely.<\/li>\n<li>The same Agent definition can participate in a Durable Functions orchestration when the process becomes stateful or long-running.<\/li>\n<\/ul>\n<h2>Project anatomy<\/h2>\n<p>An Agent-enabled project is a standard Python v2 function app with a provider package and one or more instruction files:<\/p>\n<pre><code class=\"language-text\">agent-binding-app\/\r\n\u251c\u2500\u2500 function_app.py\r\n\u251c\u2500\u2500 host.json\r\n\u251c\u2500\u2500 requirements.txt\r\n\u251c\u2500\u2500 local.settings.json\r\n\u251c\u2500\u2500 order-fulfillment.agent.md\r\n\u251c\u2500\u2500 skills\/                         # Optional skills Agent Skills\r\n\u2502   \u2514\u2500\u2500 fulfillment-policy\/\r\n\u2502       \u2514\u2500\u2500 SKILL.md\r\n\u2514\u2500\u2500 mcp.json                        # Optional remote MCP servers<\/code><\/pre>\n<p>The main pieces have distinct responsibilities:<\/p>\n<table>\n<thead>\n<tr>\n<th>Component<\/th>\n<th>Responsibility<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>function_app.py<\/code><\/td>\n<td>Defines triggers, deterministic logic, the client factory, and Agent invocation.<\/td>\n<\/tr>\n<tr>\n<td><code>order-fulfillment.agent.md<\/code><\/td>\n<td>Contains raw UTF-8 instructions for one Agent.<\/td>\n<\/tr>\n<tr>\n<td><code>requirements.txt<\/code><\/td>\n<td>Selects the Agent provider and client packages.<\/td>\n<\/tr>\n<tr>\n<td><code>local.settings.json<\/code><\/td>\n<td>Supplies local storage, Foundry project, and model settings. Don&#8217;t commit this file.<\/td>\n<\/tr>\n<tr>\n<td><code>skills\/<\/code><\/td>\n<td>Optionally provides file-based Skills discovered by the provider.<\/td>\n<\/tr>\n<tr>\n<td><code>mcp.json<\/code><\/td>\n<td>Optionally configures remote MCP servers and tool allowlists.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>An Agent name resolves to exactly one instruction file. For example, <code>agent_name=\"order-fulfillment\"<\/code> resolves either <code>order-fulfillment.agent.md<\/code> in the app root or <code>agents\/order-fulfillment.agent.md<\/code>.<\/p>\n<h2>Add an Agent to an HTTP-triggered function<\/h2>\n<p>This approach is useful when Agent reasoning can complete within the current function invocation and its result belongs in the immediate response.<\/p>\n<p>Before starting the walkthrough, you need an Azure subscription and a Microsoft Foundry project with a deployed model. You also need Azure Functions Core Tools, Azurite or an Azure Storage account, and the Azure CLI. Your local identity must have access to the Foundry project.<\/p>\n<h3>Install the provider and client packages<\/h3>\n<p>The example uses Python 3.13 or later and a Microsoft Foundry model through Microsoft Agent Framework. Its <code>requirements.txt<\/code> contains:<\/p>\n<pre><code class=\"language-text\">azure-functions\r\nazurefunctions-agents-extensions-agent-framework[mcp]\r\nagent-framework-foundry\r\nazure-identity<\/code><\/pre>\n<p><code>azurefunctions-agents-extensions-agent-framework<\/code> integrates the binding with Microsoft Agent Framework, and its <code>mcp<\/code> extra installs the optional dependencies used to discover remote MCP servers from <code>mcp.json<\/code>. <code>agent-framework-foundry<\/code> supplies <code>FoundryChatClient<\/code>, while <code>azure-identity<\/code> supplies the credential used to authenticate during local development.<\/p>\n<p>Configure the corresponding values in <code>local.settings.json<\/code>:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"IsEncrypted\": false,\r\n  \"Values\": {\r\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\r\n    \"FUNCTIONS_WORKER_RUNTIME\": \"python\",\r\n    \"FOUNDRY_PROJECT_ENDPOINT\": \"https:\/\/&lt;resource-name&gt;.services.ai.azure.com\/api\/projects\/&lt;project-name&gt;\",\r\n    \"FOUNDRY_MODEL\": \"&lt;model-deployment-name&gt;\"\r\n  }\r\n}<\/code><\/pre>\n<p><code>AzureWebJobsStorage<\/code> points the local Functions host to Azurite.<\/p>\n<h3>Define the Agent instructions<\/h3>\n<p>Create <code>order-fulfillment.agent.md<\/code> in the app root:<\/p>\n<pre><code class=\"language-text\">You are an order fulfillment specialist.\r\nThe supplied order has already been validated and minimized by application code.\r\nTreat the supplied fields as trusted facts. Explain operational risk, identify\r\nmissing fulfillment context, and return a concise, actionable response.<\/code><\/pre>\n<p>The extension passes the entire file to the provider as Agent instructions. It doesn&#8217;t parse YAML front matter, model configuration, or tool declarations from this file. Model selection belongs in the client factory, and Python tools remain explicit application configuration.<\/p>\n<h3>Configure the chat client<\/h3>\n<p><code>AgentFunctionApp<\/code> accepts a zero-argument client factory. This example creates a Foundry client from environment settings and authenticates with <code>DefaultAzureCredential<\/code>:<\/p>\n<pre><code class=\"language-python\">def create_chat_client():\r\n    from agent_framework.foundry import FoundryChatClient\r\n    from azure.identity.aio import DefaultAzureCredential\r\n\r\n    return FoundryChatClient(\r\n        project_endpoint=os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"],\r\n        model=os.environ[\"FOUNDRY_MODEL\"],\r\n        credential=DefaultAzureCredential(),\r\n    )<\/code><\/pre>\n<p>The extension invokes this factory for each function invocation. That gives each invocation its own live client, Agent, and credential resources, which the extension closes when execution ends.<\/p>\n<h3>Keep deterministic preparation in code<\/h3>\n<p>Before invoking the Agent, select the fields needed for the assessment:<\/p>\n<pre><code class=\"language-python\">def prepare_order(payload: dict, order_id: str) -&gt; dict:\r\n    return {\r\n        \"order_id\": order_id,\r\n        \"customer_id\": payload[\"customer\"][\"id\"],\r\n        \"currency\": str(payload.get(\"currency\", \"USD\")).upper(),\r\n        \"shipping_country_or_region\": payload[\"shipping\"][\"country_or_region\"],\r\n        \"shipping_method\": payload[\"shipping\"][\"method\"],\r\n        \"items\": payload[\"items\"],\r\n    }<\/code><\/pre>\n<p>This helper is intentionally ordinary Python. It creates an explicit boundary between incoming application data and model input. Production applications can apply schema validation, authorization, calculated fields, and stricter data minimization at this point.<\/p>\n<h3>Bind and invoke the Agent<\/h3>\n<p>Create <code>AgentFunctionApp<\/code>, retain the standard HTTP trigger, and add <code>markdown_agent<\/code>:<\/p>\n<pre><code class=\"language-python\">app = AgentFunctionApp(client_factory=create_chat_client)\r\n\r\n@app.route(route=\"orders\/{orderId}\", methods=[\"POST\"])\r\n@app.markdown_agent(\r\n    arg_name=\"order_agent\",\r\n    agent_name=\"order-fulfillment\",\r\n)\r\nasync def process_order(\r\n    req: func.HttpRequest,\r\n    order_agent: Agent,\r\n) -&gt; func.HttpResponse:\r\n    try:\r\n        prepared_order = prepare_order(\r\n            req.get_json(),\r\n            req.route_params[\"orderId\"],\r\n        )\r\n    except (KeyError, TypeError, ValueError):\r\n        return func.HttpResponse(\r\n            body=json.dumps({\"error\": \"Order failed validation.\"}),\r\n            status_code=400,\r\n            mimetype=\"application\/json\",\r\n        )\r\n\r\n    response = await order_agent.run(\r\n        json.dumps(\r\n            {\r\n                \"order\": prepared_order,\r\n                \"task\": \"assess fulfillment readiness\",\r\n            }\r\n        )\r\n    )\r\n    return func.HttpResponse(\r\n        body=json.dumps(\r\n            {\r\n                \"order_id\": prepared_order[\"order_id\"],\r\n                \"assessment\": response.text,\r\n            }\r\n        ),\r\n        mimetype=\"application\/json\",\r\n    )<\/code><\/pre>\n<p>Two names connect the decorator to the function:<\/p>\n<ul>\n<li><code>arg_name=\"order_agent\"<\/code> must match the injected handler parameter.<\/li>\n<li><code>agent_name=\"order-fulfillment\"<\/code> identifies the instruction file without the <code>.agent.md<\/code> suffix.<\/li>\n<\/ul>\n<p>The Agent doesn&#8217;t run automatically when the function starts. The handler explicitly calls <code>await order_agent.run(...)<\/code>, which makes the reasoning boundary visible and testable in application code.<\/p>\n<h2>What happens during an invocation<\/h2>\n<p>When the Functions host compiles the binding, it validates the handler shape and Agent definition. At invocation time, the extension:<\/p>\n<ol>\n<li>Resolves and loads the requested instruction file.<\/li>\n<li>Calls the configured factory to create the chat client.<\/li>\n<li>Combines the instructions with explicitly configured Python tools and any discovered Skills or MCP servers.<\/li>\n<li>Constructs the Microsoft Agent Framework <code>Agent<\/code> and injects it into the named handler parameter.<\/li>\n<li>Closes invocation-owned resources after success, failure, or cancellation.<\/li>\n<\/ol>\n<p>Compiled definitions and provider discovery can be cached, but live clients and credentials aren&#8217;t reused across function invocations. The function continues to own its trigger, validation, control flow, error handling, and output.<\/p>\n<h2>Move the Agent into a durable workflow<\/h2>\n<p>A direct call fits a bounded request-response operation. The order process can eventually require more: prepare the order, assess fulfillment risk, run inventory or policy checks, wait for an external system, and publish a final result after the initiating request has ended.<\/p>\n<p>Durable Functions provides persisted orchestration state for that workflow. To enable the integration, install the provider with its Durable extra:<\/p>\n<pre><code class=\"language-text\">azure-functions\r\nazurefunctions-agents-extensions-agent-framework[durable,mcp]\r\nagent-framework-foundry\r\nazure-identity<\/code><\/pre>\n<p>The <code>durable<\/code> extra installs the Durable Functions support used by <code>AgentFunctionApp<\/code>, while <code>mcp<\/code> retains the optional MCP discovery support from the direct scenario.<\/p>\n<h3>Start the orchestration<\/h3>\n<p>The HTTP starter uses the standard Durable client binding. It validates that the request contains JSON, starts <code>order_orchestrator<\/code>, and returns the management payload:<\/p>\n<pre><code class=\"language-python\">@app.route(route=\"orders\/orchestrations\", methods=[\"POST\"])\r\n@app.durable_client_input(client_name=\"client\")\r\nasync def start_order_orchestration(\r\n    req: func.HttpRequest,\r\n    client: df.DurableFunctionsClient,\r\n) -&gt; func.HttpResponse:\r\n    try:\r\n        order = req.get_json()\r\n    except ValueError:\r\n        return func.HttpResponse(\r\n            body=json.dumps({\"error\": \"Order failed validation.\"}),\r\n            status_code=400,\r\n            mimetype=\"application\/json\",\r\n        )\r\n\r\n    instance_id = await client.start_new(\r\n        \"order_orchestrator\",\r\n        client_input=order,\r\n    )\r\n    management = client.create_http_management_payload(req, instance_id)\r\n    return func.HttpResponse(\r\n        body=json.dumps(management),\r\n        status_code=202,\r\n        mimetype=\"application\/json\",\r\n        headers={\r\n            \"Location\": management[\"statusQueryGetUri\"],\r\n            \"Retry-After\": \"10\",\r\n        },\r\n    )<\/code><\/pre>\n<p>The <code>202 Accepted<\/code> response decouples the workflow lifetime from the HTTP request. Callers can use <code>statusQueryGetUri<\/code> to observe the instance and retrieve its final output.<\/p>\n<h3>Prepare model input in an activity<\/h3>\n<p>Move order preparation into a standard activity:<\/p>\n<pre><code class=\"language-python\">@app.activity_trigger(input_name=\"order\")\r\ndef prepare_order_activity(order: dict) -&gt; dict:\r\n    return {\r\n        \"order_id\": order[\"order_id\"],\r\n        \"customer_id\": order[\"customer\"][\"id\"],\r\n        \"currency\": str(order.get(\"currency\", \"USD\")).upper(),\r\n        \"shipping_country_or_region\": order[\"shipping\"][\"country_or_region\"],\r\n        \"shipping_method\": order[\"shipping\"][\"method\"],\r\n        \"items\": order[\"items\"],\r\n    }<\/code><\/pre>\n<p>Activities are the correct place for validation, calculations, data minimization, and other work that shouldn&#8217;t run in the orchestrator itself.<\/p>\n<h3>Schedule Agent reasoning from the orchestrator<\/h3>\n<p>The orchestrator coordinates the activity and Agent call:<\/p>\n<pre><code class=\"language-python\">@app.orchestration_trigger(context_name=\"context\")\r\ndef order_orchestrator(context: DurableAgentContext):\r\n    prepared_order = yield context.call_activity(\r\n        \"prepare_order_activity\",\r\n        context.get_input(),\r\n    )\r\n\r\n    assessment = yield context.call_agent(\r\n        \"order-fulfillment\",\r\n        {\r\n            \"order\": prepared_order,\r\n            \"task\": \"assess fulfillment risk\",\r\n        },\r\n    )\r\n    return {\r\n        \"order_id\": prepared_order[\"order_id\"],\r\n        \"risk_assessment\": assessment,\r\n    }<\/code><\/pre>\n<p><code>context.call_agent()<\/code> takes the logical Agent name and a JSON-compatible input. The orchestration can use its result in later activities, pass it to another bounded reasoning step, or return it as the workflow output.<\/p>\n<h2>How Agent calls work in an orchestration<\/h2>\n<p>Durable Functions orchestrators replay to reconstruct state, so model and network operations can&#8217;t run directly in orchestrator code. <code>context.call_agent()<\/code> handles this boundary by scheduling an extension-managed activity that loads the Agent instructions, creates the client and Agent, invokes the model, and returns a JSON-serializable result.<\/p>\n<p>The result is recorded in orchestration history and reused during replay instead of repeating the model call. This makes Agent reasoning a natural fit within a durable workflow: activities can prepare trusted data, the Agent can perform a bounded reasoning task, and later steps can act on the result while the orchestrator remains deterministic.<\/p>\n<h2>Run the examples locally<\/h2>\n<p>Before starting the app, run <code>az login<\/code> so <code>DefaultAzureCredential<\/code> can use your Azure CLI identity to access the Foundry project. Start Azurite for local storage and then start the host:<\/p>\n<pre><code class=\"language-console\">azurite --silent --location .azurite<\/code><\/pre>\n<p>In another terminal:<\/p>\n<pre><code class=\"language-console\">func start<\/code><\/pre>\n<p>Invoke the direct HTTP function:<\/p>\n<pre><code class=\"language-bash\">curl -X POST http:\/\/localhost:7071\/orders\/42 \\\r\n  -H \"Content-Type: application\/json\" \\\r\n  -d '{\"customer\":{\"id\":\"C-1007\"},\"currency\":\"usd\",\"shipping\":{\"country_or_region\":\"ca\",\"method\":\"overnight\"},\"items\":[{\"sku\":\"A-100\",\"quantity\":2,\"unit_price\":\"24.95\"}]}'<\/code><\/pre>\n<p>The response includes the route order ID and model-generated assessment:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"order_id\": \"42\",\r\n  \"assessment\": \"&lt;model-generated fulfillment assessment&gt;\"\r\n}<\/code><\/pre>\n<p>For the Durable path, include the order ID in the request body because the starter doesn&#8217;t receive it as a route parameter:<\/p>\n<pre><code class=\"language-bash\">curl -X POST http:\/\/localhost:7071\/orders\/orchestrations \\\r\n    -H \"Content-Type: application\/json\" \\\r\n    -d '{\"order_id\":\"42\",\"customer\":{\"id\":\"C-1007\"},\"currency\":\"usd\",\"shipping\":{\"country_or_region\":\"ca\",\"method\":\"overnight\"},\"items\":[{\"sku\":\"A-100\",\"quantity\":2,\"unit_price\":\"24.95\"}]}'<\/code><\/pre>\n<p>The starter returns the instance ID and management URLs. Poll <code>statusQueryGetUri<\/code> until <code>runtimeStatus<\/code> becomes <code>Completed<\/code>, and then read the <code>risk_assessment<\/code> from the orchestration output.<\/p>\n<h2>Get started<\/h2>\n<p>Agent bindings let a Python function keep deterministic code in control while delegating a clearly defined reasoning task to Microsoft Agent Framework. Start with direct invocation when the result belongs in the current function execution. Add Durable Functions when the surrounding process must persist progress, coordinate multiple steps, or continue after the original request ends.<\/p>\n<p>To explore the preview:<\/p>\n<ul>\n<li>File an issue or feature request on the <a href=\"https:\/\/github.com\/Azure\/azure-functions-python-extensions\">Agent Extension Repository<\/a>.<\/li>\n<li>Read <a href=\"https:\/\/learn.microsoft.com\/azure\/azure-functions\/functions-agent-bindings\">Agent bindings for Python in Azure Functions<\/a>.<\/li>\n<li>Follow <a href=\"https:\/\/learn.microsoft.com\/azure\/azure-functions\/functions-agent-bindings-agent-framework?tabs=windows\">Use a Microsoft Agent Framework agent in a Python function<\/a>.<\/li>\n<li>Build the durable scenario in <a href=\"https:\/\/learn.microsoft.com\/azure\/azure-functions\/functions-agent-bindings-agent-framework-durable?tabs=windows\">Use a Microsoft Agent Framework agent binding in a Durable orchestration<\/a>.<\/li>\n<li>Explore the <a href=\"https:\/\/github.com\/Azure\/azure-functions-python-extensions\/tree\/dev\/azurefunctions-agents-extensions-agent-framework\/samples\/agent_samples_agent-framework\">complete direct sample<\/a> and <a href=\"https:\/\/github.com\/Azure\/azure-functions-python-extensions\/tree\/dev\/azurefunctions-agents-extensions-agent-framework\/samples\/agent_samples_agent-framework_durable\">complete Durable sample<\/a>.<\/li>\n<\/ul>\n<p>Try the preview, bring Agent reasoning to an existing function, and tell us what you build.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Add bounded AI reasoning to Python function apps with Azure Functions Agent bindings, while keeping deterministic application code in control.<\/p>\n","protected":false},"author":220229,"featured_media":4039,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[941,786,967,734,765,988,940,995,162,960],"class_list":["post-4035","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-agents","tag-ai","tag-ai-agents","tag-azure","tag-azure-functions","tag-durable-functions","tag-mcp","tag-microsoft-agent-framework","tag-python","tag-serverless"],"acf":[],"blog_post_summary":"<p>Add bounded AI reasoning to Python function apps with Azure Functions Agent bindings, while keeping deterministic application code in control.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/4035","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/220229"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=4035"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/4035\/revisions"}],"predecessor-version":[{"id":4040,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/4035\/revisions\/4040"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/4039"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=4035"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=4035"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=4035"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}