mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
907654a489
* Initial working version with tests.
* Updates to validate class data once instead of for each handler method. Also updated Diagnostics Ids to format of MAFGENWF{NUM}
* Formatting and trying to fix generation project pack.
* Another atempt at getting the genrators project to build.
* More attempts to fix generator build and pack.
* Fixing file encodings.
* Initail round of cleanup.
* Trying to fix packing.
* Still trying to fix pipeline pack.
* Remove obsolescence markers, sample updates, and docs from generator branch.
This commit separates the generator core functionality from the
deprecation of ReflectingExecutor. The removed changes will be
re-added in a dependent branch (wf-obsolete-reflector).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark ReflectingExecutor and IMessageHandler as obsolete.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Obsoleteing Reflector-based workflow code generation in favor of Source Generators and updating some samples to use new pattern.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Cleaning up temporary design and progress files.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
189 lines
9.3 KiB
C#
189 lines
9.3 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
#pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility
|
|
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.AI.Workflows.Reflection;
|
|
using Microsoft.Shared.Diagnostics;
|
|
|
|
namespace Microsoft.Agents.AI.Workflows;
|
|
|
|
/// <summary>
|
|
/// Provides a base class for executors that maintain and manage state across multiple message handling operations.
|
|
/// </summary>
|
|
/// <typeparam name="TState">The type of state associated with this Executor.</typeparam>
|
|
public abstract class StatefulExecutor<TState> : Executor
|
|
{
|
|
private readonly Func<TState> _initialStateFactory;
|
|
|
|
private TState? _stateCache;
|
|
|
|
/// <summary>
|
|
/// Initializes the executor with a unique id and an initial value for the state.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier for this executor instance. Cannot be null or empty.</param>
|
|
/// <param name="initialStateFactory">A factory to initialize the state value to be used by the executor.</param>
|
|
/// <param name="options">Optional configuration settings for the executor. If null, default options are used.</param>
|
|
/// <param name="declareCrossRunShareable">true to declare that the executor's state can be shared across multiple runs; otherwise, false.</param>
|
|
protected StatefulExecutor(string id,
|
|
Func<TState> initialStateFactory,
|
|
StatefulExecutorOptions? options = null,
|
|
bool declareCrossRunShareable = false)
|
|
: base(id, options ?? new StatefulExecutorOptions(), declareCrossRunShareable)
|
|
{
|
|
this.Options = (StatefulExecutorOptions)base.Options;
|
|
this._initialStateFactory = Throw.IfNull(initialStateFactory);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected new StatefulExecutorOptions Options { get; }
|
|
|
|
private string DefaultStateKey => $"{this.GetType().Name}.State";
|
|
|
|
/// <summary>
|
|
/// Gets the key used to identify the executor's state.
|
|
/// </summary>
|
|
protected string StateKey => this.Options.StateKey ?? this.DefaultStateKey;
|
|
|
|
/// <summary>
|
|
/// Reads the state associated with this executor. If it is not initialized, it will be set to the initial state.
|
|
/// </summary>
|
|
/// <param name="context">The workflow context in which the executor executes.</param>
|
|
/// <param name="skipCache">Ignore the cached value, if any. State is not cached when running in Cross-Run Shareable
|
|
/// mode.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
|
/// The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns></returns>
|
|
protected async ValueTask<TState> ReadStateAsync(IWorkflowContext context, bool skipCache = false, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!skipCache && this._stateCache is not null)
|
|
{
|
|
return this._stateCache;
|
|
}
|
|
|
|
TState? state = await context.ReadOrInitStateAsync(this.StateKey, this._initialStateFactory, this.Options.ScopeName, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
if (!context.ConcurrentRunsEnabled)
|
|
{
|
|
this._stateCache = state;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues up an update to the executor's state.
|
|
/// </summary>
|
|
/// <param name="state">The new value of state.</param>
|
|
/// <param name="context">The workflow context in which the executor executes.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
|
/// The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns></returns>
|
|
protected ValueTask QueueStateUpdateAsync(TState state, IWorkflowContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!context.ConcurrentRunsEnabled)
|
|
{
|
|
this._stateCache = state;
|
|
}
|
|
|
|
return context.QueueStateUpdateAsync(this.StateKey, state, this.Options.ScopeName, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes an asynchronous operation that reads, updates, and persists workflow state associated with the specified
|
|
/// key.
|
|
/// </summary>
|
|
/// <param name="invocation">A delegate that receives the current state, workflow context, and cancellation token,
|
|
/// and returns the updated state asynchronously.</param>
|
|
/// <param name="context">The workflow context in which the executor executes.</param>
|
|
/// <param name="skipCache">Ignore the cached value, if any. State is not cached when running in Cross-Run Shareable
|
|
/// mode.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
|
/// The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns>A ValueTask that represents the asynchronous operation.</returns>
|
|
protected async ValueTask InvokeWithStateAsync(
|
|
Func<TState, IWorkflowContext, CancellationToken, ValueTask<TState?>> invocation,
|
|
IWorkflowContext context,
|
|
bool skipCache = false,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (!skipCache && !context.ConcurrentRunsEnabled)
|
|
{
|
|
TState newState = await invocation(this._stateCache ?? this._initialStateFactory(),
|
|
context,
|
|
cancellationToken).ConfigureAwait(false)
|
|
?? this._initialStateFactory();
|
|
|
|
await context.QueueStateUpdateAsync(this.StateKey,
|
|
newState,
|
|
this.Options.ScopeName,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
this._stateCache = newState;
|
|
}
|
|
else
|
|
{
|
|
await context.InvokeWithStateAsync(invocation,
|
|
this.StateKey,
|
|
this._initialStateFactory,
|
|
this.Options.ScopeName,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IResettableExecutor.ResetAsync"/>
|
|
protected ValueTask ResetAsync()
|
|
{
|
|
this._stateCache = this._initialStateFactory();
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a simple executor implementation that uses a single message handler function to process incoming messages,
|
|
/// and maintain state across invocations.
|
|
/// </summary>
|
|
/// <typeparam name="TState">The type of state associated with this Executor.</typeparam>
|
|
/// <typeparam name="TInput">The type of input message.</typeparam>
|
|
/// <param name="id">A unique identifier for the executor.</param>
|
|
/// <param name="initialStateFactory">A factory to initialize the state value to be used by the executor.</param>
|
|
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
|
|
/// <param name="declareCrossRunShareable">Declare that this executor may be used simultaneously by multiple runs safely.</param>
|
|
public abstract class StatefulExecutor<TState, TInput>(string id, Func<TState> initialStateFactory, StatefulExecutorOptions? options = null, bool declareCrossRunShareable = false)
|
|
: StatefulExecutor<TState>(id, initialStateFactory, options, declareCrossRunShareable), IMessageHandler<TInput>
|
|
{
|
|
/// <inheritdoc/>
|
|
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
|
routeBuilder.AddHandler<TInput>(this.HandleAsync);
|
|
|
|
/// <inheritdoc/>
|
|
public abstract ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a simple executor implementation that uses a single message handler function to process incoming messages,
|
|
/// and maintain state across invocations.
|
|
/// </summary>
|
|
/// <typeparam name="TState">The type of state associated with this Executor.</typeparam>
|
|
/// <typeparam name="TInput">The type of input message.</typeparam>
|
|
/// <typeparam name="TOutput">The type of output message.</typeparam>
|
|
/// <param name="id">A unique identifier for the executor.</param>
|
|
/// <param name="initialStateFactory">A factory to initialize the state value to be used by the executor.</param>
|
|
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
|
|
/// <param name="declareCrossRunShareable">Declare that this executor may be used simultaneously by multiple runs safely.</param>
|
|
public abstract class StatefulExecutor<TState, TInput, TOutput>(string id, Func<TState> initialStateFactory, StatefulExecutorOptions? options = null, bool declareCrossRunShareable = false)
|
|
: StatefulExecutor<TState>(id, initialStateFactory, options, declareCrossRunShareable), IMessageHandler<TInput, TOutput>
|
|
{
|
|
/// <inheritdoc/>
|
|
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
|
routeBuilder.AddHandler<TInput, TOutput>(this.HandleAsync);
|
|
|
|
/// <inheritdoc/>
|
|
public abstract ValueTask<TOutput> HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken = default);
|
|
}
|