// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Workflows; /// /// Provides a that selects agents in a round-robin fashion. /// public class RoundRobinGroupChatManager : GroupChatManager { private readonly IReadOnlyList _agents; private readonly Func, CancellationToken, ValueTask>? _shouldTerminateFunc; private int _nextIndex; /// /// Initializes a new instance of the class. /// /// The agents to be managed as part of this workflow. /// /// An optional function that determines whether the group chat should terminate based on the chat history /// before factoring in the default behavior, which is to terminate based only on the iteration count. /// public RoundRobinGroupChatManager( IReadOnlyList agents, Func, CancellationToken, ValueTask>? shouldTerminateFunc = null) { Throw.IfNullOrEmpty(agents); foreach (var agent in agents) { Throw.IfNull(agent, nameof(agents)); } this._agents = agents; this._shouldTerminateFunc = shouldTerminateFunc; } /// protected internal override ValueTask SelectNextAgentAsync( IReadOnlyList history, CancellationToken cancellationToken = default) { AIAgent nextAgent = this._agents[this._nextIndex]; this._nextIndex = (this._nextIndex + 1) % this._agents.Count; return new ValueTask(nextAgent); } /// protected internal override async ValueTask ShouldTerminateAsync( IReadOnlyList history, CancellationToken cancellationToken = default) { if (this._shouldTerminateFunc is { } func && await func(this, history, cancellationToken).ConfigureAwait(false)) { return true; } return await base.ShouldTerminateAsync(history, cancellationToken).ConfigureAwait(false); } /// protected internal override void Reset() { base.Reset(); this._nextIndex = 0; } /// protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) => context.QueueStateUpdateAsync(StateKey, new RoundRobinGroupChatManagerState(this._nextIndex), cancellationToken: cancellationToken); /// protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) { RoundRobinGroupChatManagerState? state = await context.ReadStateAsync(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false); this._nextIndex = state?.NextIndex ?? 0; if (this._nextIndex < 0 || this._nextIndex >= this._agents.Count) { this._nextIndex = 0; } } private const string StateKey = "next_index"; } internal sealed record RoundRobinGroupChatManagerState(int NextIndex);