// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Specialized;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows;
///
/// Provides utility methods for constructing common patterns of workflows composed of agents.
///
public static partial class AgentWorkflowBuilder
{
///
/// Builds a composed of a pipeline of agents where the output of one agent is the input to the next.
///
/// The sequence of agents to compose into a sequential workflow.
/// The built workflow composed of the supplied , in the order in which they were yielded from the source.
public static Workflow BuildSequential(params IEnumerable agents)
=> BuildSequentialCore(workflowName: null, agents);
///
/// Builds a composed of a pipeline of agents where the output of one agent is the input to the next.
///
/// The name of workflow.
/// The sequence of agents to compose into a sequential workflow.
/// The built workflow composed of the supplied , in the order in which they were yielded from the source.
public static Workflow BuildSequential(string workflowName, params IEnumerable agents)
=> BuildSequentialCore(workflowName, agents);
private static Workflow BuildSequentialCore(string? workflowName, params IEnumerable agents)
{
Throw.IfNullOrEmpty(agents);
// Create a builder that chains the agents together in sequence. The workflow simply begins
// with the first agent in the sequence.
AIAgentHostOptions options = new()
{
ReassignOtherAgentsAsUsers = true,
ForwardIncomingMessages = true,
};
List agentExecutors = agents.Select(agent => agent.BindAsExecutor(options)).ToList();
ExecutorBinding previous = agentExecutors[0];
WorkflowBuilder builder = new(previous);
foreach (ExecutorBinding next in agentExecutors.Skip(1))
{
builder.AddEdge(previous, next);
previous = next;
}
OutputMessagesExecutor end = new();
builder = builder.AddEdge(previous, end).WithOutputFrom(end);
if (workflowName is not null)
{
builder = builder.WithName(workflowName);
}
return builder.Build();
}
///
/// Builds a composed of agents that operate concurrently on the same input,
/// aggregating their outputs into a single collection.
///
/// The set of agents to compose into a concurrent workflow.
///
/// The aggregation function that accepts a list of the output messages from each and produces
/// a single result list. If , the default behavior is to return a list containing the last message
/// from each agent that produced at least one message.
///
/// The built workflow composed of the supplied concurrent .
public static Workflow BuildConcurrent(
IEnumerable agents,
Func>, List>? aggregator = null)
=> BuildConcurrentCore(workflowName: null, agents, aggregator);
///
/// Builds a composed of agents that operate concurrently on the same input,
/// aggregating their outputs into a single collection.
///
/// The name of the workflow.
/// The set of agents to compose into a concurrent workflow.
///
/// The aggregation function that accepts a list of the output messages from each and produces
/// a single result list. If , the default behavior is to return a list containing the last message
/// from each agent that produced at least one message.
///
/// The built workflow composed of the supplied concurrent .
public static Workflow BuildConcurrent(
string workflowName,
IEnumerable agents,
Func>, List>? aggregator = null)
=> BuildConcurrentCore(workflowName, agents, aggregator);
private static Workflow BuildConcurrentCore(
string? workflowName,
IEnumerable agents,
Func>, List>? aggregator = null)
{
Throw.IfNull(agents);
// A workflow needs a starting executor, so we create one that forwards everything to each agent.
ChatForwardingExecutor start = new("Start");
WorkflowBuilder builder = new(start);
// For each agent, we create an executor to host it and an accumulator to batch up its output messages,
// so that the final accumulator receives a single list of messages from each agent. Otherwise, the
// accumulator would not be able to determine what came from what agent, as there's currently no
// provenance tracking exposed in the workflow context passed to a handler.
ExecutorBinding[] agentExecutors = (from agent in agents
select agent.BindAsExecutor(new AIAgentHostOptions() { ReassignOtherAgentsAsUsers = true })).ToArray();
ExecutorBinding[] accumulators = [.. from agent in agentExecutors select (ExecutorBinding)new AggregateTurnMessagesExecutor($"Batcher/{agent.Id}")];
builder.AddFanOutEdge(start, agentExecutors);
for (int i = 0; i < agentExecutors.Length; i++)
{
builder.AddEdge(agentExecutors[i], accumulators[i]);
}
// Create the accumulating executor that will gather the results from each agent, and connect
// each agent's accumulator to it. If no aggregation function was provided, we default to returning
// the last message from each agent
aggregator ??= static lists => (from list in lists where list.Count > 0 select list.Last()).ToList();
Func> endFactory =
(_, __) => new(new ConcurrentEndExecutor(agentExecutors.Length, aggregator));
ExecutorBinding end = endFactory.BindExecutor(ConcurrentEndExecutor.ExecutorId);
builder.AddFanInBarrierEdge(accumulators, end);
builder = builder.WithOutputFrom(end);
if (workflowName is not null)
{
builder = builder.WithName(workflowName);
}
return builder.Build();
}
/// Creates a new using as the starting agent in the workflow.
/// The agent that will receive inputs provided to the workflow.
/// The builder for creating a workflow based on handoffs.
///
/// Handoffs between agents are achieved by the current agent invoking an provided to an agent
/// via 's ..
/// The must be capable of understanding those provided. If the agent
/// ignores the tools or is otherwise unable to advertize them to the underlying provider, handoffs will not occur.
///
[Experimental(DiagnosticConstants.ExperimentalFeatureDiagnostic)]
public static HandoffWorkflowBuilder CreateHandoffBuilderWith(AIAgent initialAgent)
{
Throw.IfNull(initialAgent);
return new(initialAgent);
}
/// Creates a new with .
///
/// Function that will create the for the workflow instance. The manager will be
/// provided with the set of agents that will participate in the group chat.
///
/// The builder for creating a workflow based on handoffs.
///
/// Handoffs between agents are achieved by the current agent invoking an provided to an agent
/// via 's ..
/// The must be capable of understanding those provided. If the agent
/// ignores the tools or is otherwise unable to advertize them to the underlying provider, handoffs will not occur.
///
public static GroupChatWorkflowBuilder CreateGroupChatBuilderWith(Func, GroupChatManager> managerFactory)
{
Throw.IfNull(managerFactory);
return new GroupChatWorkflowBuilder(managerFactory);
}
}