mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
refactor: Remove instance-shared current agent tracking in handoffs
Because the tracker was instance-shared between the start and end executors, it would be shared between all sessions, resulting in incorrect behaviour. The corect way to do this is to keep the data in a shared executor scope, which is per-session.
This commit is contained in:
@@ -145,7 +145,7 @@ public static partial class AgentWorkflowBuilder
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>Creates a new <see cref="HandoffsWorkflowBuilder"/> using <paramref name="initialAgent"/> as the starting agent in the workflow.</summary>
|
||||
/// <summary>Creates a new <see cref="HandoffWorkflowBuilder"/> using <paramref name="initialAgent"/> as the starting agent in the workflow.</summary>
|
||||
/// <param name="initialAgent">The agent that will receive inputs provided to the workflow.</param>
|
||||
/// <returns>The builder for creating a workflow based on handoffs.</returns>
|
||||
/// <remarks>
|
||||
@@ -154,7 +154,7 @@ public static partial class AgentWorkflowBuilder
|
||||
/// The <see cref="AIAgent"/> must be capable of understanding those <see cref="AgentRunOptions"/> provided. If the agent
|
||||
/// ignores the tools or is otherwise unable to advertize them to the underlying provider, handoffs will not occur.
|
||||
/// </remarks>
|
||||
public static HandoffsWorkflowBuilder CreateHandoffBuilderWith(AIAgent initialAgent)
|
||||
public static HandoffWorkflowBuilder CreateHandoffBuilderWith(AIAgent initialAgent)
|
||||
{
|
||||
Throw.IfNull(initialAgent);
|
||||
return new(initialAgent);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Specialized;
|
||||
|
||||
/// <summary>Tracks the current agent ID across turns when return-to-previous routing is enabled.</summary>
|
||||
internal sealed class HandoffsCurrentAgentTracker
|
||||
{
|
||||
public string? CurrentAgentId { get; set; }
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using Microsoft.Extensions.AI;
|
||||
namespace Microsoft.Agents.AI.Workflows.Specialized;
|
||||
|
||||
/// <summary>Executor used at the end of a handoff workflow to raise a final completed event.</summary>
|
||||
internal sealed class HandoffsEndExecutor(HandoffsCurrentAgentTracker? tracker = null) : Executor(ExecutorId, declareCrossRunShareable: true), IResettableExecutor
|
||||
internal sealed class HandoffsEndExecutor(bool returnToPrevious) : Executor(ExecutorId, declareCrossRunShareable: true), IResettableExecutor
|
||||
{
|
||||
public const string ExecutorId = "HandoffEnd";
|
||||
|
||||
@@ -19,9 +19,13 @@ internal sealed class HandoffsEndExecutor(HandoffsCurrentAgentTracker? tracker =
|
||||
|
||||
private async ValueTask HandleAsync(HandoffState handoff, IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (tracker is not null && handoff.CurrentAgentId is not null)
|
||||
if (returnToPrevious)
|
||||
{
|
||||
tracker.CurrentAgentId = handoff.CurrentAgentId;
|
||||
await context.QueueStateUpdateAsync<string?>(HandoffConstants.CurrentAgentTrackerKey,
|
||||
handoff.CurrentAgentId,
|
||||
HandoffConstants.CurrentAgentTrackerScope,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await context.YieldOutputAsync(handoff.Messages, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -7,8 +7,14 @@ using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Specialized;
|
||||
|
||||
internal static class HandoffConstants
|
||||
{
|
||||
internal const string CurrentAgentTrackerKey = "LastAgentId";
|
||||
internal const string CurrentAgentTrackerScope = "HandoffOrchestration";
|
||||
}
|
||||
|
||||
/// <summary>Executor used at the start of a handoffs workflow to accumulate messages and emit them as HandoffState upon receiving a turn token.</summary>
|
||||
internal sealed class HandoffsStartExecutor(HandoffsCurrentAgentTracker? tracker = null) : ChatProtocolExecutor(ExecutorId, DefaultOptions, declareCrossRunShareable: true), IResettableExecutor
|
||||
internal sealed class HandoffsStartExecutor(bool returnToPrevious) : ChatProtocolExecutor(ExecutorId, DefaultOptions, declareCrossRunShareable: true), IResettableExecutor
|
||||
{
|
||||
internal const string ExecutorId = "HandoffStart";
|
||||
|
||||
@@ -22,7 +28,25 @@ internal sealed class HandoffsStartExecutor(HandoffsCurrentAgentTracker? tracker
|
||||
base.ConfigureProtocol(protocolBuilder).SendsMessage<HandoffState>();
|
||||
|
||||
protected override ValueTask TakeTurnAsync(List<ChatMessage> messages, IWorkflowContext context, bool? emitEvents, CancellationToken cancellationToken = default)
|
||||
=> context.SendMessageAsync(new HandoffState(new(emitEvents), null, messages, tracker?.CurrentAgentId), cancellationToken: cancellationToken);
|
||||
{
|
||||
if (returnToPrevious)
|
||||
{
|
||||
return context.InvokeWithStateAsync(
|
||||
async (string? currentAgentId, IWorkflowContext context, CancellationToken cancellationToken) =>
|
||||
{
|
||||
HandoffState handoffState = new(new(emitEvents), null, messages, currentAgentId);
|
||||
await context.SendMessageAsync(handoffState, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return currentAgentId;
|
||||
},
|
||||
HandoffConstants.CurrentAgentTrackerKey,
|
||||
HandoffConstants.CurrentAgentTrackerScope,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
HandoffState handoff = new(new(emitEvents), null, messages);
|
||||
return context.SendMessageAsync(handoff, cancellationToken);
|
||||
}
|
||||
|
||||
public new ValueTask ResetAsync() => base.ResetAsync();
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ internal sealed class HandoffTestEchoAgent(string id, string name, string prefix
|
||||
{
|
||||
IEnumerable<AITool>? handoffs = chatClientOptions.ChatOptions
|
||||
.Tools?
|
||||
.Where(tool => tool.Name?.StartsWith(HandoffsWorkflowBuilder.FunctionPrefix,
|
||||
.Where(tool => tool.Name?.StartsWith(HandoffWorkflowBuilder.FunctionPrefix,
|
||||
StringComparison.OrdinalIgnoreCase) is true);
|
||||
|
||||
if (handoffs != null)
|
||||
@@ -58,7 +58,7 @@ internal static class Step12EntryPoint
|
||||
.Select(i => new HandoffTestEchoAgent($"{EchoAgentIdPrefix}{i}", $"{EchoAgentNamePrefix}{i}", EchoPrefixForAgent(i)))
|
||||
.ToArray();
|
||||
|
||||
return new HandoffsWorkflowBuilder(echoAgents[0])
|
||||
return new HandoffWorkflowBuilder(echoAgents[0])
|
||||
.WithHandoff(echoAgents[0], echoAgents[1])
|
||||
.Build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user