diff --git a/.github/workflows/release_sample.yml b/.github/workflows/release_sample.yml new file mode 100644 index 0000000..05d1ab4 --- /dev/null +++ b/.github/workflows/release_sample.yml @@ -0,0 +1,98 @@ +name: Release Hosted Agents + +on: + workflow_dispatch: + inputs: + channel: + description: "Release channel" + required: true + type: choice + options: + - stable + - pre-release + default: stable + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Read version and branch from manifest + id: version + run: | + CHANNEL="${{ github.event.inputs.channel }}" + VERSION=$(jq -r ".channels.\"$CHANNEL\".sampleVersion" samples/hosted-agent/version-manifest.json) + BRANCH=$(jq -r ".channels.\"$CHANNEL\".branch" samples/hosted-agent/version-manifest.json) + echo "tag=$VERSION" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + + - name: Checkout sample branch + uses: actions/checkout@v4 + with: + ref: ${{ steps.version.outputs.branch }} + path: sample-source + + - name: Copy samples from target branch + run: | + rm -rf samples/hosted-agent/dotnet samples/hosted-agent/python + cp -r sample-source/samples/hosted-agent/dotnet samples/hosted-agent/dotnet + cp -r sample-source/samples/hosted-agent/python samples/hosted-agent/python + + - name: Create dotnet agent package + run: | + cd samples/hosted-agent + zip -r dotnet.zip dotnet/ + ls -lh dotnet.zip + + - name: Create python agent package + run: | + cd samples/hosted-agent + zip -r python.zip python/ + ls -lh python.zip + + - name: Upload packages as artifacts + uses: actions/upload-artifact@v4 + with: + name: hosted-agents-packages + path: | + samples/hosted-agent/dotnet.zip + samples/hosted-agent/python.zip + retention-days: 30 + + release: + needs: build + runs-on: ubuntu-latest + # ------------------------------------------------------------ + # This environment requires manual approval before proceeding. + # Go to: Settings → Environments → release-approval + # Add the specific GitHub users/teams as "Required reviewers". + # They will receive a notification with an approval link when + # this workflow runs. + # ------------------------------------------------------------ + environment: release-approval + steps: + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: hosted-agents-packages + path: samples/hosted-agent/ + + - name: Upload packages to Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ needs.build.outputs.version }} + name: Hosted Agents ${{ needs.build.outputs.version }} + prerelease: ${{ github.event.inputs.channel == 'pre-release' }} + files: | + samples/hosted-agent/dotnet.zip + samples/hosted-agent/python.zip + generate_release_notes: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/samples/hosted-agent/dotnet/agent/.dockerignore b/samples/hosted-agent/dotnet/agent/.dockerignore new file mode 100644 index 0000000..6bfa65a --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/.dockerignore @@ -0,0 +1,57 @@ +# Build outputs +bin/ +obj/ +out/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +*.sln.docstates +.foundry/ + +# Git +.git/ +.gitignore + +# Documentation and samples (not needed in container) +*.md +*.http + +# Ignore files +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Environment files with secrets +.env +.env.* +*.local +appsettings.*.json +!appsettings.json + +.venv/ +__pycache__/ diff --git a/samples/hosted-agent/dotnet/agent/Dockerfile b/samples/hosted-agent/dotnet/agent/Dockerfile new file mode 100644 index 0000000..6a9cd12 --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/Dockerfile @@ -0,0 +1,20 @@ +# Build the application +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /src + +# Copy files from the current directory on the host to the working directory in the container +COPY . . + +RUN dotnet restore +RUN dotnet build -c Release --no-restore +RUN dotnet publish -c Release --no-build -o /app + +# Run the application +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final +WORKDIR /app + +# Copy everything needed to run the app from the "build" stage. +COPY --from=build /app . + +EXPOSE 8088 +ENTRYPOINT ["dotnet", "{{SafeProjectName}}.dll"] diff --git a/samples/hosted-agent/dotnet/agent/Program.cs b/samples/hosted-agent/dotnet/agent/Program.cs new file mode 100644 index 0000000..5b28596 --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/Program.cs @@ -0,0 +1,136 @@ +// 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 System.ClientModel.Primitives; +using Azure.AI.AgentServer.AgentFramework.Extensions; +using Azure.AI.OpenAI; +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-4.1-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}"; + } +} + +// 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}"); + +var chatClient = new AzureOpenAIClient(openAiEndpoint, credential) + .GetChatClient(deploymentName) + .AsIChatClient() + .AsBuilder() + .UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false) + .Build(); + +var agent = new ChatClientAgent(chatClient, + name: "SeattleHotelAgent", + 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)]) + .AsBuilder() + .UseOpenTelemetry(sourceName: "Agents", configure: cfg => cfg.EnableSensitiveData = false) + .Build(); + +Console.WriteLine("Seattle Hotel Agent Server running on http://localhost:8088"); +await agent.RunAIAgentAsync(telemetrySourceName: "Agents"); + +// Hotel record for simulated data +record Hotel(string Name, int PricePerNight, double Rating, string Location); diff --git a/samples/hosted-agent/dotnet/agent/README.md b/samples/hosted-agent/dotnet/agent/README.md new file mode 100644 index 0000000..4039a84 --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/README.md @@ -0,0 +1,166 @@ +**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). + +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. + +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. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Local C# tool execution** - Run custom C# methods as agent tools + +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. + +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. + +## How It Works + +### Local Tools Integration + +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. + +The tool accepts: + +- **checkInDate** - Check-in date in YYYY-MM-DD format +- **checkOutDate** - Check-out date in YYYY-MM-DD format +- **maxPrice** - Maximum price per night in USD (optional, defaults to $500) + +### Agent Hosting + +The agent is hosted using the [Azure AI AgentServer SDK](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.agentserver.agentframework-readme), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Azure AI Foundry Project** + - Project created. + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`) + - Note your project endpoint URL and model deployment name + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **.NET 10.0 SDK or later** + - Verify your version: `dotnet --version` + - Download from [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download) + +### Environment Variables + +Set the following environment variables (matching `agent.yaml`): + +- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Foundry project endpoint URL (required) +- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4.1-mini`) + +**PowerShell:** + +```powershell +# Replace with your actual values +$env:AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +**Bash:** + +```bash +export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Running the Sample + +To run the agent, execute the following command in your terminal: + +```bash +dotnet restore +dotnet build +dotnet run +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +### Interacting with the Agent + +**VS Code:** + +1. Open the Visual Studio Code Command Palette and execute the `Microsoft Foundry: Open Container Agent Playground Locally` command. +2. Execute the following commands to start the containerized hosted agent. + + ```bash + dotnet restore + dotnet build + dotnet run + ``` + +3. Submit a request to the agent through the playground interface. For example, you may enter a prompt such as: "I need a hotel in Seattle from 2025-03-15 to 2025-03-18, budget under $200 per night." +4. The agent will use the GetAvailableHotels tool to search for available hotels matching your criteria. + +> **Note**: Open the local playground before starting the container agent to ensure the visualization functions correctly. + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "I need a hotel in Seattle from 2025-03-15 to 2025-03-18, budget under `$200 per night" + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Find me hotels in Seattle for March 20-23, 2025 under $200 per night","stream":false}' +``` + +You can also use the `run-requests.http` file in this directory with the VS Code REST Client extension. + +The agent will use the `GetAvailableHotels` tool to search for available hotels matching your criteria. + +## Deploying the Agent to Microsoft Foundry + +**Preparation (required)** + +Please check the environment_variables section in [agent.yaml](agent.yaml) and ensure the variables there are set in your target Microsoft Foundry Project. + +To deploy the hosted agent: + +1. Open the VS Code Command Palette and run the `Microsoft Foundry: Deploy Hosted Agent` command. +2. Follow the interactive deployment prompts. The extension will help you select or create the container files it needs. +3. After deployment completes, the hosted agent appears under the `Hosted Agents (Preview)` section of the extension tree. You can select the agent there to view details and test it using the integrated playground. + +**What the deploy flow does for you:** + +- Creates or obtains an Azure Container Registry for the target project. +- Builds and pushes a container image from your workspace (the build packages the workspace respecting `.dockerignore`). +- Creates an agent version in Microsoft Foundry using the built image. If a `.env` file exists at the workspace root, the extension will parse it and include its key/value pairs as the hosted agent's environment variables in the create request (these variables will be available to the agent runtime). +- Starts the agent container on the project's capability host. If the capability host is not provisioned, the extension will prompt you to enable it and will guide you through creating it. + +## MSI Configuration in the Azure Portal + +This sample requires the Microsoft Foundry Project to authenticate using a Managed Identity when running remotely in Azure. Grant the project's managed identity the required permissions by assigning the built-in [Azure AI User](https://aka.ms/foundry-ext-project-role) role. + +To configure the Managed Identity: + +1. In the Azure Portal, open the Foundry Project. +2. Select "Access control (IAM)" from the left-hand menu. +3. Click "Add" and choose "Add role assignment". +4. In the role selection, search for and select "Azure AI User", then click "Next". +5. For "Assign access to", choose "Managed identity". +6. Click "Select members", locate the managed identity associated with your Foundry Project (you can search by the project name), then click "Select". +7. Click "Review + assign" to complete the assignment. +8. Allow a few minutes for the role assignment to propagate before running the application. + +## Additional Resources + +- [Microsoft Agents Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) +- [Managed Identities for Azure Resources](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/) diff --git a/samples/hosted-agent/dotnet/agent/agent.yaml b/samples/hosted-agent/dotnet/agent/agent.yaml new file mode 100644 index 0000000..014464d --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/agent.yaml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: {{AgentName}} +description: > + A travel assistant agent that helps users find hotels in Seattle. + Demonstrates local C# tool execution - a key advantage of code-based + hosted agents over prompt agents. +metadata: + authors: + - Microsoft + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Local Tools + - Travel Assistant + - Hotel Search +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: PROJECT_ENDPOINT + value: {{=<% %>=}}"{{AZURE_AI_PROJECT_ENDPOINT}}"<%={{ }}=%> + - name: MODEL_DEPLOYMENT_NAME + value: {{=<% %>=}}"{{MODEL_DEPLOYMENT_NAME}}"<%={{ }}=%> \ No newline at end of file diff --git a/samples/hosted-agent/dotnet/agent/run-requests.http b/samples/hosted-agent/dotnet/agent/run-requests.http new file mode 100644 index 0000000..4f2e87e --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/run-requests.http @@ -0,0 +1,52 @@ +@host = http://localhost:8088 +@endpoint = {{host}}/responses + +### Health Check +GET {{host}}/readiness + +### Simple hotel search - budget under $200 +POST {{endpoint}} +Content-Type: application/json + +{ + "input": "I need a hotel in Seattle from 2025-03-15 to 2025-03-18, budget under $200 per night", + "stream": false +} + +### Hotel search with higher budget +POST {{endpoint}} +Content-Type: application/json + +{ + "input": "Find me hotels in Seattle for March 20-23, 2025 under $250 per night", + "stream": false +} + +### Ask for recommendations without dates (agent should ask for clarification) +POST {{endpoint}} +Content-Type: application/json + +{ + "input": "What hotels do you recommend in Seattle?", + "stream": false +} + +### Explicit input format +POST {{endpoint}} +Content-Type: application/json + +{ + "input": [ + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "I'm looking for a hotel in Seattle from 2025-04-01 to 2025-04-05, my budget is $150 per night maximum" + } + ] + } + ], + "stream": false +} diff --git a/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj b/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj new file mode 100644 index 0000000..d714c09 --- /dev/null +++ b/samples/hosted-agent/dotnet/agent/{{SafeProjectName}}.csproj @@ -0,0 +1,16 @@ + + + Exe + net10.0 + enable + enable + true + + + + + + + + + diff --git a/samples/hosted-agent/dotnet/minimal/.dockerignore b/samples/hosted-agent/dotnet/minimal/.dockerignore new file mode 100644 index 0000000..79cc807 --- /dev/null +++ b/samples/hosted-agent/dotnet/minimal/.dockerignore @@ -0,0 +1,51 @@ +# Build artifacts +bin/ +obj/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +.foundry/ + +# Source control +.git/ + +# Documentation +README.md + +# Ignore files +.gitignore +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Local development config +appsettings.Development.json +.env + +.venv/ +__pycache__/ diff --git a/samples/hosted-agent/dotnet/minimal/Dockerfile b/samples/hosted-agent/dotnet/minimal/Dockerfile new file mode 100644 index 0000000..e4091cf --- /dev/null +++ b/samples/hosted-agent/dotnet/minimal/Dockerfile @@ -0,0 +1,21 @@ +# Build the application +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /src + +# Copy files from the current directory on the host to the working directory in the container +COPY . . + +# Restore packages +RUN dotnet restore +RUN dotnet build -c Release --no-restore +RUN dotnet publish -c Release --no-build -o /app -p:AssemblyName=app + +# Run the application +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final +WORKDIR /app + +# Copy everything needed to run the app from the "build" stage. +COPY --from=build /app . + +EXPOSE 8088 +ENTRYPOINT ["dotnet", "app.dll"] diff --git a/samples/hosted-agent/dotnet/workflow/.dockerignore b/samples/hosted-agent/dotnet/workflow/.dockerignore new file mode 100644 index 0000000..6bfa65a --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/.dockerignore @@ -0,0 +1,57 @@ +# Build outputs +bin/ +obj/ +out/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +*.sln.docstates +.foundry/ + +# Git +.git/ +.gitignore + +# Documentation and samples (not needed in container) +*.md +*.http + +# Ignore files +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Environment files with secrets +.env +.env.* +*.local +appsettings.*.json +!appsettings.json + +.venv/ +__pycache__/ diff --git a/samples/hosted-agent/dotnet/workflow/.env b/samples/hosted-agent/dotnet/workflow/.env new file mode 100644 index 0000000..2ce907d --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/.env @@ -0,0 +1,4 @@ +# IMPORTANT: Never commit .env to version control - add it to .gitignore + +PROJECT_ENDPOINT={{{AzureAIProjectEndpoint}}} +MODEL_DEPLOYMENT_NAME={{ModelDeploymentName}} \ No newline at end of file diff --git a/samples/hosted-agent/dotnet/workflow/Dockerfile b/samples/hosted-agent/dotnet/workflow/Dockerfile new file mode 100644 index 0000000..31bbacb --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/Dockerfile @@ -0,0 +1,22 @@ +# Build the application +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copy project files for dependency resolution +COPY *.csproj* . +RUN dotnet restore {{SafeProjectName}}.csproj + +# Copy files from the current directory on the host to the working directory in the container +COPY . . + +RUN dotnet publish -c Release -o /app -p:AssemblyName=app + +# Run the application +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app + +# Copy everything needed to run the app from the "build" stage. +COPY --from=build /app . + +EXPOSE 8088 +ENTRYPOINT ["dotnet", "app.dll"] diff --git a/samples/hosted-agent/dotnet/workflow/Program.cs b/samples/hosted-agent/dotnet/workflow/Program.cs new file mode 100644 index 0000000..318f15e --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/Program.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Azure.AI.AgentServer.AgentFramework.Extensions; +using Azure.Core; +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); + + (ChatClientAgent agent, string id)? writer = null; + (ChatClientAgent agent, string id)? reviewer = null; + + 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(); + + var workflow = new WorkflowBuilder(writer.Value.agent) + .AddEdge(writer.Value.agent, reviewer.Value.agent) + .WithOutputFrom(reviewer.Value.agent) + .Build(); + + 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); + + 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(); + + Console.WriteLine($"OpenTelemetry configured. OTLP endpoint: {otlpEndpoint}"); + } +} diff --git a/samples/hosted-agent/dotnet/workflow/README.md b/samples/hosted-agent/dotnet/workflow/README.md new file mode 100644 index 0000000..fde0fb2 --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/README.md @@ -0,0 +1,169 @@ +**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). + +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. + +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. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Multi-agent workflows** - Orchestrate multiple agents working together + +Code-based agents can execute **any C# code** you write. This sample includes a Writer-Reviewer workflow where two agents collaborate: a Writer creates content and a Reviewer provides feedback. + +The agent is hosted using the [Azure AI AgentServer SDK](https://www.nuget.org/packages/Azure.AI.AgentServer.AgentFramework/) and can be deployed to Microsoft Foundry. + +## How It Works + +### Multi-Agent Workflow + +In [Program.cs](Program.cs), the sample creates two agents using `PersistentAgentsClient`: + +- **Writer** - An agent that creates and edits content based on feedback +- **Reviewer** - An agent that provides actionable feedback on the content + +The `WorkflowBuilder` connects these agents in a sequential flow: + +1. The Writer receives the initial request and generates content +2. The Reviewer evaluates the content and provides feedback +3. Both agent responses are output to the user + +### Agent Hosting + +The agent is hosted using the [Azure AI AgentServer SDK](https://www.nuget.org/packages/Azure.AI.AgentServer.AgentFramework/), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Azure AI Foundry Project** + - Project created. + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`) + - Note your project endpoint URL and model deployment name + > **Note**: You can right-click the project in the Microsoft Foundry VS Code extension and select `Copy Project Endpoint URL` to get the endpoint. + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **.NET 10.0 SDK or later** + - Verify your version: `dotnet --version` + - Download from [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download) + +### Environment Variables + +**Foundry VS Code Extension Users:** + +If you created your hosted agent project using the Microsoft Foundry VS Code extension, a `appsettings.Development.json` file is already created in the project root with the necessary environment variables. Double-check that the values are correct. + +**PowerShell:** + +```powershell +# Replace with your actual values +$env:PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +**Bash:** + +```bash +export PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Running the Sample + +To run the agent, execute the following command in your terminal: + +```bash +dotnet restore +dotnet build +dotnet run +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +### Interacting with the Agent + +**VS Code:** + +1. Open the Visual Studio Code Command Palette and execute the `Microsoft Foundry: Open Container Agent Playground Locally` command. +2. Execute the following commands to start the containerized hosted agent. + ```bash + dotnet restore + dotnet build + dotnet run + ``` +3. Submit a request to the agent through the playground interface. For example, you may enter a prompt such as: "Create a slogan for a new electric SUV that is affordable and fun to drive." +4. Review the agent's response in the playground interface. + +> **Note**: Open the local playground before starting the container agent to ensure the visualization functions correctly. + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "Create a slogan for a new electric SUV that is affordable and fun to drive" + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive","stream":false}' +``` + +You can also use the `run-requests.http` file in this directory with the VS Code REST Client extension. + +The Writer agent will generate content based on your prompt, and the Reviewer agent will provide feedback on the output. + +## Deploying the Agent to Microsoft Foundry + +**Preparation (required)** + +Please check the environment_variables section in [agent.yaml](agent.yaml) and ensure the variables there are set in your target Microsoft Foundry Project. + +To deploy the hosted agent: + +1. Open the VS Code Command Palette and run the `Microsoft Foundry: Deploy Hosted Agent` command. + +2. Follow the interactive deployment prompts. The extension will help you select or create the container files it needs. + +3. After deployment completes, the hosted agent appears under the `Hosted Agents (Preview)` section of the extension tree. You can select the agent there to view details and test it using the integrated playground. + +**What the deploy flow does for you:** + +- Creates or obtains an Azure Container Registry for the target project. +- Builds and pushes a container image from your workspace (the build packages the workspace respecting `.dockerignore`). +- Creates an agent version in Microsoft Foundry using the built image. If a `.env` file exists at the workspace root, the extension will parse it and include its key/value pairs as the hosted agent's environment variables in the create request (these variables will be available to the agent runtime). +- Starts the agent container on the project's capability host. If the capability host is not provisioned, the extension will prompt you to enable it and will guide you through creating it. + +## MSI Configuration in the Azure Portal + +This sample requires the Microsoft Foundry Project to authenticate using a Managed Identity when running remotely in Azure. Grant the project's managed identity the required permissions by assigning the built-in [Azure AI User](https://aka.ms/foundry-ext-project-role) role. + +To configure the Managed Identity: + +1. In the Azure Portal, open the Foundry Project. +2. Select "Access control (IAM)" from the left-hand menu. +3. Click "Add" and choose "Add role assignment". +4. In the role selection, search for and select "Azure AI User", then click "Next". +5. For "Assign access to", choose "Managed identity". +6. Click "Select members", locate the managed identity associated with your Foundry Project (you can search by the project name), then click "Select". +7. Click "Review + assign" to complete the assignment. +8. Allow a few minutes for the role assignment to propagate before running the application. + +## Additional Resources + +- [Microsoft Agents Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) +- [Managed Identities for Azure Resources](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/) diff --git a/samples/hosted-agent/dotnet/workflow/agent.yaml b/samples/hosted-agent/dotnet/workflow/agent.yaml new file mode 100644 index 0000000..054f56e --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/agent.yaml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: {{AgentName}} +description: > + A multi-agent workflow featuring a Writer and Reviewer that collaborate + to create and refine content. +metadata: + authors: + - Microsoft + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Multi-Agent Workflow + - Writer-Reviewer + - Content Creation +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: PROJECT_ENDPOINT + value: {{=<% %>=}}"{{PROJECT_ENDPOINT}}"<%={{ }}=%> + - name: MODEL_DEPLOYMENT_NAME + value: {{=<% %>=}}"{{MODEL_DEPLOYMENT_NAME}}"<%={{ }}=%> diff --git a/samples/hosted-agent/dotnet/workflow/appsettings.Development.json b/samples/hosted-agent/dotnet/workflow/appsettings.Development.json new file mode 100644 index 0000000..3545752 --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/appsettings.Development.json @@ -0,0 +1,4 @@ +{ + "PROJECT_ENDPOINT": "{{{AzureAIProjectEndpoint}}}", + "MODEL_DEPLOYMENT_NAME": "{{ModelDeploymentName}}" +} diff --git a/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj b/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj new file mode 100644 index 0000000..d884ae2 --- /dev/null +++ b/samples/hosted-agent/dotnet/workflow/{{SafeProjectName}}.csproj @@ -0,0 +1,30 @@ + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/samples/hosted-agent/python/agent/.dockerignore b/samples/hosted-agent/python/agent/.dockerignore new file mode 100644 index 0000000..779bc67 --- /dev/null +++ b/samples/hosted-agent/python/agent/.dockerignore @@ -0,0 +1,66 @@ +# Virtual environments +.venv/ +venv/ +env/ +.python-version + +# Environment files with secrets +.env +.env.* +*.local + +# Python build artifacts +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Testing +.tox/ +.nox/ +.coverage +.coverage.* +htmlcov/ +.pytest_cache/ +.mypy_cache/ + +# IDE and OS files +.DS_Store +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Foundry config +.foundry/ +build-source-*/ + +# Git +.git/ +.gitignore + +# Docker +.dockerignore + +# Documentation +docs/ +*.md +!README.md +LICENSE diff --git a/samples/hosted-agent/python/agent/.env b/samples/hosted-agent/python/agent/.env new file mode 100644 index 0000000..2ce907d --- /dev/null +++ b/samples/hosted-agent/python/agent/.env @@ -0,0 +1,4 @@ +# IMPORTANT: Never commit .env to version control - add it to .gitignore + +PROJECT_ENDPOINT={{{AzureAIProjectEndpoint}}} +MODEL_DEPLOYMENT_NAME={{ModelDeploymentName}} \ No newline at end of file diff --git a/samples/hosted-agent/python/agent/.vscode/launch.json b/samples/hosted-agent/python/agent/.vscode/launch.json new file mode 100644 index 0000000..fe92f4a --- /dev/null +++ b/samples/hosted-agent/python/agent/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Local Workflow HTTP Server", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5679 + }, + "preLaunchTask": "Open Agent Inspector", + "internalConsoleOptions": "neverOpen", + "postDebugTask": "Terminate All Tasks" + } + ] +} diff --git a/samples/hosted-agent/python/agent/.vscode/tasks.json b/samples/hosted-agent/python/agent/.vscode/tasks.json new file mode 100644 index 0000000..4dbd4ea --- /dev/null +++ b/samples/hosted-agent/python/agent/.vscode/tasks.json @@ -0,0 +1,70 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Validate prerequisites", + "type": "aitk", + "command": "debug-check-prerequisites", + "args": { + "portOccupancy": [5679, 8088] + } + }, + { + "label": "Run Agent/Workflow HTTP Server", + "type": "shell", + "command": "${command:python.interpreterPath} -m debugpy --listen 127.0.0.1:5679 -m agentdev run main.py --verbose --port 8088", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": ["Validate prerequisites"], + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Application startup complete|running on|Started server process" + } + } + }, + { + "label": "Open Agent Inspector", + "type": "shell", + "command": "echo '${input:openAgentInspector}'", + "presentation": { + "reveal": "never" + }, + "dependsOn": ["Run Agent/Workflow HTTP Server"] + }, + { + "label": "Terminate All Tasks", + "command": "echo ${input:terminate}", + "type": "shell", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "openAgentInspector", + "type": "command", + "command": "ai-mlstudio.openTestTool", + "args": { + "triggeredFrom": "tasks", + "port": 8088 + } + }, + { + "id": "terminate", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "terminateAll" + } + ] +} diff --git a/samples/hosted-agent/python/agent/Dockerfile b/samples/hosted-agent/python/agent/Dockerfile new file mode 100644 index 0000000..413c6ac --- /dev/null +++ b/samples/hosted-agent/python/agent/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY ./ . + +RUN if [ -f requirements.txt ]; then \ + pip install --no-cache-dir -r requirements.txt; \ + else \ + echo "No requirements.txt found"; \ + fi + +EXPOSE 8088 + +CMD ["python", "main.py"] diff --git a/samples/hosted-agent/python/agent/README.md b/samples/hosted-agent/python/agent/README.md new file mode 100644 index 0000000..fc6e876 --- /dev/null +++ b/samples/hosted-agent/python/agent/README.md @@ -0,0 +1,195 @@ +**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). + +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. + +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. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Local Python tool execution** - Run custom Python functions as agent tools + +Code-based agents can execute **any Python code** you write. This sample includes a Seattle Hotel Agent with a `get_available_hotels` tool that searches for available hotels based on check-in/check-out dates and budget preferences. + +The agent is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/) and can be deployed to Microsoft Foundry. + +## How It Works + +### Local Tools Integration + +In [main.py](main.py), the agent uses a local Python function (`get_available_hotels`) that simulates a hotel availability API. This demonstrates how code-based agents can execute custom server-side logic that prompt agents cannot access. + +The tool accepts: + +- **check_in_date** - Check-in date in YYYY-MM-DD format +- **check_out_date** - Check-out date in YYYY-MM-DD format +- **max_price** - Maximum price per night in USD (optional, defaults to $500) + +### Agent Hosting + +The agent is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Microsoft Foundry Project** + - A Microsoft Project created. + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`). + - Note your project endpoint URL and model deployment name. + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **Python 3.10 or higher** + - Verify your version: `python --version` + - If you have Python 3.9 or older, install a newer version: + - Windows: `winget install Python.Python.3.12` + - macOS: `brew install python@3.12` + - Linux: Use your package manager + +### Environment Variables + +Set the following environment variables: + +- `PROJECT_ENDPOINT` - Your Microsoft Foundry project endpoint URL (required) +- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4.1-mini`) + +This sample loads environment variables from a local `.env` file if present. + +Create a `.env` file in this directory with the following content: + +``` +PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +MODEL_DEPLOYMENT_NAME=gpt-4.1-mini +``` + +Or set them via PowerShell: + +```powershell +# Replace with your actual values +$env:PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Setting Up a Virtual Environment + +It's recommended to use a virtual environment to isolate project dependencies: + +**macOS/Linux:** + +```bash +python -m venv .venv +source .venv/bin/activate +``` + +**Windows (PowerShell):** + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +``` + +### Installing Dependencies + +Install the required Python dependencies using pip: + +```bash +pip install -r requirements.txt +``` + +The required packages are: + +- `azure-ai-agentserver-agentframework` - Agent Framework and AgentServer SDK + +### Running the Sample + +#### Option 1: Press F5 (Recommended) + +Press **F5** in VS Code to start debugging. Alternatively, you can use the VS Code debug menu: + +1. Open the **Run and Debug** view (Ctrl+Shift+D / Cmd+Shift+D) +2. Select **"Debug Local Workflow HTTP Server"** from the dropdown +3. Click the green **Start Debugging** button (or press F5) + +This will: + +1. Start the HTTP server with debugging enabled +2. Open the AI Toolkit Agent Inspector for interactive testing +3. Allow you to set breakpoints and inspect the workflow + +#### Option 2: Run in Terminal + +Run as HTTP server (default): + +```bash +python main.py +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "I need a hotel in Seattle from 2025-03-15 to 2025-03-18, budget under `$200 per night" + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Find me hotels in Seattle for March 20-23, 2025 under $200 per night","stream":false}' +``` + +The agent will use the `get_available_hotels` tool to search for available hotels matching your criteria. + +## Deploying the Agent to Microsoft Foundry + +To deploy the hosted agent: + +1. Open the VS Code Command Palette and run the `Microsoft Foundry: Deploy Hosted Agent` command. + +2. Follow the interactive deployment prompts. The extension will help you select or create the container files it needs: + - It first looks for a Dockerfile at the repository root. If not found, you can select an existing Dockerfile or generate a new one. + - If you choose to generate a Dockerfile, the extension will place the files at the repo root and open the Dockerfile in the editor; the deployment flow is intentionally cancelled in that case so you can review and edit the generated files before re-running the deploy command. + +3. After deployment completes, the hosted agent appears under the `Hosted Agents (Preview)` section of the extension tree. You can select the agent there to view details and test it using the integrated playground. + +**What the deploy flow does for you:** + +- Creates or obtains an Azure Container Registry for the target project. +- Builds and pushes a container image from your workspace (the build packages the workspace respecting `.dockerignore`). +- Creates an agent version in Microsoft Foundry using the built image. If a `.env` file exists at the workspace root, the extension will parse it and include its key/value pairs as the hosted agent's environment variables in the create request (these variables will be available to the agent runtime). +- Starts the agent container on the project's capability host. If the capability host is not provisioned, the extension will prompt you to enable it and will guide you through creating it. + +### MSI Configuration in the Azure Portal + +This sample requires the Microsoft Foundry Project to authenticate using a Managed Identity when running remotely in Azure. Grant the project's managed identity the required permissions by assigning the built-in [Azure AI User](https://aka.ms/foundry-ext-project-role) role. + +To configure the Managed Identity: + +1. In the Azure Portal, open the Foundry Project. +2. Select "Access control (IAM)" from the left-hand menu. +3. Click "Add" and choose "Add role assignment". +4. In the role selection, search for and select "Azure AI User", then click "Next". +5. For "Assign access to", choose "Managed identity". +6. Click "Select members", locate the managed identity associated with your Foundry Project (you can search by the project name), then click "Select". +7. Click "Review + assign" to complete the assignment. +8. Allow a few minutes for the role assignment to propagate before running the application. + +## Additional Resources + +- [Microsoft Agents Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) +- [Managed Identities for Azure Resources](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/) diff --git a/samples/hosted-agent/python/agent/agent.yaml b/samples/hosted-agent/python/agent/agent.yaml new file mode 100644 index 0000000..89e57e5 --- /dev/null +++ b/samples/hosted-agent/python/agent/agent.yaml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: {{AgentName}} +description: > + A travel assistant agent that helps users find hotels in Seattle. + Demonstrates local Python tool execution - a key advantage of code-based + hosted agents over prompt agents. +metadata: + authors: + - Microsoft + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Local Tools + - Travel Assistant + - Hotel Search +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: PROJECT_ENDPOINT + value: {{=<% %>=}}"{{PROJECT_ENDPOINT}}"<%={{ }}=%> + - name: MODEL_DEPLOYMENT_NAME + value: {{=<% %>=}}"{{MODEL_DEPLOYMENT_NAME}}"<%={{ }}=%> \ No newline at end of file diff --git a/samples/hosted-agent/python/agent/main.py b/samples/hosted-agent/python/agent/main.py new file mode 100644 index 0000000..19acbdd --- /dev/null +++ b/samples/hosted-agent/python/agent/main.py @@ -0,0 +1,149 @@ +""" +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. +""" + +import asyncio +import os +from datetime import datetime +from typing import Annotated + +from dotenv import load_dotenv + +load_dotenv(override=True) + +from agent_framework.azure import AzureAIAgentClient +from azure.ai.agentserver.agentframework import from_agent_framework +from azure.identity.aio import DefaultAzureCredential + +# Configure these for your Foundry project +# Read the explicit variables present in the .env file +PROJECT_ENDPOINT = os.getenv( + "PROJECT_ENDPOINT" +) # e.g., "https://.services.ai.azure.com" +MODEL_DEPLOYMENT_NAME = os.getenv( + "MODEL_DEPLOYMENT_NAME", "gpt-4.1-mini" +) # Your model deployment name e.g., "gpt-4.1-mini" + + +# Simulated hotel data for Seattle +SEATTLE_HOTELS = [ + { + "name": "Contoso Suites", + "price_per_night": 189, + "rating": 4.5, + "location": "Downtown", + }, + { + "name": "Fabrikam Residences", + "price_per_night": 159, + "rating": 4.2, + "location": "Pike Place Market", + }, + { + "name": "Alpine Ski House", + "price_per_night": 249, + "rating": 4.7, + "location": "Seattle Center", + }, + { + "name": "Margie's Travel Lodge", + "price_per_night": 219, + "rating": 4.4, + "location": "Waterfront", + }, + { + "name": "Northwind Inn", + "price_per_night": 139, + "rating": 4.0, + "location": "Capitol Hill", + }, + { + "name": "Relecloud Hotel", + "price_per_night": 99, + "rating": 3.8, + "location": "University District", + }, +] + + +def get_available_hotels( + check_in_date: Annotated[str, "Check-in date in YYYY-MM-DD format"], + check_out_date: Annotated[str, "Check-out date in YYYY-MM-DD format"], + max_price: Annotated[int, "Maximum price per night in USD (optional)"] = 500, +) -> str: + """ + Get available hotels in Seattle for the specified dates. + This simulates a call to a fake hotel availability API. + """ + try: + # Parse dates + check_in = datetime.strptime(check_in_date, "%Y-%m-%d") + check_out = datetime.strptime(check_out_date, "%Y-%m-%d") + + # Validate dates + if check_out <= check_in: + return "Error: Check-out date must be after check-in date." + + nights = (check_out - check_in).days + + # Filter hotels by price + available_hotels = [ + hotel for hotel in SEATTLE_HOTELS if hotel["price_per_night"] <= max_price + ] + + if not available_hotels: + return ( + f"No hotels found in Seattle within your budget of ${max_price}/night." + ) + + # Build response + result = f"Available hotels in Seattle from {check_in_date} to {check_out_date} ({nights} nights):\n\n" + + for hotel in available_hotels: + total_cost = hotel["price_per_night"] * nights + result += f"**{hotel['name']}**\n" + result += f" Location: {hotel['location']}\n" + result += f" Rating: {hotel['rating']}/5\n" + result += f" ${hotel['price_per_night']}/night (Total: ${total_cost})\n\n" + + return result + + except ValueError as e: + return f"Error parsing dates. Please use YYYY-MM-DD format. Details: {str(e)}" + + +async def main(): + """Main function to run the agent as a web server.""" + async with ( + DefaultAzureCredential() as credential, + AzureAIAgentClient( + project_endpoint=PROJECT_ENDPOINT, + model_deployment_name=MODEL_DEPLOYMENT_NAME, + credential=credential, + ) as client, + ): + agent = client.create_agent( + name="SeattleHotelAgent", + 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 get_available_hotels 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=[get_available_hotels], + ) + + print("Seattle Hotel Agent Server running on http://localhost:8088") + server = from_agent_framework(agent) + await server.run_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/samples/hosted-agent/python/agent/requirements.txt b/samples/hosted-agent/python/agent/requirements.txt new file mode 100644 index 0000000..e2e7cd2 --- /dev/null +++ b/samples/hosted-agent/python/agent/requirements.txt @@ -0,0 +1,4 @@ +azure-ai-agentserver-agentframework==1.0.0b14 +debugpy +# Agent development CLI tool (preview) +agent-dev-cli \ No newline at end of file diff --git a/samples/hosted-agent/python/minimal/.dockerignore b/samples/hosted-agent/python/minimal/.dockerignore new file mode 100644 index 0000000..79cc807 --- /dev/null +++ b/samples/hosted-agent/python/minimal/.dockerignore @@ -0,0 +1,51 @@ +# Build artifacts +bin/ +obj/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +.foundry/ + +# Source control +.git/ + +# Documentation +README.md + +# Ignore files +.gitignore +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Local development config +appsettings.Development.json +.env + +.venv/ +__pycache__/ diff --git a/samples/hosted-agent/python/minimal/Dockerfile b/samples/hosted-agent/python/minimal/Dockerfile new file mode 100644 index 0000000..413c6ac --- /dev/null +++ b/samples/hosted-agent/python/minimal/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY ./ . + +RUN if [ -f requirements.txt ]; then \ + pip install --no-cache-dir -r requirements.txt; \ + else \ + echo "No requirements.txt found"; \ + fi + +EXPOSE 8088 + +CMD ["python", "main.py"] diff --git a/samples/hosted-agent/python/workflow/.dockerignore b/samples/hosted-agent/python/workflow/.dockerignore new file mode 100644 index 0000000..79cc807 --- /dev/null +++ b/samples/hosted-agent/python/workflow/.dockerignore @@ -0,0 +1,51 @@ +# Build artifacts +bin/ +obj/ + +# IDE and editor files +.vs/ +.vscode/ +*.user +*.suo +.foundry/ + +# Source control +.git/ + +# Documentation +README.md + +# Ignore files +.gitignore +.dockerignore + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +# OS files +.DS_Store +Thumbs.db + +# Package manager directories +node_modules/ +packages/ + +# Test results +TestResults/ +*.trx + +# Coverage reports +coverage/ +*.coverage +*.coveragexml + +# Local development config +appsettings.Development.json +.env + +.venv/ +__pycache__/ diff --git a/samples/hosted-agent/python/workflow/.env b/samples/hosted-agent/python/workflow/.env new file mode 100644 index 0000000..2ce907d --- /dev/null +++ b/samples/hosted-agent/python/workflow/.env @@ -0,0 +1,4 @@ +# IMPORTANT: Never commit .env to version control - add it to .gitignore + +PROJECT_ENDPOINT={{{AzureAIProjectEndpoint}}} +MODEL_DEPLOYMENT_NAME={{ModelDeploymentName}} \ No newline at end of file diff --git a/samples/hosted-agent/python/workflow/.vscode/launch.json b/samples/hosted-agent/python/workflow/.vscode/launch.json new file mode 100644 index 0000000..fe92f4a --- /dev/null +++ b/samples/hosted-agent/python/workflow/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Local Workflow HTTP Server", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5679 + }, + "preLaunchTask": "Open Agent Inspector", + "internalConsoleOptions": "neverOpen", + "postDebugTask": "Terminate All Tasks" + } + ] +} diff --git a/samples/hosted-agent/python/workflow/.vscode/tasks.json b/samples/hosted-agent/python/workflow/.vscode/tasks.json new file mode 100644 index 0000000..4dbd4ea --- /dev/null +++ b/samples/hosted-agent/python/workflow/.vscode/tasks.json @@ -0,0 +1,70 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Validate prerequisites", + "type": "aitk", + "command": "debug-check-prerequisites", + "args": { + "portOccupancy": [5679, 8088] + } + }, + { + "label": "Run Agent/Workflow HTTP Server", + "type": "shell", + "command": "${command:python.interpreterPath} -m debugpy --listen 127.0.0.1:5679 -m agentdev run main.py --verbose --port 8088", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": ["Validate prerequisites"], + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Application startup complete|running on|Started server process" + } + } + }, + { + "label": "Open Agent Inspector", + "type": "shell", + "command": "echo '${input:openAgentInspector}'", + "presentation": { + "reveal": "never" + }, + "dependsOn": ["Run Agent/Workflow HTTP Server"] + }, + { + "label": "Terminate All Tasks", + "command": "echo ${input:terminate}", + "type": "shell", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "openAgentInspector", + "type": "command", + "command": "ai-mlstudio.openTestTool", + "args": { + "triggeredFrom": "tasks", + "port": 8088 + } + }, + { + "id": "terminate", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "terminateAll" + } + ] +} diff --git a/samples/hosted-agent/python/workflow/Dockerfile b/samples/hosted-agent/python/workflow/Dockerfile new file mode 100644 index 0000000..413c6ac --- /dev/null +++ b/samples/hosted-agent/python/workflow/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY ./ . + +RUN if [ -f requirements.txt ]; then \ + pip install --no-cache-dir -r requirements.txt; \ + else \ + echo "No requirements.txt found"; \ + fi + +EXPOSE 8088 + +CMD ["python", "main.py"] diff --git a/samples/hosted-agent/python/workflow/README.md b/samples/hosted-agent/python/workflow/README.md new file mode 100644 index 0000000..3fd239e --- /dev/null +++ b/samples/hosted-agent/python/workflow/README.md @@ -0,0 +1,204 @@ +**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). + +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. + +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. + +Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output. + +# What this sample demonstrates + +This sample demonstrates a **key advantage of code-based hosted agents**: + +- **Multi-agent workflows** - Orchestrate multiple agents working together + +Code-based agents can execute **any Python code** you write. This sample includes a **Writer-Reviewer workflow** where two agents collaborate: a Writer creates content and a Reviewer provides feedback. + +The agent is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/) and can be deployed to Microsoft Foundry. + +## How It Works + +### Multi-Agent Workflow + +In [main.py](main.py), the sample creates two agents: + +- **Writer** - An agent that creates and edits content based on feedback +- **Reviewer** - An agent that provides actionable feedback on the content + +The `WorkflowBuilder` connects these agents in a sequential flow: + +1. The Writer receives the initial request and generates content +2. The Reviewer evaluates the content and provides feedback +3. Both agent responses are output to the user. + +### Agent Hosting + +The agent is hosted using the [Azure AI AgentServer SDK](https://pypi.org/project/azure-ai-agentserver-agentframework/), +which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +## Running the Agent Locally + +### Prerequisites + +Before running this sample, ensure you have: + +1. **Microsoft Foundry Project** + - A Microsoft Project created. + - Chat model deployed (e.g., `gpt-4o` or `gpt-4.1`). + - Note your project endpoint URL and model deployment name. + +2. **Azure CLI** + - Installed and authenticated + - Run `az login` and verify with `az account show` + +3. **Python 3.10 or higher** + - Verify your version: `python --version` + - If you have Python 3.9 or older, install a newer version: + - Windows: `winget install Python.Python.3.12` + - macOS: `brew install python@3.12` + - Linux: Use your package manager + +### Environment Variables + +Set the following environment variables: + +- `PROJECT_ENDPOINT` - Your Microsoft Foundry project endpoint URL (required) +- `MODEL_DEPLOYMENT_NAME` - The deployment name for your chat model (defaults to `gpt-4.1-mini`) + +This sample loads environment variables from a local `.env` file if present. + +Create a `.env` file in this directory with the following content: + +``` +PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +MODEL_DEPLOYMENT_NAME=gpt-4.1-mini +``` + +Or set them via PowerShell: + +```powershell +# Replace with your actual values +$env:PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +$env:MODEL_DEPLOYMENT_NAME="gpt-4.1-mini" +``` + +### Setting Up a Virtual Environment + +It's recommended to use a virtual environment to isolate project dependencies: + +**macOS/Linux:** + +```bash +python -m venv .venv +source .venv/bin/activate +``` + +**Windows (PowerShell):** + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +``` + +### Installing Dependencies + +Install the required Python dependencies using pip: + +```bash +pip install -r requirements.txt +``` + +The required packages are: + +- `azure-ai-agentserver-agentframework` - Agent Framework and AgentServer SDK + +### Running the Sample + +### Option 1: Press F5 (Recommended) + +Press **F5** in VS Code to start debugging. Alternatively, you can use the VS Code debug menu: + +1. Open the **Run and Debug** view (Ctrl+Shift+D / Cmd+Shift+D) +2. Select **"Debug Local Workflow HTTP Server"** from the dropdown +3. Click the green **Start Debugging** button (or press F5) + +This will: + +1. Start the HTTP server with debugging enabled +2. Open the AI Toolkit Agent Inspector for interactive testing +3. Allow you to set breakpoints and inspect the workflow + +### Option 2: Run in Terminal + +Run as HTTP server (default): + +```bash +python main.py +``` + +This will start the hosted agent locally on `http://localhost:8088/`. + +**PowerShell (Windows):** + +```powershell +$body = @{ + input = "Create a slogan for a new electric SUV that is affordable and fun to drive" + stream = $false +} | ConvertTo-Json + +Invoke-RestMethod -Uri http://localhost:8088/responses -Method Post -Body $body -ContentType "application/json" +``` + +**Bash/curl (Linux/macOS):** + +```bash +curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses \ + -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive","stream":false}' +``` + +The agent will respond with both the Writer's slogan and the Reviewer's feedback. + +Or run in CLI mode for quick testing: + +```bash +python main.py --cli +``` + +## Deploying the Agent to Microsoft Foundry + +To deploy the hosted agent: + +1. Open the VS Code Command Palette and run the `Microsoft Foundry: Deploy Hosted Agent` command. + +2. Follow the interactive deployment prompts. The extension will help you select or create the container files it needs: + - It first looks for a Dockerfile at the repository root. If not found, you can select an existing Dockerfile or generate a new one. + - If you choose to generate a Dockerfile, the extension will place the files at the repo root and open the Dockerfile in the editor; the deployment flow is intentionally cancelled in that case so you can review and edit the generated files before re-running the deploy command. + +3. After deployment completes, the hosted agent appears under the `Hosted Agents (Preview)` section of the extension tree. You can select the agent there to view details and test it using the integrated playground. + +**What the deploy flow does for you:** + +- Creates or obtains an Azure Container Registry for the target project. +- Builds and pushes a container image from your workspace (the build packages the workspace respecting `.dockerignore`). +- Creates an agent version in Microsoft Foundry using the built image. If a `.env` file exists at the workspace root, the extension will parse it and include its key/value pairs as the hosted agent's environment variables in the create request (these variables will be available to the agent runtime). +- Starts the agent container on the project's capability host. If the capability host is not provisioned, the extension will prompt you to enable it and will guide you through creating it. + +### MSI Configuration in the Azure Portal + +This sample requires the Microsoft Foundry Project to authenticate using a Managed Identity when running remotely in Azure. Grant the project's managed identity the required permissions by assigning the built-in [Azure AI User](https://aka.ms/foundry-ext-project-role) role. + +To configure the Managed Identity: + +1. In the Azure Portal, open the Foundry Project. +2. Select "Access control (IAM)" from the left-hand menu. +3. Click "Add" and choose "Add role assignment". +4. In the role selection, search for and select "Azure AI User", then click "Next". +5. For "Assign access to", choose "Managed identity". +6. Click "Select members", locate the managed identity associated with your Foundry Project (you can search by the project name), then click "Select". +7. Click "Review + assign" to complete the assignment. +8. Allow a few minutes for the role assignment to propagate before running the application. + +## Additional Resources + +- [Microsoft Agents Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) +- [Managed Identities for Azure Resources](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/) diff --git a/samples/hosted-agent/python/workflow/agent.yaml b/samples/hosted-agent/python/workflow/agent.yaml new file mode 100644 index 0000000..054f56e --- /dev/null +++ b/samples/hosted-agent/python/workflow/agent.yaml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: {{AgentName}} +description: > + A multi-agent workflow featuring a Writer and Reviewer that collaborate + to create and refine content. +metadata: + authors: + - Microsoft + tags: + - Azure AI AgentServer + - Microsoft Agent Framework + - Multi-Agent Workflow + - Writer-Reviewer + - Content Creation +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: PROJECT_ENDPOINT + value: {{=<% %>=}}"{{PROJECT_ENDPOINT}}"<%={{ }}=%> + - name: MODEL_DEPLOYMENT_NAME + value: {{=<% %>=}}"{{MODEL_DEPLOYMENT_NAME}}"<%={{ }}=%> diff --git a/samples/hosted-agent/python/workflow/main.py b/samples/hosted-agent/python/workflow/main.py new file mode 100644 index 0000000..6365887 --- /dev/null +++ b/samples/hosted-agent/python/workflow/main.py @@ -0,0 +1,107 @@ +import asyncio +import os +import sys +from contextlib import asynccontextmanager + +from agent_framework import WorkflowBuilder +from agent_framework.azure import AzureAIAgentClient +from azure.identity.aio import DefaultAzureCredential, ManagedIdentityCredential +from dotenv import load_dotenv + +load_dotenv(override=True) + +# Configure these for your Foundry project +# Read the explicit variables present in the .env file +PROJECT_ENDPOINT = os.getenv( + "PROJECT_ENDPOINT" +) # e.g., "https://.services.ai.azure.com" +MODEL_DEPLOYMENT_NAME = os.getenv( + "MODEL_DEPLOYMENT_NAME", "gpt-4.1-mini" +) # Your model deployment name e.g., "gpt-4.1-mini" + + +def get_credential(): + """Will use Managed Identity when running in Azure, otherwise falls back to DefaultAzureCredential.""" + return ( + ManagedIdentityCredential() + if os.getenv("MSI_ENDPOINT") + else DefaultAzureCredential() + ) + + +@asynccontextmanager +async def create_agents(): + async with ( + get_credential() as credential, + AzureAIAgentClient( + project_endpoint=PROJECT_ENDPOINT, + model_deployment_name=MODEL_DEPLOYMENT_NAME, + credential=credential, + ) as writer_client, + AzureAIAgentClient( + project_endpoint=PROJECT_ENDPOINT, + model_deployment_name=MODEL_DEPLOYMENT_NAME, + credential=credential, + ) as reviewer_client, + ): + writer = writer_client.create_agent( + name="Writer", + instructions="You are an excellent content writer. You create new content and edit contents based on the feedback.", + ) + reviewer = reviewer_client.create_agent( + name="Reviewer", + instructions="You are an excellent content reviewer. Provide actionable feedback to the writer about the provided content in the most concise manner possible.", + ) + yield writer, reviewer + + +def create_workflow(writer, reviewer): + workflow = ( + WorkflowBuilder(name="Writer-Reviewer") + .register_agent(lambda: writer, name="Writer", output_response=True) + .register_agent(lambda: reviewer, name="Reviewer", output_response=True) + .set_start_executor("Writer") + .add_edge("Writer", "Reviewer") + .build() + ) + return workflow.as_agent() + + +async def main() -> None: + """ + The writer and reviewer multi-agent workflow. + + Usage: + python main.py # Run in server mode + + Environment variables required: + - PROJECT_ENDPOINT: Your Microsoft Foundry project endpoint + - MODEL_DEPLOYMENT_NAME: Your Microsoft Foundry model deployment name + """ + + async with create_agents() as (writer, reviewer): + agent = create_workflow(writer, reviewer) + + # Check if running in CLI mode (default is server mode) + if "--cli" in sys.argv: + # CLI mode for testing + print("Running workflow agent in CLI mode...") + + # Test with a sample query + user_message = "Create a slogan for a new electric SUV that is affordable and fun to drive." + print(f"\nUser: {user_message}\n") + + response = await agent.run(user_message) + for msg in response.messages: + if msg.text: + print(f"{msg.author_name}: {msg.text}\n") + else: + # Server mode (default) + print("Starting workflow agent HTTP server...") + from azure.ai.agentserver.agentframework import from_agent_framework + + await from_agent_framework(agent).run_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/samples/hosted-agent/python/workflow/requirements.txt b/samples/hosted-agent/python/workflow/requirements.txt new file mode 100644 index 0000000..e2e7cd2 --- /dev/null +++ b/samples/hosted-agent/python/workflow/requirements.txt @@ -0,0 +1,4 @@ +azure-ai-agentserver-agentframework==1.0.0b14 +debugpy +# Agent development CLI tool (preview) +agent-dev-cli \ No newline at end of file diff --git a/samples/hosted-agent/version-manifest.json b/samples/hosted-agent/version-manifest.json new file mode 100644 index 0000000..cabf8eb --- /dev/null +++ b/samples/hosted-agent/version-manifest.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": "1.0", + "lastUpdated": "2026-02-28T10:00:00Z", + "channels": { + "stable": { + "branch": "stable", + "sampleVersion": "v1.0.0", + "lang": ["dotnet", "python"], + "downloadUrl": "https://github.com/microsoft/ai-foundry-for-vscode/releases/download/v1.0.0/{{lang}}.zip", + "lastUpdated": "2026-02-28T10:00:00Z" + } + } +}