From b207921fcc25b3c40e19e5b7aaf4ef7b8281df7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 21:23:17 +0000 Subject: [PATCH] Fix MagenticOrchestrator output declaration and add first E2E test Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/322c9e2d-59bc-42ad-9a1e-f6fd4c866b26 Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> --- .../Magentic/MagenticOrchestrator.cs | 1 + .../MagenticOrchestrationTests.cs | 178 ++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticOrchestrator.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticOrchestrator.cs index a08e0b542e..b863c44f1c 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticOrchestrator.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticOrchestrator.cs @@ -101,6 +101,7 @@ internal class MagenticOrchestrator(AIAgent managerAgent, List team, Ta return base.ConfigureProtocol(protocolBuilder) .SendsMessage() .SendsMessage() + .YieldsOutput>() .ConfigureRoutes(ConfigureRoutes); void ConfigureRoutes(RouteBuilder routeBuilder) => routeBuilder.AddPortHandler( diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index 55c8937a0c..e2855bf6ad 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -1 +1,179 @@ // Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Agents.AI.Workflows.InProc; +using Microsoft.Agents.AI.Workflows.Specialized.Magentic; +using Microsoft.Extensions.AI; + +namespace Microsoft.Agents.AI.Workflows.UnitTests; + +/// +/// End-to-end tests for the Magentic orchestrator workflow. +/// +public class MagenticOrchestrationTests +{ + [Fact] + public async Task Task_Completes_When_RequestSatisfied() + { + // Arrange: Manager reports task satisfied on first coordination round + // Each response must have unique message IDs, so create separate instances + List factsResponse = CreatePlanResponse("Facts about the task"); + List planResponse = CreatePlanResponse("Step 1: Do the task"); + List progressLedgerResponse = CreateProgressLedgerResponse( + isRequestSatisfied: true, + isInLoop: false, + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Complete the task"); + List finalAnswerResponse = CreateFinalAnswerResponse("Task completed successfully!"); + + TestReplayAgent manager = new( + [factsResponse, planResponse, progressLedgerResponse, finalAnswerResponse], + name: "Manager"); + TestEchoAgent worker = new(name: "Worker"); + + Workflow workflow = new MagenticWorkflowBuilder(manager) + .AddParticipants(worker) + .RequirePlanSignoff(false) + .Build(); + + // Act + WorkflowRunResult runResult = await RunMagenticWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "Do the task")]); + + // Assert: Check the result contains the final answer + runResult.Result.Should().NotBeNull(); + runResult.Result.Should().ContainSingle(); + runResult.Result![0].Text.Should().Contain("Task completed successfully!"); + runResult.PendingRequests.Should().BeEmpty(); + } + + #region Helper Methods + + private sealed record WorkflowRunResult( + string UpdateText, + List? Result, + CheckpointInfo? LastCheckpoint, + List PendingRequests); + + private static List CreatePlanResponse(string plan) + { + return + [ + new ChatMessage(ChatRole.Assistant, plan) + { + MessageId = Guid.NewGuid().ToString("N"), + CreatedAt = DateTimeOffset.UtcNow + } + ]; + } + + private static List CreateProgressLedgerResponse( + bool isRequestSatisfied, + bool isInLoop, + bool isProgressBeingMade, + string nextSpeaker, + string instructionOrQuestion) + { + string isRequestSatisfiedStr = isRequestSatisfied ? "true" : "false"; + string isInLoopStr = isInLoop ? "true" : "false"; + string isProgressBeingMadeStr = isProgressBeingMade ? "true" : "false"; + + string ledgerJson = $$""" + { + "is_request_satisfied": { "answer": {{isRequestSatisfiedStr}}, "reason": "test reason" }, + "is_in_loop": { "answer": {{isInLoopStr}}, "reason": "test reason" }, + "is_progress_being_made": { "answer": {{isProgressBeingMadeStr}}, "reason": "test reason" }, + "next_speaker": { "answer": "{{nextSpeaker}}", "reason": "test reason" }, + "instruction_or_question": { "answer": "{{instructionOrQuestion}}", "reason": "test reason" } + } + """; + + return + [ + new ChatMessage(ChatRole.Assistant, ledgerJson) + { + MessageId = Guid.NewGuid().ToString("N"), + CreatedAt = DateTimeOffset.UtcNow + } + ]; + } + + private static List CreateFinalAnswerResponse(string answer) + { + return + [ + new ChatMessage(ChatRole.Assistant, answer) + { + MessageId = Guid.NewGuid().ToString("N"), + CreatedAt = DateTimeOffset.UtcNow + } + ]; + } + + private static async Task RunMagenticWorkflowAsync( + Workflow workflow, + List input, + CheckpointManager? checkpointManager = null, + List? eventCollector = null) + { + checkpointManager ??= CheckpointManager.CreateInMemory(); + + InProcessExecutionEnvironment environment = ExecutionEnvironment.InProcess_Lockstep + .ToWorkflowExecutionEnvironment() + .WithCheckpointing(checkpointManager); + + await using StreamingRun run = await environment.OpenStreamingAsync(workflow); + + await run.TrySendMessageAsync(input); + await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); + + return await ProcessWorkflowRunAsync(run, eventCollector); + } + + private static async Task ProcessWorkflowRunAsync( + StreamingRun run, + List? eventCollector = null) + { + StringBuilder sb = new(); + WorkflowOutputEvent? output = null; + CheckpointInfo? lastCheckpoint = null; + List pendingRequests = []; + + await foreach (WorkflowEvent evt in run.WatchStreamAsync(blockOnPendingRequest: false).ConfigureAwait(false)) + { + eventCollector?.Add(evt); + + switch (evt) + { + case AgentResponseUpdateEvent responseUpdate: + sb.Append(responseUpdate.Data); + break; + + case RequestInfoEvent requestInfo: + pendingRequests.Add(requestInfo); + break; + + case WorkflowOutputEvent e: + output = e; + break; + + case WorkflowErrorEvent errorEvent: + Assert.Fail($"Workflow execution failed with error: {errorEvent.Exception}"); + break; + + case SuperStepCompletedEvent stepCompleted: + lastCheckpoint = stepCompleted.CompletionInfo?.Checkpoint; + break; + } + } + + return new(sb.ToString(), output?.As>(), lastCheckpoint, pendingRequests); + } + + #endregion +}