.NET Workflows - Declarative update for AzureAgentProvider usage (#907)

* Updated

* Update dotnet/src/Microsoft.Agents.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Constant / override

* Namespace

* Protect against race

* Simplify collection usage

* Rollback name resolution

* One more rollback

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Chris
2025-09-25 17:17:59 +00:00
committed by GitHub
co-authored by Copilot
parent 9355329dfd
commit f527fbe6ce
12 changed files with 170 additions and 29 deletions
@@ -1,5 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
@@ -43,15 +42,20 @@ public sealed class AzureAgentProvider(string projectEndpoint, TokenCredential p
}
/// <inheritdoc/>
public override async Task CreateMessageAsync(string conversationId, ChatMessage conversationMessage, CancellationToken cancellationToken = default)
public override Task CreateMessageAsync(string conversationId, ChatMessage conversationMessage, CancellationToken cancellationToken = default)
{
await this.GetAgentsClient().Messages.CreateMessageAsync(
// TODO: Switch to asynchronous "CreateMessageAsync", when fix properly applied:
// BUG: https://github.com/Azure/azure-sdk-for-net/issues/52571
// PR: https://github.com/Azure/azure-sdk-for-net/pull/52653
this.GetAgentsClient().Messages.CreateMessage(
conversationId,
role: s_roleMap[conversationMessage.Role.Value.ToUpperInvariant()],
contentBlocks: GetContent(),
attachments: null,
metadata: GetMetadata(),
cancellationToken).ConfigureAwait(false);
cancellationToken);
return Task.CompletedTask;
Dictionary<string, string>? GetMetadata()
{
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.Workflows.Declarative.Interpreter;
@@ -36,6 +37,35 @@ internal static class IWorkflowContextExtensions
public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) =>
DeclarativeContext(context).State.Get(key, scopeName);
public static async ValueTask QueueConversationUpdateAsync(this IWorkflowContext context, string conversationId)
{
RecordValue conversation = (RecordValue)context.ReadState(SystemScope.Names.Conversation, VariableScopeNames.System);
conversation.UpdateField("Id", FormulaValue.New(conversationId));
await context.QueueSystemUpdateAsync(SystemScope.Names.Conversation, conversation).ConfigureAwait(false);
await context.QueueSystemUpdateAsync(SystemScope.Names.ConversationId, FormulaValue.New(conversationId)).ConfigureAwait(false);
await context.AddEventAsync(new ConversationUpdateEvent(conversationId)).ConfigureAwait(false);
}
// Ensure "System.Conversation.Id" and "System.ConversationId" are properly initialized when referenced.
public static async ValueTask EnsureWorkflowConversationAsync(this IWorkflowContext context, WorkflowAgentProvider agentProvider, StringExpression expression, CancellationToken cancellationToken)
{
if (expression.IsVariableReference &&
expression.VariableReference.IsVariableReferenceWithScope(VariableScopeNames.System, out string? variableName))
{
if (string.Equals(variableName, SystemScope.Names.Conversation, StringComparison.Ordinal) ||
string.Equals(variableName, SystemScope.Names.ConversationId, StringComparison.Ordinal))
{
FormulaValue variableValue = context.ReadState(SystemScope.Names.ConversationId, VariableScopeNames.System);
if (variableValue is BlankValue)
{
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false);
await context.QueueConversationUpdateAsync(conversationId).ConfigureAwait(false);
}
}
}
}
private static DeclarativeWorkflowContext DeclarativeContext(IWorkflowContext context)
{
if (context is not DeclarativeWorkflowContext declarativeContext)
@@ -17,8 +17,10 @@ internal sealed class AddConversationMessageExecutor(AddConversationMessage mode
{
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.Evaluator.GetValue(conversationExpression).Value;
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
await context.EnsureWorkflowConversationAsync(agentProvider, this.Model.ConversationId, cancellationToken).ConfigureAwait(false);
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
ChatMessage newMessage = new(this.Model.Role.Value.ToChatRole(), [.. this.GetContent()]) { AdditionalProperties = this.GetMetadata() };
@@ -17,7 +17,10 @@ internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string conversationId = this.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value;
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
await context.EnsureWorkflowConversationAsync(agentProvider, this.Model.ConversationId, cancellationToken).ConfigureAwait(false);
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
DataValue? inputMessages = this.GetInputMessages();
if (inputMessages is not null)
@@ -11,7 +11,6 @@ using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Abstractions;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.Workflows.Declarative.ObjectModel;
@@ -79,12 +78,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA
{
conversationId = assignValue;
RecordValue conversation = (RecordValue)context.ReadState(SystemScope.Names.Conversation, VariableScopeNames.System);
conversation.UpdateField("Id", FormulaValue.New(conversationId));
await context.QueueSystemUpdateAsync(SystemScope.Names.Conversation, conversation).ConfigureAwait(false);
await context.QueueSystemUpdateAsync(SystemScope.Names.ConversationId, FormulaValue.New(conversationId)).ConfigureAwait(false);
await context.AddEventAsync(new ConversationUpdateEvent(conversationId)).ConfigureAwait(false);
await context.QueueConversationUpdateAsync(conversationId).ConfigureAwait(false);
}
}
}
@@ -16,7 +16,9 @@ internal sealed class RetrieveConversationMessageExecutor(RetrieveConversationMe
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string conversationId = this.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value;
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
await context.EnsureWorkflowConversationAsync(agentProvider, this.Model.ConversationId, cancellationToken).ConfigureAwait(false);
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);
@@ -18,7 +18,9 @@ internal sealed class RetrieveConversationMessagesExecutor(RetrieveConversationM
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string conversationId = this.Evaluator.GetValue(Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}")).Value;
Throw.IfNull(this.Model.ConversationId, $"{nameof(this.Model)}.{nameof(this.Model.ConversationId)}");
await context.EnsureWorkflowConversationAsync(agentProvider, this.Model.ConversationId, cancellationToken).ConfigureAwait(false);
string conversationId = this.Evaluator.GetValue(this.Model.ConversationId).Value;
ChatMessage[] messages = await agentProvider.GetMessagesAsync(
conversationId,
@@ -1,7 +1,6 @@
// 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;
@@ -14,7 +13,7 @@ using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.Workflows.Declarative.PowerFx;
internal sealed record class WorkflowTypeInfo(FrozenSet<string> EnvironmentVariables, IEnumerable<VariableInformationDiagnostic> UserVariables);
internal sealed record class WorkflowTypeInfo(ISet<string> EnvironmentVariables, IList<VariableInformationDiagnostic> UserVariables);
internal static class WorkflowDiagnostics
{
@@ -26,8 +25,8 @@ internal static class WorkflowDiagnostics
return
new WorkflowTypeInfo(
semanticModel.GetAllEnvironmentVariablesReferencedInTheBot().ToFrozenSet(),
semanticModel.GetVariables(workflowElement.SchemaName.Value).Where(x => !x.IsSystemVariable).Select(v => v.ToDiagnostic()));
semanticModel.GetAllEnvironmentVariablesReferencedInTheBot(),
[.. 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