Skip to content

Commit c4623c3

Browse files
crickmanTaoChenOSU
andauthored
.Net Agents - Magentic Agent Orchestration (#12110)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Introducing _Magentic_ agent orchestration. Fixes: #10900 Fixes: #12156 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Add _Magentic_ ledger based orchestration, based on AutoGen _Magentic One_ ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄 --------- Co-authored-by: Tao Chen <taochen@microsoft.com>
1 parent d72182f commit c4623c3

32 files changed

+1803
-25
lines changed

dotnet/SK-dotnet.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorData.UnitTests", "src
552552
EndProject
553553
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.Orchestration", "src\Agents\Orchestration\Agents.Orchestration.csproj", "{D1A02387-FA60-22F8-C2ED-4676568B6CC3}"
554554
EndProject
555+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.Magentic", "src\Agents\Magentic\Agents.Magentic.csproj", "{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}"
556+
EndProject
555557
Global
556558
GlobalSection(SolutionConfigurationPlatforms) = preSolution
557559
Debug|Any CPU = Debug|Any CPU
@@ -1519,6 +1521,12 @@ Global
15191521
{D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Publish|Any CPU.Build.0 = Publish|Any CPU
15201522
{D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
15211523
{D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Release|Any CPU.Build.0 = Release|Any CPU
1524+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1525+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
1526+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
1527+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Publish|Any CPU.Build.0 = Publish|Any CPU
1528+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
1529+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7}.Release|Any CPU.Build.0 = Release|Any CPU
15221530
EndGlobalSection
15231531
GlobalSection(SolutionProperties) = preSolution
15241532
HideSolutionNode = FALSE
@@ -1725,6 +1733,7 @@ Global
17251733
{DA6B4ED4-ED0B-D25C-889C-9F940E714891} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
17261734
{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89}
17271735
{D1A02387-FA60-22F8-C2ED-4676568B6CC3} = {6823CD5E-2ABE-41EB-B865-F86EC13F0CF9}
1736+
{38059FCB-2CD7-C0DE-AE71-DA4D0245ECC7} = {6823CD5E-2ABE-41EB-B865-F86EC13F0CF9}
17281737
EndGlobalSection
17291738
GlobalSection(ExtensibilityGlobals) = postSolution
17301739
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}

dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<ProjectReference Include="..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
4848
<ProjectReference Include="..\..\src\Agents\Bedrock\Agents.Bedrock.csproj" />
4949
<ProjectReference Include="..\..\src\Agents\Orchestration\Agents.Orchestration.csproj" />
50+
<ProjectReference Include="..\..\src\Agents\Magentic\Agents.Magentic.csproj" />
5051
<ProjectReference Include="..\..\src\Agents\Runtime\InProcess\Runtime.InProcess.csproj" />
5152
<ProjectReference Include="..\..\src\Agents\Yaml\Agents.Yaml.csproj" />
5253
<ProjectReference Include="..\..\src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj" />

dotnet/samples/GettingStartedWithAgents/Orchestration/Step01_Concurrent.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@ public async Task ConcurrentTaskAsync()
2727
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
2828
description: "An expert in chemistry");
2929

30-
// Define the orchestration
30+
// Create a monitor to capturing agent responses (via ResponseCallback)
31+
// to display at the end of this sample. (optional)
32+
// NOTE: Create your own callback to capture responses in your application or service.
3133
OrchestrationMonitor monitor = new();
34+
35+
// Define the orchestration
3236
ConcurrentOrchestration orchestration =
3337
new(physicist, chemist)
3438
{
3539
ResponseCallback = monitor.ResponseCallback,
36-
LoggerFactory = this.LoggerFactory
40+
LoggerFactory = this.LoggerFactory,
3741
};
3842

3943
// Start the runtime

dotnet/samples/GettingStartedWithAgents/Orchestration/Step02_Sequential.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ give format and make it polished. Output the final improved copy as a single tex
5050
""",
5151
description: "An agent that formats and proofreads the marketing copy.");
5252

53-
// Define the orchestration
53+
// Create a monitor to capturing agent responses (via ResponseCallback)
54+
// to display at the end of this sample. (optional)
55+
// NOTE: Create your own callback to capture responses in your application or service.
5456
OrchestrationMonitor monitor = new();
57+
// Define the orchestration
5558
SequentialOrchestration orchestration =
5659
new(analystAgent, writerAgent, editorAgent)
5760
{

dotnet/samples/GettingStartedWithAgents/Orchestration/Step03_GroupChat.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ Consider suggestions when refining an idea.
4949
If not, provide insight on how to refine suggested copy without example.
5050
""");
5151

52-
// Define the orchestration
52+
// Create a monitor to capturing agent responses (via ResponseCallback)
53+
// to display at the end of this sample. (optional)
54+
// NOTE: Create your own callback to capture responses in your application or service.
5355
OrchestrationMonitor monitor = new();
56+
// Define the orchestration
5457
GroupChatOrchestration orchestration =
5558
new(new RoundRobinGroupChatManager()
5659
{

dotnet/samples/GettingStartedWithAgents/Orchestration/Step04_Handoff.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,20 @@ public async Task OrderSupportAsync()
4444
description: "A customer support agent that handles order refund.");
4545
refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
4646

47-
// Define the orchestration
47+
// Create a monitor to capturing agent responses (via ResponseCallback)
48+
// to display at the end of this sample. (optional)
49+
// NOTE: Create your own callback to capture responses in your application or service.
4850
OrchestrationMonitor monitor = new();
51+
// Define user responses for InteractiveCallback (since sample is not interactive)
4952
Queue<string> responses = new();
53+
string task = "I am a customer that needs help with my orders";
54+
responses.Enqueue("I'd like to track the status of my order");
55+
responses.Enqueue("My order ID is 123");
56+
responses.Enqueue("I want to return another order of mine");
57+
responses.Enqueue("Order ID 321");
58+
responses.Enqueue("Broken item");
59+
responses.Enqueue("No, bye");
60+
// Define the orchestration
5061
HandoffOrchestration orchestration =
5162
new(OrchestrationHandoffs
5263
.StartWith(triageAgent)
@@ -74,13 +85,6 @@ public async Task OrderSupportAsync()
7485
await runtime.StartAsync();
7586

7687
// Run the orchestration
77-
string task = "I am a customer that needs help with my orders";
78-
responses.Enqueue("I'd like to track the status of my order");
79-
responses.Enqueue("My order ID is 123");
80-
responses.Enqueue("I want to return another order of mine");
81-
responses.Enqueue("Order ID 321");
82-
responses.Enqueue("Broken item");
83-
responses.Enqueue("No, bye");
8488
Console.WriteLine($"\n# INPUT:\n{task}\n");
8589
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
8690

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Azure.Identity;
4+
using Microsoft.SemanticKernel;
5+
using Microsoft.SemanticKernel.Agents;
6+
using Microsoft.SemanticKernel.Agents.AzureAI;
7+
using Microsoft.SemanticKernel.Agents.Magentic;
8+
using Microsoft.SemanticKernel.Agents.Orchestration;
9+
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
10+
using Microsoft.SemanticKernel.ChatCompletion;
11+
using Microsoft.SemanticKernel.Connectors.OpenAI;
12+
using AAIP = Azure.AI.Projects;
13+
14+
namespace GettingStarted.Orchestration;
15+
16+
/// <summary>
17+
/// Demonstrates how to use the <see cref="MagenticOrchestration"/> with two agents:
18+
/// - A Research agent that can perform web searches
19+
/// - A Coder agent that can run code using the code interpreter
20+
/// </summary>
21+
public class Step05_Magentic(ITestOutputHelper output) : BaseOrchestrationTest(output)
22+
{
23+
private const string ManagerModel = "o3-mini";
24+
private const string ResearcherModel = "gpt-4o-search-preview";
25+
26+
/// <summary>
27+
/// Require OpenAI services in order to use "gpt-4o-search-preview" model
28+
/// </summary>
29+
protected override bool ForceOpenAI => true;
30+
31+
[Fact]
32+
public async Task MagenticTaskAsync()
33+
{
34+
// Define the agents
35+
Kernel researchKernel = CreateKernelWithOpenAIChatCompletion(ResearcherModel);
36+
ChatCompletionAgent researchAgent =
37+
this.CreateAgent(
38+
name: "ResearchAgent",
39+
description: "A helpful assistant with access to web search. Ask it to perform web searches.",
40+
instructions: "You are a Researcher. You find information without additional computation or quantitative analysis.",
41+
kernel: researchKernel);
42+
43+
AAIP.AIProjectClient projectClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
44+
AAIP.AgentsClient agentsClient = projectClient.GetAgentsClient();
45+
AAIP.Agent definition =
46+
await agentsClient.CreateAgentAsync(
47+
TestConfiguration.AzureAI.ChatModelId,
48+
name: "CoderAgent",
49+
description: "Write and executes code to process and analyze data.",
50+
instructions: "You solve questions using code. Please provide detailed analysis and computation process.",
51+
tools: [new Azure.AI.Projects.CodeInterpreterToolDefinition()]);
52+
AzureAIAgent coderAgent = new(definition, agentsClient);
53+
54+
// Create a monitor to capturing agent responses (via ResponseCallback)
55+
// to display at the end of this sample. (optional)
56+
// NOTE: Create your own callback to capture responses in your application or service.
57+
OrchestrationMonitor monitor = new();
58+
// Define the orchestration
59+
Kernel managerKernel = this.CreateKernelWithChatCompletion(ManagerModel);
60+
StandardMagenticManager manager =
61+
new(managerKernel.GetRequiredService<IChatCompletionService>(), new OpenAIPromptExecutionSettings())
62+
{
63+
MaximumInvocationCount = 5,
64+
};
65+
MagenticOrchestration orchestration =
66+
new(manager, researchAgent, coderAgent)
67+
{
68+
ResponseCallback = monitor.ResponseCallback,
69+
LoggerFactory = this.LoggerFactory,
70+
};
71+
72+
// Start the runtime
73+
InProcessRuntime runtime = new();
74+
await runtime.StartAsync();
75+
76+
string input =
77+
"""
78+
I am preparing a report on the energy efficiency of different machine learning model architectures.
79+
Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 on standard datasets
80+
(e.g., ImageNet for ResNet, GLUE for BERT, WebText for GPT-2).
81+
Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 VM for 24 hours.
82+
Provide tables for clarity, and recommend the most energy-efficient model per task type
83+
(image classification, text classification, and text generation).
84+
""";
85+
Console.WriteLine($"\n# INPUT:\n{input}\n");
86+
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
87+
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 10));
88+
Console.WriteLine($"\n# RESULT: {text}");
89+
90+
await runtime.RunUntilIdleAsync();
91+
92+
Console.WriteLine("\n\nORCHESTRATION HISTORY");
93+
foreach (ChatMessageContent message in monitor.History)
94+
{
95+
this.WriteAgentChatMessage(message);
96+
}
97+
}
98+
99+
private Kernel CreateKernelWithOpenAIChatCompletion(string model)
100+
{
101+
IKernelBuilder builder = Kernel.CreateBuilder();
102+
103+
builder.AddOpenAIChatCompletion(
104+
model,
105+
TestConfiguration.OpenAI.ApiKey);
106+
107+
return builder.Build();
108+
}
109+
}

dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ private static List<ToolConnection> GetToolConnections(this AgentToolDefinition
242242

243243
var toolConnections = agentToolDefinition.GetRequiredOption<List<object>>("tool_connections");
244244

245-
return toolConnections.Select(connectionId => new ToolConnection(connectionId.ToString())).ToList();
245+
return [.. toolConnections.Select(connectionId => new ToolConnection(connectionId.ToString()))];
246246
}
247247

248248
private static T GetRequiredOption<T>(this AgentToolDefinition agentToolDefinition, string key)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<!-- THIS PROPERTY GROUP MUST COME FIRST -->
5+
<AssemblyName>Microsoft.SemanticKernel.Agents.Magentic</AssemblyName>
6+
<RootNamespace>Microsoft.SemanticKernel.Agents.Magentic</RootNamespace>
7+
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
8+
<NoWarn>$(NoWarn);IDE1006;SKEXP0110;SKEXP0001</NoWarn>
9+
<EnablePackageValidation>false</EnablePackageValidation>
10+
<VersionSuffix>preview</VersionSuffix>
11+
</PropertyGroup>
12+
13+
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
14+
15+
<PropertyGroup>
16+
<!-- NuGet Package Settings -->
17+
<Title>Semantic Kernel Agents - Magentic Agents</Title>
18+
<Description>Defines Magentic agents and orchestration.</Description>
19+
</PropertyGroup>
20+
21+
<ItemGroup>
22+
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/src/Diagnostics/*" Link="%(RecursiveDir)Utilities/%(Filename)%(Extension)" />
23+
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/src/System/*" Link="%(RecursiveDir)Utilities/%(Filename)%(Extension)" />
24+
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/agents/Extensions/AgentExtensions.cs" Link="%(RecursiveDir)Utilities/%(Filename)%(Extension)" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\Orchestration\Agents.Orchestration.csproj" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<InternalsVisibleTo Include="SemanticKernel.Agents.UnitTests" />
33+
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
34+
</ItemGroup>
35+
36+
</Project>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Reflection;
5+
6+
namespace Microsoft.SemanticKernel.Agents.Magentic.Internal;
7+
8+
/// <summary>
9+
/// Extension methods for <see cref="PromptExecutionSettings"/> to support response format operations.
10+
/// </summary>
11+
public static class PromptExecutionSettingsExtensions
12+
{
13+
private const string ResponseFormatPropertyName = "ResponseFormat";
14+
15+
/// <summary>
16+
/// Determines whether the <paramref name="settings"/> object supports a "ResponseFormat" property of type <see cref="object"/>.
17+
/// </summary>
18+
/// <param name="settings">The <see cref="PromptExecutionSettings"/> instance to check.</param>
19+
/// <returns><c>true</c> if the "ResponseFormat" property exists and is of type <see cref="object"/>; otherwise, <c>false</c>.</returns>
20+
public static bool SupportsResponseFormat(this PromptExecutionSettings settings)
21+
{
22+
Type settingsType = settings.GetType();
23+
PropertyInfo? property = settingsType.GetProperty(ResponseFormatPropertyName);
24+
return property != null && property.PropertyType == typeof(object);
25+
}
26+
27+
/// <summary>
28+
/// Sets the "ResponseFormat" property of the <paramref name="settings"/> object to the specified response type.
29+
/// </summary>
30+
/// <typeparam name="TResponse">The type to set as the response format.</typeparam>
31+
/// <param name="settings">The <see cref="PromptExecutionSettings"/> instance to update.</param>
32+
public static void SetResponseFormat<TResponse>(this PromptExecutionSettings settings)
33+
{
34+
Type settingsType = settings.GetType();
35+
PropertyInfo? property = settingsType.GetProperty(ResponseFormatPropertyName);
36+
property?.SetValue(settings, typeof(TResponse));
37+
}
38+
}

0 commit comments

Comments
 (0)