.NET Workflows - Capture the identifier of the LastMessage (the workflow input) (#1372)

* Fixed

* Test fix
This commit is contained in:
Chris
2025-10-09 23:45:09 -07:00
committed by GitHub
Unverified
parent 2c9cf6f59d
commit 7e1fd67e76
8 changed files with 99 additions and 9 deletions
@@ -40,8 +40,8 @@ internal sealed class DeclarativeWorkflowExecutor<TInput>(
}
await declarativeContext.QueueConversationUpdateAsync(conversationId, isExternal: true, cancellationToken).ConfigureAwait(false);
await options.AgentProvider.CreateMessageAsync(conversationId, input, cancellationToken).ConfigureAwait(false);
await declarativeContext.SetLastMessageAsync(input).ConfigureAwait(false);
ChatMessage inputMessage = await options.AgentProvider.CreateMessageAsync(conversationId, input, cancellationToken).ConfigureAwait(false);
await declarativeContext.SetLastMessageAsync(inputMessage).ConfigureAwait(false);
await context.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false);
}
@@ -67,8 +67,8 @@ public abstract class RootExecutor<TInput> : Executor<TInput>, IResettableExecut
}
await declarativeContext.QueueConversationUpdateAsync(this._conversationId, isExternal: true, cancellationToken).ConfigureAwait(false);
await this._agentProvider.CreateMessageAsync(this._conversationId, input, cancellationToken).ConfigureAwait(false);
await declarativeContext.SetLastMessageAsync(input).ConfigureAwait(false);
ChatMessage inputMessage = await this._agentProvider.CreateMessageAsync(this._conversationId, input, cancellationToken).ConfigureAwait(false);
await declarativeContext.SetLastMessageAsync(inputMessage).ConfigureAwait(false);
await declarativeContext.SendResultMessageAsync(this.Id, cancellationToken).ConfigureAwait(false);
}
@@ -274,6 +274,13 @@ internal sealed class WorkflowExpressionEngine
expression.VariableReference?.ToString() :
expression.ExpressionText;
return new(this._engine.Eval(expressionText), SensitivityLevel.None);
FormulaValue result = this._engine.Eval(expressionText);
if (result is ErrorValue errorValue)
{
throw new DeclarativeActionException(errorValue.Format());
}
return new(result, SensitivityLevel.None);
}
}
@@ -15,6 +15,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
public sealed class DeclarativeCodeGenTest(ITestOutputHelper output) : WorkflowTest(output)
{
[Theory]
[InlineData("CheckSystem.yaml", "CheckSystem.json")]
[InlineData("SendActivity.yaml", "SendActivity.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json", true)]
@@ -15,6 +15,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : WorkflowTest(output)
{
[Theory]
[InlineData("CheckSystem.yaml", "CheckSystem.json")]
[InlineData("SendActivity.yaml", "SendActivity.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json", true)]
@@ -0,0 +1,24 @@
{
"description": "Send an activity message.",
"setup": {
"input": {
"type": "String",
"value": "Everything good?"
}
},
"validation": {
"conversation_count": 1,
"min_action_count": 2,
"max_action_count": -1,
"min_response_count": 0,
"actions": {
"start": [
"check_system"
],
"final": [
"activity_passed",
"check_system_Post"
]
}
}
}
@@ -0,0 +1,57 @@
kind: Workflow
trigger:
kind: OnConversationStart
id: workflow_test
actions:
- kind: ConditionGroup
id: check_system
conditions:
- condition: =IsBlank(System.Conversation)
id: conversation_check
actions:
- kind: EndDialog
id: conversation_bad
- condition: =IsBlank(System.Conversation.Id)
id: conversation_id_check1
actions:
- kind: EndDialog
id: conversation_id_bad1
- condition: =IsBlank(System.ConversationId)
id: conversation_id_check2
actions:
- kind: EndDialog
id: conversation_id_bad2
- condition: =IsBlank(System.LastMessage)
id: message_check
actions:
- kind: EndDialog
id: message_bad
- condition: =IsBlank(System.LastMessage.Id)
id: message_id_check1
actions:
- kind: EndDialog
id: message_id_bad1
- condition: =IsBlank(System.LastMessageId)
id: message_id_check2
actions:
- kind: EndDialog
id: message_id_bad2
- condition: =IsBlank(System.LastMessageText)
id: message_text_check
actions:
- kind: EndDialog
id: message_text_bad
elseActions:
- kind: SendActivity
id: activity_passed
activity: PASSED!
@@ -215,7 +215,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
AdaptiveDialog dialog = dialogBuilder.Build();
WorkflowFormulaState state = new(RecalcEngineFactory.Create());
Mock<WorkflowAgentProvider> mockAgentProvider = CreateMockProvider();
Mock<WorkflowAgentProvider> mockAgentProvider = CreateMockProvider("1");
DeclarativeWorkflowOptions options = new(mockAgentProvider.Object);
WorkflowActionVisitor visitor = new(new DeclarativeWorkflowExecutor<string>(WorkflowActionVisitor.Steps.Root("anything"), options, state, (message) => DeclarativeWorkflowBuilder.DefaultTransform(message)), state, options);
WorkflowElementWalker walker = new(visitor);
@@ -255,7 +255,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
private async Task RunWorkflowAsync<TInput>(string workflowPath, TInput workflowInput) where TInput : notnull
{
using StreamReader yamlReader = File.OpenText(Path.Combine("Workflows", workflowPath));
Mock<WorkflowAgentProvider> mockAgentProvider = CreateMockProvider();
Mock<WorkflowAgentProvider> mockAgentProvider = CreateMockProvider($"{workflowInput}");
DeclarativeWorkflowOptions workflowContext = new(mockAgentProvider.Object) { LoggerFactory = this.Output };
Workflow workflow = DeclarativeWorkflowBuilder.Build<TInput>(yamlReader, workflowContext);
@@ -301,11 +301,11 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
this.WorkflowEventCounts = this.WorkflowEvents.GroupBy(e => e.GetType()).ToDictionary(e => e.Key, e => e.Count());
}
private static Mock<WorkflowAgentProvider> CreateMockProvider()
private static Mock<WorkflowAgentProvider> CreateMockProvider(string input)
{
Mock<WorkflowAgentProvider> mockAgentProvider = new(MockBehavior.Strict);
mockAgentProvider.Setup(provider => provider.CreateConversationAsync(It.IsAny<CancellationToken>())).Returns(() => Task.FromResult(Guid.NewGuid().ToString("N")));
mockAgentProvider.Setup(provider => provider.CreateMessageAsync(It.IsAny<string>(), It.IsAny<ChatMessage>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new ChatMessage(ChatRole.Assistant, "Hi!")));
mockAgentProvider.Setup(provider => provider.CreateMessageAsync(It.IsAny<string>(), It.IsAny<ChatMessage>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new ChatMessage(ChatRole.Assistant, input)));
return mockAgentProvider;
}
}