From fb513c38a623e783a3b1891d1672817ad1ed628d Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:09:12 -0700 Subject: [PATCH] .NET Workflow - Declarative State Consolidation (#759) * Checkpoint * Update workflows/DeepResearch.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Comment * Fix comment * Update package version * Fix nuget haxx * Checkpoint * Code complete * Testing * Message content workaround * Add sequential flow * Checkpoint * Integration test project * Checkpoint * Checkpoint cleanup * Complete * Checkpoint * Fix tests * Comment cleanup * Namespace * Formatting * Analyzer updates * Workflow update * Fixed! * Update dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs Co-authored-by: Tao Chen * Fix build error * Purge "immutable" set and dictionary * Fix as task * Collection expression * Another * Frozen => Readonly (perf) * Fix * Namespace --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Tao Chen --- dotnet/Directory.Packages.props | 1 + .../DeclarativeWorkflowBuilder.cs | 34 +-- .../Extensions/ChatMessageExtensions.cs | 6 +- .../Extensions/DataValueExtensions.cs | 24 ++- .../Extensions/DialogBaseExtensions.cs | 27 +++ .../Extensions/FormulaValueExtensions.cs | 4 +- .../Extensions/IWorkflowContextExtensions.cs | 19 ++ .../Interpreter/DeclarativeActionExecutor.cs | 27 ++- .../Interpreter/DeclarativeWorkflowContext.cs | 81 +++++++ .../DeclarativeWorkflowExecutor.cs | 7 +- .../Interpreter/DeclarativeWorkflowModel.cs | 5 +- .../Interpreter/DeclarativeWorkflowState.cs | 103 --------- .../Interpreter/DelegateActionExecutor.cs | 11 +- .../Interpreter/WorkflowActionVisitor.cs | 31 +-- .../Interpreter/WorkflowElementWalker.cs | 7 +- ...rosoft.Agents.Workflows.Declarative.csproj | 1 + .../AddConversationMessageExecutor.cs | 11 +- .../ObjectModel/ClearAllVariablesExecutor.cs | 10 +- .../ObjectModel/ConditionGroupExecutor.cs | 5 +- .../CopyConversationMessagesExecutor.cs | 7 +- .../ObjectModel/CreateConversationExecutor.cs | 3 +- .../ObjectModel/EditTableExecutor.cs | 9 +- .../ObjectModel/EditTableV2Executor.cs | 9 +- .../ObjectModel/ForeachExecutor.cs | 9 +- .../ObjectModel/GotoExecutor.cs | 3 +- .../ObjectModel/InvokeAzureAgentExecutor.cs | 14 +- .../ObjectModel/ParseValueExecutor.cs | 5 +- .../ObjectModel/QuestionExecutor.cs | 16 +- .../ObjectModel/ResetVariableExecutor.cs | 6 +- .../RetrieveConversationMessageExecutor.cs | 7 +- .../RetrieveConversationMessagesExecutor.cs | 11 +- .../ObjectModel/SendActivityExecutor.cs | 6 +- .../SetMultipleVariablesExecutor.cs | 5 +- .../ObjectModel/SetTextVariableExecutor.cs | 6 +- .../ObjectModel/SetVariableExecutor.cs | 5 +- .../PowerFx/SystemScope.cs | 32 +-- .../PowerFx/UnassignedValue.cs | 14 ++ .../PowerFx/WorkflowDiagnostics.cs | 22 +- .../PowerFx/WorkflowExpressionEngine.cs | 125 +++-------- .../PowerFx/WorkflowFormulaState.cs | 191 ++++++++++++++++ .../PowerFx/WorkflowScopes.cs | 129 ----------- .../DeclarativeWorkflowTest.cs | 9 +- .../ClearAllVariablesExecutorTest.cs | 6 +- .../ObjectModel/ParseValueExecutorTest.cs | 8 +- .../ObjectModel/ResetVariableExecutorTest.cs | 10 +- .../ObjectModel/SendActivityExecutorTest.cs | 2 +- .../SetTextVariableExecutorTest.cs | 6 +- .../ObjectModel/SetVariableExecutorTest.cs | 14 +- .../ObjectModel/WorkflowActionExecutorTest.cs | 14 +- .../PowerFx/RecalcEngineFactoryTests.cs | 12 +- .../PowerFx/RecalcEngineTest.cs | 4 +- .../PowerFx/TemplateExtensionsTests.cs | 31 +-- .../PowerFx/WorkflowExpressionEngineTests.cs | 203 ++++++------------ .../PowerFx/WorkflowScopesTests.cs | 78 +++---- .../Workflows/Condition.yaml | 2 +- .../Workflows/ConditionElse.yaml | 2 +- .../Workflows/Goto.yaml | 6 +- .../Workflows/SendActivity.yaml | 16 ++ .../Workflows/SetTextVariable.yaml | 2 +- .../Workflows/SetVariable.yaml | 10 + workflows/HumanInLoop.yaml | 29 ++- workflows/MathChat.yaml | 8 +- 62 files changed, 768 insertions(+), 752 deletions(-) create mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DialogBaseExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs delete mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowState.cs create mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/UnassignedValue.cs create mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowFormulaState.cs delete mode 100644 dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowScopes.cs create mode 100644 dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SendActivity.yaml create mode 100644 dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetVariable.yaml diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 184812cab2..cc9ddcbdf4 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -37,6 +37,7 @@ + diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/DeclarativeWorkflowBuilder.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/DeclarativeWorkflowBuilder.cs index a8f0eaad05..2225921310 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/DeclarativeWorkflowBuilder.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/DeclarativeWorkflowBuilder.cs @@ -2,7 +2,6 @@ using System; using System.IO; -using System.Linq; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; using Microsoft.Agents.Workflows.Declarative.PowerFx; @@ -22,7 +21,7 @@ public static class DeclarativeWorkflowBuilder /// /// The type of the input message /// The path to the workflow. - /// The execution context for the workflow. + /// Configuration options for workflow execution. /// An optional function to transform the input message into a . /// public static Workflow Build( @@ -40,7 +39,7 @@ public static class DeclarativeWorkflowBuilder /// /// The type of the input message /// The reader that provides the workflow object model YAML. - /// The execution context for the workflow. + /// Configuration options for workflow execution. /// An optional function to transform the input message into a . /// The that corresponds with the YAML object model. public static Workflow Build( @@ -59,18 +58,18 @@ public static class DeclarativeWorkflowBuilder string rootId = WorkflowActionVisitor.Steps.Root(workflowElement.BeginDialog?.Id.Value); - WorkflowScopes scopes = new(); - scopes.Initialize(WrapWithBot(workflowElement), options.Configuration); - DeclarativeWorkflowState state = new(options.CreateRecalcEngine(), scopes); + WorkflowFormulaState state = new(options.CreateRecalcEngine()); + state.Initialize(workflowElement.WrapWithBot(), options.Configuration); DeclarativeWorkflowExecutor rootExecutor = new(rootId, state, message => inputTransform?.Invoke(message) ?? DefaultTransform(message)); WorkflowActionVisitor visitor = new(rootExecutor, state, options); - WorkflowElementWalker walker = new(rootElement, visitor); + WorkflowElementWalker walker = new(visitor); + walker.Visit(rootElement); - return walker.GetWorkflow(); + return visitor.Complete(); } private static ChatMessage DefaultTransform(object message) => @@ -80,23 +79,4 @@ public static class DeclarativeWorkflowBuilder string stringMessage => new ChatMessage(ChatRole.User, stringMessage), _ => new(ChatRole.User, $"{message}") }; - - // Wrap with bot to ensure schema is set. - private static AdaptiveDialog WrapWithBot(AdaptiveDialog dialog) - { - BotDefinition bot - = new BotDefinition.Builder - { - Components = - { - new DialogComponent.Builder - { - SchemaName = dialog.HasSchemaName ? dialog.SchemaName : "default-schema", - Dialog = new AdaptiveDialog.Builder(dialog), - } - } - }.Build(); - - return bot.Descendants().OfType().First(); - } } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/ChatMessageExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/ChatMessageExtensions.cs index 32de8a66a6..34e7efdb80 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/ChatMessageExtensions.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/ChatMessageExtensions.cs @@ -141,7 +141,7 @@ internal static class ChatMessageExtensions } AgentMessageRole? role = null; - if (Enum.TryParse(roleValue.Value, out AgentMessageRole parsedRole)) + if (Enum.TryParse(roleValue.Value, out AgentMessageRole parsedRole)) { role = parsedRole; } @@ -188,13 +188,13 @@ internal static class ChatMessageExtensions yield return new NamedValue(TypeSchema.Message.Fields.Id, message.MessageId.ToFormula()); yield return new NamedValue(TypeSchema.Message.Fields.Role, message.Role.Value.ToFormula()); yield return new NamedValue(TypeSchema.Message.Fields.Author, message.AuthorName.ToFormula()); - yield return new NamedValue(TypeSchema.Message.Fields.Content, TableValue.NewTable(s_contentRecordType, message.GetContentRecords())); + yield return new NamedValue(TypeSchema.Message.Fields.Content, FormulaValue.NewTable(s_contentRecordType, message.GetContentRecords())); yield return new NamedValue(TypeSchema.Message.Fields.Text, message.Text.ToFormula()); yield return new NamedValue(TypeSchema.Message.Fields.Metadata, message.AdditionalProperties.ToRecord()); } private static IEnumerable GetContentRecords(this ChatMessage message) => - message.Contents.Select(content => RecordValue.NewRecordFromFields(content.GetContentFields())); + message.Contents.Select(content => FormulaValue.NewRecordFromFields(content.GetContentFields())); private static IEnumerable GetContentFields(this AIContent content) { diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DataValueExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DataValueExtensions.cs index 687cc302f2..7fa82066e1 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DataValueExtensions.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DataValueExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Dynamic; using System.Linq; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; @@ -59,13 +60,13 @@ internal static class DataValueExtensions null => null, BlankDataValue => null, BooleanDataValue boolValue => boolValue.Value, - NumberDataValue numberValue => (numberValue.Value), - FloatDataValue floatValue => (floatValue.Value), - StringDataValue stringValue => (stringValue.Value), - DateTimeDataValue dateTimeValue => (dateTimeValue.Value.DateTime), + NumberDataValue numberValue => numberValue.Value, + FloatDataValue floatValue => floatValue.Value, + StringDataValue stringValue => stringValue.Value, + DateTimeDataValue dateTimeValue => dateTimeValue.Value.DateTime, DateDataValue dateValue => dateValue.Value, TimeDataValue timeValue => timeValue.Value, - TableDataValue tableValue => tableValue.Values.Select(value => value.ToRecordValue()).ToArray(), + TableDataValue tableValue => tableValue.Values.Select(value => value.ToObject()).ToArray(), RecordDataValue recordValue => recordValue.ToDictionary(), OptionDataValue optionValue => optionValue.Value.Value, _ => throw new DeclarativeModelException($"Unsupported {nameof(DataValue)} type: {value.GetType().Name}"), @@ -88,6 +89,19 @@ internal static class DataValueExtensions return recordType; } + public static ExpandoObject ToObject(this RecordDataValue recordDataValue) + { + ExpandoObject expandoObject = new(); + + IDictionary dictionary = expandoObject; + foreach (KeyValuePair field in recordDataValue.Properties) + { + dictionary[field.Key] = field.Value?.ToObject(); + } + + return expandoObject; + } + private static RecordType ParseRecordType(this RecordDataValue record) { RecordType recordType = RecordType.Empty(); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DialogBaseExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DialogBaseExtensions.cs new file mode 100644 index 0000000000..97b07068e7 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/DialogBaseExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; +using Microsoft.Bot.ObjectModel; + +namespace Microsoft.Agents.Workflows.Declarative.Extensions; + +internal static class DialogBaseExtensions +{ + public static TDialog WrapWithBot(this TDialog dialog) where TDialog : DialogBase + { + BotDefinition bot + = new BotDefinition.Builder + { + Components = + { + new DialogComponent.Builder + { + SchemaName = dialog.HasSchemaName ? dialog.SchemaName : "default-schema", + Dialog = dialog.ToBuilder(), + } + } + }.Build(); + + return bot.Descendants().OfType().First(); + } +} diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/FormulaValueExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/FormulaValueExtensions.cs index 278a6b0ba9..f815486686 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/FormulaValueExtensions.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/FormulaValueExtensions.cs @@ -8,6 +8,7 @@ using System.Dynamic; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; using BlankType = Microsoft.PowerFx.Types.BlankType; @@ -24,6 +25,7 @@ internal static class FormulaValueExtensions value switch { null => FormulaValue.NewBlank(), + UnassignedValue => FormulaValue.NewBlank(), FormulaValue formulaValue => formulaValue, bool booleanValue => FormulaValue.New(booleanValue), int decimalValue => FormulaValue.New(decimalValue), @@ -142,7 +144,7 @@ internal static class FormulaValueExtensions TableDataValue.TableFromRecords(value.Rows.Select(row => row.Value.ToRecord()).ToImmutableArray()); public static RecordDataValue ToRecord(this RecordValue value) => - RecordDataValue.RecordFromFields(value.OriginalFields.Select(field => field.GetKeyValuePair()).ToImmutableArray()); + RecordDataValue.RecordFromFields(value.OriginalFields.Select(field => field.GetKeyValuePair())); private static RecordValue ToRecord(this IDictionary value) { diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs index fb4312ec42..aa6fc18a98 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs @@ -3,7 +3,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; +using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.Extensions; @@ -17,4 +19,21 @@ internal static class IWorkflowContextExtensions public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result = null, CancellationToken cancellationToken = default) => context.SendMessageAsync(new ExecutorResultMessage(id, result)); + + public static ValueTask QueueStateUpdateAsync(this IWorkflowContext context, PropertyPath variablePath, TValue? value) => + context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.VariableScopeName)); + + public static async Task GetStateAsync(this IWorkflowContext context, CancellationToken cancellationToken) + { + if (context is DeclarativeWorkflowContext declarativeContext) + { + return declarativeContext.State; + } + + WorkflowFormulaState state = new(RecalcEngineFactory.Create()); + + await state.RestoreAsync(context, cancellationToken).ConfigureAwait(false); + + return state; + } } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs index e8978347fd..44e845e63c 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Frozen; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -15,7 +15,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.Interpreter; -internal abstract class DeclarativeActionExecutor(TAction model, DeclarativeWorkflowState state) : +internal abstract class DeclarativeActionExecutor(TAction model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) where TAction : DialogAction { @@ -24,16 +24,15 @@ internal abstract class DeclarativeActionExecutor(TAction model, Declar internal abstract class DeclarativeActionExecutor : Executor { - private static readonly ImmutableHashSet s_mutableScopes = - new HashSet - { - VariableScopeNames.Topic, - VariableScopeNames.Global, - }.ToImmutableHashSet(); + private static readonly FrozenSet s_mutableScopes = + [ + VariableScopeNames.Topic, + VariableScopeNames.Global + ]; private string? _parentId; - protected DeclarativeActionExecutor(DialogAction model, DeclarativeWorkflowState state) + protected DeclarativeActionExecutor(DialogAction model, WorkflowFormulaState state) : base(model.Id.Value) { if (!model.HasRequiredProperties) @@ -51,7 +50,7 @@ internal abstract class DeclarativeActionExecutor : Executor.Instance; - protected DeclarativeWorkflowState State { get; } + protected WorkflowFormulaState State { get; } protected virtual bool IsDiscreteAction => true; @@ -72,7 +71,7 @@ internal abstract class DeclarativeActionExecutor : Executor + public ValueTask AddEventAsync(WorkflowEvent workflowEvent) => this.Source.AddEventAsync(workflowEvent); + + /// + public ValueTask QueueClearScopeAsync(string? scopeName = null) + { + this.State.ResetAll(scopeName); + return this.Source.QueueClearScopeAsync(scopeName); + } + + /// + public async ValueTask QueueStateUpdateAsync(string key, T? value, string? scopeName = null) + { + ValueTask task = value switch + { + null => QueueEmptyStateAsync(), + FormulaValue formulaValue => QueueFormulaStateAsync(formulaValue), + DataValue dataValue => QueueDataValueStateAsync(dataValue), + _ => QueueNativeStateAsync(value), + }; + + await task.ConfigureAwait(false); + + ValueTask QueueEmptyStateAsync() + { + this.State.Set(key, FormulaValue.NewBlank(), scopeName); + return this.Source.QueueStateUpdateAsync(key, UnassignedValue.Instance, scopeName); + } + + ValueTask QueueFormulaStateAsync(FormulaValue formulaValue) + { + this.State.Set(key, formulaValue, scopeName); + return this.Source.QueueStateUpdateAsync(key, formulaValue.ToObject(), scopeName); + } + + ValueTask QueueDataValueStateAsync(DataValue dataValue) + { + FormulaValue formulaValue = dataValue.ToFormula(); + this.State.Set(key, formulaValue, scopeName); + return this.Source.QueueStateUpdateAsync(key, formulaValue.ToObject(), scopeName); + } + + ValueTask QueueNativeStateAsync(object? rawValue) + { + FormulaValue formulaValue = rawValue.ToFormula(); + this.State.Set(key, formulaValue, scopeName); + return this.Source.QueueStateUpdateAsync(key, rawValue, scopeName); + } + } + + /// + public ValueTask ReadStateAsync(string key, string? scopeName = null) => this.Source.ReadStateAsync(key, scopeName); + + /// + public ValueTask> ReadStateKeysAsync(string? scopeName = null) => this.Source.ReadStateKeysAsync(scopeName); + + /// + public ValueTask SendMessageAsync(object message, string? targetId = null) => this.Source.SendMessageAsync(message, targetId); +} diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs index 9e6a39d50f..a912258157 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs @@ -12,15 +12,18 @@ namespace Microsoft.Agents.Workflows.Declarative.Interpreter; /// internal sealed class DeclarativeWorkflowExecutor( string workflowId, - DeclarativeWorkflowState state, + WorkflowFormulaState state, Func inputTransform) : Executor(workflowId) where TInput : notnull { public override async ValueTask HandleAsync(TInput message, IWorkflowContext context) { + // No state to restore if we're starting from the beginning. + state.SetInitialized(); + ChatMessage input = inputTransform.Invoke(message); - await state.SetLastMessageAsync(context, input).ConfigureAwait(false); + state.SetLastMessage(input); await context.SendMessageAsync(new ExecutorResultMessage(this.Id)).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowModel.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowModel.cs index 3bff3ad5db..b78c935020 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowModel.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowModel.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; namespace Microsoft.Agents.Workflows.Declarative.Interpreter; @@ -89,7 +89,8 @@ internal sealed class DeclarativeWorkflowModel public void ConnectNodes(WorkflowBuilder workflowBuilder) { - foreach (ModelNode node in this.Nodes.Values.ToImmutableArray()) + // Push `Values` into array to avoid modification during iteration. + foreach (ModelNode node in this.Nodes.Values.ToArray()) { if (node.CompletionHandler is not null) { diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowState.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowState.cs deleted file mode 100644 index adcfba8798..0000000000 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DeclarativeWorkflowState.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Agents.Workflows.Declarative.Extensions; -using Microsoft.Agents.Workflows.Declarative.PowerFx; -using Microsoft.Bot.ObjectModel; -using Microsoft.PowerFx; -using Microsoft.PowerFx.Types; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Agents.Workflows.Declarative.Interpreter; - -internal sealed class DeclarativeWorkflowState -{ - private static readonly ImmutableHashSet s_mutableScopes = - new HashSet - { - VariableScopeNames.Topic, - VariableScopeNames.Global, - VariableScopeNames.System, - }.ToImmutableHashSet(); - - private readonly RecalcEngine _engine; - private readonly WorkflowScopes _scopes; - private WorkflowExpressionEngine? _expressionEngine; - private int _isInitialized; - - public DeclarativeWorkflowState(RecalcEngine engine, WorkflowScopes? scopes = null) - { - this._scopes = scopes ?? new WorkflowScopes(); - this._engine = engine; - this._scopes.Bind(this._engine); - } - - public WorkflowExpressionEngine ExpressionEngine => this._expressionEngine ??= new WorkflowExpressionEngine(this._engine); - - public void Reset(PropertyPath variablePath) => - this.Reset(Throw.IfNull(variablePath.VariableScopeName), Throw.IfNull(variablePath.VariableName)); - - public void Reset(string scopeName, string? varName = null) - { - if (string.IsNullOrWhiteSpace(varName)) - { - this._scopes.Clear(scopeName); - } - else - { - this._scopes.Reset(varName, scopeName); - } - - this._scopes.Bind(this._engine, scopeName); - } - - public FormulaValue Get(PropertyPath variablePath) => - this.Get(Throw.IfNull(variablePath.VariableScopeName), Throw.IfNull(variablePath.VariableName)); - - public FormulaValue Get(string scope, string varName) => - this._scopes.Get(varName, scope); - - public ValueTask SetAsync(PropertyPath variablePath, FormulaValue value, IWorkflowContext context) => - this.SetAsync(Throw.IfNull(variablePath.VariableScopeName), Throw.IfNull(variablePath.VariableName), value, context); - - public async ValueTask SetAsync(string scopeName, string varName, FormulaValue value, IWorkflowContext context) - { - if (!s_mutableScopes.Contains(scopeName)) - { - throw new DeclarativeModelException($"Invalid scope: {scopeName}"); - } - - this._scopes.Set(varName, value, scopeName); - this._scopes.Bind(this._engine, scopeName); - - await context.QueueStateUpdateAsync(varName, value.ToObject(), scopeName).ConfigureAwait(false); - } - - public string Format(IEnumerable template) => this._engine.Format(template); - - public string Format(TemplateLine? line) => this._engine.Format(line); - - public async ValueTask RestoreAsync(IWorkflowContext context, CancellationToken cancellationToken) - { - if (Interlocked.CompareExchange(ref this._isInitialized, 1, 0) == 1) - { - return; - } - - await Task.WhenAll(s_mutableScopes.Select(scopeName => ReadScopeAsync(scopeName).AsTask())).ConfigureAwait(false); - - async ValueTask ReadScopeAsync(string scopeName) - { - HashSet keys = await context.ReadStateKeysAsync(scopeName).ConfigureAwait(false); - foreach (string key in keys) - { - object? value = await context.ReadStateAsync(key, scopeName).ConfigureAwait(false); - this._scopes.Set(key, value.ToFormula(), scopeName); - } - } - } -} diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs index d9186b1901..efa4f04643 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs @@ -4,13 +4,14 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; +using Microsoft.Agents.Workflows.Declarative.PowerFx; namespace Microsoft.Agents.Workflows.Declarative.Interpreter; internal delegate ValueTask DelegateAction(IWorkflowContext context, TMessage message, CancellationToken cancellationToken) where TMessage : notnull; -internal sealed class DelegateActionExecutor(string actionId, DelegateAction? action = null, bool emitResult = true) - : DelegateActionExecutor(actionId, action, emitResult) +internal sealed class DelegateActionExecutor(string actionId, WorkflowFormulaState state, DelegateAction? action = null, bool emitResult = true) + : DelegateActionExecutor(actionId, state, action, emitResult) { public override ValueTask HandleAsync(ExecutorResultMessage message, IWorkflowContext context) { @@ -22,12 +23,14 @@ internal sealed class DelegateActionExecutor(string actionId, DelegateAction : Executor where TMessage : notnull { + private readonly WorkflowFormulaState _state; private readonly DelegateAction? _action; private readonly bool _emitResult; - public DelegateActionExecutor(string actionId, DelegateAction? action = null, bool emitResult = true) + public DelegateActionExecutor(string actionId, WorkflowFormulaState state, DelegateAction? action = null, bool emitResult = true) : base(actionId) { + this._state = state; this._action = action; this._emitResult = emitResult; } @@ -36,7 +39,7 @@ internal class DelegateActionExecutor : Executor where TMess { if (this._action is not null) { - await this._action.Invoke(context, message, default).ConfigureAwait(false); + await this._action.Invoke(new DeclarativeWorkflowContext(context, this._state), message, default).ConfigureAwait(false); } if (this._emitResult) diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs index a2675bd5d4..7b573c69e2 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.Agents.Workflows.Declarative.Events; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.ObjectModel; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; namespace Microsoft.Agents.Workflows.Declarative.Interpreter; @@ -24,11 +25,11 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor private readonly WorkflowBuilder _workflowBuilder; private readonly DeclarativeWorkflowModel _workflowModel; private readonly DeclarativeWorkflowOptions _workflowOptions; - private readonly DeclarativeWorkflowState _workflowState; + private readonly WorkflowFormulaState _workflowState; public WorkflowActionVisitor( Executor rootAction, - DeclarativeWorkflowState state, + WorkflowFormulaState state, DeclarativeWorkflowOptions options) { this._workflowBuilder = new WorkflowBuilder(rootAction); @@ -60,7 +61,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor parentId = Steps.Root(parentId); } - this.ContinueWith(new DelegateActionExecutor(item.Id.Value), parentId, condition: null, CompletionHandler); + this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId, condition: null, CompletionHandler); // Complete the action scope. void CompletionHandler() @@ -83,7 +84,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor { string stepId = ConditionGroupExecutor.Steps.Item(conditionGroup.Model, item); string parentId = GetParentId(item); - this._workflowModel.AddNode(new DelegateActionExecutor(stepId), parentId, CompletionHandler); + this._workflowModel.AddNode(new DelegateActionExecutor(stepId, this._workflowState), parentId, CompletionHandler); base.VisitConditionItem(item); @@ -151,18 +152,18 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor ForeachExecutor action = new(item, this._workflowState); string loopId = ForeachExecutor.Steps.Next(action.Id); this.ContinueWith(action, condition: null, CompletionHandler); // Foreach - this.ContinueWith(new DelegateActionExecutor(loopId, action.TakeNextAsync), action.Id); // Loop Increment + this.ContinueWith(new DelegateActionExecutor(loopId, this._workflowState, action.TakeNextAsync), action.Id); // Loop Increment string continuationId = this.ContinuationFor(action.Id, action.ParentId); // Action continuation this._workflowModel.AddLink(loopId, continuationId, (_) => !action.HasValue); string startId = ForeachExecutor.Steps.Start(action.Id); - this._workflowModel.AddNode(new DelegateActionExecutor(startId), action.Id); + this._workflowModel.AddNode(new DelegateActionExecutor(startId, this._workflowState), action.Id); this._workflowModel.AddLink(loopId, startId, (_) => action.HasValue); void CompletionHandler() { string endActionsId = ForeachExecutor.Steps.End(action.Id); // Loop continuation - this.ContinueWith(new DelegateActionExecutor(endActionsId, action.ResetAsync), action.Id); + this.ContinueWith(new DelegateActionExecutor(endActionsId, this._workflowState, action.ResetAsync), action.Id); this._workflowModel.AddLink(endActionsId, loopId); } } @@ -175,7 +176,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor if (loopExecutor is not null) { string parentId = GetParentId(item); - this.ContinueWith(new DelegateActionExecutor(item.Id.Value), parentId); + this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId); this._workflowModel.AddLink(item.Id.Value, Steps.Post(loopExecutor.Id)); this.RestartAfter(item.Id.Value, parentId); } @@ -189,7 +190,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor if (loopExecutor is not null) { string parentId = GetParentId(item); - this.ContinueWith(new DelegateActionExecutor(item.Id.Value), parentId); + this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId); this._workflowModel.AddLink(item.Id.Value, ForeachExecutor.Steps.Next(loopExecutor.Id)); this.RestartAfter(item.Id.Value, parentId); } @@ -200,7 +201,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor this.Trace(item); string parentId = GetParentId(item); - this.ContinueWith(new DelegateActionExecutor(item.Id.Value), parentId); + this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId); this.RestartAfter(item.Id.Value, parentId); } @@ -217,7 +218,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor this._workflowModel.AddLink(actionId, postId, message => questionExecutor.IsComplete(message)); string prepareId = QuestionExecutor.Steps.Prepare(actionId); - this.ContinueWith(new DelegateActionExecutor(prepareId, questionExecutor.PrepareResponseAsync, emitResult: false), parentId, message => !questionExecutor.IsComplete(message)); + this.ContinueWith(new DelegateActionExecutor(prepareId, this._workflowState, questionExecutor.PrepareResponseAsync, emitResult: false), parentId, message => !questionExecutor.IsComplete(message)); string inputId = QuestionExecutor.Steps.Input(actionId); InputPort inputPort = InputPort.Create(inputId); @@ -225,9 +226,9 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor this._workflowModel.AddLinkFromPeer(parentId, inputId); string captureId = QuestionExecutor.Steps.Capture(actionId); - this.ContinueWith(new DelegateActionExecutor(captureId, questionExecutor.CaptureResponseAsync, emitResult: false), parentId); + this.ContinueWith(new DelegateActionExecutor(captureId, this._workflowState, questionExecutor.CaptureResponseAsync, emitResult: false), parentId); - this.ContinueWith(new DelegateActionExecutor(postId, questionExecutor.CompleteAsync), parentId, message => questionExecutor.IsComplete(message)); + this.ContinueWith(new DelegateActionExecutor(postId, this._workflowState, questionExecutor.CompleteAsync), parentId, message => questionExecutor.IsComplete(message)); this._workflowModel.AddLink(captureId, prepareId, message => !questionExecutor.IsComplete(message)); } @@ -539,12 +540,12 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor private string ContinuationFor(string actionId, string parentId, DelegateAction? stepAction = null) { actionId = Steps.Post(actionId); - this._workflowModel.AddNode(new DelegateActionExecutor(actionId, stepAction), parentId); + this._workflowModel.AddNode(new DelegateActionExecutor(actionId, this._workflowState, stepAction), parentId); return actionId; } private void RestartAfter(string actionId, string parentId) => - this._workflowModel.AddNode(new DelegateActionExecutor($"{actionId}_Continue"), parentId); + this._workflowModel.AddNode(new DelegateActionExecutor($"{actionId}_Continue", this._workflowState), parentId); private static string GetParentId(BotElement item) => item.GetParentId() ?? diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowElementWalker.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowElementWalker.cs index 0769786420..26f37f9102 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowElementWalker.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Interpreter/WorkflowElementWalker.cs @@ -6,16 +6,13 @@ namespace Microsoft.Agents.Workflows.Declarative.Interpreter; internal sealed class WorkflowElementWalker : BotElementWalker { - private readonly WorkflowActionVisitor _visitor; + private readonly DialogActionVisitor _visitor; - public WorkflowElementWalker(BotElement rootElement, WorkflowActionVisitor visitor) + public WorkflowElementWalker(DialogActionVisitor visitor) { this._visitor = visitor; - this.Visit(rootElement); } - public Workflow GetWorkflow() => this._visitor.Complete(); - public override bool DefaultVisit(BotElement definition) { if (definition is DialogAction action) diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Microsoft.Agents.Workflows.Declarative.csproj b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Microsoft.Agents.Workflows.Declarative.csproj index 634cd93836..f143816c5a 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/Microsoft.Agents.Workflows.Declarative.csproj +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/Microsoft.Agents.Workflows.Declarative.csproj @@ -27,6 +27,7 @@ + diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/AddConversationMessageExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/AddConversationMessageExecutor.cs index 79400a339e..558d58ff2c 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/AddConversationMessageExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/AddConversationMessageExecutor.cs @@ -5,19 +5,20 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class AddConversationMessageExecutor(AddConversationMessage model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class AddConversationMessageExecutor(AddConversationMessage model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { StringExpression conversationExpression = Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}"); - string conversationId = this.State.ExpressionEngine.GetValue(conversationExpression).Value; + string conversationId = this.State.Evaluator.GetValue(conversationExpression).Value; ChatMessage newMessage = new(this.GetRole(), [.. this.GetContent()]) { AdditionalProperties = this.GetMetadata() }; @@ -32,7 +33,7 @@ internal sealed class AddConversationMessageExecutor(AddConversationMessage mode { foreach (AddConversationMessageContent content in this.Model.Content) { - AIContent? messageContent = content.Type.Value.ToContent(this.State.Format(content.Value)); + AIContent? messageContent = content.Type.Value.ToContent(this.State.Engine.Format(content.Value)); if (messageContent is not null) { yield return messageContent; @@ -47,7 +48,7 @@ internal sealed class AddConversationMessageExecutor(AddConversationMessage mode return ChatRole.User; } - AgentMessageRoleWrapper roleWrapper = this.State.ExpressionEngine.GetValue(this.Model.Role).Value; + AgentMessageRoleWrapper roleWrapper = this.State.Evaluator.GetValue(this.Model.Role).Value; return roleWrapper.Value.ToChatRole(); } @@ -59,7 +60,7 @@ internal sealed class AddConversationMessageExecutor(AddConversationMessage mode return null; } - RecordDataValue? metadataValue = this.State.ExpressionEngine.GetValue(this.Model.Metadata).Value; + RecordDataValue? metadataValue = this.State.Evaluator.GetValue(this.Model.Metadata).Value; return metadataValue.ToMetadata(); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ClearAllVariablesExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ClearAllVariablesExecutor.cs index 080a985db4..f0fee71ade 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ClearAllVariablesExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ClearAllVariablesExecutor.cs @@ -10,19 +10,19 @@ using Microsoft.Bot.ObjectModel.Abstractions; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, DeclarativeWorkflowState state) +internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { - EvaluationResult variablesResult = this.State.ExpressionEngine.GetValue(this.Model.Variables); + EvaluationResult variablesResult = this.State.Evaluator.GetValue(this.Model.Variables); variablesResult.Value.Handle(new ScopeHandler(this.Id, this.State)); return default; } - private sealed class ScopeHandler(string executorId, DeclarativeWorkflowState state) : IEnumVariablesToClearHandler + private sealed class ScopeHandler(string executorId, WorkflowFormulaState state) : IEnumVariablesToClearHandler { public void HandleAllGlobalVariables() { @@ -36,7 +36,7 @@ internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, Declara public void HandleConversationScopedVariables() { - this.ClearAll(WorkflowScopes.DefaultScopeName); + this.ClearAll(WorkflowFormulaState.DefaultScopeName); } public void HandleUnknownValue() @@ -51,7 +51,7 @@ internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, Declara private void ClearAll(string scope) { - state.Reset(scope); + state.ResetAll(scope); Debug.WriteLine( $""" STATE: {this.GetType().Name} [{executorId}] diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ConditionGroupExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ConditionGroupExecutor.cs index 76b735bc84..01b88ec6d6 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ConditionGroupExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ConditionGroupExecutor.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; @@ -27,7 +28,7 @@ internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor model.ElseActions.Id.Value ?? $"{model.Id}_Else"; } - public ConditionGroupExecutor(ConditionGroup model, DeclarativeWorkflowState state) + public ConditionGroupExecutor(ConditionGroup model, WorkflowFormulaState state) : base(model, state) { } @@ -58,7 +59,7 @@ internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor expressionResult = this.State.ExpressionEngine.GetValue(conditionItem.Condition); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(conditionItem.Condition); if (expressionResult.Value) { return Steps.Item(this.Model, conditionItem); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs index 991cb09326..b15b712e04 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.Extensions.AI; @@ -11,12 +12,12 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { - string conversationId = this.State.ExpressionEngine.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; + string conversationId = this.State.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; DataValue? inputMessages = this.GetInputMessages(); if (inputMessages is not null) @@ -36,7 +37,7 @@ internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages if (this.Model.Messages is not null) { - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Messages); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(this.Model.Messages); messages = expressionResult.Value; } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs index b21e2626eb..042603b69f 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs @@ -3,12 +3,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class CreateConversationExecutor(CreateConversation model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class CreateConversationExecutor(CreateConversation model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableExecutor.cs index 8da790bb16..ade179dbdb 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableExecutor.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; @@ -13,7 +14,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class EditTableExecutor(EditTable model, DeclarativeWorkflowState state) : DeclarativeActionExecutor(model, state) +internal sealed class EditTableExecutor(EditTable model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { @@ -22,7 +23,7 @@ internal sealed class EditTableExecutor(EditTable model, DeclarativeWorkflowStat FormulaValue table = this.State.Get(variablePath); if (table is not TableValue tableValue) { - throw this.Exception($"Require '{variablePath.Format()}' to be a table, not: '{table.GetType().Name}'."); + throw this.Exception($"Require '{variablePath}' to be a table, not: '{table.GetType().Name}'."); } TableChangeType changeType = this.Model.ChangeType.Value; @@ -30,14 +31,14 @@ internal sealed class EditTableExecutor(EditTable model, DeclarativeWorkflowStat { case TableChangeType.Add: ValueExpression addItemValue = Throw.IfNull(this.Model.Value, $"{nameof(this.Model)}.{nameof(this.Model.Value)}"); - EvaluationResult addResult = this.State.ExpressionEngine.GetValue(addItemValue); + EvaluationResult addResult = this.State.Evaluator.GetValue(addItemValue); RecordValue newRecord = BuildRecord(tableValue.Type.ToRecord(), addResult.Value.ToFormula()); await tableValue.AppendAsync(newRecord, cancellationToken).ConfigureAwait(false); await this.AssignAsync(variablePath, newRecord, context).ConfigureAwait(false); break; case TableChangeType.Remove: ValueExpression removeItemValue = Throw.IfNull(this.Model.Value, $"{nameof(this.Model)}.{nameof(this.Model.Value)}"); - EvaluationResult removeResult = this.State.ExpressionEngine.GetValue(removeItemValue); + EvaluationResult removeResult = this.State.Evaluator.GetValue(removeItemValue); if (removeResult.Value is TableDataValue removeItemTable) { await tableValue.RemoveAsync(removeItemTable?.Values.Select(row => row.ToRecordValue()), all: true, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableV2Executor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableV2Executor.cs index 5880211c81..3ad45a32c2 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableV2Executor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/EditTableV2Executor.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; @@ -13,7 +14,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class EditTableV2Executor(EditTableV2 model, DeclarativeWorkflowState state) : DeclarativeActionExecutor(model, state) +internal sealed class EditTableV2Executor(EditTableV2 model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { @@ -22,14 +23,14 @@ internal sealed class EditTableV2Executor(EditTableV2 model, DeclarativeWorkflow FormulaValue table = this.State.Get(variablePath); if (table is not TableValue tableValue) { - throw this.Exception($"Require '{variablePath.Format()}' to be a table, not: '{table.GetType().Name}'."); + throw this.Exception($"Require '{variablePath}' to be a table, not: '{table.GetType().Name}'."); } EditTableOperation? changeType = this.Model.ChangeType; if (changeType is AddItemOperation addItemOperation) { ValueExpression addItemValue = Throw.IfNull(addItemOperation.Value, $"{nameof(this.Model)}.{nameof(this.Model.ChangeType)}"); - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(addItemValue); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(addItemValue); RecordValue newRecord = BuildRecord(tableValue.Type.ToRecord(), expressionResult.Value.ToFormula()); await tableValue.AppendAsync(newRecord, cancellationToken).ConfigureAwait(false); await this.AssignAsync(variablePath, newRecord, context).ConfigureAwait(false); @@ -42,7 +43,7 @@ internal sealed class EditTableV2Executor(EditTableV2 model, DeclarativeWorkflow else if (changeType is RemoveItemOperation removeItemOperation) { ValueExpression removeItemValue = Throw.IfNull(removeItemOperation.Value, $"{nameof(this.Model)}.{nameof(this.Model.ChangeType)}"); - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(removeItemValue); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(removeItemValue); if (expressionResult.Value.ToFormula() is TableValue removeItemTable) { await tableValue.RemoveAsync(removeItemTable?.Rows.Select(row => row.Value), all: true, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ForeachExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ForeachExecutor.cs index 28525a9f2f..91d2abf2f4 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ForeachExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ForeachExecutor.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; @@ -24,7 +25,7 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor private int _index; private FormulaValue[] _values; - public ForeachExecutor(Foreach model, DeclarativeWorkflowState state) + public ForeachExecutor(Foreach model, WorkflowFormulaState state) : base(model, state) { this._values = []; @@ -45,7 +46,7 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor } else { - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Items); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(this.Model.Items); if (expressionResult.Value is TableDataValue tableValue) { this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())]; @@ -67,11 +68,11 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor { FormulaValue value = this._values[this._index]; - await this.State.SetAsync(Throw.IfNull(this.Model.Value), value, context).ConfigureAwait(false); + await context.QueueStateUpdateAsync(Throw.IfNull(this.Model.Value), value).ConfigureAwait(false); if (this.Model.Index is not null) { - await this.State.SetAsync(this.Model.Index.Path, FormulaValue.New(this._index), context).ConfigureAwait(false); + await context.QueueStateUpdateAsync(this.Model.Index.Path, FormulaValue.New(this._index)).ConfigureAwait(false); } this._index++; diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/GotoExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/GotoExecutor.cs index 31082fbf5c..a7ba9bb731 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/GotoExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/GotoExecutor.cs @@ -3,11 +3,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class GotoExecutor(GotoAction model, DeclarativeWorkflowState state) : +internal sealed class GotoExecutor(GotoAction model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/InvokeAzureAgentExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/InvokeAzureAgentExecutor.cs index 0db23861ec..689c7d6e1f 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/InvokeAzureAgentExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/InvokeAzureAgentExecutor.cs @@ -15,7 +15,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { private AzureAgentUsage AgentUsage => Throw.IfNull(this.Model.Agent, $"{nameof(this.Model)}.{nameof(this.Model.Agent)}"); @@ -77,7 +77,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA if (assignValue is not null && conversationId is null) { conversationId = assignValue; - await this.State.SetConversationIdAsync(context, conversationId).ConfigureAwait(false); + this.State.SetConversationId(conversationId); await context.AddEventAsync(new ConversationUpdateEvent(conversationId)).ConfigureAwait(false); } } @@ -88,7 +88,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA DataValue? userInput = null; if (this.AgentInput?.Messages is not null) { - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(this.AgentInput.Messages); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(this.AgentInput.Messages); userInput = expressionResult.Value; } @@ -102,12 +102,12 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA return null; } - EvaluationResult conversationIdResult = this.State.ExpressionEngine.GetValue(this.Model.ConversationId); + EvaluationResult conversationIdResult = this.State.Evaluator.GetValue(this.Model.ConversationId); return conversationIdResult.Value.Length == 0 ? null : conversationIdResult.Value; } private string GetAgentName() => - this.State.ExpressionEngine.GetValue( + this.State.Evaluator.GetValue( Throw.IfNull( this.AgentUsage.Name, $"{nameof(this.Model)}.{nameof(this.Model.Agent)}.{nameof(this.Model.Agent.Name)}")).Value; @@ -118,7 +118,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA if (this.AgentInput?.AdditionalInstructions is not null) { - additionalInstructions = this.State.Format(this.AgentInput.AdditionalInstructions); + additionalInstructions = this.State.Engine.Format(this.AgentInput.AdditionalInstructions); } return additionalInstructions; @@ -131,7 +131,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA return true; } - EvaluationResult autoSendResult = this.State.ExpressionEngine.GetValue(this.AgentOutput.AutoSend); + EvaluationResult autoSendResult = this.State.Evaluator.GetValue(this.AgentOutput.AutoSend); return autoSendResult.Value; } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ParseValueExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ParseValueExecutor.cs index 80b7c665bc..26318a81bc 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ParseValueExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ParseValueExecutor.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; @@ -14,7 +15,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class ParseValueExecutor(ParseValue model, DeclarativeWorkflowState state) : +internal sealed class ParseValueExecutor(ParseValue model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) @@ -22,7 +23,7 @@ internal sealed class ParseValueExecutor(ParseValue model, DeclarativeWorkflowSt PropertyPath variablePath = Throw.IfNull(this.Model.Variable?.Path, $"{nameof(this.Model)}.{nameof(model.Variable)}"); ValueExpression valueExpression = Throw.IfNull(this.Model.Value, $"{nameof(this.Model)}.{nameof(this.Model.Value)}"); - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(valueExpression); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(valueExpression); FormulaValue? parsedResult = null; diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/QuestionExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/QuestionExecutor.cs index 74baaa3ebf..1fa2285d36 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/QuestionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/QuestionExecutor.cs @@ -6,13 +6,14 @@ using Microsoft.Agents.Workflows.Declarative.Entities; using Microsoft.Agents.Workflows.Declarative.Events; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class QuestionExecutor(Question model, DeclarativeWorkflowState state) : +internal sealed class QuestionExecutor(Question model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { public static class Steps @@ -40,16 +41,16 @@ internal sealed class QuestionExecutor(Question model, DeclarativeWorkflowState InitializablePropertyPath variable = Throw.IfNull(this.Model.Variable); bool hasValue = this.State.Get(variable.Path) is BlankValue; - bool alwaysPrompt = this.State.ExpressionEngine.GetValue(this.Model.AlwaysPrompt).Value; + bool alwaysPrompt = this.State.Evaluator.GetValue(this.Model.AlwaysPrompt).Value; bool proceed = !alwaysPrompt || hasValue; if (proceed) { - SkipQuestionMode mode = this.State.ExpressionEngine.GetValue(this.Model.SkipQuestionMode).Value; + SkipQuestionMode mode = this.State.Evaluator.GetValue(this.Model.SkipQuestionMode).Value; proceed = mode switch { - SkipQuestionMode.SkipOnFirstExecutionIfVariableHasValue => !(await this._hasExecuted.ReadAsync(context).ConfigureAwait(false)), + SkipQuestionMode.SkipOnFirstExecutionIfVariableHasValue => !await this._hasExecuted.ReadAsync(context).ConfigureAwait(false), SkipQuestionMode.AlwaysSkipIfVariableHasValue => hasValue, SkipQuestionMode.AlwaysAsk => true, _ => true, @@ -117,12 +118,13 @@ internal sealed class QuestionExecutor(Question model, DeclarativeWorkflowState private async ValueTask PromptAsync(IWorkflowContext context, CancellationToken cancellationToken) { - long repeatCount = this.State.ExpressionEngine.GetValue(this.Model.RepeatCount).Value; + long repeatCount = this.State.Evaluator.GetValue(this.Model.RepeatCount).Value; int actualCount = await this._promptCount.ReadAsync(context).ConfigureAwait(false); if (actualCount >= repeatCount) { ValueExpression defaultValueExpression = Throw.IfNull(this.Model.DefaultValue); - DataValue defaultValue = this.State.ExpressionEngine.GetValue(defaultValueExpression).Value; + DataValue defaultValue = this.State.Evaluator.GetValue(defaultValueExpression).Value; + await this.AssignAsync(this.Model.Variable?.Path, defaultValue.ToFormula(), context).ConfigureAwait(false); string defaultValueResponse = this.FormatPrompt(this.Model.DefaultValueResponse); await context.AddEventAsync(new MessageActivityEvent(defaultValueResponse.Trim())).ConfigureAwait(false); await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false); @@ -140,6 +142,6 @@ internal sealed class QuestionExecutor(Question model, DeclarativeWorkflowState return string.Empty; } - return this.State.Format(messageActivity.Text).Trim(); + return this.State.Engine.Format(messageActivity.Text).Trim(); } } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ResetVariableExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ResetVariableExecutor.cs index 953233762c..e1ade47533 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ResetVariableExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/ResetVariableExecutor.cs @@ -3,14 +3,14 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class ResetVariableExecutor(ResetVariable model, DeclarativeWorkflowState state) : +internal sealed class ResetVariableExecutor(ResetVariable model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) @@ -21,7 +21,7 @@ internal sealed class ResetVariableExecutor(ResetVariable model, DeclarativeWork Debug.WriteLine( $""" STATE: {this.GetType().Name} [{this.Id}] - NAME: {this.Model.Variable!.Format()} + NAME: {this.Model.Variable} """); return default; diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessageExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessageExecutor.cs index a686f27f8f..782e3aaef6 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessageExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessageExecutor.cs @@ -4,19 +4,20 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class RetrieveConversationMessageExecutor(RetrieveConversationMessage model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class RetrieveConversationMessageExecutor(RetrieveConversationMessage model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { - string conversationId = this.State.ExpressionEngine.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; - string messageId = this.State.ExpressionEngine.GetValue(Throw.IfNull(this.Model.MessageId, $"{nameof(this.Model)}.{nameof(this.Model.MessageId)}")).Value; + string conversationId = this.State.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; + string messageId = this.State.Evaluator.GetValue(Throw.IfNull(this.Model.MessageId, $"{nameof(this.Model)}.{nameof(this.Model.MessageId)}")).Value; ChatMessage message = await agentProvider.GetMessageAsync(conversationId, messageId, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessagesExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessagesExecutor.cs index c7f3cc8cc8..588f244f08 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessagesExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/RetrieveConversationMessagesExecutor.cs @@ -6,18 +6,19 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationMessages model, WorkflowAgentProvider agentProvider, DeclarativeWorkflowState state) : +internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationMessages model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { - string conversationId = this.State.ExpressionEngine.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; + string conversationId = this.State.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value; ChatMessage[] messages = await agentProvider.GetMessagesAsync( conversationId, @@ -39,7 +40,7 @@ internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationM return null; } - long limit = this.State.ExpressionEngine.GetValue(this.Model.Limit).Value; + long limit = this.State.Evaluator.GetValue(this.Model.Limit).Value; return Convert.ToInt32(Math.Min(limit, 100)); } @@ -50,7 +51,7 @@ internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationM return null; } - return this.State.ExpressionEngine.GetValue(messagExpression).Value; + return this.State.Evaluator.GetValue(messagExpression).Value; } private bool IsDescending() @@ -60,7 +61,7 @@ internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationM return false; } - AgentMessageSortOrderWrapper sortOrderWrapper = this.State.ExpressionEngine.GetValue(this.Model.SortOrder).Value; + AgentMessageSortOrderWrapper sortOrderWrapper = this.State.Evaluator.GetValue(this.Model.SortOrder).Value; return sortOrderWrapper.Value == AgentMessageSortOrder.NewestFirst; } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SendActivityExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SendActivityExecutor.cs index dd950ed070..8c78036069 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SendActivityExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SendActivityExecutor.cs @@ -2,19 +2,21 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class SendActivityExecutor(SendActivity model, DeclarativeWorkflowState state) : +internal sealed class SendActivityExecutor(SendActivity model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) { if (this.Model.Activity is MessageActivityTemplate messageActivity) { - string activityText = this.State.Format(messageActivity.Text).Trim(); + string activityText = this.State.Engine.Format(messageActivity.Text).Trim(); await context.AddEventAsync(new MessageActivityEvent(activityText.Trim())).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetMultipleVariablesExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetMultipleVariablesExecutor.cs index 9e920dc9a6..3e77c6d76b 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetMultipleVariablesExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetMultipleVariablesExecutor.cs @@ -4,13 +4,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class SetMultipleVariablesExecutor(SetMultipleVariables model, DeclarativeWorkflowState state) +internal sealed class SetMultipleVariablesExecutor(SetMultipleVariables model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) @@ -28,7 +29,7 @@ internal sealed class SetMultipleVariablesExecutor(SetMultipleVariables model, D } else { - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(assignment.Value); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(assignment.Value); await this.AssignAsync(assignment.Variable, expressionResult.Value.ToFormula(), context).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetTextVariableExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetTextVariableExecutor.cs index 2ee97a7b25..6778c8074d 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetTextVariableExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetTextVariableExecutor.cs @@ -2,14 +2,16 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class SetTextVariableExecutor(SetTextVariable model, DeclarativeWorkflowState state) +internal sealed class SetTextVariableExecutor(SetTextVariable model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) @@ -22,7 +24,7 @@ internal sealed class SetTextVariableExecutor(SetTextVariable model, Declarative } else { - FormulaValue expressionResult = FormulaValue.New(this.State.Format(this.Model.Value)); + FormulaValue expressionResult = FormulaValue.New(this.State.Engine.Format(this.Model.Value)); await this.AssignAsync(variablePath, expressionResult, context).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetVariableExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetVariableExecutor.cs index 62a678bf28..07d00eaa22 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetVariableExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/ObjectModel/SetVariableExecutor.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Agents.Workflows.Declarative.Interpreter; +using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.PowerFx.Types; @@ -11,7 +12,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.ObjectModel; -internal sealed class SetVariableExecutor(SetVariable model, DeclarativeWorkflowState state) +internal sealed class SetVariableExecutor(SetVariable model, WorkflowFormulaState state) : DeclarativeActionExecutor(model, state) { protected override async ValueTask ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken) @@ -24,7 +25,7 @@ internal sealed class SetVariableExecutor(SetVariable model, DeclarativeWorkflow } else { - EvaluationResult expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Value); + EvaluationResult expressionResult = this.State.Evaluator.GetValue(this.Model.Value); await this.AssignAsync(variablePath, expressionResult.Value.ToFormula(), context).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/SystemScope.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/SystemScope.cs index cd0ed99d1b..33f4f4a5bf 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/SystemScope.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/SystemScope.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Frozen; using System.Collections.Generic; -using System.Collections.Immutable; using System.Globalization; -using System.Threading.Tasks; using Microsoft.Agents.Workflows.Declarative.Extensions; -using Microsoft.Agents.Workflows.Declarative.Interpreter; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.SystemVariables; using Microsoft.Extensions.AI; @@ -16,6 +14,8 @@ namespace Microsoft.Agents.Workflows.Declarative.PowerFx; internal static class SystemScope { + private static readonly RecordValue s_emptyMessage = new ChatMessage(ChatRole.User, string.Empty).ToRecord(); + public static class Names { public const string Activity = nameof(Activity); @@ -31,7 +31,7 @@ internal static class SystemScope public const string UserLanguage = nameof(UserLanguage); } - public static ImmutableHashSet AllNames { get; } = GetNames().ToImmutableHashSet(); + public static FrozenSet AllNames { get; } = GetNames().ToFrozenSet(); public static IEnumerable GetNames() { @@ -48,12 +48,12 @@ internal static class SystemScope yield return Names.UserLanguage; } - public static void InitializeSystem(this WorkflowScopes scopes) + public static void InitializeSystem(this WorkflowFormulaState scopes) { scopes.Set(Names.Activity, RecordValue.Empty(), VariableScopeNames.System); scopes.Set(Names.Bot, RecordValue.Empty(), VariableScopeNames.System); - scopes.Set(Names.LastMessage, FormulaType.String.NewBlank(), VariableScopeNames.System); + scopes.Set(Names.LastMessage, s_emptyMessage, VariableScopeNames.System); Set(Names.LastMessageId); Set(Names.LastMessageText); @@ -95,21 +95,21 @@ internal static class SystemScope } } - public static FormulaValue GetConversationId(this DeclarativeWorkflowState state) => - state.Get(VariableScopeNames.System, Names.ConversationId); + public static FormulaValue GetConversationId(this WorkflowFormulaState state) => + state.Get(Names.ConversationId, VariableScopeNames.System); - public static async ValueTask SetConversationIdAsync(this DeclarativeWorkflowState state, IWorkflowContext context, string conversationId) + public static void SetConversationId(this WorkflowFormulaState state, string conversationId) { - RecordValue conversation = (RecordValue)state.Get(VariableScopeNames.System, Names.Conversation); + RecordValue conversation = (RecordValue)state.Get(Names.Conversation, VariableScopeNames.System); conversation.UpdateField("Id", FormulaValue.New(conversationId)); - await state.SetAsync(VariableScopeNames.System, Names.Conversation, conversation, context).ConfigureAwait(false); - await state.SetAsync(VariableScopeNames.System, Names.ConversationId, FormulaValue.New(conversationId), context).ConfigureAwait(false); + state.Set(Names.Conversation, conversation, VariableScopeNames.System); + state.Set(Names.ConversationId, FormulaValue.New(conversationId), VariableScopeNames.System); } - public static async ValueTask SetLastMessageAsync(this DeclarativeWorkflowState state, IWorkflowContext context, ChatMessage message) + public static void SetLastMessage(this WorkflowFormulaState state, ChatMessage message) { - await state.SetAsync(VariableScopeNames.System, Names.LastMessage, message.ToRecord(), context).ConfigureAwait(false); - await state.SetAsync(VariableScopeNames.System, Names.LastMessageId, message.MessageId is null ? FormulaValue.NewBlank(FormulaType.String) : FormulaValue.New(message.MessageId), context).ConfigureAwait(false); - await state.SetAsync(VariableScopeNames.System, Names.LastMessageText, FormulaValue.New(message.Text), context).ConfigureAwait(false); + state.Set(Names.LastMessage, message.ToRecord(), VariableScopeNames.System); + state.Set(Names.LastMessageId, message.MessageId is null ? FormulaValue.NewBlank(FormulaType.String) : FormulaValue.New(message.MessageId), VariableScopeNames.System); + state.Set(Names.LastMessageText, FormulaValue.New(message.Text), VariableScopeNames.System); } } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/UnassignedValue.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/UnassignedValue.cs new file mode 100644 index 0000000000..11b2e763d2 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/UnassignedValue.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.Agents.Workflows.Declarative.PowerFx; + +/// +/// Represents the absence of an assigned value for a variable used in an expression. +/// +public sealed record class UnassignedValue +{ + /// + /// A singleton instance of . + /// + public static UnassignedValue Instance { get; } = new(); +} diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowDiagnostics.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowDiagnostics.cs index dab7cae1a6..db3c860d54 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowDiagnostics.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowDiagnostics.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Frozen; +using System.Collections.Generic; using System.Linq; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Bot.ObjectModel; @@ -12,11 +14,23 @@ using Microsoft.PowerFx.Types; namespace Microsoft.Agents.Workflows.Declarative.PowerFx; +internal sealed record class WorkflowTypeInfo(FrozenSet EnvironmentVariables, IEnumerable UserVariables); + internal static class WorkflowDiagnostics { private static readonly WorkflowFeatureConfiguration s_semanticFeatureConfig = new(); - public static void Initialize(this WorkflowScopes scopes, TElement workflowElement, IConfiguration? configuration) where TElement : BotElement, IDialogBase + public static WorkflowTypeInfo Describe(this TElement workflowElement) where TElement : BotElement, IDialogBase + { + SemanticModel semanticModel = workflowElement.GetSemanticModel(new PowerFxExpressionChecker(s_semanticFeatureConfig), s_semanticFeatureConfig); + + return + new WorkflowTypeInfo( + semanticModel.GetAllEnvironmentVariablesReferencedInTheBot().ToFrozenSet(), + semanticModel.GetVariables(workflowElement.SchemaName.Value).Where(x => !x.IsSystemVariable).Select(v => v.ToDiagnostic())); + } + + public static void Initialize(this WorkflowFormulaState scopes, TElement workflowElement, IConfiguration? configuration) where TElement : BotElement, IDialogBase { scopes.InitializeSystem(); @@ -25,7 +39,7 @@ internal static class WorkflowDiagnostics scopes.InitializeDefaults(semanticModel, workflowElement.SchemaName.Value); } - private static void InitializeEnvironment(this WorkflowScopes scopes, SemanticModel semanticModel, IConfiguration? configuration) + private static void InitializeEnvironment(this WorkflowFormulaState scopes, SemanticModel semanticModel, IConfiguration? configuration) { foreach (string variableName in semanticModel.GetAllEnvironmentVariablesReferencedInTheBot()) { @@ -35,7 +49,7 @@ internal static class WorkflowDiagnostics } } - private static void InitializeDefaults(this WorkflowScopes scopes, SemanticModel semanticModel, string schemaName) + private static void InitializeDefaults(this WorkflowFormulaState scopes, SemanticModel semanticModel, string schemaName) { foreach (VariableInformationDiagnostic variableDiagnostic in semanticModel.GetVariables(schemaName).Where(x => !x.IsSystemVariable).Select(v => v.ToDiagnostic())) { @@ -54,7 +68,7 @@ internal static class WorkflowDiagnostics } } - scopes.Set(variableDiagnostic.Path.VariableName, defaultValue, variableDiagnostic.Path.VariableScopeName ?? WorkflowScopes.DefaultScopeName); + scopes.Set(variableDiagnostic.Path.VariableName, defaultValue, variableDiagnostic.Path.VariableScopeName ?? WorkflowFormulaState.DefaultScopeName); } } diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowExpressionEngine.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowExpressionEngine.cs index 70b594e50d..68fb410aaa 100644 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowExpressionEngine.cs +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowExpressionEngine.cs @@ -13,7 +13,7 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Declarative.PowerFx; -internal class WorkflowExpressionEngine : IExpressionEngine +internal class WorkflowExpressionEngine { private readonly RecalcEngine _engine; @@ -22,60 +22,26 @@ internal class WorkflowExpressionEngine : IExpressionEngine this._engine = engine; } - public EvaluationResult GetValue(BoolExpression boolean, WorkflowScopes? state = null) => this.GetValue(boolean, state, this.EvaluateScope); + public EvaluationResult GetValue(BoolExpression boolean) => this.Evaluate(boolean); - public EvaluationResult GetValue(BoolExpression boolean, RecordDataValue state) => this.GetValue(boolean, state, this.EvaluateState); + public EvaluationResult GetValue(StringExpression expression) => this.Evaluate(expression); - public EvaluationResult GetValue(StringExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope); + public EvaluationResult GetValue(ValueExpression expression) => this.Evaluate(expression); - public EvaluationResult GetValue(StringExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState); + public EvaluationResult GetValue(IntExpression expression) => this.Evaluate(expression); - public EvaluationResult GetValue(ValueExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope); + public EvaluationResult GetValue(NumberExpression expression) => this.Evaluate(expression); - public EvaluationResult GetValue(ValueExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState); + public EvaluationResult GetValue(ObjectExpression expression) where TValue : BotElement => this.Evaluate(expression); - public EvaluationResult GetValue(IntExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope); + public ImmutableArray GetValue(ArrayExpression expression) => this.Evaluate(expression).Value; - public EvaluationResult GetValue(IntExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState); + public ImmutableArray GetValue(ArrayExpressionOnly expression) => this.Evaluate(expression).Value; - public EvaluationResult GetValue(NumberExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope); + public EvaluationResult GetValue(EnumExpression expression) where TValue : EnumWrapper => + this.Evaluate(expression); - public EvaluationResult GetValue(NumberExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState); - - public EvaluationResult GetValue(ObjectExpression expression, WorkflowScopes? state = null) where TValue : BotElement => this.GetValue(expression, state, this.EvaluateScope); - - public EvaluationResult GetValue(ObjectExpression expression, RecordDataValue state) where TValue : BotElement => this.GetValue(expression, state, this.EvaluateState); - - public ImmutableArray GetValue(ArrayExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope).Value; - - public ImmutableArray GetValue(ArrayExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState).Value; - - public ImmutableArray GetValue(ArrayExpressionOnly expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope).Value; - - public ImmutableArray GetValue(ArrayExpressionOnly expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState).Value; - - public EvaluationResult GetValue(EnumExpression expression, WorkflowScopes? state = null) where TValue : EnumWrapper => - this.GetValue(expression, state, this.EvaluateScope); - - public EvaluationResult GetValue(EnumExpression expression, RecordDataValue state) where TValue : EnumWrapper => - this.GetValue(expression, state, this.EvaluateState); - - public DialogSchemaName GetValue(DialogExpression expression, RecordDataValue state) - { - throw new NotSupportedException(); - } - - public EvaluationResult GetValue(AdaptiveCardExpression expression, RecordDataValue state) - { - throw new NotSupportedException(); - } - - public EvaluationResult GetValue(FileExpression expression, RecordDataValue state) - { - throw new NotSupportedException(); - } - - private EvaluationResult GetValue(BoolExpression expression, TState state, Func> evaluator) + private EvaluationResult Evaluate(BoolExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -84,7 +50,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); if (expressionResult.Value is BlankValue) { @@ -99,7 +65,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(formulaValue.Value, expressionResult.Sensitivity); } - private EvaluationResult GetValue(StringExpression expression, TState state, Func> evaluator) + private EvaluationResult Evaluate(StringExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -108,7 +74,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); if (expressionResult.Value is BlankValue) { @@ -128,7 +94,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(formulaValue.Value, expressionResult.Sensitivity); } - private EvaluationResult GetValue(IntExpression expression, TState state, Func> evaluator) + private EvaluationResult Evaluate(IntExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -137,7 +103,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); if (expressionResult.Value is BlankValue) { @@ -152,7 +118,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(Convert.ToInt64(formulaValue.Value), expressionResult.Sensitivity); } - private EvaluationResult GetValue(NumberExpression expression, TState state, Func> evaluator) + private EvaluationResult Evaluate(NumberExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -161,7 +127,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); if (expressionResult.Value is BlankValue) { @@ -181,7 +147,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(formulaValue.Value, expressionResult.Sensitivity); } - private EvaluationResult GetValue(ValueExpression expression, TState? state, Func> evaluator) + private EvaluationResult Evaluate(ValueExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -190,12 +156,12 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue ?? BlankDataValue.Instance, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); return new EvaluationResult(expressionResult.Value.ToDataValue(), expressionResult.Sensitivity); } - private EvaluationResult GetValue(EnumExpression expression, TState? state, Func> evaluator) where TValue : EnumWrapper + private EvaluationResult Evaluate(EnumExpression expression) where TValue : EnumWrapper { Throw.IfNull(expression, nameof(expression)); @@ -204,7 +170,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); return expressionResult.Value switch { @@ -216,7 +182,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine }; } - private EvaluationResult GetValue(ObjectExpression expression, TState state, Func> evaluator) where TValue : BotElement + private EvaluationResult Evaluate(ObjectExpression expression) where TValue : BotElement { Throw.IfNull(expression, nameof(expression)); @@ -225,7 +191,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); if (expressionResult.Value is BlankValue) { @@ -247,7 +213,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine } } - private EvaluationResult> GetValue(ArrayExpression expression, TState state, Func> evaluator) + private EvaluationResult> Evaluate(ArrayExpression expression) { Throw.IfNull(expression, nameof(expression)); @@ -256,16 +222,16 @@ internal class WorkflowExpressionEngine : IExpressionEngine return new EvaluationResult>(expression.LiteralValue, SensitivityLevel.None); } - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); return new EvaluationResult>(ParseArrayResults(expressionResult.Value), expressionResult.Sensitivity); } - private EvaluationResult> GetValue(ArrayExpressionOnly expression, TState state, Func> evaluator) + private EvaluationResult> Evaluate(ArrayExpressionOnly expression) { Throw.IfNull(expression, nameof(expression)); - EvaluationResult expressionResult = evaluator.Invoke(expression, state); + EvaluationResult expressionResult = this.EvaluateScope(expression); return new EvaluationResult>(ParseArrayResults(expressionResult.Value), expressionResult.Sensitivity); } @@ -302,40 +268,11 @@ internal class WorkflowExpressionEngine : IExpressionEngine } } - private EvaluationResult EvaluateState(ExpressionBase expression, RecordDataValue? state) - { - if (state is not null) - { - foreach (KeyValuePair kvp in state.Properties) - { - if (kvp.Value is RecordDataValue scopeRecord) - { - Bind(kvp.Key, scopeRecord.ToRecordValue()); - } - } - } - - return this.Evaluate(expression); - - void Bind(string scopeName, RecordValue stateRecord) - { - this._engine.DeleteFormula(scopeName); - this._engine.UpdateVariable(scopeName, stateRecord); - } - } - - private EvaluationResult EvaluateScope(ExpressionBase expression, WorkflowScopes? state = null) - { - state?.Bind(this._engine); - - return this.Evaluate(expression); - } - - private EvaluationResult Evaluate(ExpressionBase expression) + private EvaluationResult EvaluateScope(ExpressionBase expression) { string? expressionText = expression.IsVariableReference ? - expression.VariableReference?.Format() : + expression.VariableReference?.ToString() : expression.ExpressionText; return new(this._engine.Eval(expressionText), SensitivityLevel.None); diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowFormulaState.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowFormulaState.cs new file mode 100644 index 0000000000..c067d8bc8d --- /dev/null +++ b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowFormulaState.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.Workflows.Declarative.Extensions; +using Microsoft.Bot.ObjectModel; +using Microsoft.PowerFx; +using Microsoft.PowerFx.Types; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Agents.Workflows.Declarative.PowerFx; + +/// +/// Contains all variables scopes for a workflow. +/// +internal sealed class WorkflowFormulaState +{ + // ISSUE #488 - Update default scope for workflows to `Workflow` (instead of `Topic`) + public const string DefaultScopeName = VariableScopeNames.Topic; + + private static readonly FrozenSet s_mutableScopes = + [ + VariableScopeNames.Topic, + VariableScopeNames.Global, + VariableScopeNames.System, + ]; + + private readonly Dictionary _scopes; + private int _isInitialized; + + public RecalcEngine Engine { get; } + + public WorkflowExpressionEngine Evaluator { get; } + + public WorkflowFormulaState(RecalcEngine engine) + { + this.Engine = engine; + this.Evaluator = new WorkflowExpressionEngine(engine); + this._scopes = VariableScopeNames.AllScopes.ToDictionary(scopeName => scopeName, scopeName => new WorkflowScope(scopeName)); + this.Bind(); + } + + public FormulaValue Get(PropertyPath variablePath) => this.Get(Throw.IfNull(variablePath.VariableName), variablePath.VariableScopeName); + + public FormulaValue Get(string variableName, string? scopeName = null) + { + if (this.GetScope(scopeName).TryGetValue(variableName, out FormulaValue? value)) + { + return value; + } + + return FormulaValue.NewBlank(); + } + + public void ResetAll(string? scopeName = null) + { + if (scopeName is not null) + { + this.GetScope(scopeName).ResetAll(); + } + else + { + foreach (string targetScope in VariableScopeNames.AllScopes) + { + this.GetScope(targetScope).ResetAll(); + } + } + + this.Bind(); + } + + public void Reset(PropertyPath variablePath) => this.Reset(Throw.IfNull(variablePath.VariableName), variablePath.VariableScopeName); + + public void Reset(string variableName, string? scopeName = null) + { + this.GetScope(scopeName).Reset(variableName); + this.Bind(); + } + + public void Set(PropertyPath variablePath, FormulaValue value) => this.Set(Throw.IfNull(variablePath.VariableName), value, variablePath.VariableScopeName); + + public void Set(string variableName, FormulaValue value, string? scopeName = null) + { + this.GetScope(scopeName)[variableName] = value; + this.Bind(); + } + + public bool SetInitialized() => Interlocked.CompareExchange(ref this._isInitialized, 1, 0) == 0; + + public async ValueTask RestoreAsync(IWorkflowContext context, CancellationToken cancellationToken) + { + if (!this.SetInitialized()) + { + return; + } + + await Task.WhenAll(s_mutableScopes.Select(scopeName => ReadScopeAsync(scopeName))).ConfigureAwait(false); + + async Task ReadScopeAsync(string scopeName) + { + HashSet keys = await context.ReadStateKeysAsync(scopeName).ConfigureAwait(false); + foreach (string key in keys) + { + object? value = await context.ReadStateAsync(key, scopeName).ConfigureAwait(false); + if (value is null || value is UnassignedValue) + { + value = FormulaValue.NewBlank(); + } + this.Set(key, value.ToFormula(), scopeName); + } + + this.Bind(scopeName); + } + } + + public RecordValue BuildRecord(string scopeName) => this.GetScope(scopeName).BuildRecord(); + + public void Bind(string? targetScope = null) + { + if (targetScope is not null) + { + Bind(targetScope); + } + else + { + foreach (string scopeName in VariableScopeNames.AllScopes) + { + Bind(scopeName); + } + } + + void Bind(string scopeName) + { + RecordValue scopeRecord = this.BuildRecord(scopeName); + this.Engine.DeleteFormula(scopeName); + this.Engine.UpdateVariable(scopeName, scopeRecord); + } + } + + private WorkflowScope GetScope(string? scopeName) + { + scopeName ??= WorkflowFormulaState.DefaultScopeName; + + if (!VariableScopeNames.IsValidName(scopeName)) + { + throw new DeclarativeActionException($"Invalid variable scope name: '{scopeName}'."); + } + + return this._scopes[scopeName]; + } + + /// + /// The set of variables for a specific action scope. + /// + private sealed class WorkflowScope(string scopeName) : Dictionary + { + public string Name => scopeName; + + public void ResetAll() + { + foreach (string variableName in this.Keys.ToArray()) + { + this.Reset(variableName); + } + } + + public void Reset(string variableName) + { + if (this.TryGetValue(variableName, out FormulaValue? value)) + { + this[variableName] = value.Type.NewBlank(); + } + } + + public RecordValue BuildRecord() + { + return FormulaValue.NewRecordFromFields(GetFields()); + + IEnumerable GetFields() + { + foreach (KeyValuePair kvp in this) + { + yield return new NamedValue(kvp.Key, kvp.Value); + } + } + } + } +} diff --git a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowScopes.cs b/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowScopes.cs deleted file mode 100644 index b68e76d6f6..0000000000 --- a/dotnet/src/Microsoft.Agents.Workflows.Declarative/PowerFx/WorkflowScopes.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.Agents.Workflows.Declarative.Extensions; -using Microsoft.Bot.ObjectModel; -using Microsoft.PowerFx; -using Microsoft.PowerFx.Types; - -namespace Microsoft.Agents.Workflows.Declarative.PowerFx; - -/// -/// Contains all action scopes for a process. -/// -internal sealed class WorkflowScopes -{ - // ISSUE #488 - Update default scope for workflows to `Workflow` (instead of `Topic`) - public const string DefaultScopeName = VariableScopeNames.Topic; - - private readonly ImmutableDictionary _scopes; - - public WorkflowScopes() - { - this._scopes = VariableScopeNames.AllScopes.ToDictionary(scopeName => scopeName, scopeName => new WorkflowScope(scopeName)).ToImmutableDictionary(); - } - - public FormulaValue Get(string variableName, string? scopeName = null) - { - if (this._scopes[scopeName ?? WorkflowScopes.DefaultScopeName].TryGetValue(variableName, out FormulaValue? value)) - { - return value; - } - - return FormulaValue.NewBlank(); - } - - public void Clear(string scopeName) => this._scopes[scopeName].Reset(); - - public void Reset(string variableName, string? scopeName = null) => this._scopes[scopeName ?? WorkflowScopes.DefaultScopeName].Reset(variableName); - - public void Set(string variableName, FormulaValue value, string? scopeName = null) => this._scopes[scopeName ?? WorkflowScopes.DefaultScopeName][variableName] = value; - - public RecordValue BuildRecord(string scopeName) => this._scopes[scopeName].BuildRecord(); - - public RecordDataValue BuildState() - { - return DataValue.RecordFromFields(BuildStateFields()); - - IEnumerable> BuildStateFields() - { - foreach (KeyValuePair kvp in this._scopes) - { - yield return new(kvp.Key, kvp.Value.BuildState()); - } - } - } - - public void Bind(RecalcEngine engine, string? type = null) - { - if (type is not null) - { - Bind(type); - } - else - { - foreach (string scopeName in VariableScopeNames.AllScopes) - { - Bind(scopeName); - } - } - - void Bind(string scopeName) - { - RecordValue scopeRecord = this.BuildRecord(scopeName); - engine.DeleteFormula(scopeName); - engine.UpdateVariable(scopeName, scopeRecord); - } - } - - /// - /// The set of variables for a specific action scope. - /// - private sealed class WorkflowScope(string scopeName) : Dictionary - { - public string Name => scopeName; - - public void Reset() - { - foreach (string variableName in this.Keys.ToArray()) - { - this.Reset(variableName); - } - } - - public void Reset(string variableName) - { - if (this.TryGetValue(variableName, out FormulaValue? value)) - { - this[variableName] = value.Type.NewBlank(); - } - } - - public RecordValue BuildRecord() - { - return FormulaValue.NewRecordFromFields(GetFields()); - - IEnumerable GetFields() - { - foreach (KeyValuePair kvp in this) - { - yield return new NamedValue(kvp.Key, kvp.Value); - } - } - } - - public RecordDataValue BuildState() - { - RecordDataValue.Builder recordBuilder = new(); - - foreach (KeyValuePair kvp in this) - { - recordBuilder.Properties.Add(kvp.Key, kvp.Value.ToDataValue()); - } - - return recordBuilder.Build(); - } - } -} diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs index ddfa6f089e..59d7aa8d5c 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs @@ -135,6 +135,8 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow [InlineData("EditTable.yaml", 2, "edit_var")] [InlineData("EditTableV2.yaml", 2, "edit_var")] [InlineData("ParseValue.yaml", 1, "parse_var")] + [InlineData("SendActivity.yaml", 2, "activity_input")] + [InlineData("SetVariable.yaml", 1, "set_var")] [InlineData("SetTextVariable.yaml", 1, "set_text")] [InlineData("ClearAllVariables.yaml", 1, "clear_all")] [InlineData("ResetVariable.yaml", 2, "clear_var")] @@ -197,11 +199,12 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow }; AdaptiveDialog dialog = dialogBuilder.Build(); - WorkflowScopes scopes = new(); + WorkflowFormulaState state = new(RecalcEngineFactory.Create()); Mock mockAgentProvider = new(MockBehavior.Strict); DeclarativeWorkflowOptions options = new(mockAgentProvider.Object); - WorkflowActionVisitor visitor = new(new RootExecutor(), new DeclarativeWorkflowState(RecalcEngineFactory.Create()), options); - WorkflowElementWalker walker = new(dialog, visitor); + WorkflowActionVisitor visitor = new(new RootExecutor(), state, options); + WorkflowElementWalker walker = new(visitor); + walker.Visit(dialog); Assert.True(visitor.HasUnsupportedActions); } diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ClearAllVariablesExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ClearAllVariablesExecutorTest.cs index af3a31c735..7da024f4ce 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ClearAllVariablesExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ClearAllVariablesExecutorTest.cs @@ -17,7 +17,7 @@ public sealed class ClearAllVariablesExecutorTest(ITestOutputHelper output) : Wo public async Task ClearWorkflowScope() { // Arrange - this.Scopes.Set("NoVar", FormulaValue.New("Old value")); + this.State.Set("NoVar", FormulaValue.New("Old value")); ClearAllVariables model = this.CreateModel( @@ -25,7 +25,7 @@ public sealed class ClearAllVariablesExecutorTest(ITestOutputHelper output) : Wo VariablesToClear.ConversationScopedVariables); // Act - ClearAllVariablesExecutor action = new(model, this.GetState()); + ClearAllVariablesExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -43,7 +43,7 @@ public sealed class ClearAllVariablesExecutorTest(ITestOutputHelper output) : Wo VariablesToClear.UserScopedVariables); // Act - ClearAllVariablesExecutor action = new(model, this.GetState()); + ClearAllVariablesExecutor action = new(model, this.State); await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ParseValueExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ParseValueExecutorTest.cs index 35f8494d93..032f02cd05 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ParseValueExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ParseValueExecutorTest.cs @@ -32,7 +32,7 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA @"{ ""key1"": ""val1"" }"); // Act - ParseValueExecutor action = new(model, this.GetState()); + ParseValueExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -51,7 +51,7 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA "True"); // Act - ParseValueExecutor action = new(model, this.GetState()); + ParseValueExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -70,7 +70,7 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA "42"); // Act - ParseValueExecutor action = new(model, this.GetState()); + ParseValueExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -89,7 +89,7 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA "Hello, World!"); // Act - ParseValueExecutor action = new(model, this.GetState()); + ParseValueExecutor action = new(model, this.State); await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ResetVariableExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ResetVariableExecutorTest.cs index d705d4ebb8..b3e8d29b1b 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ResetVariableExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/ResetVariableExecutorTest.cs @@ -17,8 +17,8 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl public async Task ResetDefinedValue() { // Arrange - this.Scopes.Set("MyVar1", FormulaValue.New("Value #1")); - this.Scopes.Set("MyVar2", FormulaValue.New("Value #2")); + this.State.Set("MyVar1", FormulaValue.New("Value #1")); + this.State.Set("MyVar2", FormulaValue.New("Value #2")); ResetVariable model = this.CreateModel( @@ -26,7 +26,7 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl FormatVariablePath("MyVar1")); // Act - ResetVariableExecutor action = new(model, this.GetState()); + ResetVariableExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -39,7 +39,7 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl public async Task ResetUndefinedValue() { // Arrange - this.Scopes.Set("MyVar1", FormulaValue.New("Value #1")); + this.State.Set("MyVar1", FormulaValue.New("Value #1")); ResetVariable model = this.CreateModel( @@ -47,7 +47,7 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl FormatVariablePath("NoVar")); // Act - ResetVariableExecutor action = new(model, this.GetState()); + ResetVariableExecutor action = new(model, this.State); await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SendActivityExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SendActivityExecutorTest.cs index 5ef54a47a3..8704e80257 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SendActivityExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SendActivityExecutorTest.cs @@ -22,7 +22,7 @@ public sealed class SendActivityExecutorTest(ITestOutputHelper output) : Workflo "Test activity message"); // Act - SendActivityExecutor action = new(model, this.GetState()); + SendActivityExecutor action = new(model, this.State); WorkflowEvent[] events = await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetTextVariableExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetTextVariableExecutorTest.cs index 6ca809d1dd..50c547fda5 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetTextVariableExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetTextVariableExecutorTest.cs @@ -24,7 +24,7 @@ public sealed class SetTextVariableExecutorTest(ITestOutputHelper output) : Work "Text variable value"); // Act - SetTextVariableExecutor action = new(model, this.GetState()); + SetTextVariableExecutor action = new(model, this.State); await this.Execute(action); // Assert @@ -36,7 +36,7 @@ public sealed class SetTextVariableExecutorTest(ITestOutputHelper output) : Work public async Task UpdateExistingValue() { // Arrange - this.Scopes.Set("TextVar", FormulaValue.New("Old value")); + this.State.Set("TextVar", FormulaValue.New("Old value")); SetTextVariable model = this.CreateModel( @@ -45,7 +45,7 @@ public sealed class SetTextVariableExecutorTest(ITestOutputHelper output) : Work "New value"); // Act - SetTextVariableExecutor action = new(model, this.GetState()); + SetTextVariableExecutor action = new(model, this.State); await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetVariableExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetVariableExecutorTest.cs index a6ff29a597..fe9e195e08 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetVariableExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/SetVariableExecutorTest.cs @@ -17,7 +17,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow public void InvalidModel() { // Arrange, Act, Assert - Assert.Throws(() => new SetVariableExecutor(new SetVariable(), this.GetState())); + Assert.Throws(() => new SetVariableExecutor(new SetVariable(), this.State)); } [Fact] @@ -99,7 +99,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow public async Task SetBooleanVariable() { // Arrange - this.Scopes.Set("Source", FormulaValue.New(true)); + this.State.Set("Source", FormulaValue.New(true)); ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source"))); // Act, Assert @@ -114,7 +114,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow public async Task SetNumberVariable() { // Arrange - this.Scopes.Set("Source", FormulaValue.New(321)); + this.State.Set("Source", FormulaValue.New(321)); ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source"))); // Act, Assert @@ -129,7 +129,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow public async Task SetStringVariable() { // Arrange - this.Scopes.Set("Source", FormulaValue.New("Test")); + this.State.Set("Source", FormulaValue.New("Test")); ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source"))); // Act, Assert @@ -144,7 +144,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow public async Task UpdateExistingValue() { // Arrange - this.Scopes.Set("VarA", FormulaValue.New(33)); + this.State.Set("VarA", FormulaValue.New(33)); // Act, Assert await this.ExecuteTest( @@ -180,10 +180,10 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow FormatVariablePath(variableName), valueExpression); - this.Scopes.Set(variableName, FormulaValue.New(33)); + this.State.Set(variableName, FormulaValue.New(33)); // Act - SetVariableExecutor action = new(model, this.GetState()); + SetVariableExecutor action = new(model, this.State); await this.Execute(action); // Assert diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs index b2149c1c92..26cb44f243 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs @@ -18,9 +18,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel; /// public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : WorkflowTest(output) { - internal WorkflowScopes Scopes { get; } = new(); - - internal DeclarativeWorkflowState GetState() => new(RecalcEngineFactory.Create(), this.Scopes); + internal WorkflowFormulaState State { get; } = new(RecalcEngineFactory.Create()); protected ActionId CreateActionId() => new($"{this.GetType().Name}_{Guid.NewGuid():N}"); @@ -31,7 +29,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor TestWorkflowExecutor workflowExecutor = new(); WorkflowBuilder workflowBuilder = new(workflowExecutor); workflowBuilder.AddEdge(workflowExecutor, executor); - StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build(), this.Scopes); + StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build(), this.State); WorkflowEvent[] events = await run.WatchStreamAsync().ToArrayAsync(); Assert.Contains(events, e => e is DeclarativeActionInvokeEvent); Assert.Contains(events, e => e is DeclarativeActionCompleteEvent); @@ -48,7 +46,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor internal void VerifyState(string variableName, string scopeName, FormulaValue expectedValue) { - FormulaValue actualValue = this.Scopes.Get(variableName, scopeName); + FormulaValue actualValue = this.State.Get(variableName, scopeName); Assert.Equal(expectedValue.Format(), actualValue.Format()); } @@ -56,7 +54,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor internal void VerifyUndefined(string variableName, string scopeName) { - Assert.IsType(this.Scopes.Get(variableName, scopeName)); + Assert.IsType(this.State.Get(variableName, scopeName)); } protected TAction AssignParent(DialogAction.Builder actionBuilder) where TAction : DialogAction @@ -76,9 +74,9 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor internal sealed class TestWorkflowExecutor() : ReflectingExecutor(nameof(TestWorkflowExecutor)), - IMessageHandler + IMessageHandler { - public async ValueTask HandleAsync(WorkflowScopes message, IWorkflowContext context) + public async ValueTask HandleAsync(WorkflowFormulaState message, IWorkflowContext context) { await context.SendMessageAsync(new ExecutorResultMessage(this.Id)).ConfigureAwait(false); } diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineFactoryTests.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineFactoryTests.cs index 690a20614c..e247f0b9bb 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineFactoryTests.cs @@ -10,12 +10,12 @@ using Xunit.Abstractions; namespace Microsoft.Agents.Workflows.Declarative.UnitTests.PowerFx; -public class RecalcEngineFactoryTests(ITestOutputHelper output) : RecalcEngineTest(output) +public class RecalcEngineFactoryTests(ITestOutputHelper output) : WorkflowTest(output) { [Fact] public void VariableUpdateTest() { - RecalcEngine engine = this.CreateEngine(); + RecalcEngine engine = RecalcEngineFactory.Create(); FormulaValue evalResult; @@ -60,7 +60,7 @@ public class RecalcEngineFactoryTests(ITestOutputHelper output) : RecalcEngineTe public void DefaultNotNull() { // Act - RecalcEngine engine = this.CreateEngine(); + RecalcEngine engine = RecalcEngineFactory.Create(); // Assert Assert.NotNull(engine); @@ -70,8 +70,8 @@ public class RecalcEngineFactoryTests(ITestOutputHelper output) : RecalcEngineTe public void NewInstanceEachTime() { // Act - RecalcEngine engine1 = this.CreateEngine(); - RecalcEngine engine2 = this.CreateEngine(); + RecalcEngine engine1 = RecalcEngineFactory.Create(); + RecalcEngine engine2 = RecalcEngineFactory.Create(); // Assert Assert.NotNull(engine1); @@ -83,7 +83,7 @@ public class RecalcEngineFactoryTests(ITestOutputHelper output) : RecalcEngineTe public void HasSetFunctionEnabled() { // Arrange - RecalcEngine engine = this.CreateEngine(); + RecalcEngine engine = RecalcEngineFactory.Create(); // Act CheckResult result = engine.Check("1+1"); diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineTest.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineTest.cs index db849323aa..8321ce0d1b 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineTest.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/RecalcEngineTest.cs @@ -11,7 +11,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.PowerFx; /// public abstract class RecalcEngineTest(ITestOutputHelper output) : WorkflowTest(output) { - internal WorkflowScopes Scopes { get; } = new(); + internal WorkflowFormulaState State { get; } = new(RecalcEngineFactory.Create()); - protected RecalcEngine CreateEngine(int maximumExpressionLength = 500) => RecalcEngineFactory.Create(maximumExpressionLength); + protected RecalcEngine Engine => this.State.Engine; } diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/TemplateExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/TemplateExtensionsTests.cs index cd2f6c05ea..84136e9fab 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/TemplateExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/TemplateExtensionsTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.Agents.Workflows.Declarative.Extensions; using Microsoft.Bot.ObjectModel; -using Microsoft.PowerFx; using Microsoft.PowerFx.Types; using Xunit.Abstractions; @@ -21,10 +20,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes TemplateLine.Parse(" "), TemplateLine.Parse("World"), ]; - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(template); + string? result = this.Engine.Format(template); // Assert Assert.Equal("Hello World", result); @@ -35,10 +33,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes { // Arrange List template = []; - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(template); + string? result = this.Engine.Format(template); // Assert Assert.Equal(string.Empty, result); @@ -49,10 +46,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes { // Arrange TemplateLine line = TemplateLine.Parse("Test"); - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal("Test", result); @@ -63,10 +59,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes { // Arrange TemplateLine? line = null; - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal(string.Empty, result); @@ -78,10 +73,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes // Arrange TemplateSegment textSegment = TextSegment.FromText("Hello World"); TemplateLine line = new([textSegment]); - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal("Hello World", result); @@ -93,10 +87,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes // Arrange ExpressionSegment expressionSegment = new(ValueExpression.Expression("1 + 1")); TemplateLine line = new([expressionSegment]); - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal("2", result); @@ -106,14 +99,12 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes public void FormatVariableSegment() { // Arrange - this.Scopes.Set("Source", FormulaValue.New("Hello World")); + this.State.Set("Source", FormulaValue.New("Hello World")); ExpressionSegment expressionSegment = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source"))); TemplateLine line = new([expressionSegment]); - RecalcEngine engine = this.CreateEngine(); - this.Scopes.Bind(engine); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal("Hello World", result); @@ -125,10 +116,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes // Arrange ExpressionSegment expressionSegment = new(); TemplateLine line = new([expressionSegment]); - RecalcEngine engine = this.CreateEngine(); // Act & Assert - Assert.Throws(() => engine.Format(line)); + Assert.Throws(() => this.Engine.Format(line)); } [Fact] @@ -138,10 +128,9 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes TemplateSegment textSegment = TextSegment.FromText("Hello "); ExpressionSegment expressionSegment = new(ValueExpression.Expression(@"""World""")); TemplateLine line = new([textSegment, expressionSegment]); - RecalcEngine engine = this.CreateEngine(); // Act - string? result = engine.Format(line); + string? result = this.Engine.Format(line); // Assert Assert.Equal("Hello World", result); diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowExpressionEngineTests.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowExpressionEngineTests.cs index 53cd9983f5..c12396b395 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowExpressionEngineTests.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowExpressionEngineTests.cs @@ -7,7 +7,6 @@ using Microsoft.Agents.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.Bot.ObjectModel.Abstractions; using Microsoft.Bot.ObjectModel.Exceptions; -using Microsoft.PowerFx; using Microsoft.PowerFx.Types; using Xunit.Abstractions; @@ -34,39 +33,17 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest public WorkflowExpressionEngineTests(ITestOutputHelper output) : base(output) { - this.Scopes.Set(Variables.GlobalValue, FormulaValue.New(255), VariableScopeNames.Global); - this.Scopes.Set(Variables.BoolValue, FormulaValue.New(true), VariableScopeNames.Topic); - this.Scopes.Set(Variables.StringValue, FormulaValue.New("Hello World"), VariableScopeNames.Topic); - this.Scopes.Set(Variables.IntValue, FormulaValue.New(long.MaxValue), VariableScopeNames.Topic); - this.Scopes.Set(Variables.NumberValue, FormulaValue.New(33.3), VariableScopeNames.Topic); - this.Scopes.Set(Variables.EnumValue, FormulaValue.New(nameof(VariablesToClear.ConversationScopedVariables)), VariableScopeNames.Topic); - this.Scopes.Set(Variables.ObjectValue, ObjectData, VariableScopeNames.Topic); - this.Scopes.Set(Variables.ArrayValue, TableData, VariableScopeNames.Topic); - this.Scopes.Set(Variables.BlankValue, FormulaValue.NewBlank(), VariableScopeNames.Topic); + this.State.Set(Variables.GlobalValue, FormulaValue.New(255), VariableScopeNames.Global); + this.State.Set(Variables.BoolValue, FormulaValue.New(true), VariableScopeNames.Topic); + this.State.Set(Variables.StringValue, FormulaValue.New("Hello World"), VariableScopeNames.Topic); + this.State.Set(Variables.IntValue, FormulaValue.New(long.MaxValue), VariableScopeNames.Topic); + this.State.Set(Variables.NumberValue, FormulaValue.New(33.3), VariableScopeNames.Topic); + this.State.Set(Variables.EnumValue, FormulaValue.New(nameof(VariablesToClear.ConversationScopedVariables)), VariableScopeNames.Topic); + this.State.Set(Variables.ObjectValue, ObjectData, VariableScopeNames.Topic); + this.State.Set(Variables.ArrayValue, TableData, VariableScopeNames.Topic); + this.State.Set(Variables.BlankValue, FormulaValue.NewBlank(), VariableScopeNames.Topic); } - #region Unsupported Expression Tests - - [Fact] - public void AdaptiveCardExpressionGetValueUnsupported() - { - this.EvaluateUnsupportedExpression(expressionEngine => expressionEngine.GetValue(AdaptiveCardExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)), this.Scopes.BuildState())); - } - - [Fact] - public void DialogExpressionGetValueUnsupported() - { - this.EvaluateUnsupportedExpression(expressionEngine => expressionEngine.GetValue(DialogExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)), this.Scopes.BuildState())); - } - - [Fact] - public void FileExpressionGetValueUnsupported() - { - this.EvaluateUnsupportedExpression(expressionEngine => expressionEngine.GetValue(FileExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)), this.Scopes.BuildState())); - } - - #endregion - #region BoolExpression Tests [Fact] @@ -101,16 +78,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: false); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void BoolExpressionGetValueForVariable(bool useState) + [Fact] + public void BoolExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( BoolExpression.Variable(PropertyPath.TopicVariable(Variables.BoolValue)), - expectedValue: true, - useState); + expectedValue: true); } [Fact] @@ -158,16 +132,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: "test"); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void StringExpressionGetValueForVariable(bool useState) + [Fact] + public void StringExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( StringExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)), - expectedValue: "Hello World", - useState); + expectedValue: "Hello World"); } [Fact] @@ -184,7 +155,7 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest { // Arrange RecordValue state = FormulaValue.NewRecordFromFields([new NamedValue("test", FormulaValue.New("value"))]); - this.Scopes.Set("TestRecord", state, VariableScopeNames.Global); + this.State.Set("TestRecord", state, VariableScopeNames.Global); // Arrange, Act & Assert this.EvaluateExpression( @@ -233,16 +204,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: 7); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void IntExpressionGetValueForVariable(bool useState) + [Fact] + public void IntExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( IntExpression.Variable(PropertyPath.TopicVariable(Variables.IntValue)), - expectedValue: long.MaxValue, - useState); + expectedValue: long.MaxValue); } [Fact] @@ -337,16 +305,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: DataValue.Create("test")); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataValueExpressionGetValueForVariable(bool useState) + [Fact] + public void DataValueExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( ValueExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)), - expectedValue: DataValue.Create("Hello World"), - useState); + expectedValue: DataValue.Create("Hello World")); } [Fact] @@ -394,16 +359,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: VariablesToClear.ConversationScopedVariables); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void EnumExpressionGetValueForVariable(bool useState) + [Fact] + public void EnumExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( EnumExpression.Variable(PropertyPath.TopicVariable(Variables.EnumValue)), - expectedValue: VariablesToClear.ConversationScopedVariables, - useState); + expectedValue: VariablesToClear.ConversationScopedVariables); } [Fact] @@ -455,16 +417,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: null); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ObjectExpressionGetValueForVariable(bool useState) + [Fact] + public void ObjectExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( ObjectExpression.Variable(PropertyPath.TopicVariable(Variables.ObjectValue)), - expectedValue: ObjectData.ToRecord(), - useState); + expectedValue: ObjectData.ToRecord()); } #endregion @@ -504,16 +463,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: []); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ArrayExpressionGetValueForVariable(bool useState) + [Fact] + public void ArrayExpressionGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( ArrayExpression.Variable(PropertyPath.TopicVariable(Variables.ArrayValue)), - expectedValue: ["a", "b"], - useState); + expectedValue: ["a", "b"]); } [Fact] @@ -552,16 +508,13 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest expectedValue: []); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ArrayExpressionOnlyGetValueForVariable(bool useState) + [Fact] + public void ArrayExpressionOnlyGetValueForVariable() { // Arrange, Act & Assert this.EvaluateExpression( ArrayExpressionOnly.Variable(PropertyPath.TopicVariable(Variables.ArrayValue)), - expectedValue: ["a", "b"], - useState); + expectedValue: ["a", "b"]); } [Fact] @@ -575,85 +528,80 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest #endregion - private EvaluationResult EvaluateExpression(BoolExpression expression, bool expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + private EvaluationResult EvaluateExpression(BoolExpression expression, bool expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(BoolExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(StringExpression expression, string expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + private EvaluationResult EvaluateExpression(StringExpression expression, string expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(StringExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(IntExpression expression, long expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + private EvaluationResult EvaluateExpression(IntExpression expression, long expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(IntExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(NumberExpression expression, double expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + private EvaluationResult EvaluateExpression(NumberExpression expression, double expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(NumberExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(ValueExpression expression, DataValue expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + private EvaluationResult EvaluateExpression(ValueExpression expression, DataValue expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(ValueExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(EnumExpression expression, TEnum expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + private EvaluationResult EvaluateExpression(EnumExpression expression, TEnum expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) where TEnum : EnumWrapper - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(EnumExpression expression) where TException : Exception where TEnum : EnumWrapper - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private EvaluationResult EvaluateExpression(ObjectExpression expression, TValue? expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None) + private EvaluationResult EvaluateExpression(ObjectExpression expression, TValue? expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) where TValue : BotElement - => this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity); + => this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity); private void EvaluateInvalidExpression(ObjectExpression expression) where TException : Exception where TValue : BotElement - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private ImmutableArray EvaluateExpression(ArrayExpression expression, TValue[] expectedValue, bool useState = false) - => this.EvaluateArrayExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue); + private ImmutableArray EvaluateExpression(ArrayExpression expression, TValue[] expectedValue) + => this.EvaluateArrayExpression((evaluator) => evaluator.GetValue(expression), expectedValue); private void EvaluateInvalidExpression(ArrayExpression expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); - private ImmutableArray EvaluateExpression(ArrayExpressionOnly expression, TValue[] expectedValue, bool useState = false) - => this.EvaluateArrayExpression((evaluator) => useState ? evaluator.GetValue(expression, this.Scopes) : evaluator.GetValue(expression, this.Scopes.BuildState()), expectedValue); + private ImmutableArray EvaluateExpression(ArrayExpressionOnly expression, TValue[] expectedValue) + => this.EvaluateArrayExpression((evaluator) => evaluator.GetValue(expression), expectedValue); private void EvaluateInvalidExpression(ArrayExpressionOnly expression) where TException : Exception - => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression, this.Scopes)); + => this.EvaluateInvalidExpression((evaluator) => evaluator.GetValue(expression)); private EvaluationResult EvaluateExpression( Func> evaluator, TValue? expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None) { - // Arrange - RecalcEngine engine = this.CreateEngine(); - this.Scopes.Bind(engine); - WorkflowExpressionEngine expressionEngine = new(engine); - // Act - EvaluationResult result = evaluator.Invoke(expressionEngine); + EvaluationResult result = evaluator.Invoke(this.State.Evaluator); // Assert Assert.Equal(expectedValue, result.Value); @@ -666,13 +614,8 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest Func> evaluator, TValue[] expectedValue) { - // Arrange - RecalcEngine engine = this.CreateEngine(); - this.Scopes.Bind(engine); - WorkflowExpressionEngine expressionEngine = new(engine); - // Act - ImmutableArray result = evaluator.Invoke(expressionEngine); + ImmutableArray result = evaluator.Invoke(this.State.Evaluator); // Assert Assert.Equal(expectedValue.Length, result.Length); @@ -683,23 +626,7 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest private void EvaluateInvalidExpression(Action evaluator) where TException : Exception { - // Arrange - RecalcEngine engine = this.CreateEngine(); - this.Scopes.Bind(engine); - WorkflowExpressionEngine expressionEngine = new(engine); - - // Act - Assert.Throws(() => evaluator.Invoke(expressionEngine)); - } - - private void EvaluateUnsupportedExpression(Action evaluator) - { - // Arrange - RecalcEngine engine = this.CreateEngine(); - this.Scopes.Bind(engine); - WorkflowExpressionEngine expressionEngine = new(engine); - - // Act - Assert.Throws(() => evaluator.Invoke(expressionEngine)); + // Act & Assert + Assert.Throws(() => evaluator.Invoke(this.State.Evaluator)); } } diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowScopesTests.cs b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowScopesTests.cs index c09a448656..606e21c327 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowScopesTests.cs +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/PowerFx/WorkflowScopesTests.cs @@ -9,17 +9,16 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.PowerFx; public class WorkflowScopesTests { + internal WorkflowFormulaState State { get; } = new(RecalcEngineFactory.Create()); + [Fact] public void ConstructorInitializesAllScopes() { - // Arrange & Act - WorkflowScopes scopes = new(); - - // Assert - RecordValue envRecord = scopes.BuildRecord(VariableScopeNames.Environment); - RecordValue topicRecord = scopes.BuildRecord(VariableScopeNames.Topic); - RecordValue globalRecord = scopes.BuildRecord(VariableScopeNames.Global); - RecordValue systemRecord = scopes.BuildRecord(VariableScopeNames.System); + // Act & Assert + RecordValue envRecord = this.State.BuildRecord(VariableScopeNames.Environment); + RecordValue topicRecord = this.State.BuildRecord(VariableScopeNames.Topic); + RecordValue globalRecord = this.State.BuildRecord(VariableScopeNames.Global); + RecordValue systemRecord = this.State.BuildRecord(VariableScopeNames.System); Assert.NotNull(envRecord); Assert.NotNull(topicRecord); @@ -30,11 +29,8 @@ public class WorkflowScopesTests [Fact] public void BuildRecordWhenEmpty() { - // Arrange - WorkflowScopes scopes = new(); - // Act - RecordValue record = scopes.BuildRecord(VariableScopeNames.Topic); + RecordValue record = this.State.BuildRecord(VariableScopeNames.Topic); // Assert Assert.NotNull(record); @@ -45,12 +41,11 @@ public class WorkflowScopesTests public void BuildRecordContainsSetValues() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); - scopes.Set("key1", testValue, VariableScopeNames.Topic); + this.State.Set("key1", testValue, VariableScopeNames.Topic); // Act - RecordValue record = scopes.BuildRecord(VariableScopeNames.Topic); + RecordValue record = this.State.BuildRecord(VariableScopeNames.Topic); // Assert Assert.NotNull(record); @@ -63,24 +58,23 @@ public class WorkflowScopesTests public void BuildRecordForAllScopeTypes() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); // Act & Assert - scopes.Set("envKey", testValue, VariableScopeNames.Environment); - RecordValue envRecord = scopes.BuildRecord(VariableScopeNames.Environment); + this.State.Set("envKey", testValue, VariableScopeNames.Environment); + RecordValue envRecord = this.State.BuildRecord(VariableScopeNames.Environment); Assert.Single(envRecord.Fields); - scopes.Set("topicKey", testValue, VariableScopeNames.Topic); - RecordValue topicRecord = scopes.BuildRecord(VariableScopeNames.Topic); + this.State.Set("topicKey", testValue, VariableScopeNames.Topic); + RecordValue topicRecord = this.State.BuildRecord(VariableScopeNames.Topic); Assert.Single(topicRecord.Fields); - scopes.Set("globalKey", testValue, VariableScopeNames.Global); - RecordValue globalRecord = scopes.BuildRecord(VariableScopeNames.Global); + this.State.Set("globalKey", testValue, VariableScopeNames.Global); + RecordValue globalRecord = this.State.BuildRecord(VariableScopeNames.Global); Assert.Single(globalRecord.Fields); - scopes.Set("systemKey", testValue, VariableScopeNames.System); - RecordValue systemRecord = scopes.BuildRecord(VariableScopeNames.System); + this.State.Set("systemKey", testValue, VariableScopeNames.System); + RecordValue systemRecord = this.State.BuildRecord(VariableScopeNames.System); Assert.Single(systemRecord.Fields); } @@ -88,12 +82,11 @@ public class WorkflowScopesTests public void GetWithImplicitScope() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); - scopes.Set("key1", testValue, VariableScopeNames.Topic); + this.State.Set("key1", testValue, VariableScopeNames.Topic); // Act - FormulaValue result = scopes.Get("key1"); + FormulaValue result = this.State.Get("key1"); // Assert Assert.Equal(testValue, result); @@ -103,12 +96,11 @@ public class WorkflowScopesTests public void GetWithSpecifiedScope() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); - scopes.Set("key1", testValue, VariableScopeNames.Global); + this.State.Set("key1", testValue, VariableScopeNames.Global); // Act - FormulaValue result = scopes.Get("key1", VariableScopeNames.Global); + FormulaValue result = this.State.Get("key1", VariableScopeNames.Global); // Assert Assert.Equal(testValue, result); @@ -118,14 +110,13 @@ public class WorkflowScopesTests public void SetDefaultScope() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); // Act - scopes.Set("key1", testValue); + this.State.Set("key1", testValue); // Assert - FormulaValue result = scopes.Get("key1", VariableScopeNames.Topic); + FormulaValue result = this.State.Get("key1", VariableScopeNames.Topic); Assert.Equal(testValue, result); } @@ -133,14 +124,13 @@ public class WorkflowScopesTests public void SetSpecifiedScope() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); // Act - scopes.Set("key1", testValue, VariableScopeNames.System); + this.State.Set("key1", testValue, VariableScopeNames.System); // Assert - FormulaValue result = scopes.Get("key1", VariableScopeNames.System); + FormulaValue result = this.State.Get("key1", VariableScopeNames.System); Assert.Equal(testValue, result); } @@ -148,16 +138,15 @@ public class WorkflowScopesTests public void SetOverwritesExistingValue() { // Arrange - WorkflowScopes scopes = new(); FormulaValue initialValue = FormulaValue.New("initial"); FormulaValue newValue = FormulaValue.New("new"); // Act - scopes.Set("key1", initialValue, VariableScopeNames.Topic); - scopes.Set("key1", newValue, VariableScopeNames.Topic); + this.State.Set("key1", initialValue, VariableScopeNames.Topic); + this.State.Set("key1", newValue, VariableScopeNames.Topic); // Assert - FormulaValue result = scopes.Get("key1", VariableScopeNames.Topic); + FormulaValue result = this.State.Get("key1", VariableScopeNames.Topic); Assert.Equal(newValue, result); } @@ -165,21 +154,20 @@ public class WorkflowScopesTests public void RemoveSpecifiedScope() { // Arrange - WorkflowScopes scopes = new(); FormulaValue testValue = FormulaValue.New("test"); // Act - scopes.Set("key1", testValue); + this.State.Set("key1", testValue); // Assert - FormulaValue result = scopes.Get("key1"); + FormulaValue result = this.State.Get("key1"); Assert.Equal(testValue, result); // Act - scopes.Reset("key1"); + this.State.Reset("key1"); // Assert - FormulaValue resultBlank = scopes.Get("key1"); + FormulaValue resultBlank = this.State.Get("key1"); Assert.IsType(resultBlank); } } diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Condition.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Condition.yaml index 5c29fe4a00..99fd2f2ea7 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Condition.yaml +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Condition.yaml @@ -9,7 +9,7 @@ beginDialog: - kind: SetVariable id: setVariable_test variable: Topic.TestValue - value: =Value(System.LastMessage.Text) + value: =System.LastMessageText - kind: ConditionGroup id: conditionGroup_test diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/ConditionElse.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/ConditionElse.yaml index 3f0073439d..35026406f6 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/ConditionElse.yaml +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/ConditionElse.yaml @@ -9,7 +9,7 @@ beginDialog: - kind: SetVariable id: setVariable_test variable: Topic.TestValue - value: =Value(System.LastMessage.Text) + value: =System.LastMessageText - kind: ConditionGroup id: conditionGroup_test diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Goto.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Goto.yaml index 1b300dbe1e..b2c28be457 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Goto.yaml +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/Goto.yaml @@ -10,15 +10,15 @@ beginDialog: actionId: end_all - kind: SendActivity - id: sendActivity_1 + id: send_activity_1 activity: NEVER 1! - kind: SendActivity - id: sendActivity_2 + id: send_activity_2 activity: NEVER 2! - kind: SendActivity - id: sendActivity_3 + id: send_activity_3 activity: NEVER 3! - kind: EndConversation diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SendActivity.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SendActivity.yaml new file mode 100644 index 0000000000..1b1e1b8883 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SendActivity.yaml @@ -0,0 +1,16 @@ +kind: AdaptiveDialog +beginDialog: + kind: OnActivity + id: my_workflow + type: Message + actions: + + - kind: SetVariable + id: set_input + variable: Topic.TestValue + value: =System.LastMessageText + + - kind: SendActivity + id: activity_input + activity: |- + Input: {Topic.TestValue} diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetTextVariable.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetTextVariable.yaml index 75d8b99239..8f88186994 100644 --- a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetTextVariable.yaml +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetTextVariable.yaml @@ -7,4 +7,4 @@ beginDialog: - kind: SetTextVariable id: set_text variable: Topic.TestVar - value: "Test content" + value: Test content diff --git a/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetVariable.yaml b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetVariable.yaml new file mode 100644 index 0000000000..5f191db2a5 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/Workflows/SetVariable.yaml @@ -0,0 +1,10 @@ +kind: AdaptiveDialog +beginDialog: + kind: OnActivity + id: my_workflow + type: Message + actions: + - kind: SetVariable + id: set_var + variable: Topic.TestVar + value: =3 diff --git a/workflows/HumanInLoop.yaml b/workflows/HumanInLoop.yaml index 997dc558df..a1a28d3b61 100644 --- a/workflows/HumanInLoop.yaml +++ b/workflows/HumanInLoop.yaml @@ -29,16 +29,29 @@ beginDialog: entity: kind: StringPrebuiltEntity - # Respond with input - - kind: SendActivity - id: sendActivity_input - activity: |- - You entered: - {Topic.OriginalInput} + - kind: ConditionGroup + id: check_completion + conditions: - Confirmed input: - {Topic.ConfirmedInput} + - condition: =Topic.OriginalInput <> Topic.ConfirmedInput + id: check_confirm + actions: + - kind: SendActivity + id: sendActivity_mismatch + activity: |- + "{Topic.ConfirmedInput}" does not match the original input of "{Topic.OriginalInput}". Please try again. + - kind: GotoAction + id: goto_again + actionId: question_confirm + elseActions: # // %%% REMOVE / BUG + - kind: SendActivity + id: sendActivity_confirmed + activity: |- + You entered: + {Topic.OriginalInput} + Confirmed input: + {Topic.ConfirmedInput} diff --git a/workflows/MathChat.yaml b/workflows/MathChat.yaml index 3ddb611e21..581bc0d5c2 100644 --- a/workflows/MathChat.yaml +++ b/workflows/MathChat.yaml @@ -34,7 +34,7 @@ beginDialog: - kind: SetVariable id: set_project variable: Topic.Project - value: =System.LastMessageText + value: =System.LastMessage.Text - kind: InvokeAzureAgent id: question_student @@ -43,8 +43,6 @@ beginDialog: name: =Env.FOUNDRY_AGENT_STUDENT input: messages: =[UserMessage(Topic.Project)] - output: - messages: Topic.Answer - kind: ResetVariable id: reset_project @@ -55,6 +53,8 @@ beginDialog: conversationId: =System.ConversationId agent: name: =Env.FOUNDRY_AGENT_TEACHER + output: + messages: Topic.TeacherResponse - kind: SetVariable id: set_count_increment @@ -65,7 +65,7 @@ beginDialog: id: check_completion conditions: - - condition: =!IsBlank(Find("congratulations", Lower(System.LastMessageText))) + - condition: =!IsBlank(Find("CONGRATULATIONS", Upper(Topic.TeacherResponse.Text))) id: check_turn_done actions: