mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Workflows - Add declarative action events (#679)
* Updated * Typos * Update sample * Typo * Add parent and prior action ids to event
This commit is contained in:
@@ -94,11 +94,19 @@ internal sealed class Program
|
||||
{
|
||||
if (evt is ExecutorInvokeEvent executorInvoked)
|
||||
{
|
||||
Debug.WriteLine($"STEP ENTER #{executorInvoked.ExecutorId}");
|
||||
Debug.WriteLine($"EXECUTOR ENTER #{executorInvoked.ExecutorId}");
|
||||
}
|
||||
else if (evt is ExecutorCompleteEvent executorComplete)
|
||||
{
|
||||
Debug.WriteLine($"STEP EXIT #{executorComplete.ExecutorId}");
|
||||
Debug.WriteLine($"EXECUTOR EXIT #{executorComplete.ExecutorId}");
|
||||
}
|
||||
if (evt is DeclarativeActionInvokeEvent actionInvoked)
|
||||
{
|
||||
Debug.WriteLine($"ACTION ENTER #{actionInvoked.ActionId} [{actionInvoked.ActionType}]");
|
||||
}
|
||||
else if (evt is DeclarativeActionCompleteEvent actionComplete)
|
||||
{
|
||||
Debug.WriteLine($"ACTION EXIT #{actionComplete.ActionId} [{actionComplete.ActionType}]");
|
||||
}
|
||||
else if (evt is ExecutorFailureEvent executorFailure)
|
||||
{
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative;
|
||||
|
||||
/// <summary>
|
||||
/// Event that represents a message produced by a declarative workflow.
|
||||
/// </summary>
|
||||
public class ConversationUpdateEvent(string executorid, string conversationId) : ExecutorEvent(executorid, conversationId)
|
||||
{
|
||||
/// <summary>
|
||||
/// The conversation ID associated with the workflow.
|
||||
/// </summary>
|
||||
public string ConversationId { get; } = conversationId;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative;
|
||||
|
||||
/// <summary>
|
||||
/// Event that broadcasts the conversation identifier.
|
||||
/// </summary>
|
||||
public class ConversationUpdateEvent(string executorid, string conversationId) : ExecutorEvent(executorid, conversationId)
|
||||
{
|
||||
/// <summary>
|
||||
/// The conversation ID associated with the workflow.
|
||||
/// </summary>
|
||||
public string ConversationId { get; } = conversationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that indicates a declarative action has been invoked.
|
||||
/// </summary>
|
||||
public class DeclarativeActionInvokeEvent(string actionId, DialogAction action, string? priorActionId) : WorkflowEvent(action)
|
||||
{
|
||||
/// <summary>
|
||||
/// The declarative action id.
|
||||
/// </summary>
|
||||
public string ActionId => actionId;
|
||||
|
||||
/// <summary>
|
||||
/// The declarative action type name.
|
||||
/// </summary>
|
||||
public string ActionType => action.GetType().Name;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the parent action.
|
||||
/// </summary>
|
||||
public string? ParentActionId => action.GetParentId();
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the previous action.
|
||||
/// </summary>
|
||||
public string? PriorActionId => priorActionId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that indicates a declarative action has completed.
|
||||
/// </summary>
|
||||
public class DeclarativeActionCompleteEvent(string actionId, DialogAction action) : WorkflowEvent(action)
|
||||
{
|
||||
/// <summary>
|
||||
/// The declarative action identifier.
|
||||
/// </summary>
|
||||
public string ActionId => actionId;
|
||||
|
||||
/// <summary>
|
||||
/// The declarative action type name.
|
||||
/// </summary>
|
||||
public string ActionType => action.GetType().Name;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the parent action.
|
||||
/// </summary>
|
||||
public string? ParentActionId => action.GetParentId();
|
||||
}
|
||||
+17
-5
@@ -7,7 +7,6 @@ using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.Workflows.Reflection;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
@@ -25,9 +24,7 @@ internal abstract class DeclarativeActionExecutor<TAction>(TAction model, Declar
|
||||
public new TAction Model => (TAction)base.Model;
|
||||
}
|
||||
|
||||
internal abstract class WorkflowActionExecutor :
|
||||
ReflectingExecutor<WorkflowActionExecutor>,
|
||||
IMessageHandler<DeclarativeExecutorResult>
|
||||
internal abstract class WorkflowActionExecutor : Executor<DeclarativeExecutorResult>
|
||||
{
|
||||
public const string RootActionId = "(root)";
|
||||
|
||||
@@ -60,8 +57,10 @@ internal abstract class WorkflowActionExecutor :
|
||||
|
||||
protected DeclarativeWorkflowState State { get; }
|
||||
|
||||
protected virtual bool IsDiscreteAction => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandleAsync(DeclarativeExecutorResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(DeclarativeExecutorResult message, IWorkflowContext context)
|
||||
{
|
||||
if (this.Model.Disabled)
|
||||
{
|
||||
@@ -69,6 +68,8 @@ internal abstract class WorkflowActionExecutor :
|
||||
return;
|
||||
}
|
||||
|
||||
await this.RaiseInvocationEventAsync(context, message.ExecutorId).ConfigureAwait(false);
|
||||
|
||||
await this.State.RestoreAsync(context, default).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
@@ -87,6 +88,13 @@ internal abstract class WorkflowActionExecutor :
|
||||
Debug.WriteLine($"ERROR [{this.Id}] {exception.GetType().Name}\n{exception.Message}");
|
||||
throw new DeclarativeActionException($"Unhandled workflow failure - #{this.Id} ({this.Model.GetType().Name})", exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.IsDiscreteAction)
|
||||
{
|
||||
await this.RaiseCompletionEventAsync(context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default);
|
||||
@@ -117,4 +125,8 @@ internal abstract class WorkflowActionExecutor :
|
||||
string message = $"Unexpected workflow failure during {this.Model.GetType().Name} [{this.Id}]: {text}";
|
||||
return exception is null ? new(message) : new(message, exception);
|
||||
}
|
||||
|
||||
protected ValueTask RaiseInvocationEventAsync(IWorkflowContext context, string? priorEventId = null) => context.AddEventAsync(new DeclarativeActionInvokeEvent(this.Id, this.Model, priorEventId));
|
||||
|
||||
protected ValueTask RaiseCompletionEventAsync(IWorkflowContext context) => context.AddEventAsync(new DeclarativeActionCompleteEvent(this.Id, this.Model));
|
||||
}
|
||||
|
||||
+6
-5
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.Interpreter;
|
||||
@@ -11,12 +10,14 @@ namespace Microsoft.Agents.Workflows.Declarative.Interpreter;
|
||||
/// <summary>
|
||||
/// The root executor for a declarative workflow.
|
||||
/// </summary>
|
||||
internal sealed class DeclarativeWorkflowExecutor<TInput>(string workflowId, DeclarativeWorkflowState state, Func<TInput, ChatMessage> inputTransform) :
|
||||
ReflectingExecutor<DeclarativeWorkflowExecutor<TInput>>(workflowId),
|
||||
IMessageHandler<TInput>
|
||||
internal sealed class DeclarativeWorkflowExecutor<TInput>(
|
||||
string workflowId,
|
||||
DeclarativeWorkflowState state,
|
||||
Func<TInput, ChatMessage> inputTransform) :
|
||||
Executor<TInput>(workflowId)
|
||||
where TInput : notnull
|
||||
{
|
||||
public async ValueTask HandleAsync(TInput message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(TInput message, IWorkflowContext context)
|
||||
{
|
||||
ChatMessage input = inputTransform.Invoke(message);
|
||||
await state.SetLastMessageAsync(context, input).ConfigureAwait(false);
|
||||
|
||||
+2
-3
@@ -2,13 +2,12 @@
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Workflows.Reflection;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.Interpreter;
|
||||
|
||||
internal delegate ValueTask DelegateAction(IWorkflowContext context, CancellationToken cancellationToken);
|
||||
|
||||
internal sealed class DelegateActionExecutor : ReflectingExecutor<DelegateActionExecutor>, IMessageHandler<DeclarativeExecutorResult>
|
||||
internal sealed class DelegateActionExecutor : Executor<DeclarativeExecutorResult>
|
||||
{
|
||||
private readonly DelegateAction? _action;
|
||||
|
||||
@@ -18,7 +17,7 @@ internal sealed class DelegateActionExecutor : ReflectingExecutor<DelegateAction
|
||||
this._action = action;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(DeclarativeExecutorResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(DeclarativeExecutorResult message, IWorkflowContext context)
|
||||
{
|
||||
if (this._action is not null)
|
||||
{
|
||||
|
||||
+4
-4
@@ -80,7 +80,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
|
||||
// Complete the condition item.
|
||||
void CompletionHandler()
|
||||
{
|
||||
string completionId = this.ContinuationFor(stepId); // End items
|
||||
string completionId = this.ContinuationFor(stepId, conditionGroup.DoneAsync); // End items
|
||||
this._workflowModel.AddLink(completionId, PostId(conditionGroup.Id)); // Merge with parent scope
|
||||
|
||||
// Merge link when no action group is defined
|
||||
@@ -461,12 +461,12 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
|
||||
item.GetParentId() ??
|
||||
throw new DeclarativeModelException($"Missing parent ID for action element: {item.GetId()} [{item.GetType().Name}].");
|
||||
|
||||
private string ContinuationFor(string parentId) => this.ContinuationFor(parentId, parentId);
|
||||
private string ContinuationFor(string parentId, DelegateAction? stepAction = null) => this.ContinuationFor(parentId, parentId, stepAction);
|
||||
|
||||
private string ContinuationFor(string actionId, string parentId)
|
||||
private string ContinuationFor(string actionId, string parentId, DelegateAction? stepAction = null)
|
||||
{
|
||||
actionId = PostId(actionId);
|
||||
this._workflowModel.AddNode(this.CreateStep(actionId), parentId);
|
||||
this._workflowModel.AddNode(this.CreateStep(actionId, stepAction), parentId);
|
||||
return actionId;
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -31,6 +31,8 @@ internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor<Conditi
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsDiscreteAction => false;
|
||||
|
||||
public bool IsMatch(ConditionItem conditionItem, object? result)
|
||||
{
|
||||
if (result is not DeclarativeExecutorResult message)
|
||||
@@ -72,4 +74,9 @@ internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor<Conditi
|
||||
|
||||
return Steps.Else(this.Model);
|
||||
}
|
||||
|
||||
public async ValueTask DoneAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.RaiseCompletionEventAsync(context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
|
||||
|
||||
public bool HasValue { get; private set; }
|
||||
|
||||
protected override bool IsDiscreteAction => false;
|
||||
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
this._index = 0;
|
||||
@@ -78,10 +80,17 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
|
||||
|
||||
public async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
this.State.Reset(Throw.IfNull(this.Model.Value));
|
||||
if (this.Model.Index is not null)
|
||||
try
|
||||
{
|
||||
this.State.Reset(this.Model.Index);
|
||||
this.State.Reset(Throw.IfNull(this.Model.Value));
|
||||
if (this.Model.Index is not null)
|
||||
{
|
||||
this.State.Reset(this.Model.Index);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await this.RaiseCompletionEventAsync(context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -33,6 +33,8 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
workflowBuilder.AddEdge(workflowExecutor, executor);
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build<WorkflowScopes>(), this.Scopes);
|
||||
WorkflowEvent[] events = await run.WatchStreamAsync().ToArrayAsync();
|
||||
Assert.Contains(events, e => e is DeclarativeActionInvokeEvent);
|
||||
Assert.Contains(events, e => e is DeclarativeActionCompleteEvent);
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user