@@ -20,29 +20,23 @@ namespace Microsoft.Agents.Orchestration;
2020/// </summary>
2121public sealed partial class HandoffOrchestration : OrchestratingAgent
2222{
23- private readonly OrchestrationHandoffs _handoffs ;
23+ private readonly Handoffs _handoffs ;
2424
2525 /// <summary>
2626 /// Initializes a new instance of the <see cref="HandoffOrchestration"/> class.
2727 /// </summary>
2828 /// <param name="handoffs">Defines the handoff connections for each agent.</param>
29- /// <param name="agents">Additional agents participating in the orchestration that weren't passed to <paramref name="handoffs"/>.</param>
30- public HandoffOrchestration ( OrchestrationHandoffs handoffs , params AIAgent [ ] agents ) : base (
31- agents is { Length : 0 } ? [ .. handoffs . Agents ] :
32- handoffs . Agents is { Count : 0 } ? agents :
33- [ .. handoffs . Agents . Concat ( agents ) . Distinct ( ) ] )
29+ public HandoffOrchestration ( Handoffs handoffs ) : this ( handoffs , name : null )
3430 {
35- // Create list of distinct agent names
36- HashSet < string > agentNames = [ .. base . Agents . Select ( a => a . DisplayName ) , handoffs . FirstAgentName ] ;
37-
38- // Extract names from handoffs that don't align with a member agent.
39- // Fail fast if invalid names are present.
40- string [ ] badNames = [ .. handoffs . Keys . Concat ( handoffs . Values . SelectMany ( h => h . Keys ) ) . Where ( name => ! agentNames . Contains ( name ) ) ] ;
41- if ( badNames . Length > 0 )
42- {
43- Throw . ArgumentException ( nameof ( handoffs ) , $ "The following agents are not defined in the orchestration: { string . Join ( ", " , badNames ) } ") ;
44- }
31+ }
4532
33+ /// <summary>
34+ /// Initializes a new instance of the <see cref="HandoffOrchestration"/> class.
35+ /// </summary>
36+ /// <param name="handoffs">Defines the handoff connections for each agent.</param>
37+ /// <param name="name">An optional name for this orchestrating agent.</param>
38+ public HandoffOrchestration ( Handoffs handoffs , string ? name ) : base ( handoffs . Agents . ToArray ( ) , name )
39+ {
4640 this . _handoffs = handoffs ;
4741 }
4842
@@ -54,39 +48,51 @@ protected override Task<AgentRunResponse> RunCoreAsync(IReadOnlyCollection<ChatM
5448 {
5549 List < ChatMessage > allMessages = [ .. messages ] ;
5650 int originalMessageCount = allMessages . Count ;
57- return this . ResumeAsync ( this . _handoffs . FirstAgentName , allMessages , originalMessageCount , context , cancellationToken ) ;
51+ return this . ResumeAsync ( this . _handoffs . InitialAgent , allMessages , originalMessageCount , context , cancellationToken ) ;
5852 }
5953
6054 /// <inheritdoc />
6155 protected override Task < AgentRunResponse > ResumeCoreAsync ( JsonElement checkpointState , OrchestratingAgentContext context , CancellationToken cancellationToken )
6256 {
6357 var state = checkpointState . Deserialize ( OrchestrationJsonContext . Default . HandoffState ) ?? throw new InvalidOperationException ( "The checkpoint state is invalid." ) ;
64- return this . ResumeAsync ( state . NextAgent , state . AllMessages , state . OriginalMessageCount , context , cancellationToken ) ;
58+
59+ AIAgent ? nextAgent = null ;
60+ foreach ( var agent in this . Agents )
61+ {
62+ if ( agent . Id == state . NextAgent )
63+ {
64+ nextAgent = agent ;
65+ break ;
66+ }
67+ }
68+
69+ if ( nextAgent is null )
70+ {
71+ Throw . InvalidOperationException ( $ "The next agent '{ state . NextAgent } ' is not defined in the orchestration.") ;
72+ }
73+
74+ return this . ResumeAsync ( nextAgent , state . AllMessages , state . OriginalMessageCount , context , cancellationToken ) ;
6575 }
6676
6777 /// <inheritdoc />
6878 private async Task < AgentRunResponse > ResumeAsync (
69- string ? nextAgent , List < ChatMessage > allMessages , int originalMessageCount , OrchestratingAgentContext context , CancellationToken cancellationToken )
79+ AIAgent ? agent , List < ChatMessage > allMessages , int originalMessageCount , OrchestratingAgentContext context , CancellationToken cancellationToken )
7080 {
71- Debug . Assert ( nextAgent is not null ) ;
81+ Debug . Assert ( agent is not null ) ;
7282 AgentRunResponse ? response = null ;
7383
74- while ( nextAgent is not null )
84+ while ( agent is not null )
7585 {
76- AIAgent ? agent =
77- this . Agents . FirstOrDefault ( a => a . Name == nextAgent || a . Id == nextAgent ) ??
78- throw new InvalidOperationException ( $ "The agent '{ nextAgent } ' is not defined in the orchestration.") ;
79-
8086 this . LogOrchestrationSubagentRunning ( context , agent ) ;
8187
82- if ( ! this . _handoffs . TryGetValue ( agent . DisplayName , out AgentHandoffs ? handoffs ) || handoffs . Count == 0 )
88+ if ( ! this . _handoffs . Targets . TryGetValue ( agent , out var handoffs ) || handoffs . Count == 0 )
8389 {
8490 // If no handoff is available, we can run the agent directly and return its response.
8591 response = await RunAsync ( agent , context , allMessages , context . Options , cancellationToken ) . ConfigureAwait ( false ) ;
92+ this . LogOrchestrationSubagentCompleted ( context , agent ) ;
8693 allMessages . AddRange ( response . Messages ) ;
87- nextAgent = null ;
94+ agent = null ;
8895 await CheckpointAsync ( ) . ConfigureAwait ( false ) ;
89- this . LogOrchestrationSubagentCompleted ( context , agent ) ;
9096 break ;
9197 }
9298
@@ -107,8 +113,9 @@ private async Task<AgentRunResponse> ResumeAsync(
107113
108114 // Invoke the next agent with all of the messages collected so far.
109115 response = await RunAsync ( agent , context , allMessages , options , cancellationToken ) . ConfigureAwait ( false ) ;
116+ this . LogOrchestrationSubagentCompleted ( context , agent ) ;
110117 allMessages . AddRange ( response . Messages ) ;
111- nextAgent = handoffCtx . TargetedAgent ;
118+ agent = handoffCtx . TargetedAgent ;
112119 RemoveHandoffFunctionCalls ( response , handoffTools ) ;
113120
114121 if ( this . InteractiveCallback is not null )
@@ -118,12 +125,10 @@ private async Task<AgentRunResponse> ResumeAsync(
118125 break ;
119126 }
120127
121- nextAgent = agent . DisplayName ;
122128 allMessages . Add ( await this . InteractiveCallback ( ) . ConfigureAwait ( false ) ) ;
123129 }
124130
125131 await CheckpointAsync ( ) . ConfigureAwait ( false ) ;
126- this . LogOrchestrationSubagentCompleted ( context , agent ) ;
127132 }
128133
129134 allMessages . RemoveRange ( 0 , originalMessageCount ) ;
@@ -132,7 +137,7 @@ private async Task<AgentRunResponse> ResumeAsync(
132137 return response ;
133138
134139 Task CheckpointAsync ( ) => context . Runtime is not null ?
135- base . WriteCheckpointAsync ( JsonSerializer . SerializeToElement ( new ( nextAgent , allMessages , originalMessageCount ) , OrchestrationJsonContext . Default . HandoffState ) , context , cancellationToken ) :
140+ base . WriteCheckpointAsync ( JsonSerializer . SerializeToElement ( new ( agent ? . Id , allMessages , originalMessageCount ) , OrchestrationJsonContext . Default . HandoffState ) , context , cancellationToken ) :
136141 Task . CompletedTask ;
137142 }
138143
@@ -173,9 +178,9 @@ private static void RemoveHandoffFunctionCalls(AgentRunResponse response, List<A
173178 }
174179 }
175180
176- private sealed class HandoffContext ( AgentHandoffs handoffs )
181+ private sealed class HandoffContext ( HashSet < Handoffs . HandoffTarget > handoffs )
177182 {
178- public string ? TargetedAgent { get ; set ; }
183+ public AIAgent ? TargetedAgent { get ; set ; }
179184 public bool EndTaskInvoked { get ; set ; }
180185
181186 public List < AITool > CreateHandoffFunctions ( bool needsEndTask )
@@ -194,16 +199,16 @@ public List<AITool> CreateHandoffFunctions(bool needsEndTask)
194199 description : "Invoke this function when all work is completed and no further interactions are required." ) ) ;
195200 }
196201
197- foreach ( KeyValuePair < string , string > handoff in handoffs )
202+ foreach ( Handoffs . HandoffTarget handoff in handoffs )
198203 {
199204 functions . Add ( AIFunctionFactory . Create (
200205 ( ) =>
201206 {
202- this . TargetedAgent = handoff . Key ;
207+ this . TargetedAgent = handoff . Target ;
203208 Terminate ( ) ;
204209 } ,
205- name : $ "handoff_to_{ InvalidNameCharsRegex ( ) . Replace ( handoff . Key , "_" ) } ",
206- description : handoff . Value ) ) ;
210+ name : $ "handoff_to_{ InvalidNameCharsRegex ( ) . Replace ( handoff . Target . DisplayName , "_" ) } ",
211+ description : handoff . Reason ) ) ;
207212 }
208213
209214 return functions ;
0 commit comments