Files
agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/ConcurrentEndExecutor.cs
T
Jacob AlberandGitHub 6a3d22598f .NET: [BREAKING] Implement Polymorphic Routing (#3792)
* feat: Implement Polymorphic Routing

* feat: Add support for Send/Yield annotations with basic Executor

* Adds annotations to Declarative workflow executors

* fix: Address PR Comments

* Implicit filter in collection loops
* Remove debug / usused / superfluous code
* Fix ProtocolBuilder implicit output registrations
* Fix logic error in ExecuteRouteGeneratorTests.ClassWithManualConfigureProtocol_DoesNotGenerate

* fix: Solidify type checks and send/yield type registrations

* fix: Suppress generation of TurnTokens out of AggregateTurnMessagesExecutor

* Fixes an issue where ConcurrentEndExecutor is not expecting TurnTokens.

* fix: Add ProtocolBuilder support for chained-delegation

* Updates Declarative pacakge to rely on chained-delegation Send/Yield registration
* Renames DeclarativeActionExectuor's new ExecuteAsync to ExecuteActionAsync to avoid colliding with Executor.ExecutoeAsync

* fix: Address PR Comments

* Fixes type mapping in FanInEdgeRunner
* Fixes and expalins send/yield type registration in FunctionExecutor

* fixup: build-break

* fix: Add missing SendsMesage declaration to InvokeAzureAgentExecutor
2026-02-19 14:09:03 +00:00

71 lines
2.3 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Specialized;
/// <summary>
/// Provides an executor that accepts the output messages from each of the concurrent agents
/// and produces a result list containing the last message from each.
/// </summary>
internal sealed class ConcurrentEndExecutor : Executor, IResettableExecutor
{
public const string ExecutorId = "ConcurrentEnd";
private readonly int _expectedInputs;
private readonly Func<IList<List<ChatMessage>>, List<ChatMessage>> _aggregator;
private List<List<ChatMessage>> _allResults;
private int _remaining;
public ConcurrentEndExecutor(int expectedInputs, Func<IList<List<ChatMessage>>, List<ChatMessage>> aggregator) : base(ExecutorId)
{
this._expectedInputs = expectedInputs;
this._aggregator = Throw.IfNull(aggregator);
this._allResults = new List<List<ChatMessage>>(expectedInputs);
this._remaining = expectedInputs;
}
private void Reset()
{
this._allResults = new List<List<ChatMessage>>(this._expectedInputs);
this._remaining = this._expectedInputs;
}
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBuilder)
{
protocolBuilder.RouteBuilder.AddHandler<List<ChatMessage>>(async (messages, context, cancellationToken) =>
{
// TODO: https://github.com/microsoft/agent-framework/issues/784
// This locking should not be necessary.
bool done;
lock (this._allResults)
{
this._allResults.Add(messages);
done = --this._remaining == 0;
}
if (done)
{
this._remaining = this._expectedInputs;
var results = this._allResults;
this._allResults = new List<List<ChatMessage>>(this._expectedInputs);
await context.YieldOutputAsync(this._aggregator(results), cancellationToken).ConfigureAwait(false);
}
});
return protocolBuilder.YieldsOutput<List<ChatMessage>>();
}
public ValueTask ResetAsync()
{
this.Reset();
return default;
}
}