diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Events/InputResponse.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Events/InputResponse.cs
index a34d41610e..65454d2994 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Events/InputResponse.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Events/InputResponse.cs
@@ -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
///
/// The response value.
///
- public string Value { get; }
+ public ChatMessage Value { get; }
///
/// Initializes a new instance of the class.
///
/// The response value.
[JsonConstructor]
- public InputResponse(string value)
+ public InputResponse(ChatMessage value)
{
this.Value = value;
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The response value.
+ public InputResponse(string value)
+ {
+ this.Value = new ChatMessage(ChatRole.User, value);
+ }
}
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 720636178e..067810d007 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs
@@ -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)
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs
index 377d65c99b..ce511040cd 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs
@@ -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);
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 9dbaa2efa1..1c6e1596df 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/QuestionExecutor.cs
@@ -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(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);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputRequest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputRequestTest.cs
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputRequest.cs
rename to dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputRequestTest.cs
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponse.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponseTest.cs
similarity index 55%
rename from dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponse.cs
rename to dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponseTest.cs
index 6304aef69b..015d80e246 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponse.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Events/InputResponseTest.cs
@@ -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);
}
}
diff --git a/workflow-samples/HumanInLoop.yaml b/workflow-samples/HumanInLoop.yaml
index 18959b067d..11c63adb5e 100644
--- a/workflow-samples/HumanInLoop.yaml
+++ b/workflow-samples/HumanInLoop.yaml
@@ -21,6 +21,7 @@ trigger:
- kind: Question
id: question_confirm
alwaysPrompt: false
+ autoSend: false
property: Local.ConfirmedInput
prompt:
kind: Message