.NET: Rename workflows projects (#975)

* Renaming Microsoft.Agent.Workflows to Microsoft.Agents.AI.Workflows

* Removing local settings.

* Removing remining old files from merge.
This commit is contained in:
Ben Thomas
2025-09-29 18:30:45 +00:00
committed by GitHub
parent aaf340096e
commit 647db9635a
340 changed files with 519 additions and 519 deletions
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class AddConversationMessageExecutor(AddConversationMessage model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<AddConversationMessage>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
ChatMessage newMessage = new(this.Model.Role.Value.ToChatRole(), [.. this.GetContent()]) { AdditionalProperties = this.GetMetadata() };
await agentProvider.CreateMessageAsync(conversationId, newMessage, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(this.Model.Message?.Path, newMessage.ToRecord(), context).ConfigureAwait(false);
return default;
}
private IEnumerable<AIContent> GetContent()
{
foreach (AddConversationMessageContent content in this.Model.Content)
{
AIContent? messageContent = content.Type.Value.ToContent(this.Engine.Format(content.Value));
if (messageContent is not null)
{
yield return messageContent;
}
}
}
private AdditionalPropertiesDictionary? GetMetadata()
{
if (this.Model.Metadata is null)
{
return null;
}
RecordDataValue? metadataValue = this.Evaluator.GetValue(this.Model.Metadata).Value;
return metadataValue.ToMetadata();
}
}
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, WorkflowFormulaState state)
: DeclarativeActionExecutor<ClearAllVariables>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
EvaluationResult<VariablesToClearWrapper> variablesResult = this.Evaluator.GetValue(this.Model.Variables);
string? scope = variablesResult.Value.Value switch
{
VariablesToClear.AllGlobalVariables => VariableScopeNames.Global,
VariablesToClear.ConversationScopedVariables => WorkflowFormulaState.DefaultScopeName,
VariablesToClear.ConversationHistory => null,
VariablesToClear.UserScopedVariables => null,
_ => null
};
if (scope is not null)
{
await context.QueueClearScopeAsync(scope).ConfigureAwait(false);
Debug.WriteLine(
$"""
STATE: {this.GetType().Name} [{this.Id}]
SCOPE: {scope}
""");
}
return default;
}
}
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor<ConditionGroup>
{
public static class Steps
{
public static string Item(ConditionGroup model, ConditionItem conditionItem)
{
if (conditionItem.Id is not null)
{
return conditionItem.Id;
}
int index = model.Conditions.IndexOf(conditionItem);
return $"{model.Id}_Items{index}";
}
public static string Else(ConditionGroup model) => model.ElseActions.Id.Value ?? $"{model.Id}_Else";
}
public ConditionGroupExecutor(ConditionGroup model, WorkflowFormulaState state)
: base(model, state)
{
}
protected override bool IsDiscreteAction => false;
public bool IsMatch(ConditionItem conditionItem, object? message)
{
ActionExecutorResult executorMessage = ActionExecutorResult.ThrowIfNot(message);
return string.Equals(Steps.Item(this.Model, conditionItem), executorMessage.Result as string, StringComparison.Ordinal);
}
public bool IsElse(object? message)
{
ActionExecutorResult executorMessage = ActionExecutorResult.ThrowIfNot(message);
return string.Equals(Steps.Else(this.Model), executorMessage.Result as string, StringComparison.Ordinal);
}
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
for (int index = 0; index < this.Model.Conditions.Length; ++index)
{
ConditionItem conditionItem = this.Model.Conditions[index];
if (conditionItem.Condition is null)
{
continue; // Skip if no condition is defined
}
EvaluationResult<bool> expressionResult = this.Evaluator.GetValue(conditionItem.Condition);
if (expressionResult.Value)
{
return Steps.Item(this.Model, conditionItem);
}
}
return Steps.Else(this.Model);
}
public async ValueTask DoneAsync(IWorkflowContext context, ActionExecutorResult _, CancellationToken cancellationToken) =>
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
}
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<CopyConversationMessages>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
IEnumerable<ChatMessage>? inputMessages = this.GetInputMessages();
if (inputMessages is not null)
{
foreach (ChatMessage message in inputMessages)
{
await agentProvider.CreateMessageAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
}
}
return default;
}
private IEnumerable<ChatMessage>? GetInputMessages()
{
DataValue? messages = null;
if (this.Model.Messages is not null)
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Messages);
messages = expressionResult.Value;
}
return messages?.ToChatMessages();
}
}
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class CreateConversationExecutor(CreateConversation model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<CreateConversation>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false);
await this.AssignAsync(this.Model.ConversationId?.Path, FormulaValue.New(conversationId), context).ConfigureAwait(false);
return default;
}
}
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class DefaultActionExecutor(DialogAction model, WorkflowFormulaState state) :
DeclarativeActionExecutor(model, state)
{
protected override ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
// No action needed - the edge will be followed automatically
return default;
}
}
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class EditTableExecutor(EditTable model, WorkflowFormulaState state) : DeclarativeActionExecutor<EditTable>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
PropertyPath variablePath = Throw.IfNull(this.Model.ItemsVariable?.Path, $"{nameof(this.Model)}.{nameof(this.Model.ItemsVariable)}");
FormulaValue table = context.ReadState(variablePath);
if (table is not TableValue tableValue)
{
throw this.Exception($"Require '{variablePath}' to be a table, not: '{table.GetType().Name}'.");
}
TableChangeType changeType = this.Model.ChangeType.Value;
switch (this.Model.ChangeType.Value)
{
case TableChangeType.Add:
ValueExpression addItemValue = Throw.IfNull(this.Model.Value, $"{nameof(this.Model)}.{nameof(this.Model.Value)}");
EvaluationResult<DataValue> addResult = this.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.Evaluator.GetValue(removeItemValue);
if (removeResult.Value is TableDataValue removeItemTable)
{
await tableValue.RemoveAsync(removeItemTable?.Values.Select(row => row.ToRecordValue()), all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, RecordValue.Empty(), context).ConfigureAwait(false);
}
break;
case TableChangeType.Clear:
await tableValue.ClearAsync(cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, FormulaValue.NewBlank(), context).ConfigureAwait(false);
break;
case TableChangeType.TakeFirst:
RecordValue? firstRow = tableValue.Rows.FirstOrDefault()?.Value;
if (firstRow is not null)
{
await tableValue.RemoveAsync([firstRow], all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, firstRow, context).ConfigureAwait(false);
}
break;
case TableChangeType.TakeLast:
RecordValue? lastRow = tableValue.Rows.LastOrDefault()?.Value;
if (lastRow is not null)
{
await tableValue.RemoveAsync([lastRow], all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, lastRow, context).ConfigureAwait(false);
}
break;
}
return default;
static RecordValue BuildRecord(RecordType recordType, FormulaValue value)
{
return FormulaValue.NewRecordFromFields(recordType, GetValues());
IEnumerable<NamedValue> GetValues()
{
foreach (NamedFormulaType fieldType in recordType.GetFieldTypes())
{
if (value is RecordValue recordValue)
{
yield return new NamedValue(fieldType.Name, recordValue.GetField(fieldType.Name));
}
else
{
yield return new NamedValue(fieldType.Name, value);
}
}
}
}
}
}
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class EditTableV2Executor(EditTableV2 model, WorkflowFormulaState state) : DeclarativeActionExecutor<EditTableV2>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
PropertyPath variablePath = Throw.IfNull(this.Model.ItemsVariable?.Path, $"{nameof(this.Model)}.{nameof(this.Model.ItemsVariable)}");
FormulaValue table = context.ReadState(variablePath);
if (table is not TableValue tableValue)
{
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.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);
}
else if (changeType is ClearItemsOperation)
{
await tableValue.ClearAsync(cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, FormulaValue.NewBlank(), context).ConfigureAwait(false);
}
else if (changeType is RemoveItemOperation removeItemOperation)
{
ValueExpression removeItemValue = Throw.IfNull(removeItemOperation.Value, $"{nameof(this.Model)}.{nameof(this.Model.ChangeType)}");
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(removeItemValue);
if (expressionResult.Value.ToFormula() is TableValue removeItemTable)
{
await tableValue.RemoveAsync(removeItemTable?.Rows.Select(row => row.Value), all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, FormulaValue.NewBlank(), context).ConfigureAwait(false);
}
}
else if (changeType is TakeLastItemOperation)
{
RecordValue? lastRow = tableValue.Rows.LastOrDefault()?.Value;
if (lastRow is not null)
{
await tableValue.RemoveAsync([lastRow], all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, lastRow, context).ConfigureAwait(false);
}
}
else if (changeType is TakeFirstItemOperation)
{
RecordValue? firstRow = tableValue.Rows.FirstOrDefault()?.Value;
if (firstRow is not null)
{
await tableValue.RemoveAsync([firstRow], all: true, cancellationToken).ConfigureAwait(false);
await this.AssignAsync(variablePath, firstRow, context).ConfigureAwait(false);
}
}
return default;
static RecordValue BuildRecord(RecordType recordType, FormulaValue value)
{
return FormulaValue.NewRecordFromFields(recordType, GetValues());
IEnumerable<NamedValue> GetValues()
{
foreach (NamedFormulaType fieldType in recordType.GetFieldTypes())
{
if (value is RecordValue recordValue)
{
yield return new NamedValue(fieldType.Name, recordValue.GetField(fieldType.Name));
}
else
{
yield return new NamedValue(fieldType.Name, value);
}
}
}
}
}
}
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
{
public static class Steps
{
public static string Start(string id) => $"{id}_{nameof(Start)}";
public static string Next(string id) => $"{id}_{nameof(Next)}";
public static string End(string id) => $"{id}_{nameof(End)}";
}
private int _index;
private FormulaValue[] _values;
public ForeachExecutor(Foreach model, WorkflowFormulaState state)
: base(model, state)
{
this._values = [];
}
public bool HasValue { get; private set; }
protected override bool IsDiscreteAction => false;
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
this._index = 0;
if (this.Model.Items is null)
{
this._values = [];
this.HasValue = false;
}
else
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Items);
if (expressionResult.Value is TableDataValue tableValue)
{
this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];
}
else
{
this._values = [expressionResult.Value.ToFormula()];
}
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
return default;
}
public async ValueTask TakeNextAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
if (this.HasValue = this._index < this._values.Length)
{
FormulaValue value = this._values[this._index];
await context.QueueStateUpdateAsync(Throw.IfNull(this.Model.Value), value).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateUpdateAsync(this.Model.Index.Path, FormulaValue.New(this._index)).ConfigureAwait(false);
}
this._index++;
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
try
{
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value)).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateResetAsync(this.Model.Index).ConfigureAwait(false);
}
}
finally
{
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
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)}");
private AzureAgentInput? AgentInput => this.Model.Input;
private AzureAgentOutput? AgentOutput => this.Model.Output;
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? conversationId = this.GetConversationId();
string agentName = this.GetAgentName();
string? additionalInstructions = this.GetAdditionalInstructions();
bool autoSend = this.GetAutoSendValue();
IEnumerable<ChatMessage>? inputMessages = this.GetInputMessages();
AgentRunResponse agentResponse = agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, additionalInstructions, inputMessages, cancellationToken).ToEnumerable().ToAgentRunResponse();
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
}
await this.AssignAsync(this.AgentOutput?.Messages?.Path, agentResponse.Messages.ToTable(), context).ConfigureAwait(false);
return default;
}
private IEnumerable<ChatMessage>? GetInputMessages()
{
DataValue? userInput = null;
if (this.AgentInput?.Messages is not null)
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.AgentInput.Messages);
userInput = expressionResult.Value;
}
return userInput?.ToChatMessages();
}
private string? GetConversationId()
{
if (this.Model.ConversationId is null)
{
return null;
}
EvaluationResult<string> conversationIdResult = this.Evaluator.GetValue(this.Model.ConversationId);
return conversationIdResult.Value.Length == 0 ? null : conversationIdResult.Value;
}
private string GetAgentName() =>
this.Evaluator.GetValue(
Throw.IfNull(
this.AgentUsage.Name,
$"{nameof(this.Model)}.{nameof(this.Model.Agent)}.{nameof(this.Model.Agent.Name)}")).Value;
private string? GetAdditionalInstructions()
{
string? additionalInstructions = null;
if (this.AgentInput?.AdditionalInstructions is not null)
{
additionalInstructions = this.Engine.Format(this.AgentInput.AdditionalInstructions);
}
return additionalInstructions;
}
private bool GetAutoSendValue()
{
if (this.AgentOutput?.AutoSend is null)
{
return true;
}
EvaluationResult<bool> autoSendResult = this.Evaluator.GetValue(this.AgentOutput.AutoSend);
return autoSendResult.Value;
}
}
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class ParseValueExecutor(ParseValue model, WorkflowFormulaState state) :
DeclarativeActionExecutor<ParseValue>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
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.Evaluator.GetValue(valueExpression);
FormulaValue? parsedResult = null;
if (expressionResult.Value is RecordDataValue recordValue)
{
parsedResult = recordValue.ToFormula();
}
else if (expressionResult.Value is StringDataValue stringValue)
{
if (string.IsNullOrWhiteSpace(stringValue.Value))
{
parsedResult = FormulaValue.NewBlank(expressionResult.Value.GetDataType().ToFormulaType());
}
else
{
parsedResult =
this.Model.ValueType switch
{
StringDataType => FormulaValue.New(stringValue.Value),
NumberDataType => FormulaValue.New(stringValue.Value),
BooleanDataType => FormulaValue.New(stringValue.Value),
RecordDataType recordType => ParseRecord(recordType, stringValue.Value),
_ => null
};
}
}
if (parsedResult is null)
{
throw this.Exception("Unable to parse value.");
}
await this.AssignAsync(variablePath, parsedResult, context).ConfigureAwait(false);
return default;
RecordValue ParseRecord(RecordDataType recordType, string rawText)
{
string jsonText = rawText.TrimJsonDelimiter();
using JsonDocument json = JsonDocument.Parse(jsonText);
try
{
return recordType.ParseRecord(json.RootElement);
}
catch (Exception exception)
{
throw this.Exception("Failed to parse value.", exception);
}
}
}
}
@@ -0,0 +1,147 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Entities;
using Microsoft.Agents.AI.Workflows.Declarative.Events;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class QuestionExecutor(Question model, WorkflowFormulaState state) :
DeclarativeActionExecutor<Question>(model, state)
{
public static class Steps
{
public static string Prepare(string id) => $"{id}_{nameof(Prepare)}";
public static string Input(string id) => $"{id}_{nameof(Input)}";
public static string Capture(string id) => $"{id}_{nameof(Capture)}";
}
private readonly DurableProperty<int> _promptCount = new(nameof(_promptCount));
private readonly DurableProperty<bool> _hasExecuted = new(nameof(_hasExecuted));
protected override bool IsDiscreteAction => false;
protected override bool EmitResultEvent => false;
public static bool IsComplete(object? message) // %%% BASE CLASS ???
{
ActionExecutorResult executorMessage = ActionExecutorResult.ThrowIfNot(message);
return executorMessage.Result is null;
}
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await this._promptCount.WriteAsync(context, 0).ConfigureAwait(false);
InitializablePropertyPath variable = Throw.IfNull(this.Model.Variable);
bool hasValue = context.ReadState(variable.Path) is BlankValue;
bool alwaysPrompt = this.Evaluator.GetValue(this.Model.AlwaysPrompt).Value;
bool proceed = !alwaysPrompt || hasValue;
if (proceed)
{
SkipQuestionMode mode = this.Evaluator.GetValue(this.Model.SkipQuestionMode).Value;
proceed =
mode switch
{
SkipQuestionMode.SkipOnFirstExecutionIfVariableHasValue => !await this._hasExecuted.ReadAsync(context).ConfigureAwait(false),
SkipQuestionMode.AlwaysSkipIfVariableHasValue => hasValue,
SkipQuestionMode.AlwaysAsk => true,
_ => true,
};
}
if (proceed)
{
await this.PromptAsync(context, cancellationToken).ConfigureAwait(false);
}
else
{
await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false);
}
return default;
}
public async ValueTask PrepareResponseAsync(IWorkflowContext context, ActionExecutorResult message, CancellationToken cancellationToken)
{
int count = await this._promptCount.ReadAsync(context).ConfigureAwait(false);
InputRequest inputRequest = new(this.FormatPrompt(this.Model.Prompt));
await context.SendMessageAsync(inputRequest).ConfigureAwait(false);
await this._promptCount.WriteAsync(context, count + 1).ConfigureAwait(false);
}
public async ValueTask CaptureResponseAsync(IWorkflowContext context, InputResponse message, CancellationToken cancellationToken)
{
FormulaValue? extractedValue = null;
if (string.IsNullOrWhiteSpace(message.Value))
{
string unrecognizedResponse = this.FormatPrompt(this.Model.UnrecognizedPrompt);
await context.AddEventAsync(new MessageActivityEvent(unrecognizedResponse.Trim())).ConfigureAwait(false);
}
else
{
EntityExtractionResult entityResult = EntityExtractor.Parse(this.Model.Entity, message.Value);
if (entityResult.IsValid)
{
extractedValue = entityResult.Value;
}
else
{
string invalidResponse = this.FormatPrompt(this.Model.InvalidPrompt);
await context.AddEventAsync(new MessageActivityEvent(invalidResponse.Trim())).ConfigureAwait(false);
}
}
if (extractedValue is null)
{
await this.PromptAsync(context, cancellationToken).ConfigureAwait(false);
}
else
{
await this.AssignAsync(this.Model.Variable?.Path, extractedValue, context).ConfigureAwait(false);
await this._hasExecuted.WriteAsync(context, true).ConfigureAwait(false);
await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false);
}
}
public async ValueTask CompleteAsync(IWorkflowContext context, ActionExecutorResult message, CancellationToken cancellationToken)
{
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
}
private async ValueTask PromptAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
long repeatCount = this.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.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);
}
else
{
await context.SendResultMessageAsync(this.Id, result: true, cancellationToken).ConfigureAwait(false);
}
}
private string FormatPrompt(ActivityTemplateBase? promptTemplate)
{
if (promptTemplate is not MessageActivityTemplate messageActivity)
{
return string.Empty;
}
return this.Engine.Format(messageActivity.Text).Trim();
}
}
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class ResetVariableExecutor(ResetVariable model, WorkflowFormulaState state) :
DeclarativeActionExecutor<ResetVariable>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
Throw.IfNull(this.Model.Variable, $"{nameof(this.Model)}.{nameof(model.Variable)}");
await context.QueueStateResetAsync(this.Model.Variable).ConfigureAwait(false);
Debug.WriteLine(
$"""
STATE: {this.GetType().Name} [{this.Id}]
NAME: {this.Model.Variable}
""");
return default;
}
}
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class RetrieveConversationMessageExecutor(RetrieveConversationMessage model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<RetrieveConversationMessage>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
string messageId = this.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);
await this.AssignAsync(this.Model.Message?.Path, message.ToRecord(), context).ConfigureAwait(false);
return default;
}
}
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationMessages model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<RetrieveConversationMessages>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
ChatMessage[] messages = await agentProvider.GetMessagesAsync(
conversationId,
limit: this.GetLimit(),
after: this.GetMessage(this.Model.MessageAfter),
before: this.GetMessage(this.Model.MessageBefore),
newestFirst: this.IsDescending(),
cancellationToken).ToArrayAsync(cancellationToken).ConfigureAwait(false);
await this.AssignAsync(this.Model.Messages?.Path, messages.ToTable(), context).ConfigureAwait(false);
return default;
}
private int? GetLimit()
{
if (this.Model.Limit is null)
{
return null;
}
long limit = this.Evaluator.GetValue(this.Model.Limit).Value;
return Convert.ToInt32(Math.Min(limit, 100));
}
private string? GetMessage(StringExpression? messagExpression)
{
if (messagExpression is null)
{
return null;
}
return this.Evaluator.GetValue(messagExpression).Value;
}
private bool IsDescending()
{
if (this.Model.SortOrder is null)
{
return false;
}
AgentMessageSortOrderWrapper sortOrderWrapper = this.Evaluator.GetValue(this.Model.SortOrder).Value;
return sortOrderWrapper.Value == AgentMessageSortOrder.NewestFirst;
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
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.Engine.Format(messageActivity.Text).Trim();
await context.AddEventAsync(new MessageActivityEvent(activityText.Trim())).ConfigureAwait(false);
}
return default;
}
}
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class SetMultipleVariablesExecutor(SetMultipleVariables model, WorkflowFormulaState state)
: DeclarativeActionExecutor<SetMultipleVariables>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
foreach (VariableAssignment assignment in this.Model.Assignments)
{
if (assignment.Variable is null)
{
continue;
}
if (assignment.Value is null)
{
await this.AssignAsync(assignment.Variable, FormulaValue.NewBlank(), context).ConfigureAwait(false);
}
else
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(assignment.Value);
await this.AssignAsync(assignment.Variable, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
}
}
return default;
}
}
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class SetTextVariableExecutor(SetTextVariable model, WorkflowFormulaState state)
: DeclarativeActionExecutor<SetTextVariable>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
if (this.Model.Value is null)
{
await this.AssignAsync(this.Model.Variable?.Path, FormulaValue.NewBlank(), context).ConfigureAwait(false);
}
else
{
FormulaValue expressionResult = FormulaValue.New(this.Engine.Format(this.Model.Value));
await this.AssignAsync(this.Model.Variable?.Path, expressionResult, context).ConfigureAwait(false);
}
return default;
}
}
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class SetVariableExecutor(SetVariable model, WorkflowFormulaState state)
: DeclarativeActionExecutor<SetVariable>(model, state)
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
PropertyPath variablePath = Throw.IfNull(this.Model.Variable?.Path, $"{nameof(this.Model)}.{nameof(model.Variable)}");
if (this.Model.Value is null)
{
await this.AssignAsync(variablePath, FormulaValue.NewBlank(), context).ConfigureAwait(false);
}
else
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Value);
await this.AssignAsync(variablePath, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
}
return default;
}
}