Clean up handoff orchestration creation (#235)

- Remove Dictionary-derived types
- Add an optional name to orchestrations
- Make Handoffs based purely on AIAgent instances rather than separately provided names
This commit is contained in:
Stephen Toub
2025-07-24 12:38:23 -04:00
committed by GitHub
Unverified
parent a72245287f
commit 233c557173
13 changed files with 780 additions and 354 deletions
@@ -19,7 +19,14 @@ public partial class ConcurrentOrchestration : OrchestratingAgent
/// <summary>Initializes a new instance of the <see cref="ConcurrentOrchestration"/> class.</summary>
/// <param name="subagents">The agents participating in the orchestration.</param>
public ConcurrentOrchestration(params AIAgent[] subagents) : base(subagents)
public ConcurrentOrchestration(params AIAgent[] subagents) : this(subagents, name: null)
{
}
/// <summary>Initializes a new instance of the <see cref="ConcurrentOrchestration"/> class.</summary>
/// <param name="subagents">The agents participating in the orchestration.</param>
/// <param name="name">An optional name for this orchestrating agent.</param>
public ConcurrentOrchestration(AIAgent[] subagents, string? name) : base(subagents, name)
{
}
@@ -86,11 +86,9 @@ public abstract class GroupChatManager
/// <returns>A <see cref="GroupChatManagerResult{TValue}"/> indicating whether the chat should be terminated.</returns>
protected internal virtual ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
{
Interlocked.Increment(ref this._invocationCount);
bool resultValue = false;
string reason = "Maximum number of invocations has not been reached.";
if (this.InvocationCount > this.MaximumInvocationCount)
if (Interlocked.Increment(ref this._invocationCount) > this.MaximumInvocationCount)
{
resultValue = true;
reason = "Maximum number of invocations reached.";
@@ -24,7 +24,17 @@ public sealed partial class GroupChatOrchestration : OrchestratingAgent
/// </summary>
/// <param name="manager">The manager that controls the flow of the group-chat.</param>
/// <param name="agents">The agents participating in the orchestration.</param>
public GroupChatOrchestration(GroupChatManager manager, params AIAgent[] agents) : base(agents)
public GroupChatOrchestration(GroupChatManager manager, params AIAgent[] agents) : this(manager, agents, name: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GroupChatOrchestration"/> class.
/// </summary>
/// <param name="manager">The manager that controls the flow of the group-chat.</param>
/// <param name="agents">The agents participating in the orchestration.</param>
/// <param name="name">An optional name for this orchestrating agent.</param>
public GroupChatOrchestration(GroupChatManager manager, AIAgent[] agents, string? name) : base(agents, name)
{
this._manager = Throw.IfNull(manager);
}
@@ -19,14 +19,16 @@ public class RoundRobinGroupChatManager : GroupChatManager
private int _currentAgentIndex;
/// <inheritdoc/>
protected internal override ValueTask<GroupChatManagerResult<string>> FilterResults(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
protected internal override ValueTask<GroupChatManagerResult<string>> FilterResults(
IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
{
GroupChatManagerResult<string> result = new(history.LastOrDefault()?.Text ?? string.Empty) { Reason = "Default result filter provides the final chat message." };
return new ValueTask<GroupChatManagerResult<string>>(result);
}
/// <inheritdoc/>
protected internal override ValueTask<GroupChatManagerResult<string>> SelectNextAgent(IReadOnlyCollection<ChatMessage> history, GroupChatTeam team, CancellationToken cancellationToken = default)
protected internal override ValueTask<GroupChatManagerResult<string>> SelectNextAgent(
IReadOnlyCollection<ChatMessage> history, GroupChatTeam team, CancellationToken cancellationToken = default)
{
string nextAgent = team.Skip(this._currentAgentIndex).First().Key;
this._currentAgentIndex = (this._currentAgentIndex + 1) % team.Count;
@@ -35,7 +37,8 @@ public class RoundRobinGroupChatManager : GroupChatManager
}
/// <inheritdoc/>
protected internal override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
protected internal override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(
IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
{
GroupChatManagerResult<bool> result = new(false) { Reason = "The default round-robin group chat manager does not request user input." };
return new ValueTask<GroupChatManagerResult<bool>>(result);
@@ -20,29 +20,23 @@ namespace Microsoft.Agents.Orchestration;
/// </summary>
public sealed partial class HandoffOrchestration : OrchestratingAgent
{
private readonly OrchestrationHandoffs _handoffs;
private readonly Handoffs _handoffs;
/// <summary>
/// Initializes a new instance of the <see cref="HandoffOrchestration"/> class.
/// </summary>
/// <param name="handoffs">Defines the handoff connections for each agent.</param>
/// <param name="agents">Additional agents participating in the orchestration that weren't passed to <paramref name="handoffs"/>.</param>
public HandoffOrchestration(OrchestrationHandoffs handoffs, params AIAgent[] agents) : base(
agents is { Length: 0 } ? [.. handoffs.Agents] :
handoffs.Agents is { Count: 0 } ? agents :
[.. handoffs.Agents.Concat(agents).Distinct()])
public HandoffOrchestration(Handoffs handoffs) : this(handoffs, name: null)
{
// Create list of distinct agent names
HashSet<string> agentNames = [.. base.Agents.Select(a => a.DisplayName), handoffs.FirstAgentName];
// Extract names from handoffs that don't align with a member agent.
// Fail fast if invalid names are present.
string[] badNames = [.. handoffs.Keys.Concat(handoffs.Values.SelectMany(h => h.Keys)).Where(name => !agentNames.Contains(name))];
if (badNames.Length > 0)
{
Throw.ArgumentException(nameof(handoffs), $"The following agents are not defined in the orchestration: {string.Join(", ", badNames)}");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HandoffOrchestration"/> class.
/// </summary>
/// <param name="handoffs">Defines the handoff connections for each agent.</param>
/// <param name="name">An optional name for this orchestrating agent.</param>
public HandoffOrchestration(Handoffs handoffs, string? name) : base(handoffs.Agents.ToArray(), name)
{
this._handoffs = handoffs;
}
@@ -54,39 +48,51 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
{
List<ChatMessage> allMessages = [.. messages];
int originalMessageCount = allMessages.Count;
return this.ResumeAsync(this._handoffs.FirstAgentName, allMessages, originalMessageCount, context, cancellationToken);
return this.ResumeAsync(this._handoffs.InitialAgent, allMessages, originalMessageCount, context, cancellationToken);
}
/// <inheritdoc />
protected override Task<AgentRunResponse> ResumeCoreAsync(JsonElement checkpointState, OrchestratingAgentContext context, CancellationToken cancellationToken)
{
var state = checkpointState.Deserialize(OrchestrationJsonContext.Default.HandoffState) ?? throw new InvalidOperationException("The checkpoint state is invalid.");
return this.ResumeAsync(state.NextAgent, state.AllMessages, state.OriginalMessageCount, context, cancellationToken);
AIAgent? nextAgent = null;
foreach (var agent in this.Agents)
{
if (agent.Id == state.NextAgent)
{
nextAgent = agent;
break;
}
}
if (nextAgent is null)
{
Throw.InvalidOperationException($"The next agent '{state.NextAgent}' is not defined in the orchestration.");
}
return this.ResumeAsync(nextAgent, state.AllMessages, state.OriginalMessageCount, context, cancellationToken);
}
/// <inheritdoc />
private async Task<AgentRunResponse> ResumeAsync(
string? nextAgent, List<ChatMessage> allMessages, int originalMessageCount, OrchestratingAgentContext context, CancellationToken cancellationToken)
AIAgent? agent, List<ChatMessage> allMessages, int originalMessageCount, OrchestratingAgentContext context, CancellationToken cancellationToken)
{
Debug.Assert(nextAgent is not null);
Debug.Assert(agent is not null);
AgentRunResponse? response = null;
while (nextAgent is not null)
while (agent is not null)
{
AIAgent? agent =
this.Agents.FirstOrDefault(a => a.Name == nextAgent || a.Id == nextAgent) ??
throw new InvalidOperationException($"The agent '{nextAgent}' is not defined in the orchestration.");
this.LogOrchestrationSubagentRunning(context, agent);
if (!this._handoffs.TryGetValue(agent.DisplayName, out AgentHandoffs? handoffs) || handoffs.Count == 0)
if (!this._handoffs.Targets.TryGetValue(agent, out var handoffs) || handoffs.Count == 0)
{
// If no handoff is available, we can run the agent directly and return its response.
response = await RunAsync(agent, context, allMessages, context.Options, cancellationToken).ConfigureAwait(false);
allMessages.AddRange(response.Messages);
nextAgent = null;
await CheckpointAsync().ConfigureAwait(false);
this.LogOrchestrationSubagentCompleted(context, agent);
allMessages.AddRange(response.Messages);
agent = null;
await CheckpointAsync().ConfigureAwait(false);
break;
}
@@ -107,8 +113,9 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
// Invoke the next agent with all of the messages collected so far.
response = await RunAsync(agent, context, allMessages, options, cancellationToken).ConfigureAwait(false);
this.LogOrchestrationSubagentCompleted(context, agent);
allMessages.AddRange(response.Messages);
nextAgent = handoffCtx.TargetedAgent;
agent = handoffCtx.TargetedAgent;
RemoveHandoffFunctionCalls(response, handoffTools);
if (this.InteractiveCallback is not null)
@@ -118,12 +125,10 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
break;
}
nextAgent = agent.DisplayName;
allMessages.Add(await this.InteractiveCallback().ConfigureAwait(false));
}
await CheckpointAsync().ConfigureAwait(false);
this.LogOrchestrationSubagentCompleted(context, agent);
}
allMessages.RemoveRange(0, originalMessageCount);
@@ -132,7 +137,7 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
return response;
Task CheckpointAsync() => context.Runtime is not null ?
base.WriteCheckpointAsync(JsonSerializer.SerializeToElement(new(nextAgent, allMessages, originalMessageCount), OrchestrationJsonContext.Default.HandoffState), context, cancellationToken) :
base.WriteCheckpointAsync(JsonSerializer.SerializeToElement(new(agent?.Id, allMessages, originalMessageCount), OrchestrationJsonContext.Default.HandoffState), context, cancellationToken) :
Task.CompletedTask;
}
@@ -173,9 +178,9 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
}
}
private sealed class HandoffContext(AgentHandoffs handoffs)
private sealed class HandoffContext(HashSet<Handoffs.HandoffTarget> handoffs)
{
public string? TargetedAgent { get; set; }
public AIAgent? TargetedAgent { get; set; }
public bool EndTaskInvoked { get; set; }
public List<AITool> CreateHandoffFunctions(bool needsEndTask)
@@ -194,16 +199,16 @@ public sealed partial class HandoffOrchestration : OrchestratingAgent
description: "Invoke this function when all work is completed and no further interactions are required."));
}
foreach (KeyValuePair<string, string> handoff in handoffs)
foreach (Handoffs.HandoffTarget handoff in handoffs)
{
functions.Add(AIFunctionFactory.Create(
() =>
{
this.TargetedAgent = handoff.Key;
this.TargetedAgent = handoff.Target;
Terminate();
},
name: $"handoff_to_{InvalidNameCharsRegex().Replace(handoff.Key, "_")}",
description: handoff.Value));
name: $"handoff_to_{InvalidNameCharsRegex().Replace(handoff.Target.DisplayName, "_")}",
description: handoff.Reason));
}
return functions;
@@ -1,155 +1,187 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime;
using Microsoft.Shared.Diagnostics;
#pragma warning disable CA1710 // Identifiers should have correct suffix
namespace Microsoft.Agents.Orchestration;
/// <summary>
/// Defines the handoff relationships for a given agent.
/// Maps target agent names/IDs to handoff descriptions.
/// </summary>
public sealed class AgentHandoffs : Dictionary<string, string>
{
/// <summary>
/// Initializes a new instance of the <see cref="AgentHandoffs"/> class with no handoff relationships.
/// </summary>
public AgentHandoffs() { }
/// <summary>
/// Initializes a new instance of the <see cref="AgentHandoffs"/> class with the specified handoff relationships.
/// </summary>
/// <param name="handoffs">A dictionary mapping target agent names/IDs to handoff descriptions.</param>
public AgentHandoffs(Dictionary<string, string> handoffs) : base(handoffs) { }
}
/// <summary>
/// Defines the orchestration handoff relationships for all agents in the system.
/// Maps source agent names/IDs to their <see cref="AgentHandoffs"/>.
/// </summary>
public sealed class OrchestrationHandoffs : Dictionary<string, AgentHandoffs>
public sealed class Handoffs :
IReadOnlyDictionary<AIAgent, IEnumerable<Handoffs.HandoffTarget>>
{
/// <summary>
/// Initializes a new instance of the <see cref="OrchestrationHandoffs"/> class with no handoff relationships.
/// Initializes a new instance of the <see cref="Orchestration.Handoffs"/> class with no handoff relationships.
/// </summary>
/// <param name="firstAgent">The first agent to be invoked (prior to any handoff).</param>
public OrchestrationHandoffs(AIAgent firstAgent)
: this(firstAgent.DisplayName)
/// <param name="initialAgent">The first agent to be invoked (prior to any handoff).</param>
private Handoffs(AIAgent initialAgent)
{
this.Agents.Add(firstAgent);
Throw.IfNull(initialAgent);
this.Agents.Add(initialAgent);
this.InitialAgent = initialAgent;
}
/// <summary>
/// Initializes a new instance of the <see cref="OrchestrationHandoffs"/> class with no handoff relationships.
/// </summary>
/// <param name="firstAgentName">The name of the first agent to be invoked (prior to any handoff).</param>
public OrchestrationHandoffs(string firstAgentName)
{
Throw.IfNullOrWhitespace(firstAgentName, nameof(firstAgentName));
this.FirstAgentName = firstAgentName;
}
/// <summary>Gets the initial agent to which the first messages will be sent.</summary>
public AIAgent InitialAgent { get; }
/// <summary>Gets a collection of all handoff targets, indexed by the source of the handoffs.</summary>
internal Dictionary<AIAgent, HashSet<HandoffTarget>> Targets { get; } = [];
/// <summary>Gets a set of all agents involved in the handoffs, sources and targets.</summary>
internal HashSet<AIAgent> Agents { get; } = [];
/// <summary>
/// The name of the first agent to be invoked (prior to any handoff).
/// Creates a new collection of handoffs that start with the specified agent.
/// </summary>
public string FirstAgentName { get; }
/// <param name="initialAgent">The initial agent.</param>
/// <returns>The new <see cref="Orchestration.Handoffs"/> instance.</returns>
public static Handoffs StartWith(AIAgent initialAgent) => new(initialAgent);
/// <summary>Creates a new <see cref="HandoffOrchestration"/> from the described handoffs.</summary>
/// <param name="name">An optional name for this orchestrating agent.</param>
/// <returns>The new <see cref="HandoffOrchestration"/>.</returns>
public HandoffOrchestration Build(string? name = null) => new(this, name);
/// <summary>
/// Adds handoff relationships from a source agent to one or more target agents.
/// Each target agent's name or ID is mapped to its description.
/// </summary>
/// <param name="source">The source agent.</param>
/// <returns>The updated <see cref="OrchestrationHandoffs"/> instance.</returns>
public static OrchestrationHandoffs StartWith(AIAgent source) => new(source);
/// <summary>
/// Adds handoff relationships from a source agent to one or more target agents.
/// Each target agent's name or ID is mapped to its description.
/// </summary>
/// <param name="source">The source agent.</param>
/// <param name="targets">The target agents to add as handoff targets for the source agent.</param>
/// <returns>The updated <see cref="OrchestrationHandoffs"/> instance.</returns>
public OrchestrationHandoffs Add(AIAgent source, params AIAgent[] targets)
/// <returns>The updated <see cref="Orchestration.Handoffs"/> instance.</returns>
/// <remarks>The handoff reason for each target is derived from its description or name.</remarks>
public Handoffs Add(AIAgent source, AIAgent[] targets)
{
string key = source.DisplayName;
AgentHandoffs agentHandoffs = this.GetAgentHandoffs(key);
foreach (AIAgent target in targets)
Throw.IfNull(source);
Throw.IfNull(targets);
if (Array.IndexOf(targets, null) >= 0)
{
if (string.IsNullOrWhiteSpace(target.Description) && string.IsNullOrWhiteSpace(target.Name))
{
Throw.InvalidOperationException($"The provided target agent with Id '{target.Id}' has no description or name, and no handoff description has been provided. At least one of these are required to register a handoff so that the appropriate target agent can be chosen.");
}
this.Agents.Add(target);
agentHandoffs[target.DisplayName] = target.Description ?? target.Name!;
Throw.ArgumentNullException(nameof(targets), "One or more target agents are null.");
}
this.Agents.Add(source);
foreach (var target in targets)
{
this.Add(source, target);
}
return this;
}
/// <summary>
/// Adds a handoff relationship from a source agent to a target agent with a custom description.
/// Adds a handoff relationship from a source agent to a target agent with a custom handoff reason.
/// </summary>
/// <param name="source">The source agent.</param>
/// <param name="target">The target agent.</param>
/// <param name="description">The handoff description.</param>
/// <returns>The updated <see cref="OrchestrationHandoffs"/> instance.</returns>
public OrchestrationHandoffs Add(AIAgent source, AIAgent target, string description)
/// <param name="handoffReason">The reason the <paramref name="source"/> should hand off to the <paramref name="target"/>.</param>
/// <returns>The updated <see cref="Orchestration.Handoffs"/> instance.</returns>
public Handoffs Add(AIAgent source, AIAgent target, string? handoffReason = null)
{
Throw.IfNull(source);
Throw.IfNull(target);
this.Agents.Add(source);
this.Agents.Add(target);
return this.Add(source.DisplayName, target.DisplayName, description);
}
/// <summary>
/// Adds a handoff relationship from a source agent to a target agent name/ID with a custom description.
/// </summary>
/// <param name="source">The source agent.</param>
/// <param name="targetName">The target agent's name or ID.</param>
/// <param name="description">The handoff description.</param>
/// <returns>The updated <see cref="OrchestrationHandoffs"/> instance.</returns>
public OrchestrationHandoffs Add(AIAgent source, string targetName, string description)
{
this.Agents.Add(source);
return this.Add(source.DisplayName, targetName, description);
}
if (!this.Targets.TryGetValue(source, out var handoffs))
{
this.Targets[source] = handoffs = [];
}
/// <summary>
/// Adds a handoff relationship from a source agent name/ID to a target agent name/ID with a custom description.
/// </summary>
/// <param name="sourceName">The source agent's name or ID.</param>
/// <param name="targetName">The target agent's name or ID.</param>
/// <param name="description">The handoff description.</param>
/// <returns>The updated <see cref="OrchestrationHandoffs"/> instance.</returns>
public OrchestrationHandoffs Add(string sourceName, string targetName, string description)
{
AgentHandoffs agentHandoffs = this.GetAgentHandoffs(sourceName);
agentHandoffs[targetName] = description;
if (!handoffs.Add(new(target, handoffReason)))
{
Throw.InvalidOperationException($"A handoff from agent '{source.DisplayName}' to agent '{target.DisplayName}' has already been registered.");
}
return this;
}
private AgentHandoffs GetAgentHandoffs(string key)
{
if (!this.TryGetValue(key, out AgentHandoffs? agentHandoffs))
{
this[key] = agentHandoffs = [];
}
/// <inheritdoc />
IEnumerable<HandoffTarget> IReadOnlyDictionary<AIAgent, IEnumerable<HandoffTarget>>.this[AIAgent key] => this.Targets[key];
return agentHandoffs;
/// <inheritdoc />
IEnumerable<AIAgent> IReadOnlyDictionary<AIAgent, IEnumerable<HandoffTarget>>.Keys => this.Targets.Keys;
/// <inheritdoc />
IEnumerable<IEnumerable<HandoffTarget>> IReadOnlyDictionary<AIAgent, IEnumerable<HandoffTarget>>.Values => this.Targets.Values;
/// <inheritdoc />
int IReadOnlyCollection<KeyValuePair<AIAgent, IEnumerable<HandoffTarget>>>.Count => this.Targets.Count;
/// <inheritdoc />
bool IReadOnlyDictionary<AIAgent, IEnumerable<HandoffTarget>>.ContainsKey(AIAgent key) => this.Targets.ContainsKey(key);
/// <inheritdoc />
IEnumerator<KeyValuePair<AIAgent, IEnumerable<HandoffTarget>>> IEnumerable<KeyValuePair<AIAgent, IEnumerable<HandoffTarget>>>.GetEnumerator()
{
foreach (var kvp in this.Targets)
{
yield return new(kvp.Key, kvp.Value);
}
}
internal HashSet<AIAgent> Agents { get; } = [];
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() =>
((IReadOnlyDictionary<AIAgent, IEnumerable<Handoffs.HandoffTarget>>)this).GetEnumerator();
/// <summary>
/// Handoff relationships post-processed into a name-based lookup table that includes the agent type and handoff description.
/// Maps agent names/IDs to a tuple of <see cref="ActorType"/> and handoff description.
/// </summary>
internal sealed class HandoffLookup : Dictionary<string, (ActorType AgentType, string Description)>;
/// <inheritdoc />
bool IReadOnlyDictionary<AIAgent, IEnumerable<HandoffTarget>>.TryGetValue(AIAgent key, out IEnumerable<HandoffTarget> value)
{
if (this.Targets.TryGetValue(key, out var handoffs))
{
value = handoffs;
return true;
}
value = [];
return false;
}
/// <summary>Describes a handoff to a specific target <see cref="AIAgent"/>.</summary>
public readonly struct HandoffTarget : IEquatable<HandoffTarget>
{
internal HandoffTarget(AIAgent target, string? reason = null)
{
this.Target = Throw.IfNull(target);
if (string.IsNullOrWhiteSpace(reason))
{
reason = target.Description ?? target.Name;
if (string.IsNullOrWhiteSpace(reason))
{
Throw.InvalidOperationException(
$"The provided target agent with Id '{target.Id}' has no description or name, and no handoff description has been provided. " +
"At least one of these are required to register a handoff so that the appropriate target agent can be chosen.");
}
}
this.Reason = reason!;
}
/// <summary>Gets the target <see cref="AIAgent"/> of the handoff.</summary>
public AIAgent Target { get; }
/// <summary>Gets the reason a handoff to <see cref="Target"/> should be performed.</summary>
public string Reason { get; }
/// <inheritdoc />
public bool Equals(HandoffTarget other) => this.Target == other.Target;
/// <inheritdoc />
public override bool Equals(object? obj) => obj is HandoffTarget other && this.Equals(other);
/// <inheritdoc />
public override int GetHashCode() => this.Target.GetHashCode();
/// <inheritdoc />
public static bool operator ==(HandoffTarget left, HandoffTarget right) => left.Equals(right);
/// <inheritdoc />
public static bool operator !=(HandoffTarget left, HandoffTarget right) => !left.Equals(right);
}
}
@@ -27,13 +27,18 @@ public abstract partial class OrchestratingAgent : AIAgent
/// Initializes a new instance of the <see cref="OrchestratingAgent"/> class.
/// </summary>
/// <param name="agents">Specifies the agents participating in this orchestration.</param>
protected OrchestratingAgent(IReadOnlyList<AIAgent> agents)
/// <param name="name">An optional name for this agent.</param>
protected OrchestratingAgent(IReadOnlyList<AIAgent> agents, string? name = null)
{
_ = Throw.IfNullOrEmpty(agents);
this.Agents = agents;
this.Name = name;
}
/// <inheritdoc />
public override string? Name { get; }
/// <summary>
/// Gets the list of member targets involved in the orchestration.
/// </summary>
@@ -16,7 +16,14 @@ public sealed partial class SequentialOrchestration : OrchestratingAgent
{
/// <summary>Initializes a new instance of the <see cref="SequentialOrchestration"/> class.</summary>
/// <param name="agents">The agents participating in the orchestration.</param>
public SequentialOrchestration(params AIAgent[] agents) : base(agents)
public SequentialOrchestration(params AIAgent[] agents) : this(agents, name: null)
{
}
/// <summary>Initializes a new instance of the <see cref="SequentialOrchestration"/> class.</summary>
/// <param name="agents">The agents participating in the orchestration.</param>
/// <param name="name">An optional name for this orchestrating agent.</param>
public SequentialOrchestration(AIAgent[] agents, string? name) : base(agents, name)
{
}