From 68295bf9655aa56500f8a007777f58d5cf1598ff Mon Sep 17 00:00:00 2001
From: huimiu <107838226+huimiu@users.noreply.github.com>
Date: Mon, 9 Mar 2026 07:04:05 +0000
Subject: [PATCH] ci: sync Agent Framework hosted-agent samples (pre-release)
---
samples/hosted-agent/dotnet/agent/Program.cs | 58 +++---
.../dotnet/agent/{{SafeProjectName}}.csproj | 26 ++-
.../hosted-agent/dotnet/workflow/Program.cs | 187 ++++--------------
.../workflow/{{SafeProjectName}}.csproj | 44 +++--
4 files changed, 117 insertions(+), 198 deletions(-)
diff --git a/samples/hosted-agent/dotnet/agent/Program.cs b/samples/hosted-agent/dotnet/agent/Program.cs
index 5b28596..759636b 100644
--- a/samples/hosted-agent/dotnet/agent/Program.cs
+++ b/samples/hosted-agent/dotnet/agent/Program.cs
@@ -1,3 +1,5 @@
+// Copyright (c) Microsoft. All rights reserved.
+
// Seattle Hotel Agent - A simple agent with a tool to find hotels in Seattle.
// Uses Microsoft Agent Framework with Azure AI Foundry.
// Ready for deployment to Foundry Hosted Agent service.
@@ -5,9 +7,8 @@
using System.ComponentModel;
using System.Globalization;
using System.Text;
-using System.ClientModel.Primitives;
+
using Azure.AI.AgentServer.AgentFramework.Extensions;
-using Azure.AI.OpenAI;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -16,7 +17,7 @@
// Get configuration from environment variables
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME") ?? "gpt-4.1-mini";
+var deploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
Console.WriteLine($"Project Endpoint: {endpoint}");
Console.WriteLine($"Model Deployment: {deploymentName}");
// Simulated hotel data for Seattle
@@ -41,12 +42,12 @@ string GetAvailableHotels(
// Parse dates
if (!DateTime.TryParseExact(checkInDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var checkIn))
{
- return $"Error parsing check-in date. Please use YYYY-MM-DD format.";
+ return "Error parsing check-in date. Please use YYYY-MM-DD format.";
}
if (!DateTime.TryParseExact(checkOutDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var checkOut))
{
- return $"Error parsing check-out date. Please use YYYY-MM-DD format.";
+ return "Error parsing check-out date. Please use YYYY-MM-DD format.";
}
// Validate dates
@@ -88,29 +89,15 @@ string GetAvailableHotels(
}
}
-// Create chat client using AIProjectClient to get the OpenAI connection from the project
-var credential = new DefaultAzureCredential();
-AIProjectClient projectClient = new AIProjectClient(new Uri(endpoint), credential);
-
-// Get the OpenAI connection from the project
-ClientConnection connection = projectClient.GetConnection(typeof(AzureOpenAIClient).FullName!);
-
-if (!connection.TryGetLocatorAsUri(out Uri? openAiEndpoint) || openAiEndpoint is null)
-{
- throw new InvalidOperationException("Failed to get OpenAI endpoint from project connection.");
-}
-openAiEndpoint = new Uri($"https://{openAiEndpoint.Host}");
-Console.WriteLine($"OpenAI Endpoint: {openAiEndpoint}");
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
-var chatClient = new AzureOpenAIClient(openAiEndpoint, credential)
- .GetChatClient(deploymentName)
- .AsIChatClient()
- .AsBuilder()
- .UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false)
- .Build();
-
-var agent = new ChatClientAgent(chatClient,
+// Create Foundry agent with hotel search tool
+AIAgent agent = await aiProjectClient.CreateAIAgentAsync(
name: "SeattleHotelAgent",
+ model: deploymentName,
instructions: """
You are a helpful travel assistant specializing in finding hotels in Seattle, Washington.
@@ -124,13 +111,18 @@ 5. Offer to help with additional questions about the hotels or Seattle
Be conversational and helpful. If users ask about things outside of Seattle hotels,
politely let them know you specialize in Seattle hotel recommendations.
""",
- tools: [AIFunctionFactory.Create(GetAvailableHotels)])
- .AsBuilder()
- .UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false)
- .Build();
+ tools: [AIFunctionFactory.Create(GetAvailableHotels)]);
-Console.WriteLine("Seattle Hotel Agent Server running on http://localhost:8088");
-await agent.RunAIAgentAsync(telemetrySourceName: "Agents");
+try
+{
+ Console.WriteLine("Seattle Hotel Agent Server running on http://localhost:8088");
+ await agent.RunAIAgentAsync(telemetrySourceName: "Agents");
+}
+finally
+{
+ // Cleanup server-side agent
+ await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
+}
// Hotel record for simulated data
-record Hotel(string Name, int PricePerNight, double Rating, string Location);
+internal sealed record Hotel(string Name, int PricePerNight, double Rating, string Location);
diff --git a/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj b/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj
index d714c09..7c8c9c9 100644
--- a/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj
+++ b/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj
@@ -5,12 +5,36 @@
enable
enable
true
+ net10.0
+ false
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/samples/hosted-agent/dotnet/workflow/Program.cs b/samples/hosted-agent/dotnet/workflow/Program.cs
index 318f15e..138efb0 100644
--- a/samples/hosted-agent/dotnet/workflow/Program.cs
+++ b/samples/hosted-agent/dotnet/workflow/Program.cs
@@ -1,166 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
-using Azure.AI.Agents.Persistent;
+// This sample demonstrates a multi-agent workflow with Writer and Reviewer agents
+// using Azure AI Foundry AIProjectClient and the Agent Framework WorkflowBuilder.
+
using Azure.AI.AgentServer.AgentFramework.Extensions;
-using Azure.Core;
+using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.Configuration;
-using OpenTelemetry;
-using OpenTelemetry.Resources;
-using OpenTelemetry.Trace;
-
-namespace {{SafeProjectName}};
-
-internal static class Program
-{
- private static TracerProvider? s_tracerProvider;
-
- private static async Task Main(string[] args)
- {
- try
- {
- // Enable OpenTelemetry tracing for visualization
- ConfigureObservability();
-
- await RunAsync().ConfigureAwait(false);
- }
- catch (Exception e)
- {
- Console.WriteLine($"Critical error: {e}");
- }
- }
-
- private static async ValueTask RunAsync()
- {
- // Build configuration
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.Development.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- var endpoint =
- configuration["PROJECT_ENDPOINT"]
- ?? throw new InvalidOperationException(
- "PROJECT_ENDPOINT is required. Set it in appsettings.Development.json for local development or as PROJECT_ENDPOINT environment variable for production");
- var deployment =
- configuration["MODEL_DEPLOYMENT_NAME"]
- ?? throw new InvalidOperationException(
- "MODEL_DEPLOYMENT_NAME is required. Set it in appsettings.Development.json for local development or as MODEL_DEPLOYMENT_NAME environment variable for containers");
-
- Console.WriteLine($"Using Azure AI endpoint: {endpoint}");
- Console.WriteLine($"Using model deployment: {deployment}");
-
- // Create credential - use ManagedIdentityCredential if MSI_ENDPOINT exists, otherwise DefaultAzureCredential
- TokenCredential credential = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSI_ENDPOINT"))
- ? new DefaultAzureCredential()
- : new ManagedIdentityCredential();
- // Create separate PersistentAgentsClient for each agent
- var writerClient = new PersistentAgentsClient(endpoint, credential);
- var reviewerClient = new PersistentAgentsClient(endpoint, credential);
+var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
- (ChatClientAgent agent, string id)? writer = null;
- (ChatClientAgent agent, string id)? reviewer = null;
+Console.WriteLine($"Using Azure AI endpoint: {endpoint}");
+Console.WriteLine($"Using model deployment: {deploymentName}");
- try
- {
- // Create Foundry agents with separate clients
- writer = await CreateAgentAsync(
- writerClient,
- deployment,
- "Writer",
- "You are an excellent content writer. You create new content and edit contents based on the feedback."
- );
- reviewer = await CreateAgentAsync(
- reviewerClient,
- deployment,
- "Reviewer",
- "You are an excellent content reviewer. Provide actionable feedback to the writer about the provided content. Provide the feedback in the most concise manner possible."
- );
- Console.WriteLine();
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
- var workflow = new WorkflowBuilder(writer.Value.agent)
- .AddEdge(writer.Value.agent, reviewer.Value.agent)
- .WithOutputFrom(reviewer.Value.agent)
- .Build();
+// Create Foundry agents
+AIAgent writerAgent = await aiProjectClient.CreateAIAgentAsync(
+ name: "Writer",
+ model: deploymentName,
+ instructions: "You are an excellent content writer. You create new content and edit contents based on the feedback.");
- Console.WriteLine("Starting Writer-Reviewer Workflow Agent Server on http://localhost:8088");
- await workflow.AsAgent().RunAIAgentAsync();
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error running workflow: {ex.Message}");
- throw;
- }
- finally
- {
- // Clean up all resources
- await CleanupAsync(writerClient, writer?.id);
- await CleanupAsync(reviewerClient, reviewer?.id);
+AIAgent reviewerAgent = await aiProjectClient.CreateAIAgentAsync(
+ name: "Reviewer",
+ model: deploymentName,
+ instructions: "You are an excellent content reviewer. Provide actionable feedback to the writer about the provided content. Provide the feedback in the most concise manner possible.");
- if (credential is IDisposable disposable)
- {
- disposable.Dispose();
- }
- }
- }
-
- private static async Task<(ChatClientAgent agent, string id)> CreateAgentAsync(
- PersistentAgentsClient client,
- string model,
- string name,
- string instructions)
- {
- var agentMetadata = await client.Administration.CreateAgentAsync(
- model: model,
- name: name,
- instructions: instructions
- );
-
- var chatClient = client.AsIChatClient(agentMetadata.Value.Id);
- return (new ChatClientAgent(chatClient), agentMetadata.Value.Id);
- }
-
- private static async Task CleanupAsync(PersistentAgentsClient client, string? agentId)
- {
- if (string.IsNullOrEmpty(agentId))
- {
- return;
- }
-
- try
- {
- await client.Administration.DeleteAgentAsync(agentId);
- }
- catch (Exception e)
- {
- Console.WriteLine($"Cleanup failed for agent {agentId}: {e.Message}");
- }
- }
-
- private static void ConfigureObservability()
- {
- var otlpEndpoint =
- Environment.GetEnvironmentVariable("OTLP_ENDPOINT") ?? "http://localhost:4319";
-
- var resourceBuilder = ResourceBuilder.CreateDefault()
- .AddService("WorkflowSample");
-
- s_tracerProvider = Sdk.CreateTracerProviderBuilder()
- .SetResourceBuilder(resourceBuilder)
- .AddSource("Microsoft.Agents.AI.*") // All agent framework sources
- .SetSampler(new AlwaysOnSampler()) // Ensure all traces are sampled
- .AddOtlpExporter(options =>
- {
- options.Endpoint = new Uri(otlpEndpoint);
- options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
- })
- .Build();
+try
+{
+ var workflow = new WorkflowBuilder(writerAgent)
+ .AddEdge(writerAgent, reviewerAgent)
+ .Build();
- Console.WriteLine($"OpenTelemetry configured. OTLP endpoint: {otlpEndpoint}");
- }
+ Console.WriteLine("Starting Writer-Reviewer Workflow Agent Server on http://localhost:8088");
+ await workflow.AsAgent().RunAIAgentAsync();
+}
+finally
+{
+ // Cleanup server-side agents
+ await aiProjectClient.Agents.DeleteAgentAsync(writerAgent.Name);
+ await aiProjectClient.Agents.DeleteAgentAsync(reviewerAgent.Name);
}
diff --git a/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj b/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj
index d884ae2..79a0268 100644
--- a/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj
+++ b/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj
@@ -1,26 +1,46 @@
-
+
Exe
net9.0
enable
enable
+ net10.0
+ false
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+