From 988623e7b83e47f38f4134a76dacc15bf018d9e5 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:32:47 -0700 Subject: [PATCH] .NET Workflow - Update declarative code patterns (#1313) * It hurts * Fixed * Namespace * Namespace --- .../AzureAgentProvider.cs | 10 ++++-- .../Extensions/AgentProviderExtensions.cs | 2 +- .../Extensions/IWorkflowContextExtensions.cs | 33 ++++++++++++++----- .../Interpreter/DeclarativeWorkflowContext.cs | 12 ++----- .../DeclarativeWorkflowExecutor.cs | 2 +- .../Interpreter/DelegateActionExecutor.cs | 2 +- .../Kit/RootExecutor.cs | 5 ++- .../ObjectModel/CreateConversationExecutor.cs | 2 +- .../ObjectModel/QuestionExecutor.cs | 8 ++--- .../ObjectModel/WorkflowActionExecutorTest.cs | 3 +- 10 files changed, 47 insertions(+), 32 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs index 562a77a8d9..0ff167d50d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/AzureAgentProvider.cs @@ -37,7 +37,13 @@ public sealed class AzureAgentProvider(string projectEndpoint, TokenCredential p /// public override async Task CreateConversationAsync(CancellationToken cancellationToken = default) { - PersistentAgentThread conversation = await this.GetAgentsClient().Threads.CreateThreadAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + PersistentAgentThread conversation = + await this.GetAgentsClient().Threads.CreateThreadAsync( + messages: null, + toolResources: null, + metadata: null, + cancellationToken).ConfigureAwait(false); + return conversation.Id; } @@ -92,7 +98,7 @@ public sealed class AzureAgentProvider(string projectEndpoint, TokenCredential p /// public override async Task GetAgentAsync(string agentId, CancellationToken cancellationToken = default) => - await this.GetAgentsClient().GetAIAgentAsync(agentId, chatOptions: null, cancellationToken: cancellationToken).ConfigureAwait(false); + await this.GetAgentsClient().GetAIAgentAsync(agentId, chatOptions: null, clientFactory: null, cancellationToken).ConfigureAwait(false); /// public override async Task GetMessageAsync(string conversationId, string messageId, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs index 2de25ec7dc..5b6bbbc297 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs @@ -103,7 +103,7 @@ internal static class AgentProviderExtensions { conversationId = assignValue; - await context.QueueConversationUpdateAsync(conversationId, cancellationToken: cancellationToken).ConfigureAwait(false); + await context.QueueConversationUpdateAsync(conversationId, cancellationToken).ConfigureAwait(false); } } } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs index aa38ee0b7a..720636178e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs @@ -20,8 +20,17 @@ internal static class IWorkflowContextExtensions public static ValueTask RaiseCompletionEventAsync(this IWorkflowContext context, DialogAction action, CancellationToken cancellationToken = default) => context.AddEventAsync(new DeclarativeActionCompletedEvent(action), cancellationToken); - public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result = null, CancellationToken cancellationToken = default) => - context.SendMessageAsync(new ActionExecutorResult(id, result), cancellationToken: cancellationToken); + public static FormulaValue ReadState(this IWorkflowContext context, PropertyPath variablePath) => + context.ReadState(Throw.IfNull(variablePath.VariableName), Throw.IfNull(variablePath.NamespaceAlias)); + + public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) => + DeclarativeContext(context).State.Get(key, scopeName); + + public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, CancellationToken cancellationToken = default) => + context.SendResultMessageAsync(id, result: null, cancellationToken); + + public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result, CancellationToken cancellationToken = default) => + context.SendMessageAsync(new ActionExecutorResult(id, result), targetId: null, cancellationToken); public static ValueTask QueueStateResetAsync(this IWorkflowContext context, PropertyPath variablePath, CancellationToken cancellationToken = default) => context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), UnassignedValue.Instance, Throw.IfNull(variablePath.NamespaceAlias), cancellationToken); @@ -29,14 +38,22 @@ internal static class IWorkflowContextExtensions public static ValueTask QueueStateUpdateAsync(this IWorkflowContext context, PropertyPath variablePath, TValue? value, CancellationToken cancellationToken = default) => context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.NamespaceAlias), cancellationToken); - public static ValueTask QueueSystemUpdateAsync(this IWorkflowContext context, string key, TValue? value, CancellationToken cancellationToken = default) => - DeclarativeContext(context).QueueSystemUpdateAsync(key, value, cancellationToken); + public static async ValueTask QueueEnvironmentUpdateAsync(this IWorkflowContext context, string key, TValue? value, CancellationToken cancellationToken = default) + { + DeclarativeWorkflowContext declarativeContext = DeclarativeContext(context); + await declarativeContext.UpdateStateAsync(key, value, VariableScopeNames.Environment, allowSystem: true, cancellationToken).ConfigureAwait(false); + declarativeContext.State.Bind(); + } - public static FormulaValue ReadState(this IWorkflowContext context, PropertyPath variablePath) => - context.ReadState(Throw.IfNull(variablePath.VariableName), Throw.IfNull(variablePath.NamespaceAlias)); + public static async ValueTask QueueSystemUpdateAsync(this IWorkflowContext context, string key, TValue? value, CancellationToken cancellationToken = default) + { + DeclarativeWorkflowContext declarativeContext = DeclarativeContext(context); + await declarativeContext.UpdateStateAsync(key, value, VariableScopeNames.System, allowSystem: true, cancellationToken).ConfigureAwait(false); + declarativeContext.State.Bind(); + } - public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) => - DeclarativeContext(context).State.Get(key, scopeName); + public static ValueTask QueueConversationUpdateAsync(this IWorkflowContext context, string conversationId, CancellationToken cancellationToken = default) => + context.QueueConversationUpdateAsync(conversationId, isExternal: false, cancellationToken); public static async ValueTask QueueConversationUpdateAsync(this IWorkflowContext context, string conversationId, bool isExternal = false, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs index 7d373d5962..f4b7cb14ba 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs @@ -53,7 +53,7 @@ internal sealed class DeclarativeWorkflowContext : IWorkflowContext // Copy keys to array to avoid modifying collection during enumeration. foreach (string key in this.State.Keys(scopeName).ToArray()) { - await this.UpdateStateAsync(key, UnassignedValue.Instance, scopeName, cancellationToken: cancellationToken).ConfigureAwait(false); + await this.UpdateStateAsync(key, UnassignedValue.Instance, scopeName, allowSystem: false, cancellationToken).ConfigureAwait(false); } } else @@ -68,13 +68,7 @@ internal sealed class DeclarativeWorkflowContext : IWorkflowContext /// public async ValueTask QueueStateUpdateAsync(string key, T? value, string? scopeName = null, CancellationToken cancellationToken = default) { - await this.UpdateStateAsync(key, value, scopeName, cancellationToken: cancellationToken).ConfigureAwait(false); - this.State.Bind(); - } - - public async ValueTask QueueSystemUpdateAsync(string key, TValue? value, CancellationToken cancellationToken = default) - { - await this.UpdateStateAsync(key, value, VariableScopeNames.System, allowSystem: true, cancellationToken).ConfigureAwait(false); + await this.UpdateStateAsync(key, value, scopeName, allowSystem: false, cancellationToken).ConfigureAwait(false); this.State.Bind(); } @@ -105,7 +99,7 @@ internal sealed class DeclarativeWorkflowContext : IWorkflowContext public ValueTask SendMessageAsync(object message, string? targetId = null, CancellationToken cancellationToken = default) => this.Source.SendMessageAsync(message, targetId, cancellationToken); - private ValueTask UpdateStateAsync(string key, T? value, string? scopeName, bool allowSystem = true, CancellationToken cancellationToken = default) + public ValueTask UpdateStateAsync(string key, T? value, string? scopeName, bool allowSystem, CancellationToken cancellationToken = default) { bool isManagedScope = scopeName is not null && // null scope cannot be managed diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs index c228533f17..52cd02f916 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs @@ -43,6 +43,6 @@ internal sealed class DeclarativeWorkflowExecutor( await options.AgentProvider.CreateMessageAsync(conversationId, input, cancellationToken).ConfigureAwait(false); await declarativeContext.SetLastMessageAsync(input).ConfigureAwait(false); - await context.SendResultMessageAsync(this.Id, cancellationToken: cancellationToken).ConfigureAwait(false); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs index e05f0908af..1d9a2c7552 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs @@ -49,7 +49,7 @@ internal class DelegateActionExecutor : Executor, IResettabl if (this._emitResult) { - await context.SendResultMessageAsync(this.Id, cancellationToken: cancellationToken).ConfigureAwait(false); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } } } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/RootExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/RootExecutor.cs index 87957b8b9d..1254440342 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/RootExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/RootExecutor.cs @@ -6,7 +6,6 @@ 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.Extensions.Configuration; @@ -71,7 +70,7 @@ public abstract class RootExecutor : Executor, IResettableExecut await this._agentProvider.CreateMessageAsync(this._conversationId, input, cancellationToken).ConfigureAwait(false); await declarativeContext.SetLastMessageAsync(input).ConfigureAwait(false); - await declarativeContext.SendMessageAsync(new ActionExecutorResult(this.Id), cancellationToken: cancellationToken).ConfigureAwait(false); + await declarativeContext.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } /// @@ -94,7 +93,7 @@ public abstract class RootExecutor : Executor, IResettableExecut { foreach (string variableName in variableNames) { - await context.QueueStateUpdateAsync(variableName, GetEnvironmentVariable(variableName), VariableScopeNames.Environment).ConfigureAwait(false); + await context.QueueEnvironmentUpdateAsync(variableName, GetEnvironmentVariable(variableName)).ConfigureAwait(false); } string GetEnvironmentVariable(string name) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs index 6ed1d4db32..e229046864 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CreateConversationExecutor.cs @@ -17,7 +17,7 @@ internal sealed class CreateConversationExecutor(CreateConversation model, Workf { string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false); await this.AssignAsync(this.Model.ConversationId?.Path, FormulaValue.New(conversationId), context).ConfigureAwait(false); - await context.QueueConversationUpdateAsync(conversationId, cancellationToken: cancellationToken).ConfigureAwait(false); + await context.QueueConversationUpdateAsync(conversationId, cancellationToken).ConfigureAwait(false); return default; } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs index be82ca4935..9dbaa2efa1 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs @@ -65,7 +65,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat } else { - await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } return default; @@ -75,7 +75,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat { int count = await this._promptCount.ReadAsync(context).ConfigureAwait(false); InputRequest inputRequest = new(this.FormatPrompt(this.Model.Prompt)); - await context.SendMessageAsync(inputRequest, cancellationToken: cancellationToken).ConfigureAwait(false); + await context.SendMessageAsync(inputRequest, targetId: null, cancellationToken).ConfigureAwait(false); await this._promptCount.WriteAsync(context, count + 1).ConfigureAwait(false); } @@ -109,7 +109,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat { 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); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } } @@ -129,7 +129,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat 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()), cancellationToken).ConfigureAwait(false); - await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } else { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs index 738b26b6f9..9b667b55f7 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs @@ -6,7 +6,6 @@ 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.Kit; using Microsoft.Agents.AI.Workflows.Declarative.PowerFx; using Microsoft.Bot.ObjectModel; using Microsoft.PowerFx.Types; @@ -72,6 +71,6 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor internal sealed class TestWorkflowExecutor() : Executor("test_workflow") { public override async ValueTask HandleAsync(WorkflowFormulaState message, IWorkflowContext context, CancellationToken cancellationToken) => - await context.SendMessageAsync(new ActionExecutorResult(this.Id), cancellationToken: cancellationToken).ConfigureAwait(false); + await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false); } }