Files
Leo Yao 56bba795cb .NET: Add foundry extension samples for python and dotnet (#4359)
* Add foundry extension samples for python and dotnet

* Align foundry extension samples with existing hosted agent patterns

- Fix Python multiagent indentation bug (from_agent_framework ran in both modes)
- Remove hardcoded personal endpoint from appsettings.Development.json
- Rename .NET folders/projects to PascalCase (FoundryMultiAgent, FoundrySingleAgent)
- Upgrade .NET multiagent from net9.0 to net10.0
- Add ManagePackageVersionsCentrally=false and analyzer blocks to .csproj files
- Replace wildcard package versions with fixed versions
- Use alpine Docker images and standard build pattern
- Align agent.yaml structure (template nesting, displayName, resources, authors)
- Convert .NET multiagent from namespace/class to top-level statements
- Add run-requests.http for multiagent sample
- Fix Python requirements.txt (remove dev deps, add agent-framework)
- Add proper copyright headers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Align foundry samples: fix builds, upgrade AgentServer to beta.8

- Fix TargetFrameworks (plural) to override inherited net472 from Directory.Build.props
- Upgrade Azure.AI.AgentServer.AgentFramework to 1.0.0-beta.8 (latest)
- Bump OpenTelemetry packages to 1.12.0 (required by beta.8)
- Fix Roslynator/format errors (imports ordering, BOM, sealed record, target-typed new)
- Verified with docker dotnet format (matching CI pipeline)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor hosted samples to use AIProjectClient.CreateAIAgentAsync

Replace PersistentAgentsClient and manual AzureOpenAIClient setup with
AIProjectClient.CreateAIAgentAsync() from Microsoft.Agents.AI.AzureAI.

- FoundryMultiAgent: Remove Azure.AI.Agents.Persistent, use CreateAIAgentAsync
  for Writer and Reviewer agents with cleanup in finally block
- FoundrySingleAgent: Remove manual GetConnection/AzureOpenAIClient chain,
  use CreateAIAgentAsync with hotel search tool
- Update csproj: add Microsoft.Agents.AI.AzureAI, remove unused packages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update READMEs to reflect AIProjectClient.CreateAIAgentAsync usage

- Reference Microsoft.Agents.AI.AzureAI and Microsoft.Agents.AI.Workflows packages
- Add Azure AI Developer role requirement for agents/write data action
- Replace PersistentAgentsClient references

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add HostedAgents READMEs and Foundry samples to solution

- Create dotnet/samples/05-end-to-end/HostedAgents/README.md with sample index
- Create python/samples/05-end-to-end/hosted_agents/README.md with sample index
- Add FoundryMultiAgent and FoundrySingleAgent to agent-framework-dotnet.slnx

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Python linting: reorder imports before load_dotenv, remove trailing whitespace

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update uv.lock to match latest package versions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix trailing whitespace in foundry_single_agent agent.yaml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Exclude dotnet.microsoft.com from link checker

This domain intermittently times out in CI, causing flaky markdown
link check failures unrelated to PR changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Align env vars to AZURE_AI_PROJECT_ENDPOINT and default model to gpt-4o-mini

Addresses PR review feedback:
- Rename PROJECT_ENDPOINT to AZURE_AI_PROJECT_ENDPOINT across all
  Foundry samples (dotnet + python) to match existing samples
- Change default model from gpt-4.1-mini to gpt-4o-mini consistently

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip flaky test CreatesWorkflowEndToEndActivities_WithCorrectName_DefaultAsync

Tracked in #4398

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove Python foundry samples from PR scope

Python hosted agent samples need further alignment with the azure-ai
package conventions. Removing from this PR to ship .NET samples first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Narrow linkspector exclusion to dotnet.microsoft.com/download only

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Leo Yao <leoyao@Leos-MacBook-Pro.local>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-05 11:43:24 +00:00

129 lines
5.1 KiB
C#

// 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.
using System.ComponentModel;
using System.Globalization;
using System.Text;
using Azure.AI.AgentServer.AgentFramework.Extensions;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
// 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-4o-mini";
Console.WriteLine($"Project Endpoint: {endpoint}");
Console.WriteLine($"Model Deployment: {deploymentName}");
// Simulated hotel data for Seattle
var seattleHotels = new[]
{
new Hotel("Contoso Suites", 189, 4.5, "Downtown"),
new Hotel("Fabrikam Residences", 159, 4.2, "Pike Place Market"),
new Hotel("Alpine Ski House", 249, 4.7, "Seattle Center"),
new Hotel("Margie's Travel Lodge", 219, 4.4, "Waterfront"),
new Hotel("Northwind Inn", 139, 4.0, "Capitol Hill"),
new Hotel("Relecloud Hotel", 99, 3.8, "University District"),
};
[Description("Get available hotels in Seattle for the specified dates. This simulates a call to a hotel availability API.")]
string GetAvailableHotels(
[Description("Check-in date in YYYY-MM-DD format")] string checkInDate,
[Description("Check-out date in YYYY-MM-DD format")] string checkOutDate,
[Description("Maximum price per night in USD (optional, defaults to 500)")] int maxPrice = 500)
{
try
{
// 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.";
}
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.";
}
// Validate dates
if (checkOut <= checkIn)
{
return "Error: Check-out date must be after check-in date.";
}
var nights = (checkOut - checkIn).Days;
// Filter hotels by price
var availableHotels = seattleHotels.Where(h => h.PricePerNight <= maxPrice).ToList();
if (availableHotels.Count == 0)
{
return $"No hotels found in Seattle within your budget of ${maxPrice}/night.";
}
// Build response
var result = new StringBuilder();
result.AppendLine($"Available hotels in Seattle from {checkInDate} to {checkOutDate} ({nights} nights):");
result.AppendLine();
foreach (var hotel in availableHotels)
{
var totalCost = hotel.PricePerNight * nights;
result.AppendLine($"**{hotel.Name}**");
result.AppendLine($" Location: {hotel.Location}");
result.AppendLine($" Rating: {hotel.Rating}/5");
result.AppendLine($" ${hotel.PricePerNight}/night (Total: ${totalCost})");
result.AppendLine();
}
return result.ToString();
}
catch (Exception ex)
{
return $"Error processing request. Details: {ex.Message}";
}
}
// 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());
// 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.
When a user asks about hotels in Seattle:
1. Ask for their check-in and check-out dates if not provided
2. Ask about their budget preferences if not mentioned
3. Use the GetAvailableHotels tool to find available options
4. Present the results in a friendly, informative way
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)]);
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
internal sealed record Hotel(string Name, int PricePerNight, double Rating, string Location);