Round 2 of cleanup for agent runtime and orchestrations (#164)

- Moved InProcessRuntime type into abstractions package and deleted InProcess package.
- Moved several members of IAgentRuntime to be extension methods instead, e.g. multiple GetActorAsync overloads.
- Added synchronous RegisterMessageHandler overloads and used them to avoid unnecessary async usage at call sites.
- Removed unnecessary surface area from InProcessRuntime, e.g. StopAsync, RunUntilIdleAsync, etc.
- Fixed spin loop in InProcessRuntime that would consume an entire core for the duration of the orchestration's operation.
- Removed a bunch of allocation from InProcessRuntime.
- Made a runtime optional for orchestrations, defaulting to using a temporary InProcessRuntime if none is provided.
- Removed custom delegate types from orchestrations.
- Consolidated namespaces.
- Used records to simplify message classes.
- Tweaked naming on AgentActor to make purpose of protected methods more clear.
- Removed invocation in AgentActor.InvokeAsync of empty update / isFinal parameter.
- Changed OrchestrationHandoffs to avoid needing to pass in agents duplicatively.
- Made various extension methods, such as those on OrchestrationHandoffsExtensions, into instance methods.
This commit is contained in:
Stephen Toub
2025-07-11 11:12:18 -04:00
committed by GitHub
parent eca0eb9cd0
commit 456e7d1b65
83 changed files with 939 additions and 1664 deletions
+1 -4
View File
@@ -159,11 +159,8 @@ public class AgentSample(ITestOutputHelper output) : BaseSample(output)
private async Task AzureAIAgentsPersistentAgentCleanUpAsync(ChatClientAgent agent, AgentThread? thread, CancellationToken cancellationToken)
{
var persistentAgentsClient = agent.ChatClient.GetService<PersistentAgentsClient>();
if (persistentAgentsClient is null)
{
var persistentAgentsClient = agent.ChatClient.GetService<PersistentAgentsClient>() ??
throw new InvalidOperationException("The provided chat client is not a Persistent Agents Chat Client");
}
await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id, cancellationToken);
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Concurrent;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -42,20 +40,14 @@ public class ConcurrentOrchestration_Intro(ITestOutputHelper output) : Orchestra
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "What is temperature?";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string[]> result = await orchestration.InvokeAsync(input, runtime);
OrchestrationResult<string[]> result = await orchestration.InvokeAsync(input);
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
string[] output = await result;
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
await runtime.RunUntilIdleAsync();
this.DisplayHistory(monitor.History);
}
}
@@ -2,10 +2,7 @@
using System.Text.Json;
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Concurrent;
using Microsoft.Agents.Orchestration.Transforms;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
using Microsoft.Shared.Samples;
namespace Orchestration;
@@ -43,20 +40,14 @@ public class ConcurrentOrchestration_With_StructuredOutput(ITestOutputHelper out
ResultTransform = outputTransform.TransformAsync,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
const string resourceId = "Hamlet_full_play_summary.txt";
string input = Resources.Read(resourceId);
Console.WriteLine($"\n# INPUT: @{resourceId}\n");
OrchestrationResult<Analysis> result = await orchestration.InvokeAsync(input, runtime);
OrchestrationResult<Analysis> result = await orchestration.InvokeAsync(input);
Analysis output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
Analysis output = await result;
Console.WriteLine($"\n# RESULT:\n{JsonSerializer.Serialize(output, s_options)}");
await runtime.RunUntilIdleAsync();
}
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.GroupChat;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -68,17 +66,10 @@ public class GroupChatOrchestration_Intro(ITestOutputHelper output) : Orchestrat
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
OrchestrationResult<string> result = await orchestration.InvokeAsync(input);
Console.WriteLine($"\n# RESULT: {await result}");
this.DisplayHistory(monitor.History);
}
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.GroupChat;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -132,17 +130,10 @@ public class GroupChatOrchestration_With_AIManager(ITestOutputHelper output) : O
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT: {topic}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(topic, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
OrchestrationResult<string> result = await orchestration.InvokeAsync(topic);
Console.WriteLine($"\n# RESULT: {await result}");
this.DisplayHistory(monitor.History);
}
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.GroupChat;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -68,18 +66,11 @@ public class GroupChatOrchestration_With_HumanInTheLoop(ITestOutputHelper output
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
OrchestrationResult<string> result = await orchestration.InvokeAsync(input);
Console.WriteLine($"\n# RESULT: {await result}");
this.DisplayHistory(monitor.History);
}
@@ -97,23 +88,12 @@ public class GroupChatOrchestration_With_HumanInTheLoop(ITestOutputHelper output
{
string? lastAgent = history.LastOrDefault()?.AuthorName;
GroupChatManagerResult<bool> result;
GroupChatManagerResult<bool> result =
lastAgent is null ? new(false) { Reason = "No agents have spoken yet." } :
lastAgent is "Reviewer" ? new(true) { Reason = "User input is needed after the reviewer's message." } :
new(false) { Reason = "User input is not needed until the reviewer's message." };
if (lastAgent is null)
{
result = new GroupChatManagerResult<bool>(false) { Reason = "No agents have spoken yet." };
}
if (lastAgent == "Reviewer")
{
result = new GroupChatManagerResult<bool>(true) { Reason = "User input is needed after the reviewer's message." };
}
else
{
result = new GroupChatManagerResult<bool>(false) { Reason = "User input is not needed until the reviewer's message." };
}
return new ValueTask<GroupChatManagerResult<bool>>(result);
return new(result);
}
}
}
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Handoff;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -65,11 +63,7 @@ public class HandoffOrchestration_Intro(ITestOutputHelper output) : Orchestratio
.Add(triageAgent, statusAgent, returnAgent, refundAgent)
.Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
triageAgent,
statusAgent,
returnAgent,
refundAgent)
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"))
{
InteractiveCallback = () =>
{
@@ -84,18 +78,11 @@ public class HandoffOrchestration_Intro(ITestOutputHelper output) : Orchestratio
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{task}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
OrchestrationResult<string> result = await orchestration.InvokeAsync(task);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(300));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine($"\n# RESULT: {await result}");
this.DisplayHistory(monitor.History);
}
@@ -2,10 +2,8 @@
using System.Text.Json.Serialization;
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Handoff;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -49,10 +47,7 @@ public class HandoffOrchestration_With_StructuredInput(ITestOutputHelper output)
HandoffOrchestration<GithubIssue, string> orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, dotnetAgent, pythonAgent),
triageAgent,
pythonAgent,
dotnetAgent)
.Add(triageAgent, dotnetAgent, pythonAgent))
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
@@ -83,18 +78,11 @@ public class HandoffOrchestration_With_StructuredInput(ITestOutputHelper output)
Labels = []
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{input.Id}: {input.Title}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input);
Console.WriteLine($"\n# RESULT: {await result}");
Console.WriteLine($"\n# LABELS: {string.Join(",", githubPlugin.Labels["12345"])}\n");
await runtime.RunUntilIdleAsync();
}
private sealed class GithubIssue
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Sequential;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -64,18 +62,11 @@ public class SequentialOrchestration_Intro(ITestOutputHelper output) : Orchestra
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
OrchestrationResult<string> result = await orchestration.InvokeAsync(input);
Console.WriteLine($"\n# RESULT: {await result}");
this.DisplayHistory(monitor.History);
}
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.Orchestration;
using Microsoft.Agents.Orchestration.Sequential;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
namespace Orchestration;
@@ -26,29 +24,22 @@ public class SequentialOrchestration_With_Cancellation(ITestOutputHelper output)
// Define the orchestration
SequentialOrchestration orchestration = new(agent) { LoggerFactory = this.LoggerFactory };
// Start the runtime
await using InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "42";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
OrchestrationResult<string> result = await orchestration.InvokeAsync(input);
result.Cancel();
await Task.Delay(TimeSpan.FromSeconds(3));
try
{
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
Console.WriteLine($"\n# RESULT: {await result}");
}
catch (AggregateException exception)
catch (TimeoutException exception)
{
Console.WriteLine($"\n# CANCELLED: {exception.InnerException?.Message}");
Console.WriteLine($"\n# CANCELED: {exception.Message}");
}
await runtime.RunUntilIdleAsync();
}
}