.NET Workflows - Declarative updated for Question action (#1532)

* Updated

* Namespace
This commit is contained in:
Chris
2025-10-16 17:44:19 -07:00
committed by GitHub
Unverified
parent 8c24a72107
commit df776ae77b
7 changed files with 57 additions and 23 deletions
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Workflows.Declarative.Events;
@@ -12,15 +13,24 @@ public sealed class InputResponse
/// <summary>
/// The response value.
/// </summary>
public string Value { get; }
public ChatMessage Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="InputResponse"/> class.
/// </summary>
/// <param name="value">The response value.</param>
[JsonConstructor]
public InputResponse(string value)
public InputResponse(ChatMessage value)
{
this.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="InputResponse"/> class.
/// </summary>
/// <param name="value">The response value.</param>
public InputResponse(string value)
{
this.Value = new ChatMessage(ChatRole.User, value);
}
}
@@ -69,25 +69,20 @@ internal static class IWorkflowContextExtensions
await context.AddEventAsync(new ConversationUpdateEvent(conversationId) { IsWorkflow = isExternal }, cancellationToken).ConfigureAwait(false);
}
public static string? GetWorkflowConversation(this IWorkflowContext context) =>
context.ReadState(SystemScope.Names.ConversationId, VariableScopeNames.System) switch
{
StringValue stringValue when stringValue.Value.Length > 0 => stringValue.Value,
_ => null,
};
public static bool IsWorkflowConversation(
this IWorkflowContext context,
string? conversationId,
out string? workflowConversationId)
{
FormulaValue idValue = context.ReadState(SystemScope.Names.ConversationId, VariableScopeNames.System);
switch (idValue)
{
case BlankValue:
case ErrorValue:
workflowConversationId = null;
return false;
case StringValue stringValue when stringValue.Value.Length > 0:
workflowConversationId = stringValue.Value;
return workflowConversationId.Equals(conversationId, StringComparison.Ordinal);
default:
// Something has gone terribly wrong.
throw new DeclarativeActionException($"Invalid '{SystemScope.Names.ConversationId}' value type: {idValue.GetType().Name}.");
}
workflowConversationId = context.GetWorkflowConversation();
return workflowConversationId?.Equals(conversationId, StringComparison.Ordinal) ?? false;
}
private static DeclarativeWorkflowContext DeclarativeContext(IWorkflowContext context)
@@ -237,7 +237,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
this.Trace(item);
// Entry point for question
QuestionExecutor action = new(item, this._workflowState);
QuestionExecutor action = new(item, this._workflowOptions.AgentProvider, this._workflowState);
this.ContinueWith(action);
// Transition to post action if complete
string postId = Steps.Post(action.Id);
@@ -9,12 +9,13 @@ 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.Extensions.AI;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
internal sealed class QuestionExecutor(Question model, WorkflowFormulaState state) :
internal sealed class QuestionExecutor(Question model, WorkflowAgentProvider agentProvider, WorkflowFormulaState state) :
DeclarativeActionExecutor<Question>(model, state)
{
public static class Steps
@@ -82,21 +83,21 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
public async ValueTask CaptureResponseAsync(IWorkflowContext context, InputResponse message, CancellationToken cancellationToken)
{
FormulaValue? extractedValue = null;
if (string.IsNullOrWhiteSpace(message.Value))
if (message.Value is null)
{
string unrecognizedResponse = this.FormatPrompt(this.Model.UnrecognizedPrompt);
await context.AddEventAsync(new MessageActivityEvent(unrecognizedResponse.Trim()), cancellationToken).ConfigureAwait(false);
}
else
{
EntityExtractionResult entityResult = EntityExtractor.Parse(this.Model.Entity, message.Value);
EntityExtractionResult entityResult = EntityExtractor.Parse(this.Model.Entity, message.Value.Text);
if (entityResult.IsValid)
{
extractedValue = entityResult.Value;
}
else
{
string invalidResponse = this.FormatPrompt(this.Model.InvalidPrompt);
string invalidResponse = this.Model.InvalidPrompt is not null ? this.FormatPrompt(this.Model.InvalidPrompt) : "Invalid response";
await context.AddEventAsync(new MessageActivityEvent(invalidResponse.Trim()), cancellationToken).ConfigureAwait(false);
}
}
@@ -107,6 +108,25 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
}
else
{
bool autoSend = true;
if (this.Model.ExtensionData?.Properties.TryGetValue("autoSend", out DataValue? autoSendValue) ?? false)
{
autoSend = autoSendValue.ToObject() is bool value && value;
}
if (autoSend)
{
string? workflowConversationId = context.GetWorkflowConversation();
if (workflowConversationId is not null)
{
// Input message always defined if values has been extracted.
ChatMessage input = message.Value!;
await agentProvider.CreateMessageAsync(workflowConversationId, input, cancellationToken).ConfigureAwait(false);
await context.SetLastMessageAsync(input).ConfigureAwait(false);
}
}
await this.AssignAsync(this.Model.Variable?.Path, extractedValue, context).ConfigureAwait(false);
await this._hasExecuted.WriteAsync(context, true).ConfigureAwait(false);
await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false);
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows.Declarative.Events;
using Microsoft.Extensions.AI;
using Xunit.Abstractions;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Events;
@@ -11,9 +12,16 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Events;
public sealed class InputResponseTest(ITestOutputHelper output) : EventTest(output)
{
[Fact]
public void VerifySerialization()
public void VerifySerializationText()
{
InputResponse copy = VerifyEventSerialization(new InputResponse("test response"));
Assert.Equal("test response", copy.Value);
Assert.Equal("test response", copy.Value.Text);
}
[Fact]
public void VerifySerializationMessage()
{
InputResponse copy = VerifyEventSerialization(new InputResponse(new ChatMessage(ChatRole.User, "test response")));
Assert.Equal("test response", copy.Value.Text);
}
}
+1
View File
@@ -21,6 +21,7 @@ trigger:
- kind: Question
id: question_confirm
alwaysPrompt: false
autoSend: false
property: Local.ConfirmedInput
prompt:
kind: Message