mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Forward Magentic participant replies to manager (#6156)
MagenticOrchestrator.TakeTurnAsync dropped the `messages` parameter on subsequent turns, so participant replies never reached the manager's ChatHistory. The manager kept re-dispatching the same speaker every round until MaxRounds. Append the incoming messages to taskContext.ChatHistory before running the coordination round (matches Python's _handle_response). Adds RecordingReplayAgent + regression test that asserts the worker's reply reaches round-2's progress-ledger call. Co-authored-by: Jacob Alber <jaalber@microsoft.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
fa2a6af443
commit
07a1e83492
+6
-1
@@ -195,7 +195,12 @@ internal class MagenticOrchestrator(AIAgent managerAgent, List<AIAgent> team, Ta
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subsequent turns: agent returned control, go directly to coordination (progress ledger only, no replan)
|
||||
// Subsequent turns: agent returned control, go directly to coordination (progress ledger only, no replan).
|
||||
// Capture the participant's reply into the manager-visible chat history so the progress ledger can see it.
|
||||
if (messages is { Count: > 0 })
|
||||
{
|
||||
this._taskContext.ChatHistory.AddRange(messages);
|
||||
}
|
||||
await this.RunCoordinationRoundAsync(this._taskContext, context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,6 +361,64 @@ public class MagenticOrchestrationTests
|
||||
runResult.Result![0].Text.Should().Contain("Multi-round task completed!");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunCoordinationRound_Forwards_Participant_Reply_To_ManagerAsync()
|
||||
{
|
||||
// Regression: MagenticOrchestrator.TakeTurnAsync used to drop the `messages`
|
||||
// parameter on subsequent turns, so participant replies never reached the
|
||||
// manager's ChatHistory. The manager then re-dispatched the same speaker
|
||||
// every round until MaxRounds. Assert that round-2's progress-ledger call
|
||||
// actually sees the worker's reply in its input.
|
||||
|
||||
const string TaskPrompt = "Echo back this exact magentic-regression-marker";
|
||||
|
||||
List<ChatMessage> factsResponse = CreatePlanResponse("Facts");
|
||||
List<ChatMessage> planResponse = CreatePlanResponse("Plan");
|
||||
List<ChatMessage> round1Ledger = CreateProgressLedgerResponse(
|
||||
isRequestSatisfied: false,
|
||||
isInLoop: false,
|
||||
isProgressBeingMade: true,
|
||||
nextSpeaker: "Worker",
|
||||
instructionOrQuestion: TaskPrompt);
|
||||
List<ChatMessage> round2Ledger = CreateProgressLedgerResponse(
|
||||
isRequestSatisfied: true,
|
||||
isInLoop: false,
|
||||
isProgressBeingMade: true,
|
||||
nextSpeaker: "Worker",
|
||||
instructionOrQuestion: "Done");
|
||||
List<ChatMessage> finalAnswer = CreateFinalAnswerResponse("All good");
|
||||
|
||||
RecordingReplayAgent manager = new(
|
||||
[factsResponse, planResponse, round1Ledger, round2Ledger, finalAnswer],
|
||||
name: "Manager");
|
||||
TestEchoAgent worker = new(name: "Worker");
|
||||
|
||||
Workflow workflow = new MagenticWorkflowBuilder(manager)
|
||||
.AddParticipants(worker)
|
||||
.RequirePlanSignoff(false)
|
||||
.Build();
|
||||
|
||||
WorkflowRunResult runResult = await RunMagenticWorkflowAsync(
|
||||
workflow,
|
||||
[new ChatMessage(ChatRole.User, TaskPrompt)]);
|
||||
|
||||
runResult.Result.Should().NotBeNull();
|
||||
runResult.Result![0].Text.Should().Contain("All good");
|
||||
|
||||
// Calls in order: facts, plan, ledger1, ledger2, finalAnswer.
|
||||
manager.RecordedInputs.Should().HaveCount(5);
|
||||
|
||||
manager.RecordedInputs[3].Should().Contain(
|
||||
m => m.Role == ChatRole.Assistant
|
||||
&& m.AuthorName == "Worker"
|
||||
&& m.Text.Contains(TaskPrompt),
|
||||
"round-2 progress ledger must see the worker's reply; without it the manager loops to MaxRounds");
|
||||
|
||||
manager.RecordedInputs[4].Should().Contain(
|
||||
m => m.Role == ChatRole.Assistant && m.AuthorName == "Worker",
|
||||
"final-answer synthesis must see what participants actually said");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlanReview_Revised_Triggers_ReplanAsync()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="TestReplayAgent"/> that records the input messages it receives on each call.
|
||||
/// Used by tests that need to assert what context the agent was actually handed.
|
||||
/// </summary>
|
||||
internal sealed class RecordingReplayAgent(List<List<ChatMessage>> messages, string? id = null, string? name = null)
|
||||
: TestReplayAgent(messages, id, name)
|
||||
{
|
||||
public List<List<ChatMessage>> RecordedInputs { get; } = [];
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.RecordedInputs.Add(messages.ToList());
|
||||
await foreach (AgentResponseUpdate update in base.RunCoreStreamingAsync(messages, session, options, cancellationToken))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user