Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
<Folder Name="/Samples/GettingStarted/Workflows/Agents/">
<Project Path="samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj" />
<Project Path="samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj" />
<Project Path="samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj" />
<Project Path="samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/Checkpoint/">
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Custom GroupChatManager that selects the next speaker based on the conversation flow.
/// </summary>
/// <remarks>
/// 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)
/// </remarks>
internal sealed class DeploymentGroupChatManager : GroupChatManager
{
private readonly IReadOnlyList<AIAgent> _agents;

public DeploymentGroupChatManager(IReadOnlyList<AIAgent> agents)
{
this._agents = agents;
}

protected override ValueTask<AIAgent> SelectNextAgentAsync(
IReadOnlyList<ChatMessage> 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<AIAgent>(qaAgent);
}

// Subsequent speakers are DevOps Engineer
AIAgent devopsAgent = this._agents.First(a => a.Name == "DevOpsEngineer");
return new ValueTask<AIAgent>(devopsAgent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>

<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// This sample demonstrates how to use GroupChatBuilder with tools that require human
/// approval before execution.
/// </summary>
/// <remarks>
/// Pre-requisites:
/// - An Azure OpenAI chat completion deployment must be configured.
/// </remarks>
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<ChatMessage> 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";
}
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions dotnet/samples/GettingStarted/Workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading