.NET: Remove sequential\concurrent workflow extensions (#1731)

* remove non-standard workflow extensions, create overloads for IHostedAgentBuilder

* rework

* rollback extensions in a2a
This commit is contained in:
Korolev Dmitry
2025-11-07 12:58:21 +01:00
committed by GitHub
Unverified
parent e5d9d74c2d
commit b7cddaaba8
4 changed files with 20 additions and 143 deletions
@@ -78,8 +78,19 @@ var literatureAgent = builder.AddAIAgent("literator",
description: "An agent that helps with literature.",
chatClientServiceKey: "chat-model");
builder.AddSequentialWorkflow("science-sequential-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
builder.AddConcurrentWorkflow("science-concurrent-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
var scienceSequentialWorkflow = builder.AddWorkflow("science-sequential-workflow", (sp, key) =>
{
List<IHostedAgentBuilder> usedAgents = [chemistryAgent, mathsAgent, literatureAgent];
var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildSequential(workflowName: key, agents: agents);
}).AddAsAIAgent();
var scienceConcurrentWorkflow = builder.AddWorkflow("science-concurrent-workflow", (sp, key) =>
{
List<IHostedAgentBuilder> usedAgents = [chemistryAgent, mathsAgent, literatureAgent];
var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildConcurrent(workflowName: key, agents: agents);
}).AddAsAIAgent();
builder.AddOpenAIChatCompletions();
builder.AddOpenAIResponses();
@@ -4,8 +4,10 @@
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.DevUI;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace DevUI_Step01_BasicUsage;
@@ -56,10 +58,11 @@ internal static class Program
// Register sample workflows
var assistantBuilder = builder.AddAIAgent("workflow-assistant", "You are a helpful assistant in a workflow.");
var reviewerBuilder = builder.AddAIAgent("workflow-reviewer", "You are a reviewer. Review and critique the previous response.");
builder.AddSequentialWorkflow(
"review-workflow",
[assistantBuilder, reviewerBuilder])
.AddAsAIAgent();
builder.AddWorkflow("review-workflow", (sp, key) =>
{
var agents = new List<IHostedAgentBuilder>() { assistantBuilder, reviewerBuilder }.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildSequential(workflowName: key, agents: agents);
}).AddAsAIAgent();
if (builder.Environment.IsDevelopment())
{
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Hosting.Local;
using Microsoft.Agents.AI.Workflows;
@@ -16,46 +15,6 @@ namespace Microsoft.Agents.AI.Hosting;
/// </summary>
public static class HostApplicationBuilderWorkflowExtensions
{
/// <summary>
/// Registers a concurrent workflow that executes multiple agents in parallel.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="name">The unique name for the workflow.</param>
/// <param name="agentBuilders">A collection of <see cref="IHostedAgentBuilder"/> instances representing agents to execute concurrently.</param>
/// <returns>An <see cref="IHostedWorkflowBuilder"/> that can be used to further configure the workflow.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="name"/>, or <paramref name="agentBuilders"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> or <paramref name="agentBuilders"/> is empty.</exception>
public static IHostedWorkflowBuilder AddConcurrentWorkflow(this IHostApplicationBuilder builder, string name, IEnumerable<IHostedAgentBuilder> agentBuilders)
{
Throw.IfNullOrEmpty(agentBuilders);
return builder.AddWorkflow(name, (sp, key) =>
{
var agents = agentBuilders.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildConcurrent(workflowName: name, agents: agents);
});
}
/// <summary>
/// Registers a sequential workflow that executes agents in a specific order.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="name">The unique name for the workflow.</param>
/// <param name="agentBuilders">A collection of <see cref="IHostedAgentBuilder"/> instances representing agents to execute in sequence.</param>
/// <returns>An <see cref="IHostedWorkflowBuilder"/> that can be used to further configure the workflow.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="name"/>, or <paramref name="agentBuilders"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> or <paramref name="agentBuilders"/> is empty.</exception>
public static IHostedWorkflowBuilder AddSequentialWorkflow(this IHostApplicationBuilder builder, string name, IEnumerable<IHostedAgentBuilder> agentBuilders)
{
Throw.IfNullOrEmpty(agentBuilders);
return builder.AddWorkflow(name, (sp, key) =>
{
var agents = agentBuilders.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildSequential(workflowName: name, agents: agents);
});
}
/// <summary>
/// Registers a custom workflow using a factory delegate.
/// </summary>
@@ -137,102 +137,6 @@ public class HostApplicationBuilderWorkflowExtensionsTests
Assert.NotNull(descriptor);
}
/// <summary>
/// Verifies that providing a null builder to AddConcurrentWorkflow throws an ArgumentNullException.
/// </summary>
[Fact]
public void AddConcurrentWorkflow_NullBuilder_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() =>
HostApplicationBuilderWorkflowExtensions.AddConcurrentWorkflow(null!, "workflow", [null!]));
}
/// <summary>
/// Verifies that AddConcurrentWorkflow throws ArgumentNullException for null name.
/// </summary>
[Fact]
public void AddConcurrentWorkflow_NullName_ThrowsArgumentNullException()
{
var builder = new HostApplicationBuilder();
var exception = Assert.Throws<ArgumentNullException>(() =>
builder.AddConcurrentWorkflow(null!, [new HostedAgentBuilder("test", builder)]));
Assert.Equal("name", exception.ParamName);
}
/// <summary>
/// Verifies that AddConcurrentWorkflow throws ArgumentNullException for null agent builders.
/// </summary>
[Fact]
public void AddConcurrentWorkflow_NullAgentBuilders_ThrowsArgumentNullException()
{
var builder = new HostApplicationBuilder();
var exception = Assert.Throws<ArgumentNullException>(() =>
builder.AddConcurrentWorkflow("workflowName", null!));
Assert.Equal("agentBuilders", exception.ParamName);
}
/// <summary>
/// Verifies that AddConcurrentWorkflow returns IHostWorkflowBuilder instance.
/// </summary>
[Fact]
public void AddConcurrentWorkflow_ValidParameters_ReturnsBuilder()
{
var builder = new HostApplicationBuilder();
var result = builder.AddConcurrentWorkflow("concurrentWorkflow", [new HostedAgentBuilder("test", builder)]);
Assert.NotNull(result);
Assert.IsAssignableFrom<IHostedWorkflowBuilder>(result);
}
/// <summary>
/// Verifies that providing a null builder to AddSequentialWorkflow throws an ArgumentNullException.
/// </summary>
[Fact]
public void AddSequentialWorkflow_NullBuilder_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() =>
HostApplicationBuilderWorkflowExtensions.AddSequentialWorkflow(null!, "workflow", [null!]));
}
/// <summary>
/// Verifies that AddSequentialWorkflow throws ArgumentNullException for null name.
/// </summary>
[Fact]
public void AddSequentialWorkflow_NullName_ThrowsArgumentNullException()
{
var builder = new HostApplicationBuilder();
var exception = Assert.Throws<ArgumentNullException>(() =>
builder.AddSequentialWorkflow(null!, [new HostedAgentBuilder("test", builder)]));
Assert.Equal("name", exception.ParamName);
}
/// <summary>
/// Verifies that AddSequentialWorkflow throws ArgumentNullException for null agent builders.
/// </summary>
[Fact]
public void AddSequentialWorkflow_NullAgentBuilders_ThrowsArgumentNullException()
{
var builder = new HostApplicationBuilder();
var exception = Assert.Throws<ArgumentNullException>(() =>
builder.AddSequentialWorkflow("workflowName", null!));
Assert.Equal("agentBuilders", exception.ParamName);
}
[Fact]
public void AddSequentialWorkflow_EmptyAgentBuilders_Throws()
{
var builder = new HostApplicationBuilder();
var exception = Assert.Throws<ArgumentException>(() =>
builder.AddSequentialWorkflow("sequentialWorkflow", Array.Empty<IHostedAgentBuilder>()));
Assert.Equal("agentBuilders", exception.ParamName);
}
/// <summary>
/// Verifies that AddAsAIAgent without a name parameter uses the workflow name as the agent name.
/// </summary>