diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 8b1b00fd2b..91aa267eb6 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -229,6 +229,7 @@ + diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs new file mode 100644 index 0000000000..db50333697 --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Workflows; +using Microsoft.Extensions.AI; + +namespace WorkflowGroupChatToolApprovalSample; + +/// +/// Custom GroupChatManager that selects the next speaker based on the conversation flow. +/// +/// +/// This simple selector follows a predefined flow: +/// 1. QA Engineer runs tests +/// 2. DevOps Engineer checks staging and creates rollback plan +/// 3. DevOps Engineer deploys to production (triggers approval) +/// +internal sealed class DeploymentGroupChatManager : GroupChatManager +{ + private readonly IReadOnlyList _agents; + + public DeploymentGroupChatManager(IReadOnlyList agents) + { + this._agents = agents; + } + + protected override ValueTask SelectNextAgentAsync( + IReadOnlyList history, + CancellationToken cancellationToken = default) + { + if (history.Count == 0) + { + throw new InvalidOperationException("Conversation is empty; cannot select next speaker."); + } + + // First speaker after initial user message + if (this.IterationCount == 0) + { + AIAgent qaAgent = this._agents.First(a => a.Name == "QAEngineer"); + return new ValueTask(qaAgent); + } + + // Subsequent speakers are DevOps Engineer + AIAgent devopsAgent = this._agents.First(a => a.Name == "DevOpsEngineer"); + return new ValueTask(devopsAgent); + } +} diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj new file mode 100644 index 0000000000..d0c0656ade --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + + enable + enable + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs new file mode 100644 index 0000000000..267c6d7ce5 --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample demonstrates how to use GroupChatBuilder with tools that require human +// approval before execution. A group of specialized agents collaborate on a task, and +// sensitive tool calls trigger human-in-the-loop approval. +// +// This sample works as follows: +// 1. A GroupChatBuilder workflow is created with multiple specialized agents. +// 2. A custom manager determines which agent speaks next based on conversation state. +// 3. Agents collaborate on a software deployment task. +// 4. When the deployment agent tries to deploy to production, it triggers an approval request. +// 5. The sample simulates human approval and the workflow completes. +// +// Purpose: +// Show how tool call approvals integrate with multi-agent group chat workflows where +// different agents have different levels of tool access. +// +// Demonstrate: +// - Using custom GroupChatManager with agents that have approval-required tools. +// - Handling FunctionApprovalRequestContent in group chat scenarios. +// - Multi-round group chat with tool approval interruption and resumption. + +using System.ComponentModel; +using System.Text.Json; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Workflows; +using Microsoft.Extensions.AI; + +namespace WorkflowGroupChatToolApprovalSample; + +/// +/// This sample demonstrates how to use GroupChatBuilder with tools that require human +/// approval before execution. +/// +/// +/// Pre-requisites: +/// - An Azure OpenAI chat completion deployment must be configured. +/// +public static class Program +{ + private static async Task Main() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); + var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + + // 1. Create AI client + IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) + .GetChatClient(deploymentName) + .AsIChatClient(); + + // 2. Create specialized agents with their tools + ChatClientAgent qaEngineer = new( + client, + "You are a QA engineer responsible for running tests before deployment. Run the appropriate test suites and report results clearly.", + "QAEngineer", + "QA engineer who runs tests", + [AIFunctionFactory.Create(RunTests)]); + + ChatClientAgent devopsEngineer = new( + client, + "You are a DevOps engineer responsible for deployments. First check staging status and create a rollback plan, then proceed with production deployment. Always ensure safety measures are in place before deploying.", + "DevOpsEngineer", + "DevOps engineer who handles deployments", + [ + AIFunctionFactory.Create(CheckStagingStatus), + AIFunctionFactory.Create(CreateRollbackPlan), + new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction)) + ]); + + // 3. Create custom GroupChatManager with speaker selection logic + DeploymentGroupChatManager manager = new([qaEngineer, devopsEngineer]) + { + MaximumIterationCount = 4 // Limit to 4 rounds + }; + + // 4. Build a group chat workflow with the custom manager + Workflow workflow = AgentWorkflowBuilder + .CreateGroupChatBuilderWith(_ => manager) + .AddParticipants(qaEngineer, devopsEngineer) + .Build(); + + // 5. Start the workflow + Console.WriteLine("Starting group chat workflow for software deployment..."); + Console.WriteLine($"Agents: [{qaEngineer.Name}, {devopsEngineer.Name}]"); + Console.WriteLine(new string('-', 60)); + + List messages = [new(ChatRole.User, "We need to deploy version 2.4.0 to production. Please coordinate the deployment.")]; + + await using StreamingRun run = await InProcessExecution.Lockstep.StreamAsync(workflow, messages); + await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); + + string? lastExecutorId = null; + await foreach (WorkflowEvent evt in run.WatchStreamAsync()) + { + switch (evt) + { + case RequestInfoEvent e: + { + if (e.Request.DataIs(out FunctionApprovalRequestContent? approvalRequestContent)) + { + Console.WriteLine(); + Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.Request.PortInfo.PortId}"); + Console.WriteLine($" Tool: {approvalRequestContent.FunctionCall.Name}"); + Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequestContent.FunctionCall.Arguments)}"); + Console.WriteLine(); + + // Approve the tool call request + Console.WriteLine($"Tool: {approvalRequestContent.FunctionCall.Name} approved"); + await run.SendResponseAsync(e.Request.CreateResponse(approvalRequestContent.CreateResponse(approved: true))); + } + + break; + } + + case AgentResponseUpdateEvent e: + { + if (e.ExecutorId != lastExecutorId) + { + if (lastExecutorId is not null) + { + Console.WriteLine(); + } + + Console.WriteLine($"- {e.ExecutorId}: "); + lastExecutorId = e.ExecutorId; + } + + Console.Write(e.Update.Text); + + break; + } + } + } + + Console.WriteLine(); + Console.WriteLine(new string('-', 60)); + Console.WriteLine("Deployment workflow completed successfully!"); + Console.WriteLine("All agents have finished their tasks."); + } + + // Tool definitions - These are called by the agents during workflow execution + [Description("Run automated tests for the application.")] + private static string RunTests([Description("Name of the test suite to run")] string testSuite) + => $"Test suite '{testSuite}' completed: 47 passed, 0 failed, 0 skipped"; + + [Description("Check the current status of the staging environment.")] + private static string CheckStagingStatus() + => "Staging environment: Healthy, Version 2.3.0 deployed, All services running"; + + [Description("Deploy specified components to production. Requires human approval.")] + private static string DeployToProduction( + [Description("The version to deploy")] string version, + [Description("Comma-separated list of components to deploy")] string components) + => $"Production deployment complete: Version {version}, Components: {components}"; + + [Description("Create a rollback plan for the deployment.")] + private static string CreateRollbackPlan([Description("The version being deployed")] string version) + => $"Rollback plan created for version {version}: Automated rollback to v2.2.0 if health checks fail within 5 minutes"; +} diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md new file mode 100644 index 0000000000..84c6baa83c --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md @@ -0,0 +1,70 @@ +# Group Chat with Tool Approval Sample + +This sample demonstrates how to use `GroupChatBuilder` with tools that require human approval before execution. A group of specialized agents collaborate on a task, and sensitive tool calls trigger human-in-the-loop approval. + +## What This Sample Demonstrates + +- Using a custom `GroupChatManager` with agents that have approval-required tools +- Handling `FunctionApprovalRequestContent` in group chat scenarios +- Multi-round group chat with tool approval interruption and resumption +- Integrating tool call approvals with multi-agent workflows where different agents have different levels of tool access + +## How It Works + +1. A `GroupChatBuilder` workflow is created with multiple specialized agents +2. A custom `DeploymentGroupChatManager` determines which agent speaks next based on conversation state +3. Agents collaborate on a software deployment task: + - **QA Engineer**: Runs automated tests + - **DevOps Engineer**: Checks staging status, creates rollback plan, and deploys to production +4. When the deployment agent tries to deploy to production, it triggers an approval request +5. The sample simulates human approval and the workflow completes + +## Key Components + +### Approval-Required Tools + +The `DeployToProduction` function is wrapped with `ApprovalRequiredAIFunction` to require human approval: + +```csharp +new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction)) +``` + +### Custom Group Chat Manager + +The `DeploymentGroupChatManager` implements custom speaker selection logic: +- First iteration: QA Engineer runs tests +- Subsequent iterations: DevOps Engineer handles deployment tasks + +### Approval Handling + +The sample demonstrates continuous event-driven execution with inline approval handling: +- The workflow runs in a single event loop. +- When an approval-required tool is invoked, the loop surfaces an approval request, processes the (simulated) human response, and then continues execution without starting a separate phase. + +## Prerequisites + +- Azure OpenAI or OpenAI configured with the required environment variables +- `AZURE_OPENAI_ENDPOINT` environment variable set +- `AZURE_OPENAI_DEPLOYMENT_NAME` environment variable (defaults to "gpt-4o-mini") + +## Running the Sample + +```bash +dotnet run +``` + +## Expected Output + +The sample will show: +1. QA Engineer running tests +2. DevOps Engineer checking staging and creating rollback plan +3. An approval request for production deployment +4. Simulated approval response +5. DevOps Engineer completing the deployment +6. Workflow completion message + +## Related Samples + +- [Agent Function Tools with Approvals](../../../Agents/Agent_Step04_UsingFunctionToolsWithApprovals) - Basic function approval pattern +- [Agent Workflow Patterns](../../_Foundational/04_AgentWorkflowPatterns) - Group chat without approvals +- [Human-in-the-Loop Basic](../../HumanInTheLoop/HumanInTheLoopBasic) - Workflow-level human interaction diff --git a/dotnet/samples/GettingStarted/Workflows/README.md b/dotnet/samples/GettingStarted/Workflows/README.md index 072acfa560..f7704d0e42 100644 --- a/dotnet/samples/GettingStarted/Workflows/README.md +++ b/dotnet/samples/GettingStarted/Workflows/README.md @@ -32,6 +32,7 @@ Once completed, please proceed to other samples listed below. | [Foundry Agents in Workflows](./Agents/FoundryAgent) | Demonstrates using Azure Foundry Agents within a workflow | | [Custom Agent Executors](./Agents/CustomAgentExecutors) | Shows how to create a custom agent executor for more complex scenarios | | [Workflow as an Agent](./Agents/WorkflowAsAnAgent) | Illustrates how to encapsulate a workflow as an agent | +| [Group Chat with Tool Approval](./Agents/GroupChatToolApproval) | Shows multi-agent group chat with tool approval requests and human-in-the-loop interaction | ### Concurrent Execution