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,30 @@
# Controls which files are excluded from the code-deploy ZIP upload (.gitignore syntax).
# Note: only the root .agentignore is read; subdirectory files are not supported.
#
# To include a file that is excluded by default, use negation: !filename

# azd tooling files
azure.yaml
.agentignore

# Security / secrets
.env
.env.*
.azure/
.git/

# .NET build output
bin/
obj/
*.user
*.suo
.vs/

# Agent session state written by FileSystemAgentSessionStore during local runs. The hosted
# runtime writes its own under the container's home directory, so uploading the local copy
# would ship stale sessions with the agent.
.checkpoints/

# Contributor mode (scripts/Add-LocalFrameworkFeed.ps1) generates local-feed/ and nuget.config.
# Those are deliberately NOT excluded: the server-side restore needs them to resolve the Agent
# Framework from the packages shipped in this upload instead of nuget.org.
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
# Foundry project endpoint is not required by this echo sample (no model call).
# Local development only: bind to the readiness port the Foundry runtime probes.
ASPNETCORE_URLS=http://+:8088

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project>

<!--
Source (ZIP) deploy sample. The code-deploy upload is a flat folder with no repo-level props,
so this project is intentionally self-contained: a single target framework and explicit package
versions. Foundry runs `dotnet restore` + `dotnet publish` on it during provisioning
(dependencyResolution: remote_build in azure.yaml).

ImportDirectoryPackagesProps has to be set before the SDK props are imported, hence the explicit
Sdk imports below instead of the usual Sdk attribute on the Project element. It stops MSBuild
from walking up to the repository's dotnet/Directory.Packages.props, which would turn on central
package management and inject analyzer PackageReference items, neither of which exists inside the
ZIP, so without this the in-repo build would resolve differently from the server-side build.
-->

<PropertyGroup>
<ImportDirectoryPackagesProps>false</ImportDirectoryPackagesProps>
</PropertyGroup>

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.Web" />

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks></TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<RootNamespace>HostedInvocationsEchoAgent</RootNamespace>
<AssemblyName>HostedInvocationsEchoAgent</AssemblyName>
<NoWarn>$(NoWarn);</NoWarn>
<UserSecretsId>27e5c7df-546c-477b-ab05-d1e070a1b78a</UserSecretsId>
<AgentFrameworkVersion>1.15.0</AgentFrameworkVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.Invocations" />
<PackageReference Include="DotNetEnv" />
<PackageReference Include="OpenTelemetry.Api" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="Microsoft.Agents.AI.Abstractions" Version="$(AgentFrameworkVersion)" />
<PackageReference Include="Azure.AI.AgentServer.Invocations" Version="1.0.0-beta.5" />
<PackageReference Include="OpenTelemetry.Api" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="DotNetEnv" Version="3.1.1" />
</ItemGroup>

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

<!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" Version="1.0.0" />
<PackageReference Include="Azure.AI.AgentServer.Invocations" />
</ItemGroup>
-->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.Web" />

</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@

var app = builder.Build();

// The Foundry hosted runtime probes GET /readiness before routing invocations to the container.
// The Invocations SDK does not map that route (unlike the Responses SDK), so map it explicitly;
// without it every invoke fails with HTTP 424 session_not_ready.
app.MapGet("/readiness", () => Results.Ok());

// Map the Invocations protocol endpoints:
// POST /invocations — invoke the agent
// GET /invocations/{id} — get result (not used by this sample)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,104 +1,122 @@
# Hosted-Invocations-EchoAgent

A minimal echo agent hosted as a Foundry Hosted Agent using the **Invocations protocol**. The agent reads the request body as plain text, passes it through a custom `EchoAIAgent`, and writes the echoed text back in the response. No LLM or Azure credentials are required.
A minimal agent that echoes the user's input back, hosted as a Foundry Hosted Agent over the **Invocations protocol**. No LLM or external service is required, so it is the simplest way to see the hosting pipeline end to end.

## Prerequisites
This sample deploys to Foundry **directly from source (code / ZIP upload)**: the platform builds and runs your code with no container image, so there is no Dockerfile to author or container registry to manage. Source deploy is the default for .NET.

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
## How it works

## Configuration
- `EchoAIAgent.cs` — a tiny `AIAgent` that returns `Echo: <input>`; no model call.
- `EchoInvocationHandler.cs` — an `InvocationHandler` that reads the request body as plain text, runs the agent, and writes the response back as `text/plain`.
- `Program.cs` — registers the agent and the Invocations SDK (`AddInvocationsServer` / `MapInvocationsServer`), and maps `GET /readiness`.

Copy the template:
> **Readiness note:** unlike the Responses SDK, the Invocations SDK does **not** auto-map the
> `GET /readiness` route the Foundry runtime probes before routing calls. `Program.cs` maps it
> explicitly; without it every invoke fails with HTTP 424 `session_not_ready`.

```bash
cp .env.example .env
```
## Files

> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
| File | Purpose |
|------|---------|
| `Program.cs` | Registers the echo agent and the Invocations server, maps `/readiness`. |
| `EchoAIAgent.cs` | The echo agent (no LLM). |
| `EchoInvocationHandler.cs` | Reads the request body, runs the agent, writes `text/plain`. |
| `azure.yaml` | The unified `azd` project file. Declares the Foundry project and the hosted agent with `codeConfiguration` (source/ZIP deploy) and the `invocations` protocol. |
| `.agentignore` | Controls which files are excluded from the code-deploy ZIP upload (`.gitignore` syntax). |
| `HostedInvocationsEchoAgent.csproj` | Self-contained project: single target framework and explicit package versions. |
| `.env.example` | Template for local configuration. |
| `../../scripts/Add-LocalFrameworkFeed.ps1`, `../../scripts/add-local-framework-feed.sh` | Contributor-only helpers, see [Deploy your local framework changes](#deploy-your-local-framework-changes-contributors). |

## Running directly (contributors)
## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- An **existing** Foundry project (no model deployment is needed for this echo sample).
- Azure CLI logged in (`az login`)
- Azure Developer CLI (`azd`) with the AI agents extension: `azd extension install azure.ai.agents`

This project uses `ProjectReference` to build against the local Agent Framework source.
## Run and test locally

```bash
**Terminal 1 — host the agent:**

```
cd dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent
dotnet run
```

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

### Test it
**Terminal 2 — invoke it** (the Invocations protocol takes a plain-text body and returns `text/plain`):

```bash
curl -X POST http://localhost:8088/invocations \
-H "Content-Type: text/plain" \
-d "Hello, world!"
```
PowerShell:

Expected response:

```
Echo: Hello, world!
```powershell
(Invoke-WebRequest -Uri http://localhost:8088/invocations -Method POST -Body "Hello!").Content
```

## Running with Docker

Since this project uses `ProjectReference`, the standard `Dockerfile` cannot resolve dependencies outside this folder. Use `Dockerfile.contributor` which takes a pre-published output.

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

```bash
dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
curl -X POST http://localhost:8088/invocations -d "Hello!"
```

### 2. Build the Docker image
You get back `Echo: Hello!`.

```bash
docker build -f Dockerfile.contributor -t hosted-invocations-echo-agent .
```
## Deploy to Foundry (source / ZIP)

### 3. Run the container
`azd` scaffolds the project into a working folder, so every step below runs from an **empty
directory outside the repository**, and `-m` points at this sample's `azure.yaml`.

```bash
docker run --rm -p 8088:8088 hosted-invocations-echo-agent
```
```powershell
$work = Join-Path $env:TEMP "hosted-invocations-echo-work"
mkdir $work
cd $work

### 4. Test it
$sample = "<repo>/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/azure.yaml"
azd auth login
azd ai agent init -m $sample

```bash
curl -X POST http://localhost:8088/invocations \
-H "Content-Type: text/plain" \
-d "Hello from Docker!"
cd hosted-invocations-echo-agent
azd provision
azd deploy
```

## Deploying to Foundry (azd spec)

This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
`azd` packages the source into a ZIP (honoring `.agentignore`), uploads it, and Foundry runs
`dotnet restore` + `dotnet publish` on it during provisioning (`dependencyResolution: remote_build`
in `azure.yaml`). No Dockerfile, no container registry.

Initialize an `azd` project from this sample's manifest:
Invoke the deployed agent with a bearer token (the Invocations endpoint returns `text/plain`):

```bash
mkdir hosted-invocations-echo-agent && cd hosted-invocations-echo-agent
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/agent.manifest.yaml
TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
curl -X POST "<project-endpoint>/agents/hosted-invocations-echo-agent/endpoint/protocols/invocations?api-version=v1" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "Hello!"
```

Then deploy:
Clean up with `azd down`, then delete the working directory.

```bash
azd deploy
```
> **`azd down` does not delete the hosted agent.** It reports success but leaves the deployed agent
> in place. Delete it explicitly with a REST call:
>
> ```bash
> TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
> curl -X DELETE "<project-endpoint>/agents/hosted-invocations-echo-agent?api-version=v1&force=true" \
> -H "Authorization: Bearer $TOKEN" -H "Foundry-Features: HostedAgents=V1Preview"
> ```

If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
## Deploy your local framework changes (contributors)

```bash
azd env set AGENT_NAME hosted-invocations-echo-agent
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
```

For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
**Skip this section unless you are changing the Agent Framework itself.** The project restores the
**published** Agent Framework packages, and Foundry restores from nuget.org when it builds the
upload. To ship a local framework build instead, run the helper between `azd ai agent init` and
`azd provision`:

---
```powershell
cd $work
<repo>/dotnet/samples/04-hosting/FoundryHostedAgents/scripts/Add-LocalFrameworkFeed.ps1 -Path ./hosted-invocations-echo-agent
```

## NuGet package users
See the
[`Hosted-ChatClientAgent`](../../responses/Hosted-ChatClientAgent/README.md#deploy-your-local-framework-changes-contributors)
README for the full explanation.

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 `Hosted-Invocations-EchoAgent.csproj` for the `PackageReference` alternative.
For the full hosted-agent deployment guide, see the [official source-code deployment doc](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent-code).

This file was deleted.

This file was deleted.

Loading
Loading