Skip to content

Commit ff0cd4e

Browse files
authored
csharp hosted local tools example for AgentFramework (#520)
* Sample for local tool calls in agent * Remove image since it is not relevant. * Updating sample to use hotels instead of date and time for local tool calls * -Align PROJECT_ENDPOINT and MODEL_DEPLOYMENT_NAME with azd conventions for agents and add VS Code launch.json for easier local debugging * Updating model to use chat reference from resources * Adding AgentWithLocalTools csharp sample * Updating default model * Moving to AgentFramework folder
1 parent 6d66b2a commit ff0cd4e

7 files changed

Lines changed: 418 additions & 0 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Build outputs
2+
bin/
3+
obj/
4+
out/
5+
6+
# Environment files with secrets
7+
.env
8+
.env.*
9+
*.local
10+
appsettings.*.json
11+
!appsettings.json
12+
13+
# IDE and editor files
14+
.vs/
15+
.vscode/
16+
*.user
17+
*.suo
18+
*.sln.docstates
19+
20+
# Git
21+
.git/
22+
.gitignore
23+
24+
# Documentation and samples (not needed in container)
25+
*.md
26+
*.http
27+
28+
# Test results
29+
TestResults/
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<EnablePreviewFeatures>true</EnablePreviewFeatures>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.6" />
12+
<PackageReference Include="Azure.AI.Projects" Version="1.2.0-beta.5" />
13+
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
14+
<PackageReference Include="Azure.Identity" Version="1.17.1" />
15+
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.2.0-preview.1.26063.2" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Build the application
2+
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
3+
WORKDIR /src
4+
5+
# Copy files from the current directory on the host to the working directory in the container
6+
COPY . .
7+
8+
RUN dotnet restore
9+
RUN dotnet build -c Release --no-restore
10+
RUN dotnet publish -c Release --no-build -o /app
11+
12+
# Run the application
13+
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
14+
WORKDIR /app
15+
16+
# Copy everything needed to run the app from the "build" stage.
17+
COPY --from=build /app .
18+
19+
EXPOSE 8088
20+
ENTRYPOINT ["dotnet", "AgentWithLocalTools.dll"]
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Seattle Hotel Agent - A simple agent with a tool to find hotels in Seattle.
2+
// Uses Microsoft Agent Framework with Azure AI Foundry.
3+
// Ready for deployment to Foundry Hosted Agent service.
4+
5+
using System.ComponentModel;
6+
using System.Globalization;
7+
using System.Text;
8+
using System.ClientModel.Primitives;
9+
using Azure.AI.AgentServer.AgentFramework.Extensions;
10+
using Azure.AI.OpenAI;
11+
using Azure.AI.Projects;
12+
using Azure.Identity;
13+
using Microsoft.Agents.AI;
14+
using Microsoft.Extensions.AI;
15+
16+
// Get configuration from environment variables
17+
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
18+
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
19+
var deploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME") ?? "gpt-4.1-mini";
20+
Console.WriteLine($"Project Endpoint: {endpoint}");
21+
Console.WriteLine($"Model Deployment: {deploymentName}");
22+
// Simulated hotel data for Seattle
23+
var seattleHotels = new[]
24+
{
25+
new Hotel("Contoso Suites", 189, 4.5, "Downtown"),
26+
new Hotel("Fabrikam Residences", 159, 4.2, "Pike Place Market"),
27+
new Hotel("Alpine Ski House", 249, 4.7, "Seattle Center"),
28+
new Hotel("Margie's Travel Lodge", 219, 4.4, "Waterfront"),
29+
new Hotel("Northwind Inn", 139, 4.0, "Capitol Hill"),
30+
new Hotel("Relecloud Hotel", 99, 3.8, "University District"),
31+
};
32+
33+
[Description("Get available hotels in Seattle for the specified dates. This simulates a call to a hotel availability API.")]
34+
string GetAvailableHotels(
35+
[Description("Check-in date in YYYY-MM-DD format")] string checkInDate,
36+
[Description("Check-out date in YYYY-MM-DD format")] string checkOutDate,
37+
[Description("Maximum price per night in USD (optional, defaults to 500)")] int maxPrice = 500)
38+
{
39+
try
40+
{
41+
// Parse dates
42+
if (!DateTime.TryParseExact(checkInDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var checkIn))
43+
{
44+
return $"Error parsing check-in date. Please use YYYY-MM-DD format.";
45+
}
46+
47+
if (!DateTime.TryParseExact(checkOutDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var checkOut))
48+
{
49+
return $"Error parsing check-out date. Please use YYYY-MM-DD format.";
50+
}
51+
52+
// Validate dates
53+
if (checkOut <= checkIn)
54+
{
55+
return "Error: Check-out date must be after check-in date.";
56+
}
57+
58+
var nights = (checkOut - checkIn).Days;
59+
60+
// Filter hotels by price
61+
var availableHotels = seattleHotels.Where(h => h.PricePerNight <= maxPrice).ToList();
62+
63+
if (availableHotels.Count == 0)
64+
{
65+
return $"No hotels found in Seattle within your budget of ${maxPrice}/night.";
66+
}
67+
68+
// Build response
69+
var result = new StringBuilder();
70+
result.AppendLine($"Available hotels in Seattle from {checkInDate} to {checkOutDate} ({nights} nights):");
71+
result.AppendLine();
72+
73+
foreach (var hotel in availableHotels)
74+
{
75+
var totalCost = hotel.PricePerNight * nights;
76+
result.AppendLine($"**{hotel.Name}**");
77+
result.AppendLine($" Location: {hotel.Location}");
78+
result.AppendLine($" Rating: {hotel.Rating}/5");
79+
result.AppendLine($" ${hotel.PricePerNight}/night (Total: ${totalCost})");
80+
result.AppendLine();
81+
}
82+
83+
return result.ToString();
84+
}
85+
catch (Exception ex)
86+
{
87+
return $"Error processing request. Details: {ex.Message}";
88+
}
89+
}
90+
91+
// Create chat client using AIProjectClient to get the OpenAI connection from the project
92+
var credential = new DefaultAzureCredential();
93+
AIProjectClient projectClient = new AIProjectClient(new Uri(endpoint), credential);
94+
95+
// Get the OpenAI connection from the project
96+
ClientConnection connection = projectClient.GetConnection(typeof(AzureOpenAIClient).FullName!);
97+
98+
if (!connection.TryGetLocatorAsUri(out Uri? openAiEndpoint) || openAiEndpoint is null)
99+
{
100+
throw new InvalidOperationException("Failed to get OpenAI endpoint from project connection.");
101+
}
102+
openAiEndpoint = new Uri($"https://{openAiEndpoint.Host}");
103+
Console.WriteLine($"OpenAI Endpoint: {openAiEndpoint}");
104+
105+
var chatClient = new AzureOpenAIClient(openAiEndpoint, credential)
106+
.GetChatClient(deploymentName)
107+
.AsIChatClient()
108+
.AsBuilder()
109+
.UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false)
110+
.Build();
111+
112+
var agent = new ChatClientAgent(chatClient,
113+
name: "SeattleHotelAgent",
114+
instructions: """
115+
You are a helpful travel assistant specializing in finding hotels in Seattle, Washington.
116+
117+
When a user asks about hotels in Seattle:
118+
1. Ask for their check-in and check-out dates if not provided
119+
2. Ask about their budget preferences if not mentioned
120+
3. Use the GetAvailableHotels tool to find available options
121+
4. Present the results in a friendly, informative way
122+
5. Offer to help with additional questions about the hotels or Seattle
123+
124+
Be conversational and helpful. If users ask about things outside of Seattle hotels,
125+
politely let them know you specialize in Seattle hotel recommendations.
126+
""",
127+
tools: [AIFunctionFactory.Create(GetAvailableHotels)])
128+
.AsBuilder()
129+
.UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false)
130+
.Build();
131+
132+
Console.WriteLine("Seattle Hotel Agent Server running on http://localhost:8088");
133+
await agent.RunAIAgentAsync(telemetrySourceName: "Agents");
134+
135+
// Hotel record for simulated data
136+
record Hotel(string Name, int PricePerNight, double Rating, string Location);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
**IMPORTANT!** All samples and other resources made available in this GitHub repository ("samples") are designed to assist in accelerating development of agents, solutions, and agent workflows for various scenarios. Review all provided resources and carefully test output behavior in the context of your use case. AI responses may be inaccurate and AI actions should be monitored with human oversight. Learn more in the transparency documents for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note) and [Agent Framework](https://github.com/microsoft/agent-framework/blob/main/TRANSPARENCY_FAQ.md).
2+
3+
Agents, solutions, or other output you create may be subject to legal and regulatory requirements, may require licenses, or may not be suitable for all industries, scenarios, or use cases. By using any sample, you are acknowledging that any output created using those samples are solely your responsibility, and that you will comply with all applicable laws, regulations, and relevant safety standards, terms of service, and codes of conduct.
4+
5+
Third-party samples contained in this folder are subject to their own designated terms, and they have not been tested or verified by Microsoft or its affiliates.
6+
7+
Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output.
8+
9+
# What this sample demonstrates
10+
11+
This sample demonstrates a **key advantage of code-based hosted agents**:
12+
13+
- **Local C# tool execution** - Run custom C# methods as agent tools
14+
15+
Code-based agents can execute **any C# code** you write. This sample includes a Seattle Hotel Agent with a `GetAvailableHotels` tool that searches for available hotels based on check-in/check-out dates and budget preferences.
16+
17+
The agent is hosted using the [Azure AI AgentServer SDK](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.agentserver.agentframework-readme) and can be deployed to Microsoft Foundry using the Azure Developer CLI.
18+
19+
## How It Works
20+
21+
### Local Tools Integration
22+
23+
In [Program.cs](Program.cs), the agent uses a local C# method (`GetAvailableHotels`) that simulates a hotel availability API. This demonstrates how code-based agents can execute custom server-side logic that prompt agents cannot access.
24+
25+
The tool accepts:
26+
- **checkInDate** - Check-in date in YYYY-MM-DD format
27+
- **checkOutDate** - Check-out date in YYYY-MM-DD format
28+
- **maxPrice** - Maximum price per night in USD (optional, defaults to $500)
29+
30+
### Agent Hosting
31+
32+
The agent is hosted using the [Azure AI AgentServer SDK](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.agentserver.agentframework-readme),
33+
which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
34+
35+
### Agent Deployment
36+
37+
The hosted agent can be deployed to Microsoft Foundry using the Azure Developer CLI [ai agent](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/concepts/hosted-agents?view=foundry&tabs=cli#create-a-hosted-agent) extension.
38+
39+
## Running the Agent Locally
40+
41+
### Prerequisites
42+
43+
Before running this sample, ensure you have:
44+
45+
1. **Azure AI Foundry Project**
46+
- Project created in [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/what-is-foundry?view=foundry#microsoft-foundry-portals)
47+
- Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`)
48+
- Note your project endpoint URL and model deployment name
49+
50+
2. **Azure CLI**
51+
- Installed and authenticated
52+
- Run `az login` and verify with `az account show`
53+
54+
3. **.NET 10.0 SDK or later**
55+
- Verify your version: `dotnet --version`
56+
- Download from [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)
57+
58+
### Environment Variables
59+
60+
Set the following environment variables (matching `agent.yaml`):
61+
62+
- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Foundry project endpoint URL (required)
63+
- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4.1-mini`)
64+
65+
**PowerShell:**
66+
67+
```powershell
68+
# Replace with your actual values
69+
$env:AZURE_AI_PROJECT_ENDPOINT="https://<your-resource>.services.ai.azure.com/api/projects/<your-project>"
70+
$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"
71+
```
72+
73+
**Bash:**
74+
75+
```bash
76+
export AZURE_AI_PROJECT_ENDPOINT="https://<your-resource>.services.ai.azure.com/api/projects/<your-project>"
77+
export MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"
78+
```
79+
80+
### Running the Sample
81+
82+
To run the agent, execute the following command in your terminal:
83+
84+
```bash
85+
dotnet run
86+
```
87+
88+
This will start the hosted agent locally on `http://localhost:8088/`.
89+
90+
### Interacting with the Agent
91+
92+
**PowerShell (Windows):**
93+
```powershell
94+
$body = @{
95+
input = "I need a hotel in Seattle from 2025-03-15 to 2025-03-18, budget under $200 per night"
96+
stream = $false
97+
} | ConvertTo-Json
98+
99+
Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json"
100+
```
101+
102+
**Bash/curl (Linux/macOS):**
103+
```bash
104+
curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \
105+
-d '{"input": "Find me hotels in Seattle for March 20-23, 2025 under $200 per night","stream":false}'
106+
```
107+
108+
You can also use the `run-requests.http` file in this directory with the VS Code REST Client extension.
109+
110+
The agent will use the `GetAvailableHotels` tool to search for available hotels matching your criteria.
111+
112+
### Deploying the Agent to Microsoft Foundry
113+
114+
To deploy your agent to Microsoft Foundry, follow the comprehensive deployment guide at https://learn.microsoft.com/en-us/azure/ai-foundry/agents/concepts/hosted-agents?view=foundry&tabs=cli
115+
116+
## Troubleshooting
117+
118+
### Images built on Apple Silicon or other ARM64 machines do not work on our service
119+
120+
We **recommend using `azd` cloud build**, which always builds images with the correct architecture.
121+
122+
If you choose to **build locally**, and your machine is **not `linux/amd64`** (for example, an Apple Silicon Mac), the image will **not be compatible with our service**, causing runtime failures.
123+
124+
**Fix for local builds**
125+
126+
Use this command to build the image locally:
127+
128+
```shell
129+
docker build --platform=linux/amd64 -t image .
130+
```
131+
132+
This forces the image to be built for the required `amd64` architecture.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Unique identifier/name for this agent
2+
name: seattle-hotel-agent
3+
# Brief description of what this agent does
4+
description: >
5+
A travel assistant agent that helps users find hotels in Seattle.
6+
Demonstrates local C# tool execution - a key advantage of code-based
7+
hosted agents over prompt agents.
8+
metadata:
9+
# Categorization tags for organizing and discovering agents
10+
authors:
11+
- Microsoft
12+
tags:
13+
- Azure AI AgentServer
14+
- Microsoft Agent Framework
15+
- Local Tools
16+
- Travel Assistant
17+
- Hotel Search
18+
template:
19+
name: seattle-hotel-agent
20+
kind: hosted
21+
protocols:
22+
- protocol: responses
23+
version: v1
24+
environment_variables:
25+
- name: AZURE_AI_PROJECT_ENDPOINT
26+
value: ${AZURE_AI_PROJECT_ENDPOINT}
27+
- name: MODEL_DEPLOYMENT_NAME
28+
value: "{{chat}}"
29+
resources:
30+
- kind: model
31+
id: gpt-4.1-mini
32+
name: chat

0 commit comments

Comments
 (0)