// 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); } }