Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
4 changes: 4 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/HostedFoundryAgent.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/HostedFiles.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/HostedLocalTools.csproj" />
</Folder>
Expand All @@ -332,6 +335,7 @@
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/HostedWorkflowSimple.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/SessionFilesClient.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/SimpleAgent.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**/bin
**/obj
**/.vs
**/.vscode
.env
*.user
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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

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
ENTRYPOINT ["dotnet", "HostedFiles.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Dockerfile for contributors building from the agent-framework repository source.
#
# This project uses ProjectReference to the local Microsoft.Agents.AI.Foundry source,
# 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-files .
# docker run --rm -p 8088:8088 -e AGENT_NAME=hosted-files -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN --env-file .env hosted-files
#
# 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
COPY out/ .
EXPOSE 8088
ENV ASPNETCORE_URLS=http://+:8088
ENTRYPOINT ["dotnet", "HostedFiles.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

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

<ItemGroup>
<PackageReference Include="Azure.AI.Projects" VersionOverride="2.1.0-beta.1" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="DotNetEnv" />
</ItemGroup>

<ItemGroup>
<!-- Bake demo resources into the published output so the deployed agent's
tools can read them from /app/resources/ inside the container. -->
<Content Include="resources\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</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" />
</ItemGroup>

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

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

// Hosted Files Agent - A hosted agent that exposes file content baked into the
// container image as knowledge accessible through three local C# tools.
//
// The contents of the project's `resources/` folder are copied into the
// published output (see HostedFiles.csproj) and live at `/app/resources/`
// inside the container at runtime. The agent's tools read files from that
// directory and surface their contents to the model.
//
// Required environment variables:
// AZURE_AI_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-4o)
//
// Optional:
// AGENT_NAME - Agent name (default: hosted-files)
// RESOURCES_DIR - Override the data directory the tools
// read from (default: <baseDir>/resources)

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

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

// Bypass SampleEnvironment alias (which prompts on missing env vars) for optional values.
string? GetOptionalEnv(string key) => System.Environment.GetEnvironmentVariable(key);

string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string deploymentName = GetOptionalEnv("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";

// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
new DevTemporaryTokenCredential(),
new DefaultAzureCredential());

// ── Resources directory (baked into the image) ──────────────────────────────

// Defaults to <process-base-dir>/resources, which is where the csproj's
// CopyToOutputDirectory entries land. Inside the container that resolves to
// /app/resources/. Override via RESOURCES_DIR if needed.
string resourcesDir = GetOptionalEnv("RESOURCES_DIR")
?? Path.Combine(AppContext.BaseDirectory, "resources");

// ── Tools: read files from the agent's bundled data directory ────────────────

[Description("List the names of files available to the agent, one per line.")]
string ListFiles()
{
try
{
if (!Directory.Exists(resourcesDir))
{
return string.Empty;
}

return string.Join(
Environment.NewLine,
Directory.EnumerateFiles(resourcesDir).Select(Path.GetFileName));
}
catch (Exception ex)
{
return $"Error listing files: {ex.Message}";
}
}

[Description("Read the full text contents of a file by name.")]
string ReadFile(
[Description("Name of the file to read (as returned by ListFiles).")] string fileName)
{
try
{
string fullPath = Path.Combine(resourcesDir, Path.GetFileName(fileName));
return File.Exists(fullPath)
? File.ReadAllText(fullPath)
: $"File '{fileName}' not found.";
}
catch (Exception ex)
{
return $"Error reading '{fileName}': {ex.Message}";
}
}

// ── Create and host the agent ────────────────────────────────────────────────

AIAgent agent = new AIProjectClient(new Uri(endpoint), credential)
.AsAIAgent(
model: deploymentName,
instructions: """
You are a friendly assistant that answers questions about a small set of
files bundled with you.

Always discover the available files with the ListFiles tool first, then
use ReadFile to read the file you need before answering. Quote numbers
and figures verbatim from the file rather than paraphrasing them.
""",
name: GetOptionalEnv("AGENT_NAME") ?? "hosted-files",
description: "Hosted agent that answers questions over a small set of bundled files.",
tools:
[
AIFunctionFactory.Create(ListFiles),
AIFunctionFactory.Create(ReadFile),
]);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFoundryResponses(agent);

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

if (app.Environment.IsDevelopment())
{
app.MapFoundryResponses("openai/v1");
}

app.Run();

/// <summary>
/// A <see cref="TokenCredential"/> for local Docker debugging only.
/// Reads a pre-fetched bearer token from the <c>AZURE_BEARER_TOKEN</c> environment variable
/// once at startup. This should NOT be used in production.
///
/// Generate a token on your host and pass it to the container:
/// export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
/// docker run -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN ...
/// </summary>
internal sealed class DevTemporaryTokenCredential : TokenCredential
{
private const string EnvironmentVariable = "AZURE_BEARER_TOKEN";
private readonly string? _token;

public DevTemporaryTokenCredential()
{
this._token = System.Environment.GetEnvironmentVariable(EnvironmentVariable);
}

public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
=> this.GetAccessToken();

public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
=> new(this.GetAccessToken());

private AccessToken GetAccessToken()
{
if (string.IsNullOrEmpty(this._token) || this._token == "DefaultAzureCredential")
{
throw new CredentialUnavailableException($"{EnvironmentVariable} environment variable is not set.");
}

return new AccessToken(this._token, DateTimeOffset.UtcNow.AddHours(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Hosted-Files

A hosted agent that exposes a small set of files baked into its container image as knowledge accessible through local C# function tools. Demonstrates the typical "data shipped with the agent" pattern for Foundry hosted agents.

The contents of [`resources/`](./resources/) are copied into the published output (see `HostedFiles.csproj`) and live at `/app/resources/` inside the container. The agent's two tools surface them to the model on demand:

| Tool | Description |
|------|-------------|
| `ListFiles` | Returns the names of files available to the agent. |
| `ReadFile` | Reads the full text contents of a file by name. |

Companion sample: [`Using-Samples/SessionFilesClient`](../Using-Samples/SessionFilesClient/) — a thin chat REPL (same shape as [`SimpleAgent`](../Using-Samples/SimpleAgent/)) that points at the deployed Hosted-Files endpoint via `FoundryAgent` and lets you ask questions whose answers come from the bundled files.

## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- 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`:

```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
```

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

## Running directly (contributors)

```bash
cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files
AGENT_NAME=hosted-files dotnet run
```

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

## Try it from the SessionFilesClient REPL

```bash
cd ../Using-Samples/SessionFilesClient
$env:AGENT_ENDPOINT = "http://localhost:8088"
$env:AGENT_NAME = "hosted-files"
dotnet run

You> Give me the total revenue in the contoso file.
Agent> The contoso file reports total revenue of "$1,482.6M".
```

The agent's `ListFiles`/`ReadFile` tools resolve relative paths against `/app/resources/`, find `contoso_q1_2026_report.txt`, and surface the figure verbatim.

## Running with Docker

This project uses `ProjectReference`, so use `Dockerfile.contributor` which takes a pre-published output:

```bash
dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
docker build -f Dockerfile.contributor -t hosted-files .

export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
docker run --rm -p 8088:8088 \
-e AGENT_NAME=hosted-files \
-e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
--env-file .env \
hosted-files
```

The bundled `resources/` folder is part of the published output and ships inside the image.

## NuGet package users

If consuming the Agent Framework as a NuGet package, use the standard `Dockerfile` instead of `Dockerfile.contributor` and switch the `ProjectReference` entries in `HostedFiles.csproj` to `PackageReference` (commented section in the csproj).

## Adding more files

Drop additional text files into [`resources/`](./resources/). The csproj `<Content Include="resources\**\*" CopyToOutputDirectory="PreserveNewest" />` rule picks them up on the next `dotnet build` / `docker build`.

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-files
displayName: "Hosted Files Agent"

description: >
A hosted agent that reads files from the per-session $HOME sandbox volume.
Demonstrates session-scoped file storage on Foundry hosted agents using
three local C# function tools (GetHomeDirectory, ListFiles, ReadFile).

metadata:
tags:
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Session Files
- Local Tools
- Agent Framework

template:
name: hosted-files
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
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-files
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
Loading
Loading