.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 <taochen@microsoft.com>

* 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 <taochen@microsoft.com>
This commit is contained in:
Chris
2025-09-15 20:09:12 -07:00
committed by GitHub
Unverified
parent a0664201e2
commit fb513c38a6
62 changed files with 768 additions and 752 deletions
+1
View File
@@ -37,6 +37,7 @@
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="9.0.9" />
<PackageVersion Include="System.Text.Json" Version="9.0.9" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.9" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.9" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.9" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
@@ -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
/// </summary>
/// <typeparam name="TInput">The type of the input message</typeparam>
/// <param name="workflowFile">The path to the workflow.</param>
/// <param name="options">The execution context for the workflow.</param>
/// <param name="options">Configuration options for workflow execution.</param>
/// <param name="inputTransform">An optional function to transform the input message into a <see cref="ChatMessage"/>.</param>
/// <returns></returns>
public static Workflow<TInput> Build<TInput>(
@@ -40,7 +39,7 @@ public static class DeclarativeWorkflowBuilder
/// </summary>
/// <typeparam name="TInput">The type of the input message</typeparam>
/// <param name="yamlReader">The reader that provides the workflow object model YAML.</param>
/// <param name="options">The execution context for the workflow.</param>
/// <param name="options">Configuration options for workflow execution.</param>
/// <param name="inputTransform">An optional function to transform the input message into a <see cref="ChatMessage"/>.</param>
/// <returns>The <see cref="Workflow"/> that corresponds with the YAML object model.</returns>
public static Workflow<TInput> Build<TInput>(
@@ -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<TInput> 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<TInput>();
return visitor.Complete<TInput>();
}
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<AdaptiveDialog>().First();
}
}
@@ -141,7 +141,7 @@ internal static class ChatMessageExtensions
}
AgentMessageRole? role = null;
if (Enum.TryParse<AgentMessageRole>(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<RecordValue> GetContentRecords(this ChatMessage message) =>
message.Contents.Select(content => RecordValue.NewRecordFromFields(content.GetContentFields()));
message.Contents.Select(content => FormulaValue.NewRecordFromFields(content.GetContentFields()));
private static IEnumerable<NamedValue> GetContentFields(this AIContent content)
{
@@ -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<string, object?> dictionary = expandoObject;
foreach (KeyValuePair<string, DataValue> field in recordDataValue.Properties)
{
dictionary[field.Key] = field.Value?.ToObject();
}
return expandoObject;
}
private static RecordType ParseRecordType(this RecordDataValue record)
{
RecordType recordType = RecordType.Empty();
@@ -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<TDialog>(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<TDialog>().First();
}
}
@@ -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)
{
@@ -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<TValue>(this IWorkflowContext context, PropertyPath variablePath, TValue? value) =>
context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.VariableScopeName));
public static async Task<WorkflowFormulaState> 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;
}
}
@@ -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>(TAction model, DeclarativeWorkflowState state) :
internal abstract class DeclarativeActionExecutor<TAction>(TAction model, WorkflowFormulaState state) :
DeclarativeActionExecutor(model, state)
where TAction : DialogAction
{
@@ -24,16 +24,15 @@ internal abstract class DeclarativeActionExecutor<TAction>(TAction model, Declar
internal abstract class DeclarativeActionExecutor : Executor<ExecutorResultMessage>
{
private static readonly ImmutableHashSet<string> s_mutableScopes =
new HashSet<string>
{
VariableScopeNames.Topic,
VariableScopeNames.Global,
}.ToImmutableHashSet();
private static readonly FrozenSet<string> 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<ExecutorResultMessa
internal ILogger Logger { get; set; } = NullLogger<DeclarativeActionExecutor>.Instance;
protected DeclarativeWorkflowState State { get; }
protected WorkflowFormulaState State { get; }
protected virtual bool IsDiscreteAction => true;
@@ -72,7 +71,7 @@ internal abstract class DeclarativeActionExecutor : Executor<ExecutorResultMessa
try
{
object? result = await this.ExecuteAsync(context, cancellationToken: default).ConfigureAwait(false);
object? result = await this.ExecuteAsync(new DeclarativeWorkflowContext(context, this.State), cancellationToken: default).ConfigureAwait(false);
if (this.EmitResultEvent)
{
@@ -119,7 +118,7 @@ internal abstract class DeclarativeActionExecutor : Executor<ExecutorResultMessa
throw new DeclarativeModelException($"Invalid scope: {targetPath.VariableScopeName}");
}
await this.State.SetAsync(targetPath, result, context).ConfigureAwait(false);
await context.QueueStateUpdateAsync(targetPath, result).ConfigureAwait(false);
#if DEBUG
string? resultValue = result.Format();
@@ -127,7 +126,7 @@ internal abstract class DeclarativeActionExecutor : Executor<ExecutorResultMessa
Debug.WriteLine(
$"""
STATE: {this.GetType().Name} [{this.Id}]
NAME: {targetPath.Format()}
NAME: {targetPath}
VALUE:{valuePosition}{result.Format()} ({result.GetType().Name})
""");
#endif
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Agents.Workflows.Declarative.Extensions;
using Microsoft.Agents.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.Workflows.Declarative.Interpreter;
internal sealed class DeclarativeWorkflowContext : IWorkflowContext
{
public DeclarativeWorkflowContext(IWorkflowContext source, WorkflowFormulaState state)
{
this.Source = source;
this.State = state;
}
private IWorkflowContext Source { get; }
public WorkflowFormulaState State { get; }
/// <inheritdoc/>
public ValueTask AddEventAsync(WorkflowEvent workflowEvent) => this.Source.AddEventAsync(workflowEvent);
/// <inheritdoc/>
public ValueTask QueueClearScopeAsync(string? scopeName = null)
{
this.State.ResetAll(scopeName);
return this.Source.QueueClearScopeAsync(scopeName);
}
/// <inheritdoc/>
public async ValueTask QueueStateUpdateAsync<T>(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);
}
}
/// <inheritdoc/>
public ValueTask<T?> ReadStateAsync<T>(string key, string? scopeName = null) => this.Source.ReadStateAsync<T>(key, scopeName);
/// <inheritdoc/>
public ValueTask<HashSet<string>> ReadStateKeysAsync(string? scopeName = null) => this.Source.ReadStateKeysAsync(scopeName);
/// <inheritdoc/>
public ValueTask SendMessageAsync(object message, string? targetId = null) => this.Source.SendMessageAsync(message, targetId);
}
@@ -12,15 +12,18 @@ namespace Microsoft.Agents.Workflows.Declarative.Interpreter;
/// </summary>
internal sealed class DeclarativeWorkflowExecutor<TInput>(
string workflowId,
DeclarativeWorkflowState state,
WorkflowFormulaState state,
Func<TInput, ChatMessage> inputTransform) :
Executor<TInput>(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);
}
@@ -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)
{
@@ -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<string> s_mutableScopes =
new HashSet<string>
{
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<TemplateLine> 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<string> keys = await context.ReadStateKeysAsync(scopeName).ConfigureAwait(false);
foreach (string key in keys)
{
object? value = await context.ReadStateAsync<object>(key, scopeName).ConfigureAwait(false);
this._scopes.Set(key, value.ToFormula(), scopeName);
}
}
}
}
@@ -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<TMessage>(IWorkflowContext context, TMessage message, CancellationToken cancellationToken) where TMessage : notnull;
internal sealed class DelegateActionExecutor(string actionId, DelegateAction<ExecutorResultMessage>? action = null, bool emitResult = true)
: DelegateActionExecutor<ExecutorResultMessage>(actionId, action, emitResult)
internal sealed class DelegateActionExecutor(string actionId, WorkflowFormulaState state, DelegateAction<ExecutorResultMessage>? action = null, bool emitResult = true)
: DelegateActionExecutor<ExecutorResultMessage>(actionId, state, action, emitResult)
{
public override ValueTask HandleAsync(ExecutorResultMessage message, IWorkflowContext context)
{
@@ -22,12 +23,14 @@ internal sealed class DelegateActionExecutor(string actionId, DelegateAction<Exe
internal class DelegateActionExecutor<TMessage> : Executor<TMessage> where TMessage : notnull
{
private readonly WorkflowFormulaState _state;
private readonly DelegateAction<TMessage>? _action;
private readonly bool _emitResult;
public DelegateActionExecutor(string actionId, DelegateAction<TMessage>? action = null, bool emitResult = true)
public DelegateActionExecutor(string actionId, WorkflowFormulaState state, DelegateAction<TMessage>? action = null, bool emitResult = true)
: base(actionId)
{
this._state = state;
this._action = action;
this._emitResult = emitResult;
}
@@ -36,7 +39,7 @@ internal class DelegateActionExecutor<TMessage> : Executor<TMessage> 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)
@@ -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<InputRequest, InputResponse>(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<InputResponse>(captureId, questionExecutor.CaptureResponseAsync, emitResult: false), parentId);
this.ContinueWith(new DelegateActionExecutor<InputResponse>(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<ExecutorResultMessage>? 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() ??
@@ -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<TInput> GetWorkflow<TInput>() => this._visitor.Complete<TInput>();
public override bool DefaultVisit(BotElement definition)
{
if (definition is DialogAction action)
@@ -27,6 +27,7 @@
<PackageReference Include="Microsoft.Bot.ObjectModel.Json" />
<PackageReference Include="Microsoft.Bot.ObjectModel.PowerFx" />
<PackageReference Include="Microsoft.PowerFx.Interpreter" />
<PackageReference Include="System.Collections.Immutable" />
</ItemGroup>
<ItemGroup>
@@ -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<AddConversationMessage>(model, state)
{
protected override async ValueTask<object?> 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();
}
@@ -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<ClearAllVariables>(model, state)
{
protected override ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
EvaluationResult<VariablesToClearWrapper> variablesResult = this.State.ExpressionEngine.GetValue<VariablesToClearWrapper>(this.Model.Variables);
EvaluationResult<VariablesToClearWrapper> variablesResult = this.State.Evaluator.GetValue<VariablesToClearWrapper>(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}]
@@ -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<Conditi
public static string Else(ConditionGroup model) => 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<Conditi
continue; // Skip if no condition is defined
}
EvaluationResult<bool> expressionResult = this.State.ExpressionEngine.GetValue(conditionItem.Condition);
EvaluationResult<bool> expressionResult = this.State.Evaluator.GetValue(conditionItem.Condition);
if (expressionResult.Value)
{
return Steps.Item(this.Model, conditionItem);
@@ -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<CopyConversationMessages>(model, state)
{
protected override async ValueTask<object?> 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<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Messages);
EvaluationResult<DataValue> expressionResult = this.State.Evaluator.GetValue(this.Model.Messages);
messages = expressionResult.Value;
}
@@ -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<CreateConversation>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
@@ -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<EditTable>(model, state)
internal sealed class EditTableExecutor(EditTable model, WorkflowFormulaState state) : DeclarativeActionExecutor<EditTable>(model, state)
{
protected override async ValueTask<object?> 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<DataValue> addResult = this.State.ExpressionEngine.GetValue(addItemValue);
EvaluationResult<DataValue> 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<DataValue> removeResult = this.State.ExpressionEngine.GetValue(removeItemValue);
EvaluationResult<DataValue> 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);
@@ -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<EditTableV2>(model, state)
internal sealed class EditTableV2Executor(EditTableV2 model, WorkflowFormulaState state) : DeclarativeActionExecutor<EditTableV2>(model, state)
{
protected override async ValueTask<object?> 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<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(addItemValue);
EvaluationResult<DataValue> 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<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(removeItemValue);
EvaluationResult<DataValue> 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);
@@ -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<Foreach>
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<Foreach>
}
else
{
EvaluationResult<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Items);
EvaluationResult<DataValue> 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<Foreach>
{
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++;
@@ -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<GotoAction>(model, state)
{
protected override ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
@@ -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<InvokeAzureAgent>(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<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(this.AgentInput.Messages);
EvaluationResult<DataValue> 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<string> conversationIdResult = this.State.ExpressionEngine.GetValue(this.Model.ConversationId);
EvaluationResult<string> 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<bool> autoSendResult = this.State.ExpressionEngine.GetValue(this.AgentOutput.AutoSend);
EvaluationResult<bool> autoSendResult = this.State.Evaluator.GetValue(this.AgentOutput.AutoSend);
return autoSendResult.Value;
}
@@ -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<ParseValue>(model, state)
{
protected override async ValueTask<object?> 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<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(valueExpression);
EvaluationResult<DataValue> expressionResult = this.State.Evaluator.GetValue(valueExpression);
FormulaValue? parsedResult = null;
@@ -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<Question>(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();
}
}
@@ -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<ResetVariable>(model, state)
{
protected override ValueTask<object?> 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;
@@ -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<RetrieveConversationMessage>(model, state)
{
protected override async ValueTask<object?> 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);
@@ -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<RetrieveConversationMessages>(model, state)
{
protected override async ValueTask<object?> 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;
}
@@ -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<SendActivity>(model, state)
{
protected override async ValueTask<object?> 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);
}
@@ -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<SetMultipleVariables>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
@@ -28,7 +29,7 @@ internal sealed class SetMultipleVariablesExecutor(SetMultipleVariables model, D
}
else
{
EvaluationResult<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(assignment.Value);
EvaluationResult<DataValue> expressionResult = this.State.Evaluator.GetValue(assignment.Value);
await this.AssignAsync(assignment.Variable, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
}
@@ -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<SetTextVariable>(model, state)
{
protected override async ValueTask<object?> 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);
}
@@ -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<SetVariable>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
@@ -24,7 +25,7 @@ internal sealed class SetVariableExecutor(SetVariable model, DeclarativeWorkflow
}
else
{
EvaluationResult<DataValue> expressionResult = this.State.ExpressionEngine.GetValue(this.Model.Value);
EvaluationResult<DataValue> expressionResult = this.State.Evaluator.GetValue(this.Model.Value);
await this.AssignAsync(variablePath, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
}
@@ -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<string> AllNames { get; } = GetNames().ToImmutableHashSet();
public static FrozenSet<string> AllNames { get; } = GetNames().ToFrozenSet();
public static IEnumerable<string> 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);
}
}
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Agents.Workflows.Declarative.PowerFx;
/// <summary>
/// Represents the absence of an assigned value for a variable used in an expression.
/// </summary>
public sealed record class UnassignedValue
{
/// <summary>
/// A singleton instance of <see cref="UnassignedValue"/>.
/// </summary>
public static UnassignedValue Instance { get; } = new();
}
@@ -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<string> EnvironmentVariables, IEnumerable<VariableInformationDiagnostic> UserVariables);
internal static class WorkflowDiagnostics
{
private static readonly WorkflowFeatureConfiguration s_semanticFeatureConfig = new();
public static void Initialize<TElement>(this WorkflowScopes scopes, TElement workflowElement, IConfiguration? configuration) where TElement : BotElement, IDialogBase
public static WorkflowTypeInfo Describe<TElement>(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<TElement>(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);
}
}
@@ -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<bool> GetValue(BoolExpression boolean, WorkflowScopes? state = null) => this.GetValue(boolean, state, this.EvaluateScope);
public EvaluationResult<bool> GetValue(BoolExpression boolean) => this.Evaluate(boolean);
public EvaluationResult<bool> GetValue(BoolExpression boolean, RecordDataValue state) => this.GetValue(boolean, state, this.EvaluateState);
public EvaluationResult<string> GetValue(StringExpression expression) => this.Evaluate(expression);
public EvaluationResult<string> GetValue(StringExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope);
public EvaluationResult<DataValue> GetValue(ValueExpression expression) => this.Evaluate(expression);
public EvaluationResult<string> GetValue(StringExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState);
public EvaluationResult<long> GetValue(IntExpression expression) => this.Evaluate(expression);
public EvaluationResult<DataValue> GetValue(ValueExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope);
public EvaluationResult<double> GetValue(NumberExpression expression) => this.Evaluate(expression);
public EvaluationResult<DataValue> GetValue(ValueExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState);
public EvaluationResult<TValue?> GetValue<TValue>(ObjectExpression<TValue> expression) where TValue : BotElement => this.Evaluate(expression);
public EvaluationResult<long> GetValue(IntExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope);
public ImmutableArray<T> GetValue<T>(ArrayExpression<T> expression) => this.Evaluate(expression).Value;
public EvaluationResult<long> GetValue(IntExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState);
public ImmutableArray<T> GetValue<T>(ArrayExpressionOnly<T> expression) => this.Evaluate(expression).Value;
public EvaluationResult<double> GetValue(NumberExpression expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope);
public EvaluationResult<TValue> GetValue<TValue>(EnumExpression<TValue> expression) where TValue : EnumWrapper =>
this.Evaluate<TValue>(expression);
public EvaluationResult<double> GetValue(NumberExpression expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState);
public EvaluationResult<TValue?> GetValue<TValue>(ObjectExpression<TValue> expression, WorkflowScopes? state = null) where TValue : BotElement => this.GetValue(expression, state, this.EvaluateScope);
public EvaluationResult<TValue?> GetValue<TValue>(ObjectExpression<TValue> expression, RecordDataValue state) where TValue : BotElement => this.GetValue(expression, state, this.EvaluateState);
public ImmutableArray<T> GetValue<T>(ArrayExpression<T> expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope).Value;
public ImmutableArray<T> GetValue<T>(ArrayExpression<T> expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState).Value;
public ImmutableArray<T> GetValue<T>(ArrayExpressionOnly<T> expression, WorkflowScopes? state = null) => this.GetValue(expression, state, this.EvaluateScope).Value;
public ImmutableArray<T> GetValue<T>(ArrayExpressionOnly<T> expression, RecordDataValue state) => this.GetValue(expression, state, this.EvaluateState).Value;
public EvaluationResult<TValue> GetValue<TValue>(EnumExpression<TValue> expression, WorkflowScopes? state = null) where TValue : EnumWrapper =>
this.GetValue<TValue, WorkflowScopes>(expression, state, this.EvaluateScope);
public EvaluationResult<TValue> GetValue<TValue>(EnumExpression<TValue> expression, RecordDataValue state) where TValue : EnumWrapper =>
this.GetValue<TValue, RecordDataValue>(expression, state, this.EvaluateState);
public DialogSchemaName GetValue(DialogExpression expression, RecordDataValue state)
{
throw new NotSupportedException();
}
public EvaluationResult<string> GetValue(AdaptiveCardExpression expression, RecordDataValue state)
{
throw new NotSupportedException();
}
public EvaluationResult<FileDataValue?> GetValue(FileExpression expression, RecordDataValue state)
{
throw new NotSupportedException();
}
private EvaluationResult<bool> GetValue<TState>(BoolExpression expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<bool> Evaluate(BoolExpression expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -84,7 +50,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<bool>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
if (expressionResult.Value is BlankValue)
{
@@ -99,7 +65,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<bool>(formulaValue.Value, expressionResult.Sensitivity);
}
private EvaluationResult<string> GetValue<TState>(StringExpression expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<string> Evaluate(StringExpression expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -108,7 +74,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<string>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
if (expressionResult.Value is BlankValue)
{
@@ -128,7 +94,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<string>(formulaValue.Value, expressionResult.Sensitivity);
}
private EvaluationResult<long> GetValue<TState>(IntExpression expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<long> Evaluate(IntExpression expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -137,7 +103,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<long>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
if (expressionResult.Value is BlankValue)
{
@@ -152,7 +118,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<long>(Convert.ToInt64(formulaValue.Value), expressionResult.Sensitivity);
}
private EvaluationResult<double> GetValue<TState>(NumberExpression expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<double> Evaluate(NumberExpression expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -161,7 +127,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<double>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
if (expressionResult.Value is BlankValue)
{
@@ -181,7 +147,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<double>(formulaValue.Value, expressionResult.Sensitivity);
}
private EvaluationResult<DataValue> GetValue<TState>(ValueExpression expression, TState? state, Func<ExpressionBase, TState?, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<DataValue> Evaluate(ValueExpression expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -190,12 +156,12 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<DataValue>(expression.LiteralValue ?? BlankDataValue.Instance, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
return new EvaluationResult<DataValue>(expressionResult.Value.ToDataValue(), expressionResult.Sensitivity);
}
private EvaluationResult<TValue> GetValue<TValue, TState>(EnumExpression<TValue> expression, TState? state, Func<ExpressionBase, TState?, EvaluationResult<FormulaValue>> evaluator) where TValue : EnumWrapper
private EvaluationResult<TValue> Evaluate<TValue>(EnumExpression<TValue> expression) where TValue : EnumWrapper
{
Throw.IfNull(expression, nameof(expression));
@@ -204,7 +170,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<TValue>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
return expressionResult.Value switch
{
@@ -216,7 +182,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
};
}
private EvaluationResult<TValue?> GetValue<TValue, TState>(ObjectExpression<TValue> expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator) where TValue : BotElement
private EvaluationResult<TValue?> Evaluate<TValue>(ObjectExpression<TValue> expression) where TValue : BotElement
{
Throw.IfNull(expression, nameof(expression));
@@ -225,7 +191,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<TValue?>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
if (expressionResult.Value is BlankValue)
{
@@ -247,7 +213,7 @@ internal class WorkflowExpressionEngine : IExpressionEngine
}
}
private EvaluationResult<ImmutableArray<TValue>> GetValue<TState, TValue>(ArrayExpression<TValue> expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<ImmutableArray<TValue>> Evaluate<TValue>(ArrayExpression<TValue> expression)
{
Throw.IfNull(expression, nameof(expression));
@@ -256,16 +222,16 @@ internal class WorkflowExpressionEngine : IExpressionEngine
return new EvaluationResult<ImmutableArray<TValue>>(expression.LiteralValue, SensitivityLevel.None);
}
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
return new EvaluationResult<ImmutableArray<TValue>>(ParseArrayResults<TValue>(expressionResult.Value), expressionResult.Sensitivity);
}
private EvaluationResult<ImmutableArray<TValue>> GetValue<TState, TValue>(ArrayExpressionOnly<TValue> expression, TState state, Func<ExpressionBase, TState, EvaluationResult<FormulaValue>> evaluator)
private EvaluationResult<ImmutableArray<TValue>> Evaluate<TValue>(ArrayExpressionOnly<TValue> expression)
{
Throw.IfNull(expression, nameof(expression));
EvaluationResult<FormulaValue> expressionResult = evaluator.Invoke(expression, state);
EvaluationResult<FormulaValue> expressionResult = this.EvaluateScope(expression);
return new EvaluationResult<ImmutableArray<TValue>>(ParseArrayResults<TValue>(expressionResult.Value), expressionResult.Sensitivity);
}
@@ -302,40 +268,11 @@ internal class WorkflowExpressionEngine : IExpressionEngine
}
}
private EvaluationResult<FormulaValue> EvaluateState(ExpressionBase expression, RecordDataValue? state)
{
if (state is not null)
{
foreach (KeyValuePair<string, DataValue> 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<FormulaValue> EvaluateScope(ExpressionBase expression, WorkflowScopes? state = null)
{
state?.Bind(this._engine);
return this.Evaluate(expression);
}
private EvaluationResult<FormulaValue> Evaluate(ExpressionBase expression)
private EvaluationResult<FormulaValue> EvaluateScope(ExpressionBase expression)
{
string? expressionText =
expression.IsVariableReference ?
expression.VariableReference?.Format() :
expression.VariableReference?.ToString() :
expression.ExpressionText;
return new(this._engine.Eval(expressionText), SensitivityLevel.None);
@@ -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;
/// <summary>
/// Contains all variables scopes for a workflow.
/// </summary>
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<string> s_mutableScopes =
[
VariableScopeNames.Topic,
VariableScopeNames.Global,
VariableScopeNames.System,
];
private readonly Dictionary<string, WorkflowScope> _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<string> keys = await context.ReadStateKeysAsync(scopeName).ConfigureAwait(false);
foreach (string key in keys)
{
object? value = await context.ReadStateAsync<object>(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];
}
/// <summary>
/// The set of variables for a specific action scope.
/// </summary>
private sealed class WorkflowScope(string scopeName) : Dictionary<string, FormulaValue>
{
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<NamedValue> GetFields()
{
foreach (KeyValuePair<string, FormulaValue> kvp in this)
{
yield return new NamedValue(kvp.Key, kvp.Value);
}
}
}
}
}
@@ -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;
/// <summary>
/// Contains all action scopes for a process.
/// </summary>
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<string, WorkflowScope> _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<KeyValuePair<string, DataValue>> BuildStateFields()
{
foreach (KeyValuePair<string, WorkflowScope> 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);
}
}
/// <summary>
/// The set of variables for a specific action scope.
/// </summary>
private sealed class WorkflowScope(string scopeName) : Dictionary<string, FormulaValue>
{
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<NamedValue> GetFields()
{
foreach (KeyValuePair<string, FormulaValue> kvp in this)
{
yield return new NamedValue(kvp.Key, kvp.Value);
}
}
}
public RecordDataValue BuildState()
{
RecordDataValue.Builder recordBuilder = new();
foreach (KeyValuePair<string, FormulaValue> kvp in this)
{
recordBuilder.Properties.Add(kvp.Key, kvp.Value.ToDataValue());
}
return recordBuilder.Build();
}
}
}
@@ -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<WorkflowAgentProvider> 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);
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -17,7 +17,7 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow
public void InvalidModel()
{
// Arrange, Act, Assert
Assert.Throws<DeclarativeModelException>(() => new SetVariableExecutor(new SetVariable(), this.GetState()));
Assert.Throws<DeclarativeModelException>(() => 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
@@ -18,9 +18,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
/// </summary>
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<WorkflowScopes>(), this.Scopes);
StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build<WorkflowFormulaState>(), 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<BlankValue>(this.Scopes.Get(variableName, scopeName));
Assert.IsType<BlankValue>(this.State.Get(variableName, scopeName));
}
protected TAction AssignParent<TAction>(DialogAction.Builder actionBuilder) where TAction : DialogAction
@@ -76,9 +74,9 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
internal sealed class TestWorkflowExecutor() :
ReflectingExecutor<TestWorkflowExecutor>(nameof(TestWorkflowExecutor)),
IMessageHandler<WorkflowScopes>
IMessageHandler<WorkflowFormulaState>
{
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);
}
@@ -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");
@@ -11,7 +11,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.PowerFx;
/// </summary>
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;
}
@@ -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<TemplateLine> 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<DeclarativeModelException>(() => engine.Format(line));
Assert.Throws<DeclarativeModelException>(() => 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);
@@ -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<VariablesToClearWrapper>(
EnumExpression<VariablesToClearWrapper>.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<RecordDataValue>.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<string>.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<string>.Variable(PropertyPath.TopicVariable(Variables.ArrayValue)),
expectedValue: ["a", "b"],
useState);
expectedValue: ["a", "b"]);
}
[Fact]
@@ -575,85 +528,80 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
#endregion
private EvaluationResult<bool> 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<bool> EvaluateExpression(BoolExpression expression, bool expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TException>(BoolExpression expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
private EvaluationResult<string> 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<string> EvaluateExpression(StringExpression expression, string expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TException>(StringExpression expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
private EvaluationResult<long> 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<long> EvaluateExpression(IntExpression expression, long expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TException>(IntExpression expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
private EvaluationResult<double> 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<double> EvaluateExpression(NumberExpression expression, double expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TException>(NumberExpression expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
private EvaluationResult<DataValue> 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<DataValue> EvaluateExpression(ValueExpression expression, DataValue expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TException>(ValueExpression expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
private EvaluationResult<TEnum> EvaluateExpression<TEnum>(EnumExpression<TEnum> expression, TEnum expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
private EvaluationResult<TEnum> EvaluateExpression<TEnum>(EnumExpression<TEnum> expression, TEnum expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
where TEnum : EnumWrapper
=> this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue<TEnum>(expression, this.Scopes) : evaluator.GetValue<TEnum>(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity);
=> this.EvaluateExpression((evaluator) => evaluator.GetValue<TEnum>(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TEnum, TException>(EnumExpression<TEnum> expression)
where TException : Exception
where TEnum : EnumWrapper
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TEnum>(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TEnum>(expression));
private EvaluationResult<TValue?> EvaluateExpression<TValue>(ObjectExpression<TValue> expression, TValue? expectedValue, bool useState = false, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
private EvaluationResult<TValue?> EvaluateExpression<TValue>(ObjectExpression<TValue> expression, TValue? expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
where TValue : BotElement
=> this.EvaluateExpression((evaluator) => useState ? evaluator.GetValue<TValue>(expression, this.Scopes) : evaluator.GetValue<TValue>(expression, this.Scopes.BuildState()), expectedValue, expectedSensitivity);
=> this.EvaluateExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue, expectedSensitivity);
private void EvaluateInvalidExpression<TValue, TException>(ObjectExpression<TValue> expression)
where TException : Exception
where TValue : BotElement
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpression<TValue> expression, TValue[] expectedValue, bool useState = false)
=> this.EvaluateArrayExpression((evaluator) => useState ? evaluator.GetValue<TValue>(expression, this.Scopes) : evaluator.GetValue<TValue>(expression, this.Scopes.BuildState()), expectedValue);
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpression<TValue> expression, TValue[] expectedValue)
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue);
private void EvaluateInvalidExpression<TValue, TException>(ArrayExpression<TValue> expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpressionOnly<TValue> expression, TValue[] expectedValue, bool useState = false)
=> this.EvaluateArrayExpression((evaluator) => useState ? evaluator.GetValue<TValue>(expression, this.Scopes) : evaluator.GetValue<TValue>(expression, this.Scopes.BuildState()), expectedValue);
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpressionOnly<TValue> expression, TValue[] expectedValue)
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue);
private void EvaluateInvalidExpression<TValue, TException>(ArrayExpressionOnly<TValue> expression)
where TException : Exception
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression, this.Scopes));
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
private EvaluationResult<TValue> EvaluateExpression<TValue>(
Func<WorkflowExpressionEngine, EvaluationResult<TValue>> evaluator,
TValue? expectedValue,
SensitivityLevel expectedSensitivity = SensitivityLevel.None)
{
// Arrange
RecalcEngine engine = this.CreateEngine();
this.Scopes.Bind(engine);
WorkflowExpressionEngine expressionEngine = new(engine);
// Act
EvaluationResult<TValue> result = evaluator.Invoke(expressionEngine);
EvaluationResult<TValue> result = evaluator.Invoke(this.State.Evaluator);
// Assert
Assert.Equal(expectedValue, result.Value);
@@ -666,13 +614,8 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
Func<WorkflowExpressionEngine, ImmutableArray<TValue>> evaluator,
TValue[] expectedValue)
{
// Arrange
RecalcEngine engine = this.CreateEngine();
this.Scopes.Bind(engine);
WorkflowExpressionEngine expressionEngine = new(engine);
// Act
ImmutableArray<TValue> result = evaluator.Invoke(expressionEngine);
ImmutableArray<TValue> result = evaluator.Invoke(this.State.Evaluator);
// Assert
Assert.Equal(expectedValue.Length, result.Length);
@@ -683,23 +626,7 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
private void EvaluateInvalidExpression<TException>(Action<WorkflowExpressionEngine> evaluator) where TException : Exception
{
// Arrange
RecalcEngine engine = this.CreateEngine();
this.Scopes.Bind(engine);
WorkflowExpressionEngine expressionEngine = new(engine);
// Act
Assert.Throws<TException>(() => evaluator.Invoke(expressionEngine));
}
private void EvaluateUnsupportedExpression(Action<WorkflowExpressionEngine> evaluator)
{
// Arrange
RecalcEngine engine = this.CreateEngine();
this.Scopes.Bind(engine);
WorkflowExpressionEngine expressionEngine = new(engine);
// Act
Assert.Throws<NotSupportedException>(() => evaluator.Invoke(expressionEngine));
// Act & Assert
Assert.Throws<TException>(() => evaluator.Invoke(this.State.Evaluator));
}
}
@@ -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<BlankValue>(resultBlank);
}
}
@@ -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
@@ -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
@@ -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
@@ -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}
@@ -7,4 +7,4 @@ beginDialog:
- kind: SetTextVariable
id: set_text
variable: Topic.TestVar
value: "Test content"
value: Test content
@@ -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
+21 -8
View File
@@ -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}
+4 -4
View File
@@ -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: