From 471654abfabeb6083240d64524e98a91cc962737 Mon Sep 17 00:00:00 2001 From: Naveen Chatlapalli Date: Sat, 3 Oct 2026 00:16:06 -0500 Subject: [PATCH 1/3] samples: add Container Apps Dynamic Sessions function tools --- dotnet/agent-framework-dotnet.slnx | 1 + .../Agent_ContainerAppsDynamicSessions.csproj | 19 ++++ .../Program.cs | 57 ++++++++++++ .../README.md | 46 ++++++++++ dotnet/samples/02-agents/Agents/README.md | 1 + python/samples/02-agents/tools/README.md | 3 + .../tools/container_apps_dynamic_sessions.py | 86 +++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Agent_ContainerAppsDynamicSessions.csproj create mode 100644 dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs create mode 100644 dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/README.md create mode 100644 python/samples/02-agents/tools/container_apps_dynamic_sessions.py diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 77e772cf214..875a37d7422 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -45,6 +45,7 @@ + diff --git a/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Agent_ContainerAppsDynamicSessions.csproj b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Agent_ContainerAppsDynamicSessions.csproj new file mode 100644 index 00000000000..952d9a1362a --- /dev/null +++ b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Agent_ContainerAppsDynamicSessions.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs new file mode 100644 index 00000000000..33628136fea --- /dev/null +++ b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +// Expose Azure Container Apps Dynamic Sessions as an agent function tool. + +using System.ComponentModel; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using Azure.AI.Projects; +using Azure.Core; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; + +string projectEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") + ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."); +string model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") + ?? throw new InvalidOperationException("FOUNDRY_MODEL is not set."); +string poolEndpoint = Environment.GetEnvironmentVariable("POOL_MANAGEMENT_ENDPOINT") + ?? throw new InvalidOperationException("POOL_MANAGEMENT_ENDPOINT is not set."); +Uri poolUri = new(poolEndpoint); +if (poolUri.Scheme != Uri.UriSchemeHttps || poolUri.UserInfo.Length != 0 || poolUri.Query.Length != 0 || poolUri.Fragment.Length != 0) +{ + throw new InvalidOperationException("POOL_MANAGEMENT_ENDPOINT must be a trusted HTTPS pool URL without credentials or a query."); +} + +// One application-owned session per run. Reuse it for this agent's tool calls only. +string sessionId = Guid.NewGuid().ToString("N"); +Uri executionUri = new($"{poolEndpoint.TrimEnd('/')}/executions?api-version=2025-10-02-preview&identifier={sessionId}"); +AzureCliCredential credential = new(); +using HttpClient httpClient = new(new HttpClientHandler { AllowAutoRedirect = false, CheckCertificateRevocationList = true }) { Timeout = TimeSpan.FromSeconds(70) }; + +AIAgent agent = new AIProjectClient(new Uri(projectEndpoint), credential).AsAIAgent( + model: model, + instructions: "Use ExecutePython for calculations. Check its status and errors before reporting a result.", + tools: [AIFunctionFactory.Create(ExecutePythonAsync, "ExecutePython")]); + +Console.WriteLine(await agent.RunAsync("Use Python to calculate the total of 19.95, 42.50, and 8.75, then add 8% tax.")); + +[Description("Run Python in the conversation's cloud sandbox and return its output and execution status.")] +async Task ExecutePythonAsync([Description("Python code to execute in the remote session.")] string code, CancellationToken cancellationToken) +{ + AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(["https://dynamicsessions.io/.default"]), cancellationToken); + using HttpRequestMessage request = new(HttpMethod.Post, executionUri); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); + request.Content = JsonContent.Create(new + { + codeInputType = "Inline", + executionType = "Synchronous", + code, + timeoutInSeconds = 30, + outputStreamsMaxLength = 4096, + }); + using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadFromJsonAsync(cancellationToken); +} diff --git a/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/README.md b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/README.md new file mode 100644 index 00000000000..77bbd2272e8 --- /dev/null +++ b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/README.md @@ -0,0 +1,46 @@ +# Azure Container Apps Dynamic Sessions + +This sample gives an Agent Framework agent a function tool that runs Python in an +Azure Container Apps code-interpreter session. It uses the existing HTTP and Azure +identity libraries to call the [code execution REST API](https://learn.microsoft.com/rest/api/data-plane/containerapps/code-execution/execute). +Generated code runs in the cloud session, not on the machine running this sample. + +## Setup + +Use .NET 10 and an existing Microsoft Foundry model deployment. Create a Python +code-interpreter session pool using the [Dynamic Sessions setup guide](https://learn.microsoft.com/azure/container-apps/sessions-usage). +The signed-in identity needs `Azure ContainerApps Session Executor` on the pool +and permission to invoke the Foundry model. Sign in with `az login`. + +Retrieve the pool management endpoint from +`az containerapp sessionpool show --name --resource-group --query properties.poolManagementEndpoint --output tsv`. +Then configure and run: + +```powershell +$env:FOUNDRY_PROJECT_ENDPOINT = "" +$env:FOUNDRY_MODEL = "" +$env:POOL_MANAGEMENT_ENDPOINT = "" +dotnet run +``` + +The agent asks the tool to calculate an order total with tax. The tool returns +the execution status and result, including standard output and errors. HTTP +failures are surfaced to the agent's function invocation pipeline. Each execution +is limited to 30 seconds with a bounded output stream; the HTTP client allows +additional time for transport and pool startup. + +## Session ownership and lifecycle + +The application generates a new session identifier for each run and reuses it +for that run's tool calls, allowing Python state to persist between calls. The +model supplies only code, not the endpoint or session identifier. For a multi-user +host, bind a separate tool/session identifier to each authorized conversation. +Do not share a session across unrelated users. + +Use only a trusted pool management endpoint. Configure the pool's network access +for your workload and do not pass application credentials or confidential data +into generated code. This example uses interactive developer authentication; +deployed applications should use an identity configured for their host. + +The sample does not provision or delete Azure resources. The pool's configured +cooldown manages idle session cleanup. Model and pool usage incur costs. diff --git a/dotnet/samples/02-agents/Agents/README.md b/dotnet/samples/02-agents/Agents/README.md index 487e7f269a2..da36e783cd3 100644 --- a/dotnet/samples/02-agents/Agents/README.md +++ b/dotnet/samples/02-agents/Agents/README.md @@ -26,6 +26,7 @@ Before you begin, ensure you have the following prerequisites: |Sample|Description| |---|---| +|[Container Apps Dynamic Sessions](./Agent_ContainerAppsDynamicSessions/)|Run agent-generated Python in a cloud code-interpreter session using a function tool| |[Using OpenAPI function tools with a simple agent](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI)|This sample demonstrates how to create function tools from an OpenAPI spec and use them with a simple agent (note that this sample is in the Semantic Kernel repository)| |[Using function tools with approvals](./Agent_Step01_UsingFunctionToolsWithApprovals/)|This sample demonstrates how to use function tools where approvals require human in the loop approvals before execution| |[Structured output with a simple agent](./Agent_Step02_StructuredOutput/)|This sample demonstrates how to use structured output with a simple agent| diff --git a/python/samples/02-agents/tools/README.md b/python/samples/02-agents/tools/README.md index a15f85f56c9..6972943138b 100644 --- a/python/samples/02-agents/tools/README.md +++ b/python/samples/02-agents/tools/README.md @@ -1,5 +1,8 @@ # Tools +[Container Apps Dynamic Sessions](./container_apps_dynamic_sessions.py) shows a +function tool that executes Python in a cloud session with per-run session ownership. + Samples that show how to define, configure, and control function tools for an agent — from basic declarations to approvals, invocation limits, session injection, and dynamic (progressive) tool exposure. diff --git a/python/samples/02-agents/tools/container_apps_dynamic_sessions.py b/python/samples/02-agents/tools/container_apps_dynamic_sessions.py new file mode 100644 index 00000000000..1d4ef6cfcc8 --- /dev/null +++ b/python/samples/02-agents/tools/container_apps_dynamic_sessions.py @@ -0,0 +1,86 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Run agent-generated Python in an Azure Container Apps Dynamic Sessions pool. + +Prerequisites: +- Install agent-framework-foundry, azure-identity, and httpx; sign in with az login. +- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL for your model deployment. +- Set POOL_MANAGEMENT_ENDPOINT to the Python code-interpreter pool's management + endpoint (az containerapp sessionpool show --query properties.poolManagementEndpoint). +- Give the signed-in identity Azure ContainerApps Session Executor on the pool, + and permission to invoke the Foundry model. + +Each program run generates a fresh session identifier, reused for its tool calls. +The identifier is owned by the application, never selected by the model. In a +multi-user host, create a separate agent/tool/session binding for each authorized +conversation. Pool cooldown controls cleanup after the last use; this sample does +not provision or delete Azure resources. Model and session-pool usage incur costs. + +The tool sends code to the configured cloud sandbox; it does not execute code on +this machine. Configure the pool's network access for your workload and do not +pass application credentials or confidential data into generated code. + +REST contract: +https://learn.microsoft.com/rest/api/data-plane/containerapps/code-execution/execute +""" + +import asyncio +import os +from typing import Annotated, Any +from urllib.parse import urlsplit +from uuid import uuid4 + +import httpx +from agent_framework import Agent, tool +from agent_framework.foundry import FoundryChatClient +from azure.identity.aio import AzureCliCredential +from dotenv import load_dotenv + +load_dotenv() + + +async def main() -> None: + pool_endpoint = os.environ["POOL_MANAGEMENT_ENDPOINT"].rstrip("/") + endpoint = urlsplit(pool_endpoint) + if endpoint.scheme != "https" or not endpoint.netloc or endpoint.query or endpoint.fragment or endpoint.username: + raise ValueError("POOL_MANAGEMENT_ENDPOINT must be a trusted HTTPS pool URL without credentials or a query.") + session_id = str(uuid4()) + + async with AzureCliCredential() as credential, httpx.AsyncClient(timeout=70, follow_redirects=False) as http_client: + + @tool + async def execute_python( + code: Annotated[str, "Python code to execute in the remote session."], + ) -> dict[str, Any]: + """Run Python in the conversation's cloud sandbox and return its output and execution status.""" + token = await credential.get_token("https://dynamicsessions.io/.default") + response = await http_client.post( + f"{pool_endpoint}/executions", + params={"api-version": "2025-10-02-preview", "identifier": session_id}, + headers={"Authorization": f"Bearer {token.token}"}, + json={ + "codeInputType": "Inline", + "executionType": "Synchronous", + "code": code, + "timeoutInSeconds": 30, + "outputStreamsMaxLength": 4096, + }, + ) + response.raise_for_status() + return response.json() + + async with Agent( + client=FoundryChatClient( + project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], + model=os.environ["FOUNDRY_MODEL"], + credential=credential, + ), + instructions="Use execute_python for calculations. Check its status and errors before reporting a result.", + tools=[execute_python], + ) as agent: + result = await agent.run("Use Python to calculate the total of 19.95, 42.50, and 8.75, then add 8% tax.") + print(result.text) + + +if __name__ == "__main__": + asyncio.run(main()) From c8785e596bf6705db15f0b35ec17f343385acf3e Mon Sep 17 00:00:00 2001 From: Naveen Chatlapalli Date: Sat, 3 Oct 2026 06:58:04 -0500 Subject: [PATCH 2/3] samples: reject ambiguous Dynamic Sessions endpoint URLs --- .../Agents/Agent_ContainerAppsDynamicSessions/Program.cs | 2 +- .../02-agents/tools/container_apps_dynamic_sessions.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs index 33628136fea..338628100a5 100644 --- a/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs +++ b/dotnet/samples/02-agents/Agents/Agent_ContainerAppsDynamicSessions/Program.cs @@ -19,7 +19,7 @@ string poolEndpoint = Environment.GetEnvironmentVariable("POOL_MANAGEMENT_ENDPOINT") ?? throw new InvalidOperationException("POOL_MANAGEMENT_ENDPOINT is not set."); Uri poolUri = new(poolEndpoint); -if (poolUri.Scheme != Uri.UriSchemeHttps || poolUri.UserInfo.Length != 0 || poolUri.Query.Length != 0 || poolUri.Fragment.Length != 0) +if (poolUri.Scheme != Uri.UriSchemeHttps || poolUri.UserInfo.Length != 0 || poolEndpoint.Contains('?') || poolEndpoint.Contains('#')) { throw new InvalidOperationException("POOL_MANAGEMENT_ENDPOINT must be a trusted HTTPS pool URL without credentials or a query."); } diff --git a/python/samples/02-agents/tools/container_apps_dynamic_sessions.py b/python/samples/02-agents/tools/container_apps_dynamic_sessions.py index 1d4ef6cfcc8..f9ade657584 100644 --- a/python/samples/02-agents/tools/container_apps_dynamic_sessions.py +++ b/python/samples/02-agents/tools/container_apps_dynamic_sessions.py @@ -42,7 +42,13 @@ async def main() -> None: pool_endpoint = os.environ["POOL_MANAGEMENT_ENDPOINT"].rstrip("/") endpoint = urlsplit(pool_endpoint) - if endpoint.scheme != "https" or not endpoint.netloc or endpoint.query or endpoint.fragment or endpoint.username: + if ( + endpoint.scheme != "https" + or not endpoint.netloc + or "?" in pool_endpoint + or "#" in pool_endpoint + or endpoint.username is not None + ): raise ValueError("POOL_MANAGEMENT_ENDPOINT must be a trusted HTTPS pool URL without credentials or a query.") session_id = str(uuid4()) From ff7facffb08ea278a2764c2953d03c0bdc80e27e Mon Sep 17 00:00:00 2001 From: Naveen Chatlapalli Date: Tue, 6 Oct 2026 06:49:05 -0500 Subject: [PATCH 3/3] refactor: move Python changes to a separate PR --- python/samples/02-agents/tools/README.md | 3 - .../tools/container_apps_dynamic_sessions.py | 92 ------------------- 2 files changed, 95 deletions(-) delete mode 100644 python/samples/02-agents/tools/container_apps_dynamic_sessions.py diff --git a/python/samples/02-agents/tools/README.md b/python/samples/02-agents/tools/README.md index 6972943138b..a15f85f56c9 100644 --- a/python/samples/02-agents/tools/README.md +++ b/python/samples/02-agents/tools/README.md @@ -1,8 +1,5 @@ # Tools -[Container Apps Dynamic Sessions](./container_apps_dynamic_sessions.py) shows a -function tool that executes Python in a cloud session with per-run session ownership. - Samples that show how to define, configure, and control function tools for an agent — from basic declarations to approvals, invocation limits, session injection, and dynamic (progressive) tool exposure. diff --git a/python/samples/02-agents/tools/container_apps_dynamic_sessions.py b/python/samples/02-agents/tools/container_apps_dynamic_sessions.py deleted file mode 100644 index f9ade657584..00000000000 --- a/python/samples/02-agents/tools/container_apps_dynamic_sessions.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -"""Run agent-generated Python in an Azure Container Apps Dynamic Sessions pool. - -Prerequisites: -- Install agent-framework-foundry, azure-identity, and httpx; sign in with az login. -- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL for your model deployment. -- Set POOL_MANAGEMENT_ENDPOINT to the Python code-interpreter pool's management - endpoint (az containerapp sessionpool show --query properties.poolManagementEndpoint). -- Give the signed-in identity Azure ContainerApps Session Executor on the pool, - and permission to invoke the Foundry model. - -Each program run generates a fresh session identifier, reused for its tool calls. -The identifier is owned by the application, never selected by the model. In a -multi-user host, create a separate agent/tool/session binding for each authorized -conversation. Pool cooldown controls cleanup after the last use; this sample does -not provision or delete Azure resources. Model and session-pool usage incur costs. - -The tool sends code to the configured cloud sandbox; it does not execute code on -this machine. Configure the pool's network access for your workload and do not -pass application credentials or confidential data into generated code. - -REST contract: -https://learn.microsoft.com/rest/api/data-plane/containerapps/code-execution/execute -""" - -import asyncio -import os -from typing import Annotated, Any -from urllib.parse import urlsplit -from uuid import uuid4 - -import httpx -from agent_framework import Agent, tool -from agent_framework.foundry import FoundryChatClient -from azure.identity.aio import AzureCliCredential -from dotenv import load_dotenv - -load_dotenv() - - -async def main() -> None: - pool_endpoint = os.environ["POOL_MANAGEMENT_ENDPOINT"].rstrip("/") - endpoint = urlsplit(pool_endpoint) - if ( - endpoint.scheme != "https" - or not endpoint.netloc - or "?" in pool_endpoint - or "#" in pool_endpoint - or endpoint.username is not None - ): - raise ValueError("POOL_MANAGEMENT_ENDPOINT must be a trusted HTTPS pool URL without credentials or a query.") - session_id = str(uuid4()) - - async with AzureCliCredential() as credential, httpx.AsyncClient(timeout=70, follow_redirects=False) as http_client: - - @tool - async def execute_python( - code: Annotated[str, "Python code to execute in the remote session."], - ) -> dict[str, Any]: - """Run Python in the conversation's cloud sandbox and return its output and execution status.""" - token = await credential.get_token("https://dynamicsessions.io/.default") - response = await http_client.post( - f"{pool_endpoint}/executions", - params={"api-version": "2025-10-02-preview", "identifier": session_id}, - headers={"Authorization": f"Bearer {token.token}"}, - json={ - "codeInputType": "Inline", - "executionType": "Synchronous", - "code": code, - "timeoutInSeconds": 30, - "outputStreamsMaxLength": 4096, - }, - ) - response.raise_for_status() - return response.json() - - async with Agent( - client=FoundryChatClient( - project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], - model=os.environ["FOUNDRY_MODEL"], - credential=credential, - ), - instructions="Use execute_python for calculations. Check its status and errors before reporting a result.", - tools=[execute_python], - ) as agent: - result = await agent.run("Use Python to calculate the total of 19.95, 42.50, and 8.75, then add 8% tax.") - print(result.text) - - -if __name__ == "__main__": - asyncio.run(main())