Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
AZURE_AI_PROJECT_ENDPOINT=<your-azure-ai-project-endpoint>
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
LOCAL_CODEACT_PYTHON=python3
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use the official .NET 10.0 ASP.NET runtime as a parent image
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app

# Install Python 3 so LocalCodeAct can spawn the embedded runner / validator.
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 \
&& rm -rf /var/lib/apt/lists/*

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app/publish

# Final stage
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8088
ENV ASPNETCORE_URLS=http://+:8088
ENV LOCAL_CODEACT_PYTHON=python3
ENTRYPOINT ["dotnet", "HostedLocalCodeAct.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Dockerfile for contributors building from the agent-framework repository source.
#
# This project uses ProjectReference to the local Microsoft.Agents.AI.Foundry and
# Microsoft.Agents.AI.LocalCodeAct sources, which means a standard multi-stage
# Docker build cannot resolve dependencies outside this folder. Instead, pre-publish
# the app targeting the container runtime and copy the output into the container:
#
# dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
# docker build -f Dockerfile.contributor -t hosted-local-codeact .
# docker run --rm -p 8088:8088 -e AGENT_NAME=hosted-local-codeact -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN --env-file .env hosted-local-codeact
#
# For end-users consuming the NuGet package (not ProjectReference), use the standard
# Dockerfile which performs a full dotnet restore + publish inside the container.
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app

# Install Python 3 so LocalCodeAct can spawn the embedded runner / validator.
RUN apk add --no-cache python3

COPY out/ .
EXPOSE 8088
ENV ASPNETCORE_URLS=http://+:8088
ENV LOCAL_CODEACT_PYTHON=python3
ENTRYPOINT ["dotnet", "HostedLocalCodeAct.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<RootNamespace>HostedLocalCodeAct</RootNamespace>
<AssemblyName>HostedLocalCodeAct</AssemblyName>
<NoWarn>$(NoWarn);</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="DotNetEnv" />
</ItemGroup>

<!-- For contributors: uses ProjectReference to build against local source -->
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.LocalCodeAct\Microsoft.Agents.AI.LocalCodeAct.csproj" />
<ProjectReference Include="..\Hosted_Shared_Contributor_Setup\Hosted_Shared_Contributor_Setup.csproj" />
</ItemGroup>

<!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.6.1-preview.260514.1" />
<PackageReference Include="Microsoft.Agents.AI.Foundry.Hosting" Version="1.6.1-preview.260514.1" />
<PackageReference Include="Microsoft.Agents.AI.LocalCodeAct" Version="1.6.1-preview.260514.1" />
</ItemGroup>
-->

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft. All rights reserved.

// Hosted Local CodeAct sample. Wires Microsoft.Agents.AI.LocalCodeAct into a
// Foundry hosted agent. The model only sees a single `execute_code` tool;
// `compute` and `fetch_data` are registered as sandbox-only host tools that
// generated Python reaches via `await call_tool(...)`. This mirrors the Python
// `foundry_hosted_agent.py` sample for the local-codeact package.
//
// SECURITY: LocalCodeAct executes LLM-generated Python in the agent process.
// Only deploy this sample to an externally sandboxed environment such as a
// Foundry hosted-agent container.

using System.ComponentModel;
using Azure.AI.Projects;
using Azure.Core;
using Azure.Identity;
using DotNetEnv;
using Hosted_Shared_Contributor_Setup;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Agents.AI.LocalCodeAct;
using Microsoft.Extensions.AI;

// Load .env file if present (for local development)
Env.TraversePath().Load();

string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
string pythonExecutable = Environment.GetEnvironmentVariable("LOCAL_CODEACT_PYTHON")
?? (OperatingSystem.IsWindows() ? "python.exe" : "python3");

TokenCredential credential = new ChainedTokenCredential(
new DevTemporaryTokenCredential(),
new DefaultAzureCredential());

// ── Sandbox-only tools (model never sees these directly) ─────────────────────

[Description("Perform a math operation: add, subtract, multiply, or divide.")]
static double Compute(
[Description("Operation: add, subtract, multiply, or divide.")] string operation,
[Description("First numeric operand.")] double a,
[Description("Second numeric operand.")] double b) => operation switch
{
"add" => a + b,
"subtract" => a - b,
"multiply" => a * b,
"divide" => b == 0 ? double.PositiveInfinity : a / b,
_ => throw new ArgumentException($"Unknown operation '{operation}'.", nameof(operation)),
};

[Description("Fetch records from a named simulated table (users or products).")]
static IReadOnlyList<IReadOnlyDictionary<string, object>> FetchData(
[Description("Name of the simulated table to query.")] string table)
{
Dictionary<string, IReadOnlyList<IReadOnlyDictionary<string, object>>> data = new()
{
["users"] =
[
new Dictionary<string, object> { ["id"] = 1, ["name"] = "Alice", ["role"] = "admin" },
new Dictionary<string, object> { ["id"] = 2, ["name"] = "Bob", ["role"] = "user" },
new Dictionary<string, object> { ["id"] = 3, ["name"] = "Charlie", ["role"] = "admin" },
],
["products"] =
[
new Dictionary<string, object> { ["id"] = 101, ["name"] = "Widget", ["price"] = 9.99 },
new Dictionary<string, object> { ["id"] = 102, ["name"] = "Gadget", ["price"] = 19.99 },
],
};

return data.TryGetValue(table, out var rows) ? rows : [];
}

// ── LocalCodeAct provider with sandbox-only host tools ───────────────────────

var codeActOptions = new LocalCodeActProviderOptions(pythonExecutable)
{
Tools =
[
AIFunctionFactory.Create(Compute, name: "compute"),
AIFunctionFactory.Create(FetchData, name: "fetch_data"),
],
ExecutionLimits = new ProcessExecutionLimits { TimeoutSeconds = 5 },
};

var codeAct = new LocalCodeActProvider(codeActOptions);

// ── Build the hosted agent ───────────────────────────────────────────────────

AIAgent agent = new AIProjectClient(new Uri(endpoint), credential)
.AsAIAgent(new ChatClientAgentOptions
{
Name = Environment.GetEnvironmentVariable("AGENT_NAME") ?? "hosted-local-codeact",
Description = "Hosted CodeAct agent with sandbox-only compute and fetch_data tools.",
ChatOptions = new ChatOptions
{
ModelId = deploymentName,
Instructions =
"""
You are a helpful assistant. Keep your answers brief. Prefer orchestrating your work
in a single `execute_code` block using `await call_tool(...)` over issuing many
direct tool calls. The sandbox exposes `compute` and `fetch_data` via `call_tool`.
""",
},
AIContextProviders = [codeAct],
});

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFoundryResponses(agent);
builder.Services.AddDevTemporaryLocalContributorSetup(); // Local Docker debugging only - must not be used in production.

var app = builder.Build();
app.MapFoundryResponses();

// Contributor-only: in Development, also map the per-agent OpenAI route shape that live Foundry uses
// so a local REPL client can target this server via AIProjectClient.AsAIAgent(Uri agentEndpoint).
// Do not use this in production. Hosted Foundry agents only support the agent-endpoint path.
app.MapDevTemporaryLocalAgentEndpoint();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Hosted-LocalCodeAct

A hosted agent that uses [`Microsoft.Agents.AI.LocalCodeAct`](../../../../../src/Microsoft.Agents.AI.LocalCodeAct/README.md)
to give the model a single `execute_code` tool. Two sandbox-only host tools,
`compute` and `fetch_data`, are registered on `LocalCodeActProvider` and are
reachable from inside generated Python via `await call_tool(...)` — never as
direct LLM tool calls.

This mirrors the Python
[`foundry_hosted_agent.py`](https://github.com/microsoft/agent-framework/blob/main/python/packages/local_codeact/samples/foundry_hosted_agent.py)
sample for the `agent-framework-local-codeact` package.

> **⚠️ Security:** LocalCodeAct executes LLM-generated Python in the agent
> process. The package is not a sandbox — it relies on the Foundry hosted-agent
> container (or another externally sandboxed environment) for process,
> filesystem, and network isolation. Do not run this outside of a sandbox.

## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- Python 3 available on `PATH` (used by `LocalCodeActProvider` to execute the
embedded runner and validator). Override with the `LOCAL_CODEACT_PYTHON`
environment variable if you need a specific interpreter path.
- An Azure AI Foundry project with a deployed model (e.g., `gpt-4o`)
- Azure CLI logged in (`az login`)

## Configuration

Copy the template and fill in your project endpoint:

```bash
cp .env.example .env
```

Edit `.env` and set your Azure AI Foundry project endpoint:

```env
AZURE_AI_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project>
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
LOCAL_CODEACT_PYTHON=python3
```

> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.

## Running directly (contributors)

This project uses `ProjectReference` to build against the local Agent Framework
source, including the `Microsoft.Agents.AI.LocalCodeAct` package.

```bash
cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct
AGENT_NAME=hosted-local-codeact dotnet run
```

The agent will start on `http://localhost:8088`.

### Test it

Using the Azure Developer CLI:

```bash
azd ai agent invoke --local "Fetch all users, find the admins, multiply 7 by 6, and print the users, admins, and the multiplication result. Use execute_code with await call_tool(...)."
```

Or with curl:

```bash
curl -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "Fetch all users, find the admins, multiply 7 by 6, and print the users, admins, and the multiplication result. Use execute_code with await call_tool(...).", "model": "hosted-local-codeact"}'
```

## Running with Docker

Since this project uses `ProjectReference`, use `Dockerfile.contributor` which
takes a pre-published output. The image installs Python 3 so the embedded
runner and validator scripts can execute.

### 1. Publish for the container runtime (Linux Alpine)

```bash
dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
```

### 2. Build the Docker image

```bash
docker build -f Dockerfile.contributor -t hosted-local-codeact .
```

### 3. Run the container

Generate a bearer token on your host and pass it to the container:

```bash
# Generate token (expires in ~1 hour)
export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)

# Run with token
docker run --rm -p 8088:8088 \
-e AGENT_NAME=hosted-local-codeact \
-e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
--env-file .env \
hosted-local-codeact
```

### 4. Test it

```bash
azd ai agent invoke --local "Fetch all users and print the admins."
```

## How CodeAct works here

`LocalCodeActProvider` is registered as an `AIContextProvider`. On every run it
injects:

- A single `execute_code` tool that the model can call with a Python snippet.
- CodeAct instructions that teach the model to use `await call_tool(...)` for
the provider-owned host tools, rather than asking for direct tool calls.

The provider-owned host tools in this sample:

| Tool | Description |
|------|-------------|
| `compute(operation, a, b)` | Math operation: `add`, `subtract`, `multiply`, `divide`. |
| `fetch_data(table)` | Returns rows from a simulated `users` or `products` table. |

`execute_code` runs the generated Python in a separate Python process governed
by `ProcessExecutionLimits` (5 second timeout in this sample) and the
default-on AST allow-list validator that rejects disallowed imports, builtins,
and dynamic-eval constructs before execution.

## NuGet package users

If you are consuming the Agent Framework as a NuGet package (not building from
source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See
the commented section in `HostedLocalCodeAct.csproj` for the `PackageReference`
alternative.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
name: hosted-local-codeact
displayName: "Hosted Local CodeAct Agent"

description: >
A hosted agent that uses the CodeAct pattern via
Microsoft.Agents.AI.LocalCodeAct. The model only sees an `execute_code`
tool and orchestrates `compute` and `fetch_data` sandbox-only host tools
via `await call_tool(...)` from inside generated Python.

metadata:
tags:
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Local CodeAct
- Agent Framework

template:
name: hosted-local-codeact
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.5"
memory: 1Gi
parameters:
properties: []
resources: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
kind: hosted
name: hosted-local-codeact
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.5"
memory: 1Gi
Loading
Loading