mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING] refactor: Normalize WorkflowBuilder APIs (#1935)
* [BREAKING] refactor: Normalize WorkflowBuilder APIs * "partitioner" => "assigner" * normalize ordering so sources always to the left of targets for edges * normalize parameter ordering so sources and targets are always first arguments * remove `params` (users should use collection expressions instead) * refactor: Align name with Python
This commit is contained in:
@@ -127,7 +127,7 @@ public static partial class AgentWorkflowBuilder
|
||||
// provenance tracking exposed in the workflow context passed to a handler.
|
||||
ExecutorBinding[] agentExecutors = (from agent in agents select (ExecutorBinding)new AgentRunStreamingExecutor(agent, includeInputInOutput: false)).ToArray();
|
||||
ExecutorBinding[] accumulators = [.. from agent in agentExecutors select (ExecutorBinding)new CollectChatMessagesExecutor($"Batcher/{agent.Id}")];
|
||||
builder.AddFanOutEdge(start, targets: agentExecutors);
|
||||
builder.AddFanOutEdge(start, agentExecutors);
|
||||
for (int i = 0; i < agentExecutors.Length; i++)
|
||||
{
|
||||
builder.AddEdge(agentExecutors[i], accumulators[i]);
|
||||
@@ -143,7 +143,7 @@ public static partial class AgentWorkflowBuilder
|
||||
|
||||
ExecutorBinding end = endFactory.BindExecutor(ConcurrentEndExecutor.ExecutorId);
|
||||
|
||||
builder.AddFanInEdge(end, sources: accumulators);
|
||||
builder.AddFanInEdge(accumulators, end);
|
||||
|
||||
builder = builder.WithOutputFrom(end);
|
||||
if (workflowName is not null)
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class SwitchBuilder
|
||||
List<(Func<object?, bool> Predicate, HashSet<int> OutgoingIndicies)> caseMap = this._caseMap;
|
||||
HashSet<int> defaultIndicies = this._defaultIndicies;
|
||||
|
||||
return builder.AddFanOutEdge<object>(source, CasePartitioner, [.. this._executors]);
|
||||
return builder.AddFanOutEdge<object>(source, this._executors, CasePartitioner);
|
||||
|
||||
IEnumerable<int> CasePartitioner(object? input, int targetCount)
|
||||
{
|
||||
|
||||
@@ -272,12 +272,12 @@ public class WorkflowBuilder
|
||||
/// <param name="source">The source executor from which the fan-out edge originates. Cannot be null.</param>
|
||||
/// <param name="targets">One or more target executors that will receive the fan-out edge. Cannot be null or empty.</param>
|
||||
/// <returns>The current instance of <see cref="WorkflowBuilder"/>.</returns>
|
||||
public WorkflowBuilder AddFanOutEdge(ExecutorBinding source, params IEnumerable<ExecutorBinding> targets)
|
||||
=> this.AddFanOutEdge<object>(source, null, targets);
|
||||
public WorkflowBuilder AddFanOutEdge(ExecutorBinding source, IEnumerable<ExecutorBinding> targets)
|
||||
=> this.AddFanOutEdge<object>(source, targets, null);
|
||||
|
||||
internal static Func<object?, int, IEnumerable<int>>? CreateEdgeAssignerFunc<T>(Func<T?, int, IEnumerable<int>>? partitioner)
|
||||
internal static Func<object?, int, IEnumerable<int>>? CreateTargetAssignerFunc<T>(Func<T?, int, IEnumerable<int>>? targetAssigner)
|
||||
{
|
||||
if (partitioner is null)
|
||||
if (targetAssigner is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -289,7 +289,7 @@ public class WorkflowBuilder
|
||||
maybeObj = portableValue.AsType(typeof(T));
|
||||
}
|
||||
|
||||
return partitioner(maybeObj is T typed ? typed : default, count);
|
||||
return targetAssigner(maybeObj is T typed ? typed : default, count);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -300,11 +300,11 @@ public class WorkflowBuilder
|
||||
/// <remarks>If a partitioner function is provided, it will be used to distribute input across the target
|
||||
/// executors. The order of targets determines their mapping in the partitioning process.</remarks>
|
||||
/// <param name="source">The source executor from which the fan-out edge originates. Cannot be null.</param>
|
||||
/// <param name="partitioner">An optional function that determines how input is partitioned among the target executors.
|
||||
/// If null, messages will route to all targets.</param>
|
||||
/// <param name="targets">One or more target executors that will receive the fan-out edge. Cannot be null or empty.</param>
|
||||
/// <returns>The current instance of <see cref="WorkflowBuilder"/>.</returns>
|
||||
public WorkflowBuilder AddFanOutEdge<T>(ExecutorBinding source, Func<T?, int, IEnumerable<int>>? partitioner = null, params IEnumerable<ExecutorBinding> targets)
|
||||
/// <param name="targetSelector">An optional function that determines how input is assigned among the target executors.
|
||||
/// If null, messages will route to all targets.</param>
|
||||
public WorkflowBuilder AddFanOutEdge<T>(ExecutorBinding source, IEnumerable<ExecutorBinding> targets, Func<T?, int, IEnumerable<int>>? targetSelector = null)
|
||||
{
|
||||
Throw.IfNull(source);
|
||||
Throw.IfNull(targets);
|
||||
@@ -321,7 +321,7 @@ public class WorkflowBuilder
|
||||
this.Track(source).Id,
|
||||
sinkIds,
|
||||
this.TakeEdgeId(),
|
||||
CreateEdgeAssignerFunc(partitioner));
|
||||
CreateTargetAssignerFunc(targetSelector));
|
||||
|
||||
this.EnsureEdgesFor(source.Id).Add(new(fanOutEdge));
|
||||
|
||||
@@ -335,10 +335,10 @@ public class WorkflowBuilder
|
||||
/// <remarks>This method establishes a fan-in relationship, allowing the target executor to be activated
|
||||
/// based on the completion or state of multiple sources. The trigger parameter can be used to customize activation
|
||||
/// behavior.</remarks>
|
||||
/// <param name="target">The target executor that receives input from the specified source executors. Cannot be null.</param>
|
||||
/// <param name="sources">One or more source executors that provide input to the target. Cannot be null or empty.</param>
|
||||
/// <param name="target">The target executor that receives input from the specified source executors. Cannot be null.</param>
|
||||
/// <returns>The current instance of <see cref="WorkflowBuilder"/>.</returns>
|
||||
public WorkflowBuilder AddFanInEdge(ExecutorBinding target, params IEnumerable<ExecutorBinding> sources)
|
||||
public WorkflowBuilder AddFanInEdge(IEnumerable<ExecutorBinding> sources, ExecutorBinding target)
|
||||
{
|
||||
Throw.IfNull(target);
|
||||
Throw.IfNull(sources);
|
||||
@@ -364,8 +364,14 @@ public class WorkflowBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddFanInEdge(IEnumerable{ExecutorBinding}, ExecutorBinding)"/>
|
||||
[Obsolete("Use AddFanInEdge(IEnumerable<ExecutorBinding>, ExecutorBinding) instead.")]
|
||||
public WorkflowBuilder AddFanInEdge(ExecutorBinding target, params IEnumerable<ExecutorBinding> sources)
|
||||
=> this.AddFanInEdge(sources, target);
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
// Validate that there are no unbound executors
|
||||
if (this._unboundExecutors.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
||||
@@ -22,10 +22,10 @@ public static class WorkflowBuilderExtensions
|
||||
/// <typeparam name="TMessage">The type of message to forward.</typeparam>
|
||||
/// <param name="builder">The <see cref="WorkflowBuilder"/> to which the edges will be added.</param>
|
||||
/// <param name="source">The source executor from which messages will be forwarded.</param>
|
||||
/// <param name="executors">The target executors to which messages will be forwarded.</param>
|
||||
/// <param name="target">The target executor to which messages will be forwarded.</param>
|
||||
/// <returns>The updated <see cref="WorkflowBuilder"/> instance.</returns>
|
||||
public static WorkflowBuilder ForwardMessage<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, params IEnumerable<ExecutorBinding> executors)
|
||||
=> builder.ForwardMessage<TMessage>(source, condition: null, executors);
|
||||
public static WorkflowBuilder ForwardMessage<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, ExecutorBinding target)
|
||||
=> builder.ForwardMessage<TMessage>(source, [target], condition: null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds edges to the workflow that forward messages of the specified type from the source executor to
|
||||
@@ -34,26 +34,38 @@ public static class WorkflowBuilderExtensions
|
||||
/// <typeparam name="TMessage">The type of message to forward.</typeparam>
|
||||
/// <param name="builder">The <see cref="WorkflowBuilder"/> to which the edges will be added.</param>
|
||||
/// <param name="source">The source executor from which messages will be forwarded.</param>
|
||||
/// <param name="targets">The target executors to which messages will be forwarded.</param>
|
||||
/// <returns>The updated <see cref="WorkflowBuilder"/> instance.</returns>
|
||||
public static WorkflowBuilder ForwardMessage<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, IEnumerable<ExecutorBinding> targets)
|
||||
=> builder.ForwardMessage<TMessage>(source, targets, condition: null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds edges to the workflow that forward messages of the specified type from the source executor to
|
||||
/// one or more target executors.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The type of message to forward.</typeparam>
|
||||
/// <param name="builder">The <see cref="WorkflowBuilder"/> to which the edges will be added.</param>
|
||||
/// <param name="source">The source executor from which messages will be forwarded.</param>
|
||||
/// <param name="targets">The target executors to which messages will be forwarded.</param>
|
||||
/// <param name="condition">An optional condition that messages must satisfy to be forwarded. If <see langword="null"/>,
|
||||
/// all messages of type <typeparamref name="TMessage"/> will be forwarded.</param>
|
||||
/// <param name="executors">The target executors to which messages will be forwarded.</param>
|
||||
/// <returns>The updated <see cref="WorkflowBuilder"/> instance.</returns>
|
||||
public static WorkflowBuilder ForwardMessage<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, Func<TMessage, bool>? condition = null, params IEnumerable<ExecutorBinding> executors)
|
||||
public static WorkflowBuilder ForwardMessage<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, IEnumerable<ExecutorBinding> targets, Func<TMessage, bool>? condition = null)
|
||||
{
|
||||
Throw.IfNull(executors);
|
||||
Throw.IfNull(targets);
|
||||
|
||||
Func<object?, bool> predicate = WorkflowBuilder.CreateConditionFunc<TMessage>(IsAllowedTypeAndMatchingCondition)!;
|
||||
|
||||
#if NET
|
||||
if (executors.TryGetNonEnumeratedCount(out int count) && count == 1)
|
||||
if (targets.TryGetNonEnumeratedCount(out int count) && count == 1)
|
||||
#else
|
||||
if (executors is ICollection<ExecutorBinding> { Count: 1 })
|
||||
if (targets is ICollection<ExecutorBinding> { Count: 1 })
|
||||
#endif
|
||||
{
|
||||
return builder.AddEdge(source, executors.First(), predicate);
|
||||
return builder.AddEdge(source, targets.First(), predicate);
|
||||
}
|
||||
|
||||
return builder.AddSwitch(source, (switch_) => switch_.AddCase(predicate, executors));
|
||||
return builder.AddSwitch(source, (switch_) => switch_.AddCase(predicate, targets));
|
||||
|
||||
// The reason we can check for "not null" here is that CreateConditionFunc<T> will do the correct unwrapping
|
||||
// logic for PortableValues.
|
||||
@@ -66,24 +78,35 @@ public static class WorkflowBuilderExtensions
|
||||
/// <typeparam name="TMessage">The type of messages to exclude from being forwarded to the executors.</typeparam>
|
||||
/// <param name="builder">The <see cref="WorkflowBuilder"/> instance to which the edges will be added.</param>
|
||||
/// <param name="source">The source executor from which messages will be forwarded.</param>
|
||||
/// <param name="executors">The target executors to which messages, except those of type <typeparamref name="TMessage"/>, will be forwarded.</param>
|
||||
/// <param name="target">The target executor to which messages, except those of type <typeparamref name="TMessage"/>, will be forwarded.</param>
|
||||
/// <returns>The updated <see cref="WorkflowBuilder"/> instance with the added edges.</returns>
|
||||
public static WorkflowBuilder ForwardExcept<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, params IEnumerable<ExecutorBinding> executors)
|
||||
public static WorkflowBuilder ForwardExcept<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, ExecutorBinding target)
|
||||
=> builder.ForwardExcept<TMessage>(source, [target]);
|
||||
|
||||
/// <summary>
|
||||
/// Adds edges from the specified source to the provided executors, excluding messages of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The type of messages to exclude from being forwarded to the executors.</typeparam>
|
||||
/// <param name="builder">The <see cref="WorkflowBuilder"/> instance to which the edges will be added.</param>
|
||||
/// <param name="source">The source executor from which messages will be forwarded.</param>
|
||||
/// <param name="targets">The target executors to which messages, except those of type <typeparamref name="TMessage"/>, will be forwarded.</param>
|
||||
/// <returns>The updated <see cref="WorkflowBuilder"/> instance with the added edges.</returns>
|
||||
public static WorkflowBuilder ForwardExcept<TMessage>(this WorkflowBuilder builder, ExecutorBinding source, IEnumerable<ExecutorBinding> targets)
|
||||
{
|
||||
Throw.IfNull(executors);
|
||||
Throw.IfNull(targets);
|
||||
|
||||
Func<object?, bool> predicate = WorkflowBuilder.CreateConditionFunc<TMessage>((Func<object?, bool>)IsAllowedType)!;
|
||||
|
||||
#if NET
|
||||
if (executors.TryGetNonEnumeratedCount(out int count) && count == 1)
|
||||
if (targets.TryGetNonEnumeratedCount(out int count) && count == 1)
|
||||
#else
|
||||
if (executors is ICollection<ExecutorBinding> { Count: 1 })
|
||||
if (targets is ICollection<ExecutorBinding> { Count: 1 })
|
||||
#endif
|
||||
{
|
||||
return builder.AddEdge(source, executors.First(), predicate);
|
||||
return builder.AddEdge(source, targets.First(), predicate);
|
||||
}
|
||||
|
||||
return builder.AddSwitch(source, (switch_) => switch_.AddCase(predicate, executors));
|
||||
return builder.AddSwitch(source, (switch_) => switch_.AddCase(predicate, targets));
|
||||
|
||||
// The reason we can check for "null" here is that CreateConditionFunc<T> will do the correct unwrapping
|
||||
// logic for PortableValues.
|
||||
@@ -98,11 +121,11 @@ public static class WorkflowBuilderExtensions
|
||||
/// executor in the order provided.</remarks>
|
||||
/// <param name="builder">The workflow builder to which the executor chain will be added. </param>
|
||||
/// <param name="source">The initial executor in the chain. Cannot be null.</param>
|
||||
/// <param name="allowRepetition">If set to <see langword="true"/>, the same executor can be added to the chain multiple times.</param>
|
||||
/// <param name="executors">An ordered array of executors to be added to the chain after the source.</param>
|
||||
/// <param name="executors">An ordered sequence of executors to be added to the chain after the source.</param>
|
||||
/// <returns>The original workflow builder instance with the specified executor chain added.</returns>
|
||||
/// <param name="allowRepetition">If set to <see langword="true"/>, the same executor can be added to the chain multiple times.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if there is a cycle in the chain.</exception>
|
||||
public static WorkflowBuilder AddChain(this WorkflowBuilder builder, ExecutorBinding source, bool allowRepetition = false, params IEnumerable<ExecutorBinding> executors)
|
||||
public static WorkflowBuilder AddChain(this WorkflowBuilder builder, ExecutorBinding source, IList<ExecutorBinding> executors, bool allowRepetition = false)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
Throw.IfNull(source);
|
||||
|
||||
Reference in New Issue
Block a user